Skip to content
Closed
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));

// The Adornments already have their Frame's set by SetRelativeLayout so we call LayoutSubViews vs. Layout here.
Expand Down
54 changes: 44 additions & 10 deletions Terminal.Gui/ViewBase/View.ScrollBars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ namespace Terminal.Gui.ViewBase;

public partial class View
{
// Track whether scrollbar padding has been applied to prevent accumulation
// during layout re-entry. The VisibleChanged handlers use +1/-1 relative
// adjustments, which compound when the event fires multiple times per cycle.
private bool _verticalScrollBarPaddingApplied;
private bool _horizontalScrollBarPaddingApplied;

private Lazy<ScrollBar> _horizontalScrollBar = null!;

/// <summary>
Expand Down Expand Up @@ -130,18 +136,30 @@ private void OnScrollBarInitialized (object? sender, EventArgs e)

if (scrollBar.Orientation == Orientation.Vertical)
{
Padding.Thickness = Padding.Thickness with { Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : Padding.Thickness.Right };
if (scrollBar.Visible && !_verticalScrollBarPaddingApplied)
{
Padding.Thickness = Padding.Thickness with { Right = Padding.Thickness.Right + 1 };
_verticalScrollBarPaddingApplied = true;
}
}
else
{
Padding.Thickness = Padding.Thickness with { Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : Padding.Thickness.Bottom };
if (scrollBar.Visible && !_horizontalScrollBarPaddingApplied)
{
Padding.Thickness = Padding.Thickness with { Bottom = Padding.Thickness.Bottom + 1 };
_horizontalScrollBarPaddingApplied = true;
}
}
scrollBar.Layout ();
}

private void ConfigureVerticalScrollBarEvents (ScrollBar scrollBar)
{
Padding.Thickness = Padding.Thickness with { Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : Padding.Thickness.Right };
if (scrollBar.Visible && !_verticalScrollBarPaddingApplied)
{
Padding.Thickness = Padding.Thickness with { Right = Padding.Thickness.Right + 1 };
_verticalScrollBarPaddingApplied = true;
}

scrollBar.ValueChanged += (_, args) =>
{
Expand All @@ -156,16 +174,26 @@ private void ConfigureVerticalScrollBarEvents (ScrollBar scrollBar)
Viewport = Viewport with { Y = 0 };
}

Padding.Thickness = Padding.Thickness with
if (scrollBar.Visible && !_verticalScrollBarPaddingApplied)
{
Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : Padding.Thickness.Right - 1
};
Padding.Thickness = Padding.Thickness with { Right = Padding.Thickness.Right + 1 };
_verticalScrollBarPaddingApplied = true;
}
else if (!scrollBar.Visible && _verticalScrollBarPaddingApplied)
{
Padding.Thickness = Padding.Thickness with { Right = Padding.Thickness.Right - 1 };
_verticalScrollBarPaddingApplied = false;
}
};
}

private void ConfigureHorizontalScrollBarEvents (ScrollBar scrollBar)
{
Padding.Thickness = Padding.Thickness with { Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : Padding.Thickness.Bottom };
if (scrollBar.Visible && !_horizontalScrollBarPaddingApplied)
{
Padding.Thickness = Padding.Thickness with { Bottom = Padding.Thickness.Bottom + 1 };
_horizontalScrollBarPaddingApplied = true;
}

scrollBar.ValueChanged += (_, args) =>
{
Expand All @@ -180,10 +208,16 @@ private void ConfigureHorizontalScrollBarEvents (ScrollBar scrollBar)
Viewport = Viewport with { X = 0 };
}

Padding.Thickness = Padding.Thickness with
if (scrollBar.Visible && !_horizontalScrollBarPaddingApplied)
{
Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : Padding.Thickness.Bottom - 1
};
Padding.Thickness = Padding.Thickness with { Bottom = Padding.Thickness.Bottom + 1 };
_horizontalScrollBarPaddingApplied = true;
}
else if (!scrollBar.Visible && _horizontalScrollBarPaddingApplied)
{
Padding.Thickness = Padding.Thickness with { Bottom = Padding.Thickness.Bottom - 1 };
_horizontalScrollBarPaddingApplied = false;
}
};
}

Expand Down
20 changes: 10 additions & 10 deletions Terminal.Gui/Views/DialogTResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,37 +178,37 @@ protected override bool OnAccepting (CommandEventArgs args)

}

/// <inheritdoc/>
/// <inheritdoc/>
protected override void OnViewportChanged (DrawEventArgs e)
{
//if (!IsInitialized)
{
SetContentSize (new Size (Math.Max (_minimumButtonsSize.Width, Viewport.Width), Math.Max (_minimumButtonsSize.Height, Viewport.Height)));
}
SetContentSize (new Size (Math.Max (_minimumButtonsSize.Width, Viewport.Width), Math.Max (_minimumButtonsSize.Height, Viewport.Height)));
base.OnViewportChanged (e);
}

private void UpdateSizes ()
{
if (SubViews.Count == 0)
{
// This is primarily to support MessageBox where there are no subviews but
// Text is used.
return;
}

// For DimAuto dialogs, content must be at least as large as the subviews require
// so the dialog grows to fit. For fixed-size dialogs, content should match the
// Viewport directly; using _minimumSubViewsSize as a floor causes height drift
// on maximize/restore because the minimum captures the high-water mark and never
// shrinks back down.
int subViewsWidth = _minimumSubViewsSize.Width;
int subViewsHeight = _minimumSubViewsSize.Height;

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

int subViewsHeight = _minimumSubViewsSize.Height;

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

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