123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- //
- // Label.cs: Label control
- //
- // Authors:
- // Miguel de Icaza ([email protected])
- //
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using NStack;
- namespace Terminal.Gui {
- /// <summary>
- /// Text alignment enumeration, controls how text is displayed.
- /// </summary>
- public enum TextAlignment {
- /// <summary>
- /// Aligns the text to the left of the frame.
- /// </summary>
- Left,
- /// <summary>
- /// Aligns the text to the right side of the frame.
- /// </summary>
- Right,
- /// <summary>
- /// Centers the text in the frame.
- /// </summary>
- Centered,
- /// <summary>
- /// Shows the text as justified text in the frame.
- /// </summary>
- Justified
- }
- /// <summary>
- /// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separted by newline characters.
- /// </summary>
- 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"/> 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.
- /// </summary>
- public Label (int x, int y, ustring text) : this (CalcRect (x, y, text), text)
- {
- }
- /// <summary>
- /// Initializes a new instance of <see cref="Label"/> at the given
- /// coordinate with the given string and uses the specified
- /// frame for the string.
- /// </summary>
- public Label (Rect rect, ustring text) : base (rect)
- {
- this.text = text;
- }
- /// <summary>
- /// Initializes a new instance of <see cref="Label"/> and configures the default Width and Height based on the text, the result is suitable for Computed layout.
- /// </summary>
- /// <param name="text">Text.</param>
- 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<ustring> 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));
- }
- ///<inheritdoc cref="Redraw"/>
- 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);
- }
- }
- /// <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);
- return result.Count;
- }
- /// <summary>
- /// Computes the 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);
- return result.Max(s => s.RuneCount);
- }
- /// <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-alignemtn 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 ();
- }
- }
- }
- }
|