diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs
index d7b4bf3c35..8283b7c6d3 100644
--- a/Terminal.Gui/ViewBase/View.Drawing.cs
+++ b/Terminal.Gui/ViewBase/View.Drawing.cs
@@ -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
@@ -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 ();
@@ -750,6 +756,7 @@ public void DrawSubViews (DrawContext? context = null)
{
continue;
}
+
LineCanvas.Merge (view.LineCanvas);
view.LineCanvas.Clear ();
}
diff --git a/Terminal.Gui/ViewBase/View.NeedsDraw.cs b/Terminal.Gui/ViewBase/View.NeedsDraw.cs
index b3e44435a1..fc2e123b5d 100644
--- a/Terminal.Gui/ViewBase/View.NeedsDraw.cs
+++ b/Terminal.Gui/ViewBase/View.NeedsDraw.cs
@@ -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 ();
}
diff --git a/Tests/UnitTestsParallelizable/ViewBase/Draw/AdornmentSubViewLineCanvasTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Draw/AdornmentSubViewLineCanvasTests.cs
new file mode 100644
index 0000000000..a11943ef10
--- /dev/null
+++ b/Tests/UnitTestsParallelizable/ViewBase/Draw/AdornmentSubViewLineCanvasTests.cs
@@ -0,0 +1,78 @@
+// Copilot
+using Terminal.Gui.Tracing;
+using UnitTests;
+
+namespace ViewBaseTests.Draw;
+
+///
+/// Tests that SubViews of adornments with = true
+/// get their border lines auto-joined with the parent View's border lines.
+///
+/// BUG (#4854): runs AFTER
+/// in the draw pipeline, so merged lines arrive too late.
+///
+public class AdornmentSubViewLineCanvasTests (ITestOutputHelper output) : TestDriverBase
+{
+ ///
+ /// 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.
+ ///
+ [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);
+ }
+}