diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs
index f6f96c495..18b7abd8f 100644
--- a/Terminal.Gui/Core/View.cs
+++ b/Terminal.Gui/Core/View.cs
@@ -1,4 +1,4 @@
-//
+//
// Authors:
// Miguel de Icaza (miguel@gnome.org)
//
@@ -13,6 +13,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using NStack;
@@ -132,6 +133,482 @@ namespace Terminal.Gui {
///
///
public class View : Responder, IEnumerable {
+ ///
+ /// Suppports text formatting, including horizontal alignment and word wrap for .
+ ///
+ public class ViewText {
+ List lines = new List ();
+ ustring text;
+ TextAlignment textAlignment;
+ Attribute textColor = -1;
+ View view;
+
+ ///
+ /// Inititalizes a new object.
+ ///
+ ///
+ public ViewText (View view)
+ {
+ this.view = view;
+ recalcPending = true;
+ }
+
+ ///
+ /// The text to be displayed.
+ ///
+ public virtual ustring Text {
+ get => text;
+ set {
+ text = value;
+ recalcPending = true;
+ view.SetNeedsDisplay ();
+ }
+ }
+
+ // TODO: Add Vertical Text Alignment
+ ///
+ /// Controls the horizontal text-alignment property.
+ ///
+ /// The text alignment.
+ public TextAlignment TextAlignment {
+ get => textAlignment;
+ set {
+ textAlignment = value;
+ recalcPending = true;
+ view.SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// The color used for the drawing of the .
+ ///
+ public Attribute TextColor {
+ get => textColor;
+ set {
+ textColor = value;
+ recalcPending = true;
+ view.SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// Gets the size of the area the text will be drawn in.
+ ///
+ public Size TextSize { get; internal set; }
+
+ bool recalcPending = false;
+
+ public int HotKeyPos { get => hotKeyPos; set => hotKeyPos = value; }
+ public Rune HotKey { get => hotKey; set => hotKey = value; }
+ Rune hotKey;
+
+ ///
+ /// The specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'.
+ ///
+ public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF;
+
+ ///
+ /// Causes the Text to be formatted, based on and .
+ ///
+ public void ReFormat ()
+ {
+ // With this check, we protect against subclasses with overrides of Text
+ if (ustring.IsNullOrEmpty (Text)) {
+ return;
+ }
+ recalcPending = false;
+ var shown_text = ProcessHotKeyText (text, HotKeySpecifier, false, out hotKeyPos, out hotKey);
+ Reformat (shown_text, lines, TextSize.Width, textAlignment, TextSize.Height > 1);
+ }
+
+ static ustring StripCRLF (ustring str)
+ {
+ var runes = new List ();
+ foreach (var r in str.ToRunes ()) {
+ if (r != '\r' && r != '\n') {
+ runes.Add (r);
+ }
+ }
+ return ustring.Make (runes); ;
+ }
+ static ustring ReplaceCRLFWithSpace (ustring str)
+ {
+ var runes = new List ();
+ foreach (var r in str.ToRunes ()) {
+ if (r == '\r' || r == '\n') {
+ runes.Add (new Rune (' ')); // r + 0x2400)); // U+25A1 □ WHITE SQUARE
+ } else {
+ runes.Add (r);
+ }
+ }
+ return ustring.Make (runes); ;
+ }
+
+ static List WordWrap (ustring text, int margin)
+ {
+ int start = 0, end;
+ var lines = new List ();
+
+ text = StripCRLF (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 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 = 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} - ");
+ 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;
+ }
+ }
+
+ static char [] whitespace = new char [] { ' ', '\t' };
+ private int hotKeyPos;
+
+ ///
+ /// Reformats text into lines, applying text alignment and word wraping.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// if false, forces text to fit a single line. Line breaks are converted to spaces.
+ static void Reformat (ustring textStr, List lineResult, int width, TextAlignment talign, bool wordWrap)
+ {
+ lineResult.Clear ();
+
+ if (wordWrap == false) {
+ textStr = ReplaceCRLFWithSpace (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') {
+ 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;
+ }
+ }
+ foreach (var line in WordWrap (textStr [lp, textLen], width)) {
+ lineResult.Add (ClipAndJustify (line, width, talign));
+ }
+ }
+
+ ///
+ /// Computes the number of lines needed to render the specified text given the width.
+ ///
+ /// Number of lines.
+ /// Text, may contain newlines.
+ /// The minimum width for the text.
+ public static int MaxLines (ustring text, int width)
+ {
+ var result = new List ();
+ ViewText.Reformat (text, result, width, TextAlignment.Left, true);
+ return result.Count;
+ }
+
+ ///
+ /// Computes the maximum width needed to render the text (single line or multple lines).
+ ///
+ /// Max width of lines.
+ /// Text, may contain newlines.
+ /// The minimum width for the text.
+ public static int MaxWidth (ustring text, int width)
+ {
+ var result = new List ();
+ ViewText.Reformat (text, result, width, TextAlignment.Left, true);
+ return result.Max (s => s.RuneCount);
+ }
+
+ internal void Draw (Rect bounds)
+ {
+ // With this check, we protect against subclasses with overrides of Text
+ if (ustring.IsNullOrEmpty (text)) {
+ return;
+ }
+
+ if (recalcPending) {
+ ReFormat ();
+ }
+
+ if (TextColor != -1)
+ Driver.SetAttribute (TextColor);
+ else
+ Driver.SetAttribute (view.ColorScheme.Normal);
+
+ view.Clear ();
+ for (int line = 0; line < lines.Count; line++) {
+ if (line < bounds.Top || line >= bounds.Bottom)
+ continue;
+ var str = lines [line];
+ int x;
+ switch (textAlignment) {
+ case TextAlignment.Left:
+ x = 0;
+ break;
+ case TextAlignment.Justified:
+ 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 ();
+ }
+ view.Move (x, line);
+ Driver.AddStr (str);
+ }
+
+ if (HotKeyPos != -1) {
+ _ = GetAlignedText (lines [0], TextSize.Width, hotKeyPos, out hotKeyPos, textAlignment);
+
+ view.Move (HotKeyPos, 0);
+ Driver.SetAttribute (view.HasFocus ? view.ColorScheme.HotFocus : view.ColorScheme.HotNormal);
+ Driver.AddRune (hotKey);
+ }
+ }
+
+ ///
+ /// Calculates the rectangle requried to hold text, assuming no word wrapping.
+ ///
+ /// The x location of the rectangle
+ /// The y location of the rectangle
+ /// The text to measure
+ ///
+ public static Rect CalcRect (int x, int y, ustring text)
+ {
+ if (ustring.IsNullOrEmpty (text))
+ return Rect.Empty;
+
+ int mw = 0;
+ int ml = 1;
+
+ int cols = 0;
+ foreach (var rune in text) {
+ 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);
+ }
+
+
+ ///
+ /// Gets the position and Rune for the hotkey in text and removes the hotkey specifier.
+ ///
+ /// The text to manipulate.
+ /// The hot-key specifier (e.g. '_') to look for.
+ /// If true and no hotkey is found via the hotkey specifier, the first upper case char found will be the hotkey.
+ /// Returns the postion of the hot-key in the text. -1 if not found.
+ /// Returns the Rune immediately to the right of the hot-key position
+ /// The input text with the hotkey specifier ('_') removed.
+ public static ustring ProcessHotKeyText (ustring text, Rune hotKeySpecifier, bool firstUpperCase, out int hotPos, out Rune showHotKey)
+ {
+ if (hotKeySpecifier == (Rune)0xFFFF) {
+ hotPos = -1;
+ showHotKey = (Rune)0xFFFF;
+ return text;
+ }
+ Rune hot_key = (Rune)0;
+ int hot_pos = -1;
+ ustring shown_text = text;
+
+ // Use first hot_key char passed into 'hotKey'.
+ // TODO: Ignore hot_key of two are provided
+ int i = 0;
+ foreach (Rune c in shown_text) {
+ if ((char)c != 0xFFFD) {
+ if (c == hotKeySpecifier) {
+ hot_pos = i;
+ } else if (hot_pos > -1) {
+ hot_key = c;
+ break;
+ }
+ }
+ i++;
+ }
+
+ // Legacy support - use first upper case char if the specifier was not found
+ if (hot_pos == -1 && firstUpperCase) {
+ i = 0;
+ foreach (Rune c in shown_text) {
+ if ((char)c != 0xFFFD) {
+ if (Rune.IsUpper (c)) {
+ hot_key = c;
+ hot_pos = i;
+ break;
+ }
+ }
+ i++;
+ }
+ }
+ else {
+ if (hot_pos != -1) {
+ // Use char after 'hotKey'
+ ustring start = "";
+ i = 0;
+ foreach (Rune c in shown_text) {
+ start += ustring.Make (c);
+ i++;
+ if (i == hot_pos)
+ break;
+ }
+ var st = shown_text;
+ shown_text = start;
+ i = 0;
+ foreach (Rune c in st) {
+ i++;
+ if (i > hot_pos + 1) {
+ shown_text += ustring.Make (c);
+ }
+ }
+ }
+ }
+ hotPos = hot_pos;
+ showHotKey = hot_key;
+ return shown_text;
+ }
+
+ ///
+ /// Formats a single line of text with a hot-key and .
+ ///
+ /// The text to align.
+ /// The maximum width for the text.
+ /// The hot-key position before reformatting.
+ /// The hot-key position after reformatting.
+ /// The to align to.
+ /// The aligned text.
+ public static ustring GetAlignedText (ustring shown_text, int width, int hot_pos, out int c_hot_pos, TextAlignment textAlignment)
+ {
+ int start;
+ var caption = shown_text;
+ c_hot_pos = hot_pos;
+
+ if (width > shown_text.Length + 1) {
+ switch (textAlignment) {
+ case TextAlignment.Left:
+ caption += new string (' ', width - caption.RuneCount);
+ break;
+ case TextAlignment.Right:
+ start = width - caption.RuneCount;
+ caption = $"{new string (' ', width - caption.RuneCount)}{caption}";
+ if (c_hot_pos > -1) {
+ c_hot_pos += start;
+ }
+ break;
+ case TextAlignment.Centered:
+ start = width / 2 - caption.RuneCount / 2;
+ caption = $"{new string (' ', start)}{caption}{new string (' ', width - caption.RuneCount - start)}";
+ if (c_hot_pos > -1) {
+ c_hot_pos += start;
+ }
+ break;
+ case TextAlignment.Justified:
+ var words = caption.Split (" ");
+ var wLen = GetWordsLength (words, c_hot_pos, out int runeCount, out int w_hot_pos);
+ var space = (width - runeCount) / (caption.Length - wLen);
+ caption = "";
+ for (int i = 0; i < words.Length; i++) {
+ if (i == words.Length - 1) {
+ caption += new string (' ', width - caption.RuneCount - 1);
+ caption += words [i];
+ } else {
+ caption += words [i];
+ }
+ if (i < words.Length - 1) {
+ caption += new string (' ', space);
+ }
+ }
+ if (c_hot_pos > -1) {
+ c_hot_pos += w_hot_pos * space - space - w_hot_pos + 1;
+ }
+ break;
+ }
+ }
+
+ return caption;
+ }
+
+ static int GetWordsLength (ustring [] words, int hotPos, out int runeCount, out int wordHotPos)
+ {
+ int length = 0;
+ int rCount = 0;
+ int wHotPos = -1;
+ for (int i = 0; i < words.Length; i++) {
+ if (wHotPos == -1 && rCount + words [i].RuneCount >= hotPos)
+ wHotPos = i;
+ length += words [i].Length;
+ rCount += words [i].RuneCount;
+ }
+ if (wHotPos == -1 && hotPos > -1)
+ wHotPos = words.Length;
+ runeCount = rCount;
+ wordHotPos = wHotPos;
+ return length;
+ }
+ }
+
internal enum Direction {
Forward,
Backward
@@ -142,6 +619,8 @@ namespace Terminal.Gui {
View focused = null;
Direction focusDirection;
+ ViewText viewText;
+
///
/// Event fired when the view gets focus.
///
@@ -167,6 +646,27 @@ namespace Terminal.Gui {
///
public Action MouseClick;
+ ///
+ /// The HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
+ ///
+ public Rune HotKey { get => viewText.HotKey; set => viewText.HotKey = value; }
+
+ ///
+ ///
+ ///
+ public Rune HotKeySpecifier { get => viewText.HotKeySpecifier; set => viewText.HotKeySpecifier = value; }
+
+ ///
+ /// Clicked , raised when the user clicks the primary mouse button within the Bounds of this
+ /// or if the user presses the action key while this view is focused. (TODO: IsDefault)
+ ///
+ ///
+ /// Client code can hook up to this event, it is
+ /// raised when the button is activated either with
+ /// the mouse or the keyboard.
+ ///
+ public Action Clicked;
+
internal Direction FocusDirection {
get => SuperView?.FocusDirection ?? focusDirection;
set {
@@ -389,29 +889,99 @@ namespace Terminal.Gui {
///
public View (Rect frame)
{
+ viewText = new ViewText (this);
+ this.Text = ustring.Empty;
+
this.Frame = frame;
- CanFocus = false;
LayoutStyle = LayoutStyle.Absolute;
}
///
- /// Initializes a new instance of class.
+ /// Initializes a new instance of using layout.
///
///
+ ///
/// Use , , , and properties to dynamically control the size and location of the view.
- ///
- ///
+ /// The will be created using
+ /// coordinates. The initial size ( will be
+ /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
+ ///
+ ///
+ /// If Height is greater than one, word wrapping is provided.
+ ///
+ ///
/// This constructor intitalize a View with a of .
/// Use , , , and properties to dynamically control the size and location of the view.
+ ///
///
- public View ()
+ public View () : this (text: string.Empty) { }
+
+
+ ///
+ /// Initializes a new instance of using layout.
+ ///
+ ///
+ ///
+ /// The will be created at the given
+ /// coordinates with the given string. The size ( will be
+ /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
+ ///
+ ///
+ /// No line wrapping is provided.
+ ///
+ ///
+ /// column to locate the Label.
+ /// row to locate the Label.
+ /// text to initialize the property with.
+ public View (int x, int y, ustring text) : this (ViewText.CalcRect (x, y, text), text) { }
+
+ ///
+ /// Initializes a new instance of using layout.
+ ///
+ ///
+ ///
+ /// The will be created at the given
+ /// coordinates with the given string. The initial size ( will be
+ /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
+ ///
+ ///
+ /// If rect.Height is greater than one, word wrapping is provided.
+ ///
+ ///
+ /// Location.
+ /// text to initialize the property with.
+ public View (Rect rect, ustring text) : this (rect)
{
+ viewText = new ViewText (this);
+ this.Text = text;
+ }
+
+ ///
+ /// Initializes a new instance of using layout.
+ ///
+ ///
+ ///
+ /// The will be created using
+ /// coordinates with the given string. The initial size ( will be
+ /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
+ ///
+ ///
+ /// If Height is greater than one, word wrapping is provided.
+ ///
+ ///
+ /// text to initialize the property with.
+ public View (ustring text) : base ()
+ {
+ viewText = new ViewText (this);
+ this.Text = text;
+
CanFocus = false;
LayoutStyle = LayoutStyle.Computed;
+ var r = ViewText.CalcRect (0, 0, text);
x = Pos.At (0);
y = Pos.At (0);
- Height = 0;
- Width = 0;
+ Width = r.Width;
+ Height = r.Height;
}
///
@@ -432,6 +1002,7 @@ namespace Terminal.Gui {
if (SuperView == null)
return;
SuperView.SetNeedsLayout ();
+ viewText.ReFormat ();
}
///
@@ -819,8 +1390,13 @@ namespace Terminal.Gui {
{
if (focused != null)
focused.PositionCursor ();
- else
- Move (frame.X, frame.Y);
+ else {
+ if (CanFocus && HasFocus) {
+ Move (viewText.HotKeyPos == -1 ? 1 : viewText.HotKeyPos, 0);
+ } else {
+ Move (frame.X, frame.Y);
+ }
+ }
}
///
@@ -971,6 +1547,13 @@ namespace Terminal.Gui {
{
var clipRect = new Rect (Point.Empty, frame.Size);
+ Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
+
+ // Draw any Text
+ // TODO: Figure out if this should go here or after OnDrawContent
+ viewText?.ReFormat ();
+ viewText?.Draw (bounds);
+
// Invoke DrawContentEvent
OnDrawContent (bounds);
@@ -1083,7 +1666,6 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent keyEvent)
{
-
KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
KeyPress?.Invoke (args);
if (args.Handled)
@@ -1091,6 +1673,12 @@ namespace Terminal.Gui {
if (Focused?.ProcessKey (keyEvent) == true)
return true;
+ var c = keyEvent.KeyValue;
+ if (c == '\n' || c == ' ' || Rune.ToUpper ((uint)c) == HotKey) {
+ Clicked?.Invoke ();
+ return true;
+ }
+
return false;
}
@@ -1381,7 +1969,7 @@ namespace Terminal.Gui {
}
if (edges.Any ()) {
- if (!object.ReferenceEquals(edges.First ().From, edges.First ().To)) {
+ if (!object.ReferenceEquals (edges.First ().From, edges.First ().To)) {
throw new InvalidOperationException ($"TopologicalSort (for Pos/Dim) cannot find {edges.First ().From}. Did you forget to add it to {this}?");
} else {
throw new InvalidOperationException ("TopologicalSort encountered a recursive cycle in the relative Pos/Dim in the views of " + this);
@@ -1430,6 +2018,9 @@ namespace Terminal.Gui {
if (!layoutNeeded)
return;
+ viewText.TextSize = Bounds.Size;
+ viewText.ReFormat ();
+
Rect oldBounds = Bounds;
// Sort out the dependencies of the X, Y, Width, Height properties
@@ -1471,146 +2062,40 @@ namespace Terminal.Gui {
}
///
- /// A generic virtual method at the level of View to manipulate any hot-keys.
+ /// The text displayed by the .
///
- /// The text to manipulate.
- /// The hot-key to look for.
- /// The returning hot-key position.
- /// The character immediately to the right relative to the hot-key position
- /// It aims to facilitate the preparation for procedures.
- public virtual ustring GetTextFromHotKey (ustring text, Rune hotKey, out int hotPos, out Rune showHotKey)
- {
- Rune hot_key = (Rune)0;
- int hot_pos = -1;
- ustring shown_text = text;
-
- // Use first hot_key char passed into 'hotKey'.
- int i = 0;
- foreach (Rune c in shown_text) {
- if ((char)c != 0xFFFD) {
- if (c == hotKey) {
- hot_pos = i;
- } else if (hot_pos > -1) {
- hot_key = c;
- break;
- }
- }
- i++;
+ ///
+ /// The text will only be displayed if the View has no subviews.
+ ///
+ public virtual ustring Text {
+ get => viewText.Text;
+ set {
+ viewText.Text = value;
+ SetNeedsDisplay ();
}
-
- if (hot_pos == -1) {
- // Use first upper-case char if there are no hot-key in the text.
- i = 0;
- foreach (Rune c in shown_text) {
- if ((char)c != 0xFFFD) {
- if (Rune.IsUpper (c)) {
- hot_key = c;
- hot_pos = i;
- break;
- }
- }
- i++;
- }
- } else {
- // Use char after 'hotKey'
- ustring start = "";
- i = 0;
- foreach (Rune c in shown_text) {
- start += ustring.Make (c);
- i++;
- if (i == hot_pos)
- break;
- }
- var st = shown_text;
- shown_text = start;
- i = 0;
- foreach (Rune c in st) {
- i++;
- if (i > hot_pos + 1) {
- shown_text += ustring.Make (c);
- }
- }
- }
- hotPos = hot_pos;
- showHotKey = hot_key;
- return shown_text;
}
///
- /// A generic virtual method at the level of View to manipulate any hot-keys with process.
+ /// Controls the text-alignment property of the View. Changing this property will redisplay the .
///
- /// The text to manipulate to align.
- /// The passed in hot-key position.
- /// The returning hot-key position.
- /// The to align to.
- /// It performs the process to the caller.
- public virtual ustring GetTextAlignment (ustring shown_text, int hot_pos, out int c_hot_pos, TextAlignment textAlignment)
- {
- int start;
- var caption = shown_text;
- c_hot_pos = hot_pos;
-
- if (Frame.Width > shown_text.Length + 1) {
- switch (textAlignment) {
- case TextAlignment.Left:
- caption += new string (' ', Frame.Width - caption.RuneCount);
- break;
- case TextAlignment.Right:
- start = Frame.Width - caption.RuneCount;
- caption = $"{new string (' ', Frame.Width - caption.RuneCount)}{caption}";
- if (c_hot_pos > -1) {
- c_hot_pos += start;
- }
- break;
- case TextAlignment.Centered:
- start = Frame.Width / 2 - caption.RuneCount / 2;
- caption = $"{new string (' ', start)}{caption}{new string (' ', Frame.Width - caption.RuneCount - start)}";
- if (c_hot_pos > -1) {
- c_hot_pos += start;
- }
- break;
- case TextAlignment.Justified:
- var words = caption.Split (" ");
- var wLen = GetWordsLength (words, c_hot_pos, out int runeCount, out int w_hot_pos);
- var space = (Frame.Width - runeCount) / (caption.Length - wLen);
- caption = "";
- for (int i = 0; i < words.Length; i++) {
- if (i == words.Length - 1) {
- caption += new string (' ', Frame.Width - caption.RuneCount - 1);
- caption += words [i];
- } else {
- caption += words [i];
- }
- if (i < words.Length - 1) {
- caption += new string (' ', space);
- }
- }
- if (c_hot_pos > -1) {
- c_hot_pos += w_hot_pos * space - space - w_hot_pos + 1;
- }
- break;
- }
+ /// The text alignment.
+ public virtual TextAlignment TextAlignment {
+ get => viewText.TextAlignment;
+ set {
+ viewText.TextAlignment = value;
+ SetNeedsDisplay ();
}
-
- return caption;
}
- int GetWordsLength (ustring [] words, int hotPos, out int runeCount, out int wordHotPos)
- {
- int length = 0;
- int rCount = 0;
- int wHotPos = -1;
- for (int i = 0; i < words.Length; i++) {
- if (wHotPos == -1 && rCount + words [i].RuneCount >= hotPos)
- wHotPos = i;
- length += words [i].Length;
- rCount += words [i].RuneCount;
+ ///
+ /// The color used for the .
+ ///
+ public virtual Attribute TextColor {
+ get => viewText.TextColor;
+ set {
+ viewText.TextColor = value;
+ SetNeedsDisplay ();
}
- if (wHotPos == -1 && hotPos > -1)
- wHotPos = words.Length;
- runeCount = rCount;
- wordHotPos = wHotPos;
- return length;
}
///
@@ -1682,6 +2167,16 @@ namespace Terminal.Gui {
if (MouseEvent (mouseEvent))
return true;
+
+ if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+ if (!HasFocus) {
+ SuperView.SetFocus (this);
+ SetNeedsDisplay ();
+ }
+
+ Clicked?.Invoke ();
+ return true;
+ }
return false;
}
}
diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs
index 1cd4be592..d02a6c21f 100644
--- a/Terminal.Gui/Core/Window.cs
+++ b/Terminal.Gui/Core/Window.cs
@@ -2,6 +2,13 @@
// Authors:
// Miguel de Icaza (miguel@gnome.org)
//
+// NOTE: Window is functionally identical to FrameView with the following exceptions.
+// - Window is a Toplevel
+// - FrameView Does not support padding (but should)
+// - FrameView Does not support mouse dragging
+// - FrameView Does not support IEnumerable
+// Any udpates done here should probably be done in FrameView as well; TODO: Merge these classes
+
using System.Collections;
using NStack;
@@ -29,7 +36,6 @@ namespace Terminal.Gui {
}
}
-
///
/// ContentView is an internal implementation detail of Window. It is used to host Views added with .
/// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds
@@ -38,21 +44,6 @@ namespace Terminal.Gui {
class ContentView : View {
public ContentView (Rect frame) : base (frame) { }
public ContentView () : base () { }
-#if false
- public override void Redraw (Rect bounds)
- {
- Driver.SetAttribute (ColorScheme.Focus);
-
- for (int y = 0; y < Frame.Height; y++) {
- Move (0, y);
- for (int x = 0; x < Frame.Width; x++) {
-
- Driver.AddRune ('x');
- }
- }
- base.Redraw (region);
- }
-#endif
}
///
@@ -263,5 +254,39 @@ namespace Terminal.Gui {
//Demo.ml.Text = me.ToString ();
return false;
}
+
+ ///
+ /// The text displayed by the .
+ ///
+ public override ustring Text {
+ get => contentView.Text;
+ set {
+ base.Text = value;
+ if (contentView != null) {
+ contentView.Text = value;
+ }
+ }
+ }
+
+ ///
+ /// Controls the text-alignment property of the label, changing it will redisplay the .
+ ///
+ /// The text alignment.
+ public override TextAlignment TextAlignment {
+ get => contentView.TextAlignment;
+ set {
+ base.TextAlignment = contentView.TextAlignment = value;
+ }
+ }
+
+ ///
+ /// The color used for the .
+ ///
+ public override Attribute TextColor {
+ get => contentView.TextColor;
+ set {
+ base.TextColor = contentView.TextColor = value;
+ }
+ }
}
}
diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs
index c542d3dee..e3acf8933 100644
--- a/Terminal.Gui/Views/Button.cs
+++ b/Terminal.Gui/Views/Button.cs
@@ -26,11 +26,7 @@ namespace Terminal.Gui {
///
public class Button : View {
ustring text;
- ustring shown_text;
- Rune hot_key;
- int hot_pos = -1;
bool is_default;
- TextAlignment textAlignment = TextAlignment.Centered;
///
/// Gets or sets whether the is the default action to activate in a dialog.
@@ -45,16 +41,6 @@ namespace Terminal.Gui {
}
}
- ///
- /// Clicked , raised when the button is clicked.
- ///
- ///
- /// Client code can hook up to this event, it is
- /// raised when the button is activated either with
- /// the mouse or the keyboard.
- ///
- public Action Clicked;
-
///
/// Initializes a new instance of using layout.
///
@@ -120,6 +106,8 @@ namespace Terminal.Gui {
void Init (ustring text, bool is_default)
{
+ HotKeySpecifier = new Rune ('_');
+
_leftBracket = new Rune (Driver != null ? Driver.LeftBracket : '[');
_rightBracket = new Rune (Driver != null ? Driver.RightBracket : ']');
_leftDefault = new Rune (Driver != null ? Driver.LeftDefaultIndicator : '<');
@@ -144,7 +132,7 @@ namespace Terminal.Gui {
///
/// The text displayed by this .
///
- public ustring Text {
+ public new ustring Text {
get {
return text;
}
@@ -156,58 +144,19 @@ namespace Terminal.Gui {
}
}
- ///
- /// Sets or gets the text alignment for the .
- ///
- public TextAlignment TextAlignment {
- get => textAlignment;
- set {
- textAlignment = value;
- Update ();
- }
- }
-
internal void Update ()
{
if (IsDefault)
- shown_text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
+ base.Text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
else
- shown_text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
-
- shown_text = GetTextFromHotKey (shown_text, '_', out hot_pos, out hot_key);
+ base.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
SetNeedsDisplay ();
}
- int c_hot_pos;
-
- ///
- public override void Redraw (Rect bounds)
- {
- Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
- Move (0, 0);
-
- var caption = GetTextAlignment (shown_text, hot_pos, out int s_hot_pos, TextAlignment);
- c_hot_pos = s_hot_pos;
-
- Driver.AddStr (caption);
-
- if (c_hot_pos != -1) {
- Move (c_hot_pos, 0);
- Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
- Driver.AddRune (hot_key);
- }
- }
-
- ///
- public override void PositionCursor ()
- {
- Move (c_hot_pos == -1 ? 1 : c_hot_pos, 0);
- }
-
bool CheckKey (KeyEvent key)
{
- if ((char)key.KeyValue == hot_key) {
+ if ((char)key.KeyValue == HotKey) {
this.SuperView.SetFocus (this);
Clicked?.Invoke ();
return true;
@@ -238,26 +187,12 @@ namespace Terminal.Gui {
public override bool ProcessKey (KeyEvent kb)
{
var c = kb.KeyValue;
- if (c == '\n' || c == ' ' || Rune.ToUpper ((uint)c) == hot_key) {
+ if (c == '\n' || c == ' ' || Rune.ToUpper ((uint)c) == HotKey) {
Clicked?.Invoke ();
return true;
}
return base.ProcessKey (kb);
}
- ///
- public override bool MouseEvent (MouseEvent me)
- {
- if (me.Flags == MouseFlags.Button1Clicked) {
- if (!HasFocus) {
- SuperView.SetFocus (this);
- SetNeedsDisplay ();
- }
-
- Clicked?.Invoke ();
- return true;
- }
- return false;
- }
}
}
diff --git a/Terminal.Gui/Views/Checkbox.cs b/Terminal.Gui/Views/Checkbox.cs
index 80f550548..ebae828ba 100644
--- a/Terminal.Gui/Views/Checkbox.cs
+++ b/Terminal.Gui/Views/Checkbox.cs
@@ -88,7 +88,7 @@ namespace Terminal.Gui {
///
/// The text displayed by this
///
- public ustring Text {
+ public new ustring Text {
get {
return text;
}
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index 99e306412..846d61041 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -270,7 +270,7 @@ namespace Terminal.Gui {
///
/// The currently selected list item
///
- public ustring Text
+ public new ustring Text
{
get
{
diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs
index 5b7f49a4f..b9c49f60d 100644
--- a/Terminal.Gui/Views/FrameView.cs
+++ b/Terminal.Gui/Views/FrameView.cs
@@ -1,12 +1,14 @@
//
-// FrameView.cs: Frame control
-//
// Authors:
// Miguel de Icaza (miguel@gnome.org)
//
-using System;
-using System.Collections;
-using System.Collections.Generic;
+// NOTE: FrameView is functionally identical to Window with the following exceptions.
+// - Is not a Toplevel
+// - Does not support mouse dragging
+// - Does not support padding (but should)
+// - Does not support IEnumerable
+// Any udpates done here should probably be done in Window as well; TODO: Merge these classes
+
using NStack;
namespace Terminal.Gui {
@@ -30,6 +32,11 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// ContentView is an internal implementation detail of Window. It is used to host Views added with .
+ /// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds
+ /// are actually deflated due to the border.
+ ///
class ContentView : View {
public ContentView (Rect frame) : base (frame) { }
public ContentView () : base () { }
@@ -40,7 +47,7 @@ namespace Terminal.Gui {
///
/// Frame.
/// Title.
- public FrameView (Rect frame, ustring title) : base (frame)
+ public FrameView (Rect frame, ustring title = null) : base (frame)
{
var cFrame = new Rect (1, 1, frame.Width - 2, frame.Height - 2);
this.title = title;
@@ -86,6 +93,7 @@ namespace Terminal.Gui {
void Initialize ()
{
base.Add (contentView);
+ contentView.Text = base.Text;
}
void DrawFrame ()
@@ -158,5 +166,40 @@ namespace Terminal.Gui {
Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);
Driver.SetAttribute (ColorScheme.Normal);
}
+
+ ///
+ /// The text displayed by the .
+ ///
+ public override ustring Text {
+ get => contentView.Text;
+ set {
+ base.Text = value;
+ if (contentView != null) {
+ contentView.Text = value;
+ }
+ }
+ }
+
+ ///
+ /// Controls the text-alignment property of the label, changing it will redisplay the .
+ ///
+ /// The text alignment.
+ public override TextAlignment TextAlignment {
+ get => contentView.TextAlignment;
+ set {
+ base.TextAlignment = contentView.TextAlignment = value;
+ }
+ }
+
+ ///
+ /// The color used for the .
+ ///
+ public override Attribute TextColor {
+ get => contentView.TextColor;
+ set {
+ base.TextColor = contentView.TextColor = value;
+ }
+ }
+
}
}
diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs
index 1bfa7a379..117d74f79 100644
--- a/Terminal.Gui/Views/Label.cs
+++ b/Terminal.Gui/Views/Label.cs
@@ -15,350 +15,33 @@ namespace Terminal.Gui {
///
/// The Label displays a string at a given position and supports multiple lines separted by newline characters. Multi-line Labels support word wrap.
///
+ ///
+ /// The view is functionality identical to and is included for API backwards compatibilty.
+ ///
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 using layout.
- ///
- ///
- ///
- /// The will be created at the given
- /// coordinates with the given string. The size ( will be
- /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
- ///
- ///
- /// No line wrapping is provided.
- ///
- ///
- /// column to locate the Label.
- /// row to locate the Label.
- /// text to initialize the property with.
- public Label (int x, int y, ustring text) : this (CalcRect (x, y, text), text)
+ ///
+ public Label ()
{
}
- ///
- /// Initializes a new instance of using layout.
- ///
- ///
- ///
- /// The will be created at the given
- /// coordinates with the given string. The initial size ( will be
- /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
- ///
- ///
- /// If rect.Height is greater than one, word wrapping is provided.
- ///
- ///
- /// Location.
- /// text to initialize the property with.
- public Label (Rect rect, ustring text) : base (rect)
+ ///
+ public Label (Rect frame) : base (frame)
{
- this.text = text;
}
- ///
- /// Initializes a new instance of using layout.
- ///
- ///
- ///
- /// The will be created using
- /// coordinates with the given string. The initial size ( will be
- /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
- ///
- ///
- /// If Height is greater than one, word wrapping is provided.
- ///
- ///
- /// text to initialize the property with.
- public Label (ustring text) : base ()
+ ///
+ public Label (ustring text) : base (text)
{
- this.text = text;
- var r = CalcRect (0, 0, text);
- Width = r.Width;
- Height = r.Height;
}
- ///
- /// Initializes a new instance of using layout.
- ///
- ///
- ///
- /// The will be created using
- /// coordinates. The initial size ( will be
- /// adjusted to fit the contents of , including newlines ('\n') for multiple lines.
- ///
- ///
- /// If Height is greater than one, word wrapping is provided.
- ///
- ///
- public Label () : this (text: string.Empty) { }
-
- static char [] whitespace = new char [] { ' ', '\t' };
-
- static ustring ClipAndJustify (ustring str, int width, TextAlignment talign)
+ ///
+ public Label (Rect rect, ustring text) : base (rect, text)
{
- 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 = 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} - ");
- 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 ()
+ ///
+ public Label (int x, int y, ustring text) : base (x, y, text)
{
- recalcPending = false;
- Recalc (text, lines, Frame.Width, textAlignment, Bounds.Height > 1);
- }
-
- static ustring StripCRLF (ustring str)
- {
- var runes = new List ();
- foreach (var r in str.ToRunes ()) {
- if (r != '\r' && r != '\n') {
- runes.Add (r);
- }
- }
- return ustring.Make (runes); ;
- }
- static ustring ReplaceCRLFWithSpace (ustring str)
- {
- var runes = new List ();
- foreach (var r in str.ToRunes ()) {
- if (r == '\r' || r == '\n') {
- runes.Add (new Rune (' ')); // r + 0x2400)); // U+25A1 □ WHITE SQUARE
- } else {
- runes.Add (r);
- }
- }
- return ustring.Make (runes); ;
- }
-
- static List WordWrap (ustring text, int margin)
- {
- int start = 0, end;
- var lines = new List ();
-
- text = StripCRLF (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 lineResult, int width, TextAlignment talign, bool wordWrap)
- {
- lineResult.Clear ();
-
- if (wordWrap == false) {
- textStr = ReplaceCRLFWithSpace (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') {
- 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;
- }
- }
- foreach (var line in WordWrap (textStr [lp, textLen], width)) {
- lineResult.Add (ClipAndJustify (line, width, talign));
- }
- }
-
- ///
- public override void LayoutSubviews ()
- {
- recalcPending = true;
- }
-
- ///
- public override void Redraw (Rect bounds)
- {
- 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 < bounds.Top || line >= bounds.Bottom)
- continue;
- var str = lines [line];
- int x;
- switch (textAlignment) {
- case TextAlignment.Left:
- x = 0;
- break;
- case TextAlignment.Justified:
- 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, true);
- return result.Count;
- }
-
- ///
- /// Computes 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, true);
- return result.Max (s => s.RuneCount);
- }
-
- ///
- /// Computes the max height of a line or multilines needed to render by the Label control
- ///
- /// Max height of lines.
- /// Text, may contain newlines.
- /// The width for the text.
- public static int MaxHeight (ustring text, int width)
- {
- var result = new List ();
- Recalc (text, result, width, TextAlignment.Left, true);
- return result.Count;
- }
-
- ///
- /// The text displayed by the .
- ///
- public virtual ustring Text {
- get => text;
- set {
- text = value;
- recalcPending = true;
- SetNeedsDisplay ();
- }
- }
-
- ///
- /// Controls the text-alignment 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 ();
- }
}
}
-
}
diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index 7bcd7aaee..50db7b3e2 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -144,7 +144,6 @@ namespace Terminal.Gui {
Driver.AddStr (ustring.Make(new Rune[] { (i == selected ? Driver.Selected : Driver.UnSelected), ' '}));
DrawHotString (radioLabels [i], HasFocus && i == cursor, ColorScheme);
}
- base.Redraw (bounds);
}
///
diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs
index 6f709af3b..82d93a178 100644
--- a/Terminal.Gui/Views/TextField.cs
+++ b/Terminal.Gui/Views/TextField.cs
@@ -63,7 +63,8 @@ namespace Terminal.Gui {
/// Initial text contents.
public TextField (ustring text)
{
- Initialize (text, Frame.Width);
+ Initialize (text, 0);
+ Width = text.Length + 1;
}
///
@@ -122,7 +123,7 @@ namespace Terminal.Gui {
///
///
///
- public ustring Text {
+ public new ustring Text {
get {
return ustring.Make (text);
}
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index b9a09527d..f1d40a9cf 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -326,7 +326,7 @@ namespace Terminal.Gui {
///
///
///
- public ustring Text {
+ public override ustring Text {
get {
return model.ToString ();
}
diff --git a/Terminal.Gui/Windows/FileDialog.cs b/Terminal.Gui/Windows/FileDialog.cs
index 974cfd61a..684565e29 100644
--- a/Terminal.Gui/Windows/FileDialog.cs
+++ b/Terminal.Gui/Windows/FileDialog.cs
@@ -440,7 +440,7 @@ namespace Terminal.Gui {
public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message) : base (title, Driver.Cols - 20, Driver.Rows - 5, null)
{
this.message = new Label (Rect.Empty, "MESSAGE" + message);
- var msgLines = Label.MeasureLines (message, Driver.Cols - 20);
+ var msgLines = ViewText.MaxLines (message, Driver.Cols - 20);
dirLabel = new Label ("Directory: ") {
X = 1,
diff --git a/Terminal.Gui/Windows/MessageBox.cs b/Terminal.Gui/Windows/MessageBox.cs
index ae1834749..70122e934 100644
--- a/Terminal.Gui/Windows/MessageBox.cs
+++ b/Terminal.Gui/Windows/MessageBox.cs
@@ -94,8 +94,8 @@ namespace Terminal.Gui {
static int QueryFull (bool useErrorColors, int width, int height, ustring title, ustring message, params ustring [] buttons)
{
const int defaultWidth = 50;
- int textWidth = Label.MaxWidth (message, width);
- int textHeight = Label.MaxHeight (message, width == 0 ? defaultWidth : width); // message.Count (ustring.Make ('\n')) + 1;
+ int textWidth = View.ViewText.MaxWidth (message, width == 0 ? defaultWidth : width);
+ int textHeight = View.ViewText.MaxLines (message, textWidth); // message.Count (ustring.Make ('\n')) + 1;
int msgboxHeight = Math.Max (1, textHeight) + 3; // textHeight + (top + top padding + buttons + bottom)
// Create button array for Dialog
diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs
index a875c4658..bf6efadb1 100644
--- a/UICatalog/Scenarios/CharacterMap.cs
+++ b/UICatalog/Scenarios/CharacterMap.cs
@@ -24,7 +24,8 @@ namespace UICatalog {
Width = CharMap.RowWidth + 2,
Height = Dim.Fill (),
Start = 0x2500,
- ColorScheme = Colors.Dialog
+ ColorScheme = Colors.Dialog,
+ CanFocus = true,
};
Win.Add (charMap);
diff --git a/UICatalog/Scenarios/LabelsAsButtons.cs b/UICatalog/Scenarios/LabelsAsButtons.cs
new file mode 100644
index 000000000..60351a34d
--- /dev/null
+++ b/UICatalog/Scenarios/LabelsAsButtons.cs
@@ -0,0 +1,271 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Terminal.Gui;
+
+namespace UICatalog {
+ [ScenarioMetadata (Name: "LabelsAsButtons", Description: "POC to see how making Label more a base class would work")]
+ [ScenarioCategory ("Controls")]
+ [ScenarioCategory ("POC")]
+ class LabelsAsButtons : Scenario {
+ public override void Setup ()
+ {
+ // Add a label & text field so we can demo IsDefault
+ var editLabel = new Label ("TextField (to demo IsDefault):") {
+ X = 0,
+ Y = 0,
+ };
+ Win.Add (editLabel);
+ // Add a TextField using Absolute layout.
+ var edit = new TextField (31, 0, 15, "");
+ Win.Add (edit);
+
+ // This is the default button (IsDefault = true); if user presses ENTER in the TextField
+ // the scenario will quit
+ var defaultButton = new Label ("_Quit") {
+ X = Pos.Center (),
+ //TODO: Change to use Pos.AnchorEnd()
+ Y = Pos.Bottom (Win) - 3,
+ //IsDefault = true,
+ Clicked = () => Application.RequestStop (),
+ };
+ Win.Add (defaultButton);
+
+ var swapButton = new Label (50, 0, "Swap Default (Absolute Layout)");
+ swapButton.Clicked = () => {
+ //defaultButton.IsDefault = !defaultButton.IsDefault;
+ //swapButton.IsDefault = !swapButton.IsDefault;
+ };
+ Win.Add (swapButton);
+
+ static void DoMessage (Label button, ustring txt)
+ {
+ button.Clicked = () => {
+ var btnText = button.Text.ToString ();
+ MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
+ };
+ }
+
+ var colorButtonsLabel = new Label ("Color Buttons:") {
+ X = 0,
+ Y = Pos.Bottom (editLabel) + 1,
+ };
+ Win.Add (colorButtonsLabel);
+
+ //View prev = colorButtonsLabel;
+
+ //With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds);
+ var x = Pos.Right (colorButtonsLabel) + 2;
+ foreach (var colorScheme in Colors.ColorSchemes) {
+ var colorButton = new Label ($"{colorScheme.Key}") {
+ ColorScheme = colorScheme.Value,
+ //X = Pos.Right (prev) + 2,
+ X = x,
+ Y = Pos.Y (colorButtonsLabel),
+ };
+ DoMessage (colorButton, colorButton.Text);
+ Win.Add (colorButton);
+ //prev = colorButton;
+ x += colorButton.Frame.Width + 2;
+ }
+ // BUGBUG: For some reason these buttons don't move to correct locations initially.
+ // This was the only way I find to resolves this with the View prev variable.
+ //Top.Ready += () => Top.Redraw (Top.Bounds);
+
+ Label button;
+ Win.Add (button = new Label ("A super long _Button that will probably expose a bug in clipping or wrapping of text. Will it?") {
+ X = 2,
+ Y = Pos.Bottom (colorButtonsLabel) + 1,
+ });
+ DoMessage (button, button.Text);
+
+ // Note the 'N' in 'Newline' will be the hotkey
+ Win.Add (button = new Label ("a Newline\nin the button") {
+ X = 2,
+ Y = Pos.Bottom (button) + 1,
+ Clicked = () => MessageBox.Query ("Message", "Question?", "Yes", "No")
+ });
+
+ var textChanger = new Label ("Te_xt Changer") {
+ X = 2,
+ Y = Pos.Bottom (button) + 1,
+ };
+ Win.Add (textChanger);
+ textChanger.Clicked = () => textChanger.Text += "!";
+
+ Win.Add (button = new Label ("Lets see if this will move as \"Text Changer\" grows") {
+ X = Pos.Right (textChanger) + 2,
+ Y = Pos.Y (textChanger),
+ });
+
+ var removeButton = new Label ("Remove this button") {
+ X = 2,
+ Y = Pos.Bottom (button) + 1,
+ ColorScheme = Colors.Error
+ };
+ Win.Add (removeButton);
+ // This in intresting test case because `moveBtn` and below are laid out relative to this one!
+ removeButton.Clicked = () => Win.Remove (removeButton);
+
+ var computedFrame = new FrameView ("Computed Layout") {
+ X = 0,
+ Y = Pos.Bottom (removeButton) + 1,
+ Width = Dim.Percent (50),
+ Height = 5
+ };
+ Win.Add (computedFrame);
+
+ // Demonstrates how changing the View.Frame property can move Views
+ var moveBtn = new Label ("Move This \u263b Button _via Pos") {
+ X = 0,
+ Y = Pos.Center () - 1,
+ Width = 30,
+ ColorScheme = Colors.Error,
+ };
+ moveBtn.Clicked = () => {
+ moveBtn.X = moveBtn.Frame.X + 5;
+ // This is already fixed with the call to SetNeedDisplay() in the Pos Dim.
+ //computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly
+ };
+ computedFrame.Add (moveBtn);
+
+ // Demonstrates how changing the View.Frame property can SIZE Views (#583)
+ var sizeBtn = new Label ("Size This \u263a Button _via Pos") {
+ X = 0,
+ Y = Pos.Center () + 1,
+ Width = 30,
+ ColorScheme = Colors.Error,
+ };
+ sizeBtn.Clicked = () => {
+ sizeBtn.Width = sizeBtn.Frame.Width + 5;
+ //computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly
+ };
+ computedFrame.Add (sizeBtn);
+
+ var absoluteFrame = new FrameView ("Absolute Layout") {
+ X = Pos.Right (computedFrame),
+ Y = Pos.Bottom (removeButton) + 1,
+ Width = Dim.Fill (),
+ Height = 5
+ };
+ Win.Add (absoluteFrame);
+
+ // Demonstrates how changing the View.Frame property can move Views
+ var moveBtnA = new Label (0, 0, "Move This Button via Frame") {
+ ColorScheme = Colors.Error,
+ };
+ moveBtnA.Clicked = () => {
+ moveBtnA.Frame = new Rect (moveBtnA.Frame.X + 5, moveBtnA.Frame.Y, moveBtnA.Frame.Width, moveBtnA.Frame.Height);
+ };
+ absoluteFrame.Add (moveBtnA);
+
+ // Demonstrates how changing the View.Frame property can SIZE Views (#583)
+ var sizeBtnA = new Label (0, 2, " ~ s gui.cs master ↑10 = Со_хранить") {
+ ColorScheme = Colors.Error,
+ };
+ sizeBtnA.Clicked = () => {
+ sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);
+ };
+ absoluteFrame.Add (sizeBtnA);
+
+ var label = new Label ("Text Alignment (changes the four buttons above): ") {
+ X = 2,
+ Y = Pos.Bottom (computedFrame) + 1,
+ };
+ Win.Add (label);
+
+ var radioGroup = new RadioGroup (new ustring [] { "Left", "Right", "Centered", "Justified" }) {
+ X = 4,
+ Y = Pos.Bottom (label) + 1,
+ SelectedItem = 2,
+ };
+ Win.Add (radioGroup);
+
+ // Demo changing hotkey
+ ustring MoveHotkey (ustring txt)
+ {
+ // Remove the '_'
+ var i = txt.IndexOf ('_');
+ ustring start = "";
+ if (i > -1)
+ start = txt [0, i];
+ txt = start + txt [i + 1, txt.Length];
+
+ // Move over one or go to start
+ i++;
+ if (i >= txt.Length) {
+ i = 0;
+ }
+
+ // Slip in the '_'
+ start = txt [0, i];
+ txt = start + ustring.Make ('_') + txt [i, txt.Length];
+
+ return txt;
+ }
+
+ var mhkb = "Click to Change th_is Button's Hotkey";
+ var moveHotKeyBtn = new Label (mhkb) {
+ X = 2,
+ Y = Pos.Bottom (radioGroup) + 1,
+ Width = mhkb.Length + 10,
+ ColorScheme = Colors.TopLevel,
+ };
+ moveHotKeyBtn.Clicked = () => {
+ moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text);
+ };
+ Win.Add (moveHotKeyBtn);
+
+ var muhkb = " ~ s gui.cs master ↑10 = Сохранить";
+ var moveUnicodeHotKeyBtn = new Label (muhkb) {
+ X = Pos.Left (absoluteFrame) + 1,
+ Y = Pos.Bottom (radioGroup) + 1,
+ Width = muhkb.Length + 30,
+ ColorScheme = Colors.TopLevel,
+ };
+ moveUnicodeHotKeyBtn.Clicked = () => {
+ moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text);
+ };
+ Win.Add (moveUnicodeHotKeyBtn);
+
+ radioGroup.SelectedItemChanged += (args) => {
+ switch (args.SelectedItem) {
+ case 0:
+ moveBtn.TextAlignment = TextAlignment.Left;
+ sizeBtn.TextAlignment = TextAlignment.Left;
+ moveBtnA.TextAlignment = TextAlignment.Left;
+ sizeBtnA.TextAlignment = TextAlignment.Left;
+ moveHotKeyBtn.TextAlignment = TextAlignment.Left;
+ moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
+ break;
+ case 1:
+ moveBtn.TextAlignment = TextAlignment.Right;
+ sizeBtn.TextAlignment = TextAlignment.Right;
+ moveBtnA.TextAlignment = TextAlignment.Right;
+ sizeBtnA.TextAlignment = TextAlignment.Right;
+ moveHotKeyBtn.TextAlignment = TextAlignment.Right;
+ moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
+ break;
+ case 2:
+ moveBtn.TextAlignment = TextAlignment.Centered;
+ sizeBtn.TextAlignment = TextAlignment.Centered;
+ moveBtnA.TextAlignment = TextAlignment.Centered;
+ sizeBtnA.TextAlignment = TextAlignment.Centered;
+ moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
+ moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
+ break;
+ case 3:
+ moveBtn.TextAlignment = TextAlignment.Justified;
+ sizeBtn.TextAlignment = TextAlignment.Justified;
+ moveBtnA.TextAlignment = TextAlignment.Justified;
+ sizeBtnA.TextAlignment = TextAlignment.Justified;
+ moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
+ moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
+ break;
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/MessageBoxes.cs b/UICatalog/Scenarios/MessageBoxes.cs
index 8075ba51a..5f41537ca 100644
--- a/UICatalog/Scenarios/MessageBoxes.cs
+++ b/UICatalog/Scenarios/MessageBoxes.cs
@@ -142,7 +142,7 @@ namespace UICatalog {
ColorScheme = Colors.Error,
};
- var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
+ var btnText = new [] { "_Zero", "_One", "T_wo", "_Three", "_Four", "Fi_ve", "Si_x", "_Seven", "_Eight", "_Nine" };
var showMessageBoxButton = new Button ("Show MessageBox") {
X = Pos.Center(),
diff --git a/UICatalog/Scenarios/ViewWithText.cs b/UICatalog/Scenarios/ViewWithText.cs
new file mode 100644
index 000000000..fd88e9922
--- /dev/null
+++ b/UICatalog/Scenarios/ViewWithText.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog {
+ [ScenarioMetadata (Name: "View Text", Description: "Demos and tests View's Text capabilities.")]
+ [ScenarioCategory ("Text")]
+ [ScenarioCategory ("POC")]
+ class ViewWithText : Scenario {
+ public override void Setup ()
+ {
+ Win.Text = "This is the Te_xt for the host Win object. TextAlignment.Centered was specified. It is intentionally very long to illustrate word wrap.\n" +
+ "<-- There is a new line here to show a hard line break. You should see this text bleed underneath the subviews, which start at Y = 3.";
+ Win.TextAlignment = TextAlignment.Centered;
+ Win.TextColor = Application.Driver.MakeAttribute (Color.BrightGreen, Color.Black);
+#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 ().ToList ();
+ var label = new View ($"Demonstrating single-line (should clip!):") { Y = 3 };
+ Win.Add (label);
+ foreach (var alignment in alignments) {
+ 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,
+ TextColor = Application.Driver.MakeAttribute (Color.BrightRed, Color.White),
+ };
+ Win.Add (label);
+ }
+
+ txt += "\nSecond line\n\nFourth Line.";
+ label = new View ($"Demonstrating multi-line and word wrap:") { Y = Pos.Bottom (label) + 1 };
+ Win.Add (label);
+ foreach (var alignment in alignments) {
+ label = new View ($"{alignment}:") { Y = Pos.Bottom (label) };
+ Win.Add (label);
+ label = new View (txt) { TextAlignment = alignment, Width = Dim.Fill (), Height = 6, ColorScheme = Colors.Dialog, Y = Pos.Bottom (label) };
+ Win.Add (label);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/WindowsAndFrameViews.cs b/UICatalog/Scenarios/WindowsAndFrameViews.cs
index 03b23a639..24264146b 100644
--- a/UICatalog/Scenarios/WindowsAndFrameViews.cs
+++ b/UICatalog/Scenarios/WindowsAndFrameViews.cs
@@ -64,11 +64,11 @@ namespace UICatalog {
X = Pos.Center (),
Y = 0,
ColorScheme = Colors.Error,
- Clicked = () => About()
+ Clicked = () => About ()
});
Win.Add (new Button ("Press ME! (Y = Pos.AnchorEnd(1))") {
X = Pos.Center (),
- Y = Pos.AnchorEnd(1),
+ Y = Pos.AnchorEnd (1),
ColorScheme = Colors.Error
});
Top.Add (Win);
@@ -80,7 +80,7 @@ namespace UICatalog {
X = margin,
Y = Pos.Bottom (listWin.Last ()) + (margin),
Width = Dim.Fill (margin),
- Height = contentHeight + (i*2) + 2,
+ Height = contentHeight + (i * 2) + 2,
};
win.ColorScheme = Colors.Dialog;
win.Add (new Button ("Press me! (Y = 0)") {
@@ -95,8 +95,10 @@ namespace UICatalog {
Width = Dim.Percent (50),
Height = 5,
ColorScheme = Colors.Base,
+ Text = "The Text in the Window",
};
- subWin.Add (new TextField (win.Title.ToString ()) {
+ subWin.Add (new TextField ("Edit me! " + win.Title.ToString ()) {
+ Y = 1,
ColorScheme = Colors.Error
});
win.Add (subWin);
@@ -106,8 +108,12 @@ namespace UICatalog {
Width = Dim.Percent (100),
Height = 5,
ColorScheme = Colors.Base,
+ Text = "The Text in the FrameView",
+
};
- frameView.Add (new TextField ("Edit Me"));
+ frameView.Add (new TextField ("Edit Me!") {
+ Y = 1,
+ });
win.Add (frameView);
Top.Add (win);
@@ -135,6 +141,7 @@ namespace UICatalog {
Width = Dim.Percent (50),
Height = Dim.Fill () - 1,
ColorScheme = Colors.Base,
+ Text = "The Text in the Window",
};
subWinofFV.Add (new TextField ("Edit Me") {
ColorScheme = Colors.Error
@@ -150,8 +157,9 @@ namespace UICatalog {
Width = Dim.Percent (100),
Height = Dim.Fill () - 1,
ColorScheme = Colors.Base,
+ Text = "The Text in the FrameView",
};
- subFrameViewofFV.Add (new TextField ("Edit Me"));
+ subFrameViewofFV.Add (new TextField (0, 0, 15, "Edit Me"));
subFrameViewofFV.Add (new CheckBox (0, 1, "Check me"));
// BUGBUG: This checkbox is not shown even though frameViewFV has 3 rows in
@@ -160,12 +168,12 @@ namespace UICatalog {
frame.Add (new CheckBox ("Btn1 (Y = Pos.AnchorEnd (1))") {
X = 0,
- Y = Pos.AnchorEnd (1),
+ Y = Pos.AnchorEnd (1),
});
CheckBox c = new CheckBox ("Btn2 (Y = Pos.AnchorEnd (1))") {
- Y = Pos.AnchorEnd (1),
+ Y = Pos.AnchorEnd (1),
};
- c.X = Pos.AnchorEnd () - (Pos.Right (c) - Pos.Left (c));
+ c.X = Pos.AnchorEnd () - (Pos.Right (c) - Pos.Left (c));
frame.Add (c);
frame.Add (subFrameViewofFV);
diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs
index f89cbf6d3..43473921b 100644
--- a/UnitTests/ViewTests.cs
+++ b/UnitTests/ViewTests.cs
@@ -25,7 +25,7 @@ namespace Terminal.Gui {
Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
Assert.Null (r.Focused);
Assert.Null (r.ColorScheme);
- Assert.Equal (Dim.Sized (0), r.Height);
+ Assert.Equal (Dim.Sized (0), r.Width);
Assert.Equal (Dim.Sized (0), r.Height);
// BUGBUG: Pos needs eqality implemented
//Assert.Equal (Pos.At (0), r.X);