Browse Source

Merge branch 'v2_develop' into v2_4488-PopoverMenu

Tig 3 days ago
parent
commit
5bf5a64ef2

+ 18 - 21
Examples/UICatalog/Scenarios/WideGlyphs.cs

@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
 
 using System.Text;
 
@@ -90,29 +90,16 @@ public sealed class WideGlyphs : Scenario
                     Rune codepoint = _codepoints [r, c];
                     if (codepoint != default (Rune))
                     {
-                        view.AddRune (c, r, codepoint);
+                        view.Move (c, r);
+                        Attribute attr = view.GetAttributeForRole (VisualRole.Normal);
+                        view.SetAttribute (attr with { Background = attr.Background + (r * 5) });
+                        view.AddRune (codepoint);
                     }
                 }
             }
             e.DrawContext?.AddDrawnRectangle (view.Viewport);
         };
 
-        Line verticalLineAtEven = new ()
-        {
-            X = 10,
-            Orientation = Orientation.Vertical,
-            Length = Dim.Fill ()
-        };
-        appWindow.Add (verticalLineAtEven);
-
-        Line verticalLineAtOdd = new ()
-        {
-            X = 25,
-            Orientation = Orientation.Vertical,
-            Length = Dim.Fill ()
-        };
-        appWindow.Add (verticalLineAtOdd);
-
         View arrangeableViewAtEven = new ()
         {
             CanFocus = true,
@@ -124,13 +111,16 @@ public sealed class WideGlyphs : Scenario
             //BorderStyle = LineStyle.Dashed
         };
 
+        arrangeableViewAtEven.SetScheme (new () { Normal = new (Color.Black, Color.Green) });
+
         // Proves it's not LineCanvas related
         arrangeableViewAtEven!.Border!.Thickness = new (1);
         arrangeableViewAtEven.Border.Add (new View () { Height = Dim.Auto (), Width = Dim.Auto (), Text = "Even" });
         appWindow.Add (arrangeableViewAtEven);
 
-        View arrangeableViewAtOdd = new ()
+        Button arrangeableViewAtOdd = new ()
         {
+            Title = $"你 {Glyphs.Apple}",
             CanFocus = true,
             Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
             X = 31,
@@ -138,8 +128,12 @@ public sealed class WideGlyphs : Scenario
             Width = 15,
             Height = 5,
             BorderStyle = LineStyle.Dashed,
+            SchemeName = "error"
         };
-
+        arrangeableViewAtOdd.Accepting += (sender, args) =>
+                                          {
+                                              MessageBox.Query ((sender as View)?.App, "Button Pressed", "You Pressed it!");
+                                          };
         appWindow.Add (arrangeableViewAtOdd);
 
         var superView = new View
@@ -150,8 +144,11 @@ public sealed class WideGlyphs : Scenario
             Width = Dim.Auto (),
             Height = Dim.Auto (),
             BorderStyle = LineStyle.Single,
-            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
+            ShadowStyle = ShadowStyle.Transparent,
         };
+        superView.Margin!.ShadowSize = superView.Margin!.ShadowSize with { Width = 2 };
+
 
         Rune codepoint = Glyphs.Apple;
 

+ 11 - 2
Terminal.Gui/Drivers/OutputBufferImpl.cs

@@ -185,8 +185,17 @@ public class OutputBufferImpl : IOutputBuffer
             if (printableGraphemeWidth > 1)
             {
                 // Skip the second column of a wide character
-                // IMPORTANT: We do NOT modify column N+1's IsDirty or Attribute here.
-                // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+                // See issue: https://github.com/gui-cs/Terminal.Gui/issues/4492
+                // Test: AddStr_WideGlyph_Second_Column_Attribute_Outputs_Correctly
+                // Test: AddStr_WideGlyph_Second_Column_Attribute_Set_When_In_Clip
+                if (Clip.Contains (Col, Row))
+                {
+                    // IMPORTANT: We do NOT modify column N+1's IsDirty or Attribute here.
+                    // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+                    Contents [Row, Col].Attribute = CurrentAttribute;
+                }
+
+                // Advance cursor again for wide character
                 Col++;
             }
         }

+ 129 - 22
Terminal.Gui/ViewBase/Adornment/Margin.cs

@@ -1,8 +1,3 @@
-
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
 namespace Terminal.Gui.ViewBase;
 
 /// <summary>The Margin for a <see cref="View"/>. Accessed via <see cref="View.Margin"/></summary>
