瀏覽代碼

Merge branch 'v2_develop' into v2_3574_self-contained-single-file

BDisp 1 年之前
父節點
當前提交
d31f798391

+ 1 - 1
.github/workflows/api-docs.yml

@@ -2,7 +2,7 @@ name: Build and publish API docs
 
 on:
   push:
-    branches: [main, develop, v2_develop]
+    branches: [main, v2_develop]
 
 permissions:
   id-token: write 

+ 29 - 24
.github/workflows/dotnet-core.yml

@@ -2,19 +2,24 @@ name: Build & Test Terminal.Gui with .NET Core
 
 on:
   push:
-    branches: [ main, develop, v2_develop ]
+    branches: [ v2_release, v2_develop ]
+    paths-ignore:
+      - '**.md'
   pull_request:
-    branches: [ main, develop, v2_develop ]
-
+    branches: [ v2_release, v2_develop ]
+    paths-ignore:
+      - '**.md'
+      
 jobs:
   build_and_test:
 
-    runs-on: windows-latest
-
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
     steps:
-    - uses: actions/checkout@v4
     
-    - name: Setup dotnet
+    - uses: actions/checkout@v4
+
+    - name: Setup .NET Core
       uses: actions/setup-dotnet@v4
       with:
         dotnet-version: 8.x
@@ -30,23 +35,23 @@ jobs:
     - name: Test
       run: |
         sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json
-        dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage"  --settings UnitTests/coverlet.runsettings --blame
+        dotnet test --verbosity normal --blame 
         mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
 
     # Note: this step is currently not writing to the gist for some reason
-    - name: Create Test Coverage Badge
-      uses: simon-k/[email protected]
-      id: create_coverage_badge
-      with:
-        label: Unit Test Coverage
-        color: brightgreen
-        path: UnitTests/TestResults/coverage.opencover.xml
-        gist-filename: code-coverage.json
-        # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27
-        gist-id: 90ef67a684cb71db1817921a970f8d27
-        gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }}   
-
-    - name: Print Code Coverage
-      run: |
-        echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%"
-        echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}"
+    # - name: Create Test Coverage Badge
+    #   uses: simon-k/[email protected]
+    #   id: create_coverage_badge
+    #   with:
+    #     label: Unit Test Coverage
+    #     color: brightgreen
+    #     path: UnitTests/TestResults/coverage.opencover.xml
+    #     gist-filename: code-coverage.json
+    #     # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27
+    #     gist-id: 90ef67a684cb71db1817921a970f8d27
+    #     gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }}   
+
+    # - name: Print Code Coverage
+    #   run: |
+    #     echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%"
+    #     echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}"

+ 1 - 1
.github/workflows/publish.yml

@@ -25,7 +25,7 @@ jobs:
           includePrerelease: true
 
     - name: Determine Version
-      uses: gittools/actions/gitversion/execute@v0
+      uses: gittools/actions/gitversion/execute@v1
       with:
         useConfigFile: true
         #additionalArguments: /b develop

+ 25 - 17
GitVersion.yml

@@ -1,10 +1,10 @@
 mode: ContinuousDeployment
 tag-prefix: '[vV]'
-continuous-delivery-fallback-tag: pre
+continuous-delivery-fallback-tag: dev
 branches:
   develop:
     mode: ContinuousDeployment
-    tag: pre
+    tag: dev
     regex: develop
     source-branches:
     - main
@@ -12,28 +12,36 @@ branches:
 
   v2_develop:
     mode: ContinuousDeployment
-    tag: pre
+    tag: dev
     regex: ^v2_develop?[/-]
-    is-release-branch: true
     tracks-release-branches: true
-    #is-source-branch-for: ['v2']
+    is-source-branch-for: ['v2_release']
     source-branches: []
 
-  main:
-    tag: rc
-    increment: Patch
-    source-branches:
-    - develop
-    - main
-  feature:
-    tag: useBranchName
-    regex: ^features?[/-]
-    source-branches:
-    - develop
-    - main
+  v2_release:
+    mode: ContinuousDeployment
+    tag: prealpha
+    regex: v2_release
+    is-release-branch: true
+    source-branches: ['v2_develop']
+
   pull-request:
+    mode: ContinuousDeployment
     tag: PullRequest.{BranchName}
     increment: Inherit
+    tag-number-pattern: '[/-](?<number>\d+)'
+    regex: ^(pull|pull\-requests|pr)[/-]
+    source-branches:
+    - develop
+    - main
+    - release
+    - v2_develop
+    - v2_release
+    - feature
+    - support
+    - hotfix
+    pre-release-weight: 30000
+
 ignore:
   sha: []
 

+ 59 - 1
Terminal.Gui/Application/Application.cs

@@ -985,7 +985,7 @@ public static partial class Application
 
         if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ())
         {
-            state.Toplevel.SetNeedsDisplay();
+            state.Toplevel.SetNeedsDisplay ();
             state.Toplevel.Draw ();
             Driver.UpdateScreen ();
 
@@ -1449,4 +1449,62 @@ public static partial class Application
     }
 
     #endregion Toplevel handling
