From 1a54ff0872505ad54a44e68cbb5d7281690b731d Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 17 Dec 2025 18:56:31 +0000 Subject: [PATCH] Fixes #4502. Shadows in overlapping views are not rendered consistently (#4465) * Fixes Wide Glyphs * Fix some unit tests failure * Fix more unit tests * Fixes #4468 - `MouseGrab` regressions (#4469) * Fixed mouse grab issue * Fixed mouse grab regrssions. * Update Terminal.Gui/ViewBase/View.Mouse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Mouse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Mouse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Mouse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Mouse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/ViewBase/View.Mouse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * code cleanup * Update Terminal.Gui/ViewBase/View.Mouse.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Addressing pr feedback * updated mouse.md --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fixes #4050. Rename Command.Select and Selecting to Activate/Activating (#4470) * Initial plan * Rename Command.Select to Command.Activate and Selecting to Activating Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add Activating event propagation to SuperView Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update all comments and docs referencing Select to Activate Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix event log messages in examples to use Activating/Activate Co-authored-by: tig <585482+tig@users.noreply.github.com> * Revert automatic Activating event propagation that broke tests Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update docfx documentation to use Activate/Activating terminology Co-authored-by: tig <585482+tig@users.noreply.github.com> * renames * Revert "Add Activating event propagation to SuperView" This reverts commit 6d82bee9ad7b4b93c15b2e2af968e38046b8f56a. * added command diagrams * mermaid * updated level 3 * again * Select->Activate in MouseTests.cs * Update Terminal.Gui/Views/Selectors/FlagSelector.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor: Rename Selecting to Activating in View APIs Renamed the `Selecting` event and `OnSelecting` method to `Activating` and `OnActivating` to better reflect their purpose. Updated all related comments, test method names, variables, and assertions in `View` and `ViewCommandTests` to align with the new terminology. Improved code clarity by using `_` for unused parameters in lambda expressions. Renamed properties like `HandleSelecting` to `HandleActivating` and adjusted naming conventions for consistency (e.g., `OnactivatingCount` to `OnActivatingCount`). These changes enhance readability, maintainability, and terminology consistency across the codebase. * Update Terminal.Gui/Views/Selectors/OptionSelector.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Typos --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add support for wide glyphs into the ShadowStyle.Transparent * The "IsDirty" content of the wide glyphs should be completely false after being displayed on the screen. * FillRect must always call AddStr method to properly handle wide characters to invalidate overlapped wide glyphs, etc. * We must set clip to the driver screen in the case where the view isn't Adornment and SuperView is null, allowing dialogs, popovers, etc to be drawn * These assertions are important to ensure that the drawing state is consistent * Subviews must be drawn after the superview content being drawn * The attribute of next column of a wide glyph should be the same if it's in the clipped area * Applied #4486 * Updated root view clipping logic to use App's screen or a large default rectangle, ensuring proper drawing area. Added comments clarifying subview drawing order and explaining issues with certain Debug.Assert checks in complex hierarchies, suggesting unit tests instead. * Remove ClearFrame and always draw Margin if Thickness set Removed the unused ClearFrame method from the View class. Updated DrawAdornments to always draw the Margin when its Thickness is not empty, regardless of ShadowStyle. This simplifies the drawing logic and removes unnecessary checks. * Fix margin thickness adjustment * Fix shadow draw on overlapped views * Only draw shadows at the end of each runnable * Only call ClearNeedsDraw in the static Draw method * Replace unit test as requested * Also use reverse in margins although not making difference * Fix the ShadowStyle, ShadowWidth and ShadowHeight logic * Making sure nothing broke. * ShadowWidth and ShadowHeight always default to 1 at invalid values * Removes unused parameter * Using Rune.ReplacementChar to not confusing with space char inserted by the clearing region * Using Rune.ReplacementChar * Using Rune.ReplacementChar * Fix merge error * Fixing unit tests * Reverting changes * Fixing unit tests * Fix unit test * Fix shadow on overlapped views * Cleanup code --------- Co-authored-by: Tig Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> --- Terminal.Gui/Drawing/Thickness.cs | 1 - Terminal.Gui/ViewBase/Adornment/Margin.cs | 2 +- .../View/Adornment/AdornmentTests.cs | 2 +- .../Adornment/BorderArrangementTests.cs | 95 ++++++++++++-- .../ViewBase/Adornment/ShadowTests.cs | 118 ++++++++++++++++-- .../ViewBase/Draw/ViewDrawingClippingTests.cs | 6 +- .../ViewBase/Mouse/HighlightStatesTests.cs | 117 +++++++++++++++++ 7 files changed, 318 insertions(+), 23 deletions(-) create mode 100644 Tests/UnitTestsParallelizable/ViewBase/Mouse/HighlightStatesTests.cs 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 (); + } +}