@@ -21,8 +16,6 @@ namespace Terminal.Gui.ViewBase;
 /// </remarks>
 public class Margin : Adornment
 {
-    private const int SHADOW_WIDTH = 1;
-    private const int SHADOW_HEIGHT = 1;
     private const int PRESS_MOVE_HORIZONTAL = 1;
     private const int PRESS_MOVE_VERTICAL = 0;
 
@@ -35,6 +28,7 @@ public class Margin : Adornment
     public Margin (View parent) : base (parent)
     {
         SubViewLayout += Margin_LayoutStarted;
+        ThicknessChanged += OnThicknessChanged;
 
         // Margin should not be focusable
         CanFocus = false;
@@ -46,6 +40,15 @@ public class Margin : Adornment
         ViewportSettings |= ViewportSettingsFlags.TransparentMouse;
     }
 
+    private void OnThicknessChanged (object? sender, EventArgs e)
+    {
+        if (!_isThicknessChanging)
+        {
+            _originalThickness = new (Thickness.Left, Thickness.Top, Thickness.Right, Thickness.Bottom);
+            SetShadow (ShadowStyle);
+        }
+    }
+
     // When the Parent is drawn, we cache the clip region so we can draw the Margin after all other Views
     // QUESTION: Why can't this just be the NeedsDisplay region?
     private Region? _cachedClip;
@@ -56,7 +59,7 @@ public class Margin : Adornment
 
     internal void CacheClip ()
     {
-        if (Thickness != Thickness.Empty /*&& ShadowStyle != ShadowStyle.None*/)
+        if (Thickness != Thickness.Empty && ShadowStyle != ShadowStyle.None)
         {
             // PERFORMANCE: How expensive are these clones?
             _cachedClip = GetClip ()?.Clone ();
@@ -64,12 +67,15 @@ public class Margin : Adornment
     }
 
     /// <summary>
-    ///     INTERNAL API - Draws the margins for the specified views. This is called by the <see cref="Application"/> on each
+    ///     INTERNAL API - Draws the transparent margins for the specified views. This is called from <see cref="View.Draw"/> on each
     ///     iteration of the main loop after all Views have been drawn.
     /// </summary>
+    /// <remarks>
+    ///     Non-transparent margins are drawn as-normal in <see cref="View.DrawAdornments"/>.
+    /// </remarks>
     /// <param name="views"></param>
     /// <returns><see langword="true"/></returns>
-    internal static bool DrawMargins (IEnumerable<View> views)
+    internal static bool DrawTransparentMargins (IEnumerable<View> views)
     {
         Stack<View> stack = new (views);
 
@@ -77,7 +83,10 @@ public class Margin : Adornment
         {
             View view = stack.Pop ();
 
-            if (view.Margin is { } margin && margin.Thickness != Thickness.Empty && margin.GetCachedClip () != null)
+            if (view.Margin is { } margin
+                && margin.Thickness != Thickness.Empty
+                && margin.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent)
+                && margin.GetCachedClip () != null)
             {
                 margin.SetNeedsDraw ();
                 Region? saved = view.GetClip ();
@@ -87,8 +96,6 @@ public class Margin : Adornment
                 margin.ClearCachedClip ();
             }
 
-            view.ClearNeedsDraw ();
-
             foreach (View subview in view.SubViews)
             {
                 stack.Push (subview);
@@ -134,7 +141,7 @@ public class Margin : Adornment
         if (ShadowStyle != ShadowStyle.None)
         {
             // Don't clear where the shadow goes
-            screen = Rectangle.Inflate (screen, -SHADOW_WIDTH, -SHADOW_HEIGHT);
+            screen = Rectangle.Inflate (screen, -ShadowSize.Width, -ShadowSize.Height);
         }
 
         return true;
@@ -151,6 +158,8 @@ public class Margin : Adornment
     // private bool _pressed;
     private ShadowView? _bottomShadow;
     private ShadowView? _rightShadow;
+    private bool _isThicknessChanging;
+    private Thickness? _originalThickness;
 
     /// <summary>
     ///     Sets whether the Margin includes a shadow effect. The shadow is drawn on the right and bottom sides of the
@@ -172,25 +181,29 @@ public class Margin : Adornment
             _bottomShadow = null;
         }
 
+        _originalThickness ??= Thickness;
+
         if (ShadowStyle != ShadowStyle.None)
         {
             // Turn off shadow
-            Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right - SHADOW_WIDTH, Thickness.Bottom - SHADOW_HEIGHT);
+            _originalThickness = new (Thickness.Left, Thickness.Top, Math.Max (Thickness.Right - ShadowSize.Width, 0), Math.Max (Thickness.Bottom - ShadowSize.Height, 0));
         }
 
         if (style != ShadowStyle.None)
         {
             // Turn on shadow
-            Thickness = new (Thickness.Left, Thickness.Top, Thickness.Right + SHADOW_WIDTH, Thickness.Bottom + SHADOW_HEIGHT);
+            _isThicknessChanging = true;
+            Thickness = new (_originalThickness.Value.Left, _originalThickness.Value.Top, _originalThickness.Value.Right + ShadowSize.Width, _originalThickness.Value.Bottom + ShadowSize.Height);
+            _isThicknessChanging = false;
         }
 
         if (style != ShadowStyle.None)
         {
             _rightShadow = new ()
             {
-                X = Pos.AnchorEnd (SHADOW_WIDTH),
+                X = Pos.AnchorEnd (ShadowSize.Width),
                 Y = 0,
-                Width = SHADOW_WIDTH,
+                Width = ShadowSize.Width,
                 Height = Dim.Fill (),
                 ShadowStyle = style,
                 Orientation = Orientation.Vertical
@@ -199,14 +212,20 @@ public class Margin : Adornment
             _bottomShadow = new ()
             {
                 X = 0,
-                Y = Pos.AnchorEnd (SHADOW_HEIGHT),
+                Y = Pos.AnchorEnd (ShadowSize.Height),
                 Width = Dim.Fill (),
-                Height = SHADOW_HEIGHT,
+                Height = ShadowSize.Height,
                 ShadowStyle = style,
                 Orientation = Orientation.Horizontal
             };
             Add (_rightShadow, _bottomShadow);
         }
+        else if (Thickness != _originalThickness)
+        {
+            _isThicknessChanging = true;
+            Thickness = new (_originalThickness.Value.Left, _originalThickness.Value.Top, _originalThickness.Value.Right, _originalThickness.Value.Bottom);
+            _isThicknessChanging = false;
+        }
 
         return style;
     }
@@ -215,7 +234,90 @@ public class Margin : Adornment
     public override ShadowStyle ShadowStyle
     {
         get => base.ShadowStyle;
-        set => base.ShadowStyle = SetShadow (value);
+        set
+        {
+            if (value == ShadowStyle.Opaque || (value == ShadowStyle.Transparent && (ShadowSize.Width == 0 || ShadowSize.Height == 0)))
+            {
+                if (ShadowSize.Width != 1)
+                {
+                    ShadowSize = ShadowSize with { Width = 1 };
+                }
+
+                if (ShadowSize.Height != 1)
+                {
+                    ShadowSize = ShadowSize with { Height = 1 };
+                }
+            }
+
+            base.ShadowStyle = SetShadow (value);
+        }
+    }
+
+    private Size _shadowSize;
+
+    /// <summary>
+    ///     Gets or sets the size of the shadow effect.
+    /// </summary>
+    public Size ShadowSize
+    {
+        get => _shadowSize;
+        set
+        {
+            if (TryValidateShadowSize (_shadowSize, value, out Size result))
+            {
+                _shadowSize = value;
+                SetShadow (ShadowStyle);
+            }
+            else
+            {
+                _shadowSize = result;
+            }
+        }
+    }
+
+    private bool TryValidateShadowSize (Size originalValue, in Size newValue, out Size result)
+    {
+        result = newValue;
+
+        bool wasValid = true;
+
+        if (newValue.Width < 0)
+        {
+            result = ShadowStyle is ShadowStyle.Opaque or ShadowStyle.Transparent ? result with { Width = 1 } : originalValue;
+
+            wasValid = false;
+        }
+
+
+        if (newValue.Height < 0)
+        {
+            result = ShadowStyle is ShadowStyle.Opaque or ShadowStyle.Transparent ? result with { Height = 1 } : originalValue;
+
+            wasValid = false;
+        }
+
+        if (!wasValid)
+        {
+            return false;
+        }
+
+        bool wasUpdated = false;
+
+        if ((ShadowStyle == ShadowStyle.Opaque && newValue.Width != 1) || (ShadowStyle == ShadowStyle.Transparent && newValue.Width < 1))
+        {
+            result = result with { Width = 1 };
+
+            wasUpdated = true;
+        }
+
+        if ((ShadowStyle == ShadowStyle.Opaque && newValue.Height != 1) || (ShadowStyle == ShadowStyle.Transparent && newValue.Height < 1))
+        {
+            result = result with { Height = 1 };
+
+            wasUpdated = true;
+        }
+
+        return !wasUpdated;
     }
 
     private void OnParentOnMouseStateChanged (object? sender, EventArgs<MouseState> args)
@@ -226,7 +328,7 @@ public class Margin : Adornment
         }
 
         bool pressed = args.Value.HasFlag (MouseState.Pressed) && parent.HighlightStates.HasFlag (MouseState.Pressed);
-        bool pressedOutside = args.Value.HasFlag (MouseState.PressedOutside) && parent.HighlightStates.HasFlag (MouseState.PressedOutside); ;
+        bool pressedOutside = args.Value.HasFlag (MouseState.PressedOutside) && parent.HighlightStates.HasFlag (MouseState.PressedOutside);
 
         if (pressedOutside)
         {
@@ -238,11 +340,13 @@ public class Margin : Adornment
             // If the view is pressed and the highlight is being removed, move the shadow back.
             // Note, for visual effects reasons, we only move horizontally.
             // TODO: Add a setting or flag that lets the view move vertically as well.
+            _isThicknessChanging = true;
             Thickness = new (
                              Thickness.Left - PRESS_MOVE_HORIZONTAL,
                              Thickness.Top - PRESS_MOVE_VERTICAL,
                              Thickness.Right + PRESS_MOVE_HORIZONTAL,
                              Thickness.Bottom + PRESS_MOVE_VERTICAL);
+            _isThicknessChanging = false;
 
             if (_rightShadow is { })
             {
@@ -264,11 +368,14 @@ public class Margin : Adornment
             // If the view is not pressed, and we want highlight move the shadow
             // Note, for visual effects reasons, we only move horizontally.
             // TODO: Add a setting or flag that lets the view move vertically as well.
+            _isThicknessChanging = true;
             Thickness = new (
                              Thickness.Left + PRESS_MOVE_HORIZONTAL,
                              Thickness.Top + PRESS_MOVE_VERTICAL,
                              Thickness.Right - PRESS_MOVE_HORIZONTAL,
                              Thickness.Bottom - PRESS_MOVE_VERTICAL);
+            _isThicknessChanging = false;
+
             MouseState |= MouseState.Pressed;
 
             if (_rightShadow is { })

+ 21 - 5
Terminal.Gui/ViewBase/Adornment/ShadowView.cs

@@ -100,7 +100,13 @@ internal class ShadowView : View
 
                 if (c < ScreenContents?.GetLength (1) && r < ScreenContents?.GetLength (0))
                 {
-                    AddStr (ScreenContents [r, c].Grapheme);
+                    string grapheme = ScreenContents [r, c].Grapheme;
+                    AddStr (grapheme);
+
+                    if (grapheme.GetColumns () > 1)
+                    {
+                        c++;
+                    }
                 }
             }
         }
@@ -125,21 +131,31 @@ internal class ShadowView : View
         Rectangle screen = ViewportToScreen (Viewport);
 
         // Fill in the rest of the rectangle
-        for (int c = Math.Max (0, screen.X); c < screen.X + screen.Width; c++)
+        for (int r = Math.Max (0, screen.Y); r < screen.Y + viewport.Height; r++)
         {
-            for (int r = Math.Max (0, screen.Y); r < screen.Y + viewport.Height; r++)
+            for (int c = Math.Max (0, screen.X); c < screen.X + screen.Width; c++)
             {
                 Driver?.Move (c, r);
                 SetAttribute (GetAttributeUnderLocation (new (c, r)));
 
-                if (ScreenContents is { } && screen.X < ScreenContents.GetLength (1) && r < ScreenContents.GetLength (0))
+                if (ScreenContents is { } && screen.X < ScreenContents.GetLength (1) && r < ScreenContents.GetLength (0)
+                    && c < ScreenContents.GetLength (1) && r < ScreenContents.GetLength (0))
                 {
-                    AddStr (ScreenContents [r, c].Grapheme);
+                    string grapheme = ScreenContents [r, c].Grapheme;
+                    AddStr (grapheme);
+
+                    if (grapheme.GetColumns () > 1)
+                    {
+                        c++;
+                    }
                 }
             }
         }
     }
 
