diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index d2b355549..95a15e457 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -196,7 +196,7 @@ public static partial class Application { Current = Top; // Ensure Top's layout is up to date. - Current.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); + Current.SetRelativeLayout (Driver.Bounds); _cachedSupportedCultures = GetSupportedCultures (); _mainThreadId = Thread.CurrentThread.ManagedThreadId; @@ -402,7 +402,7 @@ public static partial class Application { } //if (Toplevel.LayoutStyle == LayoutStyle.Computed) { - Toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); + Toplevel.SetRelativeLayout (Driver.Bounds); //} Toplevel.LayoutSubviews (); Toplevel.PositionToplevels (); @@ -714,7 +714,7 @@ public static partial class Application { && (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height) && (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) { - state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows))); + state.Toplevel.Clear (Driver.Bounds); } if (state.Toplevel.NeedsDisplay || diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 4bd0143e1..70a9a4827 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -552,6 +552,11 @@ public abstract class ConsoleDriver { /// public static DiagnosticFlags Diagnostics { get; set; } + /// + /// Gets the dimensions of the terminal. + /// + public Rect Bounds => new Rect (0, 0, Cols, Rows); + /// /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. /// diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 9423b3ed0..4f1277286 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -11,1433 +11,1360 @@ namespace Terminal.Gui; /// 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, +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 + /// + /// The position and size of the view will be computed based on + /// , , , and . + /// will + /// provide the absolute computed values. + /// + Computed } -public partial class View -{ - bool _autoSize; +public partial class View { + bool _autoSize; - /// - /// Backing property for Frame - The frame for the object. Relative to the SuperView's Bounds. - /// - Rect _frame; + /// + /// Backing property for Frame - The frame for the object. Relative to the SuperView's Bounds. + /// + Rect _frame; - /// - /// Gets or sets location and size of the view. The frame is relative to the 's - /// . - /// - /// - /// The rectangle describing the location and size of the view, in coordinates relative to the - /// . - /// - /// - /// - /// Change the Frame when using the layout style to move or resize views. - /// - /// - /// Altering the Frame will change to . - /// Additionally, , , , and will be set - /// to the values of the Frame (using and ). - /// - /// - /// Altering the Frame will eventually (when the view is next drawn) cause the - /// - /// and methods to be called. - /// - /// - public virtual Rect Frame - { - get => _frame; - set - { - _frame = new Rect(value.X, value.Y, Math.Max(value.Width, 0), Math.Max(value.Height, 0)); + /// + /// Gets or sets location and size of the view. The frame is relative to the 's + /// . + /// + /// + /// The rectangle describing the location and size of the view, in coordinates relative to the + /// . + /// + /// + /// + /// Change the Frame when using the layout style to move or resize views. + /// + /// + /// Altering the Frame will change to . + /// Additionally, , , , and will be set + /// to the values of the Frame (using and ). + /// + /// + /// Altering the Frame will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + 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 Frame gets set, by definition, the View is now LayoutStyle.Absolute, so - // set all Pos/Dim to Absolute values. - _x = _frame.X; - _y = _frame.Y; - _width = _frame.Width; - _height = _frame.Height; - if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) - { - LayoutFrames(); - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey(); - SetNeedsLayout(); - SetNeedsDisplay(); - } - } - } + // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so + // set all Pos/Dim to Absolute values. + _x = _frame.X; + _y = _frame.Y; + _width = _frame.Width; + _height = _frame.Height; + 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 ) 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; } + /// + /// 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(); - } - } + /// + /// 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; } + /// + /// 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; } - /// - /// 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. - /// - /// - /// - /// Setting this property to will cause to determine the - /// size and position of the view. and will be set to - /// using . - /// - /// - /// Setting this property to will cause the view to use the - /// method to - /// size and position of the view. If either of the and properties are `null` they - /// will be set to using - /// the current value of . - /// If either of the and properties are `null` they will be set to - /// using . - /// - /// - /// The layout style. - public LayoutStyle LayoutStyle - { - get - { - if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute) - { - return LayoutStyle.Absolute; - } - else - { - return LayoutStyle.Computed; - } - } - set - { - // TODO: Remove this setter and make LayoutStyle read-only for real. - throw new InvalidOperationException("LayoutStyle is read-only."); - } - } + /// + /// 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. + /// + /// + /// + /// Setting this property to will cause to determine the + /// size and position of the view. and will be set to + /// using . + /// + /// + /// Setting this property to will cause the view to use the + /// method to + /// size and position of the view. If either of the and properties are `null` they + /// will be set to using + /// the current value of . + /// If either of the and properties are `null` they will be set to + /// using . + /// + /// + /// The layout style. + public LayoutStyle LayoutStyle { + get { + if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute) { + return LayoutStyle.Absolute; + } else { + return LayoutStyle.Computed; + } + } + set { + // TODO: Remove this setter and make LayoutStyle read-only for real. + throw new InvalidOperationException ("LayoutStyle is read-only."); + } + } - /// - /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and - /// content are presented. - /// - /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. - /// - /// - /// If is the value of Bounds is indeterminate until - /// the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Updates to the Bounds updates , and has the same side effects as updating the - /// . - /// - /// - /// Altering the Bounds will eventually (when the view is next drawn) cause the - /// - /// and methods to be called. - /// - /// - /// Because coordinates are relative to the upper-left corner of the , - /// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). - /// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents. - /// - /// - public virtual Rect Bounds - { - get - { + /// + /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and + /// content are presented. + /// + /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. + /// + /// + /// If is the value of Bounds is indeterminate until + /// the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Updates to the Bounds updates , and has the same side effects as updating the + /// . + /// + /// + /// Altering the Bounds will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// Because coordinates are relative to the upper-left corner of the , + /// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). + /// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents. + /// + /// + 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}"); - } + 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}"); + } #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 - ) - ); - } - } + //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 + ) + ); + } + } - Pos _x = Pos.At(0); + Pos _x = Pos.At (0); - /// - /// Gets or sets the X position for the view (the column). - /// - /// The object representing the X position. - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and - /// methods to be called. - /// - /// - /// If is changing this property will cause the - /// to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - /// is the same as Pos.Absolute(0). - /// - /// - public Pos X - { - get => VerifyIsInitialized(_x, nameof(X)); - set - { - _x = value ?? throw new ArgumentNullException(nameof(value), @$"{nameof(X)} cannot be null"); - OnResizeNeeded(); - } - } + /// + /// Gets or sets the X position for the view (the column). + /// + /// The object representing the X position. + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and + /// methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// is the same as Pos.Absolute(0). + /// + /// + public Pos X { + get => VerifyIsInitialized (_x, nameof (X)); + set { + _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); + OnResizeNeeded (); + } + } - Pos _y = Pos.At(0); + Pos _y = Pos.At (0); - /// - /// Gets or sets the Y position for the view (the row). - /// - /// The object representing the Y position. - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and - /// methods to be called. - /// - /// - /// If is changing this property will cause the - /// to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - /// is the same as Pos.Absolute(0). - /// - /// - public Pos Y - { - get => VerifyIsInitialized(_y, nameof(Y)); - set - { - _y = value ?? throw new ArgumentNullException(nameof(value), @$"{nameof(Y)} cannot be null"); - OnResizeNeeded(); - } - } + /// + /// Gets or sets the Y position for the view (the row). + /// + /// The object representing the Y position. + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and + /// methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// is the same as Pos.Absolute(0). + /// + /// + public Pos Y { + get => VerifyIsInitialized (_y, nameof (Y)); + set { + _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); + OnResizeNeeded (); + } + } - Dim _width = Dim.Sized(0); + Dim _width = Dim.Sized (0); - /// - /// Gets or sets the width of the view. - /// - /// The object representing the width of the view (the number of columns). - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// - /// and methods to be called. - /// - /// - /// If is changing this property will cause the - /// to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - public Dim Width - { - get => VerifyIsInitialized(_width, nameof(Width)); - set - { - _width = value ?? throw new ArgumentNullException(nameof(value), @$"{nameof(Width)} cannot be null"); + /// + /// Gets or sets the width of the view. + /// + /// The object representing the width of the view (the number of columns). + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + public Dim Width { + get => VerifyIsInitialized (_width, nameof (Width)); + set { + _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); - if (ValidatePosDim) - { - var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth(_width); + if (ValidatePosDim) { + var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); - if (IsAdded && AutoSize && !isValidNewAutSize) - { - throw new InvalidOperationException("Must set AutoSize to false before set the Width."); - } - } - OnResizeNeeded(); - } - } + if (IsAdded && AutoSize && !isValidNewAutSize) { + throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); + } + } + OnResizeNeeded (); + } + } - Dim _height = Dim.Sized(0); + Dim _height = Dim.Sized (0); - /// - /// Gets or sets the height of the view. - /// - /// The object representing the height of the view (the number of rows). - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been - /// called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// - /// and methods to be called. - /// - /// - /// If is changing this property will cause the - /// to be updated. If - /// the new value is not of type the will change to - /// . - /// - /// - public Dim Height - { - get => VerifyIsInitialized(_height, nameof(Height)); - set - { - _height = value ?? throw new ArgumentNullException(nameof(value), @$"{nameof(Height)} cannot be null"); + /// + /// Gets or sets the height of the view. + /// + /// The object representing the height of the view (the number of rows). + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + public Dim Height { + get => VerifyIsInitialized (_height, nameof (Height)); + set { + _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); - if (ValidatePosDim) - { - var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight(_height); + if (ValidatePosDim) { + var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); - if (IsAdded && AutoSize && !isValidNewAutSize) - { - throw new InvalidOperationException("Must set AutoSize to false before setting the Height."); - } - } - OnResizeNeeded(); - } - } + if (IsAdded && AutoSize && !isValidNewAutSize) { + throw new InvalidOperationException ("Must set AutoSize to false before setting the Height."); + } + } + OnResizeNeeded (); + } + } - /// - /// 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; } + /// + /// 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; } - internal bool LayoutNeeded { get; private set; } = true; + internal bool LayoutNeeded { get; private set; } = true; - /// - /// 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(); - } - } - } + /// + /// 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 (); + } + } + } - /// - /// 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; + /// + /// 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; - /// - /// 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 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(Padding?.Thickness.GetInside(Padding.Frame).X ?? 0, Padding?.Thickness.GetInside(Padding.Frame).Y ?? 0); + /// + /// 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 (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) - { - if (IsInitialized) - { - LayoutFrames(); - } - SetNeedsLayout(); - SetNeedsDisplay(); - } + /// + /// 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) + { + if (IsInitialized) { + 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 (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; + 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 + // 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; - } + if (Padding != null) { + Padding.ThicknessChanged -= ThicknessChangedHandler; + Padding.Dispose (); + } + Padding = new Frame { Id = "Padding", Thickness = new Thickness (0) }; + Padding.ThicknessChanged += ThicknessChangedHandler; + Padding.Parent = this; + } - 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)); - } + 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, string member) - { + // Diagnostics to highlight when X or Y is read before the view has been initialized + Pos VerifyIsInitialized (Pos pos, string member) + { #if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) - { - Debug.WriteLine($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug."); - } + if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug."); + } #endif // DEBUG - return pos; - } + return pos; + } - // Diagnostics to highlight when Width or Height is read before the view has been initialized - Dim VerifyIsInitialized(Dim dim, string member) - { + // Diagnostics to highlight when Width or Height is read before the view has been initialized + Dim VerifyIsInitialized (Dim dim, string member) + { #if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) - { - Debug.WriteLine($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug."); - } + if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { + Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug."); + } #endif // DEBUG - return dim; - } - - /// - /// 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() - { - 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) - { - SetFrameToFitText(); - LayoutFrames(); - TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey(); - SetNeedsLayout(); - SetNeedsDisplay(); - } - } - - 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 var x, out var 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) - { - Debug.Assert(_x != null); - Debug.Assert(_y != null); - Debug.Assert(_width != null); - Debug.Assert(_height != null); - - 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(); - } - - // TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs - // TODO: to make architecture more clean. Do this after DimAuto is implemented and the - // TODO: View.AutoSize stuff is removed. - - // 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 Dim.DimCombine combine: - // TODO: Move combine logic into DimCombine? - var leftNewDim = GetNewDimension(combine._left, location, dimension, autosize); - var 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: - case Dim.DimAbsolute: - default: - newDimension = Math.Max(d.Anchor(dimension - location), 0); - newDimension = AutoSize && autosize > newDimension ? autosize : newDimension; - break; - } - - return newDimension; - } - - int newDimension, newLocation; - var 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: - // TODO: Move combine logic into PosCombine? - 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 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; - - /// - /// 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}\")."); - } - 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.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.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.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. - /// - /// - /// - /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, - /// the behavior of this method is indeterminate if is . - /// - /// - /// 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 ResizeView(bool autoSize) - { - if (!autoSize) - { - return false; - } - - var 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; - } - } - 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() - { - var x = 0; - var 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 !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) || - _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength() || - _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength(false)); - } - - bool IsValidAutoSizeWidth(Dim width) - { - var rect = TextFormatter.CalcRect(_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - var dimValue = width.Anchor(0); - return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength()); - } - - bool IsValidAutoSizeHeight(Dim height) - { - var rect = TextFormatter.CalcRect(_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - var dimValue = height.Anchor(0); - return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength(false)); - } - - /// - /// 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 = !ValidatePosDim; - 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 = !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) - { - 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 = !ValidatePosDim; - 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 = !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) - { - var 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 (var 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; - } + return dim; + } + + /// + /// 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. + /// + /// + /// + /// Sets the . + /// + /// + /// 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; + + //// TODO: Determine if this API should change Frame as it does. + //// TODO: Is it correct behavior? Shouldn't the Frame be changed when SetRelativeLayout + //// TODO: is eventually called because SetNeedsLayout get set? + //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; + // // Set Frame to cause Pos/Dim to be set. By Definition AutoSize = true means LayoutStyleAbsolute + // Frame = new Rect (new Point (actX, actY), new Size (w, h)); + //} 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 + + SetRelativeLayout (SuperView?.Bounds ?? Application.Top?.Bounds ?? Application.Driver?.Bounds ?? new Rect (0, 0, int.MaxValue, int.MaxValue)); + if (IsInitialized/* || LayoutStyle == LayoutStyle.Absolute*/) { + SetFrameToFitText (); + LayoutFrames (); + TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + } + + /// + /// Sets the internal flag for this View and all of it's + /// subviews and it's SuperView. The main loop will call SetRelativeLayout and LayoutSubviews + /// for any view with set. + /// + 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 var x, out var 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) + { + Debug.Assert (_x != null); + Debug.Assert (_y != null); + Debug.Assert (_width != null); + Debug.Assert (_height != null); + + 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 (); + } + + // TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs + // TODO: to make architecture more clean. Do this after DimAuto is implemented and the + // TODO: View.AutoSize stuff is removed. + + // 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 new dimension (width or height) (if relevant for Dim type) + // autosize: the size to use if autosize = true + // This method is recursive if d is Dim.DimCombine + int GetNewDimension (Dim d, int location, int dimension, int autosize) + { + int newDimension; + switch (d) { + + case Dim.DimCombine combine: + // TODO: Move combine logic into DimCombine? + var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize); + var 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.DimAbsolute: + // DimAbsoulte.Anchor (int width) ignores width and returns n + newDimension = Math.Max (d.Anchor (0), 0); + 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; + var 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: + // TODO: Move combine logic into PosCombine? + 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 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) { + // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making + // the view LayoutStyle.Absolute. + _frame = r; + if (X is Pos.PosAbsolute) { + _x = Frame.X; + } + if (Y is Pos.PosAbsolute) { + _y = Frame.Y; + } + if (Width is Dim.DimAbsolute) { + _width = Frame.Width; + } + if (Height is Dim.DimAbsolute) { + _height = Frame.Height; + } + + if (IsInitialized) { + //LayoutFrames (); + //TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); + SetNeedsLayout (); + //SetNeedsDisplay (); + } + + // 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; + + /// + /// 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}\")."); + } + 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.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.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.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. + /// + /// + /// + /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, + /// the behavior of this method is indeterminate if is . + /// + /// + /// 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 ResizeView (bool autoSize) + { + if (!autoSize) { + return false; + } + + var 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; + } + } + 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 () + { + var x = 0; + var 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 !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) || + _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () || + _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); + } + + bool IsValidAutoSizeWidth (Dim width) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + var dimValue = width.Anchor (0); + return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ()); + } + + bool IsValidAutoSizeHeight (Dim height) + { + var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); + var dimValue = height.Anchor (0); + return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false)); + } + + /// + /// 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 = !ValidatePosDim; + 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 = !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) + { + 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 = !ValidatePosDim; + 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 = !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) { + var 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 (var 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/SuperViewChangedEventArgs.cs b/Terminal.Gui/View/SuperViewChangedEventArgs.cs index fdd4da3cf..13f710491 100644 --- a/Terminal.Gui/View/SuperViewChangedEventArgs.cs +++ b/Terminal.Gui/View/SuperViewChangedEventArgs.cs @@ -1,33 +1,34 @@ using System; -namespace Terminal.Gui { +namespace Terminal.Gui; + +/// +/// Args for events where the of a is changed +/// (e.g. / events). +/// +public class SuperViewChangedEventArgs : EventArgs { /// - /// Args for events where the of a is changed - /// (e.g. / events). + /// Creates a new instance of the class. /// - public class SuperViewChangedEventArgs : EventArgs + /// + /// + public SuperViewChangedEventArgs (View parent, View child) { - /// - /// Creates a new instance of the class. - /// - /// - /// - public SuperViewChangedEventArgs (View parent, View child) - { - Parent = parent; - Child = child; - } - - /// - /// The parent. For this is the old - /// parent (new parent now being null). For - /// it is the new parent to whom view now belongs. - /// - public View Parent { get; } - - /// - /// The view that is having it's changed - /// - public View Child { get; } + Parent = parent; + Child = child; } -} + + // TODO: Parent is the wrong name. It should be SuperView. + /// + /// The parent. For this is the old + /// parent (new parent now being null). For + /// it is the new parent to whom view now belongs. + /// + public View Parent { get; } + + // TODO: Child is the wrong name. It should be View. + /// + /// The view that is having it's changed + /// + public View Child { get; } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index 4f8bb4bf1..6bd7557f5 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -368,7 +368,10 @@ namespace Terminal.Gui { private int CalculateOkButtonPosX () { - return this.Bounds.Width + if (!IsInitialized) { + return 0; + } + return Bounds.Width - btnOk.Bounds.Width - btnCancel.Bounds.Width - 1 diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index 53983536b..c1ec2af7f 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/Terminal.Gui/Views/Menu/ContextMenu.cs @@ -99,7 +99,7 @@ public sealed class ContextMenu : IDisposable { } _container = Application.Current; _container.Closing += Container_Closing; - var frame = new Rect (0, 0, View.Driver.Cols, View.Driver.Rows); + var frame = Application.Driver.Bounds; var position = Position; if (Host != null) { Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y); diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 830519c53..321a48a4f 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -1445,7 +1445,7 @@ public class MenuBar : View { if (Driver == null) { return Point.Empty; } - var superViewFrame = SuperView == null ? new Rect (0, 0, Driver.Cols, Driver.Rows) : SuperView.Frame; + var superViewFrame = SuperView == null ? Driver.Bounds : SuperView.Frame; var sv = SuperView == null ? Application.Current : SuperView; var boundsOffset = sv.GetBoundsOffset (); return new Point (superViewFrame.X - sv.Frame.X - boundsOffset.X, @@ -1458,7 +1458,7 @@ public class MenuBar : View { /// The location offset. internal Point GetScreenOffsetFromCurrent () { - var screen = new Rect (0, 0, Driver.Cols, Driver.Rows); + var screen = Driver.Bounds; var currentFrame = Application.Current.Frame; var boundsOffset = Application.Top.GetBoundsOffset (); return new Point (screen.X - currentFrame.X - boundsOffset.X diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 21fb8a269..af1759b8f 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -804,8 +804,6 @@ public class Slider : View { if (!IsInitialized || AutoSize == false) { return; } - // Hack??? Otherwise we can't go back to Dim.Absolute. - LayoutStyle = LayoutStyle.Absolute; Width = 0; Height = 0; if (_config._sliderOrientation == Orientation.Horizontal) { @@ -817,7 +815,6 @@ public class Slider : View { new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcThickness ()), int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcBestLength ()))); } - LayoutStyle = LayoutStyle.Computed; } /// diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index 595af572d..8be4a169b 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -77,7 +77,7 @@ namespace UICatalog.Scenarios { }; AutoSize = false; - LayoutStyle = LayoutStyle.Absolute; + //LayoutStyle = LayoutStyle.Absolute; var fillText = new System.Text.StringBuilder (); for (int i = 0; i < Bounds.Height; i++) { diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index e5ca76be7..302d0460a 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -246,7 +246,7 @@ public class AllViewsTester : Scenario { var layout = view.LayoutStyle; try { - view.LayoutStyle = LayoutStyle.Absolute; + //view.LayoutStyle = LayoutStyle.Absolute; view.X = _xRadioGroup.SelectedItem switch { 0 => Pos.Percent (_xVal), @@ -280,7 +280,7 @@ public class AllViewsTester : Scenario { } catch (Exception e) { MessageBox.ErrorQuery ("Exception", e.Message, "Ok"); } finally { - view.LayoutStyle = layout; + //view.LayoutStyle = layout; } UpdateTitle (view); } diff --git a/UnitTests/View/DrawTests.cs b/UnitTests/View/DrawTests.cs index aaab0f330..d41803d9d 100644 --- a/UnitTests/View/DrawTests.cs +++ b/UnitTests/View/DrawTests.cs @@ -4,14 +4,16 @@ using Xunit; using Xunit.Abstractions; using Microsoft.VisualStudio.TestPlatform.Utilities; -namespace Terminal.Gui.ViewsTests; +namespace Terminal.Gui.ViewsTests; public class DrawTests { readonly ITestOutputHelper _output; public DrawTests (ITestOutputHelper output) => _output = output; - [Fact] [AutoInitShutdown] + // TODO: Refactor this test to not depend on TextView etc... Make it as primitive as possible + [Fact] + [AutoInitShutdown] public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space () { var tv = new TextView () { @@ -29,7 +31,8 @@ public class DrawTests { var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () }; win.Add (tv); Application.Top.Add (win); - var lbl = new Label ("ワイドルーン。"); + // Don't use Label. It sets AutoSize = true which is not what we're testing here. + var lbl = new View ("ワイドルーン。"); // Don't have unit tests use things that aren't absolutely critical for the test, like Dialog var dg = new Window () { X = 2, Y = 2, Width = 14, Height = 3 }; dg.Add (lbl); @@ -54,7 +57,8 @@ public class DrawTests { } // TODO: The tests below that use Label should use View instead. - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () { string us = "\U0001d539"; @@ -102,7 +106,8 @@ public class DrawTests { 0000000000", Application.Driver, expectedColors); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two () { string us = "\U0000f900"; @@ -150,7 +155,8 @@ public class DrawTests { 0000000000", Application.Driver, expectedColors); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Colors_On_TextAlignment_Right_And_Bottom () { var labelRight = new Label ("Test") { @@ -191,7 +197,8 @@ t ", _output); 0", Application.Driver, new Attribute [] { Colors.Base.Normal }); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Draw_Negative_Bounds_Horizontal_Without_New_Lines () { // BUGBUG: This previously assumed the default height of a View was 1. @@ -235,7 +242,8 @@ t ", _output); TestHelpers.AssertDriverContentsWithFrameAre ("", _output); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Draw_Negative_Bounds_Horizontal_With_New_Lines () { var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "s\nu\nb\nV\ni\ne\nw" }; @@ -304,7 +312,8 @@ t ", _output); TestHelpers.AssertDriverContentsWithFrameAre ("", _output); } - [Fact] [AutoInitShutdown] + [Fact] + [AutoInitShutdown] public void Draw_Negative_Bounds_Vertical () { var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "subView", TextDirection = TextDirection.TopBottom_LeftRight }; diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs index 45ea4d217..a2aa2bcaf 100644 --- a/UnitTests/View/Layout/LayoutTests.cs +++ b/UnitTests/View/Layout/LayoutTests.cs @@ -248,6 +248,25 @@ public class LayoutTests { top.Dispose (); } + [Fact] + [AutoInitShutdown] + public void DimFill_SizedCorrectly () + { + var view = new View () { + Width = Dim.Fill (), + Height = Dim.Fill (), + BorderStyle = LineStyle.Single, + }; + Application.Top.Add (view); + var rs = Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (32, 5); + //view.SetNeedsLayout (); + Application.Top.LayoutSubviews (); + //view.SetRelativeLayout (new Rect (0, 0, 32, 5)); + Assert.Equal (32, view.Frame.Width); + Assert.Equal (5, view.Frame.Height); + } + [Fact] [AutoInitShutdown] public void Width_Height_SetMinWidthHeight_Narrow_Wide_Runes () { @@ -761,7 +780,7 @@ public class LayoutTests { // Was named AutoSize_Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () // but doesn't actually have anything to do with AutoSize. [Fact] - public void AutoSize_Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () + public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () { Application.Init (new FakeDriver ()); @@ -781,7 +800,7 @@ public class LayoutTests { t.Add (w); t.Ready += (s, e) => { - v.LayoutStyle = LayoutStyle.Absolute; + v.Frame = new Rect (2, 2, 10, 10); Assert.Equal (2, v.X = 2); Assert.Equal (2, v.Y = 2); }; diff --git a/UnitTests/View/Layout/SetRelativeLayoutTests.cs b/UnitTests/View/Layout/SetRelativeLayoutTests.cs index cb6cc46a4..aec0f1a94 100644 --- a/UnitTests/View/Layout/SetRelativeLayoutTests.cs +++ b/UnitTests/View/Layout/SetRelativeLayoutTests.cs @@ -10,7 +10,46 @@ public class SetRelativeLayoutTests { readonly ITestOutputHelper _output; public SetRelativeLayoutTests (ITestOutputHelper output) => _output = output; - + + [Fact] + public void ComputedPosDim_StayComputed () + { + var screen = new Rect (0, 0, 10, 15); + var view = new View () { + X = 1, + Y = 2, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + Assert.Equal ("Absolute(1)", view.X.ToString ()); + Assert.Equal ("Absolute(2)", view.Y.ToString ()); + Assert.Equal ("Fill(0)", view.Width.ToString ()); + Assert.Equal ("Fill(0)", view.Height.ToString ()); + view.SetRelativeLayout (screen); + Assert.Equal ("Fill(0)", view.Width.ToString ()); + Assert.Equal ("Fill(0)", view.Height.ToString ()); + } + + [Fact] + public void AbsolutePosDim_DontChange () + { + var screen = new Rect (0, 0, 10, 15); + var view = new View () { + X = 1, // outside of screen +10 + Y = 2, // outside of screen -10 + Width = 3, + Height = 4 + }; + + // Layout is Absolute. So the X and Y are not changed. + view.SetRelativeLayout (screen); + Assert.Equal (1, view.Frame.X); + Assert.Equal (2, view.Frame.Y); + Assert.Equal (3, view.Frame.Width); + Assert.Equal (4, view.Frame.Height); + } + [Fact] public void Fill_Pos_Within_Bounds () { @@ -64,7 +103,7 @@ public class SetRelativeLayoutTests { } [Fact] - public void FIll_Pos_Outside_Bounds () + public void Fill_Pos_Outside_Bounds () { var screen = new Rect (0, 0, 80, 25); var view = new View () { @@ -74,6 +113,7 @@ public class SetRelativeLayoutTests { Height = 15 }; + // Layout is Absolute. So the X and Y are not changed. view.SetRelativeLayout (screen); Assert.Equal (90, view.Frame.X); Assert.Equal (-10, view.Frame.Y); @@ -125,10 +165,10 @@ public class SetRelativeLayoutTests { Assert.Equal (80, view.Frame.Width); Assert.Equal (25, view.Frame.Height); - view.Width = Dim.Fill (); + view.Width = Dim.Fill (); view.Height = Dim.Fill (); view.SetRelativeLayout (screen); - Assert.Equal (-41, view.Frame.X); + Assert.Equal (-41, view.Frame.X); Assert.Equal (-13, view.Frame.Y); Assert.Equal (121, view.Frame.Width); // 121 = screen.Width - (-Center - 41) Assert.Equal (38, view.Frame.Height); @@ -141,12 +181,12 @@ public class SetRelativeLayoutTests { var view = new View () { X = Pos.Center (), Y = Pos.Center (), - Width = Dim.Fill(), - Height = Dim.Fill() + Width = Dim.Fill (), + Height = Dim.Fill () }; view.SetRelativeLayout (screen); - Assert.Equal (0, view.Frame.X); + Assert.Equal (0, view.Frame.X); Assert.Equal (0, view.Frame.Y); Assert.Equal (80, view.Frame.Width); Assert.Equal (25, view.Frame.Height); @@ -176,14 +216,14 @@ public class SetRelativeLayoutTests { view.SetRelativeLayout (screen); Assert.Equal (-1, view.Frame.X); Assert.Equal (0, view.Frame.Y); - Assert.Equal (81, view.Frame.Width); + Assert.Equal (81, view.Frame.Width); Assert.Equal (25, view.Frame.Height); view.X = Pos.Center () - 2; // Fill means all the way to right. So width will be 82. (dim gets calc'd before pos). view.SetRelativeLayout (screen); Assert.Equal (-2, view.Frame.X); Assert.Equal (0, view.Frame.Y); - Assert.Equal (82, view.Frame.Width); + Assert.Equal (82, view.Frame.Width); Assert.Equal (25, view.Frame.Height); view.X = Pos.Center () - 3; // Fill means all the way to right. So width will be 83. (dim gets calc'd before pos). @@ -217,7 +257,8 @@ public class SetRelativeLayoutTests { Assert.Equal (23, view.Frame.Y); } - [Fact] [TestRespondersDisposed] + [Fact] + [TestRespondersDisposed] public void PosCombine_Plus_Absolute () { var superView = new View () { diff --git a/UnitTests/View/Text/AutoSizeTextTests.cs b/UnitTests/View/Text/AutoSizeTextTests.cs index c6083e2ea..aaec87a0d 100644 --- a/UnitTests/View/Text/AutoSizeTextTests.cs +++ b/UnitTests/View/Text/AutoSizeTextTests.cs @@ -769,17 +769,47 @@ Y Application.End (rs); } + [Fact] + public void SetRelativeLayout_Respects_AutoSize () + { + var view = new View (new Rect (0, 0, 10, 0)) { + AutoSize = true, + }; + view.Text = "01234567890123456789"; + + Assert.True (view.AutoSize); + Assert.Equal (LayoutStyle.Absolute, view.LayoutStyle); + Assert.Equal (new Rect (0, 0, 20, 1), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(20)", view.Width.ToString ()); + Assert.Equal ("Absolute(1)", view.Height.ToString ()); + + view.SetRelativeLayout (new Rect (0, 0, 25, 5)); + + Assert.True (view.AutoSize); + Assert.Equal (LayoutStyle.Absolute, view.LayoutStyle); + Assert.Equal (new Rect (0, 0, 20, 1), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(20)", view.Width.ToString ()); + Assert.Equal ("Absolute(1)", view.Height.ToString ()); + } + [Fact] [AutoInitShutdown] public void Setting_Frame_Dont_Respect_AutoSize_True_On_Layout_Absolute () { - var view1 = new View (new Rect (0, 0, 10, 0)) { Text = "Say Hello view1 你", AutoSize = true }; - var view2 = new View (new Rect (0, 0, 0, 10)) { + var view1 = new View (new Rect (0, 0, 10, 0)) { + Text = "Say Hello view1 你", + AutoSize = true + }; + var viewTopBottom_LeftRight = new View (new Rect (0, 0, 0, 10)) { Text = "Say Hello view2 你", AutoSize = true, TextDirection = TextDirection.TopBottom_LeftRight }; - Application.Top.Add (view1, view2); + Application.Top.Add (view1, viewTopBottom_LeftRight); var rs = Application.Begin (Application.Top); @@ -790,7 +820,8 @@ Y Assert.Equal ("Absolute(0)", view1.Y.ToString ()); Assert.Equal ("Absolute(18)", view1.Width.ToString ()); Assert.Equal ("Absolute(1)", view1.Height.ToString ()); - Assert.True (view2.AutoSize); + + Assert.True (viewTopBottom_LeftRight.AutoSize); // BUGBUG: v2 - Autosize is broken when setting Width/Height AutoSize. Disabling test for now. //Assert.Equal (LayoutStyle.Absolute, view2.LayoutStyle); //Assert.Equal (new Rect (0, 0, 2, 17), view2.Frame); @@ -811,14 +842,14 @@ Y Assert.Equal ("Absolute(18)", view1.Width.ToString ()); Assert.Equal ("Absolute(1)", view1.Height.ToString ()); - view2.Frame = new Rect (0, 0, 1, 25); + viewTopBottom_LeftRight.Frame = new Rect (0, 0, 1, 25); Application.RunIteration (ref rs, ref firstIteration); - Assert.True (view2.AutoSize); - Assert.Equal (LayoutStyle.Absolute, view2.LayoutStyle); - Assert.Equal (new Rect (0, 0, 1, 25), view2.Frame); - Assert.Equal ("Absolute(0)", view2.X.ToString ()); - Assert.Equal ("Absolute(0)", view2.Y.ToString ()); + Assert.True (viewTopBottom_LeftRight.AutoSize); + Assert.Equal (LayoutStyle.Absolute, viewTopBottom_LeftRight.LayoutStyle); + Assert.Equal (new Rect (0, 0, 1, 25), viewTopBottom_LeftRight.Frame); + Assert.Equal ("Absolute(0)", viewTopBottom_LeftRight.X.ToString ()); + Assert.Equal ("Absolute(0)", viewTopBottom_LeftRight.Y.ToString ()); // BUGBUG: v2 - Autosize is broken when setting Width/Height AutoSize. Disabling test for now. //Assert.Equal ("Absolute(2)", view2.Width.ToString ()); //Assert.Equal ("Absolute(17)", view2.Height.ToString ()); @@ -1865,8 +1896,7 @@ Y Assert.Equal (new Rect (0, 0, 22, 22), pos); Application.End (rs); } - - + [Fact] [AutoInitShutdown] public void AutoSize_True_Width_Height_Stay_True_If_TextFormatter_Size_Fit () @@ -1905,7 +1935,7 @@ Y Assert.Equal ("Absolute(0)", horizontalView.X.ToString ()); Assert.Equal ("Absolute(0)", horizontalView.Y.ToString ()); // BUGBUG - v2 - With v1 AutoSize = true Width/Height should always grow or keep initial value, - // but in v2, autosize will be replaced by Dim.Fit. Disabling test for now. + Assert.Equal ("Absolute(9)", horizontalView.Width.ToString ()); Assert.Equal ("Absolute(1)", horizontalView.Height.ToString ()); Assert.Equal (new Rect (0, 3, 2, 8), verticalView.Frame); @@ -1981,4 +2011,483 @@ Y Application.End (rs); } + + [Fact] + [AutoInitShutdown] + public void AutoSize_False_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute_After_IsAdded_And_IsInitialized () + { + var win = new Window (new Rect (0, 0, 30, 80)); + var label = new Label { Width = Dim.Fill () }; + win.Add (label); + Application.Top.Add (win); + + Assert.True (label.IsAdded); + + Assert.True (label.AutoSize); + + // #3127: Before: + // Text is empty but height=1 by default, see Label view + // BUGBUG: LayoutSubviews has not been called, so this test is not really valid (pos/dim are indeterminate, not 0) + // Not really a bug because View call OnResizeNeeded method on the SetInitialProperties method + // #3127: After: Text is empty Width=Dim.Fill is honored + Assert.Equal ("(0,0,28,1)", label.Bounds.ToString ()); + + label.Text = "First line\nSecond line"; + Application.Top.LayoutSubviews (); + + Assert.True (label.AutoSize); + // BUGBUG: This test is bogus: label has not been initialized. pos/dim is indeterminate! + Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ()); + Assert.False (label.IsInitialized); + + var rs = Application.Begin (Application.Top); + + Assert.True (label.AutoSize); + Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ()); + Assert.True (label.IsInitialized); + + label.AutoSize = false; + // BUGBUG: Application.Refresh has nothing to do with layout! It just redraws and sets LayoutNeeded to true + // Application.Refresh (); + + // Width should still be Dim.Fill + Assert.Equal ("Fill(0)", label.Width.ToString ()); + + // Height should be 2 + Assert.Equal ("Absolute(2)", label.Height.ToString ()); + Assert.Equal (2, label.Frame.Height); + + Assert.False (label.AutoSize); + Assert.Equal ("(0,0,28,1)", label.Bounds.ToString ()); + Application.End (rs); + } + + [Fact] + [AutoInitShutdown] + public void AutoSize_False_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute_With_Initialization () + { + var win = new Window (new Rect (0, 0, 30, 80)); + var label = new Label { Width = Dim.Fill () }; + win.Add (label); + Application.Top.Add (win); + + // Text is empty but height=1 by default, see Label view + Assert.True (label.AutoSize); + Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ()); + + var rs = Application.Begin (Application.Top); + + Assert.True (label.AutoSize); + // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) + // and height 0 because wasn't set and the text is empty + // BUGBUG: Because of #2450, this test is bogus: pos/dim is indeterminate! + //Assert.Equal ("(0,0,28,0)", label.Bounds.ToString ()); + + label.Text = "First line\nSecond line"; + Application.Refresh (); + + // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) + // and height 2 because wasn't set and the text has 2 lines + Assert.True (label.AutoSize); + Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ()); + + label.AutoSize = false; + Application.Refresh (); + + // Here the SetMinWidthHeight ensuring the minimum height + Assert.False (label.AutoSize); + Assert.Equal ("(0,0,28,1)", label.Bounds.ToString ()); + + label.Text = "First changed line\nSecond changed line\nNew line"; + Application.Refresh (); + + // Here the AutoSize is false and the width 28 (Dim.Fill) and + // height 1 because wasn't set and SetMinWidthHeight ensuring the minimum height + Assert.False (label.AutoSize); + Assert.Equal ("(0,0,28,1)", label.Bounds.ToString ()); + + label.AutoSize = true; + Application.Refresh (); + + // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) + // and height 3 because wasn't set and the text has 3 lines + Assert.True (label.AutoSize); + // BUGBUG: v2 - AutoSize is broken - temporarily disabling test See #2432 + //Assert.Equal ("(0,0,28,3)", label.Bounds.ToString ()); + Application.End (rs); + } + + + [Fact] + [AutoInitShutdown] + public void AutoSize_False_TextDirection_Toggle () + { + var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; + // View is AutoSize == true + var view = new View (); + win.Add (view); + Application.Top.Add (win); + + var rs = Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (22, 22); + + Assert.Equal (new Rect (0, 0, 22, 22), win.Frame); + Assert.Equal (new Rect (0, 0, 22, 22), win.Margin.Frame); + Assert.Equal (new Rect (0, 0, 22, 22), win.Border.Frame); + Assert.Equal (new Rect (1, 1, 20, 20), win.Padding.Frame); + Assert.False (view.AutoSize); + Assert.Equal (TextDirection.LeftRight_TopBottom, view.TextDirection); + Assert.Equal (Rect.Empty, view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(0)", view.Width.ToString ()); + Assert.Equal ("Absolute(0)", view.Height.ToString ()); + var expected = @" +┌────────────────────┐ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + + view.Text = "Hello World"; + view.Width = 11; + view.Height = 1; + win.LayoutSubviews (); + Application.Refresh (); + + Assert.Equal (new Rect (0, 0, 11, 1), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(11)", view.Width.ToString ()); + Assert.Equal ("Absolute(1)", view.Height.ToString ()); + expected = @" +┌────────────────────┐ +│Hello World │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + + view.AutoSize = true; + view.Text = "Hello Worlds"; + Application.Refresh (); + + Assert.Equal (new Rect (0, 0, 12, 1), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(11)", view.Width.ToString ()); + Assert.Equal ("Absolute(1)", view.Height.ToString ()); + expected = @" +┌────────────────────┐ +│Hello Worlds │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + + view.TextDirection = TextDirection.TopBottom_LeftRight; + Application.Refresh (); + + Assert.Equal (new Rect (0, 0, 11, 12), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(11)", view.Width.ToString ()); + Assert.Equal ("Absolute(1)", view.Height.ToString ()); + expected = @" +┌────────────────────┐ +│H │ +│e │ +│l │ +│l │ +│o │ +│ │ +│W │ +│o │ +│r │ +│l │ +│d │ +│s │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + + view.AutoSize = false; + view.Height = 1; + Application.Refresh (); + + Assert.Equal (new Rect (0, 0, 11, 1), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(11)", view.Width.ToString ()); + Assert.Equal ("Absolute(1)", view.Height.ToString ()); + expected = @" +┌────────────────────┐ +│HelloWorlds │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + + view.PreserveTrailingSpaces = true; + Application.Refresh (); + + Assert.Equal (new Rect (0, 0, 11, 1), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(11)", view.Width.ToString ()); + Assert.Equal ("Absolute(1)", view.Height.ToString ()); + expected = @" +┌────────────────────┐ +│Hello World │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + + view.PreserveTrailingSpaces = false; + var f = view.Frame; + view.Width = f.Height; + view.Height = f.Width; + view.TextDirection = TextDirection.TopBottom_LeftRight; + Application.Refresh (); + + Assert.Equal (new Rect (0, 0, 1, 11), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(1)", view.Width.ToString ()); + Assert.Equal ("Absolute(11)", view.Height.ToString ()); + expected = @" +┌────────────────────┐ +│H │ +│e │ +│l │ +│l │ +│o │ +│ │ +│W │ +│o │ +│r │ +│l │ +│d │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + + view.AutoSize = true; + Application.Refresh (); + + Assert.Equal (new Rect (0, 0, 1, 12), view.Frame); + Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view.Y.ToString ()); + Assert.Equal ("Absolute(1)", view.Width.ToString ()); + Assert.Equal ("Absolute(12)", view.Height.ToString ()); + expected = @" +┌────────────────────┐ +│H │ +│e │ +│l │ +│l │ +│o │ +│ │ +│W │ +│o │ +│r │ +│l │ +│d │ +│s │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────┘ +"; + + pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); + Assert.Equal (new Rect (0, 0, 22, 22), pos); + Application.End (rs); + } + + + [Fact, AutoInitShutdown] + public void GetTextFormatterBoundsSize_GetSizeNeededForText_HotKeySpecifier () + { + var text = "Say Hello 你"; + + // Frame: 0, 0, 12, 1 + var horizontalView = new View () { + AutoSize = true, + HotKeySpecifier = (Rune)'_' + }; + horizontalView.Text = text; + + // Frame: 0, 0, 1, 12 + var verticalView = new View () { + AutoSize = true, + HotKeySpecifier = (Rune)'_', + TextDirection = TextDirection.TopBottom_LeftRight + }; + verticalView.Text = text; + + Application.Top.Add (horizontalView, verticalView); + Application.Begin (Application.Top); + ((FakeDriver)Application.Driver).SetBufferSize (50, 50); + + Assert.True (horizontalView.AutoSize); + Assert.Equal (new Rect (0, 0, 12, 1), horizontalView.Frame); + Assert.Equal (new Size (12, 1), horizontalView.GetSizeNeededForTextWithoutHotKey ()); + Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ()); + + Assert.True (verticalView.AutoSize); + // BUGBUG: v2 - Autosize is broken; disabling this test + Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame); + Assert.Equal (new Size (2, 11), verticalView.GetSizeNeededForTextWithoutHotKey ()); + //Assert.Equal (new Size (2, 11), verticalView.GetSizeNeededForTextAndHotKey ()); + //Assert.Equal (verticalView.TextFormatter.Size, verticalView.GetSizeNeededForTextAndHotKey ()); + Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ()); + + text = "Say He_llo 你"; + horizontalView.Text = text; + verticalView.Text = text; + + Assert.True (horizontalView.AutoSize); + Assert.Equal (new Rect (0, 0, 12, 1), horizontalView.Frame); + Assert.Equal (new Size (12, 1), horizontalView.GetSizeNeededForTextWithoutHotKey ()); + //Assert.Equal (new Size (13, 1), horizontalView.GetSizeNeededForTextAndHotKey ()); + //Assert.Equal (horizontalView.TextFormatter.Size, horizontalView.GetSizeNeededForTextAndHotKey ()); + Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ()); + + Assert.True (verticalView.AutoSize); + // BUGBUG: v2 - Autosize is broken; disabling this test + //Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame); + //Assert.Equal (new Size (2, 11), verticalView.GetSizeNeededForTextWithoutHotKey ()); + //Assert.Equal (new Size (2, 12), verticalView.GetSizeNeededForTextAndHotKey ()); + //Assert.Equal (verticalView.TextFormatter.Size, verticalView.GetSizeNeededForTextAndHotKey ()); + //Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ()); + } } \ No newline at end of file diff --git a/UnitTests/View/Text/TextTests.cs b/UnitTests/View/Text/TextTests.cs index a652c12c2..fbcb5b224 100644 --- a/UnitTests/View/Text/TextTests.cs +++ b/UnitTests/View/Text/TextTests.cs @@ -33,11 +33,11 @@ public class TextTests { Assert.Equal (5, text.Length); Assert.False (view.AutoSize); - Assert.Equal (new Rect (0, 0, 3, 1), view.Frame); - Assert.Equal (new Size (3, 1), view.TextFormatter.Size); + Assert.Equal (new Rect (0, 0, 3, 1), view.Frame); + Assert.Equal (new Size (3, 1), view.TextFormatter.Size); Assert.Equal (new List { "Vie" }, view.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); + Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); + Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); var expected = @" ┌────────┐ │Vie │ @@ -53,8 +53,8 @@ public class TextTests { view.Width = Dim.Fill () - text.Length; Application.Refresh (); - Assert.Equal (new Rect (0, 0, 0, 1), view.Frame); - Assert.Equal (new Size (0, 1), view.TextFormatter.Size); + Assert.Equal (new Rect (0, 0, 0, 1), view.Frame); + Assert.Equal (new Size (0, 1), view.TextFormatter.Size); Assert.Equal (new List { string.Empty }, view.TextFormatter.Lines); expected = @" ┌────────┐ @@ -88,7 +88,7 @@ public class TextTests { Assert.Equal (5, text.Length); Assert.False (view.AutoSize); Assert.Equal (new Rect (0, 0, 3, 1), view.Frame); - Assert.Equal (new Size (3, 1), view.TextFormatter.Size); + Assert.Equal (new Size (3, 1), view.TextFormatter.Size); Assert.Single (view.TextFormatter.Lines); Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); @@ -108,7 +108,7 @@ public class TextTests { Application.Refresh (); Assert.Equal (new Rect (0, 0, 0, 1), view.Frame); - Assert.Equal (new Size (0, 1), view.TextFormatter.Size); + Assert.Equal (new Size (0, 1), view.TextFormatter.Size); var exception = Record.Exception (() => Assert.Equal (new List { string.Empty }, view.TextFormatter.Lines)); Assert.Null (exception); expected = @" @@ -144,11 +144,11 @@ public class TextTests { Assert.Equal (5, text.Length); Assert.False (label.AutoSize); - Assert.Equal (new Rect (0, 0, 3, 1), label.Frame); - Assert.Equal (new Size (3, 1), label.TextFormatter.Size); + Assert.Equal (new Rect (0, 0, 3, 1), label.Frame); + Assert.Equal (new Size (3, 1), label.TextFormatter.Size); Assert.Equal (new List { "Lab" }, label.TextFormatter.Lines); - Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); - Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); + Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); + Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); var expected = @" ┌────────┐ │Lab │ @@ -165,8 +165,8 @@ public class TextTests { Application.Refresh (); Assert.False (label.AutoSize); - Assert.Equal (new Rect (0, 0, 0, 1), label.Frame); - Assert.Equal (new Size (0, 1), label.TextFormatter.Size); + Assert.Equal (new Rect (0, 0, 0, 1), label.Frame); + Assert.Equal (new Size (0, 1), label.TextFormatter.Size); Assert.Equal (new List { string.Empty }, label.TextFormatter.Lines); expected = @" ┌────────┐ @@ -201,7 +201,7 @@ public class TextTests { Assert.Equal (5, text.Length); Assert.False (label.AutoSize); Assert.Equal (new Rect (0, 0, 3, 1), label.Frame); - Assert.Equal (new Size (3, 1), label.TextFormatter.Size); + Assert.Equal (new Size (3, 1), label.TextFormatter.Size); Assert.Single (label.TextFormatter.Lines); Assert.Equal (new Rect (0, 0, 10, 4), win.Frame); Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame); @@ -221,7 +221,7 @@ public class TextTests { Application.Refresh (); Assert.Equal (new Rect (0, 0, 0, 1), label.Frame); - Assert.Equal (new Size (0, 1), label.TextFormatter.Size); + Assert.Equal (new Size (0, 1), label.TextFormatter.Size); var exception = Record.Exception (() => Assert.Equal (new List { string.Empty }, label.TextFormatter.Lines)); Assert.Null (exception); expected = @" @@ -257,7 +257,7 @@ public class TextTests { Assert.Equal (5, text.Length); Assert.False (view.AutoSize); Assert.Equal (new Rect (0, 0, 1, 3), view.Frame); - Assert.Equal (new Size (1, 3), view.TextFormatter.Size); + Assert.Equal (new Size (1, 3), view.TextFormatter.Size); Assert.Single (view.TextFormatter.Lines); Assert.Equal (new Rect (0, 0, 4, 10), win.Frame); Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame); @@ -283,7 +283,7 @@ public class TextTests { Application.Refresh (); Assert.Equal (new Rect (0, 0, 1, 0), view.Frame); - Assert.Equal (new Size (1, 0), view.TextFormatter.Size); + Assert.Equal (new Size (1, 0), view.TextFormatter.Size); var exception = Record.Exception (() => Assert.Equal (new List { string.Empty }, view.TextFormatter.Lines)); Assert.Null (exception); expected = @" @@ -325,7 +325,7 @@ public class TextTests { Assert.Equal (5, text.Length); Assert.False (view.AutoSize); Assert.Equal (new Rect (0, 0, 2, 3), view.Frame); - Assert.Equal (new Size (2, 3), view.TextFormatter.Size); + Assert.Equal (new Size (2, 3), view.TextFormatter.Size); Assert.Single (view.TextFormatter.Lines); Assert.Equal (new Rect (0, 0, 4, 10), win.Frame); Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame); @@ -351,7 +351,7 @@ public class TextTests { Application.Refresh (); Assert.Equal (new Rect (0, 0, 2, 0), view.Frame); - Assert.Equal (new Size (2, 0), view.TextFormatter.Size); + Assert.Equal (new Size (2, 0), view.TextFormatter.Size); var exception = Record.Exception (() => Assert.Equal (new List { string.Empty }, view.TextFormatter.Lines)); Assert.Null (exception); expected = @" @@ -427,9 +427,10 @@ public class TextTests { win.Add (label); Application.Top.Add (win); - // Text is empty but height=1 by default, see Label view + // #3127: Before: Text is empty but height=1 by default, see Label view + // After: Text is empty Dim.Fill is honored Assert.False (label.AutoSize); - Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ()); + Assert.Equal ("(0,0,28,78)", label.Bounds.ToString ()); label.Text = "New text\nNew line"; Application.Top.LayoutSubviews (); @@ -445,100 +446,6 @@ public class TextTests { Application.End (rs); } - [Fact] - [AutoInitShutdown] - public void AutoSize_False_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute_After_IsAdded_And_IsInitialized () - { - var win = new Window (new Rect (0, 0, 30, 80)); - var label = new Label { Width = Dim.Fill () }; - win.Add (label); - Application.Top.Add (win); - - Assert.True (label.IsAdded); - - // Text is empty but height=1 by default, see Label view - Assert.True (label.AutoSize); - // BUGBUG: LayoutSubviews has not been called, so this test is not really valid (pos/dim are indeterminate, not 0) - // Not really a bug because View call OnResizeNeeded method on the SetInitialProperties method - Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ()); - - label.Text = "First line\nSecond line"; - Application.Top.LayoutSubviews (); - - Assert.True (label.AutoSize); - // BUGBUG: This test is bogus: label has not been initialized. pos/dim is indeterminate! - Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ()); - Assert.False (label.IsInitialized); - - var rs = Application.Begin (Application.Top); - - Assert.True (label.AutoSize); - Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ()); - Assert.True (label.IsInitialized); - - label.AutoSize = false; - Application.Refresh (); - - Assert.False (label.AutoSize); - Assert.Equal ("(0,0,28,1)", label.Bounds.ToString ()); - Application.End (rs); - } - - [Fact] - [AutoInitShutdown] - public void AutoSize_False_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute_With_Initialization () - { - var win = new Window (new Rect (0, 0, 30, 80)); - var label = new Label { Width = Dim.Fill () }; - win.Add (label); - Application.Top.Add (win); - - // Text is empty but height=1 by default, see Label view - Assert.True (label.AutoSize); - Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ()); - - var rs = Application.Begin (Application.Top); - - Assert.True (label.AutoSize); - // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) - // and height 0 because wasn't set and the text is empty - // BUGBUG: Because of #2450, this test is bogus: pos/dim is indeterminate! - //Assert.Equal ("(0,0,28,0)", label.Bounds.ToString ()); - - label.Text = "First line\nSecond line"; - Application.Refresh (); - - // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) - // and height 2 because wasn't set and the text has 2 lines - Assert.True (label.AutoSize); - Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ()); - - label.AutoSize = false; - Application.Refresh (); - - // Here the SetMinWidthHeight ensuring the minimum height - Assert.False (label.AutoSize); - Assert.Equal ("(0,0,28,1)", label.Bounds.ToString ()); - - label.Text = "First changed line\nSecond changed line\nNew line"; - Application.Refresh (); - - // Here the AutoSize is false and the width 28 (Dim.Fill) and - // height 1 because wasn't set and SetMinWidthHeight ensuring the minimum height - Assert.False (label.AutoSize); - Assert.Equal ("(0,0,28,1)", label.Bounds.ToString ()); - - label.AutoSize = true; - Application.Refresh (); - - // Here the AutoSize ensuring the right size with width 28 (Dim.Fill) - // and height 3 because wasn't set and the text has 3 lines - Assert.True (label.AutoSize); - // BUGBUG: v2 - AutoSize is broken - temporarily disabling test See #2432 - //Assert.Equal ("(0,0,28,3)", label.Bounds.ToString ()); - Application.End (rs); - } - [Fact] [AutoInitShutdown] public void AutoSize_False_Equal_Before_And_After_IsInitialized_With_Differents_Orders () @@ -576,28 +483,28 @@ public class TextTests { Assert.False (view5.IsInitialized); Assert.False (view1.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view1.Frame); - Assert.Equal ("Absolute(10)", view1.Width.ToString ()); - Assert.Equal ("Absolute(5)", view1.Height.ToString ()); + Assert.Equal ("Absolute(10)", view1.Width.ToString ()); + Assert.Equal ("Absolute(5)", view1.Height.ToString ()); Assert.False (view2.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view2.Frame); - Assert.Equal ("Absolute(10)", view2.Width.ToString ()); - Assert.Equal ("Absolute(5)", view2.Height.ToString ()); + Assert.Equal ("Absolute(10)", view2.Width.ToString ()); + Assert.Equal ("Absolute(5)", view2.Height.ToString ()); Assert.False (view3.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view3.Frame); - Assert.Equal ("Absolute(10)", view3.Width.ToString ()); - Assert.Equal ("Absolute(5)", view3.Height.ToString ()); + Assert.Equal ("Absolute(10)", view3.Width.ToString ()); + Assert.Equal ("Absolute(5)", view3.Height.ToString ()); Assert.False (view4.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view4.Frame); - Assert.Equal ("Absolute(10)", view4.Width.ToString ()); - Assert.Equal ("Absolute(5)", view4.Height.ToString ()); + Assert.Equal ("Absolute(10)", view4.Width.ToString ()); + Assert.Equal ("Absolute(5)", view4.Height.ToString ()); Assert.False (view5.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view5.Frame); - Assert.Equal ("Absolute(10)", view5.Width.ToString ()); - Assert.Equal ("Absolute(5)", view5.Height.ToString ()); + Assert.Equal ("Absolute(10)", view5.Width.ToString ()); + Assert.Equal ("Absolute(5)", view5.Height.ToString ()); Assert.False (view6.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view6.Frame); - Assert.Equal ("Absolute(10)", view6.Width.ToString ()); - Assert.Equal ("Absolute(5)", view6.Height.ToString ()); + Assert.Equal ("Absolute(10)", view6.Width.ToString ()); + Assert.Equal ("Absolute(5)", view6.Height.ToString ()); var rs = Application.Begin (Application.Top); @@ -608,343 +515,29 @@ public class TextTests { Assert.True (view5.IsInitialized); Assert.False (view1.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view1.Frame); - Assert.Equal ("Absolute(10)", view1.Width.ToString ()); - Assert.Equal ("Absolute(5)", view1.Height.ToString ()); + Assert.Equal ("Absolute(10)", view1.Width.ToString ()); + Assert.Equal ("Absolute(5)", view1.Height.ToString ()); Assert.False (view2.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view2.Frame); - Assert.Equal ("Absolute(10)", view2.Width.ToString ()); - Assert.Equal ("Absolute(5)", view2.Height.ToString ()); + Assert.Equal ("Absolute(10)", view2.Width.ToString ()); + Assert.Equal ("Absolute(5)", view2.Height.ToString ()); Assert.False (view3.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view3.Frame); - Assert.Equal ("Absolute(10)", view3.Width.ToString ()); - Assert.Equal ("Absolute(5)", view3.Height.ToString ()); + Assert.Equal ("Absolute(10)", view3.Width.ToString ()); + Assert.Equal ("Absolute(5)", view3.Height.ToString ()); Assert.False (view4.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view4.Frame); - Assert.Equal ("Absolute(10)", view4.Width.ToString ()); - Assert.Equal ("Absolute(5)", view4.Height.ToString ()); + Assert.Equal ("Absolute(10)", view4.Width.ToString ()); + Assert.Equal ("Absolute(5)", view4.Height.ToString ()); Assert.False (view5.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view5.Frame); - Assert.Equal ("Absolute(10)", view5.Width.ToString ()); - Assert.Equal ("Absolute(5)", view5.Height.ToString ()); + Assert.Equal ("Absolute(10)", view5.Width.ToString ()); + Assert.Equal ("Absolute(5)", view5.Height.ToString ()); Assert.False (view6.AutoSize); Assert.Equal (new Rect (0, 0, 10, 5), view6.Frame); - Assert.Equal ("Absolute(10)", view6.Width.ToString ()); - Assert.Equal ("Absolute(5)", view6.Height.ToString ()); + Assert.Equal ("Absolute(10)", view6.Width.ToString ()); + Assert.Equal ("Absolute(5)", view6.Height.ToString ()); Application.End (rs); } - [Fact] - [AutoInitShutdown] - public void AutoSize_False_TextDirection_Toggle () - { - var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; - // View is AutoSize == true - var view = new View (); - win.Add (view); - Application.Top.Add (win); - - var rs = Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (22, 22); - - Assert.Equal (new Rect (0, 0, 22, 22), win.Frame); - Assert.Equal (new Rect (0, 0, 22, 22), win.Margin.Frame); - Assert.Equal (new Rect (0, 0, 22, 22), win.Border.Frame); - Assert.Equal (new Rect (1, 1, 20, 20), win.Padding.Frame); - Assert.False (view.AutoSize); - Assert.Equal (TextDirection.LeftRight_TopBottom, view.TextDirection); - Assert.Equal (Rect.Empty, view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(0)", view.Width.ToString ()); - Assert.Equal ("Absolute(0)", view.Height.ToString ()); - var expected = @" -┌────────────────────┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - - view.Text = "Hello World"; - view.Width = 11; - view.Height = 1; - win.LayoutSubviews (); - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 11, 1), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(11)", view.Width.ToString ()); - Assert.Equal ("Absolute(1)", view.Height.ToString ()); - expected = @" -┌────────────────────┐ -│Hello World │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - - view.AutoSize = true; - view.Text = "Hello Worlds"; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 12, 1), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(11)", view.Width.ToString ()); - Assert.Equal ("Absolute(1)", view.Height.ToString ()); - expected = @" -┌────────────────────┐ -│Hello Worlds │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - - view.TextDirection = TextDirection.TopBottom_LeftRight; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 11, 12), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(11)", view.Width.ToString ()); - Assert.Equal ("Absolute(1)", view.Height.ToString ()); - expected = @" -┌────────────────────┐ -│H │ -│e │ -│l │ -│l │ -│o │ -│ │ -│W │ -│o │ -│r │ -│l │ -│d │ -│s │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - - view.AutoSize = false; - view.Height = 1; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 11, 1), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(11)", view.Width.ToString ()); - Assert.Equal ("Absolute(1)", view.Height.ToString ()); - expected = @" -┌────────────────────┐ -│HelloWorlds │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - - view.PreserveTrailingSpaces = true; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 11, 1), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(11)", view.Width.ToString ()); - Assert.Equal ("Absolute(1)", view.Height.ToString ()); - expected = @" -┌────────────────────┐ -│Hello World │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - - view.PreserveTrailingSpaces = false; - var f = view.Frame; - view.Width = f.Height; - view.Height = f.Width; - view.TextDirection = TextDirection.TopBottom_LeftRight; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 1, 11), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(1)", view.Width.ToString ()); - Assert.Equal ("Absolute(11)", view.Height.ToString ()); - expected = @" -┌────────────────────┐ -│H │ -│e │ -│l │ -│l │ -│o │ -│ │ -│W │ -│o │ -│r │ -│l │ -│d │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - - view.AutoSize = true; - Application.Refresh (); - - Assert.Equal (new Rect (0, 0, 1, 12), view.Frame); - Assert.Equal ("Absolute(0)", view.X.ToString ()); - Assert.Equal ("Absolute(0)", view.Y.ToString ()); - Assert.Equal ("Absolute(1)", view.Width.ToString ()); - Assert.Equal ("Absolute(12)", view.Height.ToString ()); - expected = @" -┌────────────────────┐ -│H │ -│e │ -│l │ -│l │ -│o │ -│ │ -│W │ -│o │ -│r │ -│l │ -│d │ -│s │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└────────────────────┘ -"; - - pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output); - Assert.Equal (new Rect (0, 0, 22, 22), pos); - Application.End (rs); - } } \ No newline at end of file diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index c882daed2..6e6debc5a 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -21,7 +21,7 @@ namespace Terminal.Gui.ViewTests { // Parameterless var r = new View (); Assert.NotNull (r); - Assert.Equal (LayoutStyle.Computed, r.LayoutStyle); + Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle); Assert.Equal ("View()(0,0,0,0)", r.ToString ()); Assert.False (r.CanFocus); Assert.False (r.HasFocus); @@ -29,10 +29,10 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (new Rect (0, 0, 0, 0), r.Frame); Assert.Null (r.Focused); Assert.Null (r.ColorScheme); - Assert.Null (r.Width); - Assert.Null (r.Height); - Assert.Null (r.X); - Assert.Null (r.Y); + Assert.Equal (0, r.Width); + Assert.Equal (0, r.Height); + Assert.Equal (0, r.X); + Assert.Equal (0, r.Y); Assert.False (r.IsCurrentTop); Assert.Empty (r.Id); Assert.Empty (r.Subviews); @@ -42,7 +42,7 @@ namespace Terminal.Gui.ViewTests { Assert.Null (r.MostFocused); Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection); r.Dispose (); - + // Empty Rect r = new View (Rect.Empty); Assert.NotNull (r); @@ -54,10 +54,10 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (new Rect (0, 0, 0, 0), r.Frame); Assert.Null (r.Focused); Assert.Null (r.ColorScheme); - Assert.Null (r.Width); // All view Dim are initialized now in the IsAdded setter, - Assert.Null (r.Height); // avoiding Dim errors. - Assert.Null (r.X); // All view Pos are initialized now in the IsAdded setter, - Assert.Null (r.Y); // avoiding Pos errors. + Assert.Equal (0, r.Width); + Assert.Equal (0, r.Height); + Assert.Equal (0, r.X); + Assert.Equal (0, r.Y); Assert.False (r.IsCurrentTop); Assert.Empty (r.Id); Assert.Empty (r.Subviews); @@ -79,10 +79,10 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (new Rect (1, 2, 3, 4), r.Frame); Assert.Null (r.Focused); Assert.Null (r.ColorScheme); - Assert.Null (r.Width); - Assert.Null (r.Height); - Assert.Null (r.X); - Assert.Null (r.Y); + Assert.Equal (3, r.Width); + Assert.Equal (4, r.Height); + Assert.Equal (1, r.X); + Assert.Equal (2, r.Y); Assert.False (r.IsCurrentTop); Assert.Empty (r.Id); Assert.Empty (r.Subviews); @@ -96,7 +96,7 @@ namespace Terminal.Gui.ViewTests { // Initializes a view with a vertical direction r = new View ("Vertical View", TextDirection.TopBottom_LeftRight); Assert.NotNull (r); - Assert.Equal (LayoutStyle.Computed, r.LayoutStyle); + Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle); Assert.Equal ("View(Vertical View)(0,0,1,13)", r.ToString ()); Assert.False (r.CanFocus); Assert.False (r.HasFocus); @@ -104,10 +104,6 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (new Rect (0, 0, 1, 13), r.Frame); Assert.Null (r.Focused); Assert.Null (r.ColorScheme); - Assert.Null (r.Width); // All view Dim are initialized now in the IsAdded setter, - Assert.Null (r.Height); // avoiding Dim errors. - Assert.Null (r.X); // All view Pos are initialized now in the IsAdded setter, - Assert.Null (r.Y); // avoiding Pos errors. Assert.False (r.IsCurrentTop); Assert.Equal ("Vertical View", r.Id); Assert.Empty (r.Subviews); @@ -183,23 +179,23 @@ namespace Terminal.Gui.ViewTests { #if DEBUG_IDISPOSABLE Assert.Empty (Responder.Instances); #endif - + // Default Constructor view = new View (); - Assert.Null (view.X); - Assert.Null (view.Y); - Assert.Null (view.Width); - Assert.Null (view.Height); + Assert.Equal (0, view.X); + Assert.Equal (0, view.Y); + Assert.Equal (0, view.Width); + Assert.Equal (0, view.Height); Assert.True (view.Frame.IsEmpty); Assert.True (view.Bounds.IsEmpty); view.Dispose (); // Constructor view = new View (1, 2, ""); - Assert.Null (view.X); - Assert.Null (view.Y); - Assert.Null (view.Width); - Assert.Null (view.Height); + Assert.Equal (1, view.X); + Assert.Equal (2, view.Y); + Assert.Equal (0, view.Width); + Assert.Equal (0, view.Height); Assert.False (view.Frame.IsEmpty); Assert.True (view.Bounds.IsEmpty); view.Dispose (); @@ -259,33 +255,33 @@ namespace Terminal.Gui.ViewTests { { Application.Init (new FakeDriver ()); - var t = new Toplevel () { Id = "0", }; + var top = new Toplevel () { Id = "0", }; // Frame: 0, 0, 80, 25; Bounds: 0, 0, 80, 25 - var w = new Window () { Id = "t", Width = Dim.Fill (), Height = Dim.Fill () }; - var v1 = new View () { Id = "v1", Width = Dim.Fill (), Height = Dim.Fill () }; - var v2 = new View () { Id = "v2", Width = Dim.Fill (), Height = Dim.Fill () }; - var sv1 = new View () { Id = "sv1", Width = Dim.Fill (), Height = Dim.Fill () }; + var winAddedToTop = new Window () { Id = "t", Width = Dim.Fill (), Height = Dim.Fill () }; // Frame: 0, 0, 80, 25; Bounds: 0, 0, 78, 23 + var v1AddedToWin = new View () { Id = "v1", Width = Dim.Fill (), Height = Dim.Fill () }; // Frame: 1, 1, 78, 23 (because Windows has a border) + var v2AddedToWin = new View () { Id = "v2", Width = Dim.Fill (), Height = Dim.Fill () }; // Frame: 1, 1, 78, 23 (because Windows has a border) + var svAddedTov1 = new View () { Id = "sv1", Width = Dim.Fill (), Height = Dim.Fill () }; // Frame: 1, 1, 78, 23 (same as it's superview v1AddedToWin) int tc = 0, wc = 0, v1c = 0, v2c = 0, sv1c = 0; - w.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, w.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, w.Frame.Height); + winAddedToTop.Added += (s, e) => { + Assert.Equal (e.Parent.Bounds.Width, winAddedToTop.Frame.Width); + Assert.Equal (e.Parent.Bounds.Height, winAddedToTop.Frame.Height); }; - v1.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, v1.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, v1.Frame.Height); + v1AddedToWin.Added += (s, e) => { + Assert.Equal (e.Parent.Bounds.Width, v1AddedToWin.Frame.Width); + Assert.Equal (e.Parent.Bounds.Height, v1AddedToWin.Frame.Height); }; - v2.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, v2.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, v2.Frame.Height); + v2AddedToWin.Added += (s, e) => { + Assert.Equal (e.Parent.Bounds.Width, v2AddedToWin.Frame.Width); + Assert.Equal (e.Parent.Bounds.Height, v2AddedToWin.Frame.Height); }; - sv1.Added += (s, e) => { - Assert.Equal (e.Parent.Frame.Width, sv1.Frame.Width); - Assert.Equal (e.Parent.Frame.Height, sv1.Frame.Height); + svAddedTov1.Added += (s, e) => { + Assert.Equal (e.Parent.Bounds.Width, svAddedTov1.Frame.Width); + Assert.Equal (e.Parent.Bounds.Height, svAddedTov1.Frame.Height); }; - t.Initialized += (s, e) => { + top.Initialized += (s, e) => { tc++; Assert.Equal (1, tc); Assert.Equal (1, wc); @@ -293,48 +289,61 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (1, v2c); Assert.Equal (1, sv1c); - Assert.True (t.CanFocus); - Assert.True (w.CanFocus); - Assert.False (v1.CanFocus); - Assert.False (v2.CanFocus); - Assert.False (sv1.CanFocus); + Assert.True (top.CanFocus); + Assert.True (winAddedToTop.CanFocus); + Assert.False (v1AddedToWin.CanFocus); + Assert.False (v2AddedToWin.CanFocus); + Assert.False (svAddedTov1.CanFocus); Application.Refresh (); }; - w.Initialized += (s, e) => { + winAddedToTop.Initialized += (s, e) => { wc++; - Assert.Equal (t.Frame.Width, w.Frame.Width); - Assert.Equal (t.Frame.Height, w.Frame.Height); + Assert.Equal (top.Bounds.Width, winAddedToTop.Frame.Width); + Assert.Equal (top.Bounds.Height, winAddedToTop.Frame.Height); }; - v1.Initialized += (s, e) => { + v1AddedToWin.Initialized += (s, e) => { v1c++; - Assert.Equal (t.Frame.Width, v1.Frame.Width); - Assert.Equal (t.Frame.Height, v1.Frame.Height); + // Top.Frame: 0, 0, 80, 25; Top.Bounds: 0, 0, 80, 25 + // BUGBUG: This is wrong, it should be 78, 23. This test has always been broken. + // in no way should the v1AddedToWin.Frame be the same as the Top.Frame/Bounds + // as it is a subview of winAddedToTop, which has a border! + //Assert.Equal (top.Bounds.Width, v1AddedToWin.Frame.Width); + //Assert.Equal (top.Bounds.Height, v1AddedToWin.Frame.Height); }; - v2.Initialized += (s, e) => { + v2AddedToWin.Initialized += (s, e) => { v2c++; - Assert.Equal (t.Frame.Width, v2.Frame.Width); - Assert.Equal (t.Frame.Height, v2.Frame.Height); + // Top.Frame: 0, 0, 80, 25; Top.Bounds: 0, 0, 80, 25 + // BUGBUG: This is wrong, it should be 78, 23. This test has always been broken. + // in no way should the v2AddedToWin.Frame be the same as the Top.Frame/Bounds + // as it is a subview of winAddedToTop, which has a border! + //Assert.Equal (top.Bounds.Width, v2AddedToWin.Frame.Width); + //Assert.Equal (top.Bounds.Height, v2AddedToWin.Frame.Height); }; - sv1.Initialized += (s, e) => { + svAddedTov1.Initialized += (s, e) => { sv1c++; - Assert.Equal (t.Frame.Width, sv1.Frame.Width); - Assert.Equal (t.Frame.Height, sv1.Frame.Height); - Assert.False (sv1.CanFocus); - Assert.Throws (() => sv1.CanFocus = true); - Assert.False (sv1.CanFocus); + // Top.Frame: 0, 0, 80, 25; Top.Bounds: 0, 0, 80, 25 + // BUGBUG: This is wrong, it should be 78, 23. This test has always been broken. + // in no way should the svAddedTov1.Frame be the same as the Top.Frame/Bounds + // because sv1AddedTov1 is a subview of v1AddedToWin, which is a subview of + // winAddedToTop, which has a border! + //Assert.Equal (top.Bounds.Width, svAddedTov1.Frame.Width); + //Assert.Equal (top.Bounds.Height, svAddedTov1.Frame.Height); + Assert.False (svAddedTov1.CanFocus); + Assert.Throws (() => svAddedTov1.CanFocus = true); + Assert.False (svAddedTov1.CanFocus); }; - v1.Add (sv1); - w.Add (v1, v2); - t.Add (w); + v1AddedToWin.Add (svAddedTov1); + winAddedToTop.Add (v1AddedToWin, v2AddedToWin); + top.Add (winAddedToTop); Application.Iteration += (s, a) => { Application.Refresh (); - t.Running = false; + top.Running = false; }; - Application.Run (t); + Application.Run (top); Application.Shutdown (); Assert.Equal (1, tc); @@ -343,14 +352,14 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (1, v2c); Assert.Equal (1, sv1c); - Assert.True (t.CanFocus); - Assert.True (w.CanFocus); - Assert.False (v1.CanFocus); - Assert.False (v2.CanFocus); - Assert.False (sv1.CanFocus); + Assert.True (top.CanFocus); + Assert.True (winAddedToTop.CanFocus); + Assert.False (v1AddedToWin.CanFocus); + Assert.False (v2AddedToWin.CanFocus); + Assert.False (svAddedTov1.CanFocus); - v1.CanFocus = true; - Assert.False (sv1.CanFocus); // False because sv1 was disposed and it isn't a subview of v1. + v1AddedToWin.CanFocus = true; + Assert.False (svAddedTov1.CanFocus); // False because sv1 was disposed and it isn't a subview of v1. } @@ -384,18 +393,18 @@ namespace Terminal.Gui.ViewTests { }; w.Initialized += (s, e) => { wc++; - Assert.Equal (t.Frame.Width, w.Frame.Width); - Assert.Equal (t.Frame.Height, w.Frame.Height); + Assert.Equal (t.Bounds.Width, w.Frame.Width); + Assert.Equal (t.Bounds.Height, w.Frame.Height); }; v1.Initialized += (s, e) => { v1c++; - Assert.Equal (t.Frame.Width, v1.Frame.Width); - Assert.Equal (t.Frame.Height, v1.Frame.Height); + //Assert.Equal (t.Bounds.Width, v1.Frame.Width); + //Assert.Equal (t.Bounds.Height, v1.Frame.Height); }; v2.Initialized += (s, e) => { v2c++; - Assert.Equal (t.Frame.Width, v2.Frame.Width); - Assert.Equal (t.Frame.Height, v2.Frame.Height); + //Assert.Equal (t.Bounds.Width, v2.Frame.Width); + //Assert.Equal (t.Bounds.Height, v2.Frame.Height); }; w.Add (v1, v2); t.Add (w); @@ -505,12 +514,17 @@ namespace Terminal.Gui.ViewTests { var runState = Application.Begin (top); // BUGBUG: This is a SetRelativeLayout test. It should be moved to SetRelativeLayoutTests.cs - view.Width = Dim.Fill (); - view.Height = Dim.Fill (); - Assert.Equal (10, view.Bounds.Width); - Assert.Equal (1, view.Bounds.Height); - view.LayoutStyle = LayoutStyle.Computed; + view.Width = Dim.Fill (); // Width should be 79 (Top.Width - 1) + Assert.Equal ("Fill(0)", view.Width.ToString ()); + view.Height = Dim.Fill (); // Height should be 24 (Top.Height - 1) + + // #3127: Before: Frame was not being set when Width and Height were set to Dim.Fill() + // After: Frame is set to the parent's Frame when Width and Height are set to Dim.Fill() + Assert.Equal (79, view.Bounds.Width); + Assert.Equal (24, view.Bounds.Height); + //view.LayoutStyle = LayoutStyle.Computed; view.SetRelativeLayout (top.Bounds); + Assert.Equal ("Fill(0)", view.Width.ToString ()); Assert.Equal (1, view.Frame.X); Assert.Equal (1, view.Frame.Y); Assert.Equal (79, view.Frame.Width); @@ -758,61 +772,6 @@ namespace Terminal.Gui.ViewTests { Assert.Equal (Rect.Empty, pos); } - [Fact, AutoInitShutdown] - public void GetTextFormatterBoundsSize_GetSizeNeededForText_HotKeySpecifier () - { - var text = "Say Hello 你"; - var horizontalView = new View () { - Text = text, - AutoSize = true, - HotKeySpecifier = (Rune)'_' - }; - - var verticalView = new View () { - Text = text, - AutoSize = true, - HotKeySpecifier = (Rune)'_', - TextDirection = TextDirection.TopBottom_LeftRight - }; - Application.Top.Add (horizontalView, verticalView); - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (50, 50); - - Assert.True (horizontalView.AutoSize); - Assert.Equal (new Rect (0, 0, 12, 1), horizontalView.Frame); - Assert.Equal (new Size (12, 1), horizontalView.GetSizeNeededForTextWithoutHotKey ()); - //Assert.Equal (new Size (12, 1), horizontalView.GetSizeNeededForTextAndHotKey ()); - //Assert.Equal (horizontalView.TextFormatter.Size, horizontalView.GetSizeNeededForTextAndHotKey ()); - Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ()); - - Assert.True (verticalView.AutoSize); - // BUGBUG: v2 - Autosize is broken; disabling this test - //Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame); - //Assert.Equal (new Size (2, 11), verticalView.GetSizeNeededForTextWithoutHotKey ()); - //Assert.Equal (new Size (2, 11), verticalView.GetSizeNeededForTextAndHotKey ()); - //Assert.Equal (verticalView.TextFormatter.Size, verticalView.GetSizeNeededForTextAndHotKey ()); - Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ()); - - text = "Say He_llo 你"; - horizontalView.Text = text; - verticalView.Text = text; - - Assert.True (horizontalView.AutoSize); - Assert.Equal (new Rect (0, 0, 12, 1), horizontalView.Frame); - Assert.Equal (new Size (12, 1), horizontalView.GetSizeNeededForTextWithoutHotKey ()); - //Assert.Equal (new Size (13, 1), horizontalView.GetSizeNeededForTextAndHotKey ()); - //Assert.Equal (horizontalView.TextFormatter.Size, horizontalView.GetSizeNeededForTextAndHotKey ()); - Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ()); - - Assert.True (verticalView.AutoSize); - // BUGBUG: v2 - Autosize is broken; disabling this test - //Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame); - //Assert.Equal (new Size (2, 11), verticalView.GetSizeNeededForTextWithoutHotKey ()); - //Assert.Equal (new Size (2, 12), verticalView.GetSizeNeededForTextAndHotKey ()); - //Assert.Equal (verticalView.TextFormatter.Size, verticalView.GetSizeNeededForTextAndHotKey ()); - //Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ()); - } - [Fact, TestRespondersDisposed] public void IsAdded_Added_Removed () { @@ -823,7 +782,7 @@ namespace Terminal.Gui.ViewTests { Assert.True (view.IsAdded); top.Remove (view); Assert.False (view.IsAdded); - + top.Dispose (); view.Dispose (); } @@ -831,14 +790,16 @@ namespace Terminal.Gui.ViewTests { [Fact, AutoInitShutdown] public void Visible_Clear_The_View_Output () { - var label = new Label ("Testing visibility."); + var view = new View ("Testing visibility."); // use View, not Label to avoid AutoSize == true + Assert.Equal ("Testing visibility.".Length, view.Frame.Width); + Assert.Equal (1, view.Height); var win = new Window (); - win.Add (label); + win.Add (view); var top = Application.Top; top.Add (win); var rs = Application.Begin (top); - Assert.True (label.Visible); + Assert.True (view.Visible); ((FakeDriver)Application.Driver).SetBufferSize (30, 5); TestHelpers.AssertDriverContentsWithFrameAre (@" ┌────────────────────────────┐ @@ -848,7 +809,7 @@ namespace Terminal.Gui.ViewTests { └────────────────────────────┘ ", output); - label.Visible = false; + view.Visible = false; bool firstIteration = false; Application.RunIteration (ref rs, ref firstIteration); @@ -1044,8 +1005,7 @@ At 0,0 view.Frame = new Rect (1, 1, 10, 1); Assert.Equal (new Rect (1, 1, 10, 1), view.Frame); - Assert.Equal (LayoutStyle.Computed, view.LayoutStyle); - view.LayoutStyle = LayoutStyle.Absolute; + Assert.Equal (LayoutStyle.Absolute, view.LayoutStyle); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect); top.Draw (); @@ -1115,8 +1075,7 @@ At 0,0 view.Frame = new Rect (1, 1, 10, 1); Assert.Equal (new Rect (1, 1, 10, 1), view.Frame); - Assert.Equal (LayoutStyle.Computed, view.LayoutStyle); - view.LayoutStyle = LayoutStyle.Absolute; + Assert.Equal (LayoutStyle.Absolute, view.LayoutStyle); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect); view.Draw (); @@ -1189,8 +1148,7 @@ At 0,0 view.Frame = new Rect (3, 3, 10, 1); Assert.Equal (new Rect (3, 3, 10, 1), view.Frame); - Assert.Equal (LayoutStyle.Computed, view.LayoutStyle); - view.LayoutStyle = LayoutStyle.Absolute; + Assert.Equal (LayoutStyle.Absolute, view.LayoutStyle); Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds); Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect); top.Draw (); @@ -1351,7 +1309,7 @@ At 0,0 ColorScheme = Colors.Menu, Width = Dim.Fill (), X = 0, // don't overcomplicate unit tests - Y = 0 + Y = 0 }; var button = new Button ("Press me!") { diff --git a/UnitTests/Views/Toplevel/WindowTests.cs b/UnitTests/Views/Toplevel/WindowTests.cs index 08d922321..b71501b9d 100644 --- a/UnitTests/Views/Toplevel/WindowTests.cs +++ b/UnitTests/Views/Toplevel/WindowTests.cs @@ -15,19 +15,19 @@ public class WindowTests { // Parameterless var r = new Window (); Assert.NotNull (r); - Assert.Equal (string.Empty, r.Title); + Assert.Equal (string.Empty, r.Title); Assert.Equal (LayoutStyle.Computed, r.LayoutStyle); - Assert.Equal ("Window()(0,0,0,0)", r.ToString ()); + Assert.Equal ("Window()(0,0,0,0)", r.ToString ()); Assert.True (r.CanFocus); Assert.False (r.HasFocus); Assert.Equal (new Rect (0, 0, 0, 0), r.Bounds); Assert.Equal (new Rect (0, 0, 0, 0), r.Frame); Assert.Null (r.Focused); Assert.NotNull (r.ColorScheme); + Assert.Equal (0, r.X); + Assert.Equal (0, r.Y); Assert.Equal (Dim.Fill (), r.Width); Assert.Equal (Dim.Fill (), r.Height); - Assert.Null (r.X); - Assert.Null (r.Y); Assert.False (r.IsCurrentTop); Assert.Empty (r.Id); Assert.False (r.WantContinuousButtonPressed); @@ -39,8 +39,8 @@ public class WindowTests { // Empty Rect r = new Window (Rect.Empty) { Title = "title" }; Assert.NotNull (r); - Assert.Equal ("title", r.Title); - Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle); + Assert.Equal ("title", r.Title); + Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle); Assert.Equal ("Window(title)(0,0,0,0)", r.ToString ()); Assert.True (r.CanFocus); Assert.False (r.HasFocus); @@ -48,10 +48,10 @@ public class WindowTests { Assert.Equal (new Rect (0, 0, 0, 0), r.Frame); Assert.Null (r.Focused); Assert.NotNull (r.ColorScheme); - Assert.Null (r.Width); // All view Dim are initialized now in the IsAdded setter, - Assert.Null (r.Height); // avoiding Dim errors. - Assert.Null (r.X); // All view Pos are initialized now in the IsAdded setter, - Assert.Null (r.Y); // avoiding Pos errors. + Assert.Equal (0, r.X); + Assert.Equal (0, r.Y); + Assert.Equal (0, r.Width); + Assert.Equal (0, r.Height); Assert.False (r.IsCurrentTop); Assert.Equal (r.Title, r.Id); Assert.False (r.WantContinuousButtonPressed); @@ -64,7 +64,7 @@ public class WindowTests { r = new Window (new Rect (1, 2, 3, 4)) { Title = "title" }; Assert.Equal ("title", r.Title); Assert.NotNull (r); - Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle); + Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle); Assert.Equal ("Window(title)(1,2,3,4)", r.ToString ()); Assert.True (r.CanFocus); Assert.False (r.HasFocus); @@ -72,10 +72,10 @@ public class WindowTests { Assert.Equal (new Rect (1, 2, 3, 4), r.Frame); Assert.Null (r.Focused); Assert.NotNull (r.ColorScheme); - Assert.Null (r.Width); - Assert.Null (r.Height); - Assert.Null (r.X); - Assert.Null (r.Y); + Assert.Equal (1, r.X); + Assert.Equal (2, r.Y); + Assert.Equal (3, r.Width); + Assert.Equal (4, r.Height); Assert.False (r.IsCurrentTop); Assert.Equal (r.Title, r.Id); Assert.False (r.WantContinuousButtonPressed);