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 { 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; } ///// ///// Gets 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; // } // // Get the size of the text without the hot key specifier // Rectangle rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); // int newWidth = rect.Size.Width - GetHotKeySpecifierLength (); // int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false); // 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 () { UpdateTextFormatterText (); //if (!IsInitialized) //{ // return; //} //Dim.DimAuto widthAuto = Width as Dim.DimAuto; //Dim.DimAuto heightAuto = Height as Dim.DimAuto; // TODO: This is a hack. Figure out how to move this into DimDimAuto if ((Width is Dim.DimAuto widthAuto && widthAuto._style != Dim.DimAutoStyle.Subviews) || (Height is Dim.DimAuto heightAuto && heightAuto._style != Dim.DimAutoStyle.Subviews)) { // This updates TextFormatter.Size to the text size TextFormatter.AutoSize = true; // Whenever DimAutoStyle.Text is set, ContentSize will match TextFormatter.Size. ContentSize = TextFormatter.Size; return; } TextFormatter.AutoSize = false; TextFormatter.Size = new Size (ContentSize.Width, ContentSize.Height); } ////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 (); } }