+    // BUGBUG: This will never really work completely right by looking at an underlying cell and trying
+    // BUGBUG: to do transparency by adjusting colors. Instead, it might be possible to use the A in argb for this.
+    // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/4491
     private Attribute GetAttributeUnderLocation (Point location)
     {
         if (SuperView is not Adornment

+ 20 - 4
Terminal.Gui/ViewBase/View.Drawing.cs

@@ -28,8 +28,8 @@ public partial class View // Drawing APIs
             view.Draw (context);
         }
 
-        // Draw the margins last to ensure they are drawn on top of the content.
-        Margin.DrawMargins (viewsArray);
+        // Draw Transparent margins last to ensure they are drawn on top of the content.
+        Margin.DrawTransparentMargins (viewsArray);
 
         // DrawMargins may have caused some views have NeedsDraw/NeedsSubViewDraw set; clear them all.
         foreach (View view in viewsArray)
@@ -183,7 +183,18 @@ public partial class View // Drawing APIs
 
     private void DoDrawAdornmentsSubViews ()
     {
-        // NOTE: We do not support SubViews of Margin
+        // Only SetNeedsDraw on Margin here if it is not Transparent. Transparent Margins are drawn in a separate pass in the static View.Draw
+        // via Margin.DrawTransparentMargins.
+        if (Margin is { NeedsDraw: true } && !Margin.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent) && Margin.Thickness != Thickness.Empty)
+        {
+            foreach (View subview in Margin.SubViews)
+            {
+                subview.SetNeedsDraw ();
+            }
+
+            // NOTE: We do not support arbitrary SubViews of Margin (only ShadowView)
+            // NOTE: so we do not call DoDrawSubViews on Margin.
+        }
 
         if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty && Border.NeedsDraw)
         {
@@ -268,7 +279,12 @@ public partial class View // Drawing APIs
     /// </remarks>
     public void DrawAdornments ()
     {
-        // We do not attempt to draw Margin. It is drawn in a separate pass.
+        // Only draw Margin here if it is not Transparent. Transparent Margins are drawn in a separate pass in the static View.Draw
+        // via Margin.DrawTransparentMargins.
+        if (Margin is { } && !Margin.ViewportSettings.HasFlag(ViewportSettingsFlags.Transparent) && Margin.Thickness != Thickness.Empty)
+        {
+            Margin?.Draw ();
+        }
 
         // Each of these renders lines to this View's LineCanvas
         // Those lines will be finally rendered in OnRenderLineCanvas

+ 2 - 0
Terminal.sln.DotSettings

@@ -382,6 +382,7 @@
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableEditorConfigSupport/@EntryValue">False</s:Boolean>
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/ShowEditorConfigStatusBarIndicator/@EntryValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/SyncToVisualStudio/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BMP/@EntryIndexedValue">BMP</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CWP/@EntryIndexedValue">CWP</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LL/@EntryIndexedValue">LL</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LR/@EntryIndexedValue">LR</s:String>
@@ -431,6 +432,7 @@
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Roslynator/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=RRGGBB/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=runnables/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=snek/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Toplevel/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Runnables/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Ungrab/@EntryIndexedValue">True</s:Boolean>

+ 3 - 3
Tests/UnitTests/View/Draw/ClipTests.cs

@@ -52,13 +52,13 @@ public class ClipTests (ITestOutputHelper _output)
         Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme);
 
         // When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
-        view.AddRune (0, 0, Rune.ReplacementChar);
+        view.AddRune (0, 0, Glyphs.WideGlyphReplacement);
         Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme);
 
-        view.AddRune (-1, -1, Rune.ReplacementChar);
+        view.AddRune (-1, -1, Glyphs.WideGlyphReplacement);
         Assert.Equal ("P", Application.Driver?.Contents! [1, 1].Grapheme);
 
