#nullable enable

This commit is contained in:
Tig
2024-11-04 10:23:30 -07:00
parent 2f9cf08246
commit 7656602245
16 changed files with 247 additions and 207 deletions

View File

@@ -1,4 +1,5 @@
using System.Numerics;
#nullable enable
using System.Numerics;
using System.Text.Json.Serialization;
namespace Terminal.Gui;
@@ -15,10 +16,7 @@ namespace Terminal.Gui;
/// with the thickness widths subtracted.
/// </para>
/// <para>
/// Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.
/// </para>
/// <para>
/// Thickness uses <see langword="float"/> intenrally. As a result, there is a potential precision loss for very
/// Thickness uses <see langword="float"/> internally. As a result, there is a potential precision loss for very
/// large numbers. This is typically not an issue for UI dimensions but could be relevant in other contexts.
/// </para>
/// </remarks>
@@ -91,7 +89,7 @@ public record struct Thickness
/// <param name="diagnosticFlags"></param>
/// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
/// <returns>The inner rectangle remaining to be drawn.</returns>
public Rectangle Draw (Rectangle rect, ViewDiagnosticFlags diagnosticFlags = ViewDiagnosticFlags.Off, string label = null)
public Rectangle Draw (Rectangle rect, ViewDiagnosticFlags diagnosticFlags = ViewDiagnosticFlags.Off, string? label = null)
{
if (rect.Size.Width < 1 || rect.Size.Height < 1)
{
@@ -134,26 +132,26 @@ public record struct Thickness
if (Right > 0)
{
Application.Driver?.FillRect (
rect with
{
X = Math.Max (0, rect.X + rect.Width - Right),
Width = Math.Min (rect.Width, Right)
},
rightChar
);
rect with
{
X = Math.Max (0, rect.X + rect.Width - Right),
Width = Math.Min (rect.Width, Right)
},
rightChar
);
}
// Draw the Bottom side
if (Bottom > 0)
{
Application.Driver?.FillRect (
rect with
{
Y = rect.Y + Math.Max (0, rect.Height - Bottom),
Height = Bottom
},
bottomChar
);
rect with
{
Y = rect.Y + Math.Max (0, rect.Height - Bottom),
Height = Bottom
},
bottomChar
);
}
if (diagnosticFlags.HasFlag (ViewDiagnosticFlags.Ruler))
@@ -192,6 +190,7 @@ public record struct Thickness
{
// Draw the diagnostics label on the bottom
string text = label is null ? string.Empty : $"{label} {this}";
var tf = new TextFormatter
{
Text = text,

View File

@@ -483,8 +483,8 @@ public class Border : Adornment
Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y))
?? mouseEvent.ScreenPosition;
int minHeight = Thickness.Vertical + Parent!.Margin.Thickness.Bottom;
int minWidth = Thickness.Horizontal + Parent!.Margin.Thickness.Right;
int minHeight = Thickness.Vertical + Parent!.Margin!.Thickness.Bottom;
int minWidth = Thickness.Horizontal + Parent!.Margin!.Thickness.Right;
// TODO: This code can be refactored to be more readable and maintainable.
switch (_arranging)
@@ -1072,7 +1072,7 @@ public class Border : Adornment
NoPadding = true,
ShadowStyle = ShadowStyle.None,
Text = $"{Glyphs.SizeVertical}",
X = Pos.Center () + Parent!.Margin.Thickness.Horizontal,
X = Pos.Center () + Parent!.Margin!.Thickness.Horizontal,
Y = 0,
Visible = false,
Data = ViewArrangement.TopResizable
@@ -1095,7 +1095,7 @@ public class Border : Adornment
ShadowStyle = ShadowStyle.None,
Text = $"{Glyphs.SizeHorizontal}",
X = Pos.AnchorEnd (),
Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
Y = Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
Visible = false,
Data = ViewArrangement.RightResizable
};
@@ -1117,7 +1117,7 @@ public class Border : Adornment
ShadowStyle = ShadowStyle.None,
Text = $"{Glyphs.SizeHorizontal}",
X = 0,
Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
Y = Pos.Center () + Parent!.Margin!.Thickness.Vertical / 2,
Visible = false,
Data = ViewArrangement.LeftResizable
};
@@ -1138,7 +1138,7 @@ public class Border : Adornment
NoPadding = true,
ShadowStyle = ShadowStyle.None,
Text = $"{Glyphs.SizeVertical}",
X = Pos.Center () + Parent!.Margin.Thickness.Horizontal / 2,
X = Pos.Center () + Parent!.Margin!.Thickness.Horizontal / 2,
Y = Pos.AnchorEnd (),
Visible = false,
Data = ViewArrangement.BottomResizable

View File

