diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml
index ea46a750a..0386c3fef 100644
--- a/.github/workflows/api-docs.yml
+++ b/.github/workflows/api-docs.yml
@@ -36,14 +36,14 @@ jobs:
- name: Upload artifact
if: github.ref_name == 'main' || github.ref_name == 'develop'
- uses: actions/upload-pages-artifact@v2
+ uses: actions/upload-pages-artifact@v3
with:
path: docfx/_site
- name: Deploy to GitHub Pages
if: github.ref_name == 'main' || github.ref_name == 'develop'
id: deployment
- uses: actions/deploy-pages@v3
+ uses: actions/deploy-pages@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs
index 785331ada..1ec014d5f 100644
--- a/Terminal.Gui/Application.cs
+++ b/Terminal.Gui/Application.cs
@@ -317,6 +317,8 @@ public static partial class Application {
} else if (Top != null && Toplevel != Top && _topLevels.Contains (Top)) {
Top.OnLeave (Toplevel);
}
+ // BUGBUG: We should not depend on `Id` internally.
+ // BUGBUG: It is super unclear what this code does anyway.
if (string.IsNullOrEmpty (Toplevel.Id)) {
int count = 1;
string id = (_topLevels.Count + count).ToString ();
@@ -836,6 +838,12 @@ public static partial class Application {
#endregion Run (Begin, Run, End)
#region Toplevel handling
+
+ ///
+ /// Holds the stack of TopLevel views.
+ ///
+ // BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
+ // about TopLevels that are just a SubView of another View?
static readonly Stack _topLevels = new ();
///
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
index b78d0d2dc..cda6e1359 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
@@ -1077,7 +1077,7 @@ internal class WindowsDriver : ConsoleDriver {
inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
}
var keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent);
- Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
+ //Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
var map = MapKey (keyInfo);
diff --git a/Terminal.Gui/Text/ViewLayout.cs b/Terminal.Gui/Text/ViewLayout.cs
deleted file mode 100644
index faeb8e174..000000000
--- a/Terminal.Gui/Text/ViewLayout.cs
+++ /dev/null
@@ -1,1232 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-
-namespace Terminal.Gui {
- ///
- /// Determines the LayoutStyle for a , if Absolute, during , the
- /// value from the will be used, if the value is Computed, then
- /// will be updated from the X, Y objects and the Width and Height objects.
- ///
- public enum LayoutStyle {
- ///
- /// The position and size of the view are based .
- ///
- Absolute,
-
- ///
- /// The position and size of the view will be computed based on
- /// , , , and . will
- /// provide the absolute computed values.
- ///
- Computed
- }
-
- public partial class View {
-
- // The frame for the object. Relative to the SuperView's Bounds.
- Rect _frame;
-
- ///
- /// Gets or sets the frame for the view. The frame is relative to the 's .
- ///
- /// The frame.
- ///
- ///
- /// Change the Frame when using the layout style to move or resize views.
- ///
- ///
- /// Altering the Frame of a view will trigger the redrawing of the
- /// view as well as the redrawing of the affected regions of the .
- ///
- ///
- public virtual Rect Frame {
- get => _frame;
- set {
- _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
- if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
- LayoutFrames ();
- TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
- SetNeedsLayout ();
- SetNeedsDisplay ();
- }
- }
- }
-
- ///
- /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView.
- /// The margin offsets the from the .
- ///
- ///
- ///
- /// The frames (, , and ) are not part of the View's content
- /// and are not clipped by the View's Clip Area.
- ///
- ///
- /// Changing the size of a frame (, , or )
- /// will change the size of the and trigger to update the layout of the
- /// and its .
- ///
- ///
- public Frame Margin { get; private set; }
-
- ///
- /// The frame (specified as a ) inside of the view that offsets the from the .
- /// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title.
- /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and
- /// title will take up the first row and the second row will be filled with spaces.
- ///
- ///
- ///
- /// provides a simple helper for turning a simple border frame on or off.
- ///
- ///
- /// The frames (, , and ) are not part of the View's content
- /// and are not clipped by the View's Clip Area.
- ///
- ///
- /// Changing the size of a frame (, , or )
- /// will change the size of the and trigger to update the layout of the
- /// and its .
- ///
- ///
- public Frame Border { get; private set; }
-
- ///
- /// Gets or sets whether the view has a one row/col thick border.
- ///
- ///
- ///
- /// This is a helper for manipulating the view's . Setting this property to any value other than
- /// is equivalent to setting 's
- /// to `1` and to the value.
- ///
- ///
- /// Setting this property to is equivalent to setting 's
- /// to `0` and to .
- ///
- ///
- /// For more advanced customization of the view's border, manipulate see directly.
- ///
- ///
- public LineStyle BorderStyle {
- get {
- return Border?.BorderStyle ?? LineStyle.None;
- }
- set {
- if (Border == null) {
- throw new InvalidOperationException ("Border is null; this is likely a bug.");
- }
- if (value != LineStyle.None) {
- Border.Thickness = new Thickness (1);
- } else {
- Border.Thickness = new Thickness (0);
- }
- Border.BorderStyle = value;
- LayoutFrames ();
- SetNeedsLayout ();
- }
- }
-
- ///
- /// The frame (specified as a ) inside of the view that offsets the from the .
- ///
- ///
- ///
- /// The frames (, , and ) are not part of the View's content
- /// and are not clipped by the View's Clip Area.
- ///
- ///
- /// Changing the size of a frame (, , or )
- /// will change the size of the and trigger to update the layout of the
- /// and its .
- ///
- ///
- public Frame Padding { get; private set; }
-
- ///
- /// Helper to get the total thickness of the , , and .
- ///
- /// A thickness that describes the sum of the Frames' thicknesses.
- public Thickness GetFramesThickness ()
- {
- var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
- var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
- var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
- var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
- return new Thickness (left, top, right, bottom);
- }
-
- ///
- /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
- /// , and .
- ///
- public Point GetBoundsOffset () => new Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
-
- ///
- /// Creates the view's objects. This internal method is overridden by Frame to do nothing
- /// to prevent recursion during View construction.
- ///
- internal virtual void CreateFrames ()
- {
- void ThicknessChangedHandler (object sender, EventArgs e)
- {
- LayoutFrames ();
- SetNeedsLayout ();
- SetNeedsDisplay ();
- }
-
- if (Margin != null) {
- Margin.ThicknessChanged -= ThicknessChangedHandler;
- Margin.Dispose ();
- }
- Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) };
- Margin.ThicknessChanged += ThicknessChangedHandler;
- Margin.Parent = this;
-
- if (Border != null) {
- Border.ThicknessChanged -= ThicknessChangedHandler;
- Border.Dispose ();
- }
- Border = new Frame () { Id = "Border", Thickness = new Thickness (0) };
- Border.ThicknessChanged += ThicknessChangedHandler;
- Border.Parent = this;
-
- // TODO: Create View.AddAdornment
-
- if (Padding != null) {
- Padding.ThicknessChanged -= ThicknessChangedHandler;
- Padding.Dispose ();
- }
- Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) };
- Padding.ThicknessChanged += ThicknessChangedHandler;
- Padding.Parent = this;
- }
-
- LayoutStyle _layoutStyle;
-
- ///
- /// Controls how the View's is computed during . If the style is set to
- /// ,
- /// LayoutSubviews does not change the . If the style is
- /// the is updated using
- /// the , , , and properties.
- ///
- /// The layout style.
- public LayoutStyle LayoutStyle {
- get => _layoutStyle;
- set {
- _layoutStyle = value;
- SetNeedsLayout ();
- }
- }
-
- ///
- /// The view's content area.
- ///
- /// SubViews are positioned relative to Bounds.
- ///
- ///
- /// Drawing is clipped to Bounds ( clips drawing to Bounds.Size).
- ///
- ///
- /// Mouse events are reported relative to Bounds.
- ///
- ///
- /// The view's content area.
- ///
- ///
- /// The of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use
- /// .
- ///
- ///
- /// When using , Bounds is not valid until after the view has been initialized (after has been called and
- /// has fired). Accessing this property before the view is initialized is considered an error./>
- ///
- ///
- public virtual Rect Bounds {
- get {
-#if DEBUG
- if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
- Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug. View: {this}");
- Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design.");
- }
-#endif // DEBUG
- //var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size);
- var frameRelativeBounds = FrameGetInsideBounds ();
- return new Rect (default, frameRelativeBounds.Size);
- }
- set {
- // BUGBUG: Margin etc.. can be null (if typeof(Frame))
- Frame = new Rect (Frame.Location,
- new Size (
- value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
- value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical
- )
- );
- }
- }
-
- private Rect FrameGetInsideBounds ()
- {
- if (Margin == null || Border == null || Padding == null) {
- return new Rect (default, Frame.Size);
- }
- var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
- var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
- return new Rect (Point.Empty, new Size (width, height));
- }
-
- // Diagnostics to highlight when X or Y is read before the view has been initialized
- Pos VerifyIsInitialized (Pos pos)
- {
-#if DEBUG
- if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) {
- Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug.");
- }
-#endif // DEBUG
- return pos;
- }
-
- // Diagnostics to highlight when Width or Height is read before the view has been initialized
- Dim VerifyIsInitialized (Dim dim)
- {
-#if DEBUG
- if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) {
- Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug.");
- }
-#endif // DEBUG
- return dim;
- }
-
- Pos _x, _y;
-
- ///
- /// Gets or sets the X position for the view (the column). Only used if the is .
- ///
- /// The X Position.
- ///
- /// If is changing this property has no effect and its value is indeterminate.
- ///
- public Pos X {
- get => VerifyIsInitialized (_x);
- set {
- if (ForceValidatePosDim && !ValidatePosDim (_x, value)) {
- throw new ArgumentException ();
- }
-
- _x = value;
-
- OnResizeNeeded ();
- }
- }
-
- ///
- /// Gets or sets the Y position for the view (the row). Only used if the is .
- ///
- /// The y position (line).
- ///
- /// If is changing this property has no effect and its value is indeterminate.
- ///
- public Pos Y {
- get => VerifyIsInitialized (_y);
- set {
- if (ForceValidatePosDim && !ValidatePosDim (_y, value)) {
- throw new ArgumentException ();
- }
-
- _y = value;
-
- OnResizeNeeded ();
- }
- }
- Dim _width, _height;
-
- ///
- /// Gets or sets the width of the view. Only used the is .
- ///
- /// The width.
- ///
- /// If is changing this property has no effect and its value is indeterminate.
- ///
- public Dim Width {
- get => VerifyIsInitialized (_width);
- set {
- if (ForceValidatePosDim && !ValidatePosDim (_width, value)) {
- throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Width));
- }
-
- _width = value;
-
- if (ForceValidatePosDim) {
- var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
-
- if (IsAdded && AutoSize && !isValidNewAutSize) {
- throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
- }
- }
- OnResizeNeeded ();
- }
- }
-
- ///
- /// Gets or sets the height of the view. Only used the is .
- ///
- /// The height.
- /// If is changing this property has no effect and its value is indeterminate.
- public Dim Height {
- get => VerifyIsInitialized (_height);
- set {
- if (ForceValidatePosDim && !ValidatePosDim (_height, value)) {
- throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Height));
- }
-
- _height = value;
-
- if (ForceValidatePosDim) {
- var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
-
- if (IsAdded && AutoSize && !isValidNewAutSize) {
- throw new InvalidOperationException ("Must set AutoSize to false before set the Height.");
- }
- }
- OnResizeNeeded ();
- }
- }
-
- ///
- /// Forces validation with layout
- /// to avoid breaking the and settings.
- ///
- public bool ForceValidatePosDim { get; set; }
-
- bool ValidatePosDim (object oldValue, object newValue)
- {
- if (!IsInitialized || _layoutStyle == LayoutStyle.Absolute || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) {
- return true;
- }
- if (_layoutStyle == LayoutStyle.Computed) {
- if (oldValue.GetType () != newValue.GetType () && !(newValue is Pos.PosAbsolute || newValue is Dim.DimAbsolute)) {
- return true;
- }
- }
- return false;
- }
-
- // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
- // BUGBUG: this function does not belong in ViewLayout.cs - it should be in ViewText.cs
- ///
- /// 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 GetMinimumBoundsForFrame (out Size size)
- {
- if (!IsInitialized) {
- size = new Size (0, 0);
- return false;
- }
- size = Bounds.Size;
-
- if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
- 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))) {
- size = new Size (colWidth, Bounds.Height);
- return true;
- }
- break;
- default:
- if (_frame.Height < 1 &&
- (Height == null ||
- (Height is Dim.DimAbsolute &&
- Height.Anchor (0) == 0))) {
- size = new Size (Bounds.Width, 1);
- return true;
- }
- break;
- }
- }
- return false;
- }
-
- // BUGBUG: this function does not belong in ViewLayout.cs - it should be in ViewText.cs
- ///
- /// Sets the size of the View to the minimum width or height required to fit (see .
- ///
- /// if the size was changed, if
- /// will not fit.
- bool SetBoundsToFitFrame ()
- {
- if (GetMinimumBoundsForFrame (out Size size)) {
- _frame = new Rect (_frame.Location, size);
- return true;
- }
- return false;
- }
-
- ///
- /// Called whenever the view needs to be resized. Sets and
- /// triggers a call.
- ///
- ///
- /// Can be overridden if the view resize behavior is different than the default.
- ///
- protected virtual void OnResizeNeeded ()
- {
- var actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X;
- var actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y;
-
- if (AutoSize) {
- //if (TextAlignment == TextAlignment.Justified) {
- // throw new InvalidOperationException ("TextAlignment.Justified cannot be used with AutoSize");
- //}
- var s = GetAutoSize ();
- var w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width;
- var h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height;
- _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame!
- } else {
- var w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width;
- var h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height;
- // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm...
- // This is needed for DimAbsolute values by setting the frame before LayoutSubViews.
- _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame!
- }
- //// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case
- if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
- SetBoundsToFitFrame ();
- LayoutFrames ();
- TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
- SetNeedsLayout ();
- SetNeedsDisplay ();
- }
- }
-
- internal bool LayoutNeeded { get; private set; } = true;
-
- internal void SetNeedsLayout ()
- {
- if (LayoutNeeded) {
- return;
- }
- LayoutNeeded = true;
- foreach (var view in Subviews) {
- view.SetNeedsLayout ();
- }
- TextFormatter.NeedsFormat = true;
- SuperView?.SetNeedsLayout ();
- }
-
- ///
- /// Indicates that the view does not need to be laid out.
- ///
- protected void ClearLayoutNeeded ()
- {
- LayoutNeeded = false;
- }
-
- ///
- /// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means
- /// relative to the View's 's .
- ///
- /// The coordinate relative to the 's .
- /// Screen-relative column.
- /// Screen-relative row.
- public Point ScreenToFrame (int x, int y)
- {
- Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
- var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y);
- if (SuperView != null) {
- var superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y);
- ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y);
- }
- return ret;
- }
-
- ///
- /// Converts a screen-relative coordinate to a bounds-relative coordinate.
- ///
- /// The coordinate relative to this view's .
- /// Screen-relative column.
- /// Screen-relative row.
- public Point ScreenToBounds (int x, int y)
- {
- var screen = ScreenToFrame (x, y);
- var boundsOffset = GetBoundsOffset ();
- return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y);
- }
-
- ///
- /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally clamped to the screen dimensions.
- ///
- /// -relative column.
- /// -relative row.
- /// Absolute column; screen-relative.
- /// Absolute row; screen-relative.
- /// If , and will be clamped to the
- /// screen dimensions (will never be negative and will always be less than and
- /// , respectively.
- public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true)
- {
- var boundsOffset = GetBoundsOffset ();
- rx = x + Frame.X + boundsOffset.X;
- ry = y + Frame.Y + boundsOffset.Y;
-
- var super = SuperView;
- while (super != null) {
- boundsOffset = super.GetBoundsOffset ();
- rx += super.Frame.X + boundsOffset.X;
- ry += super.Frame.Y + boundsOffset.Y;
- super = super.SuperView;
- }
-
- // The following ensures that the cursor is always in the screen boundaries.
- if (clamped) {
- ry = Math.Min (ry, Driver.Rows - 1);
- rx = Math.Min (rx, Driver.Cols - 1);
- }
- }
-
- ///
- /// Converts a -relative region to a screen-relative region.
- ///
- public Rect BoundsToScreen (Rect region)
- {
- BoundsToScreen (region.X, region.Y, out var x, out var y, clamped: false);
- return new Rect (x, y, region.Width, region.Height);
- }
-
- ///
- /// Gets the with a screen-relative location.
- ///
- /// The location and size of the view in screen-relative coordinates.
- public virtual Rect FrameToScreen ()
- {
- var ret = Frame;
- var super = SuperView;
- while (super != null) {
- var boundsOffset = super.GetBoundsOffset ();
- ret.X += super.Frame.X + boundsOffset.X;
- ret.Y += super.Frame.Y + boundsOffset.Y;
- super = super.SuperView;
- }
- return ret;
- }
-
- ///
- /// Sets the View's to the frame-relative coordinates if its container. The
- /// container size and location are specified by and are relative to the
- /// View's superview.
- ///
- /// The SuperView-relative rectangle describing View's container (nominally the
- /// same as this.SuperView.Frame).
- internal void SetRelativeLayout (Rect superviewFrame)
- {
- int newX, newW, newY, newH;
- var autosize = Size.Empty;
-
- if (AutoSize) {
- // Note this is global to this function and used as such within the local functions defined
- // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function.
- autosize = GetAutoSize ();
- }
-
- // Returns the new dimension (width or height) and location (x or y) for the View given
- // the superview's Frame.X or Frame.Y
- // the superview's width or height
- // the current Pos (View.X or View.Y)
- // the current Dim (View.Width or View.Height)
- (int newLocation, int newDimension) GetNewLocationAndDimension (int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension)
- {
- int newDimension, newLocation;
-
- switch (pos) {
- case Pos.PosCenter:
- if (dim == null) {
- newDimension = AutoSize ? autosizeDimension : superviewDimension;
- } else {
- newDimension = dim.Anchor (superviewDimension);
- newDimension = AutoSize && autosizeDimension > newDimension ? autosizeDimension : newDimension;
- }
- newLocation = pos.Anchor (superviewDimension - newDimension);
- break;
-
- case Pos.PosCombine combine:
- int left, right;
- (left, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.left, dim, autosizeDimension);
- (right, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.right, dim, autosizeDimension);
- if (combine.add) {
- newLocation = left + right;
- } else {
- newLocation = left - right;
- }
- newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
- break;
-
- case Pos.PosAbsolute:
- case Pos.PosAnchorEnd:
- case Pos.PosFactor:
- case Pos.PosFunc:
- case Pos.PosView:
- default:
- newLocation = pos?.Anchor (superviewDimension) ?? 0;
- newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
- break;
- }
- return (newLocation, newDimension);
- }
-
- // Recursively calculates the new dimension (width or height) of the given Dim given:
- // the current location (x or y)
- // the current dimension (width or height)
- int CalculateNewDimension (Dim d, int location, int dimension, int autosize)
- {
- int newDimension;
- switch (d) {
- case null:
- newDimension = AutoSize ? autosize : dimension;
- break;
- case Dim.DimCombine combine:
- int leftNewDim = CalculateNewDimension (combine.left, location, dimension, autosize);
- int rightNewDim = CalculateNewDimension (combine.right, location, dimension, autosize);
- if (combine.add) {
- newDimension = leftNewDim + rightNewDim;
- } else {
- newDimension = leftNewDim - rightNewDim;
- }
- newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
- break;
-
- case Dim.DimFactor factor when !factor.IsFromRemaining ():
- newDimension = d.Anchor (dimension);
- newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
- break;
-
- case Dim.DimFill:
- default:
- newDimension = Math.Max (d.Anchor (dimension - location), 0);
- newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
- break;
- }
-
- return newDimension;
- }
-
- // horizontal
- (newX, newW) = GetNewLocationAndDimension (superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width);
-
- // vertical
- (newY, newH) = GetNewLocationAndDimension (superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height);
-
- var r = new Rect (newX, newY, newW, newH);
- if (Frame != r) {
- Frame = r;
- // BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
- if (!SetBoundsToFitFrame ()) {
- TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
- }
- }
- }
-
- ///
- /// Fired after the View's method has completed.
- ///
- ///
- /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed.
- ///
- public event EventHandler LayoutStarted;
-
- ///
- /// Raises the event. Called from before any subviews have been laid out.
- ///
- internal virtual void OnLayoutStarted (LayoutEventArgs args)
- {
- LayoutStarted?.Invoke (this, args);
- }
-
- ///
- /// Fired after the View's method has completed.
- ///
- ///
- /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed.
- ///
- public event EventHandler LayoutComplete;
-
- ///
- /// Event called only once when the is being initialized for the first time.
- /// Allows configurations and assignments to be performed before the being shown.
- /// This derived from to allow notify all the views that are being initialized.
- ///
- public event EventHandler Initialized;
-
- ///
- /// Raises the event. Called from before all sub-views have been laid out.
- ///
- internal virtual void OnLayoutComplete (LayoutEventArgs args)
- {
- LayoutComplete?.Invoke (this, args);
- }
-
- internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges)
- {
- switch (pos) {
- case Pos.PosView pv:
- // See #2461
- //if (!from.InternalSubviews.Contains (pv.Target)) {
- // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}");
- //}
- if (pv.Target != this) {
- nEdges.Add ((pv.Target, from));
- }
- return;
- case Pos.PosCombine pc:
- CollectPos (pc.left, from, ref nNodes, ref nEdges);
- CollectPos (pc.right, from, ref nNodes, ref nEdges);
- break;
- }
- }
-
- internal void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges)
- {
- switch (dim) {
- case Dim.DimView dv:
- // See #2461
- //if (!from.InternalSubviews.Contains (dv.Target)) {
- // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}");
- //}
- if (dv.Target != this) {
- nEdges.Add ((dv.Target, from));
- }
- return;
- case Dim.DimCombine dc:
- CollectDim (dc.left, from, ref nNodes, ref nEdges);
- CollectDim (dc.right, from, ref nNodes, ref nEdges);
- break;
- }
- }
-
- internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges)
- {
- foreach (var v in from.InternalSubviews) {
- nNodes.Add (v);
- if (v._layoutStyle != LayoutStyle.Computed) {
- continue;
- }
- CollectPos (v.X, v, ref nNodes, ref nEdges);
- CollectPos (v.Y, v, ref nNodes, ref nEdges);
- CollectDim (v.Width, v, ref nNodes, ref nEdges);
- CollectDim (v.Height, v, ref nNodes, ref nEdges);
- }
- }
-
- // https://en.wikipedia.org/wiki/Topological_sorting
- internal static List TopologicalSort (View superView, IEnumerable nodes, ICollection<(View From, View To)> edges)
- {
- var result = new List ();
-
- // Set of all nodes with no incoming edges
- var noEdgeNodes = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
-
- while (noEdgeNodes.Any ()) {
- // remove a node n from S
- var n = noEdgeNodes.First ();
- noEdgeNodes.Remove (n);
-
- // add n to tail of L
- if (n != superView)
- result.Add (n);
-
- // for each node m with an edge e from n to m do
- foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
- var m = e.To;
-
- // remove edge e from the graph
- edges.Remove (e);
-
- // if m has no other incoming edges then
- if (edges.All (me => !me.To.Equals (m)) && m != superView) {
- // insert m into S
- noEdgeNodes.Add (m);
- }
- }
- }
-
- if (edges.Any ()) {
- foreach ((var from, var to) in edges) {
- if (from == to) {
- // if not yet added to the result, add it and remove from edge
- if (result.Find (v => v == from) == null) {
- result.Add (from);
- }
- edges.Remove ((from, to));
- } else if (from.SuperView == to.SuperView) {
- // if 'from' is not yet added to the result, add it
- if (result.Find (v => v == from) == null) {
- result.Add (from);
- }
- // if 'to' is not yet added to the result, add it
- if (result.Find (v => v == to) == null) {
- result.Add (to);
- }
- // remove from edge
- edges.Remove ((from, to));
- } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) {
- if (ReferenceEquals (from.SuperView, to)) {
- throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\").");
- } else {
- throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?");
- }
- }
- }
- }
- // return L (a topologically sorted order)
- return result;
- } // TopologicalSort
-
- ///
- /// Overriden by to do nothing, as the does not have frames.
- ///
- internal virtual void LayoutFrames ()
- {
- if (Margin == null) return; // CreateFrames() has not been called yet
-
- if (Margin.Frame.Size != Frame.Size) {
- Margin._frame = new Rect (Point.Empty, Frame.Size);
- Margin.X = 0;
- Margin.Y = 0;
- Margin.Width = Frame.Size.Width;
- Margin.Height = Frame.Size.Height;
- Margin.SetNeedsLayout ();
- Margin.LayoutSubviews ();
- Margin.SetNeedsDisplay ();
- }
-
- var border = Margin.Thickness.GetInside (Margin.Frame);
- if (border != Border.Frame) {
- Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size);
- Border.X = border.Location.X;
- Border.Y = border.Location.Y;
- Border.Width = border.Size.Width;
- Border.Height = border.Size.Height;
- Border.SetNeedsLayout ();
- Border.LayoutSubviews ();
- Border.SetNeedsDisplay ();
- }
-
- var padding = Border.Thickness.GetInside (Border.Frame);
- if (padding != Padding.Frame) {
- Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size);
- Padding.X = padding.Location.X;
- Padding.Y = padding.Location.Y;
- Padding.Width = padding.Size.Width;
- Padding.Height = padding.Size.Height;
- Padding.SetNeedsLayout ();
- Padding.LayoutSubviews ();
- Padding.SetNeedsDisplay ();
- }
- }
-
- ///
- /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in
- /// response to the container view or terminal resizing.
- ///
- ///
- /// Raises the event) before it returns.
- ///
- public virtual void LayoutSubviews ()
- {
- if (!LayoutNeeded) {
- return;
- }
-
- LayoutFrames ();
-
- var oldBounds = Bounds;
- OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
-
- TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
-
- // Sort out the dependencies of the X, Y, Width, Height properties
- var nodes = new HashSet ();
- var edges = new HashSet<(View, View)> ();
- CollectAll (this, ref nodes, ref edges);
- var ordered = View.TopologicalSort (SuperView, nodes, edges);
- foreach (var v in ordered) {
- LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
- }
-
- // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
- // Use LayoutSubview with the Frame of the 'from'
- if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) {
- foreach ((var from, var to) in edges) {
- LayoutSubview (to, from.Frame);
- }
- }
-
- LayoutNeeded = false;
-
- OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
- }
-
- private void LayoutSubview (View v, Rect contentArea)
- {
- if (v.LayoutStyle == LayoutStyle.Computed) {
- v.SetRelativeLayout (contentArea);
- }
-
- v.LayoutSubviews ();
- v.LayoutNeeded = false;
- }
-
- bool _autoSize;
-
- ///
- /// Gets or sets a flag that determines whether the View will be automatically resized to fit the
- /// within
- ///
- /// The default is . Set to to turn on AutoSize. If then
- /// and will be used if can fit;
- /// if won't fit the view will be resized as needed.
- ///
- ///
- /// In addition, if is the new values of and
- /// must be of the same types of the existing one to avoid breaking the settings.
- ///
- ///
- public virtual bool AutoSize {
- get => _autoSize;
- set {
- var v = ResizeView (value);
- TextFormatter.AutoSize = v;
- if (_autoSize != v) {
- _autoSize = v;
- TextFormatter.NeedsFormat = true;
- UpdateTextFormatterText ();
- OnResizeNeeded ();
- }
- }
- }
-
- bool ResizeView (bool autoSize)
- {
- if (!autoSize) {
- return false;
- }
-
- var boundsChanged = true;
- var newFrameSize = GetAutoSize ();
- if (IsInitialized && newFrameSize != Frame.Size) {
- if (ForceValidatePosDim) {
- // BUGBUG: This ain't right, obviously. We need to figure out how to handle this.
- boundsChanged = ResizeBoundsToFit (newFrameSize);
- } else {
- Height = newFrameSize.Height;
- Width = newFrameSize.Width;
- }
- }
- // BUGBUG: This call may be redundant
- TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
- return boundsChanged;
- }
-
- ///
- /// Resizes the View to fit the specified size. Factors in the HotKey.
- ///
- ///
- /// whether the Bounds was changed or not
- bool ResizeBoundsToFit (Size size)
- {
- var boundsChanged = false;
- var canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out var rW);
- var canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out var rH);
- if (canSizeW) {
- boundsChanged = true;
- _width = rW;
- }
- if (canSizeH) {
- boundsChanged = true;
- _height = rH;
- }
- if (boundsChanged) {
- Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
- }
-
- return boundsChanged;
- }
-
- ///
- /// Gets the Frame dimensions required to fit within using the text specified by the
- /// property and accounting for any characters.
- ///
- /// The of the view required to fit the text.
- public Size GetAutoSize ()
- {
- int x = 0;
- int y = 0;
- if (IsInitialized) {
- x = Bounds.X;
- y = Bounds.Y;
- }
- var rect = TextFormatter.CalcRect (x, y,TextFormatter.Text, TextFormatter.Direction);
- var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal;
- var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + 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 !(ForceValidatePosDim && (!(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 !(ForceValidatePosDim && (!(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 !(ForceValidatePosDim && (!(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height
- - GetHotKeySpecifierLength (false));
- }
-
- ///
- /// Determines if the View's can be set to a new value.
- ///
- ///
- /// Contains the width that would result if were set to "/>
- /// if the View's can be changed to the specified value. False otherwise.
- internal bool TrySetWidth (int desiredWidth, out int resultWidth)
- {
- var w = desiredWidth;
- bool canSetWidth;
- switch (Width) {
- case Dim.DimCombine _:
- case Dim.DimView _:
- case Dim.DimFill _:
- // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored.
- w = Width.Anchor (w);
- canSetWidth = !ForceValidatePosDim;
- break;
- case Dim.DimFactor factor:
- // Tries to get the SuperView Width otherwise the view Width.
- var sw = SuperView != null ? SuperView.Frame.Width : w;
- if (factor.IsFromRemaining ()) {
- sw -= Frame.X;
- }
- w = Width.Anchor (sw);
- canSetWidth = !ForceValidatePosDim;
- break;
- default:
- canSetWidth = true;
- break;
- }
- resultWidth = w;
-
- return canSetWidth;
- }
-
- ///
- /// Determines if the View's can be set to a new value.
- ///
- ///
- /// Contains the width that would result if were set to "/>
- /// if the View's can be changed to the specified value. False otherwise.
- internal bool TrySetHeight (int desiredHeight, out int resultHeight)
- {
- var h = desiredHeight;
- bool canSetHeight;
- switch (Height) {
- case Dim.DimCombine _:
- case Dim.DimView _:
- case Dim.DimFill _:
- // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
- h = Height.Anchor (h);
- canSetHeight = !ForceValidatePosDim;
- break;
- case Dim.DimFactor factor:
- // Tries to get the SuperView height otherwise the view height.
- var sh = SuperView != null ? SuperView.Frame.Height : h;
- if (factor.IsFromRemaining ()) {
- sh -= Frame.Y;
- }
- h = Height.Anchor (sh);
- canSetHeight = !ForceValidatePosDim;
- break;
- default:
- canSetHeight = true;
- break;
- }
- resultHeight = h;
-
- return canSetHeight;
- }
-
- ///
- /// Finds which view that belong to the superview at the provided location.
- ///
- /// The superview where to look for.
- /// The column location in the superview.
- /// The row location in the superview.
- /// The found view screen relative column location.
- /// The found view screen relative row location.
- ///
- /// The view that was found at the and coordinates.
- /// if no view was found.
- ///
- public static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
- {
- resy = resx = 0;
- if (start == null || !start.Frame.Contains (x, y)) {
- return null;
- }
-
- var startFrame = start.Frame;
- if (start.InternalSubviews != null) {
- int count = start.InternalSubviews.Count;
- if (count > 0) {
- var boundsOffset = start.GetBoundsOffset ();
- var rx = x - (startFrame.X + boundsOffset.X);
- var ry = y - (startFrame.Y + boundsOffset.Y);
- for (int i = count - 1; i >= 0; i--) {
- View v = start.InternalSubviews [i];
- if (v.Visible && v.Frame.Contains (rx, ry)) {
- var deep = FindDeepestView (v, rx, ry, out resx, out resy);
- if (deep == null)
- return v;
- return deep;
- }
- }
- }
- }
- resx = x - startFrame.X;
- resy = y - startFrame.Y;
- return start;
- }
- }
-}
diff --git a/Terminal.Gui/View/Layout/PosDim.cs b/Terminal.Gui/View/Layout/PosDim.cs
index 4fb0cb0c1..19c0859e8 100644
--- a/Terminal.Gui/View/Layout/PosDim.cs
+++ b/Terminal.Gui/View/Layout/PosDim.cs
@@ -1,327 +1,346 @@
-//
-// PosDim.cs: Pos and Dim objects for view dimensions.
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
+using System;
+using static Terminal.Gui.Dim;
+
+namespace Terminal.Gui;
+
+///
+/// Describes the position of a which can be an absolute value, a percentage, centered, or
+/// relative to the ending dimension. Integer values are implicitly convertible to
+/// an absolute . These objects are created using the static methods Percent,
+/// AnchorEnd, and Center. The objects can be combined with the addition and
+/// subtraction operators.
+///
+///
+///
+/// Use the objects on the X or Y properties of a view to control the position.
+///
+///
+/// These can be used to set the absolute position, when merely assigning an
+/// integer value (via the implicit integer to conversion), and they can be combined
+/// to produce more useful layouts, like: Pos.Center - 3, which would shift the position
+/// of the 3 characters to the left after centering for example.
+///
+///
+/// Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View). The X(View) and Y(View) are
+/// aliases to Left(View) and Top(View) respectively.
+///
+///
+///
+///
+/// Pos Object
+/// Description
+///
+///
+///
+///
+/// Creates a object that computes the position by executing the provided function. The function will be called every time the position is needed.
+///
+///
+///
+///
+///
+/// Creates a object that is a percentage of the width or height of the SuperView.
+///
+///
+///
+///
+///
+/// Creates a object that is anchored to the end (right side or bottom) of the dimension,
+/// useful to flush the layout from the right or bottom.
+///
+///
+///
+///
+///
+/// Creates a object that can be used to center the .
+///
+///
+///
+///
+///
+/// Creates a object that is an absolute position based on the specified integer value.
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Left (X) position of the specified .
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Left (X) position of the specified .
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Top (Y) position of the specified .
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Top (Y) position of the specified .
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Right (X+Width) coordinate of the specified .
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified
+///
+///
+///
+///
+///
+///
+public class Pos {
+ internal virtual int Anchor (int width) => 0;
-using System;
-namespace Terminal.Gui {
///
- /// Describes the position of a which can be an absolute value, a percentage, centered, or
- /// relative to the ending dimension. Integer values are implicitly convertible to
- /// an absolute . These objects are created using the static methods Percent,
- /// AnchorEnd, and Center. The objects can be combined with the addition and
- /// subtraction operators.
+ /// Creates a object that computes the position by executing the provided function. The function will be called every time the position is needed.
///
- ///
- ///
- /// Use the objects on the X or Y properties of a view to control the position.
- ///
- ///
- /// These can be used to set the absolute position, when merely assigning an
- /// integer value (via the implicit integer to conversion), and they can be combined
- /// to produce more useful layouts, like: Pos.Center - 3, which would shift the position
- /// of the 3 characters to the left after centering for example.
- ///
- ///
- /// It is possible to reference coordinates of another view by using the methods
- /// Left(View), Right(View), Bottom(View), Top(View). The X(View) and Y(View) are
- /// aliases to Left(View) and Top(View) respectively.
- ///
- ///
- public class Pos {
- internal virtual int Anchor (int width)
+ /// The function to be executed.
+ /// The returned from the function.
+ public static Pos Function (Func function) => new PosFunc (function);
+
+ internal class PosFactor : Pos {
+ readonly float _factor;
+
+ public PosFactor (float n) => _factor = n;
+
+ internal override int Anchor (int width) => (int)(width * _factor);
+
+ public override string ToString () => $"Factor({_factor})";
+
+ public override int GetHashCode () => _factor.GetHashCode ();
+
+ public override bool Equals (object other) => other is PosFactor f && f._factor == _factor;
+ }
+
+ // Helper class to provide dynamic value by the execution of a function that returns an integer.
+ internal class PosFunc : Pos {
+ readonly Func _function;
+
+ public PosFunc (Func n) => _function = n;
+
+ internal override int Anchor (int width) => _function ();
+
+ public override string ToString () => $"PosFunc({_function ()})";
+
+ public override int GetHashCode () => _function.GetHashCode ();
+
+ public override bool Equals (object other) => other is PosFunc f && f._function () == _function ();
+ }
+
+ ///
+ /// Creates a percentage object
+ ///
+ /// The percent object.
+ /// A value between 0 and 100 representing the percentage.
+ ///
+ /// This creates a that is centered horizontally, is 50% of the way down,
+ /// is 30% the height, and is 80% the width of the it added to.
+ ///
+ /// var textView = new TextView () {
+ /// X = Pos.Center (),
+ /// Y = Pos.Percent (50),
+ /// Width = Dim.Percent (80),
+ /// Height = Dim.Percent (30),
+ /// };
+ ///
+ ///
+ public static Pos Percent (float n)
+ {
+ if (n is < 0 or > 100) {
+ throw new ArgumentException ("Percent value must be between 0 and 100");
+ }
+
+ return new PosFactor (n / 100);
+ }
+
+ ///
+ /// Creates a object that is anchored to the end (right side or bottom) of the dimension,
+ /// useful to flush the layout from the right or bottom.
+ ///
+ /// The object anchored to the end (the bottom or the right side).
+ /// The view will be shifted left or up by the amount specified.
+ ///
+ /// This sample shows how align a to the bottom-right of a .
+ ///
+ /// // See Issue #502
+ /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
+ /// anchorButton.Y = Pos.AnchorEnd (1);
+ ///
+ ///
+ public static Pos AnchorEnd (int offset = 0)
+ {
+ if (offset < 0) {
+ throw new ArgumentException (@"Must be positive", nameof(offset));
+ }
+
+ return new PosAnchorEnd (offset);
+ }
+
+ internal class PosAnchorEnd : Pos {
+ readonly int _offset;
+
+ public PosAnchorEnd (int offset) => _offset = offset;
+
+ internal override int Anchor (int width) => width - _offset;
+
+ public override string ToString () => $"AnchorEnd({_offset})";
+
+ public override int GetHashCode () => _offset.GetHashCode ();
+
+ public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset;
+ }
+
+ ///
+ /// Creates a object that can be used to center the .
+ ///
+ /// The center Pos.
+ ///
+ /// This creates a that is centered horizontally, is 50% of the way down,
+ /// is 30% the height, and is 80% the width of the it added to.
+ ///
+ /// var textView = new TextView () {
+ /// X = Pos.Center (),
+ /// Y = Pos.Percent (50),
+ /// Width = Dim.Percent (80),
+ /// Height = Dim.Percent (30),
+ /// };
+ ///
+ ///
+ public static Pos Center () => new PosCenter ();
+
+ internal class PosAbsolute : Pos {
+ readonly int _n;
+ public PosAbsolute (int n) => _n = n;
+
+ public override string ToString () => $"Absolute({_n})";
+
+ internal override int Anchor (int width) => _n;
+
+ public override int GetHashCode () => _n.GetHashCode ();
+
+ public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n;
+ }
+
+ internal class PosCenter : Pos {
+ internal override int Anchor (int width) => width / 2;
+
+ public override string ToString () => "Center";
+ }
+
+ ///
+ /// Creates a object that is an absolute position based on the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the .
+ public static Pos At (int n) => new PosAbsolute (n);
+
+ internal class PosCombine : Pos {
+ internal Pos _left, _right;
+ internal bool _add;
+
+ public PosCombine (bool add, Pos left, Pos right)
{
- return 0;
+ _left = left;
+ _right = right;
+ _add = add;
}
- // Helper class to provide dynamic value by the execution of a function that returns an integer.
- internal class PosFunc : Pos {
- Func function;
-
- public PosFunc (Func n)
- {
- this.function = n;
- }
-
- internal override int Anchor (int width)
- {
- return function ();
- }
-
- public override string ToString ()
- {
- return $"PosFunc({function ()})";
- }
-
- public override int GetHashCode () => function.GetHashCode ();
-
- public override bool Equals (object other) => other is PosFunc f && f.function () == function ();
- }
-
- ///
- /// Creates a "PosFunc" from the specified function.
- ///
- /// The function to be executed.
- /// The returned from the function.
- public static Pos Function (Func function)
+ internal override int Anchor (int width)
{
- return new PosFunc (function);
+ int la = _left.Anchor (width);
+ int ra = _right.Anchor (width);
+ if (_add) {
+ return la + ra;
+ } else {
+ return la - ra;
+ }
}
- internal class PosFactor : Pos {
- float factor;
+ public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})";
+ }
- public PosFactor (float n)
- {
- this.factor = n;
- }
+ ///
+ /// Creates an Absolute from the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the .
+ public static implicit operator Pos (int n) => new PosAbsolute (n);
- internal override int Anchor (int width)
- {
- return (int)(width * factor);
- }
-
- public override string ToString ()
- {
- return $"Factor({factor})";
- }
-
- public override int GetHashCode () => factor.GetHashCode ();
-
- public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
+ ///
+ /// Adds a to a , yielding a new .
+ ///
+ /// The first to add.
+ /// The second to add.
+ /// The that is the sum of the values of left and right.
+ public static Pos operator + (Pos left, Pos right)
+ {
+ if (left is PosAbsolute && right is PosAbsolute) {
+ return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
}
+ var newPos = new PosCombine (true, left, right);
+ SetPosCombine (left, newPos);
+ return newPos;
+ }
- ///
- /// Creates a percentage object
- ///
- /// The percent object.
- /// A value between 0 and 100 representing the percentage.
- ///
- /// This creates a that is centered horizontally, is 50% of the way down,
- /// is 30% the height, and is 80% the width of the it added to.
- ///
- /// var textView = new TextView () {
- /// X = Pos.Center (),
- /// Y = Pos.Percent (50),
- /// Width = Dim.Percent (80),
- /// Height = Dim.Percent (30),
- /// };
- ///
- ///
- public static Pos Percent (float n)
+ ///
+ /// Subtracts a from a , yielding a new .
+ ///
+ /// The to subtract from (the minuend).
+ /// The to subtract (the subtrahend).
+ /// The that is the left minus right.
+ public static Pos operator - (Pos left, Pos right)
+ {
+ if (left is PosAbsolute && right is PosAbsolute) {
+ return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
+ }
+ var newPos = new PosCombine (false, left, right);
+ SetPosCombine (left, newPos);
+ return newPos;
+ }
+
+ static void SetPosCombine (Pos left, PosCombine newPos)
+ {
+ var view = left as PosView;
+ if (view != null) {
+ view.Target.SetNeedsLayout ();
+ }
+ }
+
+ internal class PosView : Pos {
+ public readonly View Target;
+ int side;
+
+ public PosView (View view, int side)
{
- if (n < 0 || n > 100)
- throw new ArgumentException ("Percent value must be between 0 and 100");
-
- return new PosFactor (n / 100);
+ Target = view;
+ this.side = side;
}
- internal class PosAnchorEnd : Pos {
- int n;
-
- public PosAnchorEnd (int n)
- {
- this.n = n;
- }
-
- internal override int Anchor (int width)
- {
- return width - n;
- }
-
- public override string ToString ()
- {
- return $"AnchorEnd({n})";
- }
-
- public override int GetHashCode () => n.GetHashCode ();
-
- public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd.n == n;
- }
-
- ///
- /// Creates a object that is anchored to the end (right side or bottom) of the dimension,
- /// useful to flush the layout from the right or bottom.
- ///
- /// The object anchored to the end (the bottom or the right side).
- /// Optional margin to place to the right or below.
- ///
- /// This sample shows how align a to the bottom-right of a .
- ///
- /// // See Issue #502
- /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
- /// anchorButton.Y = Pos.AnchorEnd (1);
- ///
- ///
- public static Pos AnchorEnd (int margin = 0)
+ internal override int Anchor (int width)
{
- if (margin < 0)
- throw new ArgumentException ("Margin must be positive");
-
- return new PosAnchorEnd (margin);
- }
-
- internal class PosCenter : Pos {
- internal override int Anchor (int width)
- {
- return width / 2;
- }
-
- public override string ToString ()
- {
- return "Center";
+ switch (side) {
+ case 0: return Target.Frame.X;
+ case 1: return Target.Frame.Y;
+ case 2: return Target.Frame.Right;
+ case 3: return Target.Frame.Bottom;
+ default:
+ return 0;
}
}
- ///
- /// Returns a object that can be used to center the
- ///
- /// The center Pos.
- ///
- /// This creates a that is centered horizontally, is 50% of the way down,
- /// is 30% the height, and is 80% the width of the it added to.
- ///
- /// var textView = new TextView () {
- /// X = Pos.Center (),
- /// Y = Pos.Percent (50),
- /// Width = Dim.Percent (80),
- /// Height = Dim.Percent (30),
- /// };
- ///
- ///
- public static Pos Center ()
- {
- return new PosCenter ();
- }
-
- internal class PosAbsolute : Pos {
- int n;
- public PosAbsolute (int n) { this.n = n; }
-
- public override string ToString ()
- {
- return $"Absolute({n})";
- }
-
- internal override int Anchor (int width)
- {
- return n;
- }
-
- public override int GetHashCode () => n.GetHashCode ();
-
- public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the .
- public static implicit operator Pos (int n)
- {
- return new PosAbsolute (n);
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the .
- public static Pos At (int n)
- {
- return new PosAbsolute (n);
- }
-
- internal class PosCombine : Pos {
- internal Pos left, right;
- internal bool add;
- public PosCombine (bool add, Pos left, Pos right)
- {
- this.left = left;
- this.right = right;
- this.add = add;
- }
-
- internal override int Anchor (int width)
- {
- var la = left.Anchor (width);
- var ra = right.Anchor (width);
- if (add)
- return la + ra;
- else
- return la - ra;
- }
-
- public override string ToString ()
- {
- return $"Combine({left}{(add ? '+' : '-')}{right})";
- }
-
- }
-
- ///
- /// Adds a to a , yielding a new .
- ///
- /// The first to add.
- /// The second to add.
- /// The that is the sum of the values of left and right.
- public static Pos operator + (Pos left, Pos right)
- {
- if (left is PosAbsolute && right is PosAbsolute) {
- return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
- }
- PosCombine newPos = new PosCombine (true, left, right);
- SetPosCombine (left, newPos);
- return newPos;
- }
-
- ///
- /// Subtracts a from a , yielding a new .
- ///
- /// The to subtract from (the minuend).
- /// The to subtract (the subtrahend).
- /// The that is the left minus right.
- public static Pos operator - (Pos left, Pos right)
- {
- if (left is PosAbsolute && right is PosAbsolute) {
- return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
- }
- PosCombine newPos = new PosCombine (false, left, right);
- SetPosCombine (left, newPos);
- return newPos;
- }
-
- static void SetPosCombine (Pos left, PosCombine newPos)
- {
- var view = left as PosView;
- if (view != null) {
- view.Target.SetNeedsLayout ();
- }
- }
-
- internal class PosView : Pos {
- public View Target;
- int side;
- public PosView (View view, int side)
- {
- Target = view;
- this.side = side;
- }
- internal override int Anchor (int width)
- {
- switch (side) {
- case 0: return Target.Frame.X;
- case 1: return Target.Frame.Y;
- case 2: return Target.Frame.Right;
- case 3: return Target.Frame.Bottom;
- default:
- return 0;
- }
- }
-
public override string ToString ()
{
string tside;
@@ -336,365 +355,355 @@ namespace Terminal.Gui {
return $"View(side={tside},target={Target.ToString ()})";
}
- public override int GetHashCode () => Target.GetHashCode ();
+ public override int GetHashCode () => Target.GetHashCode ();
- public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
- }
+ public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
+ }
///
- /// Returns a object tracks the Left (X) position of the specified .
- ///
+ /// Creates a object that tracks the Left (X) position of the specified .
+ ///
/// The that depends on the other view.
/// The that will be tracked.
public static Pos Left (View view) => new PosView (view, 0);
///
- /// Returns a object tracks the Left (X) position of the specified .
+ /// Creates a object that tracks the Left (X) position of the specified .
///
/// The that depends on the other view.
/// The that will be tracked.
public static Pos X (View view) => new PosView (view, 0);
///
- /// Returns a object tracks the Top (Y) position of the specified .
+ /// Creates a object that tracks the Top (Y) position of the specified .
///
/// The that depends on the other view.
/// The that will be tracked.
public static Pos Top (View view) => new PosView (view, 1);
///
- /// Returns a object tracks the Top (Y) position of the specified .
+ /// Creates a object that tracks the Top (Y) position of the specified .
///
/// The that depends on the other view.
/// The that will be tracked.
public static Pos Y (View view) => new PosView(view, 1);
///
- /// Returns a object tracks the Right (X+Width) coordinate of the specified .
+ /// Creates a object that tracks the Right (X+Width) coordinate of the specified .
///
/// The that depends on the other view.
/// The that will be tracked.
public static Pos Right (View view) => new PosView (view, 2);
///
- /// Returns a object tracks the Bottom (Y+Height) coordinate of the specified
+ /// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified
///
/// The that depends on the other view.
/// The that will be tracked.
public static Pos Bottom (View view) => new PosView (view, 3);
- /// Serves as the default hash function.
- /// A hash code for the current object.
- public override int GetHashCode () => Anchor (0).GetHashCode ();
+ /// Serves as the default hash function.
+ /// A hash code for the current object.
+ public override int GetHashCode () => Anchor (0).GetHashCode ();
- /// Determines whether the specified object is equal to the current object.
- /// The object to compare with the current object.
- ///
- /// if the specified object is equal to the current object; otherwise, .
- public override bool Equals (object other) => other is Pos abs && abs == this;
+ /// Determines whether the specified object is equal to the current object.
+ /// The object to compare with the current object.
+ ///
+ /// if the specified object is equal to the current object; otherwise, .
+ public override bool Equals (object other) => other is Pos abs && abs == this;
+}
+
+///
+///
+/// A Dim object describes the dimensions of a . Dim is the type of the and
+/// properties of . Dim objects enable Computed Layout (see )
+/// to automatically manage the dimensions of a view.
+///
+///
+/// Integer values are implicitly convertible to an absolute . These objects are created using the static methods described below.
+/// The objects can be combined with the addition and subtraction operators.
+///
+///
+///
+///
+///
+///
+/// Dim Object
+/// Description
+///
+///
+///
+///
+/// Creates a object that computes the dimension by executing the provided function. The function will be called every time the dimension is needed.
+///
+///
+///
+///
+///
+/// Creates a object that is a percentage of the width or height of the SuperView.
+///
+///
+///
+///
+///
+/// Creates a object that fills the dimension, leaving the specified number of columns for a margin.
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Width of the specified .
+///
+///
+///
+///
+///
+/// Creates a object that tracks the Height of the specified .
+///
+///
+///
+///
+///
+///
+///
+public class Dim {
+ internal virtual int Anchor (int width) => 0;
+
+ ///
+ /// Creates a function object that computes the dimension by executing the provided function.
+ /// The function will be called every time the dimension is needed.
+ ///
+ /// The function to be executed.
+ /// The returned from the function.
+ public static Dim Function (Func function) => new DimFunc (function);
+
+ // Helper class to provide dynamic value by the execution of a function that returns an integer.
+ internal class DimFunc : Dim {
+ readonly Func _function;
+
+ public DimFunc (Func n) => _function = n;
+
+ internal override int Anchor (int width) => _function ();
+
+ public override string ToString () => $"DimFunc({_function ()})";
+
+ public override int GetHashCode () => _function.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimFunc f && f._function () == _function ();
}
///
- /// Dim properties of a to control the position.
+ /// Creates a percentage object that is a percentage of the width or height of the SuperView.
///
- ///
- ///
- /// Use the Dim objects on the Width or Height properties of a to control the position.
- ///
- ///
- /// These can be used to set the absolute position, when merely assigning an
- /// integer value (via the implicit integer to Pos conversion), and they can be combined
- /// to produce more useful layouts, like: Pos.Center - 3, which would shift the position
- /// of the 3 characters to the left after centering for example.
- ///
- ///
- public class Dim {
- internal virtual int Anchor (int width)
- {
- return 0;
+ /// The percent object.
+ /// A value between 0 and 100 representing the percentage.
+ /// If true the Percent is computed based on the remaining space after the X/Y anchor positions.
+ /// If false is computed based on the whole original space.
+ ///
+ /// This initializes a that is centered horizontally, is 50% of the way down,
+ /// is 30% the height, and is 80% the width of the it added to.
+ ///
+ /// var textView = new TextView () {
+ /// X = Pos.Center (),
+ /// Y = Pos.Percent (50),
+ /// Width = Dim.Percent (80),
+ /// Height = Dim.Percent (30),
+ /// };
+ ///
+ ///
+ public static Dim Percent (float n, bool r = false)
+ {
+ if (n is < 0 or > 100) {
+ throw new ArgumentException ("Percent value must be between 0 and 100");
}
- // Helper class to provide dynamic value by the execution of a function that returns an integer.
- internal class DimFunc : Dim {
- Func function;
-
- public DimFunc (Func n)
- {
- this.function = n;
- }
-
- internal override int Anchor (int width)
- {
- return function ();
- }
-
- public override string ToString ()
- {
- return $"DimFunc({function ()})";
- }
-
- public override int GetHashCode () => function.GetHashCode ();
-
- public override bool Equals (object other) => other is DimFunc f && f.function () == function ();
- }
-
- ///
- /// Creates a "DimFunc" from the specified function.
- ///
- /// The function to be executed.
- /// The returned from the function.
- public static Dim Function (Func function)
- {
- return new DimFunc (function);
- }
-
- internal class DimFactor : Dim {
- float factor;
- bool remaining;
-
- public DimFactor (float n, bool r = false)
- {
- factor = n;
- remaining = r;
- }
-
- internal override int Anchor (int width)
- {
- return (int)(width * factor);
- }
-
- public bool IsFromRemaining ()
- {
- return remaining;
- }
-
- public override string ToString ()
- {
- return $"Factor({factor},{remaining})";
- }
-
- public override int GetHashCode () => factor.GetHashCode ();
-
- public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
- }
-
- ///
- /// Creates a percentage object
- ///
- /// The percent object.
- /// A value between 0 and 100 representing the percentage.
- /// If true the Percent is computed based on the remaining space after the X/Y anchor positions. If false is computed based on the whole original space.
- ///
- /// This initializes a that is centered horizontally, is 50% of the way down,
- /// is 30% the height, and is 80% the width of the it added to.
- ///
- /// var textView = new TextView () {
- /// X = Pos.Center (),
- /// Y = Pos.Percent (50),
- /// Width = Dim.Percent (80),
- /// Height = Dim.Percent (30),
- /// };
- ///
- ///
- public static Dim Percent (float n, bool r = false)
- {
- if (n < 0 || n > 100)
- throw new ArgumentException ("Percent value must be between 0 and 100");
-
- return new DimFactor (n / 100, r);
- }
-
- internal class DimAbsolute : Dim {
- int n;
- public DimAbsolute (int n) { this.n = n; }
-
- public override string ToString ()
- {
- return $"Absolute({n})";
- }
-
- internal override int Anchor (int width)
- {
- return n;
- }
-
- public override int GetHashCode () => n.GetHashCode ();
-
- public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
- }
-
- internal class DimFill : Dim {
- int margin;
- public DimFill (int margin) { this.margin = margin; }
-
- public override string ToString ()
- {
- return $"Fill({margin})";
- }
-
- internal override int Anchor (int width)
- {
- return width - margin;
- }
-
- public override int GetHashCode () => margin.GetHashCode ();
-
- public override bool Equals (object other) => other is DimFill fill && fill.margin == margin;
- }
-
- ///
- /// Initializes a new instance of the class that fills the dimension, but leaves the specified number of colums for a margin.
- ///
- /// The Fill dimension.
- /// Margin to use.
- public static Dim Fill (int margin = 0)
- {
- return new DimFill (margin);
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the pos.
- public static implicit operator Dim (int n)
- {
- return new DimAbsolute (n);
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the .
- public static Dim Sized (int n)
- {
- return new DimAbsolute (n);
- }
-
- internal class DimCombine : Dim {
- internal Dim left, right;
- internal bool add;
- public DimCombine (bool add, Dim left, Dim right)
- {
- this.left = left;
- this.right = right;
- this.add = add;
- }
-
- internal override int Anchor (int width)
- {
- var la = left.Anchor (width);
- var ra = right.Anchor (width);
- if (add)
- return la + ra;
- else
- return la - ra;
- }
-
- public override string ToString ()
- {
- return $"Combine({left}{(add ? '+' : '-')}{right})";
- }
-
- }
-
- ///
- /// Adds a to a , yielding a new .
- ///
- /// The first to add.
- /// The second to add.
- /// The that is the sum of the values of left and right.
- public static Dim operator + (Dim left, Dim right)
- {
- if (left is DimAbsolute && right is DimAbsolute) {
- return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
- }
- DimCombine newDim = new DimCombine (true, left, right);
- SetDimCombine (left, newDim);
- return newDim;
- }
-
- ///
- /// Subtracts a from a , yielding a new .
- ///
- /// The to subtract from (the minuend).
- /// The to subtract (the subtrahend).
- /// The that is the left minus right.
- public static Dim operator - (Dim left, Dim right)
- {
- if (left is DimAbsolute && right is DimAbsolute) {
- return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
- }
- DimCombine newDim = new DimCombine (false, left, right);
- SetDimCombine (left, newDim);
- return newDim;
- }
-
- static void SetDimCombine (Dim left, DimCombine newPos)
- {
- var view = left as DimView;
- if (view != null) {
- view.Target.SetNeedsLayout ();
- }
- }
-
- internal class DimView : Dim {
- public View Target;
- int side;
- public DimView (View view, int side)
- {
- Target = view;
- this.side = side;
- }
-
- internal override int Anchor (int width)
- {
- switch (side) {
- case 0: return Target.Frame.Height;
- case 1: return Target.Frame.Width;
- default:
- return 0;
- }
- }
-
- public override string ToString ()
- {
- string tside;
- switch (side) {
- case 0: tside = "Height"; break;
- case 1: tside = "Width"; break;
- default: tside = "unknown"; break;
- }
- return $"View({tside},{Target.ToString ()})";
- }
-
- public override int GetHashCode () => Target.GetHashCode ();
-
- public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
- }
- ///
- /// Returns a object tracks the Width of the specified .
- ///
- /// The of the other .
- /// The view that will be tracked.
- public static Dim Width (View view) => new DimView (view, 1);
-
- ///
- /// Returns a object tracks the Height of the specified .
- ///
- /// The of the other .
- /// The view that will be tracked.
- public static Dim Height (View view) => new DimView (view, 0);
-
- /// Serves as the default hash function.
- /// A hash code for the current object.
- public override int GetHashCode () => Anchor (0).GetHashCode ();
-
- /// Determines whether the specified object is equal to the current object.
- /// The object to compare with the current object.
- ///
- /// if the specified object is equal to the current object; otherwise, .
- public override bool Equals (object other) => other is Dim abs && abs == this;
+ return new DimFactor (n / 100, r);
}
-}
+
+ internal class DimFactor : Dim {
+ readonly float _factor;
+ readonly bool _remaining;
+
+ public DimFactor (float n, bool r = false)
+ {
+ _factor = n;
+ _remaining = r;
+ }
+
+ internal override int Anchor (int width) => (int)(width * _factor);
+
+ public bool IsFromRemaining () => _remaining;
+
+ public override string ToString () => $"Factor({_factor},{_remaining})";
+
+ public override int GetHashCode () => _factor.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimFactor f && f._factor == _factor && f._remaining == _remaining;
+ }
+
+
+ internal class DimAbsolute : Dim {
+ readonly int _n;
+ public DimAbsolute (int n) => _n = n;
+
+ public override string ToString () => $"Absolute({_n})";
+
+ internal override int Anchor (int width) => _n;
+
+ public override int GetHashCode () => _n.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimAbsolute abs && abs._n == _n;
+ }
+
+ internal class DimFill : Dim {
+ readonly int _margin;
+ public DimFill (int margin) => _margin = margin;
+
+ public override string ToString () => $"Fill({_margin})";
+
+ internal override int Anchor (int width) => width - _margin;
+
+ public override int GetHashCode () => _margin.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimFill fill && fill._margin == _margin;
+ }
+
+ ///
+ /// Creates a object that fills the dimension, leaving the specified number of columns for a margin.
+ ///
+ /// The Fill dimension.
+ /// Margin to use.
+ public static Dim Fill (int margin = 0) => new DimFill (margin);
+
+ ///
+ /// Creates an Absolute from the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the pos.
+ public static implicit operator Dim (int n) => new DimAbsolute (n);
+
+ ///
+ /// Creates an Absolute from the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the .
+ public static Dim Sized (int n) => new DimAbsolute (n);
+
+ internal class DimCombine : Dim {
+ internal Dim _left, _right;
+ internal bool _add;
+
+ public DimCombine (bool add, Dim left, Dim right)
+ {
+ _left = left;
+ _right = right;
+ _add = add;
+ }
+
+ internal override int Anchor (int width)
+ {
+ int la = _left.Anchor (width);
+ int ra = _right.Anchor (width);
+ if (_add) {
+ return la + ra;
+ } else {
+ return la - ra;
+ }
+ }
+
+ public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})";
+ }
+
+ ///
+ /// Adds a to a , yielding a new .
+ ///
+ /// The first to add.
+ /// The second to add.
+ /// The that is the sum of the values of left and right.
+ public static Dim operator + (Dim left, Dim right)
+ {
+ if (left is DimAbsolute && right is DimAbsolute) {
+ return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
+ }
+ var newDim = new DimCombine (true, left, right);
+ SetDimCombine (left, newDim);
+ return newDim;
+ }
+
+ ///
+ /// Subtracts a from a , yielding a new .
+ ///
+ /// The to subtract from (the minuend).
+ /// The to subtract (the subtrahend).
+ /// The that is the left minus right.
+ public static Dim operator - (Dim left, Dim right)
+ {
+ if (left is DimAbsolute && right is DimAbsolute) {
+ return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
+ }
+ var newDim = new DimCombine (false, left, right);
+ SetDimCombine (left, newDim);
+ return newDim;
+ }
+
+ // BUGBUG: newPos is never used.
+ static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout ();
+
+ internal class DimView : Dim {
+ public View Target { get; init; }
+ readonly int _side;
+
+ public DimView (View view, int side)
+ {
+ Target = view;
+ _side = side;
+ }
+
+ internal override int Anchor (int width) => _side switch {
+ 0 => Target.Frame.Height,
+ 1 => Target.Frame.Width,
+ _ => 0
+ };
+
+ public override string ToString ()
+ {
+ if (Target == null) {
+ throw new NullReferenceException ();
+ }
+ string tside = _side switch {
+ 0 => "Height",
+ 1 => "Width",
+ _ => "unknown"
+ };
+ return $"View({tside},{Target})";
+ }
+
+ public override int GetHashCode () => Target.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
+ }
+
+ ///
+ /// Creates a object that tracks the Width of the specified .
+ ///
+ /// The width of the other .
+ /// The view that will be tracked.
+ public static Dim Width (View view) => new DimView (view, 1);
+
+ ///
+ /// Creates a object that tracks the Height of the specified .
+ ///
+ /// The height of the other .
+ /// The view that will be tracked.
+ public static Dim Height (View view) => new DimView (view, 0);
+
+ /// Serves as the default hash function.
+ /// A hash code for the current object.
+ public override int GetHashCode () => Anchor (0).GetHashCode ();
+
+ /// Determines whether the specified object is equal to the current object.
+ /// The object to compare with the current object.
+ ///
+ /// if the specified object is equal to the current object; otherwise, .
+ public override bool Equals (object other) => other is Dim abs && abs == this;
+}
\ No newline at end of file
diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs
new file mode 100644
index 000000000..0ca320c30
--- /dev/null
+++ b/Terminal.Gui/View/Layout/ViewLayout.cs
@@ -0,0 +1,1219 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+///
+/// Determines the LayoutStyle for a , if Absolute, during , the
+/// value from the will be used, if the value is Computed, then
+/// will be updated from the X, Y objects and the Width and Height objects.
+///
+public enum LayoutStyle {
+ ///
+ /// The position and size of the view are based .
+ ///
+ Absolute,
+
+ ///
+ /// The position and size of the view will be computed based on
+ /// , , , and . will
+ /// provide the absolute computed values.
+ ///
+ Computed
+}
+
+public partial class View {
+ // The frame for the object. Relative to the SuperView's Bounds.
+ Rect _frame;
+
+ ///
+ /// Gets or sets the frame for the view. The frame is relative to the 's .
+ ///
+ /// The frame.
+ ///
+ ///
+ /// Change the Frame when using the layout style to move or resize views.
+ ///
+ ///
+ /// Altering the Frame of a view will trigger the redrawing of the
+ /// view as well as the redrawing of the affected regions of the .
+ ///
+ ///
+ public virtual Rect Frame {
+ get => _frame;
+ set {
+ _frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
+ if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
+ LayoutFrames ();
+ TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+ SetNeedsLayout ();
+ SetNeedsDisplay ();
+ }
+ }
+ }
+
+ ///
+ /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView.
+ /// The margin offsets the from the .
+ ///
+ ///
+ ///
+ /// The frames (, , and ) are not part of the View's content
+ /// and are not clipped by the View's Clip Area.
+ ///
+ ///
+ /// Changing the size of a frame (, , or )
+ /// will change the size of the and trigger to update the layout of the
+ /// and its .
+ ///
+ ///
+ public Frame Margin { get; private set; }
+
+ ///
+ /// The frame (specified as a ) inside of the view that offsets the from the .
+ /// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title.
+ /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and
+ /// title will take up the first row and the second row will be filled with spaces.
+ ///
+ ///
+ ///
+ /// provides a simple helper for turning a simple border frame on or off.
+ ///
+ ///
+ /// The frames (, , and ) are not part of the View's content
+ /// and are not clipped by the View's Clip Area.
+ ///
+ ///
+ /// Changing the size of a frame (, , or )
+ /// will change the size of the and trigger to update the layout of the
+ /// and its .
+ ///
+ ///
+ public Frame Border { get; private set; }
+
+ ///
+ /// Gets or sets whether the view has a one row/col thick border.
+ ///
+ ///
+ ///
+ /// This is a helper for manipulating the view's . Setting this property to any value other than
+ /// is equivalent to setting 's
+ /// to `1` and to the value.
+ ///
+ ///
+ /// Setting this property to is equivalent to setting 's
+ /// to `0` and to .
+ ///
+ ///
+ /// For more advanced customization of the view's border, manipulate see directly.
+ ///
+ ///
+ public LineStyle BorderStyle {
+ get => Border?.BorderStyle ?? LineStyle.None;
+ set {
+ if (Border == null) {
+ throw new InvalidOperationException ("Border is null; this is likely a bug.");
+ }
+ if (value != LineStyle.None) {
+ Border.Thickness = new Thickness (1);
+ } else {
+ Border.Thickness = new Thickness (0);
+ }
+ Border.BorderStyle = value;
+ LayoutFrames ();
+ SetNeedsLayout ();
+ }
+ }
+
+ ///
+ /// The frame (specified as a ) inside of the view that offsets the from the .
+ ///
+ ///
+ ///
+ /// The frames (, , and ) are not part of the View's content
+ /// and are not clipped by the View's Clip Area.
+ ///
+ ///
+ /// Changing the size of a frame (, , or )
+ /// will change the size of the and trigger to update the layout of the
+ /// and its .
+ ///
+ ///
+ public Frame Padding { get; private set; }
+
+ ///
+ /// Helper to get the total thickness of the , , and .
+ ///
+ /// A thickness that describes the sum of the Frames' thicknesses.
+ public Thickness GetFramesThickness ()
+ {
+ int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
+ int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
+ int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
+ int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
+ return new Thickness (left, top, right, bottom);
+ }
+
+ ///
+ /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
+ /// , and .
+ ///
+ public Point GetBoundsOffset () => new Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
+
+ ///
+ /// Creates the view's objects. This internal method is overridden by Frame to do nothing
+ /// to prevent recursion during View construction.
+ ///
+ internal virtual void CreateFrames ()
+ {
+ void ThicknessChangedHandler (object sender, EventArgs e)
+ {
+ LayoutFrames ();
+ SetNeedsLayout ();
+ SetNeedsDisplay ();
+ }
+
+ if (Margin != null) {
+ Margin.ThicknessChanged -= ThicknessChangedHandler;
+ Margin.Dispose ();
+ }
+ Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) };
+ Margin.ThicknessChanged += ThicknessChangedHandler;
+ Margin.Parent = this;
+
+ if (Border != null) {
+ Border.ThicknessChanged -= ThicknessChangedHandler;
+ Border.Dispose ();
+ }
+ Border = new Frame () { Id = "Border", Thickness = new Thickness (0) };
+ Border.ThicknessChanged += ThicknessChangedHandler;
+ Border.Parent = this;
+
+ // TODO: Create View.AddAdornment
+
+ if (Padding != null) {
+ Padding.ThicknessChanged -= ThicknessChangedHandler;
+ Padding.Dispose ();
+ }
+ Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) };
+ Padding.ThicknessChanged += ThicknessChangedHandler;
+ Padding.Parent = this;
+ }
+
+ LayoutStyle _layoutStyle;
+
+ ///
+ /// Controls how the View's is computed during . If the style is set to
+ /// ,
+ /// LayoutSubviews does not change the . If the style is
+ /// the is updated using
+ /// the , , , and properties.
+ ///
+ /// The layout style.
+ public LayoutStyle LayoutStyle {
+ get => _layoutStyle;
+ set {
+ _layoutStyle = value;
+ SetNeedsLayout ();
+ }
+ }
+
+ ///
+ /// The view's content area.
+ ///
+ /// SubViews are positioned relative to Bounds.
+ ///
+ ///
+ /// Drawing is clipped to Bounds ( clips drawing to Bounds.Size).
+ ///
+ ///
+ /// Mouse events are reported relative to Bounds.
+ ///
+ ///
+ /// The view's content area.
+ ///
+ ///
+ /// The of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use
+ /// .
+ ///
+ ///
+ /// When using , Bounds is not valid until after the view has been initialized (after has been called and
+ /// has fired). Accessing this property before the view is initialized is considered an error./>
+ ///
+ ///
+ public virtual Rect Bounds {
+ get {
+#if DEBUG
+ if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
+ Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
+ Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design.");
+ }
+#endif // DEBUG
+ //var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size);
+ var frameRelativeBounds = FrameGetInsideBounds ();
+ return new Rect (default, frameRelativeBounds.Size);
+ }
+ set {
+ // BUGBUG: Margin etc.. can be null (if typeof(Frame))
+ Frame = new Rect (Frame.Location,
+ new Size (
+ value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
+ value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical
+ )
+ );
+ }
+ }
+
+ Rect FrameGetInsideBounds ()
+ {
+ if (Margin == null || Border == null || Padding == null) {
+ return new Rect (default, Frame.Size);
+ }
+ int width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
+ int height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
+ return new Rect (Point.Empty, new Size (width, height));
+ }
+
+ Pos _x, _y;
+
+ ///
+ /// Gets or sets the X position for the view (the column). Only used if the is .
+ ///
+ /// The X Position.
+ ///
+ ///
+ /// If is changing this property has no effect and its value is indeterminate.
+ ///
+ ///
+ /// is the same as Pos.Absolute(0).
+ ///
+ ///
+ public Pos X {
+ get => VerifyIsInitialized (_x);
+ set {
+ // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it?
+
+ if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) {
+ CheckAbsolute (nameof (X), _x, value);
+ }
+
+ _x = value;
+
+ OnResizeNeeded ();
+ }
+ }
+
+
+ ///
+ /// Gets or sets the Y position for the view (the row). Only used if the is .
+ ///
+ /// The X Position.
+ ///
+ ///
+ /// If is changing this property has no effect and its value is indeterminate.
+ ///
+ ///
+ /// is the same as Pos.Absolute(0).
+ ///
+ ///
+ public Pos Y {
+ get => VerifyIsInitialized (_y);
+ set {
+ // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it?
+
+ if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) {
+ CheckAbsolute (nameof (Y), _y, value);
+ }
+
+ _y = value;
+
+ OnResizeNeeded ();
+ }
+ }
+ Dim _width, _height;
+
+ ///
+ /// Gets or sets the width of the view. Only used when is .
+ ///
+ /// The width.
+ ///
+ ///
+ /// If is changing this property
+ /// has no effect and its value is indeterminate.
+ ///
+ ///
+ /// is the same as Dim.Fill (0).
+ ///
+ ///
+ public Dim Width {
+ get => VerifyIsInitialized (_width);
+ set {
+ // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
+ if (ValidatePosDim) {
+ if (LayoutStyle == LayoutStyle.Computed) {
+ CheckAbsolute (nameof (Width), _width, value);
+ }
+ }
+
+ _width = value;
+
+ if (ValidatePosDim) {
+ bool isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
+
+ if (IsAdded && AutoSize && !isValidNewAutSize) {
+ throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
+ }
+ }
+ OnResizeNeeded ();
+ }
+ }
+
+ ///
+ /// Gets or sets the height of the view. Only used when is .
+ ///
+ /// The width.
+ ///
+ ///
+ /// If is changing this property
+ /// has no effect and its value is indeterminate.
+ ///
+ ///
+ /// is the same as Dim.Fill (0).
+ ///
+ ///
+ public Dim Height {
+ get => VerifyIsInitialized (_height);
+ set {
+ // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
+ if (ValidatePosDim) {
+ if (LayoutStyle == LayoutStyle.Computed) {
+ CheckAbsolute (nameof (Height), _height, value);
+ }
+ }
+
+ _height = value;
+
+ if (ValidatePosDim) {
+ bool isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
+
+ if (IsAdded && AutoSize && !isValidNewAutSize) {
+ throw new InvalidOperationException ("Must set AutoSize to false before setting the Height.");
+ }
+ }
+ OnResizeNeeded ();
+ }
+ }
+
+ // Diagnostics to highlight when X or Y is read before the view has been initialized
+ Pos VerifyIsInitialized (Pos pos)
+ {
+#if DEBUG
+ if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
+ Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug.");
+ }
+#endif // DEBUG
+ return pos;
+ }
+
+ // Diagnostics to highlight when Width or Height is read before the view has been initialized
+ Dim VerifyIsInitialized (Dim dim)
+ {
+#if DEBUG
+ if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
+ Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug.");
+ }
+#endif // DEBUG
+ return dim;
+ }
+
+ ///
+ /// Gets or sets whether validation of and occurs.
+ ///
+ ///
+ /// Setting this to will enable validation of , , , and
+ /// during set operations and in .If invalid settings are discovered exceptions will be thrown indicating the error.
+ /// This will impose a performance penalty and thus should only be used for debugging.
+ ///
+ public bool ValidatePosDim { get; set; }
+
+ ///
+ /// Throws an if is or .
+ /// Used when is turned on to verify correct behavior.
+ ///
+ ///
+ /// Does not verify if this view is Toplevel (WHY??!?).
+ ///
+ /// The property name.
+ ///
+ ///
+ void CheckAbsolute (string prop, object oldValue, object newValue)
+ {
+ if (!IsInitialized || !ValidatePosDim || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) {
+ return;
+ }
+
+ if (oldValue.GetType () != newValue.GetType () && newValue is (Pos.PosAbsolute or Dim.DimAbsolute)) {
+ throw new ArgumentException ($@"{prop} must not be Absolute if LayoutStyle is Computed", prop);
+ }
+ }
+
+ ///
+ /// Called whenever the view needs to be resized. Sets and
+ /// triggers a call.
+ ///
+ ///
+ /// Can be overridden if the view resize behavior is different than the default.
+ ///
+ protected virtual void OnResizeNeeded ()
+ {
+ int actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X;
+ int actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y;
+
+ if (AutoSize) {
+ //if (TextAlignment == TextAlignment.Justified) {
+ // throw new InvalidOperationException ("TextAlignment.Justified cannot be used with AutoSize");
+ //}
+ var s = GetAutoSize ();
+ int w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width;
+ int h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height;
+ _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame!
+ } else {
+ int w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width;
+ int h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height;
+ // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm...
+ // This is needed for DimAbsolute values by setting the frame before LayoutSubViews.
+ _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame!
+ }
+ //// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case
+ if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
+ SetFrameToFitText ();
+ LayoutFrames ();
+ TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+ SetNeedsLayout ();
+ SetNeedsDisplay ();
+ }
+ }
+
+ internal bool LayoutNeeded { get; private set; } = true;
+
+ internal void SetNeedsLayout ()
+ {
+ if (LayoutNeeded) {
+ return;
+ }
+ LayoutNeeded = true;
+ foreach (var view in Subviews) {
+ view.SetNeedsLayout ();
+ }
+ TextFormatter.NeedsFormat = true;
+ SuperView?.SetNeedsLayout ();
+ }
+
+ ///
+ /// Indicates that the view does not need to be laid out.
+ ///
+ protected void ClearLayoutNeeded () => LayoutNeeded = false;
+
+ ///
+ /// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means
+ /// relative to the View's 's .
+ ///
+ /// The coordinate relative to the 's .
+ /// Screen-relative column.
+ /// Screen-relative row.
+ public Point ScreenToFrame (int x, int y)
+ {
+ var superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
+ var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y);
+ if (SuperView != null) {
+ var superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y);
+ ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y);
+ }
+ return ret;
+ }
+
+ ///
+ /// Converts a screen-relative coordinate to a bounds-relative coordinate.
+ ///
+ /// The coordinate relative to this view's .
+ /// Screen-relative column.
+ /// Screen-relative row.
+ public Point ScreenToBounds (int x, int y)
+ {
+ var screen = ScreenToFrame (x, y);
+ var boundsOffset = GetBoundsOffset ();
+ return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y);
+ }
+
+ ///
+ /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally clamped to the screen dimensions.
+ ///
+ /// -relative column.
+ /// -relative row.
+ /// Absolute column; screen-relative.
+ /// Absolute row; screen-relative.
+ /// If , and will be clamped to the
+ /// screen dimensions (will never be negative and will always be less than and
+ /// , respectively.
+ public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true)
+ {
+ var boundsOffset = GetBoundsOffset ();
+ rx = x + Frame.X + boundsOffset.X;
+ ry = y + Frame.Y + boundsOffset.Y;
+
+ var super = SuperView;
+ while (super != null) {
+ boundsOffset = super.GetBoundsOffset ();
+ rx += super.Frame.X + boundsOffset.X;
+ ry += super.Frame.Y + boundsOffset.Y;
+ super = super.SuperView;
+ }
+
+ // The following ensures that the cursor is always in the screen boundaries.
+ if (clamped) {
+ ry = Math.Min (ry, Driver.Rows - 1);
+ rx = Math.Min (rx, Driver.Cols - 1);
+ }
+ }
+
+ ///
+ /// Converts a -relative region to a screen-relative region.
+ ///
+ public Rect BoundsToScreen (Rect region)
+ {
+ BoundsToScreen (region.X, region.Y, out int x, out int y, false);
+ return new Rect (x, y, region.Width, region.Height);
+ }
+
+ ///
+ /// Gets the with a screen-relative location.
+ ///
+ /// The location and size of the view in screen-relative coordinates.
+ public virtual Rect FrameToScreen ()
+ {
+ var ret = Frame;
+ var super = SuperView;
+ while (super != null) {
+ var boundsOffset = super.GetBoundsOffset ();
+ ret.X += super.Frame.X + boundsOffset.X;
+ ret.Y += super.Frame.Y + boundsOffset.Y;
+ super = super.SuperView;
+ }
+ return ret;
+ }
+
+ // TODO: Come up with a better name for this method. "SetRelativeLayout" lacks clarity and confuses. AdjustSizeAndPosition?
+ ///
+ /// Applies the view's position (, ) and dimension (, and ) to
+ /// , given a rectangle describing the SuperView's Bounds (nominally the same as this.SuperView.Bounds).
+ ///
+ /// The rectangle describing the SuperView's Bounds (nominally the same as this.SuperView.Bounds).
+ internal void SetRelativeLayout (Rect superviewBounds)
+ {
+ int newX, newW, newY, newH;
+ var autosize = Size.Empty;
+
+ if (AutoSize) {
+ // Note this is global to this function and used as such within the local functions defined
+ // below. In v2 AutoSize will be re-factored to not need to be dealt with in this function.
+ autosize = GetAutoSize ();
+ }
+
+ // Returns the new dimension (width or height) and location (x or y) for the View given
+ // the superview's Bounds
+ // the current Pos (View.X or View.Y)
+ // the current Dim (View.Width or View.Height)
+ // This method is called recursively if pos is Pos.PosCombine
+ (int newLocation, int newDimension) GetNewLocationAndDimension (bool width, Rect superviewBounds, Pos pos, Dim dim, int autosizeDimension)
+ {
+ // Gets the new dimension (width or height, dependent on `width`) of the given Dim given:
+ // location: the current location (x or y)
+ // dimension: the current dimension (width or height)
+ // autosize: the size to use if autosize = true
+ // This mehod is recursive if d is Dim.DimCombine
+ int GetNewDimension (Dim d, int location, int dimension, int autosize)
+ {
+ int newDimension;
+ switch (d) {
+ case null:
+ // dim == null is the same as dim == Dim.FIll (0)
+ newDimension = AutoSize ? autosize : dimension;
+ break;
+
+ case Dim.DimCombine combine:
+ int leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
+ int rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
+ if (combine._add) {
+ newDimension = leftNewDim + rightNewDim;
+ } else {
+ newDimension = leftNewDim - rightNewDim;
+ }
+ newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+ break;
+
+ case Dim.DimFactor factor when !factor.IsFromRemaining ():
+ newDimension = d.Anchor (dimension);
+ newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+ break;
+
+ case Dim.DimFill:
+ default:
+ newDimension = Math.Max (d.Anchor (dimension - location), 0);
+ newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+ break;
+ }
+
+ return newDimension;
+ }
+
+ int newDimension, newLocation;
+ int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height;
+
+ // Determine new location
+ switch (pos) {
+ case Pos.PosCenter posCenter:
+ // For Center, the dimension is dependent on location, but we need to force getting the dimension first
+ // using a location of 0
+ newDimension = Math.Max (GetNewDimension (dim, 0, superviewDimension, autosizeDimension), 0);
+ newLocation = posCenter.Anchor (superviewDimension - newDimension);
+ newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+ break;
+
+ case Pos.PosCombine combine:
+ int left, right;
+ (left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension);
+ (right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension);
+ if (combine._add) {
+ newLocation = left + right;
+ } else {
+ newLocation = left - right;
+ }
+ newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+ break;
+
+ case Pos.PosAnchorEnd:
+ case Pos.PosAbsolute:
+ case null:
+ case Pos.PosFactor:
+ case Pos.PosFunc:
+ case Pos.PosView:
+ default:
+ newLocation = pos?.Anchor (superviewDimension) ?? 0;
+ newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+ break;
+ }
+
+
+ return (newLocation, newDimension);
+ }
+
+
+ // horizontal/width
+ (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width);
+
+ // vertical/height
+ (newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height);
+
+ var r = new Rect (newX, newY, newW, newH);
+ if (Frame != r) {
+ Frame = r;
+ // BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
+ if (!SetFrameToFitText ()) {
+ TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+ }
+ }
+ }
+
+ ///
+ /// Fired after the View's method has completed.
+ ///
+ ///
+ /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed.
+ ///
+ public event EventHandler LayoutStarted;
+
+ ///
+ /// Raises the event. Called from before any subviews have been laid out.
+ ///
+ internal virtual void OnLayoutStarted (LayoutEventArgs args) => LayoutStarted?.Invoke (this, args);
+
+ ///
+ /// Fired after the View's method has completed.
+ ///
+ ///
+ /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed.
+ ///
+ public event EventHandler LayoutComplete;
+
+ ///
+ /// Event called only once when the is being initialized for the first time.
+ /// Allows configurations and assignments to be performed before the being shown.
+ /// This derived from to allow notify all the views that are being initialized.
+ ///
+ public event EventHandler Initialized;
+
+ ///
+ /// Raises the event. Called from before all sub-views have been laid out.
+ ///
+ internal virtual void OnLayoutComplete (LayoutEventArgs args) => LayoutComplete?.Invoke (this, args);
+
+ internal void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges)
+ {
+ switch (pos) {
+ case Pos.PosView pv:
+ // See #2461
+ //if (!from.InternalSubviews.Contains (pv.Target)) {
+ // throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}");
+ //}
+ if (pv.Target != this) {
+ nEdges.Add ((pv.Target, from));
+ }
+ return;
+ case Pos.PosCombine pc:
+ CollectPos (pc._left, from, ref nNodes, ref nEdges);
+ CollectPos (pc._right, from, ref nNodes, ref nEdges);
+ break;
+ }
+ }
+
+ internal void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges)
+ {
+ switch (dim) {
+ case Dim.DimView dv:
+ // See #2461
+ //if (!from.InternalSubviews.Contains (dv.Target)) {
+ // throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}");
+ //}
+ if (dv.Target != this) {
+ nEdges.Add ((dv.Target, from));
+ }
+ return;
+ case Dim.DimCombine dc:
+ CollectDim (dc._left, from, ref nNodes, ref nEdges);
+ CollectDim (dc._right, from, ref nNodes, ref nEdges);
+ break;
+ }
+ }
+
+ internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges)
+ {
+ // BUGBUG: This should really only work on initialized subviews
+ foreach (var v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/) {
+ nNodes.Add (v);
+ if (v._layoutStyle != LayoutStyle.Computed) {
+ continue;
+ }
+ CollectPos (v.X, v, ref nNodes, ref nEdges);
+ CollectPos (v.Y, v, ref nNodes, ref nEdges);
+ CollectDim (v.Width, v, ref nNodes, ref nEdges);
+ CollectDim (v.Height, v, ref nNodes, ref nEdges);
+ }
+ }
+
+ // https://en.wikipedia.org/wiki/Topological_sorting
+ internal static List TopologicalSort (View superView, IEnumerable nodes, ICollection<(View From, View To)> edges)
+ {
+ var result = new List ();
+
+ // Set of all nodes with no incoming edges
+ var noEdgeNodes = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
+
+ while (noEdgeNodes.Any ()) {
+ // remove a node n from S
+ var n = noEdgeNodes.First ();
+ noEdgeNodes.Remove (n);
+
+ // add n to tail of L
+ if (n != superView) {
+ result.Add (n);
+ }
+
+ // for each node m with an edge e from n to m do
+ foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
+ var m = e.To;
+
+ // remove edge e from the graph
+ edges.Remove (e);
+
+ // if m has no other incoming edges then
+ if (edges.All (me => !me.To.Equals (m)) && m != superView) {
+ // insert m into S
+ noEdgeNodes.Add (m);
+ }
+ }
+ }
+
+ if (!edges.Any ()) {
+ return result;
+ }
+
+ foreach ((var from, var to) in edges) {
+ if (from == to) {
+ // if not yet added to the result, add it and remove from edge
+ if (result.Find (v => v == from) == null) {
+ result.Add (from);
+ }
+ edges.Remove ((from, to));
+ } else if (from.SuperView == to.SuperView) {
+ // if 'from' is not yet added to the result, add it
+ if (result.Find (v => v == from) == null) {
+ result.Add (from);
+ }
+ // if 'to' is not yet added to the result, add it
+ if (result.Find (v => v == to) == null) {
+ result.Add (to);
+ }
+ // remove from edge
+ edges.Remove ((from, to));
+ } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) {
+ if (ReferenceEquals (from.SuperView, to)) {
+ throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\").");
+ } else {
+ throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?");
+ }
+ }
+ }
+ // return L (a topologically sorted order)
+ return result;
+ } // TopologicalSort
+
+ ///
+ /// Overriden by to do nothing, as the does not have frames.
+ ///
+ internal virtual void LayoutFrames ()
+ {
+ if (Margin == null) {
+ return; // CreateFrames() has not been called yet
+ }
+
+ if (Margin.Frame.Size != Frame.Size) {
+ Margin._frame = new Rect (Point.Empty, Frame.Size);
+ Margin.X = 0;
+ Margin.Y = 0;
+ Margin.Width = Frame.Size.Width;
+ Margin.Height = Frame.Size.Height;
+ Margin.SetNeedsLayout ();
+ Margin.LayoutSubviews ();
+ Margin.SetNeedsDisplay ();
+ }
+
+ var border = Margin.Thickness.GetInside (Margin.Frame);
+ if (border != Border.Frame) {
+ Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size);
+ Border.X = border.Location.X;
+ Border.Y = border.Location.Y;
+ Border.Width = border.Size.Width;
+ Border.Height = border.Size.Height;
+ Border.SetNeedsLayout ();
+ Border.LayoutSubviews ();
+ Border.SetNeedsDisplay ();
+ }
+
+ var padding = Border.Thickness.GetInside (Border.Frame);
+ if (padding != Padding.Frame) {
+ Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size);
+ Padding.X = padding.Location.X;
+ Padding.Y = padding.Location.Y;
+ Padding.Width = padding.Size.Width;
+ Padding.Height = padding.Size.Height;
+ Padding.SetNeedsLayout ();
+ Padding.LayoutSubviews ();
+ Padding.SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// Invoked when a view starts executing or when the dimensions of the view have changed, for example in
+ /// response to the container view or terminal resizing.
+ ///
+ ///
+ /// Raises the event) before it returns.
+ ///
+ public virtual void LayoutSubviews ()
+ {
+ if (!IsInitialized) {
+ Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}");
+ }
+
+ if (!LayoutNeeded) {
+ return;
+ }
+
+ LayoutFrames ();
+
+ var oldBounds = Bounds;
+ OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
+
+ TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+
+ // Sort out the dependencies of the X, Y, Width, Height properties
+ var nodes = new HashSet ();
+ var edges = new HashSet<(View, View)> ();
+ CollectAll (this, ref nodes, ref edges);
+ var ordered = TopologicalSort (SuperView, nodes, edges);
+ foreach (var v in ordered) {
+ LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
+ }
+
+ // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
+ // Use LayoutSubview with the Frame of the 'from'
+ if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) {
+ foreach ((var from, var to) in edges) {
+ LayoutSubview (to, from.Frame);
+ }
+ }
+
+ LayoutNeeded = false;
+
+ OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
+ }
+
+ void LayoutSubview (View v, Rect contentArea)
+ {
+ if (v.LayoutStyle == LayoutStyle.Computed) {
+ v.SetRelativeLayout (contentArea);
+ }
+
+ v.LayoutSubviews ();
+ v.LayoutNeeded = false;
+ }
+
+ bool _autoSize;
+
+ ///
+ /// Gets or sets a flag that determines whether the View will be automatically resized to fit the
+ /// within
+ ///
+ /// The default is . Set to to turn on AutoSize. If then
+ /// and will be used if can fit;
+ /// if won't fit the view will be resized as needed.
+ ///
+ ///
+ /// In addition, if is the new values of and
+ /// must be of the same types of the existing one to avoid breaking the settings.
+ ///
+ ///
+ public virtual bool AutoSize {
+ get => _autoSize;
+ set {
+ bool v = ResizeView (value);
+ TextFormatter.AutoSize = v;
+ if (_autoSize != v) {
+ _autoSize = v;
+ TextFormatter.NeedsFormat = true;
+ UpdateTextFormatterText ();
+ OnResizeNeeded ();
+ }
+ }
+ }
+
+ bool ResizeView (bool autoSize)
+ {
+ if (!autoSize) {
+ return false;
+ }
+
+ bool boundsChanged = true;
+ var newFrameSize = GetAutoSize ();
+ if (IsInitialized && newFrameSize != Frame.Size) {
+ if (ValidatePosDim) {
+ // BUGBUG: This ain't right, obviously. We need to figure out how to handle this.
+ boundsChanged = ResizeBoundsToFit (newFrameSize);
+ } else {
+ Height = newFrameSize.Height;
+ Width = newFrameSize.Width;
+ }
+ }
+ // BUGBUG: This call may be redundant
+ TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+ return boundsChanged;
+ }
+
+ ///
+ /// Resizes the View to fit the specified size. Factors in the HotKey.
+ ///
+ ///
+ /// whether the Bounds was changed or not
+ bool ResizeBoundsToFit (Size size)
+ {
+ bool boundsChanged = false;
+ bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW);
+ bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH);
+ if (canSizeW) {
+ boundsChanged = true;
+ _width = rW;
+ }
+ if (canSizeH) {
+ boundsChanged = true;
+ _height = rH;
+ }
+ if (boundsChanged) {
+ Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
+ }
+
+ return boundsChanged;
+ }
+
+ ///
+ /// Gets the Frame dimensions required to fit within using the text specified by the
+ /// property and accounting for any characters.
+ ///
+ /// The of the view required to fit the text.
+ public Size GetAutoSize ()
+ {
+ int x = 0;
+ int 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.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal;
+ int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + 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);
+ int 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);
+ int dimValue = height.Anchor (0);
+ return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height
+ - GetHotKeySpecifierLength (false));
+ }
+
+ ///
+ /// Determines if the View's can be set to a new value.
+ ///
+ ///
+ /// Contains the width that would result if were set to "/>
+ /// if the View's can be changed to the specified value. False otherwise.
+ internal bool TrySetWidth (int desiredWidth, out int resultWidth)
+ {
+ int w = desiredWidth;
+ bool canSetWidth;
+ switch (Width) {
+ case Dim.DimCombine _:
+ case Dim.DimView _:
+ case Dim.DimFill _:
+ // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored.
+ w = Width.Anchor (w);
+ canSetWidth = !ValidatePosDim;
+ break;
+ case Dim.DimFactor factor:
+ // Tries to get the SuperView Width otherwise the view Width.
+ int sw = SuperView != null ? SuperView.Frame.Width : w;
+ if (factor.IsFromRemaining ()) {
+ sw -= Frame.X;
+ }
+ w = Width.Anchor (sw);
+ canSetWidth = !ValidatePosDim;
+ break;
+ default:
+ canSetWidth = true;
+ break;
+ }
+ resultWidth = w;
+
+ return canSetWidth;
+ }
+
+ ///
+ /// Determines if the View's can be set to a new value.
+ ///
+ ///
+ /// Contains the width that would result if were set to "/>
+ /// if the View's can be changed to the specified value. False otherwise.
+ internal bool TrySetHeight (int desiredHeight, out int resultHeight)
+ {
+ int h = desiredHeight;
+ bool canSetHeight;
+ switch (Height) {
+ case Dim.DimCombine _:
+ case Dim.DimView _:
+ case Dim.DimFill _:
+ // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
+ h = Height.Anchor (h);
+ canSetHeight = !ValidatePosDim;
+ break;
+ case Dim.DimFactor factor:
+ // Tries to get the SuperView height otherwise the view height.
+ int sh = SuperView != null ? SuperView.Frame.Height : h;
+ if (factor.IsFromRemaining ()) {
+ sh -= Frame.Y;
+ }
+ h = Height.Anchor (sh);
+ canSetHeight = !ValidatePosDim;
+ break;
+ default:
+ canSetHeight = true;
+ break;
+ }
+ resultHeight = h;
+
+ return canSetHeight;
+ }
+
+ ///
+ /// Finds which view that belong to the superview at the provided location.
+ ///
+ /// The superview where to look for.
+ /// The column location in the superview.
+ /// The row location in the superview.
+ /// The found view screen relative column location.
+ /// The found view screen relative row location.
+ ///
+ /// The view that was found at the and coordinates.
+ /// if no view was found.
+ ///
+ public static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
+ {
+ resy = resx = 0;
+ if (start == null || !start.Frame.Contains (x, y)) {
+ return null;
+ }
+
+ var startFrame = start.Frame;
+ if (start.InternalSubviews != null) {
+ int count = start.InternalSubviews.Count;
+ if (count > 0) {
+ var boundsOffset = start.GetBoundsOffset ();
+ int rx = x - (startFrame.X + boundsOffset.X);
+ int ry = y - (startFrame.Y + boundsOffset.Y);
+ for (int i = count - 1; i >= 0; i--) {
+ var v = start.InternalSubviews [i];
+ if (v.Visible && v.Frame.Contains (rx, ry)) {
+ var deep = FindDeepestView (v, rx, ry, out resx, out resy);
+ if (deep == null) {
+ return v;
+ }
+ return deep;
+ }
+ }
+ }
+ }
+ resx = x - startFrame.X;
+ resy = y - startFrame.Y;
+ return start;
+ }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index 7d045079e..a22e60d94 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -280,6 +280,7 @@ namespace Terminal.Gui {
_oldCanFocus = CanFocus;
_oldTabIndex = _tabIndex;
+ // BUGBUG: These should move to EndInit as they access Bounds causing debug spew.
UpdateTextDirection (TextDirection);
UpdateTextFormatterText ();
SetHotKey ();
diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs
index 649622bbb..73f191358 100644
--- a/Terminal.Gui/View/ViewText.cs
+++ b/Terminal.Gui/View/ViewText.cs
@@ -1,5 +1,6 @@
using System.Text;
using System;
+using System.Collections.Generic;
namespace Terminal.Gui {
@@ -122,20 +123,88 @@ namespace Terminal.Gui {
UpdateTextFormatterText ();
- if ((!ForceValidatePosDim && directionChanged && AutoSize)
- || (ForceValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) {
+ if ((!ValidatePosDim && directionChanged && AutoSize)
+ || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) {
OnResizeNeeded ();
} else if (directionChanged && IsAdded) {
ResizeBoundsToFit (Bounds.Size);
// BUGBUG: I think this call is redundant.
- SetBoundsToFitFrame ();
+ SetFrameToFitText ();
} else {
- SetBoundsToFitFrame ();
+ SetFrameToFitText ();
}
TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
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)) {
+ switch (TextFormatter.IsVerticalDirection (TextDirection)) {
+ case true:
+ int 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)) {
+ _frame = new Rect (_frame.Location, size);
+ return true;
+ }
+ return false;
+ }
+
///
/// Gets the width or height of the characters
/// in the property.
diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs
index 5e6165c08..1f64e19d0 100644
--- a/Terminal.Gui/Views/Dialog.cs
+++ b/Terminal.Gui/Views/Dialog.cs
@@ -1,243 +1,243 @@
-//
-// Dialog.cs: Dialog box
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
-using System.Text;
-using Terminal.Gui;
-using static Terminal.Gui.ConfigurationManager;
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+///
+/// The is a that by default is centered and contains one
+/// or more s. It defaults to the color scheme and has a 1 cell padding around the edges.
+///
+///
+/// To run the modally, create the , and pass it to .
+/// This will execute the dialog until it terminates via the [ESC] or [CTRL-Q] key, or when one of the views
+/// or buttons added to the dialog calls .
+///
+public class Dialog : Window {
///
- /// The is a that by default is centered and contains one
- /// or more s. It defaults to the color scheme and has a 1 cell padding around the edges.
+ /// The default for .
///
///
- /// To run the modally, create the , and pass it to .
- /// This will execute the dialog until it terminates via the [ESC] or [CTRL-Q] key, or when one of the views
- /// or buttons added to the dialog calls .
+ /// This property can be set in a Theme.
///
- public class Dialog : Window {
- ///
- /// The default for .
- ///
- ///
- /// This property can be set in a Theme.
- ///
- [SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
- public static ButtonAlignments DefaultButtonAlignment { get; set; } = ButtonAlignments.Center;
+ [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+ [JsonConverter (typeof (JsonStringEnumConverter))]
+ public static ButtonAlignments DefaultButtonAlignment { get; set; } = ButtonAlignments.Center;
- // TODO: Reenable once border/borderframe design is settled
- ///
- /// Defines the default border styling for . Can be configured via .
- ///
- //[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
- //public static Border DefaultBorder { get; set; } = new Border () {
- // LineStyle = LineStyle.Single,
- //};
+ // TODO: Reenable once border/borderframe design is settled
+ ///
+ /// Defines the default border styling for . Can be configured via .
+ ///
+ //[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+ //public static Border DefaultBorder { get; set; } = new Border () {
+ // LineStyle = LineStyle.Single,
+ //};
+ internal List