Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 34 additions & 73 deletions Terminal.Gui/Drawing/Thickness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Terminal.Gui.Drawing;
public record struct Thickness
{
/// <summary>Initializes a new instance of the <see cref="Thickness"/> class with all widths set to 0.</summary>
public Thickness () { _sides = Vector4.Zero; }
public Thickness () => _sides = Vector4.Zero;

/// <summary>Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.</summary>
/// <param name="width"></param>
Expand Down Expand Up @@ -52,15 +52,11 @@ public Thickness (int left, int top, int right, int bottom)
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public readonly Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }
public readonly Thickness Add (Thickness other) => new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom);

/// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
[JsonInclude]
public int Bottom
{
readonly get => (int)_sides.W;
set => _sides.W = value;
}
public int Bottom { readonly get => (int)_sides.W; set => _sides.W = value; }

/// <summary>
/// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside
Expand Down Expand Up @@ -120,39 +116,25 @@ public Rectangle Draw (IDriver? driver, Rectangle rect, ViewDiagnosticFlags diag
// Draw the Top side
if (Top > 0)
{
driver?.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar);
driver.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar);
}

// Draw the Left side
if (Left > 0)
{
driver?.FillRect (rect with { Width = Math.Min (rect.Width, Left) }, leftChar);
driver.FillRect (rect with { Width = Math.Min (rect.Width, Left) }, leftChar);
}

// Draw the Right side
if (Right > 0)
{
driver?.FillRect (
rect with
{
X = Math.Max (0, rect.X + rect.Width - Right),
Width = Math.Min (rect.Width, Right)
},
rightChar
);
driver.FillRect (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)
{
driver?.FillRect (
rect with
{
Y = rect.Y + Math.Max (0, rect.Height - Bottom),
Height = Bottom
},
bottomChar
);
driver.FillRect (rect with { Y = rect.Y + Math.Max (0, rect.Height - Bottom), Height = Bottom }, bottomChar);
}

if (diagnosticFlags.HasFlag (ViewDiagnosticFlags.Ruler))
Expand All @@ -163,7 +145,7 @@ rect with

if (Top > 0)
{
hRuler.Draw (driver: driver, location: rect.Location);
hRuler.Draw (driver, rect.Location);
}

//Left
Expand All @@ -177,36 +159,35 @@ rect with
// Bottom
if (Bottom > 0)
{
hRuler.Draw (driver: driver, location: rect.Location with { Y = rect.Y + rect.Height - 1 });
hRuler.Draw (driver, rect.Location with { Y = rect.Y + rect.Height - 1 });
}

// Right
if (Right > 0)
{
vRuler.Draw (driver, new (rect.X + rect.Width - 1, rect.Y + 1), 1);
vRuler.Draw (driver, new Point (rect.X + rect.Width - 1, rect.Y + 1), 1);
}
}

if (diagnosticFlags.HasFlag (ViewDiagnosticFlags.Thickness))
if (!diagnosticFlags.HasFlag (ViewDiagnosticFlags.Thickness))
{
// Draw the diagnostics label on the bottom
string text = label is null ? string.Empty : $"{label} {this}";

TextFormatter tf = new ()
{
Text = text,
Alignment = Alignment.Center,
VerticalAlignment = Alignment.End,
ConstrainToWidth = text.GetColumns (),
ConstrainToHeight = 1
};

if (driver?.CurrentAttribute is { })
{
tf.Draw (driver, rect, driver!.CurrentAttribute, driver!.CurrentAttribute, rect);
}
return GetInside (rect);
}

// Draw the diagnostics label on the bottom
string text = label is null ? string.Empty : $"{label} {this}";

TextFormatter tf = new ()
{
Text = text,
Alignment = Alignment.Center,
VerticalAlignment = Alignment.End,
ConstrainToWidth = text.GetColumns (),
ConstrainToHeight = 1
};

tf.Draw (driver, rect, driver.CurrentAttribute, driver.CurrentAttribute, rect);

return GetInside (rect);
}

Expand All @@ -233,7 +214,7 @@ public Rectangle GetInside (Rectangle rect)
int width = Math.Max (0, rect.Size.Width - Horizontal);
int height = Math.Max (0, rect.Size.Height - Vertical);

return new (x, y, width, height);
return new Rectangle (x, y, width, height);
}

/// <summary>
Expand All @@ -253,55 +234,35 @@ public Region AsRegion (Rectangle rect)
/// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and right sides
/// of the rectangle to half the specified value.
/// </summary>
public int Horizontal
{
get => Left + Right;
set => Left = Right = value / 2;
}
public int Horizontal { get => Left + Right; set => Left = Right = value / 2; }

