using System; using System.Collections.Generic; namespace Terminal.Gui; public partial class View { string _text; /// /// 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. /// /// /// Set the to enable hotkey support. To disable hotkey support set /// to /// (Rune)0xffff. /// /// /// If is true, the will be adjusted to fit the text. /// /// public virtual string Text { get => _text; set { _text = value; SetHotKey (); UpdateTextFormatterText (); OnResizeNeeded (); #if DEBUG if (_text != null && string.IsNullOrEmpty (Id)) { Id = _text; } #endif } } /// /// Gets or sets the used to format . /// public TextFormatter TextFormatter { get; set; } /// /// 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; } } } /// /// 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 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 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; } } /// /// Can be overridden if the has /// different format than the default. /// protected virtual void UpdateTextFormatterText () { if (TextFormatter != null) { TextFormatter.Text = _text; } } void UpdateTextDirection (TextDirection newDirection) { var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection); TextFormatter.Direction = newDirection; var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _); UpdateTextFormatterText (); if (!ValidatePosDim && directionChanged && AutoSize || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) { OnResizeNeeded (); } else if (directionChanged && IsAdded) { ResizeBoundsToFit (Bounds.Size); // BUGBUG: I think this call is redundant. SetFrameToFitText (); } else { SetFrameToFitText (); } SetTextFormatterSize (); SetNeedsDisplay (); } /// /// 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. /// bool SetFrameToFitText () { // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.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 = new Size (0, 0); return false; } sizeRequired = Bounds.Size; if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) { return false; } switch (TextFormatter.IsVerticalDirection (TextDirection)) { case true: var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); // TODO: v2 - This uses frame.Width; it should only use Bounds if (_frame.Width < colWidth && (Width == null || Bounds.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth)) { sizeRequired = new Size (colWidth, Bounds.Height); return true; } break; default: if (_frame.Height < 1 && (Height == null || Height is Dim.DimAbsolute && Height.Anchor (0) == 0)) { sizeRequired = new Size (Bounds.Width, 1); return true; } break; } return false; } if (GetMinimumSizeOfText (out var size)) { // TODO: This is a hack. //_width = size.Width; //_height = size.Height; _frame = new Rect (_frame.Location, size); //throw new InvalidOperationException ("This is a hack."); return true; } return false; } /// /// 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; } /// /// Gets the dimensions required for ignoring a . /// /// internal Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (), TextFormatter.Size.Height - GetHotKeySpecifierLength (false)); /// /// 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 = Bounds.Size; return; } TextFormatter.Size = new Size (Bounds.Size.Width + GetHotKeySpecifierLength (), Bounds.Size.Height + GetHotKeySpecifierLength (false)); } /// /// Gets the Frame dimensions required to fit within using the text /// specified by the /// property and accounting for any characters. /// /// The the needs to be set to fit the text. public Size GetAutoSize () { var x = 0; var y = 0; if (IsInitialized) { x = Bounds.X; y = Bounds.Y; } var 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 Size (newWidth, newHeight); } bool IsValidAutoSize (out Size autoSize) { var 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)); } bool IsValidAutoSizeWidth (Dim width) { var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); var dimValue = width.Anchor (0); return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ()); } bool IsValidAutoSizeHeight (Dim height) { var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); var dimValue = height.Anchor (0); return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false)); } }