+
+    /// <summary>
+    ///     Gets a string representation of the Application as rendered by <see cref="Driver"/>.
+    /// </summary>
+    /// <returns>A string representation of the Application </returns>
+    public new static string ToString ()
+    {
+        ConsoleDriver driver = Driver;
+
+        if (driver is null)
+        {
+            return string.Empty;
+        }
+
+        return ToString (driver);
+    }
+
+    /// <summary>
+    ///     Gets a string representation of the Application rendered by the provided <see cref="ConsoleDriver"/>.
+    /// </summary>
+    /// <param name="driver">The driver to use to render the contents.</param>
+    /// <returns>A string representation of the Application </returns>
+    public static string ToString (ConsoleDriver driver)
+    {
+        var sb = new StringBuilder ();
+
+        Cell [,] contents = driver.Contents;
+
+        for (var r = 0; r < driver.Rows; r++)
+        {
+            for (var c = 0; c < driver.Cols; c++)
+            {
+                Rune rune = contents [r, c].Rune;
+
+                if (rune.DecodeSurrogatePair (out char [] sp))
+                {
+                    sb.Append (sp);
+                }
+                else
+                {
+                    sb.Append ((char)rune.Value);
+                }
+
+                if (rune.GetColumns () > 1)
+                {
+                    c++;
+                }
+
+                // See Issue #2616
+                //foreach (var combMark in contents [r, c].CombiningMarks) {
+                //	sb.Append ((char)combMark.Value);
+                //}
+            }
+
+            sb.AppendLine ();
+        }
+        return sb.ToString ();
+    }
 }

+ 71 - 95
Terminal.Gui/Drawing/Thickness.cs

@@ -1,4 +1,5 @@
-using System.Text.Json.Serialization;
+using System.Numerics;
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
@@ -13,28 +14,18 @@ namespace Terminal.Gui;
 ///         frame,
 ///         with the thickness widths subtracted.
 ///     </para>
-///     <para>Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.</para>
+///     <para>
+///         Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.
+///     </para>
+///     <para>
+///         Thickness uses <see langword="float"/> intenrally. As a result, there is a potential precision loss for very
+///         large numbers. This is typically not an issue for UI dimensions but could be relevant in other contexts.
+///     </para>
 /// </remarks>
-public class Thickness : IEquatable<Thickness>
+public record struct Thickness
 {
-    /// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
-    [JsonInclude]
-    public int Bottom;
-
-    /// <summary>Gets or sets the width of the left side of the rectangle.</summary>
-    [JsonInclude]
-    public int Left;
-
-    /// <summary>Gets or sets the width of the right side of the rectangle.</summary>
-    [JsonInclude]
-    public int Right;
-
-    /// <summary>Gets or sets the width of the upper side of the rectangle.</summary>
-    [JsonInclude]
-    public int Top;
-
     /// <summary>Initializes a new instance of the <see cref="Thickness"/> class with all widths set to 0.</summary>
-    public Thickness () { }
+    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>
@@ -56,36 +47,24 @@ public class Thickness : IEquatable<Thickness>
         Bottom = bottom;
     }
 
-    // TODO: add operator overloads
-    /// <summary>Gets an empty thickness.</summary>
-    public static Thickness Empty => new (0);
+    private Vector4 _sides;
 
     /// <summary>
-    ///     Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides
-    ///     of the rectangle to half the specified value.
+    ///     Adds the thickness widths of another <see cref="Thickness"/> to the current <see cref="Thickness"/>, returning a
+    ///     new <see cref="Thickness"/>.
     /// </summary>
-    public int Horizontal
-    {
-        get => Left + Right;
-        set => Left = Right = value / 2;
-    }
+    /// <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); }
 
-    /// <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
+    /// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
+    [JsonInclude]
+    public int Bottom
     {
-        get => Top + Bottom;
-        set => Top = Bottom = value / 2;
+        get => (int)_sides.W;
+        set => _sides.W = value;
     }
 
-    // IEquitable
-    /// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
-    /// <param name="other"></param>
-    /// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
-    public bool Equals (Thickness other) { return other is { } && Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; }
-
     /// <summary>
     ///     Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside
     ///     the rectangle described by <see cref="GetInside(Rectangle)"/>.
@@ -100,22 +79,6 @@ public class Thickness : IEquatable<Thickness>
         return outside.Contains (location) && !inside.Contains (location);
     }
 
-    /// <summary>
-    ///     Adds the thickness widths of another <see cref="Thickness"/> to the current <see cref="Thickness"/>, returning a
-    ///     new <see cref="Thickness"/>.
-    /// </summary>
-    /// <param name="other"></param>
-    /// <returns></returns>
-    public Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }
-
-    /// <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); }
-
     /// <summary>Draws the <see cref="Thickness"/> rectangle with an optional diagnostics label.</summary>
     /// <remarks>
     ///     If <see cref="ViewDiagnosticFlags"/> is set to
@@ -240,31 +203,8 @@ public class Thickness : IEquatable<Thickness>
         return GetInside (rect);
     }
 