@@ -275,13 +275,13 @@ public class Margin : Adornment
{
case ShadowStyle.Transparent:
// BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
_rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
_rightShadow.Y = Parent!.Border!.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
break;
case ShadowStyle.Opaque:
// BUGBUG: This doesn't work right for all Border.Top sizes - Need an API on Border that gives top-right location of line corner.
_rightShadow.Y = Parent!.Border.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
_rightShadow.Y = Parent!.Border!.Thickness.Top > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).Y + 1 : 0;
_bottomShadow.X = Parent.Border.Thickness.Left > 0 ? ScreenToViewport (Parent.Border.GetBorderRectangle ().Location).X + 1 : 0;
break;

View File

@@ -20,9 +20,6 @@ public class DrawEventArgs : CancelEventArgs
OldViewport = oldViewport;
}
/// <summary>If set to true, the draw operation will be canceled, if applicable.</summary>
public bool Cancel { get; set; }
/// <summary>Gets the Content-relative rectangle describing the old visible viewport into the <see cref="View"/>.</summary>
public Rectangle OldViewport { get; }

View File

@@ -1,4 +1,5 @@
namespace Terminal.Gui;
#nullable enable
namespace Terminal.Gui;
public partial class View // Adornments
{
@@ -7,9 +8,10 @@ public partial class View // Adornments
/// </summary>
private void SetupAdornments ()
{
//// TODO: Move this to Adornment as a static factory method
// TODO: Move this to Adornment as a static factory method
if (this is not Adornment)
{
// TODO: Make the Adornments Lazy and only create them when needed
Margin = new (this);
Border = new (this);
Padding = new (this);
@@ -61,7 +63,7 @@ public partial class View // Adornments
/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
/// </para>
/// </remarks>
public Margin Margin { get; private set; }
public Margin? Margin { get; private set; }
private ShadowStyle _shadowStyle;
@@ -117,7 +119,7 @@ public partial class View // Adornments
/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
/// </para>
/// </remarks>
public Border Border { get; private set; }
public Border? Border { get; private set; }
/// <summary>Gets or sets whether the view has a one row/col thick border.</summary>
/// <remarks>
@@ -130,6 +132,10 @@ public partial class View // Adornments
/// Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s
/// <see cref="Adornment.Thickness"/> to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>.
/// </para>
/// <para>
/// Calls <see cref="OnBorderStyleChanging"/> and raises <see cref="BorderStyleChanging"/>, which allows change
/// to be cancelled.
/// </para>
/// <para>For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.</para>
/// </remarks>
public LineStyle BorderStyle
@@ -150,32 +156,32 @@ public partial class View // Adornments
return;
}
BorderStyleChanging?.Invoke (this, e);
if (e.Cancel)
{
return;
}
SetBorderStyle (e.NewValue);
SetAdornmentFrames ();
SetNeedsLayout ();
}
}
/// <summary>
/// Called when the <see cref="BorderStyle"/> is changing. Invokes <see cref="BorderStyleChanging"/>, which allows the
/// event to be cancelled.
/// Called when the <see cref="BorderStyle"/> is changing.
/// </summary>
/// <remarks>
/// Override <see cref="SetBorderStyle"/> to prevent the <see cref="BorderStyle"/> from changing.
/// Set e.Cancel to true to prevent the <see cref="BorderStyle"/> from changing.
/// </remarks>
/// <param name="e"></param>
protected virtual bool OnBorderStyleChanging (CancelEventArgs<LineStyle> e)
{
if (Border is null)
{
return false;
}
protected virtual bool OnBorderStyleChanging (CancelEventArgs<LineStyle> e) { return false; }
BorderStyleChanging?.Invoke (this, e);
return e.Cancel;
}
/// <summary>
/// Fired when the <see cref="BorderStyle"/> is changing. Allows the event to be cancelled.
/// </summary>
public event EventHandler<CancelEventArgs<LineStyle>>? BorderStyleChanging;
/// <summary>
/// Sets the <see cref="BorderStyle"/> of the view to the specified value.
@@ -198,25 +204,19 @@ public partial class View // Adornments
{
if (value != LineStyle.None)
{
if (Border.Thickness == Thickness.Empty)
if (Border!.Thickness == Thickness.Empty)
{
Border.Thickness = new (1);
}
}
else
{
Border.Thickness = new (0);
Border!.Thickness = new (0);
}
Border.LineStyle = value;
}
/// <summary>
/// Fired when the <see cref="BorderStyle"/> is changing. Allows the event to be cancelled.
/// </summary>
[CanBeNull]
public event EventHandler<CancelEventArgs<LineStyle>> BorderStyleChanging;
/// <summary>
/// The <see cref="Adornment"/> inside of the view that offsets the <see cref="Viewport"/>
/// from the <see cref="Border"/>.
@@ -232,7 +232,7 @@ public partial class View // Adornments
/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
/// </para>
/// </remarks>
public Padding Padding { get; private set; }
public Padding? Padding { get; private set; }
/// <summary>
/// <para>Gets the thickness describing the sum of the Adornments' thicknesses.</para>
@@ -245,12 +245,24 @@ public partial class View // Adornments
/// <returns>A thickness that describes the sum of the Adornments' thicknesses.</returns>
public Thickness GetAdornmentsThickness ()
{
if (Margin is null)
Thickness result = Thickness.Empty;
if (Margin is { })
{
return Thickness.Empty;
result += Margin.Thickness;
}
return Margin.Thickness + Border.Thickness + Padding.Thickness;
if (Border is { })
{
result += Border.Thickness;
}
if (Padding is { })
{
result += Padding.Thickness;
}
return result;
}
/// <summary>Sets the Frame's of the Margin, Border, and Padding.</summary>
@@ -262,13 +274,19 @@ public partial class View // Adornments
return;
}
if (Margin is null)
if (Margin is { })
{
return; // CreateAdornments () has not been called yet
Margin!.Frame = Rectangle.Empty with { Size = Frame.Size };
}
Margin.Frame = Rectangle.Empty with { Size = Frame.Size };
Border.Frame = Margin.Thickness.GetInside (Margin.Frame);
Padding.Frame = Border.Thickness.GetInside (Border.Frame);
if (Border is { } && Margin is { })
{
Border!.Frame = Margin!.Thickness.GetInside (Margin!.Frame);
}
if (Padding is { } && Border is { })
{
Padding!.Frame = Border!.Thickness.GetInside (Border!.Frame);
}
}
}

