From 550a263155da4f8769b5e8cbde2ac2734300fd98 Mon Sep 17 00:00:00 2001 From: Tig Date: Fri, 8 Mar 2024 08:11:43 -0700 Subject: [PATCH] Tweaked r# settings and applied to View and ViewLayout.cs --- Terminal.Gui/View/Layout/ViewLayout.cs | 1358 ++++++++++++------------ Terminal.Gui/View/View.cs | 379 +++---- Terminal.sln.DotSettings | 39 +- UICatalog/UICatalog.csproj | 4 +- UnitTests/UnitTests.csproj | 2 +- 5 files changed, 904 insertions(+), 878 deletions(-) diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index d2c39ce6a..dcaf12d4b 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Diagnostics; namespace Terminal.Gui; @@ -36,108 +35,247 @@ public enum LayoutStyle public partial class View { - private bool _autoSize; + #region Frame + private Rectangle _frame; - private Dim _height = Dim.Sized (0); - private Dim _width = Dim.Sized (0); + + /// Gets or sets the absolute location and dimension of the view. + /// + /// The rectangle describing absolute location and dimension of the view, in coordinates relative to the + /// 's . + /// + /// + /// Frame is relative to the 's . + /// + /// Setting Frame will set , , , and to the + /// values of the corresponding properties of the parameter. + /// + /// This causes to be . + /// + /// Altering the Frame will eventually (when the view hierarchy is next laid out via see + /// cref="LayoutSubviews"/>) cause and + /// + /// methods to be called. + /// + /// + public Rectangle Frame + { + get => _frame; + set + { + _frame = value with { Width = Math.Max (value.Width, 0), Height = 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; + + // TODO: Figure out if the below can be optimized. + if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) + { + LayoutAdornments (); + SetTextFormatterSize (); + SetNeedsLayout (); + SetNeedsDisplay (); + } + } + } + + /// Gets the with a screen-relative location. + /// The location and size of the view in screen-relative coordinates. + public virtual Rectangle FrameToScreen () + { + Rectangle ret = Frame; + View super = SuperView; + + while (super is { }) + { + Point boundsOffset = super.GetBoundsOffset (); + ret.X += super.Frame.X + boundsOffset.X; + ret.Y += super.Frame.Y + boundsOffset.Y; + super = super.SuperView; + } + + return ret; + } + + /// + /// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means relative to the + /// View's 's . + /// + /// The coordinate relative to the 's . + /// Screen-relative column. + /// Screen-relative row. + public Point ScreenToFrame (int x, int y) + { + Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty; + var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y); + + if (SuperView is { }) + { + Point superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y); + ret = new (superFrame.X - Frame.X, superFrame.Y - Frame.Y); + } + + return ret; + } + private Pos _x = Pos.At (0); + + /// Gets or sets the X position for the view (the column). + /// The object representing the X position. + /// + /// + /// If set to a relative value (e.g. ) 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. + /// + /// + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . + /// + /// The default value is Pos.At (0). + /// + public Pos X + { + get => VerifyIsInitialized (_x, nameof (X)); + set + { + _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); + OnResizeNeeded (); + } + } + private Pos _y = Pos.At (0); - /// - /// Gets or sets a flag that determines whether the View will be automatically resized to fit the - /// within . + /// Gets or sets the Y position for the view (the row). + /// The object representing the Y position. + /// /// - /// 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. + /// If set to a relative value (e.g. ) the value is indeterminate until the view has been + /// initialized ( is true) and has been + /// called. /// /// - /// If is set to then and - /// will be changed to if they are not already. + /// Changing this property will eventually (when the view is next drawn) cause the + /// and methods to be called. /// /// - /// If is set to then and - /// will left unchanged. + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . /// - /// - public virtual bool AutoSize + /// The default value is Pos.At (0). + /// + public Pos Y { - get => _autoSize; + get => VerifyIsInitialized (_y, nameof (Y)); set { - if (Width != Dim.Sized (0) && Height != Dim.Sized (0)) - { - Debug.WriteLine ( - $@"WARNING: {GetType ().Name} - Setting {nameof (AutoSize)} invalidates {nameof (Width)} and {nameof (Height)}." - ); - } - - bool v = ResizeView (value); - TextFormatter.AutoSize = v; - - if (_autoSize != v) - { - _autoSize = v; - TextFormatter.NeedsFormat = true; - UpdateTextFormatterText (); - OnResizeNeeded (); - } + _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); + OnResizeNeeded (); } } - /// - /// The 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 adornments (, , 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 Border Border { get; private set; } + private Dim _height = Dim.Sized (0); - /// Gets or sets whether the view has a one row/col thick border. + /// Gets or sets the height dimension of the view. + /// The object representing the height of the view (the number of rows). /// /// - /// 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. + /// If set to a relative value (e.g. ) the value is indeterminate until the view has + /// been initialized ( is true) and has been + /// called. /// /// - /// Setting this property to is equivalent to setting 's - /// to `0` and to . + /// Changing this property will eventually (when the view is next drawn) cause the + /// and methods to be called. /// - /// For more advanced customization of the view's border, manipulate see directly. + /// + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . + /// + /// The default value is Dim.Sized (0). /// - public LineStyle BorderStyle + public Dim Height { - get => Border.LineStyle; + get => VerifyIsInitialized (_height, nameof (Height)); set { - if (value != LineStyle.None) + _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); + + if (AutoSize) { - Border.Thickness = new (1); - } - else - { - Border.Thickness = new (0); + throw new InvalidOperationException (@$"Must set AutoSize to false before setting {nameof (Height)}."); } - Border.LineStyle = value; - LayoutAdornments (); - SetNeedsLayout (); + //if (ValidatePosDim) { + bool isValidNewAutoSize = AutoSize && IsValidAutoSizeHeight (_height); + + if (IsAdded && AutoSize && !isValidNewAutoSize) + { + throw new InvalidOperationException ( + @$"Must set AutoSize to false before setting the {nameof (Height)}." + ); + } + + //} + OnResizeNeeded (); } } + private Dim _width = Dim.Sized (0); + + /// Gets or sets the width dimension of the view. + /// The object representing the width of the view (the number of columns). + /// + /// + /// If set to a relative value (e.g. ) 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. + /// + /// + /// Changing this property will cause to be updated. If the new value is not of type + /// the will change to . + /// + /// The default value is Dim.Sized (0). + /// + public Dim Width + { + get => VerifyIsInitialized (_width, nameof (Width)); + set + { + _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); + + if (AutoSize) + { + throw new InvalidOperationException (@$"Must set AutoSize to false before setting {nameof (Width)}."); + } + + bool isValidNewAutoSize = AutoSize && IsValidAutoSizeWidth (_width); + + if (IsAdded && AutoSize && !isValidNewAutoSize) + { + throw new InvalidOperationException (@$"Must set AutoSize to false before setting {nameof (Width)}."); + } + + OnResizeNeeded (); + } + } + + #endregion Frame + + #region Bounds + /// /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where /// subviews and content are presented. @@ -208,95 +346,432 @@ public partial class View } } - /// Gets or sets the absolute location and dimension of the view. - /// - /// The rectangle describing absolute location and dimension of the view, in coordinates relative to the - /// 's . - /// - /// - /// Frame is relative to the 's . - /// - /// Setting Frame will set , , , and to the - /// values of the corresponding properties of the parameter. - /// - /// This causes to be . - /// - /// Altering the Frame will eventually (when the view hierarchy is next laid out via see - /// cref="LayoutSubviews"/>) cause and - /// - /// methods to be called. - /// - /// - public Rectangle Frame + /// Converts a -relative rectangle to a screen-relative rectangle. + public Rectangle BoundsToScreen (in Rectangle bounds) { - get => _frame; - set + // Translate bounds to Frame (our SuperView's Bounds-relative coordinates) + Point boundsOffset = GetBoundsOffset (); + + Rectangle screen = FrameToScreen (); + screen.Offset (boundsOffset.X + bounds.X, boundsOffset.Y + bounds.Y); + + return new (screen.Location, bounds.Size); + } + + /// 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) + { + Point screen = ScreenToFrame (x, y); + Point boundsOffset = GetBoundsOffset (); + + return new (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y); + } + + /// + /// 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 virtual Point GetBoundsOffset () + { + if (Padding is null) { - _frame = value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) }; + return Point.Empty; + } - // 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; + return Padding.Thickness.GetInside (Padding.Frame).Location; + } - // TODO: Figure out if the below can be optimized. - if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) + #endregion Bounds + + #region Adornments + + // TODO: Move this to Adornment as a static factory method + /// + /// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction. + /// And, because Adornments don't have Adornments. It's internal to support unit tests. + /// + /// + /// + /// + internal virtual Adornment CreateAdornment (Type adornmentType) + { + void ThicknessChangedHandler (object sender, EventArgs e) + { + if (IsInitialized) { LayoutAdornments (); - SetTextFormatterSize (); - SetNeedsLayout (); - SetNeedsDisplay (); } + + SetNeedsLayout (); + SetNeedsDisplay (); } + + Adornment adornment; + + adornment = Activator.CreateInstance (adornmentType, this) as Adornment; + adornment.ThicknessChanged += ThicknessChangedHandler; + + return adornment; } - /// Gets or sets the height dimension of the view. - /// The object representing the height of the view (the number of rows). + /// + /// The that enables separation of a View from other SubViews of the same + /// SuperView. The margin offsets the from the . + /// /// /// - /// If set to a relative value (e.g. ) the value is indeterminate until the view has - /// been initialized ( is true) and has been - /// called. + /// The adornments (, , and ) are not part of the + /// View's content and are not clipped by the View's Clip Area. /// /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// Changing the size of an adornment (, , or ) will + /// change the size of and trigger to update the layout of the + /// and its . /// - /// - /// Changing this property will cause to be updated. If the new value is not of type - /// the will change to . - /// - /// The default value is Dim.Sized (0). /// - public Dim Height + public Margin Margin { get; private set; } + + /// + /// The 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 adornments (, , 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 Border 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 => VerifyIsInitialized (_height, nameof (Height)); + get => Border.LineStyle; set { - _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); - - if (AutoSize) + if (value != LineStyle.None) { - throw new InvalidOperationException (@$"Must set AutoSize to false before setting {nameof (Height)}."); + Border.Thickness = new (1); + } + else + { + Border.Thickness = new (0); } - //if (ValidatePosDim) { - bool isValidNewAutoSize = AutoSize && IsValidAutoSizeHeight (_height); - - if (IsAdded && AutoSize && !isValidNewAutoSize) - { - throw new InvalidOperationException ( - @$"Must set AutoSize to false before setting the {nameof (Height)}." - ); - } - - //} - OnResizeNeeded (); + Border.LineStyle = value; + LayoutAdornments (); + SetNeedsLayout (); } } + /// + /// The inside of the view that offsets the + /// from the . + /// + /// + /// + /// The adornments (, , 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 Padding Padding { get; private set; } + + /// + /// Gets the thickness describing the sum of the Adornments' thicknesses. + /// + /// A thickness that describes the sum of the Adornments' thicknesses. + public Thickness GetAdornmentsThickness () { return Margin.Thickness + Border.Thickness + Padding.Thickness; } + + /// Overriden by to do nothing, as the does not have adornments. + internal virtual void LayoutAdornments () + { + if (Margin is null) + { + return; // CreateAdornments () has not been called yet + } + + if (Margin.Frame.Size != Frame.Size) + { + Margin._frame = Rectangle.Empty with { Size = Frame.Size }; + Margin.X = 0; + Margin.Y = 0; + Margin.Width = Frame.Size.Width; + Margin.Height = Frame.Size.Height; + Margin.SetNeedsLayout (); + Margin.SetNeedsDisplay (); + } + + Rectangle border = Margin.Thickness.GetInside (Margin.Frame); + + if (border != Border.Frame) + { + Border._frame = border; + Border.X = border.Location.X; + Border.Y = border.Location.Y; + Border.Width = border.Size.Width; + Border.Height = border.Size.Height; + Border.SetNeedsLayout (); + Border.SetNeedsDisplay (); + } + + Rectangle padding = Border.Thickness.GetInside (Border.Frame); + + if (padding != Padding.Frame) + { + Padding._frame = padding; + Padding.X = padding.Location.X; + Padding.Y = padding.Location.Y; + Padding.Width = padding.Size.Width; + Padding.Height = padding.Size.Height; + Padding.SetNeedsLayout (); + Padding.SetNeedsDisplay (); + } + } + + #endregion Adornments + + #region AutoSize + + private bool _autoSize; + + /// + /// Gets or sets a flag that determines whether the View will be automatically resized to fit the + /// within . + /// + /// The default is . Set to to turn on AutoSize. If + /// then and will be used if can + /// fit; if won't fit the view will be resized as needed. + /// + /// + /// If is set to then and + /// will be changed to if they are not already. + /// + /// + /// If is set to then and + /// will left unchanged. + /// + /// + public virtual bool AutoSize + { + get => _autoSize; + set + { + if (Width != Dim.Sized (0) && Height != Dim.Sized (0)) + { + Debug.WriteLine ( + $@"WARNING: {GetType ().Name} - Setting {nameof (AutoSize)} invalidates {nameof (Width)} and {nameof (Height)}." + ); + } + + bool v = ResizeView (value); + TextFormatter.AutoSize = v; + + if (_autoSize != v) + { + _autoSize = v; + TextFormatter.NeedsFormat = true; + UpdateTextFormatterText (); + OnResizeNeeded (); + } + } + } + + /// If is true, resizes the view. + /// + /// + private bool ResizeView (bool autoSize) + { + if (!autoSize) + { + return false; + } + + var boundsChanged = true; + Size 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; + } + + /// Determines if the View's can be set to a new value. + /// TrySetHeight can only be called when AutoSize is true (or being set to true). + /// + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// + internal bool TrySetHeight (int desiredHeight, out int resultHeight) + { + int h = desiredHeight; + bool canSetHeight; + + switch (Height) + { + case Dim.DimCombine _: + case Dim.DimView _: + case Dim.DimFill _: + // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. + h = Height.Anchor (h); + canSetHeight = !ValidatePosDim; + + break; + case Dim.DimFactor factor: + // Tries to get the SuperView height otherwise the view height. + int sh = SuperView is { } ? 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; + } + + /// Determines if the View's can be set to a new value. + /// TrySetWidth can only be called when AutoSize is true (or being set to true). + /// + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// + internal bool TrySetWidth (int desiredWidth, out int resultWidth) + { + int w = desiredWidth; + bool canSetWidth; + + switch (Width) + { + case Dim.DimCombine _: + case Dim.DimView _: + case Dim.DimFill _: + // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored. + w = Width.Anchor (w); + canSetWidth = !ValidatePosDim; + + break; + case Dim.DimFactor factor: + // Tries to get the SuperView Width otherwise the view Width. + int sw = SuperView is { } ? 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; + } + + /// Resizes the View to fit the specified size. Factors in the HotKey. + /// ResizeBoundsToFit can only be called when AutoSize is true (or being set to true). + /// + /// whether the Bounds was changed or not + private bool ResizeBoundsToFit (Size size) + { + //if (AutoSize == false) { + // throw new InvalidOperationException ("ResizeBoundsToFit can only be called when AutoSize is true"); + //} + + var boundsChanged = false; + bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW); + bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH); + + if (canSizeW) + { + boundsChanged = true; + _width = rW; + } + + if (canSizeH) + { + boundsChanged = true; + _height = rH; + } + + if (boundsChanged) + { + Bounds = new (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height); + } + + return boundsChanged; + } + + #endregion AutoSize + + #region Layout Engine + /// /// Controls how the View's is computed during . If the style is /// set to , LayoutSubviews does not change the . If the style is @@ -334,167 +809,10 @@ public partial class View } } - /// - /// The that enables separation of a View from other SubViews of the same - /// SuperView. The margin offsets the from the . - /// - /// - /// - /// The adornments (, , and ) are not part of the - /// View's content and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of an adornment (, , or ) will - /// change the size of and trigger to update the layout of the - /// and its . - /// - /// - public Margin Margin { get; private set; } - - /// - /// The inside of the view that offsets the - /// from the . - /// - /// - /// - /// The adornments (, , 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 Padding Padding { get; private 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; } - - /// Gets or sets the width dimension of the view. - /// The object representing the width of the view (the number of columns). - /// - /// - /// If set to a relative value (e.g. ) 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. - /// - /// - /// Changing this property will cause to be updated. If the new value is not of type - /// the will change to . - /// - /// The default value is Dim.Sized (0). - /// - public Dim Width - { - get => VerifyIsInitialized (_width, nameof (Width)); - set - { - _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); - - if (AutoSize) - { - throw new InvalidOperationException (@$"Must set AutoSize to false before setting {nameof (Width)}."); - } - - bool isValidNewAutoSize = AutoSize && IsValidAutoSizeWidth (_width); - - if (IsAdded && AutoSize && !isValidNewAutoSize) - { - throw new InvalidOperationException (@$"Must set AutoSize to false before setting {nameof (Width)}."); - } - - OnResizeNeeded (); - } - } - - /// Gets or sets the X position for the view (the column). - /// The object representing the X position. - /// - /// - /// If set to a relative value (e.g. ) 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. - /// - /// - /// Changing this property will cause to be updated. If the new value is not of type - /// the will change to . - /// - /// The default value is Pos.At (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 Y position for the view (the row). - /// The object representing the Y position. - /// - /// - /// If set to a relative value (e.g. ) 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. - /// - /// - /// Changing this property will cause to be updated. If the new value is not of type - /// the will change to . - /// - /// The default value is Pos.At (0). - /// - public Pos Y - { - get => VerifyIsInitialized (_y, nameof (Y)); - set - { - _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); - OnResizeNeeded (); - } - } + #endregion Layout Engine internal bool LayoutNeeded { get; private set; } = true; - /// - /// 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; - - /// Converts a -relative rectangle to a screen-relative rectangle. - public Rectangle BoundsToScreen (in Rectangle bounds) - { - // Translate bounds to Frame (our SuperView's Bounds-relative coordinates) - Point boundsOffset = GetBoundsOffset (); - - Rectangle screen = FrameToScreen (); - screen.Offset (boundsOffset.X + bounds.X, boundsOffset.Y + bounds.Y); - - return new (screen.Location, bounds.Size); - } - #nullable enable /// Finds which view that belong to the superview at the provided location. /// The superview where to look for. @@ -562,44 +880,6 @@ public partial class View } #nullable restore - /// Gets the with a screen-relative location. - /// The location and size of the view in screen-relative coordinates. - public virtual Rectangle FrameToScreen () - { - Rectangle ret = Frame; - View super = SuperView; - - while (super is { }) - { - Point boundsOffset = super.GetBoundsOffset (); - ret.X += super.Frame.X + boundsOffset.X; - ret.Y += super.Frame.Y + boundsOffset.Y; - super = super.SuperView; - } - - return ret; - } - - /// - /// Gets the thickness describing the sum of the Adornments' thicknesses. - /// - /// A thickness that describes the sum of the Adornments' thicknesses. - public Thickness GetAdornmentsThickness () { return Margin.Thickness + Border.Thickness + Padding.Thickness; } - - /// - /// 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 virtual Point GetBoundsOffset () - { - if (Padding is null) - { - return Point.Empty; - } - - return Padding.Thickness.GetInside (Padding.Frame).Location; - } - /// Fired after the View's method has completed. /// /// Subscribe to this event to perform tasks when the has been resized or the layout has @@ -642,7 +922,7 @@ public partial class View LayoutAdornments (); Rectangle oldBounds = Bounds; - OnLayoutStarted (new() { OldBounds = oldBounds }); + OnLayoutStarted (new () { OldBounds = oldBounds }); SetTextFormatterSize (); @@ -669,184 +949,12 @@ public partial class View LayoutNeeded = false; - OnLayoutComplete (new() { OldBounds = oldBounds }); - } - - /// 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) - { - Point screen = ScreenToFrame (x, y); - Point boundsOffset = GetBoundsOffset (); - - return new (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y); - } - - /// - /// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means relative to the - /// View's 's . - /// - /// The coordinate relative to the 's . - /// Screen-relative column. - /// Screen-relative row. - public Point ScreenToFrame (int x, int y) - { - Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty; - var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y); - - if (SuperView is { }) - { - Point superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y); - ret = new (superFrame.X - Frame.X, superFrame.Y - Frame.Y); - } - - return ret; + OnLayoutComplete (new () { OldBounds = oldBounds }); } /// Indicates that the view does not need to be laid out. protected void ClearLayoutNeeded () { LayoutNeeded = false; } - internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) - { - // BUGBUG: This should really only work on initialized subviews - foreach (View 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); - } - } - - 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 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; - } - } - - /// - /// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction. - /// And, because Adornments don't have Adornments. It's internal to support unit tests. - /// - /// - /// - /// - internal virtual Adornment CreateAdornment (Type adornmentType) - { - void ThicknessChangedHandler (object sender, EventArgs e) - { - if (IsInitialized) - { - LayoutAdornments (); - } - - SetNeedsLayout (); - SetNeedsDisplay (); - } - - Adornment adornment; - - adornment = Activator.CreateInstance (adornmentType, this) as Adornment; - adornment.ThicknessChanged += ThicknessChangedHandler; - - return adornment; - } - - /// Overriden by to do nothing, as the does not have adornments. - internal virtual void LayoutAdornments () - { - if (Margin is null) - { - return; // CreateAdornments () has not been called yet - } - - if (Margin.Frame.Size != Frame.Size) - { - Margin._frame = Rectangle.Empty with { Size = Frame.Size }; - Margin.X = 0; - Margin.Y = 0; - Margin.Width = Frame.Size.Width; - Margin.Height = Frame.Size.Height; - Margin.SetNeedsLayout (); - Margin.SetNeedsDisplay (); - } - - Rectangle border = Margin.Thickness.GetInside (Margin.Frame); - - if (border != Border.Frame) - { - Border._frame = border; - Border.X = border.Location.X; - Border.Y = border.Location.Y; - Border.Width = border.Size.Width; - Border.Height = border.Size.Height; - Border.SetNeedsLayout (); - Border.SetNeedsDisplay (); - } - - Rectangle padding = Border.Thickness.GetInside (Border.Frame); - - if (padding != Padding.Frame) - { - Padding._frame = padding; - Padding.X = padding.Location.X; - Padding.Y = padding.Location.Y; - Padding.Width = padding.Size.Width; - Padding.Height = padding.Size.Height; - Padding.SetNeedsLayout (); - Padding.SetNeedsDisplay (); - } - } - /// /// Raises the event. Called from before all sub-views /// have been laid out. @@ -1155,6 +1263,71 @@ public partial class View } } + internal void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + // BUGBUG: This should really only work on initialized subviews + foreach (View 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); + } + } + + 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 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; + } + } + // https://en.wikipedia.org/wiki/Topological_sorting internal static List TopologicalSort ( View superView, @@ -1249,106 +1422,6 @@ public partial class View return result; } // TopologicalSort - /// Determines if the View's can be set to a new value. - /// TrySetHeight can only be called when AutoSize is true (or being set to true). - /// - /// - /// Contains the width that would result if were set to - /// "/> - /// - /// - /// if the View's can be changed to the specified value. False - /// otherwise. - /// - internal bool TrySetHeight (int desiredHeight, out int resultHeight) - { - int h = desiredHeight; - bool canSetHeight; - - switch (Height) - { - case Dim.DimCombine _: - case Dim.DimView _: - case Dim.DimFill _: - // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. - h = Height.Anchor (h); - canSetHeight = !ValidatePosDim; - - break; - case Dim.DimFactor factor: - // Tries to get the SuperView height otherwise the view height. - int sh = SuperView is { } ? 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; - } - - /// Determines if the View's can be set to a new value. - /// TrySetWidth can only be called when AutoSize is true (or being set to true). - /// - /// - /// Contains the width that would result if were set to - /// "/> - /// - /// - /// if the View's can be changed to the specified value. False - /// otherwise. - /// - internal bool TrySetWidth (int desiredWidth, out int resultWidth) - { - int w = desiredWidth; - bool canSetWidth; - - switch (Width) - { - case Dim.DimCombine _: - case Dim.DimView _: - case Dim.DimFill _: - // It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored. - w = Width.Anchor (w); - canSetWidth = !ValidatePosDim; - - break; - case Dim.DimFactor factor: - // Tries to get the SuperView Width otherwise the view Width. - int sw = SuperView is { } ? 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; - } - private void LayoutSubview (View v, Rectangle contentArea) { //if (v.LayoutStyle == LayoutStyle.Computed) { @@ -1360,69 +1433,7 @@ public partial class View v.LayoutNeeded = false; } - /// Resizes the View to fit the specified size. Factors in the HotKey. - /// ResizeBoundsToFit can only be called when AutoSize is true (or being set to true). - /// - /// whether the Bounds was changed or not - private bool ResizeBoundsToFit (Size size) - { - //if (AutoSize == false) { - // throw new InvalidOperationException ("ResizeBoundsToFit can only be called when AutoSize is true"); - //} - - var boundsChanged = false; - bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW); - bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH); - - if (canSizeW) - { - boundsChanged = true; - _width = rW; - } - - if (canSizeH) - { - boundsChanged = true; - _height = rH; - } - - if (boundsChanged) - { - Bounds = new (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height); - } - - return boundsChanged; - } - - /// If is true, resizes the view. - /// - /// - private bool ResizeView (bool autoSize) - { - if (!autoSize) - { - return false; - } - - var boundsChanged = true; - Size 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; - } + #region Diagnostics // Diagnostics to highlight when Width or Height is read before the view has been initialized private Dim VerifyIsInitialized (Dim dim, string member) @@ -1451,4 +1462,15 @@ public partial class View #endif // DEBUG return pos; } + + /// 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; } + + #endregion } diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 32d9e584c..3ac4f75f2 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -73,7 +73,8 @@ namespace Terminal.Gui; /// a View can be accessed with the property. /// /// -/// To flag a region of the View's to be redrawn call . +/// To flag a region of the View's to be redrawn call +/// . /// To flag the entire view for redraw call . /// /// @@ -113,14 +114,7 @@ namespace Terminal.Gui; public partial class View : Responder, ISupportInitializeNotification { - private bool _oldEnabled; - - /// Gets or sets whether a view is cleared if the property is . - public bool ClearOnVisibleFalse { get; set; } = true; - - /// Gets or sets arbitrary data for the view. - /// This property is not used internally. - public object Data { get; set; } + #region Constructors and Initialization /// /// Points to the current driver in use by the view, it is a convenience property for simplifying the development @@ -128,6 +122,170 @@ public partial class View : Responder, ISupportInitializeNotification /// public static ConsoleDriver Driver => Application.Driver; + /// Initializes a new instance of . + /// + /// + /// Use , , , and properties to dynamically + /// control the size and location of the view. The will be created using + /// coordinates. The initial size ( ) will be adjusted + /// to fit the contents of , including newlines ('\n') for multiple lines. + /// + /// If is greater than one, word wrapping is provided. + /// + /// This constructor initialize a View with a of . + /// Use , , , and properties to dynamically + /// control the size and location of the view, changing it to . + /// + /// + public View () + { + HotKeySpecifier = (Rune)'_'; + TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged; + + TextDirection = TextDirection.LeftRight_TopBottom; + Text = string.Empty; + + CanFocus = false; + TabIndex = -1; + TabStop = false; + + AddCommands (); + + Margin = CreateAdornment (typeof (Margin)) as Margin; + Border = CreateAdornment (typeof (Border)) as Border; + Padding = CreateAdornment (typeof (Padding)) as Padding; + } + + /// + /// 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; + + /// + /// Get or sets if the has been initialized (via + /// and ). + /// + /// + /// If first-run-only initialization is preferred, overrides to + /// can be implemented, in which case the + /// methods will only be called if + /// is . This allows proper + /// inheritance hierarchies to override base class layout code optimally by doing so only on first + /// run, instead of on every run. + /// + public virtual bool IsInitialized { get; set; } + + /// Signals the View that initialization is starting. See . + /// + /// + /// Views can opt-in to more sophisticated initialization by implementing overrides to + /// and which will be called + /// when the is initialized. + /// + /// + /// If first-run-only initialization is preferred, overrides to can + /// be implemented too, in which case the methods will only be called if + /// is . This allows proper + /// inheritance hierarchies to override base class layout code optimally by doing so only on + /// first run, instead of on every run. + /// + /// + public virtual void BeginInit () + { + if (IsInitialized) + { + throw new InvalidOperationException ("The view is already initialized."); + } + + _oldCanFocus = CanFocus; + _oldTabIndex = _tabIndex; + + if (_subviews?.Count > 0) + { + foreach (View view in _subviews) + { + if (!view.IsInitialized) + { + view.BeginInit (); + } + } + } + } + + // TODO: Implement logic that allows EndInit to throw if BeginInit has not been called + // TODO: See EndInit_Called_Without_BeginInit_Throws test. + + /// Signals the View that initialization is ending. See . + /// + /// Initializes all Subviews and Invokes the event. + /// + public virtual void EndInit () + { + if (IsInitialized) + { + throw new InvalidOperationException ("The view is already initialized."); + } + + IsInitialized = true; + + // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate. + // TODO: Verify UpdateTextDirection really needs to be called here. + // These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called. + UpdateTextDirection (TextDirection); + UpdateTextFormatterText (); + OnResizeNeeded (); + + if (_subviews is { }) + { + foreach (View view in _subviews) + { + if (!view.IsInitialized) + { + view.EndInit (); + } + } + } + + Initialized?.Invoke (this, EventArgs.Empty); + } + + #endregion Constructors and Initialization + + /// Gets or sets an identifier for the view; + /// The identifier. + /// The id should be unique across all Views that share a SuperView. + public string Id { get; set; } = ""; + + /// Gets or sets arbitrary data for the view. + /// This property is not used internally. + public object Data { get; set; } + + /// + /// Cancelable event fired when the command is invoked. Set + /// + /// to cancel the event. + /// + public event EventHandler Accept; + + /// + /// Called when the command is invoked. Fires the + /// event. + /// + /// If the event was canceled. + protected bool? OnAccept () + { + var args = new CancelEventArgs (); + Accept?.Invoke (this, args); + + return args.Cancel; + } + + #region Visibility + + private bool _oldEnabled; + /// public override bool Enabled { @@ -176,13 +334,13 @@ public partial class View : Responder, ISupportInitializeNotification } } - /// Gets or sets an identifier for the view; - /// The identifier. - /// The id should be unique across all Views that share a SuperView. - public string Id { get; set; } = ""; + /// Event fired when the value is being changed. + public event EventHandler EnabledChanged; + + /// + public override void OnEnabledChanged () { EnabledChanged?.Invoke (this, EventArgs.Empty); } /// - /// > public override bool Visible { get => base.Visible; @@ -211,63 +369,15 @@ public partial class View : Responder, ISupportInitializeNotification } } - /// Event fired when the value is being changed. - public event EventHandler EnabledChanged; - - /// - public override void OnEnabledChanged () { EnabledChanged?.Invoke (this, EventArgs.Empty); } - /// public override void OnVisibleChanged () { VisibleChanged?.Invoke (this, EventArgs.Empty); } - /// Pretty prints the View - /// - public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; } + /// Gets or sets whether a view is cleared if the property is . + public bool ClearOnVisibleFalse { get; set; } = true; /// Event fired when the value is being changed. public event EventHandler VisibleChanged; - /// - /// Cancelable event fired when the command is invoked. Set - /// to cancel the event. - /// - public event EventHandler Accept; - - /// - /// Called when the command is invoked. Fires the - /// event. - /// - /// If the event was canceled. - protected bool? OnAccept () - { - var args = new CancelEventArgs (); - Accept?.Invoke (this, args); - return args.Cancel; - } - - /// - protected override void Dispose (bool disposing) - { - LineCanvas.Dispose (); - - Margin?.Dispose (); - Margin = null; - Border?.Dispose (); - Border = null; - Padding?.Dispose (); - Padding = null; - - for (int i = InternalSubviews.Count - 1; i >= 0; i--) - { - View subview = InternalSubviews [i]; - Remove (subview); - subview.Dispose (); - } - - base.Dispose (disposing); - Debug.Assert (InternalSubviews.Count == 0); - } - private bool CanBeVisible (View view) { if (!view.Visible) @@ -286,6 +396,8 @@ public partial class View : Responder, ISupportInitializeNotification return true; } + #endregion Visibility + #region Title private string _title = string.Empty; @@ -382,129 +494,30 @@ public partial class View : Responder, ISupportInitializeNotification #endregion - #region Constructors and Initialization + /// Pretty prints the View + /// + public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; } - /// Initializes a new instance of . - /// - /// - /// Use , , , and properties to dynamically - /// control the size and location of the view. The will be created using - /// coordinates. The initial size ( ) will be adjusted - /// to fit the contents of , including newlines ('\n') for multiple lines. - /// - /// If is greater than one, word wrapping is provided. - /// - /// This constructor initialize a View with a of . - /// Use , , , and properties to dynamically - /// control the size and location of the view, changing it to . - /// - /// - public View () + /// + protected override void Dispose (bool disposing) { - HotKeySpecifier = (Rune)'_'; - TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged; + LineCanvas.Dispose (); - TextDirection = TextDirection.LeftRight_TopBottom; - Text = string.Empty; + Margin?.Dispose (); + Margin = null; + Border?.Dispose (); + Border = null; + Padding?.Dispose (); + Padding = null; - CanFocus = false; - TabIndex = -1; - TabStop = false; + for (int i = InternalSubviews.Count - 1; i >= 0; i--) + { + View subview = InternalSubviews [i]; + Remove (subview); + subview.Dispose (); + } - AddCommands (); - - Margin = CreateAdornment (typeof (Margin)) as Margin; - Border = CreateAdornment (typeof (Border)) as Border; - Padding = CreateAdornment (typeof (Padding)) as Padding; + base.Dispose (disposing); + Debug.Assert (InternalSubviews.Count == 0); } - - /// - /// Get or sets if the has been initialized (via - /// and ). - /// - /// - /// If first-run-only initialization is preferred, overrides to - /// can be implemented, in which case the - /// methods will only be called if - /// is . This allows proper - /// inheritance hierarchies to override base class layout code optimally by doing so only on first - /// run, instead of on every run. - /// - public virtual bool IsInitialized { get; set; } - - /// Signals the View that initialization is starting. See . - /// - /// - /// Views can opt-in to more sophisticated initialization by implementing overrides to - /// and which will be called - /// when the is initialized. - /// - /// - /// If first-run-only initialization is preferred, overrides to can - /// be implemented too, in which case the methods will only be called if - /// is . This allows proper - /// inheritance hierarchies to override base class layout code optimally by doing so only on - /// first run, instead of on every run. - /// - /// - public virtual void BeginInit () - { - if (IsInitialized) - { - throw new InvalidOperationException ("The view is already initialized."); - } - - _oldCanFocus = CanFocus; - _oldTabIndex = _tabIndex; - - if (_subviews?.Count > 0) - { - foreach (View view in _subviews) - { - if (!view.IsInitialized) - { - view.BeginInit (); - } - } - } - } - - // TODO: Implement logic that allows EndInit to throw if BeginInit has not been called - // TODO: See EndInit_Called_Without_BeginInit_Throws test. - - /// Signals the View that initialization is ending. See . - /// - /// Initializes all Subviews and Invokes the event. - /// - public virtual void EndInit () - { - if (IsInitialized) - { - throw new InvalidOperationException ("The view is already initialized."); - } - - IsInitialized = true; - - // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate. - // TODO: Verify UpdateTextDirection really needs to be called here. - // These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called. - UpdateTextDirection (TextDirection); - UpdateTextFormatterText (); - OnResizeNeeded (); - - if (_subviews is { }) - { - foreach (View view in _subviews) - { - if (!view.IsInitialized) - { - view.EndInit (); - } - } - } - - Initialized?.Invoke (this, EventArgs.Empty); - } - - #endregion Constructors and Initialization } diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings index 348be86b5..ef874a7ca 100644 --- a/Terminal.sln.DotSettings +++ b/Terminal.sln.DotSettings @@ -1,4 +1,6 @@  + BackingField + Inherit True 5000 1000 @@ -37,11 +39,13 @@ WARNING WARNING WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="File Layout"><CSCodeStyleAttributes /><CSOptimizeUsings></CSOptimizeUsings><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSReorderTypeMembers>True</CSReorderTypeMembers></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Full Cleanup"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><XMLReformatCode>True</XMLReformatCode><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><HtmlReformatCode>True</HtmlReformatCode><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor></Profile> - Built-in: Full Cleanup + Full Cleanup True Required Required @@ -267,7 +271,7 @@ </Entry.SortBy> </Entry> </TypePattern> - <TypePattern DisplayName="Default Pattern"> + <TypePattern DisplayName="Default Pattern" Priority="100"> <Entry DisplayName="Public Delegates" Priority="100"> <Entry.Match> <And> @@ -306,6 +310,15 @@ <Name /> </Entry.SortBy> </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Access Is="0" /> + </Entry.SortBy> + </Entry> <Entry DisplayName="Fields"> <Entry.Match> <And> @@ -318,16 +331,6 @@ <Entry.SortBy> <Access Is="0" /> <Readonly /> - <Name /> - </Entry.SortBy> - </Entry> - <Entry DisplayName="Constructors"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - <Entry.SortBy> - <Static /> - <Access Is="0" /> </Entry.SortBy> </Entry> <Property DisplayName="Properties w/ Backing Field" Priority="100"> @@ -342,18 +345,6 @@ </Entry.Match> </Entry> </Property> - <Entry DisplayName="Properties, Indexers"> - <Entry.Match> - <Or> - <Kind Is="Property" /> - <Kind Is="Indexer" /> - </Or> - </Entry.Match> - <Entry.SortBy> - <Access Is="0" /> - <Name /> - </Entry.SortBy> - </Entry> <Entry DisplayName="Interface Implementations" Priority="100"> <Entry.Match> <And> diff --git a/UICatalog/UICatalog.csproj b/UICatalog/UICatalog.csproj index 58645da39..662411daf 100644 --- a/UICatalog/UICatalog.csproj +++ b/UICatalog/UICatalog.csproj @@ -29,8 +29,8 @@ - - + + diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index a45843e53..79623c6f5 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -30,7 +30,7 @@ - +