-    /// <summary>Determines whether the specified object is equal to the current object.</summary>
-    /// <param name="obj">The object to compare with the current object.</param>
-    /// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
-    public override bool Equals (object obj)
-    {
-        //Check for null and compare run-time types.
-        if (obj is null || !GetType ().Equals (obj.GetType ()))
-        {
-            return false;
-        }
-
-        return Equals ((Thickness)obj);
-    }
-
-    /// <inheritdoc/>
-    public override int GetHashCode ()
-    {
-        var hashCode = 1380952125;
-        hashCode = hashCode * -1521134295 + Left.GetHashCode ();
-        hashCode = hashCode * -1521134295 + Right.GetHashCode ();
-        hashCode = hashCode * -1521134295 + Top.GetHashCode ();
-        hashCode = hashCode * -1521134295 + Bottom.GetHashCode ();
-
-        return hashCode;
-    }
+    /// <summary>Gets an empty thickness.</summary>
+    public static Thickness Empty => new (0);
 
     /// <summary>
     ///     Returns a rectangle describing the location and size of the inside area of <paramref name="rect"/> with the
@@ -289,23 +229,59 @@ public class Thickness : IEquatable<Thickness>
         return new (x, y, width, height);
     }
 
-    /// <inheritdoc/>
-    public static bool operator == (Thickness left, Thickness right) { return EqualityComparer<Thickness>.Default.Equals (left, right); }
+    /// <summary>
+    ///     Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides
+    ///     of the rectangle to half the specified value.
+    /// </summary>
+    public int Horizontal
+    {
+        get => Left + Right;
+        set => Left = Right = value / 2;
+    }
 
-    /// <inheritdoc/>
-    public static bool operator != (Thickness left, Thickness right) { return !(left == right); }
+    /// <summary>Gets or sets the width of the left side of the rectangle.</summary>
+    [JsonInclude]
+    public int Left
+    {
+        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); }
+
+    /// <summary>Gets or sets the width of the right side of the rectangle.</summary>
+    [JsonInclude]
+    public int Right
+    {
+        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
+    {
+        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})"; }
 
-    private int validate (int width)
+    /// <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
     {
-        if (width < 0)
-        {
-            throw new ArgumentException ("Thickness widths cannot be negative.");
-        }
-
-        return width;
+        get => Top + Bottom;
+        set => Top = Bottom = value / 2;
     }
 }

+ 113 - 104
Terminal.Gui/View/Adornment/Margin.cs

@@ -1,7 +1,5 @@
 #nullable enable
 
-using System.Drawing;
-
 namespace Terminal.Gui;
 
 /// <summary>The Margin for a <see cref="View"/>.</summary>
@@ -28,58 +26,68 @@ public class Margin : Adornment
         CanFocus = false;
     }
 
-    private void Margin_LayoutStarted (object? sender, LayoutEventArgs e)
+    private bool _pressed;
+
+    private ShadowView? _bottomShadow;
+    private ShadowView? _rightShadow;
+
+    /// <inheritdoc/>
+    public override void BeginInit ()
     {
-        // Adjust the shadow such that it is drawn aligned with the Border
-        if (ShadowStyle != Gui.ShadowStyle.None && _rightShadow is { } && _bottomShadow is { })
+        base.BeginInit ();
+
+        if (Parent is null)
         {
-            _rightShadow.Y = Parent.Border.Thickness.Top > 0 ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.ShowTitle ? 1 : 0) : 1;
-            _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1;
+            return;
         }
+
+        ShadowStyle = base.ShadowStyle;
+
+        Add (
+             _rightShadow = new()
+             {
+                 X = Pos.AnchorEnd (1),
+                 Y = 0,
+                 Width = 1,
+                 Height = Dim.Fill (),
+                 ShadowStyle = ShadowStyle,
+                 Orientation = Orientation.Vertical
+             },
+             _bottomShadow = new()
+             {
+                 X = 0,
+                 Y = Pos.AnchorEnd (1),
+                 Width = Dim.Fill (),
+                 Height = 1,
+                 ShadowStyle = ShadowStyle,
+                 Orientation = Orientation.Horizontal
+             }
+            );
     }
 
-    private bool _pressed;
-    private void Margin_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
+    /// <summary>
+    ///     The color scheme for the Margin. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>'s
+    ///     <see cref="View.SuperView"/> scheme. color scheme.
+    /// </summary>
+    public override ColorScheme ColorScheme
     {
-        if (ShadowStyle != Gui.ShadowStyle.None)
+        get
         {
-            if (_pressed && e.CurrentValue == HighlightStyle.None)
+            if (base.ColorScheme is { })
             {
-                Thickness = new (Thickness.Left - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom);
-
-                if (_rightShadow is { })
-                {
-                    _rightShadow.Visible = true;
-                }
-
-                if (_bottomShadow is { })
-                {
-                    _bottomShadow.Visible = true;
-                }
-
-                _pressed = false;
-                return;
+                return base.ColorScheme;
             }
 
-            if (!_pressed && (e.CurrentValue.HasFlag (HighlightStyle.Pressed) /*|| e.HighlightStyle.HasFlag (HighlightStyle.PressedOutside)*/))
-            {
-                Thickness = new (Thickness.Left + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom);
-                _pressed = true;
-                if (_rightShadow is { })
-                {
-                    _rightShadow.Visible = false;
-                }
-
-                if (_bottomShadow is { })
-                {
-                    _bottomShadow.Visible = false;
-                }
-            }
+            return (Parent?.SuperView?.ColorScheme ?? Colors.ColorSchemes ["TopLevel"])!;
+        }
+        set
+        {
+            base.ColorScheme = value;
+            Parent?.SetNeedsDisplay ();
         }
-
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override void OnDrawContent (Rectangle viewport)
     {
         Rectangle screen = ViewportToScreen (viewport);
@@ -87,12 +95,12 @@ public class Margin : Adornment
 
         Driver?.SetAttribute (normalAttr);
 
-
         // This just draws/clears the thickness, not the insides.
         if (ShadowStyle != ShadowStyle.None)
         {
             screen = Rectangle.Inflate (screen, -1, -1);
         }
+
         Thickness.Draw (screen, ToString ());
 
         if (Subviews.Count > 0)
@@ -105,12 +113,14 @@ public class Margin : Adornment
                                                                         view => view.Visible
                                                                                 && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
                                                                        );
+
                 foreach (View view in subviewsNeedingDraw)
                 {
                     if (view.LayoutNeeded)
                     {
                         view.LayoutSubviews ();
                     }
+
                     view.Draw ();
                 }
             }
