diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index a15b49fd4..b2f4b0313 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -123,7 +123,6 @@ public record struct Thickness driver?.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar); } - // Draw the Left side // Draw the Left side if (Left > 0) { diff --git a/Terminal.Gui/ViewBase/Adornment/Margin.cs b/Terminal.Gui/ViewBase/Adornment/Margin.cs index 998478b15..5c73bc6da 100644 --- a/Terminal.Gui/ViewBase/Adornment/Margin.cs +++ b/Terminal.Gui/ViewBase/Adornment/Margin.cs @@ -96,7 +96,7 @@ public class Margin : Adornment margin.ClearCachedClip (); } - foreach (View subview in view.SubViews) + foreach (View subview in view.SubViews.OrderBy (v => v.HasFocus && v.ShadowStyle != ShadowStyle.None).Reverse ()) { stack.Push (subview); } diff --git a/Tests/UnitTests/View/Adornment/AdornmentTests.cs b/Tests/UnitTests/View/Adornment/AdornmentTests.cs index 6c19e7c70..123faf94e 100644 --- a/Tests/UnitTests/View/Adornment/AdornmentTests.cs +++ b/Tests/UnitTests/View/Adornment/AdornmentTests.cs @@ -63,7 +63,7 @@ public class AdornmentTests (ITestOutputHelper output) Assert.Equal (6, view.Width); Assert.Equal (3, view.Height); - view.SetClipToScreen (); + view.App.LayoutAndDraw (true); view.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( diff --git a/Tests/UnitTestsParallelizable/ViewBase/Adornment/BorderArrangementTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Adornment/BorderArrangementTests.cs index 71ad1202c..ed4b34968 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Adornment/BorderArrangementTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Adornment/BorderArrangementTests.cs @@ -15,6 +15,9 @@ public class BorderArrangementTests (ITestOutputHelper output) app.Init ("fake"); app.Driver?.SetScreenSize (6, 5); + + // Using a replacement char to make sure wide glyphs are handled correctly + // in the shadow area, to not confusing with a space char. app.Driver?.GetOutputBuffer ().SetWideGlyphReplacement (Rune.ReplacementChar); Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () }; @@ -101,9 +104,24 @@ public class BorderArrangementTests (ITestOutputHelper output) app.Init ("fake"); app.Driver?.SetScreenSize (8, 7); + + // Using a replacement char to make sure wide glyphs are handled correctly + // in the shadow area, to not confusing with a space char. app.Driver?.GetOutputBuffer ().SetWideGlyphReplacement (Rune.ReplacementChar); + // Don't remove this array even if it seems unused, it is used to map the attributes indexes in the DriverAssert + // Otherwise the test won't detect issues with attributes not visibly by the naked eye + Attribute [] attributes = + [ + new (ColorName16.Blue, ColorName16.BrightBlue, TextStyle.None), + new (ColorName16.BrightBlue, ColorName16.Blue, TextStyle.None), + new (ColorName16.Green, ColorName16.BrightGreen, TextStyle.None), + new (ColorName16.Magenta, ColorName16.BrightMagenta, TextStyle.None), + new (ColorName16.BrightMagenta, ColorName16.Magenta, TextStyle.None) + ]; + Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () }; + superview.SetScheme (new () { Normal = attributes [0], Focus = attributes [1] }); superview.Text = """ ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ @@ -115,17 +133,22 @@ public class BorderArrangementTests (ITestOutputHelper output) ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ """; - View view = new () + View view = new () { X = 6, Width = 2, Height = 1, Text = "๐Ÿฆฎ" }; + view.SetScheme (new () { Normal = attributes [2] }); + + View view2 = 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); + view2.Border!.Thickness = new (1); + view2.Border.Add (new View { Height = Dim.Auto (), Width = Dim.Auto (), Text = "Hi" }); + view2.SetScheme (new () { Normal = attributes [3], HotNormal = attributes [4] }); + + superview.Add (view, view2); app.Begin (superview); - Assert.Equal ("Absolute(2)", view.X.ToString ()); + Assert.Equal ("Absolute(2)", view2.X.ToString ()); DriverAssert.AssertDriverContentsAre ( """ @@ -140,6 +163,20 @@ public class BorderArrangementTests (ITestOutputHelper output) output, app.Driver); + DriverAssert.AssertDriverAttributesAre ( + """ + 11333333 + 11333333 + 11333333 + 11333333 + 11333333 + 11333333 + 11111111 + """, + output, + app.Driver, + attributes); + Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.F5.WithCtrl)); app.LayoutAndDraw (); @@ -156,8 +193,22 @@ public class BorderArrangementTests (ITestOutputHelper output) output, app.Driver); + DriverAssert.AssertDriverAttributesAre ( + """ + 11433333 + 11333333 + 11333333 + 11333333 + 11333333 + 11333333 + 11111111 + """, + output, + app.Driver, + attributes); + Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.CursorLeft)); - Assert.Equal ("Absolute(1)", view.X.ToString ()); + Assert.Equal ("Absolute(1)", view2.X.ToString ()); app.LayoutAndDraw (); DriverAssert.AssertDriverContentsAre ( @@ -173,13 +224,27 @@ public class BorderArrangementTests (ITestOutputHelper output) output, app.Driver); + DriverAssert.AssertDriverAttributesAre ( + """ + 14333332 + 13333330 + 13333330 + 13333330 + 13333330 + 13333330 + 11111111 + """, + output, + app.Driver, + attributes); + Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.CursorLeft)); - Assert.Equal ("Absolute(0)", view.X.ToString ()); + Assert.Equal ("Absolute(0)", view2.X.ToString ()); app.LayoutAndDraw (); DriverAssert.AssertDriverContentsAre ( """ - โ—Ši ๐ŸŽ + โ—Ši ๐Ÿฆฎ ๐ŸŽ ๐ŸŽ ๐ŸŽ @@ -189,5 +254,19 @@ public class BorderArrangementTests (ITestOutputHelper output) """, output, app.Driver); + + DriverAssert.AssertDriverAttributesAre ( + """ + 43333322 + 33333311 + 33333311 + 33333311 + 33333311 + 33333311 + 11111111 + """, + output, + app.Driver, + attributes); } } \ No newline at end of file diff --git a/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs index 281fa1b2f..f3691ebb0 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs @@ -80,20 +80,37 @@ public class ShadowTests (ITestOutputHelper output) } [Theory] + [InlineData (ShadowStyle.None)] [InlineData (ShadowStyle.Opaque)] [InlineData (ShadowStyle.Transparent)] - public void ShadowWidth_ShadowHeight_Defaults_To_One (ShadowStyle style) + public void ShadowWidth_ShadowHeight_Defaults (ShadowStyle style) { View view = new () { ShadowStyle = style }; - Assert.Equal (new (1, 1), view.Margin!.ShadowSize); + if (view.ShadowStyle == ShadowStyle.None) + { + Assert.Equal (new (0, 0), view.Margin!.ShadowSize); + } + else + { + Assert.Equal (new (1, 1), view.Margin!.ShadowSize); + } + } + + [Fact] + public void ShadowStyle_Opaque_Margin_ShadowWidth_ShadowHeight_Cannot_Be_Set_Different_Of_One () + { + View view = new () { ShadowStyle = ShadowStyle.Opaque }; + view.Margin!.ShadowSize = new (3, 4); + Assert.Equal (1, view.Margin.ShadowSize.Width); + Assert.Equal (1, view.Margin.ShadowSize.Height); } [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) + public void Margin_ShadowWidth_ShadowHeight_Cannot_Be_Set_Less_Than_Zero (ShadowStyle style, int expectedLength) { View view = new () { ShadowStyle = style }; view.Margin!.ShadowSize = new (-1, -1); @@ -119,6 +136,58 @@ public class ShadowTests (ITestOutputHelper output) Assert.Equal (new (0, 0, 1, 1), view.Margin.Thickness); } + [Theory] + [InlineData (ShadowStyle.None, 2, 1, 3, 0, 0, 0)] + [InlineData (ShadowStyle.Opaque, 1, 1, 1, 1, 1, 1)] + [InlineData (ShadowStyle.Transparent, 2, 1, 3, 2, 2, 3)] + public void Changing_ShadowWidth_ShadowHeight_Correctly_Set_Thickness ( + ShadowStyle style, + int expectedLength1, + int expectedLength2, + int expectedLength3, + int expectedThickness1, + int expectedThickness2, + int expectedThickness3 + ) + { + View view = new () { ShadowStyle = style }; + view.Margin!.ShadowSize = new (2, 2); + Assert.Equal (expectedLength1, view.Margin!.ShadowSize.Width); + Assert.Equal (expectedLength1, view.Margin.ShadowSize.Height); + Assert.Equal (new (0, 0, expectedThickness1, expectedThickness1), view.Margin.Thickness); + + view.Margin!.ShadowSize = new (1, 1); + Assert.Equal (expectedLength2, view.Margin!.ShadowSize.Width); + Assert.Equal (expectedLength2, view.Margin.ShadowSize.Height); + Assert.Equal (new (0, 0, expectedThickness2, expectedThickness2), view.Margin.Thickness); + + view.Margin!.ShadowSize = new (3, 3); + Assert.Equal (expectedLength3, view.Margin!.ShadowSize.Width); + Assert.Equal (expectedLength3, view.Margin.ShadowSize.Height); + Assert.Equal (new (0, 0, expectedThickness3, expectedThickness3), view.Margin.Thickness); + + view.ShadowStyle = ShadowStyle.None; + Assert.Equal (expectedLength3, view.Margin!.ShadowSize.Width); + Assert.Equal (expectedLength3, view.Margin.ShadowSize.Height); + Assert.Equal (new (0, 0, 0, 0), view.Margin.Thickness); + } + + [Theory] + [InlineData (ShadowStyle.None, 0, 1)] + [InlineData (ShadowStyle.Opaque, 1, 1)] + [InlineData (ShadowStyle.Transparent, 1, 1)] + public void Changing_Thickness_Correctly_Set_Thickness (ShadowStyle style, int expectedLength, int expectedThickness) + { + View view = new () { ShadowStyle = style }; + + Assert.Equal (new (0, 0, expectedLength, expectedLength), view.Margin!.Thickness); + + view.Margin!.Thickness = new (0, 0, 1, 1); + Assert.Equal (expectedLength, view.Margin!.ShadowSize.Width); + Assert.Equal (expectedLength, view.Margin.ShadowSize.Height); + Assert.Equal (new (0, 0, expectedThickness, expectedThickness), view.Margin.Thickness); + } + [Fact] public void ShadowStyle_Transparent_Handles_Wide_Glyphs_Correctly () { @@ -126,6 +195,9 @@ public class ShadowTests (ITestOutputHelper output) app.Init ("fake"); app.Driver?.SetScreenSize (6, 5); + + // Using a replacement char to make sure wide glyphs are handled correctly + // in the shadow area, to not confusing with a space char. app.Driver?.GetOutputBuffer ().SetWideGlyphReplacement (Rune.ReplacementChar); Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () }; @@ -143,6 +215,8 @@ public class ShadowTests (ITestOutputHelper output) superview.Add (view); app.Begin (superview); + Assert.Equal (new (2, 1), view.Margin!.ShadowSize); + Assert.Equal (new (0, 0, 2, 1), view.Margin!.Thickness); DriverAssert.AssertDriverContentsAre ( """ @@ -158,6 +232,8 @@ public class ShadowTests (ITestOutputHelper output) view.Margin!.ShadowSize = new (1, 2); app.LayoutAndDraw (); + Assert.Equal (new (1, 2), view.Margin!.ShadowSize); + Assert.Equal (new (0, 0, 2, 2), view.Margin!.Thickness); DriverAssert.AssertDriverContentsAre ( """ @@ -169,6 +245,22 @@ public class ShadowTests (ITestOutputHelper output) """, output, app.Driver); + + view.Width = Dim.Fill (1); + app.LayoutAndDraw (); + Assert.Equal (new (1, 2), view.Margin!.ShadowSize); + Assert.Equal (new (0, 0, 2, 2), view.Margin!.Thickness); + + DriverAssert.AssertDriverContentsAre ( + """ + โ”Œโ”€โ” ๐ŸŽ + โ”‚ โ”‚ ๏ฟฝ + โ””โ”€โ”˜ ๏ฟฝ + ๏ฟฝ ๐ŸŽ๏ฟฝ + ๏ฟฝ ๐ŸŽ๏ฟฝ + """, + output, + app.Driver); } [Fact] @@ -326,20 +418,20 @@ public class ShadowTests (ITestOutputHelper output) } [Theory] - [InlineData (ShadowStyle.None, 3)] - [InlineData (ShadowStyle.Opaque, 4)] - [InlineData (ShadowStyle.Transparent, 4)] - public void Margin_Thickness_Changes_Adjust_Correctly (ShadowStyle style, int expected) + [InlineData (ShadowStyle.None, 3, 4, 4)] + [InlineData (ShadowStyle.Opaque, 4, 5, 4)] + [InlineData (ShadowStyle.Transparent, 4, 5, 4)] + public void Margin_Thickness_Changes_Adjust_Correctly (ShadowStyle style, int expectedThickness, int expectedThicknessAdjust, int expectedThicknessNone) { var view = new View (); view.Margin!.Thickness = new (3); view.ShadowStyle = style; - Assert.Equal (new (3, 3, expected, expected), view.Margin.Thickness); + Assert.Equal (new (3, 3, expectedThickness, expectedThickness), 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.Margin.Thickness = new (3, 3, expectedThickness + 1, expectedThickness + 1); + Assert.Equal (new (3, 3, expectedThicknessAdjust, expectedThicknessAdjust), view.Margin.Thickness); view.ShadowStyle = ShadowStyle.None; - Assert.Equal (new (3, 3, 4, 4), view.Margin.Thickness); + Assert.Equal (new (3, 3, expectedThicknessNone, expectedThicknessNone), view.Margin.Thickness); view.Dispose (); } @@ -427,12 +519,16 @@ public class ShadowTests (ITestOutputHelper output) app.Driver.Refresh (); // Assert + Assert.Equal (new (0, 0, 2, 2), viewWithShadow.Frame); + Assert.Equal (new (0, 0, 1, 1), viewWithShadow.Viewport); + _output.WriteLine ("Actual driver contents:"); _output.WriteLine (app.Driver.ToString ()); _output.WriteLine ("\nActual driver output:"); string? output = app.Driver.GetOutput ().GetLastOutput (); _output.WriteLine (output); + // Printed with bright black (dark gray) text on bright black (dark gray) background making it invisible DriverAssert.AssertDriverOutputIs (""" \x1b[30m\x1b[107m*\x1b[90m\x1b[100mB """, _output, app.Driver); diff --git a/Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs index e24280295..6d681eee4 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs @@ -630,7 +630,11 @@ public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBas output, driver); - + // After a full redraw, all cells should be clean + foreach (Cell cell in driver.Contents!) + { + Assert.False (cell.IsDirty); + } } [Fact] diff --git a/Tests/UnitTestsParallelizable/ViewBase/Mouse/HighlightStatesTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Mouse/HighlightStatesTests.cs new file mode 100644 index 000000000..6590aa809 --- /dev/null +++ b/Tests/UnitTestsParallelizable/ViewBase/Mouse/HighlightStatesTests.cs @@ -0,0 +1,117 @@ +๏ปฟusing UnitTests; +using Xunit.Abstractions; + +namespace ViewBaseTests.Mouse; + +public class HighlightStatesTests (ITestOutputHelper output) +{ + [Fact] + public void HighlightStates_SubView_With_Single_Runnable_WorkAsExpected () + { + IApplication app = Application.Create (); + app.Init ("fake"); + + app.Driver?.SetScreenSize (6, 1); + + Attribute focus = new (ColorName16.White, ColorName16.Black, TextStyle.None); + Attribute highlight = new (ColorName16.Blue, ColorName16.Black, TextStyle.Italic); + + Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () }; + superview.SetScheme (new () { Focus = focus, Highlight = highlight }); + View view = new () { Width = Dim.Fill (), Height = Dim.Fill (), Text = "| Hi |", HighlightStates = MouseState.In }; + superview.Add (view); + + app.Begin (superview); + + for (var i = 0; i < app.Driver?.Cols; i++) + { + Assert.Equal (focus, app.Driver.Contents? [0, i].Attribute); + } + + DriverAssert.AssertDriverContentsAre ("| Hi |", output, app.Driver); + + app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (2, 0), Flags = MouseFlags.ReportMousePosition }); + app.LayoutAndDraw (); + + for (var i = 0; i < app.Driver?.Cols; i++) + { + Assert.Equal (highlight, app.Driver.Contents? [0, i].Attribute); + } + + DriverAssert.AssertDriverContentsAre ("| Hi |", output, app.Driver); + + app.Dispose (); + } + + [Fact] + public void HighlightStates_SubView_With_Multiple_Runnable_WorkAsExpected () + { + IApplication app = Application.Create (); + app.Init ("fake"); + + app.Driver?.SetScreenSize (9, 5); + + Attribute focus = new (ColorName16.White, ColorName16.Black, TextStyle.None); + Attribute highlight = new (ColorName16.Blue, ColorName16.Black, TextStyle.Italic); + + Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () }; + superview.SetScheme (new () { Focus = focus, Highlight = highlight }); + View view = new () { Width = Dim.Fill (), Height = Dim.Fill (), Text = "| Hi |", HighlightStates = MouseState.In }; + superview.Add (view); + + app.Begin (superview); + + Attribute normal = new (ColorName16.Green, ColorName16.Magenta, TextStyle.None); + Attribute highlight2 = new (ColorName16.Red, ColorName16.Yellow, TextStyle.Italic); + + Runnable modalSuperview = new () { Y = 1, Width = 9, Height = 4, BorderStyle = LineStyle.Single }; + modalSuperview.SetScheme (new () { Normal = normal, Highlight = highlight2 }); + View view2 = new () { Width = Dim.Fill (), Height = Dim.Fill (), Text = "| Hey |", HighlightStates = MouseState.In }; + modalSuperview.Add (view2); + + app.Begin (modalSuperview); + + for (var i = 0; i < app.Driver?.Cols; i++) + { + Assert.Equal (focus, app.Driver.Contents? [0, i].Attribute); + } + + for (var i = 0; i < app.Driver?.Cols; i++) + { + Assert.Equal (normal, app.Driver.Contents? [2, i].Attribute); + } + + DriverAssert.AssertDriverContentsAre (""" + | Hi | + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚| Hey |โ”‚ + โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + """ + , output, app.Driver); + + app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.ReportMousePosition }); + app.LayoutAndDraw (); + + for (var i = 0; i < app.Driver?.Cols; i++) + { + Assert.Equal (focus, app.Driver.Contents? [0, i].Attribute); + } + + for (var i = 1; i < app.Driver?.Cols - 1; i++) + { + Assert.Equal (highlight2, app.Driver?.Contents? [2, i].Attribute); + } + + DriverAssert.AssertDriverContentsAre (""" + | Hi | + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚| Hey |โ”‚ + โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + """, + output, app.Driver); + + app.Dispose (); + } +}