-        view.AddRune (1, 1, Rune.ReplacementChar);
+        view.AddRune (1, 1, Glyphs.WideGlyphReplacement);
         Assert.Equal ("P", Application.Driver?.Contents! [3, 3].Grapheme);
     }
 

+ 122 - 45
Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs

@@ -50,25 +50,6 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
         Assert.Equal (expected, driver.Contents [0, 0].Grapheme);
         Assert.Equal (" ", driver.Contents [0, 1].Grapheme);
 
-        //		var s = "a\u0301\u0300\u0306";
-
-        //		DriverAsserts.AssertDriverContentsWithFrameAre (@"
-        //ắ", output);
-
-        //		tf.Text = "\u1eaf";
-        //		Application.Refresh ();
-        //		DriverAsserts.AssertDriverContentsWithFrameAre (@"
-        //ắ", output);
-
-        //		tf.Text = "\u0103\u0301";
-        //		Application.Refresh ();
-        //		DriverAsserts.AssertDriverContentsWithFrameAre (@"
-        //ắ", output);
-
-        //		tf.Text = "\u0061\u0306\u0301";
-        //		Application.Refresh ();
-        //		DriverAsserts.AssertDriverContentsWithFrameAre (@"
-        //ắ", output);
         driver.Dispose ();
     }
 
@@ -148,31 +129,6 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
         Assert.Equal (0, driver.Row);
         Assert.Equal (2, driver.Col);
 
-        //driver.AddRune ('b');
-        //Assert.Equal ((Text)'b', driver.Contents [0, 1].Text);
-        //Assert.Equal (0, driver.Row);
-        //Assert.Equal (2, driver.Col);
-
-        //// Move to the last column of the first row
-        //var lastCol = driver.Cols - 1;
-        //driver.Move (lastCol, 0);
-        //Assert.Equal (0, driver.Row);
-        //Assert.Equal (lastCol, driver.Col);
-
-        //// Add a rune to the last column of the first row; should increment the row or col even though it's now invalid
-        //driver.AddRune ('c');
-        //Assert.Equal ((Text)'c', driver.Contents [0, lastCol].Text);
-        //Assert.Equal (lastCol + 1, driver.Col);
-
-        //// Add a rune; should succeed but do nothing as it's outside of Contents
-        //driver.AddRune ('d');
-        //Assert.Equal (lastCol + 2, driver.Col);
-        //for (var col = 0; col < driver.Cols; col++) {
-        //	for (var row = 0; row < driver.Rows; row++) {
-        //		Assert.NotEqual ((Text)'d', driver.Contents [row, col].Text);
-        //	}
-        //}
-
         driver.Dispose ();
     }
 
@@ -183,7 +139,7 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
         driver.SetScreenSize (6, 3);
         driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
 
-        driver!.Clip = new (driver.Screen);
+        driver.Clip = new (driver.Screen);
         driver.Move (1, 0);
         driver.AddStr ("┌");
         driver.Move (2, 0);
@@ -207,4 +163,125 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
         DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m①┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
                                            output, driver);
     }
+
+    [Fact]
+    public void AddStr_WideGlyph_Second_Column_Attribute_Set_When_In_Clip ()
+    {
+        // This test verifies the fix for issue #4258
+        // When a wide glyph is added and the second column is within the clip region,
+        // the attribute for column N+1 should be set to match the current attribute.
+        // See: OutputBufferImpl.cs line 194
+        using IDriver driver = CreateFakeDriver ();
+        driver.SetScreenSize (4, 2);
+
+        // Set a specific attribute for the wide glyph
+        Attribute wideGlyphAttr = new (Color.BrightRed, Color.BrightYellow);
+        driver.CurrentAttribute = wideGlyphAttr;
+
+        // Add a wide glyph at position (0, 0)
+        driver.Move (0, 0);
+        driver.AddStr ("🍎");
+
+        // Verify the wide glyph is in column 0
+        Assert.Equal ("🍎", driver.Contents! [0, 0].Grapheme);
+        Assert.Equal (wideGlyphAttr, driver.Contents [0, 0].Attribute);
+
+        // Verify column 1 (the second column of the wide glyph) has the correct attribute set
+        // This is the fix: column N+1 should have CurrentAttribute set (line 194 in OutputBufferImpl.cs)
+        Assert.Equal (wideGlyphAttr, driver.Contents [0, 1].Attribute);
+
+        // Verify cursor moved to column 2
+        Assert.Equal (2, driver.Col);
+    }
+
+    [Fact]
+    public void AddStr_WideGlyph_Second_Column_Attribute_Not_Set_When_Outside_Clip ()
+    {
+        // This test verifies that when a wide glyph's second column is outside the clip,
+        // the attribute for column N+1 is NOT modified
+        using IDriver driver = CreateFakeDriver ();
+        driver.SetScreenSize (4, 2);
+
+        // Set initial attribute for the entire contents
+        Attribute initialAttr = new (Color.White, Color.Black);
+        driver.CurrentAttribute = initialAttr;
+        driver.Move (0, 0);
+        driver.AddStr ("    ");
+        driver.Move (0, 1);
+        driver.AddStr ("    ");
+
+        // Create a clip that excludes column 1
+        driver.Clip = new (new Rectangle (0, 0, 1, 2));
+
+        // Set a different attribute for the wide glyph
+        Attribute wideGlyphAttr = new (Color.BrightRed, Color.BrightYellow);
+        driver.CurrentAttribute = wideGlyphAttr;
+
+        // Try to add a wide glyph at position (0, 0)
+        // Column 0 is in clip, but column 1 is NOT
+        driver.Move (0, 0);
+        driver.AddStr ("🍎");
+
+        // Verify column 0 has the replacement character (can't fit wide glyph)
+        Assert.NotEqual ("🍎", driver.Contents! [0, 0].Grapheme);
+
+        // Verify column 1 still has the original attribute (NOT modified)
+        Assert.Equal (initialAttr, driver.Contents [0, 1].Attribute);
+    }
+
+    [Fact]
+    public void AddStr_WideGlyph_Second_Column_Attribute_Outputs_Correctly ()
+    {
+        // This test verifies the fix for issue #4258 by checking the actual driver output
+        // This mimics what happens when TransparentShadow redraws a wide glyph from ScreenContents
+        // WITHOUT line 194, column N+1's attribute doesn't get set, causing wrong colors in output
+        // See: OutputBufferImpl.cs line ~196 (Contents [Row, Col].Attribute = CurrentAttribute;)
+        using IDriver driver = CreateFakeDriver ();
+        driver.SetScreenSize (3, 1);
+        driver.Force16Colors = true;
+
+        // Step 1: Draw initial content - a wide glyph at column 1 with white-on-black
+        driver.CurrentAttribute = new Attribute (Color.White, Color.Black);
+        driver.Move (1, 0);
+        driver.AddStr ("🍎X");  // Wide glyph at columns 1-2, 'X' at column 3 doesn't exist (off-screen)
+
+        // At this point:
+        // - Column 0: space (default) with white-on-black
+        // - Column 1: 🍎 with white-on-black
+        // - Column 2: (part of 🍎) with white-on-black (from initial ClearContents)
+
+        // Step 2: Now redraw the SAME wide glyph at column 1 but with a DIFFERENT attribute (red-on-yellow)
+        // This simulates what transparent shadow does - it redraws what's underneath with a dimmed attribute
+        driver.CurrentAttribute = new Attribute (Color.BrightRed, Color.BrightYellow);
+        driver.Move (1, 0);
+        driver.AddStr ("🍎");
+
+        // Verify internal state
+        Assert.Equal ("🍎", driver.Contents! [0, 1].Grapheme);
+        Assert.Equal (new Attribute (Color.BrightRed, Color.BrightYellow), driver.Contents [0, 1].Attribute);
+
+        // THIS is the critical assertion - column 2's attribute MUST be red-on-yellow
+        // WITHOUT line 194: column 2 retains white-on-black
+        // WITH line 194: column 2 gets red-on-yellow
+        Assert.Equal (new Attribute (Color.BrightRed, Color.BrightYellow), driver.Contents [0, 2].Attribute);
+
+        driver.Refresh ();
+
+        // Expected output:
+        // Column 0: space with white-on-black
+        // Columns 1-2: 🍎 with red-on-yellow (both columns must have same attribute!)
+        //
+        // WITHOUT line 196, the output would be:
+        // \x1b[97m\x1b[40m  (white-on-black for column 0)
+        // \x1b[91m\x1b[103m🍎 (red-on-yellow starts at column 1)
+        // \x1b[97m\x1b[40m (WRONG! Attribute changes mid-glyph because column 2 still has white-on-black)
+        //
+        // WITH line 196, the output is:
+        // \x1b[97m\x1b[40m  (white-on-black for column 0)
+        // \x1b[91m\x1b[103m🍎 (red-on-yellow for both columns 1 and 2)
+        DriverAssert.AssertDriverOutputIs (
+            "\x1b[97m\x1b[40m \x1b[91m\x1b[103m🍎",
+            output,
+            driver);
+    }
 }