@@ -118,39 +128,7 @@ public class Margin : Adornment
     }
 
     /// <summary>
-    ///     The color scheme for the Margin. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>'s
-    ///     <see cref="View.SuperView"/> scheme. color scheme.
-    /// </summary>
-    public override ColorScheme ColorScheme
-    {
-        get
-        {
-            if (base.ColorScheme is { })
-            {
-                return base.ColorScheme;
-            }
-
-            return (Parent?.SuperView?.ColorScheme ?? Colors.ColorSchemes ["TopLevel"])!;
-        }
-        set
-        {
-            base.ColorScheme = value;
-            Parent?.SetNeedsDisplay ();
-        }
-    }
-
-    /// <inheritdoc />
-    public override ShadowStyle ShadowStyle
-    {
-        get => base.ShadowStyle;
-        set
-        {
-            base.ShadowStyle = SetShadow (value);
-        }
-    }
-
-    /// <summary>
-    ///    Sets whether the Margin includes a shadow effect. The shadow is drawn on the right and bottom sides of the
+    ///     Sets whether the Margin includes a shadow effect. The shadow is drawn on the right and bottom sides of the
     ///     Margin.
     /// </summary>
     public ShadowStyle SetShadow (ShadowStyle style)
@@ -181,42 +159,73 @@ public class Margin : Adornment
         {
             _bottomShadow.ShadowStyle = style;
         }
+
         return style;
     }
 
-    private ShadowView? _bottomShadow;
-    private ShadowView? _rightShadow;
-
     /// <inheritdoc/>
-    public override void BeginInit ()
+    public override ShadowStyle ShadowStyle
     {
-        base.BeginInit ();
+        get => base.ShadowStyle;
+        set => base.ShadowStyle = SetShadow (value);
+    }
 
-        if (Parent is null)
+    private void Margin_Highlight (object? sender, CancelEventArgs<HighlightStyle> e)
+    {
+        if (ShadowStyle != ShadowStyle.None)
         {
-            return;
+            if (_pressed && e.NewValue == HighlightStyle.None)
+            {
+                // 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.
+                Thickness = new (Thickness.Left - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom);
+
+                if (_rightShadow is { })
+                {
+                    _rightShadow.Visible = true;
+                }
+
+                if (_bottomShadow is { })
+                {
+                    _bottomShadow.Visible = true;
+                }
+
+                _pressed = false;
+
+                return;
+            }
+
+            if (!_pressed && e.NewValue.HasFlag (HighlightStyle.Pressed))
+            {
+                // 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.
+                Thickness = new (Thickness.Left + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom);
+                _pressed = true;
+
+                if (_rightShadow is { })
+                {
+                    _rightShadow.Visible = false;
+                }
+
+                if (_bottomShadow is { })
+                {
+                    _bottomShadow.Visible = false;
+                }
+            }
         }
+    }
 
-        ShadowStyle = base.ShadowStyle;
-        Add (
-             _rightShadow = new ShadowView
-             {
-                 X = Pos.AnchorEnd (1),
-                 Y = 0,
-                 Width = 1,
-                 Height = Dim.Fill (),
-                 ShadowStyle = ShadowStyle,
-                 Orientation = Orientation.Vertical
-             },
-             _bottomShadow = new ShadowView
-             {
-                 X = 0,
-                 Y = Pos.AnchorEnd (1),
-                 Width = Dim.Fill (),
-                 Height = 1,
-                 ShadowStyle = ShadowStyle,
-                 Orientation = Orientation.Horizontal
-             }
-            );
+    private void Margin_LayoutStarted (object? sender, LayoutEventArgs e)
+    {
+        // Adjust the shadow such that it is drawn aligned with the Border
+        if (ShadowStyle != ShadowStyle.None && _rightShadow is { } && _bottomShadow is { })
+        {
+            _rightShadow.Y = Parent.Border.Thickness.Top > 0
+                                 ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.ShowTitle ? 1 : 0)
+                                 : 1;
+            _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1;
+        }
     }
-}
+}

+ 99 - 263
UnitTests/TestHelpers.cs

@@ -1,32 +1,28 @@
-using System.Collections;
-using System.Diagnostics;
+using System.Diagnostics;
 using System.Globalization;
 using System.Reflection;
 using System.Text;
 using System.Text.RegularExpressions;
