From 2cecc7762a87e8f8da9a43eda7351374913b27ce Mon Sep 17 00:00:00 2001 From: Tig Date: Wed, 17 Jul 2024 16:18:58 -0600 Subject: [PATCH] WIP - Trying to make TextFormatter have indepdentent width/height --- Terminal.Gui/Text/TextFormatter.cs | 118 ++++++++++++++---- Terminal.Gui/View/Layout/DimAuto.cs | 33 ++++- Terminal.Gui/View/Layout/ViewLayout.cs | 5 + Terminal.Gui/View/ViewText.cs | 48 ++----- Terminal.Gui/Views/MessageBox.cs | 34 ++--- UICatalog/Scenarios/ComputedLayout.cs | 55 +++++++- UICatalog/Scenarios/MessageBoxes.cs | 9 +- UnitTests/Text/TextFormatterTests.cs | 7 +- .../View/Layout/Dim.AutoTests.PosTypes.cs | 49 ++++++++ UnitTests/View/Layout/Dim.AutoTests.cs | 59 ++++++++- 10 files changed, 326 insertions(+), 91 deletions(-) diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 79967f517..e6d97ff09 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -15,7 +16,6 @@ public class TextFormatter private List _lines = new (); private bool _multiLine; private bool _preserveTrailingSpaces; - private Size _size; private int _tabWidth = 4; private string _text; private Alignment _textAlignment = Alignment.Start; @@ -177,6 +177,48 @@ public class TextFormatter set => _preserveTrailingSpaces = EnableNeedsFormat (value); } + private int? _width; + + public int? Width + { + get => _width; + set + { + if (_width == value) + { + return; + } + _width = value; + if (_width is null || _height is null) + { + return; + } + Size size = EnableNeedsFormat (Size!.Value); + _width = size.Width; + } + } + + private int? _height; + + public int? Height + { + get => _height; + set + { + if (_height == value) + { + return; + } + _height = value; + if (_width is null || _height is null) + { + return; + } + Size size = EnableNeedsFormat (Size!.Value); + _height = size.Height; + } + } + /// Gets or sets the size will be constrained to when formatted. /// /// @@ -185,18 +227,38 @@ public class TextFormatter /// /// When set, is set to . /// - public Size Size + public Size? Size { - get => _size; + get + { + if (_width is null || _height is null) + { + return null; + } + + return new Size (_width.Value, _height.Value); + } set { if (AutoSize) { - _size = EnableNeedsFormat (GetAutoSize ()); + Size size = EnableNeedsFormat (GetAutoSize ()); + _width = size.Width; + _height = size.Height; } else { - _size = EnableNeedsFormat (value); + if (value is null) + { + _width = null; + _height = null; + } + else + { + Size size = EnableNeedsFormat (value.Value); + _width = size.Width; + _height = size.Height; + } } } } @@ -651,19 +713,20 @@ public class TextFormatter /// The size required to hold the formatted text. public Size FormatAndGetSize (Size? constrainSize = null) { - if (constrainSize is null) + if (string.IsNullOrEmpty (Text)) { - constrainSize = Size; - } - if (string.IsNullOrEmpty (Text) || constrainSize.Value.Height == 0 || constrainSize.Value.Width == 0) - { - return Size.Empty; + return System.Drawing.Size.Empty; } // HACK: This is a total hack to work around the fact that TextFormatter.Format does not match TextFormatter.Draw. - Size prevSize = Size; - Size = constrainSize.Value; - + int? prevWidth = _width; + int? prevHeight = _height; + if (constrainSize is { }) + { + _width = constrainSize?.Width; + _height = constrainSize?.Height; + } + // HACK: Fill normally will fill the entire constraint size, but we need to know the actual size of the text. Alignment prevAlignment = Alignment; if (Alignment == Alignment.Fill) @@ -681,11 +744,16 @@ public class TextFormatter // Undo hacks Alignment = prevAlignment; VerticalAlignment = prevVerticalAlignment; - Size = prevSize; + + if (constrainSize is { }) + { + _width = prevWidth ?? null; + _height = prevHeight ?? null; + } if (lines.Count == 0) { - return Size.Empty; + return System.Drawing.Size.Empty; } int width; @@ -720,7 +788,7 @@ public class TextFormatter public List GetLines () { // With this check, we protect against subclasses with overrides of Text - if (string.IsNullOrEmpty (Text) || Size.Height == 0 || Size.Width == 0) + if (string.IsNullOrEmpty (Text) || Size is null || Size?.Height == 0 || Size?.Width == 0) { _lines = new List { string.Empty }; NeedsFormat = false; @@ -745,9 +813,9 @@ public class TextFormatter _lines = Format ( text, - Size.Height, + Size!.Value.Height, VerticalAlignment == Alignment.Fill, - Size.Width > colsWidth && WordWrap, + Size!.Value.Width > colsWidth && WordWrap, PreserveTrailingSpaces, TabWidth, Direction, @@ -757,7 +825,7 @@ public class TextFormatter if (!AutoSize) { - colsWidth = GetMaxColsForWidth (_lines, Size.Width, TabWidth); + colsWidth = GetMaxColsForWidth (_lines, Size!.Value.Width, TabWidth); if (_lines.Count > colsWidth) { @@ -769,9 +837,9 @@ public class TextFormatter { _lines = Format ( text, - Size.Width, + Size!.Value.Width, Alignment == Alignment.Fill, - Size.Height > 1 && WordWrap, + Size!.Value.Height > 1 && WordWrap, PreserveTrailingSpaces, TabWidth, Direction, @@ -779,9 +847,9 @@ public class TextFormatter this ); - if (!AutoSize && _lines.Count > Size.Height) + if (!AutoSize && _lines.Count > Size!.Value.Height) { - _lines.RemoveRange (Size.Height, _lines.Count - Size.Height); + _lines.RemoveRange (Size!.Value.Height, _lines.Count - Size!.Value.Height); } } @@ -1952,7 +2020,7 @@ public class TextFormatter { if (string.IsNullOrEmpty (text)) { - return new (new (x, y), Size.Empty); + return new (new (x, y), System.Drawing.Size.Empty); } int w, h; diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index fedc96188..20569cc98 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -70,19 +70,40 @@ public class DimAuto () : Dim if (Style.FastHasFlags (DimAutoStyle.Text)) { + if (dimension == Dimension.Width) { - //us.TextFormatter.Size = new (superviewContentSize, 2048); - textSize = us.TextFormatter.FormatAndGetSize ().Width; - //us.TextFormatter.Size = new Size (textSize, 2048); + if (us.TextFormatter.Width is null) + { + us.TextFormatter.Size = us.TextFormatter.FormatAndGetSize (new (int.Max (autoMax, superviewContentSize), screen)); + } +// else + { + textSize = us.TextFormatter.Width.Value; + } } else { - //if (us.TextFormatter.Size.Width == 0) + if (us.TextFormatter.Height is null) + { + textSize = us.TextFormatter.FormatAndGetSize (new (us.TextFormatter.Width ?? screen, int.Max (autoMax, superviewContentSize))).Height; + us.TextFormatter.Height = textSize; + } + else + { + textSize = us.TextFormatter.Height.Value; + } + + //if (us.Width.Has (typeof(DimAuto), out var widthDim)) //{ - // us.TextFormatter.Size = us.TextFormatter.GetAutoSize (); + // DimAuto widthDimAuto = (DimAuto)widthDim; + // textSize = us.TextFormatter.FormatAndGetSize (us.GetContentSize ()).Height; //} - textSize = us.TextFormatter.FormatAndGetSize ().Height; + //else + //{ + // textSize = us.TextFormatter.FormatAndGetSize ().Height; + //} + //us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize }; } } diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index d3d241d20..a569a2165 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -576,6 +576,7 @@ public partial class View SetTextFormatterSize (); int newX, newW, newY, newH; + Rectangle oldFrame = Frame; // Calculate the new X, Y, Width, and Height // If the Width or Height is Dim.Auto, calculate the Width or Height first. Otherwise, calculate the X or Y first. @@ -583,6 +584,7 @@ public partial class View { newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width); newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width); + //SetFrame (oldFrame with { X = newX, Width = newW }); } else { @@ -601,6 +603,8 @@ public partial class View newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height); } + SetFrame (oldFrame); + Rectangle newFrame = new (newX, newY, newW, newH); if (Frame != newFrame) @@ -743,6 +747,7 @@ public partial class View Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () : Application.Screen.Size; + SetTextFormatterSize (); SetRelativeLayout (superViewContentSize); if (IsInitialized) diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 8ca567fbd..9fdb6598f 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -180,48 +180,24 @@ public partial class View // Default is to use GetContentSize (). var size = GetContentSize (); - // TODO: This is a hack. Figure out how to move this into DimDimAuto + // TODO: This is a hack. Figure out how to move this logic into DimAuto // Use _width & _height instead of Width & Height to avoid debug spew DimAuto? widthAuto = _width as DimAuto; DimAuto? heightAuto = _height as DimAuto; - if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - || (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) + if ((widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text))) { - int width = 0; - int height = 0; - - if (widthAuto is null || !widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - width = GetContentSize ().Width; - } - - if (heightAuto is null || !heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - height = GetContentSize ().Height; - } - - if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - if (height == 0 && heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - height = Application.Screen.Width * 4; - } - width = TextFormatter.FormatAndGetSize (new (Application.Screen.Width * 4, height)).Width; - } - - if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - if (width == 0 && widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Text)) - { - width = Application.Screen.Width * 4; - } - height = TextFormatter.FormatAndGetSize (new (width, Application.Screen.Height * 4)).Height; - } - - size = new (width, height); + TextFormatter.Width = null; } - TextFormatter.Size = size; + if ((heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Text))) + { + TextFormatter.Height = null; + } + + if (TextFormatter.Size is { }) + { + TextFormatter.Size = size; + } } private void UpdateTextDirection (TextDirection newDirection) diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index f4150d5ab..97fcf9452 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -369,8 +369,8 @@ public static class MessageBox ButtonAlignment = Alignment.Center, ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems, BorderStyle = MessageBox.DefaultBorderStyle, - Width = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 1, maximumContentDim: Dim.Percent (90)), - Height = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 2, maximumContentDim: Dim.Percent (90)), + Width = Dim.Auto (DimAutoStyle.Auto, /*minimumContentDim: Dim.Percent (DefaultMinimumWidth), */ maximumContentDim: Dim.Percent (90)), + Height = Dim.Auto (DimAutoStyle.Auto, /*minimumContentDim: Dim.Percent (DefaultMinimumHeight),*/ maximumContentDim: Dim.Percent (90)), }; if (width != 0) @@ -385,22 +385,22 @@ public static class MessageBox d.ColorScheme = useErrorColors ? Colors.ColorSchemes ["Error"] : Colors.ColorSchemes ["Dialog"]; - d.LayoutComplete += (s, e) => - { - if (wrapMessage) - { - int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; - Debug.Assert (d.TextFormatter.WordWrap); - d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Screen.Height); - Size textSize = d.TextFormatter.GetAutoSize (); - textSize.Height += buttonHeight; + //d.LayoutComplete += (s, e) => + //{ + // if (wrapMessage) + // { + // int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0; + // Debug.Assert (d.TextFormatter.WordWrap); + // d.TextFormatter.Size = new Size (d.GetContentSize ().Width, Application.Screen.Height); + // Size textSize = d.TextFormatter.GetAutoSize (); + // textSize.Height += buttonHeight; - if (textSize != d.TextFormatter.Size) - { - d.SetNeedsLayout (); - } - } - }; + // if (textSize != d.TextFormatter.Size) + // { + // d.SetNeedsLayout (); + // } + // } + //}; d.HotKeySpecifier = new Rune ('\xFFFF'); d.Text = message; diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index cf0564126..cb7a72130 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -7,8 +7,7 @@ using static Terminal.Gui.Dialog; namespace UICatalog.Scenarios; /// -/// This Scenario demonstrates how to use Termina.gui's Dim and Pos Layout System. [x] - Using Dim.Fill to fill a -/// window [x] - Using Dim.Fill and Dim.Pos to automatically align controls based on an initial control [ ] - ... +/// This Scenario demonstrates how to use Terminal.Gui's Dim and Pos Layout System. /// [ScenarioMetadata ("Computed Layout", "Demonstrates the Computed (Dim and Pos) Layout System.")] [ScenarioCategory ("Layout")] @@ -216,6 +215,58 @@ public class ComputedLayout : Scenario fv.Title = $"{frameView.GetType ().Name} {{X={fv.X},Y={fv.Y},Width={fv.Width},Height={fv.Height}}}"; }; + + labelList = new List