/// <summary>Gets or sets the width of the left side of the rectangle.</summary>
[JsonInclude]
public int Left
{
readonly get => (int)_sides.X;
set => _sides.X = value;
}
public int Left { readonly get => (int)_sides.X; set => _sides.X = value; }

/// <summary>
/// Adds the thickness widths of another <see cref="Thickness"/> to another <see cref="Thickness"/>.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); }
public static Thickness operator + (Thickness a, Thickness b) => a.Add (b);

/// <summary>Gets or sets the width of the right side of the rectangle.</summary>
[JsonInclude]
public int Right
{
readonly get => (int)_sides.Z;
set => _sides.Z = value;
}
public int Right { readonly get => (int)_sides.Z; set => _sides.Z = value; }

/// <summary>Gets or sets the width of the upper side of the rectangle.</summary>
[JsonInclude]
public int Top
{
readonly get => (int)_sides.Y;
set => _sides.Y = value;
}
public int Top { readonly get => (int)_sides.Y; set => _sides.Y = value; }

/// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
/// <returns>The thickness widths as a string.</returns>
public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; }
public override string ToString () => $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})";

/// <summary>
/// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom
/// sides of the rectangle to half the specified value.
/// </summary>
public int Vertical
{
get => Top + Bottom;
set => Top = Bottom = value / 2;
}
public int Vertical { get => Top + Bottom; set => Top = Bottom = value / 2; }
}
4 changes: 4 additions & 0 deletions Terminal.Gui/ViewBase/View.Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,10 @@ internal void LayoutSubViews ()
Size contentSize = GetContentSize ();

OnSubViewLayout (new LayoutEventArgs (contentSize));

// Re-read content size — OnSubViewLayout handlers (e.g. Dialog.UpdateSizes) may have called SetContentSize.
contentSize = GetContentSize ();

SubViewLayout?.Invoke (this, new LayoutEventArgs (contentSize));
Comment on lines 723 to 728
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes SubViewLayout (and the later SubViewsLaidOut) receive a LayoutEventArgs whose OldContentSize may already reflect a new value set by OnSubViewLayout. Since LayoutEventArgs.OldContentSize is documented as the content size before layout, consider capturing the original size into a separate oldContentSize variable for the event args, while using a refreshed size variable purely for the layout computations (or update the event args/documentation to match the new semantics).

Copilot uses AI. Check for mistakes.

Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LayoutSubViews refreshes contentSize after OnSubViewLayout, but SubViewLayout subscribers can also call SetContentSize (e.g. LinearRange<T> wires SubViewLayout += (_, _) => SetContentSize ()). If that happens, contentSize will still be stale for the actual v.Layout(contentSize) pass. Consider re-reading contentSize again after invoking SubViewLayout (or otherwise ensuring the value used for laying out subviews reflects any SetContentSize calls from either callback).

Suggested change
// Re-read content size — SubViewLayout handlers (e.g. LinearRange<T>) may have called SetContentSize.
contentSize = GetContentSize ();

Copilot uses AI. Check for mistakes.
// The Adornments already have their Frame's set by SetRelativeLayout so we call LayoutSubViews vs. Layout here.
Expand Down
19 changes: 6 additions & 13 deletions Terminal.Gui/Views/DialogTResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,19 +197,12 @@ private void UpdateSizes ()
return;
}

int subViewsWidth = _minimumSubViewsSize.Width;

if (!Width.Has<DimAuto> (out _))
{
subViewsWidth = Math.Max (subViewsWidth, Viewport.Width);
}

int subViewsHeight = _minimumSubViewsSize.Height;

if (!Height.Has<DimAuto> (out _))
{
subViewsHeight = Math.Max (subViewsHeight, Viewport.Height);
}
// Always floor at Viewport size — the content area should never be smaller
// than what's visible. For DimAuto dialogs, the Frame may be larger than
// _minimumSubViewsSize (e.g. due to title width), so the content area
// should reflect the actual available space.
int subViewsWidth = Math.Max (_minimumSubViewsSize.Width, Viewport.Width);
int subViewsHeight = Math.Max (_minimumSubViewsSize.Height, Viewport.Height);

SetContentSize (new Size (Math.Max (_minimumButtonsSize.Width, subViewsWidth), Math.Max (_minimumButtonsSize.Height, subViewsHeight)));
}
Expand Down
Loading
Loading