-using UICatalog;
 using Xunit.Abstractions;
 using Xunit.Sdk;
 
 namespace Terminal.Gui;
 
-// This class enables test functions annotated with the [AutoInitShutdown] attribute to 
-// automatically call Application.Init at start of the test and Application.Shutdown after the
-// test exits. 
-// 
-// This is necessary because a) Application is a singleton and Init/Shutdown must be called
-// as a pair, and b) all unit test functions should be atomic..
+/// <summary>
+///     This class enables test functions annotated with the [AutoInitShutdown] attribute to
+///     automatically call Application.Init at start of the test and Application.Shutdown after the
+///     test exits.
+///     This is necessary because a) Application is a singleton and Init/Shutdown must be called
+///     as a pair, and b) all unit test functions should be atomic..
+/// </summary>
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
 public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
 {
-    private readonly Type _driverType;
-
     /// <summary>
     ///     Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and Application.Shutdown
     ///     are automatically called Before/After a test runs.
     /// </summary>
     /// <param name="autoInit">If true, Application.Init will be called Before the test runs.</param>
-    /// <param name="autoShutdown">If true, Application.Shutdown will be called After the test runs.</param>
     /// <param name="consoleDriverType">
     ///     Determines which ConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver)
     ///     will be used when Application.Init is called. If null FakeDriver will be used. Only valid if
@@ -65,7 +61,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
         ConfigurationManager.Locations = configLocation;
     }
 
-    private bool AutoInit { get; }
+    private readonly Type _driverType;
 
     public override void After (MethodInfo methodUnderTest)
     {
@@ -102,6 +98,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
             ConfigurationManager.Reset ();
 
 #if DEBUG_IDISPOSABLE
+
             // Clear out any lingering Responder instances from previous tests
             if (Responder.Instances.Count == 0)
             {
@@ -115,6 +112,8 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
             Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType));
         }
     }