+ 193 - 0
Tests/UnitTestsParallelizable/ViewBase/Adornment/BorderArrangementTests.cs

@@ -0,0 +1,193 @@
+#nullable enable
+using System.Text;
+using UnitTests;
+using Xunit.Abstractions;
+
+namespace ViewBaseTests.Adornments;
+
+[Collection ("Global Test Setup")]
+public class BorderArrangementTests (ITestOutputHelper output)
+{
+    [Fact]
+    public void Arrangement_Handles_Wide_Glyphs_Correctly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        app.Driver?.SetScreenSize (6, 5);
+        app.Driver?.GetOutputBuffer ().SetWideGlyphReplacement (Rune.ReplacementChar);
+
+        Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        superview.Text = """
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         """;
+
+        View view = new ()
+        {
+            X = 2, Width = 4, Height = 4, BorderStyle = LineStyle.Single,
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable, CanFocus = true
+        };
+        superview.Add (view);
+
+        app.Begin (superview);
+
+        Assert.Equal ("Absolute(2)", view.X.ToString ());
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎┌──┐
+                                              🍎│  │
+                                              🍎│  │
+                                              🍎└──┘
+                                              🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.F5.WithCtrl));
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎◊──┐
+                                              🍎│  │
+                                              🍎│  │
+                                              🍎└──↘
+                                              🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.CursorLeft));
+        Assert.Equal ("Absolute(1)", view.X.ToString ());
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �◊──┐
+                                              �│  │
+                                              �│  │
+                                              �└──↘
+                                              🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.CursorLeft));
+        Assert.Equal ("Absolute(0)", view.X.ToString ());
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              ◊──┐🍎
+                                              │  │🍎
+                                              │  │🍎
+                                              └──↘🍎
+                                              🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+    }
+
+    [Fact]
+    public void Arrangement_With_SubView_In_Border_Handles_Wide_Glyphs_Correctly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        app.Driver?.SetScreenSize (8, 7);
+        app.Driver?.GetOutputBuffer ().SetWideGlyphReplacement (Rune.ReplacementChar);
+
+        Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        superview.Text = """
+                         🍎🍎🍎🍎
+                         🍎🍎🍎🍎
+                         🍎🍎🍎🍎
+                         🍎🍎🍎🍎
+                         🍎🍎🍎🍎
+                         🍎🍎🍎🍎
+                         🍎🍎🍎🍎
+                         """;
+
+        View view = new ()
+        {
+            X = 2, Width = 6, Height = 6, Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable, CanFocus = true
+        };
+        view.Border!.Thickness = new (1);
+        view.Border.Add (new View { Height = Dim.Auto (), Width = Dim.Auto (), Text = "Hi" });
+        superview.Add (view);
+
+        app.Begin (superview);
+
+        Assert.Equal ("Absolute(2)", view.X.ToString ());
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎Hi
+                                              🍎
+                                              🍎
+                                              🍎
+                                              🍎
+                                              🍎
+                                              🍎🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.F5.WithCtrl));
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              🍎◊i
+                                              🍎
+                                              🍎
+                                              🍎
+                                              🍎
+                                              🍎     ↘
+                                              🍎🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.CursorLeft));
+        Assert.Equal ("Absolute(1)", view.X.ToString ());
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              �◊i
+                                              �
+                                              �
+                                              �
+                                              �
+                                              �     ↘
+                                              🍎🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.CursorLeft));
+        Assert.Equal ("Absolute(0)", view.X.ToString ());
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              ◊i    🍎
+                                                    🍎
+                                                    🍎
+                                                    🍎
+                                                    🍎
+                                                   ↘🍎
+                                              🍎🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+    }
+}

+ 22 - 0
Tests/UnitTestsParallelizable/ViewBase/Adornment/MarginTests.cs

@@ -133,4 +133,26 @@ MMM",
         Assert.True (view.Margin!.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent), "Margin should be transparent when ShadowStyle is Opaque..");
     }
 
+    [Fact]
+    public void Margin_Layouts_Correctly ()
+    {
+        View superview = new () { Width = 10, Height = 5 };
+        View view = new () { Width = 3, Height = 1, BorderStyle = LineStyle.Single };
+        view.Margin!.Thickness = new (1);
+        View view2 = new () { X = Pos.Right (view), Width = 3, Height = 1, BorderStyle = LineStyle.Single };
+        view2.Margin!.Thickness = new (1);
+        View view3 = new () { Y = Pos.Bottom (view), Width = 3, Height = 1, BorderStyle = LineStyle.Single };
+        view3.Margin!.Thickness = new (1);
+        superview.Add (view, view2, view3);
+
+        superview.LayoutSubViews ();
+
+        Assert.Equal (new (0, 0, 10, 5), superview.Frame);
+        Assert.Equal (new (0, 0, 3, 1), view.Frame);
+        Assert.Equal (Rectangle.Empty, view.Viewport);
+        Assert.Equal (new (3, 0, 3, 1), view2.Frame);
+        Assert.Equal (Rectangle.Empty, view2.Viewport);
+        Assert.Equal (new (0, 1, 3, 1), view3.Frame);
+        Assert.Equal (Rectangle.Empty, view3.Viewport);
+    }
 }

