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
25 changes: 16 additions & 9 deletions Terminal.Gui/ViewBase/View.Drawing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,18 @@ public void Draw (DrawContext? context = null)
DoDrawContent (context);

// ------------------------------------
// Draw the line canvas
// Restore the clip before rendering the line canvas and adornment subviews
// because they may draw outside the viewport.
// Draw adornment SubViews BEFORE rendering LineCanvas so their lines
// (merged via LineCanvas.Merge) participate in auto-join.
// Restore the clip because adornment subviews may draw outside the viewport.
SetClip (originalClip);
originalClip = AddFrameToClip ();
Trace.Draw (this.ToIdentifyingString (), "LineCanvas");
DoRenderLineCanvas (context);
Trace.Draw (this.ToIdentifyingString (), "AdornmentSubViews");
DoDrawAdornmentsSubViews (context);

// ------------------------------------
// Re-draw the Border and Padding Adornment SubViews
// HACK: This is a hack to ensure that the Border and Padding Adornment SubViews are drawn after the line canvas.
DoDrawAdornmentsSubViews (context);
// Draw the line canvas (includes merged lines from adornment SubViews)
Trace.Draw (this.ToIdentifyingString (), "LineCanvas");
DoRenderLineCanvas (context);

// ------------------------------------
// Advance the diagnostics draw indicator
Expand Down Expand Up @@ -208,7 +208,13 @@ private void DoDrawAdornmentsSubViews (DrawContext? context)
subview.SetNeedsDraw ();
}

LineCanvas.Exclude (new Region (subview.FrameToScreen ()));
// Only Exclude SubViews that don't merge their LC into the parent.
// SuperViewRendersLineCanvas SubViews contribute LC lines via Merge, and
// excluding them would prevent those merged lines from rendering.
if (!subview.SuperViewRendersLineCanvas)
{
LineCanvas.Exclude (new Region (subview.FrameToScreen ()));
}
}

Region? saved = borderView.AddFrameToClip ();
Expand Down Expand Up @@ -750,6 +756,7 @@ public void DrawSubViews (DrawContext? context = null)
{
continue;
}

LineCanvas.Merge (view.LineCanvas);
view.LineCanvas.Clear ();
}
Expand Down
7 changes: 5 additions & 2 deletions Terminal.Gui/ViewBase/View.NeedsDraw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,11 @@ internal void ClearNeedsDraw ()

SubViewNeedsDraw = false;

// This ensures LineCanvas' get redrawn
if (!SuperViewRendersLineCanvas)
// This ensures LineCanvas' get redrawn.
// AdornmentViews skip this because their LC may hold merged SubView lines
// that haven't been consumed by the parent's DoDrawAdornmentsSubViews yet.
// Those lines are cleared in DoDrawAdornmentsSubViews after merging into the parent's LC.
if (!SuperViewRendersLineCanvas && this is not AdornmentView)
{
LineCanvas.Clear ();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copilot
using Terminal.Gui.Tracing;
using UnitTests;

namespace ViewBaseTests.Draw;

/// <summary>
/// Tests that SubViews of adornments with <see cref="View.SuperViewRendersLineCanvas"/> = true
/// get their border lines auto-joined with the parent View's border lines.
///
/// BUG (#4854): <see cref="View.DoDrawAdornmentsSubViews"/> runs AFTER
/// <see cref="View.DoRenderLineCanvas"/> in the draw pipeline, so merged lines arrive too late.
/// </summary>
public class AdornmentSubViewLineCanvasTests (ITestOutputHelper output) : TestDriverBase
{
/// <summary>
/// Simplest repro: A 7×3 View with only a top border line. A SubView in the Border
/// adds a double-line segment via SuperViewRendersLineCanvas. The ═ should appear
/// but doesn't because the merge happens after the LineCanvas was already rendered.
/// </summary>
[Fact]
public void BorderSubView_Lines_Not_Rendered ()
{
using IDisposable tracing = TestLogging.Verbose (output, TraceCategory.Draw);

IDriver driver = CreateTestDriver (7, 3);
driver.Clip = new Region (driver.Screen);

View parent = new ()
{
Id = "parent",
Driver = driver,
Width = 7,
Height = 3,
BorderStyle = LineStyle.Single
};
parent.Border.Thickness = new Thickness (0, 1, 0, 0);

View sub = new ()
{
Id = "sub",
X = 2,
Y = 0,
Width = 3,
Height = 1,
SuperViewRendersLineCanvas = true
};
parent.Border.GetOrCreateView ().Add (sub);

parent.BeginInit ();
parent.EndInit ();
parent.Layout ();

Rectangle subScreen = sub.FrameToScreen ();
output.WriteLine ($"subScreen: {subScreen}");

sub.LineCanvas.AddLine (
new Point (subScreen.X, subScreen.Y),
3,
Orientation.Horizontal,
LineStyle.Double);

output.WriteLine ($"sub.LC.Bounds before Draw: {sub.LineCanvas.Bounds}");

parent.Draw ();

output.WriteLine ($"Driver output:");
output.WriteLine (driver.ToString ());

// Expected: ──═══── (double-line from SubView merged with parent's single-line)
DriverAssert.AssertDriverContentsAre (
"""
──═══──
""",
output,
driver);
}
}
Loading