+
+    private bool AutoInit { get; }
 }
 
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
@@ -178,8 +177,8 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
 public class TestDateAttribute : BeforeAfterTestAttribute
 {
-    private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture;
     public TestDateAttribute () { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; }
+    private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture;
 
     public override void After (MethodInfo methodUnderTest)
     {
@@ -238,12 +237,12 @@ internal partial class TestHelpers
                 switch (match.Count)
                 {
                     case 0:
-                        throw new Exception (
-                                             $"{DriverContentsToString (driver)}\n"
-                                             + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n"
-                                             + $"  Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n"
-                                             + $"  But Was: <not found>"
-                                            );
+                        throw new (
+                                   $"{Application.ToString (driver)}\n"
+                                   + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n"
+                                   + $"  Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n"
+                                   + $"  But Was: <not found>"
+                                  );
                     case > 1:
                         throw new ArgumentException (
                                                      $"Bad value for expectedColors, {match.Count} Attributes had the same Value"
@@ -255,12 +254,12 @@ internal partial class TestHelpers
 
                 if (colorUsed != userExpected)
                 {
-                    throw new Exception (
-                                         $"{DriverContentsToString (driver)}\n"
-                                         + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n"
-                                         + $"  Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n"
-                                         + $"  But Was:   {colorUsed} ({val})\n"
-                                        );
+                    throw new (
+                               $"{Application.ToString (driver)}\n"
+                               + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n"
+                               + $"  Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n"
+                               + $"  But Was:   {colorUsed} ({val})\n"
+                              );
                 }
             }
 
@@ -282,7 +281,7 @@ internal partial class TestHelpers
     )
     {
 #pragma warning restore xUnit1013 // Public method should be marked as test
-        string actualLook = DriverContentsToString (driver);
+        var actualLook = Application.ToString (driver ?? Application.Driver);
 
         if (string.Equals (expectedLook, actualLook))
         {
@@ -314,8 +313,7 @@ internal partial class TestHelpers
     }
 
     /// <summary>
-    ///     Asserts that the driver contents are equal to the expected look, and that the cursor is at the expected
-    ///     position.
+    ///     Asserts that the driver contents are equal to the provided string.
     /// </summary>
     /// <param name="expectedLook"></param>
     /// <param name="output"></param>
@@ -337,7 +335,6 @@ internal partial class TestHelpers
 
         Cell [,] contents = driver.Contents;
 
-
         for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++)
         {
             List<Rune> runes = [];
@@ -353,7 +350,7 @@ internal partial class TestHelpers
                         x = colIndex;
                         y = rowIndex;
 
-                        for (int i = 0; i < colIndex; i++)
+                        for (var i = 0; i < colIndex; i++)
                         {
                             runes.InsertRange (i, [SpaceRune]);
                         }
@@ -433,7 +430,7 @@ internal partial class TestHelpers
 
         if (string.Equals (expectedLook, actualLook))
         {
-            return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+            return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
         }
 
         // standardize line endings for the comparison
@@ -453,7 +450,7 @@ internal partial class TestHelpers
 
         Assert.Equal (expectedLook, actualLook);
 
-        return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+        return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
     }
 
 #pragma warning disable xUnit1013 // Public method should be marked as test
@@ -483,95 +480,86 @@ internal partial class TestHelpers
     }
 #pragma warning restore xUnit1013 // Public method should be marked as test
 
-    public static string DriverContentsToString (ConsoleDriver driver = null)
+    public static View CreateViewFromType (Type type, ConstructorInfo ctor)
     {
-        var sb = new StringBuilder ();
-        driver ??= Application.Driver;
-
-        Cell [,] contents = driver.Contents;
+        View viewType = null;
 
-        for (var r = 0; r < driver.Rows; r++)
+        if (type.IsGenericType && type.IsTypeDefinition)
         {
-            for (var c = 0; c < driver.Cols; c++)
+            List<Type> gTypes = new ();
+
+            foreach (Type args in type.GetGenericArguments ())
             {
-                Rune rune = contents [r, c].Rune;
+                gTypes.Add (typeof (object));
+            }
+
+            type = type.MakeGenericType (gTypes.ToArray ());
+
+            Assert.IsType (type, (View)Activator.CreateInstance (type));
+        }
+        else
+        {
+            ParameterInfo [] paramsInfo = ctor.GetParameters ();
+            Type paramType;
+            List<object> pTypes = new ();
 
-                if (rune.DecodeSurrogatePair (out char [] sp))
+            if (type.IsGenericType)
+            {
+                foreach (Type args in type.GetGenericArguments ())
                 {
-                    sb.Append (sp);
+                    paramType = args.GetType ();
+
+                    if (args.Name == "T")
+                    {
+                        pTypes.Add (typeof (object));
+                    }
+                    else
+                    {
+                        AddArguments (paramType, pTypes);
+                    }
                 }
-                else
+            }
+
+            foreach (ParameterInfo p in paramsInfo)
+            {
+                paramType = p.ParameterType;
+
+                if (p.HasDefaultValue)
                 {
-                    sb.Append ((char)rune.Value);
+                    pTypes.Add (p.DefaultValue);
                 }
-
-                if (rune.GetColumns () > 1)
+                else
                 {
-                    c++;
+                    AddArguments (paramType, pTypes);
                 }
-
-                // See Issue #2616
-                //foreach (var combMark in contents [r, c].CombiningMarks) {
-                //	sb.Append ((char)combMark.Value);
-                //}
             }
 
-            sb.AppendLine ();
+            if (type.IsGenericType && !type.IsTypeDefinition)
+            {
+                viewType = (View)Activator.CreateInstance (type);
+                Assert.IsType (type, viewType);
+            }
+            else
+            {
+                viewType = (View)ctor.Invoke (pTypes.ToArray ());
+                Assert.IsType (type, viewType);
+            }
         }
 
-        return sb.ToString ();
+        return viewType;
     }
 
-    //// TODO: Update all tests that use GetALlViews to use GetAllViewsTheoryData instead
-    ///// <summary>Gets a list of instances of all classes derived from View.</summary>
-    ///// <returns>List of View objects</returns>
-    //public static List<View> GetAllViews ()
-    //{
-    //    return typeof (View).Assembly.GetTypes ()
-    //                        .Where (
-    //                                type => type.IsClass
-    //                                        && !type.IsAbstract
-    //                                        && type.IsPublic
-    //                                        && type.IsSubclassOf (typeof (View))
-    //                               )
-    //                        .Select (type => CreateView (type, type.GetConstructor (Array.Empty<Type> ())))
-    //                        .ToList ();
-    //}
-
-    //public class AllViewsData : IEnumerable<object []>
-    //{
-    //    private Lazy<List<object []>> data;
-
-    //    public AllViewsData ()
-    //    {
-    //        data = new Lazy<List<object []>> (GetTestData);
-    //    }
-
-    //    public IEnumerator<object []> GetEnumerator ()
-    //    {
-    //        return data.Value.GetEnumerator ();
-    //    }
-
-    //    IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
-
-    //    private List<object []> GetTestData ()
-    //    {
-    //        var viewTypes = typeof (View).Assembly
-    //                                     .GetTypes ()
-    //                                     .Where (type => type.IsClass && !type.IsAbstract && type.IsPublic && type.IsSubclassOf (typeof (View)));
-
-    //        var testData = new List<object []> ();
-
-    //        foreach (var type in viewTypes)
-    //        {
-    //            var view = CreateView (type, type.GetConstructor (Array.Empty<Type> ()));
-    //            testData.Add (new object [] { view, type.Name });
-    //        }
-
-    //        return testData;
-    //    }
-    //}
-
+    public static List<Type> GetAllViewClasses ()
+    {
+        return typeof (View).Assembly.GetTypes ()
+                            .Where (
+                                    myType => myType.IsClass
+                                              && !myType.IsAbstract
+                                              && myType.IsPublic
+                                              && myType.IsSubclassOf (typeof (View))
+                                   )
+                            .ToList ();
+    }
 
     /// <summary>
     ///     Verifies the console used all the <paramref name="expectedColors"/> when rendering. If one or more of the
@@ -620,7 +608,7 @@ internal partial class TestHelpers
         sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString ())));
         sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString ())));
 
-        throw new Exception (sb.ToString ());
+        throw new (sb.ToString ());
     }
 
     private static void AddArguments (Type paramType, List<object> pTypes)
@@ -674,156 +662,6 @@ internal partial class TestHelpers
         }
     }
 