View File

@@ -1,4 +1,5 @@
namespace Terminal.Gui;
#nullable enable
namespace Terminal.Gui;
public partial class View
{

View File

@@ -1,6 +1,4 @@
#nullable enable
using static Unix.Terminal.Curses;
namespace Terminal.Gui;
public partial class View
@@ -13,21 +11,20 @@ public partial class View
/// There is a single clip region for the entire application.
/// </para>
/// <para>
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is recommended to clone it first.
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is
/// recommended to clone it first.
/// </para>
/// </remarks>
/// <returns>The current Clip.</returns>
public static Region? GetClip ()
{
return Application.Driver?.Clip;
}
public static Region? GetClip () { return Application.Driver?.Clip; }
/// <summary>
/// Sets the Clip to the specified region.
/// </summary>
/// <remarks>
/// <para>
/// There is a single clip region for the entire application. This method sets the clip region to the specified region.
/// There is a single clip region for the entire application. This method sets the clip region to the specified
/// region.
/// </para>
/// </remarks>
/// <param name="region"></param>
@@ -47,7 +44,8 @@ public partial class View
/// There is a single clip region for the entire application. This method sets the clip region to the screen.
/// </para>
/// <para>
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is recommended to clone it first.
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is
/// recommended to clone it first.
/// </para>
/// </remarks>
/// <returns>
@@ -56,6 +54,7 @@ public partial class View
public static Region? SetClipToScreen ()
{
Region? previous = GetClip ();
if (Driver is { })
{
Driver.Clip = new (Application.Screen);
@@ -65,7 +64,7 @@ public partial class View
}
/// <summary>
/// Removes the specified rectangle from the Clip.
/// Removes the specified rectangle from the Clip.
/// </summary>
/// <remarks>
/// <para>
@@ -73,17 +72,15 @@ public partial class View
/// </para>
/// </remarks>
/// <param name="rectangle"></param>
public static void ExcludeFromClip (Rectangle rectangle)
{
Driver?.Clip?.Exclude (rectangle);
}
public static void ExcludeFromClip (Rectangle rectangle) { Driver?.Clip?.Exclude (rectangle); }
/// <summary>
/// Changes the Clip to the intersection of the current Clip and the <see cref="Frame"/> of this View.
/// </summary>
/// <remarks>
/// <para>
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is recommended to clone it first.
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is
/// recommended to clone it first.
/// </para>
/// </remarks>
/// <returns>
@@ -99,6 +96,7 @@ public partial class View
Region previous = GetClip () ?? new (Application.Screen);
Region frameRegion = previous.Clone ();
// Translate viewportRegion to screen-relative coords
Rectangle screenRect = FrameToScreen ();
frameRegion.Intersect (screenRect);
@@ -109,7 +107,7 @@ public partial class View
frameRegion.Exclude (adornment.Thickness.GetInside (Frame));
}
View.SetClip (frameRegion);
SetClip (frameRegion);
return previous;
}
@@ -125,11 +123,12 @@ public partial class View
/// If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClipContentOnly"/> set, clipping will be
/// applied to just the visible content area.
/// </para>
/// <remarks>
/// <para>
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it is recommended to clone it first.
/// </para>
/// </remarks>
/// <remarks>
/// <para>
/// This method returns the current clip region, not a clone. If there is a need to modify the clip region, it
/// is recommended to clone it first.
/// </para>
/// </remarks>
/// </remarks>
/// <returns>
/// The current Clip, which can be then re-applied <see cref="View.SetClip"/>
@@ -161,7 +160,7 @@ public partial class View
viewportRegion?.Exclude (adornment.Thickness.GetInside (viewport));
}
View.SetClip (viewportRegion);
SetClip (viewportRegion);
return previous;
}

View File