+ 0 - 157
Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowStyletests.cs

@@ -1,157 +0,0 @@
-using UnitTests;
-using Xunit.Abstractions;
-
-namespace ViewBaseTests.Adornments;
-
-[Collection ("Global Test Setup")]
-
-public class ShadowStyleTests (ITestOutputHelper output)
-{
-    private readonly ITestOutputHelper _output = output;
-
-    [Fact]
-    public void Default_None ()
-    {
-        var view = new View ();
-        Assert.Equal (ShadowStyle.None, view.ShadowStyle);
-        Assert.Equal (ShadowStyle.None, view.Margin!.ShadowStyle);
-        view.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (ShadowStyle.None)]
-    [InlineData (ShadowStyle.Opaque)]
-    [InlineData (ShadowStyle.Transparent)]
-    public void Set_View_Sets_Margin (ShadowStyle style)
-    {
-        var view = new View ();
-
-        view.ShadowStyle = style;
-        Assert.Equal (style, view.ShadowStyle);
-        Assert.Equal (style, view.Margin!.ShadowStyle);
-        view.Dispose ();
-    }
-
-
-    [Theory]
-    [InlineData (ShadowStyle.None, 0, 0, 0, 0)]
-    [InlineData (ShadowStyle.Opaque, 0, 0, 1, 1)]
-    [InlineData (ShadowStyle.Transparent, 0, 0, 1, 1)]
-    public void ShadowStyle_Margin_Thickness (ShadowStyle style, int expectedLeft, int expectedTop, int expectedRight, int expectedBottom)
-    {
-        var superView = new View
-        {
-            Height = 10, Width = 10
-        };
-
-        View view = new ()
-        {
-            Width = Dim.Auto (),
-            Height = Dim.Auto (),
-            Text = "0123",
-            HighlightStates = MouseState.Pressed,
-            ShadowStyle = style,
-            CanFocus = true
-        };
-
-        superView.Add (view);
-        superView.BeginInit ();
-        superView.EndInit ();
-
-        Assert.Equal (new (expectedLeft, expectedTop, expectedRight, expectedBottom), view.Margin!.Thickness);
-    }
-
-
-    [Theory]
-    [InlineData (ShadowStyle.None, 3)]
-    [InlineData (ShadowStyle.Opaque, 4)]
-    [InlineData (ShadowStyle.Transparent, 4)]
-    public void Style_Changes_Margin_Thickness (ShadowStyle style, int expected)
-    {
-        var view = new View ();
-        view.Margin!.Thickness = new (3);
-        view.ShadowStyle = style;
-        Assert.Equal (new (3, 3, expected, expected), view.Margin.Thickness);
-
-        view.ShadowStyle = ShadowStyle.None;
-        Assert.Equal (new (3), view.Margin.Thickness);
-        view.Dispose ();
-    }
-
-
-    [Fact]
-    public void TransparentShadow_Draws_Transparent_At_Driver_Output ()
-    {
-        // Arrange
-        IApplication app = Application.Create ();
-        app.Init ("fake");
-        app.Driver!.SetScreenSize (5, 3);
-
-        // Force 16-bit colors off to get predictable RGB output
-        app.Driver.Force16Colors = false;
-
-        var superView = new Runnable
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-            Text = "ABC".Repeat (40)!
-        };
-        superView.SetScheme (new (new Attribute (Color.White, Color.Blue)));
-        superView.TextFormatter.WordWrap = true;
-
-        // Create an overlapped view with transparent shadow
-        var overlappedView = new View
-        {
-            Width = 4,
-            Height = 2,
-            Text = "123",
-            Arrangement = ViewArrangement.Overlapped,
-            ShadowStyle = ShadowStyle.Transparent
-        };
-        overlappedView.SetScheme (new (new Attribute (Color.Black, Color.Green)));
-
-        superView.Add (overlappedView);
-
-        // Act
-        SessionToken? token = app.Begin (superView);
-        app.LayoutAndDraw ();
-        app.Driver.Refresh ();
-
-        // Assert
-        _output.WriteLine ("Actual driver contents:");
-        _output.WriteLine (app.Driver.ToString ());
-        _output.WriteLine ("\nActual driver output:");
-        string? output = app.Driver.GetOutput ().GetLastOutput ();
-        _output.WriteLine (output);
-
-        DriverAssert.AssertDriverOutputIs ("""
-                                           \x1b[38;2;0;0;0m\x1b[48;2;0;128;0m123\x1b[38;2;0;0;0m\x1b[48;2;189;189;189mA\x1b[38;2;0;0;255m\x1b[48;2;255;255;255mBC\x1b[38;2;0;0;0m\x1b[48;2;189;189;189mABC\x1b[38;2;0;0;255m\x1b[48;2;255;255;255mABCABC
-                                           """, _output, app.Driver);
-
-        // The output should contain ANSI color codes for the transparent shadow
-        // which will have dimmed colors compared to the original
-        Assert.Contains ("\x1b[38;2;", output); // Should have RGB foreground color codes
-        Assert.Contains ("\x1b[48;2;", output); // Should have RGB background color codes
-
-        // Verify driver contents show the background text in shadow areas
-        int shadowX = overlappedView.Frame.X + overlappedView.Frame.Width;
-        int shadowY = overlappedView.Frame.Y + overlappedView.Frame.Height;
-
-        Cell shadowCell = app.Driver.Contents! [shadowY, shadowX];
-        _output.WriteLine ($"\nShadow cell at [{shadowY},{shadowX}]: Grapheme='{shadowCell.Grapheme}', Attr={shadowCell.Attribute}");
-
-        // The grapheme should be from background text
-        Assert.NotEqual (string.Empty, shadowCell.Grapheme);
-        Assert.Contains (shadowCell.Grapheme, "ABC"); // Should be one of the background characters
-
-        // Cleanup
-        if (token is { })
-        {
-            app.End (token);
-        }
-
-        superView.Dispose ();
-        app.Dispose ();
-    }
-
-}

+ 487 - 0
Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs

@@ -0,0 +1,487 @@
+using System.Text;
+using UnitTests;
+using Xunit.Abstractions;
+
+namespace ViewBaseTests.Adornments;
+
+[Collection ("Global Test Setup")]
+
+public class ShadowTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public void Default_None ()
+    {
+        var view = new View ();
+        Assert.Equal (ShadowStyle.None, view.ShadowStyle);
+        Assert.Equal (ShadowStyle.None, view.Margin!.ShadowStyle);
+        view.Dispose ();
+    }
+
+    [Theory]
+    [InlineData (ShadowStyle.None)]
+    [InlineData (ShadowStyle.Opaque)]
+    [InlineData (ShadowStyle.Transparent)]
+    public void Set_View_Sets_Margin (ShadowStyle style)
+    {
+        var view = new View ();
+
+        view.ShadowStyle = style;
+        Assert.Equal (style, view.ShadowStyle);
+        Assert.Equal (style, view.Margin!.ShadowStyle);
+        view.Dispose ();
+    }
+
+
+    [Theory]
+    [InlineData (ShadowStyle.None, 0, 0, 0, 0)]
+    [InlineData (ShadowStyle.Opaque, 0, 0, 1, 1)]
+    [InlineData (ShadowStyle.Transparent, 0, 0, 1, 1)]
+    public void ShadowStyle_Margin_Thickness (ShadowStyle style, int expectedLeft, int expectedTop, int expectedRight, int expectedBottom)
+    {
+        var superView = new View
+        {
+            Height = 10, Width = 10
+        };
+
+        View view = new ()
+        {
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            Text = "0123",
+            HighlightStates = MouseState.Pressed,
+            ShadowStyle = style,
+            CanFocus = true
+        };
+
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        Assert.Equal (new (expectedLeft, expectedTop, expectedRight, expectedBottom), view.Margin!.Thickness);
+    }
+
+
+    [Theory]
+    [InlineData (ShadowStyle.None, 3)]
+    [InlineData (ShadowStyle.Opaque, 4)]
+    [InlineData (ShadowStyle.Transparent, 4)]
+    public void Style_Changes_Margin_Thickness (ShadowStyle style, int expected)
+    {
+        var view = new View ();
+        view.Margin!.Thickness = new (3);
+        view.ShadowStyle = style;
+        Assert.Equal (new (3, 3, expected, expected), view.Margin.Thickness);
+
+        view.ShadowStyle = ShadowStyle.None;
+        Assert.Equal (new (3), view.Margin.Thickness);
+        view.Dispose ();
+    }
+
+    [Theory]
+    [InlineData (ShadowStyle.Opaque)]
+    [InlineData (ShadowStyle.Transparent)]
+    public void ShadowWidth_ShadowHeight_Defaults_To_One (ShadowStyle style)
+    {
+        View view = new () { ShadowStyle = style };
+
+        Assert.Equal (new (1, 1), view.Margin!.ShadowSize);
+    }
+
+    [Theory]
+    [InlineData (ShadowStyle.None, 0)]
+    [InlineData (ShadowStyle.Opaque, 1)]
+    [InlineData (ShadowStyle.Transparent, 1)]
+    public void Margin_ShadowWidth_ShadowHeight_Cannot_Be_Set_Less_Than_One (ShadowStyle style, int expectedLength)
+    {
+        View view = new () { ShadowStyle = style };
+        view.Margin!.ShadowSize = new (-1, -1);
+        Assert.Equal (expectedLength, view.Margin!.ShadowSize.Width);
+        Assert.Equal (expectedLength, view.Margin!.ShadowSize.Height);
+    }
+
+    [Fact]
+    public void Changing_ShadowStyle_Correctly_Set_ShadowWidth_ShadowHeight_Thickness ()
+    {
+        View view = new () { ShadowStyle = ShadowStyle.Transparent };
+        view.Margin!.ShadowSize = new (2, 2);
+
+        Assert.Equal (new (2, 2), view.Margin!.ShadowSize);
+        Assert.Equal (new (0, 0, 2, 2), view.Margin.Thickness);
+
+        view.ShadowStyle = ShadowStyle.None;
+        Assert.Equal (new (2, 2), view.Margin!.ShadowSize);
+        Assert.Equal (new (0, 0, 0, 0), view.Margin.Thickness);
+
+        view.ShadowStyle = ShadowStyle.Opaque;
+        Assert.Equal (new (1, 1), view.Margin!.ShadowSize);
+        Assert.Equal (new (0, 0, 1, 1), view.Margin.Thickness);
+    }
+
+    [Fact]
+    public void ShadowStyle_Transparent_Handles_Wide_Glyphs_Correctly ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        app.Driver?.SetScreenSize (6, 5);
+        app.Driver?.GetOutputBuffer ().SetWideGlyphReplacement (Rune.ReplacementChar);
+
+        Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        superview.Text = """
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         """;
+
+        View view = new () { Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single, ShadowStyle = ShadowStyle.Transparent };
+        view.Margin!.ShadowSize = view.Margin!.ShadowSize with { Width = 2 };
+        superview.Add (view);
+
+        app.Begin (superview);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              ┌──┐🍎
+                                              │  │🍎
+                                              │  │🍎
+                                              └──┘🍎
+                                              � 🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        view.Margin!.ShadowSize = new (1, 2);
+
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              ┌──┐🍎
+                                              │  │�
+                                              └──┘�
+                                              � 🍎🍎
+                                              � 🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+    }
+
+    [Fact]
+    public void ShadowStyle_Opaque_Change_Thickness_On_Mouse_Pressed_Released ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        app.Driver?.SetScreenSize (10, 4);
+
+        Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () };
+        View view = new () { Width = 7, Height = 2, ShadowStyle = ShadowStyle.Opaque, Text = "| Hi |", HighlightStates = MouseState.Pressed };
+        superview.Add (view);
+
+        app.Begin (superview);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              | Hi |▖
+                                              ▝▀▀▀▀▀▘
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (2, 0), Flags = MouseFlags.Button1Pressed });
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              | Hi |
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (2, 0), Flags = MouseFlags.Button1Released });
+        app.LayoutAndDraw ();
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              | Hi |▖
+                                              ▝▀▀▀▀▀▘
+                                              """,
+                                              output,
+                                              app.Driver);
+    }
+
+    [Fact]
+    public void ShadowStyle_Transparent_Never_Throws_Navigating_Outside_Bounds ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        app.Driver?.SetScreenSize (6, 5);
+
+        Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+        superview.Text = """
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         🍎🍎🍎
+                         """;
+
+        View view = new ()
+        {
+            Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single, ShadowStyle = ShadowStyle.Transparent,
+            Arrangement = ViewArrangement.Movable, CanFocus = true
+        };
+        view.Margin!.ShadowSize = view.Margin!.ShadowSize with { Width = 2 };
+        superview.Add (view);
+
+        app.Begin (superview);
+
+        Assert.Equal (new (0, 0), view.Frame.Location);
+
+        Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.F5.WithCtrl));
+
+        int i = 0;
+        DecrementValue (-10, Key.CursorLeft);
+        Assert.Equal (-10, i);
+
+        IncrementValue (0, Key.CursorRight);
+        Assert.Equal (0, i);
+
+        DecrementValue (-10, Key.CursorUp);
+        Assert.Equal (-10, i);
+
+        IncrementValue (20, Key.CursorDown);
+        Assert.Equal (20, i);
+
+        DecrementValue (0, Key.CursorUp);
+        Assert.Equal (0, i);
+
+        IncrementValue (20, Key.CursorRight);
+        Assert.Equal (20, i);
+
+        return;
+
+        void DecrementValue (int count, Key key)
+        {
+            for (; i > count; i--)
+            {
+                Assert.True (app.Keyboard.RaiseKeyDownEvent (key));
+                app.LayoutAndDraw ();
+
+                CheckAssertion (new (i - 1, 0), new (0, i - 1), key);
+            }
+        }
+
+        void IncrementValue (int count, Key key)
+        {
+            for (; i < count; i++)
+            {
+                Assert.True (app.Keyboard.RaiseKeyDownEvent (key));
+                app.LayoutAndDraw ();
+
+                CheckAssertion (new (i + 1, 0), new (0, i + 1), key);
+            }
+        }
+
+        bool? IsColumn (Key key)
+        {
+            if (key == Key.CursorLeft || key == Key.CursorRight)
+            {
+                return true;
+            }
+
+            if (key == Key.CursorUp || key == Key.CursorDown)
+            {
+                return false;
+            }
+
+            return null;
+        }
+
+        void CheckAssertion (Point colLocation, Point rowLocation, Key key)
+        {
+            bool? isCol = IsColumn (key);
+
+            switch (isCol)
+            {
+                case true:
+                    Assert.Equal (colLocation, view.Frame.Location);
+
+                    break;
+                case false:
+                    Assert.Equal (rowLocation, view.Frame.Location);
+
+                    break;
+                default:
+                    throw new InvalidOperationException ();
+            }
+        }
+    }
+
+    [Theory]
+    [InlineData (ShadowStyle.None, 3)]
+    [InlineData (ShadowStyle.Opaque, 4)]
+    [InlineData (ShadowStyle.Transparent, 4)]
+    public void Margin_Thickness_Changes_Adjust_Correctly (ShadowStyle style, int expected)
+    {
+        var view = new View ();
+        view.Margin!.Thickness = new (3);
+        view.ShadowStyle = style;
+        Assert.Equal (new (3, 3, expected, expected), view.Margin.Thickness);
+
+        view.Margin.Thickness = new (3, 3, expected + 1, expected + 1);
+        Assert.Equal (new (3, 3, expected + 1, expected + 1), view.Margin.Thickness);
+        view.ShadowStyle = ShadowStyle.None;
+        Assert.Equal (new (3, 3, 4, 4), view.Margin.Thickness);
+        view.Dispose ();
+    }
+
+    [Fact]
+    public void Runnable_View_Overlap_Other_Runnables ()
+    {
+        IApplication app = Application.Create ();
+        app.Init ("fake");
+
+        app.Driver?.SetScreenSize (10, 5);
+
+        Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill (), Text = "🍎".Repeat (25)! };
+        View view = new () { Width = 7, Height = 2, ShadowStyle = ShadowStyle.Opaque, Text = "| Hi |" };
+        superview.Add (view);
+
+        app.Begin (superview);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              | Hi |▖ 🍎
+                                              ▝▀▀▀▀▀▘ 🍎
+                                              🍎🍎🍎🍎🍎
+                                              🍎🍎🍎🍎🍎
+                                              🍎🍎🍎🍎🍎
+                                              """,
+                                              output,
+                                              app.Driver);
+
+        Runnable modalSuperview = new () { Y = 1, Width = Dim.Fill (), Height = 4, BorderStyle = LineStyle.Single };
+        View view1 = new () { Width = 8, Height = 2, ShadowStyle = ShadowStyle.Opaque, Text = "| Hey |" };
+        modalSuperview.Add (view1);
+
+        app.Begin (modalSuperview);
+
+        Assert.True (modalSuperview.IsModal);
+
+        DriverAssert.AssertDriverContentsAre (
+                                              """
+                                              | Hi |▖ 🍎
+                                              ┌────────┐
+                                              │| Hey |▖│
+                                              │▝▀▀▀▀▀▀▘│
+                                              └────────┘
+                                              """,
+                                              output,
+                                              app.Driver);
+
+
+        app.Dispose ();
+    }
+
+    [Fact]
+    public void TransparentShadow_Draws_Transparent_At_Driver_Output ()
+    {
+        // Arrange
+        using IApplication app = Application.Create ();
+        app.Init ("fake");
+        app.Driver!.SetScreenSize (2, 1);
+        app.Driver.Force16Colors = true;
+
+        using Runnable superView = new ();
+        superView.Width = Dim.Fill ();
+        superView.Height = Dim.Fill ();
+        superView.Text = "AB";
+        superView.TextFormatter.WordWrap = true;
+        superView.SetScheme (new (new Attribute (Color.Black, Color.White)));
+
+        // Create view with transparent shadow
+        View viewWithShadow = new ()
+        {
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            Text = "*",
+            ShadowStyle = ShadowStyle.Transparent
+        };
+        // Make it so the margin is only on the right for simplicity
+        viewWithShadow.Margin!.Thickness = new (0, 0, 1, 0);
+        viewWithShadow.SetScheme (new (new Attribute (Color.Black, Color.White)));
+
+        superView.Add (viewWithShadow);
+
+        // Act
+        app.Begin (superView);
+        app.LayoutAndDraw ();
+        app.Driver.Refresh ();
+
+        // Assert
+        _output.WriteLine ("Actual driver contents:");
+        _output.WriteLine (app.Driver.ToString ());
+        _output.WriteLine ("\nActual driver output:");
+        string? output = app.Driver.GetOutput ().GetLastOutput ();
+        _output.WriteLine (output);
+
+        DriverAssert.AssertDriverOutputIs ("""
+                                           \x1b[30m\x1b[107m*\x1b[90m\x1b[100mB
+                                           """, _output, app.Driver);
+    }
+
+    [Fact]
+    public void TransparentShadow_OverWide_Draws_Transparent_At_Driver_Output ()
+    {
+        // Arrange
+        using IApplication app = Application.Create ();
+        app.Init ("fake");
+        app.Driver!.SetScreenSize (2, 3);
+        app.Driver.Force16Colors = true;
+
+        using Runnable superView = new ();
+        superView.Width = Dim.Fill ();
+        superView.Height = Dim.Fill ();
+        superView.Text = "🍎🍎🍎🍎";
+        superView.TextFormatter.WordWrap = true;
+        superView.SetScheme (new (new Attribute (Color.Black, Color.White)));
+
+        // Create view with transparent shadow
+        View viewWithShadow = new ()
+        {
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            Text = "*",
+            ShadowStyle = ShadowStyle.Transparent
+        };
+        // Make it so the margin is only on the bottom for simplicity
+        viewWithShadow.Margin!.Thickness = new (0, 0, 0, 1);
+        viewWithShadow.SetScheme (new (new Attribute (Color.Black, Color.White)));
+
+        superView.Add (viewWithShadow);
+
+        // Act
+        app.Begin (superView);
+        app.LayoutAndDraw ();
+        app.Driver.Refresh ();
+
+        // Assert
+        _output.WriteLine ("Actual driver contents:");
+        _output.WriteLine (app.Driver.ToString ());
+        _output.WriteLine ("\nActual driver output:");
+        string? output = app.Driver.GetOutput ().GetLastOutput ();
+        _output.WriteLine (output);
+
+        DriverAssert.AssertDriverOutputIs ("""
+                                           \x1b[30m\x1b[107m*\x1b[90m\x1b[103m \x1b[97m\x1b[40m \x1b[90m\x1b[100m \x1b[97m\x1b[40m🍎
+                                           """, _output, app.Driver);
+    }
+}