-    public static View CreateView (Type type, ConstructorInfo ctor)
-    {
-        View view = null;
-
-        if (type.IsGenericType && type.IsTypeDefinition)
-        {
-            List<Type> gTypes = new ();
-
-            foreach (Type args in type.GetGenericArguments ())
-            {
-                gTypes.Add (typeof (object));
-            }
-
-            type = type.MakeGenericType (gTypes.ToArray ());
-
-            Assert.IsType (type, (View)Activator.CreateInstance (type));
-        }
-        else
-        {
-            ParameterInfo [] paramsInfo = ctor.GetParameters ();
-            Type paramType;
-            List<object> pTypes = new ();
-
-            if (type.IsGenericType)
-            {
-                foreach (Type args in type.GetGenericArguments ())
-                {
-                    paramType = args.GetType ();
-
-                    if (args.Name == "T")
-                    {
-                        pTypes.Add (typeof (object));
-                    }
-                    else
-                    {
-                        AddArguments (paramType, pTypes);
-                    }
-                }
-            }
-
-            foreach (ParameterInfo p in paramsInfo)
-            {
-                paramType = p.ParameterType;
-
-                if (p.HasDefaultValue)
-                {
-                    pTypes.Add (p.DefaultValue);
-                }
-                else
-                {
-                    AddArguments (paramType, pTypes);
-                }
-            }
-
-            if (type.IsGenericType && !type.IsTypeDefinition)
-            {
-                view = (View)Activator.CreateInstance (type);
-                Assert.IsType (type, view);
-            }
-            else
-            {
-                view = (View)ctor.Invoke (pTypes.ToArray ());
-                Assert.IsType (type, view);
-            }
-        }
-
-        return view;
-    }
-
-    public static List<Type> GetAllViewClasses ()
-    {
-        return typeof (View).Assembly.GetTypes ()
-                            .Where (
-                                    myType => myType.IsClass
-                                              && !myType.IsAbstract
-                                              && myType.IsPublic
-                                              && myType.IsSubclassOf (typeof (View))
-                                   )
-                            .ToList ();
-    }
-
-    public static View CreateViewFromType (Type type, ConstructorInfo ctor)
-    {
-        View viewType = null;
-
-        if (type.IsGenericType && type.IsTypeDefinition)
-        {
-            List<Type> gTypes = new ();
-
-            foreach (Type args in type.GetGenericArguments ())
-            {
-                gTypes.Add (typeof (object));
-            }
-
-            type = type.MakeGenericType (gTypes.ToArray ());
-
-            Assert.IsType (type, (View)Activator.CreateInstance (type));
-        }
-        else
-        {
-            ParameterInfo [] paramsInfo = ctor.GetParameters ();
-            Type paramType;
-            List<object> pTypes = new ();
-
-            if (type.IsGenericType)
-            {
-                foreach (Type args in type.GetGenericArguments ())
-                {
-                    paramType = args.GetType ();
-
-                    if (args.Name == "T")
-                    {
-                        pTypes.Add (typeof (object));
-                    }
-                    else
-                    {
-                        AddArguments (paramType, pTypes);
-                    }
-                }
-            }
-
-            foreach (ParameterInfo p in paramsInfo)
-            {
-                paramType = p.ParameterType;
-
-                if (p.HasDefaultValue)
-                {
-                    pTypes.Add (p.DefaultValue);
-                }
-                else
-                {
-                    AddArguments (paramType, pTypes);
-                }
-            }
-
-            if (type.IsGenericType && !type.IsTypeDefinition)
-            {
-                viewType = (View)Activator.CreateInstance (type);
-                Assert.IsType (type, viewType);
-            }
-            else
-            {
-                viewType = (View)ctor.Invoke (pTypes.ToArray ());
-                Assert.IsType (type, viewType);
-            }
-        }
-
-        return viewType;
-    }
-
     [GeneratedRegex ("^\\s+", RegexOptions.Multiline)]
     private static partial Regex LeadingWhitespaceRegEx ();
 
@@ -832,11 +670,11 @@ internal partial class TestHelpers
         string replaced = toReplace;
 
         replaced = Environment.NewLine.Length switch
-        {
-            2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine),
-            1 => replaced.Replace ("\r\n", Environment.NewLine),
-            var _ => replaced
-        };
+                   {
+                       2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine),
+                       1 => replaced.Replace ("\r\n", Environment.NewLine),
+                       var _ => replaced
+                   };
 
         return replaced;
     }
@@ -863,6 +701,4 @@ public class TestsAllViews
 
         return Activator.CreateInstance (type) as View;
     }
-
 }
-

+ 25 - 0
UnitTests/UnitTests.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests.csproj", "{A29633F2-B26E-48B2-997A-1733286E3C13}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{A29633F2-B26E-48B2-997A-1733286E3C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A29633F2-B26E-48B2-997A-1733286E3C13}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A29633F2-B26E-48B2-997A-1733286E3C13}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A29633F2-B26E-48B2-997A-1733286E3C13}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {86ED8EAD-F1D5-4F95-A0E6-6D73DFC8442F}
+	EndGlobalSection
+EndGlobal

+ 5 - 5
UnitTests/View/Adornment/BorderTests.cs

@@ -18,7 +18,7 @@ public class BorderTests (ITestOutputHelper output)
         view.Border.Thickness = new (0, 1, 0, 0);
         view.Border.LineStyle = LineStyle.Single;
 