@@ -2,8 +2,6 @@
public partial class View
{
#region Drawing Primitives
/// <summary>Moves the drawing cursor to the specified <see cref="Viewport"/>-relative location in the view.</summary>
/// <remarks>
/// <para>
@@ -121,8 +119,6 @@ public partial class View
Attribute prev = SetAttribute (new (color ?? GetNormalColor ().Background));
Driver.FillRect (toClear);
SetAttribute (prev);
View.SetClip (prevClip);
SetClip (prevClip);
}
#endregion Drawing Primitives
}

View File

@@ -1,13 +1,10 @@
#nullable enable
//#define HACK_DRAW_OVERLAPPED
using System.ComponentModel;
using System.Diagnostics;
namespace Terminal.Gui;
public partial class View // Drawing APIs
{
internal static void Draw (IEnumerable<View> views, bool force)
{
IEnumerable<View> viewsArray = views as View [] ?? views.ToArray ();
@@ -25,7 +22,6 @@ public partial class View // Drawing APIs
Margin.DrawMargins (viewsArray);
}
/// <summary>
/// Draws the view if it needs to be drawn.
/// </summary>
@@ -47,11 +43,12 @@ public partial class View // Drawing APIs
}
Region? saved = GetClip ();
if (NeedsDraw || SubViewNeedsDraw)
{
saved = ClipFrame ();
DoDrawBorderAndPadding ();
View.SetClip (saved);
SetClip (saved);
// By default, we clip to the viewport preventing drawing outside the viewport
// We also clip to the content, but if a developer wants to draw outside the viewport, they can do
@@ -108,7 +105,7 @@ public partial class View // Drawing APIs
DoDrawComplete ();
// QUESTION: Should this go before DoDrawComplete? What is more correct?
View.SetClip (saved);
SetClip (saved);
// Exclude this view (not including Margin) from the Clip
if (this is not Adornment && GetClip () is { })
@@ -221,7 +218,6 @@ public partial class View // Drawing APIs
SetNormalAttribute ();
}
/// <summary>
/// Called when the normal attribute for the View is to be set. This is called before the View is drawn.
/// </summary>
@@ -245,13 +241,12 @@ public partial class View // Drawing APIs
}
}
#endregion
#region ClearViewport
private void DoClearViewport ()
{
if (OnClearingViewport ())
{
return;
@@ -329,7 +324,6 @@ public partial class View // Drawing APIs
private void DoDrawText ()
{
if (OnDrawingText ())
{
return;
@@ -513,14 +507,15 @@ public partial class View // Drawing APIs
/// <summary>
/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any
/// lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by its parent's
/// SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawingBorderAndPadding"/> method will be
/// SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawingBorderAndPadding"/> method will
/// be
/// called to render the borders.
/// </summary>
public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
/// <summary>
/// Causes the contents of <see cref="LineCanvas"/> to be drawn.
/// If <see cref="SuperViewRendersLineCanvas"/> is true, only the
/// Causes the contents of <see cref="LineCanvas"/> to be drawn.
/// If <see cref="SuperViewRendersLineCanvas"/> is true, only the
/// <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
/// false (the default), this method will cause the <see cref="LineCanvas"/> to be rendered.
/// </summary>
@@ -577,6 +572,7 @@ public partial class View // Drawing APIs
LineCanvas.Clear ();
}
}
#endregion DrawLineCanvas
#region DrawComplete
@@ -757,6 +753,7 @@ public partial class View // Drawing APIs
{
Padding?.ClearNeedsDraw ();
}
foreach (View subview in Subviews)
{
subview.ClearNeedsDraw ();

View File

@@ -1,7 +1,5 @@
#nullable enable
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using static Unix.Terminal.Curses;
namespace Terminal.Gui;
@@ -33,10 +31,12 @@ public partial class View // Layout APIs
/// .
/// </para>
/// <para>
/// Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> to absoulte values.
/// Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> to
/// absoulte values.
/// </para>
/// <para>
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set, resulting in the
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set,
/// resulting in the
/// view being laid out and redrawn as appropriate in the next iteration of the <see cref="MainLoop"/>.
/// </para>
/// </remarks>
@@ -44,10 +44,11 @@ public partial class View // Layout APIs
{
get
{
if (_needsLayout)
if (NeedsLayout)
{
//Debug.WriteLine("Frame_get with _layoutNeeded");
}
return _frame ?? Rectangle.Empty;
}
set
@@ -193,7 +194,8 @@ public partial class View // Layout APIs
/// laid out (e.g. <see cref="Layout(System.Drawing.Size)"/> has been called).
/// </para>
/// <para>
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set, resulting in the
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set,
/// resulting in the
/// view being laid out and redrawn as appropriate in the next iteration of the <see cref="MainLoop"/>.
/// </para>
/// <para>
@@ -219,7 +221,6 @@ public partial class View // Layout APIs
private Pos _y = Pos.Absolute (0);
/// <summary>Gets or sets the Y position for the view (the row).</summary>
/// <value>The <see cref="Pos"/> object representing the Y position.</value>
/// <remarks>
@@ -236,7 +237,8 @@ public partial class View // Layout APIs
/// laid out (e.g. <see cref="Layout(System.Drawing.Size)"/> has been called).
/// </para>
/// <para>
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set, resulting in the
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set,
/// resulting in the
/// view being laid out and redrawn as appropriate in the next iteration of the <see cref="MainLoop"/>.
/// </para>
/// <para>
@@ -277,7 +279,8 @@ public partial class View // Layout APIs
/// laid out (e.g. <see cref="Layout(System.Drawing.Size)"/> has been called).
/// </para>
/// <para>
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set, resulting in the
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set,
/// resulting in the
/// view being laid out and redrawn as appropriate in the next iteration of the <see cref="MainLoop"/>.
/// </para>
/// <para>
@@ -323,7 +326,8 @@ public partial class View // Layout APIs
/// laid out (e.g. <see cref="Layout(System.Drawing.Size)"/> has been called).
/// </para>
/// <para>
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set, resulting in the
/// Changing this property will result in <see cref="NeedsLayout"/> and <see cref="NeedsDraw"/> to be set,
/// resulting in the
/// view being laid out and redrawn as appropriate in the next iteration of the <see cref="MainLoop"/>.
/// </para>
/// <para>
@@ -354,14 +358,15 @@ public partial class View // Layout APIs
#region Core Layout API
/// <summary>
/// INTERNAL API - Performs layout of the specified views within the specified content size. Called by the Application main loop.
/// INTERNAL API - Performs layout of the specified views within the specified content size. Called by the Application
/// main loop.
/// </summary>
/// <param name="views">The views to layout.</param>
/// <param name="contentSize">The size to bound the views by.</param>
/// <returns><see langword="true"/>If any of the views needed to be laid out.</returns>
internal static bool Layout (IEnumerable<View> views, Size contentSize)
{
bool neededLayout = false;
var neededLayout = false;
foreach (View v in views)
{
@@ -404,7 +409,8 @@ public partial class View // Layout APIs
}
/// <summary>
/// Performs layout of the view and its subviews using the content size of either the <see cref="SuperView"/> or <see cref="Application.Screen"/>.
/// Performs layout of the view and its subviews using the content size of either the <see cref="SuperView"/> or
/// <see cref="Application.Screen"/>.
/// </summary>
/// <remarks>
/// <para>
@@ -417,14 +423,12 @@ public partial class View // Layout APIs
/// </para>
/// </remarks>
/// <returns><see langword="false"/>If the view could not be laid out (typically because dependency was not ready). </returns>
public bool Layout ()
{
return Layout (GetContainerSize ());
}
public bool Layout () { return Layout (GetContainerSize ()); }
/// <summary>
/// Sets the position and size of this view, relative to the SuperView's ContentSize (nominally the same as
/// <c>this.SuperView.GetContentSize ()</c>) based on the values of <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>,
/// <c>this.SuperView.GetContentSize ()</c>) based on the values of <see cref="X"/>, <see cref="Y"/>,
/// <see cref="Width"/>,
/// and <see cref="Height"/>.
/// </summary>
/// <remarks>
@@ -467,6 +471,7 @@ public partial class View // Layout APIs
{
newW = _width.Calculate (0, superviewContentSize.Width, this, Dimension.Width);
newX = _x.Calculate (superviewContentSize.Width, newW, this, Dimension.Width);
if (newW != Frame.Width)
{
// Pos.Calculate gave us a new position. We need to redo dimension
@@ -483,6 +488,7 @@ public partial class View // Layout APIs
{
newH = _height.Calculate (0, superviewContentSize.Height, this, Dimension.Height);
newY = _y.Calculate (superviewContentSize.Height, newH, this, Dimension.Height);
if (newH != Frame.Height)
{
// Pos.Calculate gave us a new position. We need to redo dimension
@@ -494,7 +500,6 @@ public partial class View // Layout APIs
newY = _y.Calculate (superviewContentSize.Height, _height, this, Dimension.Height);
newH = _height.Calculate (newY, superviewContentSize.Height, this, Dimension.Height);
}
}
catch (LayoutException le)
{
@@ -552,7 +557,8 @@ public partial class View // Layout APIs
}
/// <summary>
/// INTERNAL API - Causes the view's subviews and adornments to be laid out within the view's content areas. Assumes the view's relative layout has been set via <see cref="SetRelativeLayout"/>.
/// INTERNAL API - Causes the view's subviews and adornments to be laid out within the view's content areas. Assumes
/// the view's relative layout has been set via <see cref="SetRelativeLayout"/>.
/// </summary>
/// <remarks>
/// <para>
@@ -584,10 +590,12 @@ public partial class View // Layout APIs
{
Margin.LayoutSubviews ();
}
if (Border is { Subviews.Count: > 0 })
{
Border.LayoutSubviews ();
}
if (Padding is { Subviews.Count: > 0 })
{
Padding.LayoutSubviews ();
@@ -600,6 +608,7 @@ public partial class View // Layout APIs
List<View> ordered = TopologicalSort (SuperView!, nodes, edges);
List<View> redo = new ();
foreach (View v in ordered)
{
if (!v.Layout (contentSize))
@@ -608,7 +617,7 @@ public partial class View // Layout APIs
}
}
bool layoutStillNeeded = false;
var layoutStillNeeded = false;
if (redo.Count > 0)
{
@@ -633,7 +642,7 @@ public partial class View // Layout APIs
}
}
_needsLayout = layoutStillNeeded;
NeedsLayout = layoutStillNeeded;
OnSubviewsLaidOut (new (contentSize));
SubviewsLaidOut?.Invoke (this, new (contentSize));
@@ -648,8 +657,10 @@ public partial class View // Layout APIs
/// </remarks>
protected virtual void OnSubviewLayout (LayoutEventArgs args) { }
/// <summary>Raised by <see cref="LayoutSubviews"/> before any subviews
/// have been laid out.</summary>
/// <summary>
/// Raised by <see cref="LayoutSubviews"/> before any subviews
/// have been laid out.
/// </summary>
/// <remarks>
/// Subscribe to this event to perform tasks when the layout is changing.
/// </remarks>
@@ -677,65 +688,72 @@ public partial class View // Layout APIs
#region NeedsLayout
// We expose no setter for this to ensure that the ONLY place it's changed is in SetNeedsLayout
private bool _needsLayout = true;
/// <summary>
/// Indicates the View's Frame or the layout of the View's subviews (including Adornments) have
/// changed since the last time the View was laid out.
/// changed since the last time the View was laid out.
/// </summary>
/// <remarks>
/// <para>Used to prevent <see cref="Layout()"/> from needlessly computing
/// layout.
/// </para>
/// <para>
/// Used to prevent <see cref="Layout()"/> from needlessly computing
/// layout.
/// </para>
/// </remarks>
/// <value>
/// <see langword="true"/> if layout is needed.
/// </value>
public bool NeedsLayout => _needsLayout;
public bool NeedsLayout { get; private set; } = true;
/// <summary>
/// Sets <see cref="NeedsLayout"/> to return <see langword="true"/>, indicating this View and all of it's subviews (including adornments) need to be laid out in the next Application iteration.
/// Sets <see cref="NeedsLayout"/> to return <see langword="true"/>, indicating this View and all of it's subviews
/// (including adornments) need to be laid out in the next Application iteration.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="MainLoop"/> will cause <see cref="Layout()"/> to be called on the next <see cref="Application.Iteration"/> so there is normally no reason to call see <see cref="Layout()"/>.
/// The <see cref="MainLoop"/> will cause <see cref="Layout()"/> to be called on the next
/// <see cref="Application.Iteration"/> so there is normally no reason to call see <see cref="Layout()"/>.
/// </para>
/// </remarks>
public void SetNeedsLayout ()
{
_needsLayout = true;
NeedsLayout = true;
if (Margin is { Subviews.Count: > 0 })
{
Margin.SetNeedsLayout ();
}
if (Border is { Subviews.Count: > 0 })
{
Border.SetNeedsLayout ();
}
if (Padding is { Subviews.Count: > 0 })
{
Padding.SetNeedsLayout ();
}
// Use a stack to avoid recursion
Stack<View> stack = new Stack<View> (Subviews);
Stack<View> stack = new (Subviews);
while (stack.Count > 0)
{
View current = stack.Pop ();
if (!current.NeedsLayout)
{
current._needsLayout = true;
current.NeedsLayout = true;
if (current.Margin is { Subviews.Count: > 0 })
{
current.Margin.SetNeedsLayout ();
}
if (current.Border is { Subviews.Count: > 0 })
{
current.Border.SetNeedsLayout ();
}
if (current.Padding is { Subviews.Count: > 0 })
{
current.Padding.SetNeedsLayout ();
@@ -757,7 +775,7 @@ public partial class View // Layout APIs
if (SuperView is null)
{
foreach (var tl in Application.TopLevels)
foreach (Toplevel tl in Application.TopLevels)
{
tl.SetNeedsDraw ();
}
@@ -778,7 +796,6 @@ public partial class View // Layout APIs
#region Topological Sort
/// <summary>
/// INTERNAL API - Collects all views and their dependencies from a given starting view for layout purposes. Used by
/// <see cref="TopologicalSort"/> to create an ordered list of views to layout.
@@ -802,7 +819,7 @@ public partial class View // Layout APIs
}
/// <summary>
/// INTERNAL API - Collects dimension (where Width or Height is `DimView`) dependencies for a given view.
/// INTERNAL API - Collects dimension (where Width or Height is `DimView`) dependencies for a given view.
/// </summary>
/// <param name="dim">The dimension (width or height) to collect dependencies for.</param>
/// <param name="from">The view for which to collect dimension dependencies.</param>
@@ -813,7 +830,7 @@ public partial class View // Layout APIs
/// </param>
internal void CollectDim (Dim? dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
{
if (dim!.Has<DimView> (out DimView dv))
if (dim!.Has (out DimView dv))
{
if (dv.Target != this)
{
@@ -821,7 +838,7 @@ public partial class View // Layout APIs
}
}
if (dim!.Has<DimCombine> (out DimCombine dc))
if (dim!.Has (out DimCombine dc))
{
CollectDim (dc.Left, from, ref nNodes, ref nEdges);
CollectDim (dc.Right, from, ref nNodes, ref nEdges);
@@ -845,6 +862,7 @@ public partial class View // Layout APIs
{
case PosView pv:
Debug.Assert (pv.Target is { });
if (pv.Target != this)
{
nEdges.Add ((pv.Target!, from));
@@ -960,22 +978,21 @@ public partial class View // Layout APIs
#region Utilities
/// <summary>
/// INTERNAL API - Gets the size of the SuperView's content (nominally the same as
/// the SuperView's <see cref="GetContentSize ()"/>) or the screen size if there's no SuperView.
/// INTERNAL API - Gets the size of the SuperView's content (nominally the same as
/// the SuperView's <see cref="GetContentSize ()"/>) or the screen size if there's no SuperView.
/// </summary>
/// <returns></returns>
private Size GetContainerSize ()
{
// TODO: Get rid of refs to Top
Size superViewContentSize = SuperView?.GetContentSize () ??
(Application.Top is { } && Application.Top != this && Application.Top.IsInitialized
? Application.Top.GetContentSize ()
: Application.Screen.Size);
Size superViewContentSize = SuperView?.GetContentSize ()
?? (Application.Top is { } && Application.Top != this && Application.Top.IsInitialized
? Application.Top.GetContentSize ()
: Application.Screen.Size);
return superViewContentSize;
}
// BUGBUG: This method interferes with Dialog/MessageBox default min/max size.
// TODO: Get rid of MenuBar coupling as part of https://github.com/gui-cs/Terminal.Gui/issues/2975
/// <summary>
@@ -1107,11 +1124,8 @@ public partial class View // Layout APIs
#endregion Utilities
#region Diagnostics and Verification
// Diagnostics to highlight when X or Y is read before the view has been initialized
private Pos VerifyIsInitialized (Pos pos, string member)
{
@@ -1228,12 +1242,12 @@ public partial class View // Layout APIs
if (bad != null)
{
throw new LayoutException (
$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} "
+ $"which depends on the SuperView's dimensions and the SuperView uses Dim.Auto."
);
$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} "
+ $"which depends on the SuperView's dimensions and the SuperView uses Dim.Auto."
);
}
}
}
#endregion Diagnostics and Verification
}
#endregion Diagnostics and Verification
}

View File

@@ -666,15 +666,15 @@ public partial class View // Mouse APIs
if (start is not Adornment)
{
if (start.Margin.Contains (currentLocation))
if (start.Margin is {} && start.Margin.Contains (currentLocation))
{
found = start.Margin;
}
else if (start.Border.Contains (currentLocation))
else if (start.Border is {} && start.Border.Contains (currentLocation))
{
found = start.Border;
}
else if (start.Padding.Contains (currentLocation))
else if (start.Padding is { } && start.Padding.Contains(currentLocation))
{
found = start.Padding;
}

View File

@@ -87,8 +87,11 @@ public class Bar : View, IOrientation, IDesignable
/// <inheritdoc/>
public override void SetBorderStyle (LineStyle value)
{
// The default changes the thickness. We don't want that. We just set the style.
Border.LineStyle = value;
if (Border is { })
{
// The default changes the thickness. We don't want that. We just set the style.
Border.LineStyle = value;
}
}
#region IOrientation members

View File

@@ -46,6 +46,7 @@ public class Menuv2 : Bar
// Menuv2 arranges the items horizontally.
// The first item has no left border, the last item has no right border.
// The Shortcuts are configured with the command, help, and key views aligned in reverse order (EndToStart).
/// <inheritdoc />
protected override void OnSubviewLayout (LayoutEventArgs args)
{
for (int index = 0; index < Subviews.Count; index++)

View File

@@ -98,7 +98,11 @@ public class Shortcut : View, IOrientation, IDesignable
CanFocus = true;
SuperViewRendersLineCanvas = true;
Border.Settings &= ~BorderSettings.Title;
if (Border is { })
{
Border.Settings &= ~BorderSettings.Title;
}
Width = GetWidthDimAuto ();
Height = Dim.Auto (DimAutoStyle.Content, 1);
@@ -237,39 +241,39 @@ public class Shortcut : View, IOrientation, IDesignable
ShowHide ();
ForceCalculateNaturalWidth ();
if (Width is DimAuto widthAuto)
if (Width is DimAuto widthAuto || HelpView!.Margin is null)
{
return;
}
}
// Frame.Width is smaller than the natural width. Reduce width of HelpView.
_maxHelpWidth = int.Max (0, GetContentSize ().Width - CommandView.Frame.Width - KeyView.Frame.Width);
if (_maxHelpWidth < 3)
{
Thickness t = GetMarginThickness ();
switch (_maxHelpWidth)
{
case 0:
case 1:
// Scrunch it by removing both margins
HelpView.Margin.Thickness = new (t.Right - 1, t.Top, t.Left - 1, t.Bottom);
break;
case 2:
// Scrunch just the right margin
HelpView.Margin.Thickness = new (t.Right, t.Top, t.Left - 1, t.Bottom);
break;
}
}
else
{
// Frame.Width is smaller than the natural width. Reduce width of HelpView.
_maxHelpWidth = int.Max (0, GetContentSize ().Width - CommandView.Frame.Width - KeyView.Frame.Width);
if (_maxHelpWidth < 3)
{
Thickness t = GetMarginThickness ();
switch (_maxHelpWidth)
{
case 0:
case 1:
// Scrunch it by removing both margins
HelpView.Margin.Thickness = new (t.Right - 1, t.Top, t.Left - 1, t.Bottom);
break;
case 2:
// Scrunch just the right margin
HelpView.Margin.Thickness = new (t.Right, t.Top, t.Left - 1, t.Bottom);
break;
}
}
else
{
// Reset to default
HelpView.Margin.Thickness = GetMarginThickness ();
}
// Reset to default
HelpView.Margin.Thickness = GetMarginThickness ();
}
}
@@ -522,7 +526,11 @@ public class Shortcut : View, IOrientation, IDesignable
private void SetHelpViewDefaultLayout ()
{
HelpView.Margin.Thickness = GetMarginThickness ();
if (HelpView.Margin is { })
{
HelpView.Margin.Thickness = GetMarginThickness ();
}
HelpView.X = Pos.Align (Alignment.End, AlignmentModes);
_maxHelpWidth = HelpView.Text.GetColumns ();
HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: Dim.Func ((() => _maxHelpWidth)));
@@ -654,7 +662,11 @@ public class Shortcut : View, IOrientation, IDesignable
private void SetKeyViewDefaultLayout ()
{
KeyView.Margin.Thickness = GetMarginThickness ();
if (KeyView.Margin is { })
{
KeyView.Margin.Thickness = GetMarginThickness ();
}
KeyView.X = Pos.Align (Alignment.End, AlignmentModes);
KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func (() => MinimumKeyTextSize));
KeyView.Height = Dim.Fill ();
@@ -763,7 +775,10 @@ public class Shortcut : View, IOrientation, IDesignable
KeyView.ColorScheme = cs;
}
CommandView.Margin.ColorScheme = base.ColorScheme;
if (CommandView.Margin is { })
{
CommandView.Margin.ColorScheme = base.ColorScheme;
}
}
/// <inheritdoc/>

View File

@@ -216,7 +216,7 @@ public class TabView : View
/// <summary>
/// Updates the control to use the latest state settings in <see cref="Style"/>. This can change the size of the
/// client area of the tab (for rendering the selected tab's content). This method includes a call to
/// <see cref="View.SetNeedsDraw"/>.
/// <see cref="View.SetNeedsDraw()"/>.
/// </summary>
public void ApplyStyleChanges ()
{
@@ -288,7 +288,7 @@ public class TabView : View
/// <summary>Updates <see cref="TabScrollOffset"/> to be a valid index of <see cref="Tabs"/>.</summary>
/// <param name="value">The value to validate.</param>
/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw"/>.</remarks>
/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>.</remarks>
/// <returns>The valid <see cref="TabScrollOffset"/> for the given value.</returns>
public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }

View File

@@ -583,7 +583,7 @@ public class TableView : View, IDesignable
/// not been set.
/// </summary>
/// <remarks>
/// Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw"/>
/// Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>
/// </remarks>
public void EnsureSelectedCellIsVisible ()
{
@@ -644,7 +644,7 @@ public class TableView : View, IDesignable
/// (by adjusting them to the nearest existing cell). Has no effect if <see cref="Table"/> has not been set.
/// </summary>
/// <remarks>
/// Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw"/>
/// Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>
/// </remarks>
public void EnsureValidScrollOffsets ()
{
@@ -663,7 +663,7 @@ public class TableView : View, IDesignable
/// <see cref="Table"/> has not been set.
/// </summary>
/// <remarks>
/// Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw"/>
/// Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>
/// </remarks>
public void EnsureValidSelection ()
{
@@ -1227,7 +1227,7 @@ public class TableView : View, IDesignable
/// Updates the view to reflect changes to <see cref="Table"/> and to (<see cref="ColumnOffset"/> /
/// <see cref="RowOffset"/>) etc
/// </summary>
/// <remarks>This always calls <see cref="View.SetNeedsDraw"/></remarks>
/// <remarks>This always calls <see cref="View.SetNeedsDraw()"/></remarks>
public void Update ()
{
if (!IsInitialized || TableIsNullOrInvisible ())