using static Terminal.Gui.SpinnerStyle;
namespace Terminal.Gui;
public partial class View
{
private string _text;
///
/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
/// or not when is enabled.
/// If trailing spaces at the end of wrapped lines will be removed when
/// is formatted for display. The default is .
///
public virtual bool PreserveTrailingSpaces
{
get => TextFormatter.PreserveTrailingSpaces;
set
{
if (TextFormatter.PreserveTrailingSpaces != value)
{
TextFormatter.PreserveTrailingSpaces = value;
TextFormatter.NeedsFormat = true;
}
}
}
///
/// The text displayed by the .
///
///
///
/// The text will be drawn before any subviews are drawn.
///
///
/// The text will be drawn starting at the view origin (0, 0) and will be formatted according
/// to and .
///
///
/// The text will word-wrap to additional lines if it does not fit horizontally. If 's height
/// is 1, the text will be clipped.
///
/// If is true, the will be adjusted to fit the text.
/// When the text changes, the is fired.
///
public virtual string Text
{
get => _text;
set
{
if (value == _text)
{
return;
}
string old = _text;
_text = value;
UpdateTextFormatterText ();
OnResizeNeeded ();
#if DEBUG
if (_text is { } && string.IsNullOrEmpty (Id))
{
Id = _text;
}
#endif
OnTextChanged (old, Text);
}
}
///
/// Called when the has changed. Fires the event.
///
///
///
public void OnTextChanged (string oldValue, string newValue)
{
TextChanged?.Invoke (this, new StateEventArgs (oldValue, newValue));
}
///
/// Text changed event, raised when the text has changed.
///
public event EventHandler> TextChanged;
///
/// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will
/// redisplay the .
///
///
/// If is true, the will be adjusted to fit the text.
///
/// The text alignment.
public virtual TextAlignment TextAlignment
{
get => TextFormatter.Alignment;
set
{
TextFormatter.Alignment = value;
UpdateTextFormatterText ();
OnResizeNeeded ();
}
}
///
/// Gets or sets the direction of the View's . Changing this property will redisplay the
/// .
///
///
/// If is true, the will be adjusted to fit the text.
///
/// The text alignment.
public virtual TextDirection TextDirection
{
get => TextFormatter.Direction;
set
{
UpdateTextDirection (value);
TextFormatter.Direction = value;
}
}
///
/// Gets or sets the used to format .
///
public TextFormatter TextFormatter { get; init; } = new ();
///
/// Gets or sets how the View's is aligned vertically when drawn. Changing this property will
/// redisplay
/// the .
///
///
/// If is true, the will be adjusted to fit the text.
///
/// The text alignment.
public virtual VerticalTextAlignment VerticalTextAlignment
{
get => TextFormatter.VerticalAlignment;
set
{
TextFormatter.VerticalAlignment = value;
SetNeedsDisplay ();
}
}
///
/// Gets the width or height of the characters
/// in the property.
///
///
/// Only the first HotKey specifier found in is supported.
///
///
/// If (the default) the width required for the HotKey specifier is returned. Otherwise the
/// height
/// is returned.
///
///
/// The number of characters required for the . If the text
/// direction specified
/// by does not match the parameter, 0 is returned.
///
public int GetHotKeySpecifierLength (bool isWidth = true)
{
if (isWidth)
{
return TextFormatter.IsHorizontalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
? Math.Max (HotKeySpecifier.GetColumns (), 0)
: 0;
}
return TextFormatter.IsVerticalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
? Math.Max (HotKeySpecifier.GetColumns (), 0)
: 0;
}
// BUGBUG: This API is fundamentally broken. Text autosize should have nothing to do with what's visible (Viewport) and only ContentSize.
///
/// Gets the Frame dimensions required to fit within using the text
/// specified by the property and accounting for any
/// characters.
///
///
///
/// The of the required to fit the formatted text.
public Size GetTextAutoSize ()
{
var x = 0;
var y = 0;
if (IsInitialized)
{
x = Viewport.X;
y = Viewport.Y;
}
Rectangle rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
int newWidth = rect.Size.Width
- GetHotKeySpecifierLength ()
+ (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
int newHeight = rect.Size.Height
- GetHotKeySpecifierLength (false)
+ (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
return new (newWidth, newHeight);
}
///
/// Can be overridden if the has
/// different format than the default.
///
protected virtual void UpdateTextFormatterText ()
{
if (TextFormatter is { })
{
TextFormatter.Text = _text;
}
}
///
/// Gets the dimensions required for ignoring a .
///
///
internal Size GetSizeNeededForTextWithoutHotKey ()
{
return new Size (
TextFormatter.Size.Width - GetHotKeySpecifierLength (),
TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
}
///
/// Internal API. Sets .Size to the current size, adjusted for
/// .
///
///
/// Use this API to set when the view has changed such that the
/// size required to fit the text has changed.
/// changes.
///
///
internal void SetTextFormatterSize ()
{
if (!IsInitialized)
{
TextFormatter.Size = Size.Empty;
return;
}
if (string.IsNullOrEmpty (TextFormatter.Text))
{
TextFormatter.Size = ContentSize;
return;
}
int w = Viewport.Size.Width + GetHotKeySpecifierLength ();
// TODO: This is a hack. Figure out how to move this into DimDimAuto
if (Width is Dim.DimAuto widthAuto && widthAuto._style != Dim.DimAutoStyle.Subviews)
{
if (Height is Dim.DimAuto)
{
// Both are auto.
TextFormatter.Size = new Size (SuperView?.ContentSize.Width ?? 0, SuperView?.ContentSize.Height ?? 0);
}
else
{
TextFormatter.Size = new Size (SuperView?.ContentSize.Width ?? 0, ContentSize.Height + GetHotKeySpecifierLength ());
}
w = TextFormatter.FormatAndGetSize ().Width;
}
else
{
TextFormatter.Size = new Size (w, SuperView?.Viewport.Height ?? 0);
}
int h = ContentSize.Height + GetHotKeySpecifierLength ();
// TODO: This is a hack. Figure out how to move this into DimDimAuto
if (Height is Dim.DimAuto heightAuto && heightAuto._style != Dim.DimAutoStyle.Subviews)
{
TextFormatter.NeedsFormat = true;
h = TextFormatter.FormatAndGetSize ().Height;
}
TextFormatter.Size = new Size (w, h);
}
//private bool IsValidAutoSize (out Size autoSize)
//{
// Rectangle rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
// autoSize = new Size (
// rect.Size.Width - GetHotKeySpecifierLength (),
// rect.Size.Height - GetHotKeySpecifierLength (false));
// return !((ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)))
// || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength ()
// || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false));
//}
private bool IsValidAutoSizeHeight (Dim height)
{
Rectangle rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
int dimValue = height.Anchor (0);
return !((ValidatePosDim && !(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false));
}
private bool IsValidAutoSizeWidth (Dim width)
{
Rectangle rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
int dimValue = width.Anchor (0);
return !((ValidatePosDim && !(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ());
}
/////
///// Sets the size of the View to the minimum width or height required to fit .
/////
/////
///// if the size was changed; if ==
///// or
///// will not fit.
/////
/////
///// Always returns if is or
///// if (Horizontal) or (Vertical) are not not set or zero.
///// Does not take into account word wrapping.
/////
//private bool SetFrameToFitText ()
//{
// if (AutoSize == false)
// {
// throw new InvalidOperationException ("SetFrameToFitText can only be called when AutoSize is true");
// }
// // BUGBUG: This API is broken - should not assume Frame.Height == ContentSize.Height
// //
// // Gets the minimum dimensions required to fit the View's , factoring in .
// //
// // The minimum dimensions required.
// // if the dimensions fit within the View's , otherwise.
// //
// // Always returns if is or
// // if (Horizontal) or (Vertical) are not not set or zero.
// // Does not take into account word wrapping.
// //
// bool GetMinimumSizeOfText (out Size sizeRequired)
// {
// if (!IsInitialized)
// {
// sizeRequired = Size.Empty;
// return false;
// }
// sizeRequired = ContentSize;
// if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text))
// {
// return false;
// }
// switch (TextFormatter.IsVerticalDirection (TextDirection))
// {
// case true:
// int colWidth = TextFormatter.GetSumMaxCharWidth (TextFormatter.Text, 0, 1);
// // TODO: v2 - This uses frame.Width; it should only use ContentSize
// if (_frame.Width < colWidth
// && (Width is null || (ContentSize.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth)))
// {
// sizeRequired = new (colWidth, ContentSize.Height);
// return true;
// }
// break;
// default:
// if (_frame.Height < 1 && (Height is null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0)))
// {
// sizeRequired = new (ContentSize.Width, 1);
// return true;
// }
// break;
// }
// return false;
// }
// if (GetMinimumSizeOfText (out Size size))
// {
// // TODO: This is a hack.
// //_width = size.Width;
// //_height = size.Height;
// SetFrame (new (_frame.Location, size));
// //throw new InvalidOperationException ("This is a hack.");
// return true;
// }
// return false;
//}
private void UpdateTextDirection (TextDirection newDirection)
{
bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection);
TextFormatter.Direction = newDirection;
//bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out Size _);
UpdateTextFormatterText ();
if (directionChanged)
{
OnResizeNeeded ();
}
//if ((!ValidatePosDim && directionChanged && AutoSize) || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize))
//{
// OnResizeNeeded ();
//}
//else if (directionChanged && IsAdded)
//{
// ResizeViewportToFit (Viewport.Size);
//}
SetTextFormatterSize ();
SetNeedsDisplay ();
}
}