-        view.ColorScheme = new()
+        view.ColorScheme = new ()
         {
             Normal = new (Color.Red, Color.Green),
             Focus = new (Color.Green, Color.Red)
@@ -53,7 +53,7 @@ public class BorderTests (ITestOutputHelper output)
         view.Border.Thickness = new (0, 1, 0, 0);
         view.Border.LineStyle = LineStyle.Single;
 
-        view.ColorScheme = new()
+        view.ColorScheme = new ()
         {
             Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red)
         };
@@ -90,7 +90,7 @@ public class BorderTests (ITestOutputHelper output)
         {
             Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double
         };
-        win.Border.Thickness.Top = 4;
+        win.Border.Thickness = win.Border.Thickness with { Top = 4 };
 
         RunState rs = Application.Begin (win);
         var firstIteration = false;
@@ -224,7 +224,7 @@ public class BorderTests (ITestOutputHelper output)
         {
             Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double
         };
-        win.Border.Thickness.Top = 3;
+        win.Border.Thickness = win.Border.Thickness with { Top = 3 };
 
         RunState rs = Application.Begin (win);
         var firstIteration = false;
@@ -358,7 +358,7 @@ public class BorderTests (ITestOutputHelper output)
         {
             Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double
         };
-        win.Border.Thickness.Top = 2;
+        win.Border.Thickness = win.Border.Thickness with { Top = 2 };
 
         RunState rs = Application.Begin (win);
         var firstIteration = false;

+ 3 - 3
UnitTests/View/Adornment/MarginTests.cs

@@ -10,13 +10,13 @@ public class MarginTests (ITestOutputHelper output)
     {
         ((FakeDriver)Application.Driver).SetBufferSize (5, 5);
         var view = new View { Height = 3, Width = 3 };
-        view.Margin.Thickness = new Thickness (1);
+        view.Margin.Thickness = new (1);
 
         var superView = new View ();
 
-        superView.ColorScheme = new ColorScheme
+        superView.ColorScheme = new()
         {
-            Normal = new Attribute (Color.Red, Color.Green), Focus = new Attribute (Color.Green, Color.Red)
+            Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red)
         };
 
         superView.Add (view);

+ 126 - 0
UnitTests/View/Adornment/ShadowStyletests.cs

@@ -27,6 +27,132 @@ public class ShadowStyleTests (ITestOutputHelper _output)
         view.Dispose ();
     }
 
+    [Theory]
+    [InlineData (ShadowStyle.None, 0, 0, 0, 0)]
+    [InlineData (ShadowStyle.Opaque, 1, 0, 0, 1)]
+    [InlineData (ShadowStyle.Transparent, 1, 0, 0, 1)]
+    public void ShadowStyle_Button1Pressed_Causes_Movement (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",
+            HighlightStyle = HighlightStyle.Pressed,
+            ShadowStyle = style,
+            CanFocus = true
+        };
+
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        Thickness origThickness = view.Margin.Thickness;
+        view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (0, 0) });
+        Assert.Equal (new (expectedLeft, expectedTop, expectedRight, expectedBottom), view.Margin.Thickness);
+
+        view.NewMouseEvent (new () { Flags = MouseFlags.Button1Released, Position = new (0, 0) });
+        Assert.Equal (origThickness, view.Margin.Thickness);
+    }
+
+    [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",
+            HighlightStyle = HighlightStyle.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,
+                    """
+                    011
+                    111
+                    111
+                    """)]
+    [InlineData (
+                    ShadowStyle.Transparent,
+                    """
+                    011
+                    131
+                    111
+                    """)]
+    [InlineData (
+                    ShadowStyle.Opaque,
+                    """
+                    011
+                    121
+                    111
+                    """)]
+    [SetupFakeDriver]
+    public void ShadowView_Colors (ShadowStyle style, string expectedAttrs)
+    {
+        Color fg = Color.Red;
+        Color bg = Color.Green;
+
+        // 0 - View
+        // 1 - SuperView
+        // 2 - Opaque - fg is Black, bg is SuperView.Bg
+        // 3 - Transparent - fg is darker fg, bg is darker bg
+        Attribute [] attributes =
+        {
+            Attribute.Default,
+            new (fg, bg),
+            new (Color.Black, bg),
+            new (fg.GetDarkerColor (), bg.GetDarkerColor ())
+        };
+
+        var superView = new View
+        {
+            Height = 3,
+            Width = 3,
+            Text = "012ABC!@#",
+            ColorScheme = new (new Attribute (fg, bg))
+        };
+        superView.TextFormatter.WordWrap = true;
+
+        View view = new ()
+        {
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            Text = " ",
+            ShadowStyle = style,
+            ColorScheme = new (Attribute.Default)
+        };
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        superView.Draw ();
+        TestHelpers.AssertDriverAttributesAre (expectedAttrs, Application.Driver, attributes);
+    }
+
     [Theory]
     [InlineData (ShadowStyle.None, 3)]
     [InlineData (ShadowStyle.Opaque, 4)]

+ 0 - 1
UnitTests/View/MouseTests.cs

@@ -647,5 +647,4 @@ public class MouseTests (ITestOutputHelper output) : TestsAllViews
             }
         }
     }
-
 }