diff --git a/Examples/UICatalog/Scenarios/ShadowStyleDemo.cs b/Examples/UICatalog/Scenarios/ShadowStyleDemo.cs index 189ca0ef7b..8392bb66af 100644 --- a/Examples/UICatalog/Scenarios/ShadowStyleDemo.cs +++ b/Examples/UICatalog/Scenarios/ShadowStyleDemo.cs @@ -12,30 +12,31 @@ public override void Main () using IApplication app = Application.Create (); app.Init (); - using Window window = new () - { - Id = "app", - Title = GetQuitKeyAndName () - }; + using Window window = new (); + window.Id = "app"; + window.Title = GetQuitKeyAndName (); + window.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Accent); AdornmentsEditor editor = new () { - Id = "editor", + BorderStyle = LineStyle.Single, AutoSelectViewToEdit = true, - ShowViewIdentifier = true - }; - editor.Initialized += (_, _) => editor.MarginEditor!.ExpanderButton!.Collapsed = false; - window.Add (editor); + // This is for giggles, to show that the editor can be moved around. + Arrangement = ViewArrangement.Movable, + Id = "editor" + }; + editor.Border.Thickness = new Thickness (1, 2, 1, 1); Window shadowWindow = new () { Id = "shadowWindow", - X = Pos.Right (editor), + X = Pos.Center (), Y = 0, Width = Dim.Percent (30), Height = Dim.Percent (30), Title = "Shadow Window", + SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Accent), Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped, BorderStyle = LineStyle.Double, ShadowStyle = ShadowStyles.Transparent @@ -51,11 +52,11 @@ public override void Main () { Id = "buttonInWin", X = Pos.Center (), - Y = Pos.Center (), Text = "Button in Window", + Y = Pos.Center (), + Text = "Button in Window", ShadowStyle = ShadowStyles.Opaque }; shadowWindow.Add (buttonInWin); - window.Add (shadowWindow); Window shadowWindow2 = new () { @@ -65,17 +66,18 @@ public override void Main () Width = Dim.Percent (30), Height = Dim.Percent (30), Title = "Shadow Window #2", + SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Error), Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped, BorderStyle = LineStyle.Double, ShadowStyle = ShadowStyles.Transparent }; - window.Add (shadowWindow2); Button button = new () { Id = "button", X = Pos.Right (editor) + 10, - Y = Pos.Center (), Text = "Button", + Y = Pos.Center (), + Text = "Button", ShadowStyle = ShadowStyles.Opaque }; button.Accepting += ButtonOnAccepting; @@ -84,7 +86,7 @@ public override void Main () { Title = "ColorPicker to illustrate highlight (currently broken)", BorderStyle = LineStyle.Dotted, - Id = "colorPicker16", + Id = "colorPicker", X = Pos.Center (), Y = Pos.AnchorEnd (), Width = Dim.Percent (80) @@ -93,13 +95,18 @@ public override void Main () colorPicker.ValueChanged += (_, args) => { Attribute normal = window.GetScheme ().Normal; - window.SetScheme (window.GetScheme () with { Normal = new (normal.Foreground, args.NewValue ?? Color.Black) }); + + window.SetScheme (window.GetScheme () with + { + Normal = new Attribute (normal.Foreground, args.NewValue ?? Color.Black) + }); }; - window.Add (button, colorPicker); + window.Add (button, colorPicker, editor, shadowWindow, shadowWindow2); + editor.ShowViewIdentifier = true; editor.AutoSelectViewToEdit = true; editor.AutoSelectSuperView = window; - editor.AutoSelectAdornments = false; + editor.AutoSelectAdornments = true; app.Run (window); } diff --git a/Terminal.Gui/ViewBase/Adornment/Margin.cs b/Terminal.Gui/ViewBase/Adornment/Margin.cs index 59c2281831..3e49b781bb 100644 --- a/Terminal.Gui/ViewBase/Adornment/Margin.cs +++ b/Terminal.Gui/ViewBase/Adornment/Margin.cs @@ -56,7 +56,7 @@ protected override void OnThicknessChanged () /// public ShadowStyles? ShadowStyle { - get => field ?? Parent?.SuperView?.ShadowStyle ?? null; + get => field; set { if (field == value) @@ -68,7 +68,11 @@ public ShadowStyles? ShadowStyle if (field is null) { // null means no shadow and no thickness - (View as MarginView)?.SetShadow (null); + if (View is MarginView mv) + { + mv.SetShadow (null); + mv.ShadowSize = Size.Empty; + } return; } diff --git a/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs index 6d4d55f6d4..353258d848 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs @@ -128,7 +128,7 @@ public void Changing_ShadowStyle_Correctly_Set_ShadowWidth_ShadowHeight_Thicknes Assert.Equal (new Thickness (0, 0, 2, 2), view.Margin.Thickness); view.ShadowStyle = null; - Assert.Equal (new Size (2, 2), (view.Margin.View as MarginView)?.ShadowSize); + Assert.Equal (Size.Empty, (view.Margin.View as MarginView)?.ShadowSize); Assert.Equal (new Thickness (0, 0, 0, 0), view.Margin.Thickness); view.ShadowStyle = ShadowStyles.Opaque; @@ -577,4 +577,62 @@ public void TransparentShadow_OverWide_Draws_Transparent_At_Driver_Output () output, app.Driver); } + + /// + /// Proves Issue #5088: ShadowStyle getter returns an inherited value from + /// SuperView even though no shadow was ever set on the view itself. Reading the + /// value and writing it back should be a no-op, but instead it creates a + /// MarginView and adds shadow thickness. + /// + [Fact] + public void ShadowStyle_Getter_Does_Not_Inherit_From_SuperView () + { + // Explicitly give the SuperView a transparent shadow for this inheritance test. + Window superView = new () { Width = 20, Height = 10, ShadowStyle = ShadowStyles.Transparent }; + + View child = new () { Width = 5, Height = 3 }; + superView.Add (child); + + // The subview was never assigned a ShadowStyle - it must be null. + Assert.Null (child.Margin.ShadowStyle); + Assert.Null (child.ShadowStyle); + + // Round-trip: reading and writing back should be a safe no-op. + ShadowStyles? readBack = child.ShadowStyle; + child.ShadowStyle = readBack; + + // After the round-trip the subview must still have no shadow and no Margin thickness. + Assert.Null (child.ShadowStyle); + Assert.Equal (Thickness.Empty, child.Margin.Thickness); + + // MarginView should NOT have been created by a no-op assignment. + Assert.Null (child.Margin.View); + + child.Dispose (); + superView.Dispose (); + } + + /// + /// Proves that setting ShadowStyle to null resets ShadowSize on the MarginView + /// to , so that state is consistent when no shadow is active. + /// + [Fact] + public void Setting_ShadowStyle_Null_Resets_ShadowSize () + { + View view = new (); + + // Set a shadow - this creates a MarginView with ShadowSize {1,1}. + view.ShadowStyle = ShadowStyles.Transparent; + var marginView = view.Margin.View as MarginView; + Assert.NotNull (marginView); + Assert.Equal (new Size (1, 1), marginView.ShadowSize); + + // Now clear the shadow. + view.ShadowStyle = null; + + // ShadowSize should be reset to Empty - no residual state. + Assert.Equal (Size.Empty, marginView.ShadowSize); + + view.Dispose (); + } }