mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 16:59:35 +01:00
* Add horizontal and vertical support for combining glyphs. * Fix text and auto size behavior. * Add TabWidth property. * Add unit test for WordWrap. * Add MultiLine property and improve more code. * Fix word wrap on MessageBox. * Fix label unit test. * Rename to GetTextFormatterSizeNeededForTextAndHotKey * Proves that TextFormatter.Size not must to have the same View.Bounds.Size. * Fix fails unit tests. * Updates AutoSize document. * Updates MultiLine document. * Removes Application dependency from the TextFormatter class. * Fix Draw XML comment.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -169,6 +168,15 @@ namespace Terminal.Gui {
|
||||
return StringExtensions.ToString (runes);
|
||||
}
|
||||
|
||||
static string ReplaceTABWithSpaces (string str, int tabWidth)
|
||||
{
|
||||
if (tabWidth == 0) {
|
||||
return str.Replace ("\t", "");
|
||||
}
|
||||
|
||||
return str.Replace ("\t", new string (' ', tabWidth));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits all newlines in the <paramref name="text"/> into a list
|
||||
/// and supports both CRLF and LF, preserving the ending newline.
|
||||
@@ -342,13 +350,13 @@ namespace Terminal.Gui {
|
||||
// }
|
||||
//}
|
||||
|
||||
while ((end = start + Math.Max (GetLengthThatFits (runes.GetRange (start, runes.Count - start), width), 1)) < runes.Count) {
|
||||
while ((end = start + GetLengthThatFits (runes.GetRange (start, runes.Count - start), width, tabWidth)) < runes.Count) {
|
||||
while (runes [end].Value != ' ' && end > start)
|
||||
end--;
|
||||
if (end == start)
|
||||
end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width);
|
||||
end = start + GetLengthThatFits (runes.GetRange (end, runes.Count - end), width, tabWidth);
|
||||
var str = StringExtensions.ToString (runes.GetRange (start, end - start));
|
||||
if (end > start && str.GetColumns () <= width) {
|
||||
if (end > start && GetRuneWidth (str, tabWidth) <= width) {
|
||||
lines.Add (str);
|
||||
start = end;
|
||||
if (runes [end].Value == ' ') {
|
||||
@@ -368,7 +376,17 @@ namespace Terminal.Gui {
|
||||
if (end == start) {
|
||||
end = start + width;
|
||||
}
|
||||
lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start)));
|
||||
var zeroLength = 0;
|
||||
for (int i = end; i < runes.Count - start; i++) {
|
||||
var r = runes [i];
|
||||
if (r.GetColumns () == 0) {
|
||||
zeroLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start + zeroLength)));
|
||||
end += zeroLength;
|
||||
start = end;
|
||||
if (runes [end].Value == ' ') {
|
||||
start++;
|
||||
@@ -427,7 +445,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
if (start < text.GetRuneCount ()) {
|
||||
var str = StringExtensions.ToString (runes.GetRange (start, runes.Count - start));
|
||||
var str = ReplaceTABWithSpaces (StringExtensions.ToString (runes.GetRange (start, runes.Count - start)), tabWidth);
|
||||
if (IsVerticalDirection (textDirection) || preserveTrailingSpaces || (!preserveTrailingSpaces && str.GetColumns () <= width)) {
|
||||
lines.Add (str);
|
||||
}
|
||||
@@ -443,10 +461,11 @@ namespace Terminal.Gui {
|
||||
/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
|
||||
/// <param name="talign">Alignment.</param>
|
||||
/// <param name="textDirection">The text direction.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>Justified and clipped text.</returns>
|
||||
public static string ClipAndJustify (string text, int width, TextAlignment talign, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
|
||||
public static string ClipAndJustify (string text, int width, TextAlignment talign, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
|
||||
{
|
||||
return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection);
|
||||
return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -456,8 +475,9 @@ namespace Terminal.Gui {
|
||||
/// <param name="width">The number of columns to clip the text to. Text longer than <paramref name="width"/> will be clipped.</param>
|
||||
/// <param name="justify">Justify.</param>
|
||||
/// <param name="textDirection">The text direction.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>Justified and clipped text.</returns>
|
||||
public static string ClipAndJustify (string text, int width, bool justify, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
|
||||
public static string ClipAndJustify (string text, int width, bool justify, TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
|
||||
{
|
||||
if (width < 0) {
|
||||
throw new ArgumentOutOfRangeException ("Width cannot be negative.");
|
||||
@@ -466,19 +486,21 @@ namespace Terminal.Gui {
|
||||
return text;
|
||||
}
|
||||
|
||||
text = ReplaceTABWithSpaces (text, tabWidth);
|
||||
var runes = text.ToRuneList ();
|
||||
int slen = runes.Count;
|
||||
if (slen > width) {
|
||||
if (IsHorizontalDirection (textDirection)) {
|
||||
return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width)));
|
||||
return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
|
||||
} else {
|
||||
return StringExtensions.ToString (runes.GetRange (0, width));
|
||||
var zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0);
|
||||
return StringExtensions.ToString (runes.GetRange (0, width + zeroLength));
|
||||
}
|
||||
} else {
|
||||
if (justify) {
|
||||
return Justify (text, width, ' ', textDirection);
|
||||
} else if (IsHorizontalDirection (textDirection) && text.GetColumns () > width) {
|
||||
return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width)));
|
||||
return Justify (text, width, ' ', textDirection, tabWidth);
|
||||
} else if (IsHorizontalDirection (textDirection) && GetRuneWidth (text, tabWidth) > width) {
|
||||
return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width, tabWidth)));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
@@ -492,8 +514,9 @@ namespace Terminal.Gui {
|
||||
/// <param name="width"></param>
|
||||
/// <param name="spaceChar">Character to replace whitespace and pad with. For debugging purposes.</param>
|
||||
/// <param name="textDirection">The text direction.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>The justified text.</returns>
|
||||
public static string Justify (string text, int width, char spaceChar = ' ', TextDirection textDirection = TextDirection.LeftRight_TopBottom)
|
||||
public static string Justify (string text, int width, char spaceChar = ' ', TextDirection textDirection = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
|
||||
{
|
||||
if (width < 0) {
|
||||
throw new ArgumentOutOfRangeException ("Width cannot be negative.");
|
||||
@@ -502,10 +525,11 @@ namespace Terminal.Gui {
|
||||
return text;
|
||||
}
|
||||
|
||||
text = ReplaceTABWithSpaces (text, tabWidth);
|
||||
var words = text.Split (' ');
|
||||
int textCount;
|
||||
if (IsHorizontalDirection (textDirection)) {
|
||||
textCount = words.Sum (arg => arg.GetColumns ());
|
||||
textCount = words.Sum (arg => GetRuneWidth (arg, tabWidth));
|
||||
} else {
|
||||
textCount = words.Sum (arg => arg.GetRuneCount ());
|
||||
}
|
||||
@@ -532,7 +556,7 @@ namespace Terminal.Gui {
|
||||
return s.ToString ();
|
||||
}
|
||||
|
||||
static char [] whitespace = new char [] { ' ', '\t' };
|
||||
//static char [] whitespace = new char [] { ' ', '\t' };
|
||||
|
||||
/// <summary>
|
||||
/// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries.
|
||||
@@ -546,6 +570,7 @@ namespace Terminal.Gui {
|
||||
/// If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <param name="textDirection">The text direction.</param>
|
||||
/// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
|
||||
/// <returns>A list of word wrapped lines.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -558,9 +583,9 @@ namespace Terminal.Gui {
|
||||
/// If <paramref name="width"/> is int.MaxValue, the text will be formatted to the maximum width possible.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static List<string> Format (string text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
|
||||
public static List<string> Format (string text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false)
|
||||
{
|
||||
return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection);
|
||||
return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth, textDirection, multiLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -575,6 +600,7 @@ namespace Terminal.Gui {
|
||||
/// If <see langword="false"/>, trailing spaces at the end of wrapped lines will be trimmed.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <param name="textDirection">The text direction.</param>
|
||||
/// <param name="multiLine">If <see langword="true"/> new lines are allowed.</param>
|
||||
/// <returns>A list of word wrapped lines.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -588,7 +614,7 @@ namespace Terminal.Gui {
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static List<string> Format (string text, int width, bool justify, bool wordWrap,
|
||||
bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
|
||||
bool preserveTrailingSpaces = false, int tabWidth = 0, TextDirection textDirection = TextDirection.LeftRight_TopBottom, bool multiLine = false)
|
||||
{
|
||||
if (width < 0) {
|
||||
throw new ArgumentOutOfRangeException ("width cannot be negative");
|
||||
@@ -600,10 +626,27 @@ namespace Terminal.Gui {
|
||||
return lineResult;
|
||||
}
|
||||
|
||||
if (wordWrap == false) {
|
||||
text = ReplaceCRLFWithSpace (text);
|
||||
lineResult.Add (ClipAndJustify (text, width, justify, textDirection));
|
||||
return lineResult;
|
||||
if (!wordWrap) {
|
||||
text = ReplaceTABWithSpaces (text, tabWidth);
|
||||
if (multiLine) {
|
||||
string [] lines = null;
|
||||
if (text.Contains ("\r\n")) {
|
||||
lines = text.Split ("\r\n");
|
||||
} else if (text.Contains ('\n')) {
|
||||
lines = text.Split ('\n');
|
||||
}
|
||||
if (lines == null) {
|
||||
lines = new [] { text };
|
||||
}
|
||||
foreach (var line in lines) {
|
||||
lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
|
||||
}
|
||||
return lineResult;
|
||||
} else {
|
||||
text = ReplaceCRLFWithSpace (text);
|
||||
lineResult.Add (ClipAndJustify (text, width, justify, textDirection, tabWidth));
|
||||
return lineResult;
|
||||
}
|
||||
}
|
||||
|
||||
var runes = StripCRLF (text, true).ToRuneList ();
|
||||
@@ -614,7 +657,7 @@ namespace Terminal.Gui {
|
||||
if (c.Value == '\n') {
|
||||
var wrappedLines = WordWrapText (StringExtensions.ToString (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth, textDirection);
|
||||
foreach (var line in wrappedLines) {
|
||||
lineResult.Add (ClipAndJustify (line, width, justify, textDirection));
|
||||
lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
|
||||
}
|
||||
if (wrappedLines.Count == 0) {
|
||||
lineResult.Add (string.Empty);
|
||||
@@ -623,7 +666,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
foreach (var line in WordWrapText (StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) {
|
||||
lineResult.Add (ClipAndJustify (line, width, justify, textDirection));
|
||||
lineResult.Add (ClipAndJustify (line, width, justify, textDirection, tabWidth));
|
||||
}
|
||||
|
||||
return lineResult;
|
||||
@@ -648,13 +691,14 @@ namespace Terminal.Gui {
|
||||
/// <returns>Width of the longest line after formatting the text constrained by <paramref name="maxColumns"/>.</returns>
|
||||
/// <param name="text">Text, may contain newlines.</param>
|
||||
/// <param name="maxColumns">The number of columns to constrain the text to for formatting.</param>
|
||||
public static int MaxWidth (string text, int maxColumns)
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
public static int MaxWidth (string text, int maxColumns, int tabWidth = 0)
|
||||
{
|
||||
var result = TextFormatter.Format (text: text, width: maxColumns, justify: false, wordWrap: true);
|
||||
var max = 0;
|
||||
result.ForEach (s => {
|
||||
var m = 0;
|
||||
s.ToRuneList ().ForEach (r => m += Math.Max (r.GetColumns (), 1));
|
||||
s.ToRuneList ().ForEach (r => m += GetRuneWidth (r, tabWidth));
|
||||
if (m > max) {
|
||||
max = m;
|
||||
}
|
||||
@@ -667,11 +711,12 @@ namespace Terminal.Gui {
|
||||
/// <paramref name="text"/> if it contains newlines.
|
||||
/// </summary>
|
||||
/// <param name="text">Text, may contain newlines.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>The length of the longest line.</returns>
|
||||
public static int MaxWidthLine (string text)
|
||||
public static int MaxWidthLine (string text, int tabWidth = 0)
|
||||
{
|
||||
var result = TextFormatter.SplitNewLine (text);
|
||||
return result.Max (x => x.GetColumns ());
|
||||
return result.Max (x => GetRuneWidth (x, tabWidth));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -681,14 +726,15 @@ namespace Terminal.Gui {
|
||||
/// <param name="lines">The lines.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>The maximum characters width.</returns>
|
||||
public static int GetSumMaxCharWidth (List<string> lines, int startIndex = -1, int length = -1)
|
||||
public static int GetSumMaxCharWidth (List<string> lines, int startIndex = -1, int length = -1, int tabWidth = 0)
|
||||
{
|
||||
var max = 0;
|
||||
for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? lines.Count : startIndex + length); i++) {
|
||||
var runes = lines [i];
|
||||
if (runes.Length > 0)
|
||||
max += runes.EnumerateRunes ().Max (r => Math.Max (r.GetColumns (), 1));
|
||||
max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r, tabWidth));
|
||||
}
|
||||
return max;
|
||||
}
|
||||
@@ -700,13 +746,14 @@ namespace Terminal.Gui {
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>The maximum characters width.</returns>
|
||||
public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1)
|
||||
public static int GetSumMaxCharWidth (string text, int startIndex = -1, int length = -1, int tabWidth = 0)
|
||||
{
|
||||
var max = 0;
|
||||
var runes = text.ToRunes ();
|
||||
for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) {
|
||||
max += Math.Max (runes [i].GetColumns (), 1);
|
||||
max += GetRuneWidth (runes [i], tabWidth);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
@@ -716,16 +763,18 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="columns">The width.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>The index of the text that fit the width.</returns>
|
||||
public static int GetLengthThatFits (string text, int columns) => GetLengthThatFits (text?.ToRuneList (), columns);
|
||||
public static int GetLengthThatFits (string text, int columns, int tabWidth = 0) => GetLengthThatFits (text?.ToRuneList (), columns, tabWidth);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of the Runes in a list of Runes that will fit in <paramref name="columns"/>.
|
||||
/// </summary>
|
||||
/// <param name="runes">The list of runes.</param>
|
||||
/// <param name="columns">The width.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>The index of the last Rune in <paramref name="runes"/> that fit in <paramref name="columns"/>.</returns>
|
||||
public static int GetLengthThatFits (List<Rune> runes, int columns)
|
||||
public static int GetLengthThatFits (List<Rune> runes, int columns, int tabWidth = 0)
|
||||
{
|
||||
if (runes == null || runes.Count == 0) {
|
||||
return 0;
|
||||
@@ -734,7 +783,7 @@ namespace Terminal.Gui {
|
||||
var runesLength = 0;
|
||||
var runeIdx = 0;
|
||||
for (; runeIdx < runes.Count; runeIdx++) {
|
||||
var runeWidth = Math.Max (runes [runeIdx].GetColumns (), 1);
|
||||
var runeWidth = GetRuneWidth (runes [runeIdx], tabWidth);
|
||||
if (runesLength + runeWidth > columns) {
|
||||
break;
|
||||
}
|
||||
@@ -743,20 +792,44 @@ namespace Terminal.Gui {
|
||||
return runeIdx;
|
||||
}
|
||||
|
||||
private static int GetRuneWidth (string str, int tabWidth)
|
||||
{
|
||||
return GetRuneWidth (str.EnumerateRunes ().ToList (), tabWidth);
|
||||
}
|
||||
|
||||
private static int GetRuneWidth (List<Rune> runes, int tabWidth)
|
||||
{
|
||||
return runes.Sum (r => GetRuneWidth (r, tabWidth));
|
||||
}
|
||||
|
||||
private static int GetRuneWidth (Rune rune, int tabWidth)
|
||||
{
|
||||
var runeWidth = rune.GetColumns ();
|
||||
if (rune.Value == '\t') {
|
||||
return tabWidth;
|
||||
}
|
||||
if (runeWidth < 0 || runeWidth > 0) {
|
||||
return Math.Max (runeWidth, 1);
|
||||
}
|
||||
|
||||
return runeWidth;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index position from the list based on the <paramref name="width"/>.
|
||||
/// </summary>
|
||||
/// <param name="lines">The lines.</param>
|
||||
/// <param name="width">The width.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns>The index of the list that fit the width.</returns>
|
||||
public static int GetMaxColsForWidth (List<string> lines, int width)
|
||||
public static int GetMaxColsForWidth (List<string> lines, int width, int tabWidth = 0)
|
||||
{
|
||||
var runesLength = 0;
|
||||
var lineIdx = 0;
|
||||
for (; lineIdx < lines.Count; lineIdx++) {
|
||||
var runes = lines [lineIdx].ToRuneList ();
|
||||
var maxRruneWidth = runes.Count > 0
|
||||
? runes.Max (r => Math.Max (r.GetColumns (), 1)) : 1;
|
||||
? runes.Max (r => GetRuneWidth (r, tabWidth)) : 1;
|
||||
if (runesLength + maxRruneWidth > width) {
|
||||
break;
|
||||
}
|
||||
@@ -772,8 +845,9 @@ namespace Terminal.Gui {
|
||||
/// <param name="y">The y location of the rectangle</param>
|
||||
/// <param name="text">The text to measure</param>
|
||||
/// <param name="direction">The text direction.</param>
|
||||
/// <param name="tabWidth">The number of columns used for a tab.</param>
|
||||
/// <returns></returns>
|
||||
public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom)
|
||||
public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
|
||||
{
|
||||
if (string.IsNullOrEmpty (text)) {
|
||||
return new Rect (new Point (x, y), Size.Empty);
|
||||
@@ -795,9 +869,16 @@ namespace Terminal.Gui {
|
||||
cols = 0;
|
||||
} else if (rune.Value != '\r') {
|
||||
cols++;
|
||||
var rw = ((Rune)rune).GetColumns ();
|
||||
if (rw > 0) {
|
||||
rw--;
|
||||
var rw = 0;
|
||||
if (rune.Value == '\t') {
|
||||
rw += tabWidth - 1;
|
||||
} else {
|
||||
rw = ((Rune)rune).GetColumns ();
|
||||
if (rw > 0) {
|
||||
rw--;
|
||||
} else if (rw == 0) {
|
||||
cols--;
|
||||
}
|
||||
}
|
||||
cols += rw;
|
||||
}
|
||||
@@ -822,10 +903,18 @@ namespace Terminal.Gui {
|
||||
cw = 1;
|
||||
} else if (rune.Value != '\r') {
|
||||
rows++;
|
||||
var rw = ((Rune)rune).GetColumns ();
|
||||
if (cw < rw) {
|
||||
cw = rw;
|
||||
vw++;
|
||||
var rw = 0;
|
||||
if (rune.Value == '\t') {
|
||||
rw += tabWidth - 1;
|
||||
rows += rw;
|
||||
} else {
|
||||
rw = ((Rune)rune).GetColumns ();
|
||||
if (rw == 0) {
|
||||
rows--;
|
||||
} else if (cw < rw) {
|
||||
cw = rw;
|
||||
vw++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -954,13 +1043,18 @@ namespace Terminal.Gui {
|
||||
#endregion // Static Members
|
||||
|
||||
List<string> _lines = new List<string> ();
|
||||
string _text;
|
||||
string _text = null;
|
||||
TextAlignment _textAlignment;
|
||||
VerticalTextAlignment _textVerticalAlignment;
|
||||
TextDirection _textDirection;
|
||||
Key _hotKey;
|
||||
int _hotKeyPos = -1;
|
||||
Size _size;
|
||||
private bool _autoSize;
|
||||
private bool _preserveTrailingSpaces;
|
||||
private int _tabWidth = 4;
|
||||
private bool _wordWrap = true;
|
||||
private bool _multiLine;
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when the <see cref="HotKey"/> is changed.
|
||||
@@ -973,15 +1067,18 @@ namespace Terminal.Gui {
|
||||
public virtual string Text {
|
||||
get => _text;
|
||||
set {
|
||||
_text = value;
|
||||
var textWasNull = _text == null && value != null;
|
||||
_text = EnableNeedsFormat (value);
|
||||
|
||||
if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
|
||||
// Provide a default size (width = length of longest line, height = 1)
|
||||
// TODO: It might makes more sense for the default to be width = length of first line?
|
||||
Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
|
||||
if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) {
|
||||
Size = CalcRect (0, 0, _text, _textDirection, TabWidth).Size;
|
||||
}
|
||||
|
||||
NeedsFormat = true;
|
||||
//if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
|
||||
// // Provide a default size (width = length of longest line, height = 1)
|
||||
// // TODO: It might makes more sense for the default to be width = length of first line?
|
||||
// Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,7 +1088,18 @@ namespace Terminal.Gui {
|
||||
/// <see cref="LayoutStyle.Absolute"/> values and doesn't work with <see cref="LayoutStyle.Computed"/> layout,
|
||||
/// to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
|
||||
/// </summary>
|
||||
public bool AutoSize { get; set; }
|
||||
/// <remarks>
|
||||
/// Auto size is ignored if the <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/> are used.
|
||||
/// </remarks>
|
||||
public bool AutoSize {
|
||||
get => _autoSize;
|
||||
set {
|
||||
_autoSize = EnableNeedsFormat (value);
|
||||
if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
|
||||
Size = CalcRect (0, 0, Text, _textDirection, TabWidth).Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
|
||||
@@ -999,7 +1107,10 @@ namespace Terminal.Gui {
|
||||
/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
|
||||
/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool PreserveTrailingSpaces { get; set; }
|
||||
public bool PreserveTrailingSpaces {
|
||||
get => _preserveTrailingSpaces;
|
||||
set => _preserveTrailingSpaces = EnableNeedsFormat (value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the horizontal text-alignment property.
|
||||
@@ -1007,10 +1118,7 @@ namespace Terminal.Gui {
|
||||
/// <value>The text alignment.</value>
|
||||
public TextAlignment Alignment {
|
||||
get => _textAlignment;
|
||||
set {
|
||||
_textAlignment = value;
|
||||
NeedsFormat = true;
|
||||
}
|
||||
set => _textAlignment = EnableNeedsFormat (value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1019,10 +1127,7 @@ namespace Terminal.Gui {
|
||||
/// <value>The text vertical alignment.</value>
|
||||
public VerticalTextAlignment VerticalAlignment {
|
||||
get => _textVerticalAlignment;
|
||||
set {
|
||||
_textVerticalAlignment = value;
|
||||
NeedsFormat = true;
|
||||
}
|
||||
set => _textVerticalAlignment = EnableNeedsFormat (value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1031,10 +1136,7 @@ namespace Terminal.Gui {
|
||||
/// <value>The text vertical alignment.</value>
|
||||
public TextDirection Direction {
|
||||
get => _textDirection;
|
||||
set {
|
||||
_textDirection = value;
|
||||
NeedsFormat = true;
|
||||
}
|
||||
set => _textDirection = EnableNeedsFormat (value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1097,11 +1199,13 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is not implemented!
|
||||
/// <summary>
|
||||
///
|
||||
/// Allows word wrap the to fit the available container width.
|
||||
/// </summary>
|
||||
public bool WordWrap { get; set; } = false;
|
||||
public bool WordWrap {
|
||||
get => _wordWrap;
|
||||
set => _wordWrap = EnableNeedsFormat (value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the area the text will be constrained to when formatted.
|
||||
@@ -1110,12 +1214,13 @@ namespace Terminal.Gui {
|
||||
/// Does not return the size the formatted text; just the value that was set.
|
||||
/// </remarks>
|
||||
public Size Size {
|
||||
get {
|
||||
return _size;
|
||||
}
|
||||
get => _size;
|
||||
set {
|
||||
_size = value;
|
||||
NeedsFormat = true;
|
||||
if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
|
||||
_size = EnableNeedsFormat (CalcRect (0, 0, Text, _textDirection, TabWidth).Size);
|
||||
} else {
|
||||
_size = EnableNeedsFormat (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,7 +1232,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// The position in the text of the hotkey. The hotkey will be rendered using the hot color.
|
||||
/// </summary>
|
||||
public int HotKeyPos { get => _hotKeyPos; set => _hotKeyPos = value; }
|
||||
public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hotkey. Will be an upper case letter or digit.
|
||||
@@ -1146,7 +1251,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets the cursor position from <see cref="HotKey"/>. If the <see cref="HotKey"/> is defined, the cursor will be positioned over it.
|
||||
/// </summary>
|
||||
public int CursorPosition { get; set; }
|
||||
public int CursorPosition { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size required to hold the formatted text, given the constraints placed by <see cref="Size"/>.
|
||||
@@ -1169,7 +1274,7 @@ namespace Terminal.Gui {
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Upon a 'get' of this property, if the text needs to be formatted (if <see cref="NeedsFormat"/> is <c>true</c>)
|
||||
/// <see cref="Format(string, int, bool, bool, bool, int, TextDirection)"/> will be called internally.
|
||||
/// <see cref="Format(string, int, bool, bool, bool, int, TextDirection, bool)"/> will be called internally.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public List<string> Lines {
|
||||
@@ -1192,18 +1297,18 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
if (IsVerticalDirection (_textDirection)) {
|
||||
var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1);
|
||||
_lines = Format (shown_text, Size.Height, _textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth,
|
||||
PreserveTrailingSpaces, 0, _textDirection);
|
||||
var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth);
|
||||
_lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap,
|
||||
PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
|
||||
if (!AutoSize) {
|
||||
colsWidth = GetMaxColsForWidth (_lines, Size.Width);
|
||||
colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth);
|
||||
if (_lines.Count > colsWidth) {
|
||||
_lines.RemoveRange (colsWidth, _lines.Count - colsWidth);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_lines = Format (shown_text, Size.Width, _textAlignment == TextAlignment.Justified, Size.Height > 1,
|
||||
PreserveTrailingSpaces, 0, _textDirection);
|
||||
_lines = Format (shown_text, Size.Width, Alignment == TextAlignment.Justified, Size.Height > 1 && WordWrap,
|
||||
PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
|
||||
if (!AutoSize && _lines.Count > Size.Height) {
|
||||
_lines.RemoveRange (Size.Height, _lines.Count - Size.Height);
|
||||
}
|
||||
@@ -1216,7 +1321,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect, bool)"/> is called.
|
||||
/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/> is called.
|
||||
/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
@@ -1226,6 +1331,33 @@ namespace Terminal.Gui {
|
||||
/// </remarks>
|
||||
public bool NeedsFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of columns used for a tab.
|
||||
/// </summary>
|
||||
public int TabWidth {
|
||||
get => _tabWidth;
|
||||
set => _tabWidth = EnableNeedsFormat (value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether multi line is allowed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Multi line is ignored if <see cref="WordWrap"/> is <see langword="true"/>.
|
||||
/// </remarks>
|
||||
public bool MultiLine {
|
||||
get => _multiLine;
|
||||
set {
|
||||
_multiLine = EnableNeedsFormat (value);
|
||||
}
|
||||
}
|
||||
|
||||
private T EnableNeedsFormat<T> (T value)
|
||||
{
|
||||
NeedsFormat = true;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes the <see cref="TextFormatter"/> to reformat the text.
|
||||
/// </summary>
|
||||
@@ -1235,27 +1367,31 @@ namespace Terminal.Gui {
|
||||
var sb = new StringBuilder ();
|
||||
// Lines_get causes a Format
|
||||
foreach (var line in Lines) {
|
||||
sb.AppendLine (line.ToString ());
|
||||
sb.AppendLine (line);
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the text held by <see cref="TextFormatter"/> to <see cref="Application.Driver"/> using the colors specified.
|
||||
/// Draws the text held by <see cref="TextFormatter"/> to <see cref="ConsoleDriver"/> using the colors specified.
|
||||
/// </summary>
|
||||
/// <param name="bounds">Specifies the screen-relative location and maximum size for drawing the text.</param>
|
||||
/// <param name="normalColor">The color to use for all text except the hotkey</param>
|
||||
/// <param name="hotColor">The color to use to draw the hotkey</param>
|
||||
/// <param name="containerBounds">Specifies the screen-relative location and maximum container size.</param>
|
||||
/// <param name="fillRemaining">Determines if the bounds width will be used (default) or only the text width will be used.</param>
|
||||
public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default, bool fillRemaining = true)
|
||||
/// <param name="driver">The console driver currently used by the application.</param>
|
||||
public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect containerBounds = default, bool fillRemaining = true, ConsoleDriver driver = null)
|
||||
{
|
||||
// With this check, we protect against subclasses with overrides of Text (like Button)
|
||||
if (string.IsNullOrEmpty (_text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Application.Driver?.SetAttribute (normalColor);
|
||||
if (driver == null) {
|
||||
driver = Application.Driver;
|
||||
}
|
||||
driver?.SetAttribute (normalColor);
|
||||
|
||||
// Use "Lines" to ensure a Format (don't use "lines"))
|
||||
|
||||
@@ -1271,7 +1407,7 @@ namespace Terminal.Gui {
|
||||
|
||||
var isVertical = IsVerticalDirection (_textDirection);
|
||||
var maxBounds = bounds;
|
||||
if (Application.Driver != null) {
|
||||
if (driver != null) {
|
||||
maxBounds = containerBounds == default
|
||||
? bounds
|
||||
: new Rect (Math.Max (containerBounds.X, bounds.X),
|
||||
@@ -1316,7 +1452,7 @@ namespace Terminal.Gui {
|
||||
// Horizontal Alignment
|
||||
if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) {
|
||||
if (isVertical) {
|
||||
var runesWidth = GetSumMaxCharWidth (Lines, line);
|
||||
var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
|
||||
x = bounds.Right - runesWidth;
|
||||
CursorPosition = bounds.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
|
||||
} else {
|
||||
@@ -1326,7 +1462,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
} else if (_textAlignment == TextAlignment.Left || _textAlignment == TextAlignment.Justified) {
|
||||
if (isVertical) {
|
||||
var runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line) : 0;
|
||||
var runesWidth = line > 0 ? GetSumMaxCharWidth (Lines, 0, line, TabWidth) : 0;
|
||||
x = bounds.Left + runesWidth;
|
||||
} else {
|
||||
x = bounds.Left;
|
||||
@@ -1334,7 +1470,7 @@ namespace Terminal.Gui {
|
||||
CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
|
||||
} else if (_textAlignment == TextAlignment.Centered) {
|
||||
if (isVertical) {
|
||||
var runesWidth = GetSumMaxCharWidth (Lines, line);
|
||||
var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
|
||||
x = bounds.Left + line + ((bounds.Width - runesWidth) / 2);
|
||||
CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0);
|
||||
} else {
|
||||
@@ -1375,46 +1511,94 @@ namespace Terminal.Gui {
|
||||
var start = isVertical ? bounds.Top : bounds.Left;
|
||||
var size = isVertical ? bounds.Height : bounds.Width;
|
||||
var current = start + colOffset;
|
||||
List<Point?> lastZeroWidthPos = null;
|
||||
Rune rune = default;
|
||||
Rune lastRuneUsed;
|
||||
var zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
|
||||
|
||||
for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size; idx++) {
|
||||
if (idx < 0 || x + current + colOffset < 0) {
|
||||
current++;
|
||||
continue;
|
||||
} else if (!fillRemaining && idx > runes.Length - 1) {
|
||||
break;
|
||||
for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size + zeroLengthCount; idx++) {
|
||||
lastRuneUsed = rune;
|
||||
if (lastZeroWidthPos == null) {
|
||||
if (idx < 0 || x + current + colOffset < 0) {
|
||||
current++;
|
||||
continue;
|
||||
} else if (!fillRemaining && idx > runes.Length - 1) {
|
||||
break;
|
||||
}
|
||||
if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
|
||||
|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) {
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
|
||||
|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
|
||||
//if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
|
||||
// || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
|
||||
|
||||
break;
|
||||
// break;
|
||||
|
||||
var rune = (Rune)' ';
|
||||
rune = (Rune)' ';
|
||||
if (isVertical) {
|
||||
Application.Driver?.Move (x, current);
|
||||
if (idx >= 0 && idx < runes.Length) {
|
||||
rune = runes [idx];
|
||||
}
|
||||
if (lastZeroWidthPos == null) {
|
||||
driver?.Move (x, current);
|
||||
} else {
|
||||
var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
|
||||
if (foundIdx > -1) {
|
||||
if (rune.IsCombiningMark ()) {
|
||||
lastZeroWidthPos [foundIdx] = (new Point (lastZeroWidthPos [foundIdx].Value.X + 1, current));
|
||||
|
||||
driver?.Move (lastZeroWidthPos [foundIdx].Value.X, current);
|
||||
} else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ()) {
|
||||
current++;
|
||||
driver?.Move (x, current);
|
||||
} else {
|
||||
driver?.Move (x, current);
|
||||
}
|
||||
} else {
|
||||
driver?.Move (x, current);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Application.Driver?.Move (current, y);
|
||||
driver?.Move (current, y);
|
||||
if (idx >= 0 && idx < runes.Length) {
|
||||
rune = runes [idx];
|
||||
}
|
||||
}
|
||||
|
||||
var runeWidth = GetRuneWidth (rune, TabWidth);
|
||||
|
||||
if (HotKeyPos > -1 && idx == HotKeyPos) {
|
||||
if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) ||
|
||||
(!isVertical && _textAlignment == TextAlignment.Justified)) {
|
||||
CursorPosition = idx - start;
|
||||
}
|
||||
Application.Driver?.SetAttribute (hotColor);
|
||||
Application.Driver?.AddRune (rune);
|
||||
Application.Driver?.SetAttribute (normalColor);
|
||||
driver?.SetAttribute (hotColor);
|
||||
driver?.AddRune (rune);
|
||||
driver?.SetAttribute (normalColor);
|
||||
} else {
|
||||
Application.Driver?.AddRune (rune);
|
||||
if (isVertical) {
|
||||
if (runeWidth == 0) {
|
||||
if (lastZeroWidthPos == null) {
|
||||
lastZeroWidthPos = new List<Point?> ();
|
||||
}
|
||||
var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
|
||||
if (foundIdx == -1) {
|
||||
current--;
|
||||
lastZeroWidthPos.Add ((new Point (x + 1, current)));
|
||||
}
|
||||
driver?.Move (x + 1, current);
|
||||
}
|
||||
}
|
||||
|
||||
driver?.AddRune (rune);
|
||||
}
|
||||
// BUGBUG: I think this is a bug. If rune is a combining mark current should not be incremented.
|
||||
var runeWidth = rune.GetColumns (); //Math.Max (rune.GetColumns (), 1);
|
||||
|
||||
if (isVertical) {
|
||||
current++;
|
||||
if (runeWidth > 0) {
|
||||
current++;
|
||||
}
|
||||
} else {
|
||||
current += runeWidth;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user