Ver código fonte

Merge branch 'v2_develop' into copilot/enable-menubar-replacement

Tig 2 dias atrás
pai
commit
cab91881b0
48 arquivos alterados com 1994 adições e 540 exclusões
  1. 2 1
      Examples/UICatalog/Resources/config.json
  2. 1 0
      Examples/UICatalog/Scenarios/Adornments.cs
  3. 19 18
      Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs
  4. 9 8
      Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs
  5. 6 13
      Examples/UICatalog/Scenarios/EditorsAndHelpers/AllViewsView.cs
  6. 25 22
      Examples/UICatalog/Scenarios/EditorsAndHelpers/BorderEditor.cs
  7. 37 34
      Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs
  8. 21 30
      Examples/UICatalog/Scenarios/EditorsAndHelpers/EditorBase.cs
  9. 1 3
      Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs
  10. 8 12
      Examples/UICatalog/Scenarios/EditorsAndHelpers/ExpanderButton.cs
  11. 0 5
      Examples/UICatalog/Scenarios/EditorsAndHelpers/LayoutEditor.cs
  12. 5 12
      Examples/UICatalog/Scenarios/EditorsAndHelpers/MarginEditor.cs
  13. 3 6
      Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs
  14. 11 19
      Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewPropertiesEditor.cs
  15. 50 54
      Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewportSettingsEditor.cs
  16. 25 2
      Examples/UICatalog/Scenarios/ShadowStyles.cs
  17. 2 0
      Examples/UICatalog/Scenarios/ViewportSettings.cs
  18. 45 25
      Examples/UICatalog/Scenarios/WideGlyphs.cs
  19. 1 1
      Terminal.Gui/App/ApplicationImpl.Run.cs
  20. 1 1
      Terminal.Gui/App/ApplicationNavigation.cs
  21. 1 0
      Terminal.Gui/App/CWP/CWPEventHelper.cs
  22. 4 2
      Terminal.Gui/App/CWP/CWPPropertyHelper.cs
  23. 2 0
      Terminal.Gui/App/CWP/CWPWorkflowHelper.cs
  24. 2 2
      Terminal.Gui/App/Keyboard/KeyboardImpl.cs
  25. 1 1
      Terminal.Gui/App/Mouse/MouseImpl.cs
  26. 5 0
      Terminal.Gui/Drawing/Glyphs.cs
  27. 3 3
      Terminal.Gui/Drivers/DriverImpl.cs
  28. 7 9
      Terminal.Gui/Drivers/IDriver.cs
  29. 12 2
      Terminal.Gui/Drivers/IOutputBuffer.cs
  30. 7 0
      Terminal.Gui/Drivers/OutputBase.cs
  31. 85 65
      Terminal.Gui/Drivers/OutputBufferImpl.cs
  32. 129 22
      Terminal.Gui/ViewBase/Adornment/Margin.cs
  33. 21 5
      Terminal.Gui/ViewBase/Adornment/ShadowView.cs
  34. 20 4
      Terminal.Gui/ViewBase/View.Drawing.cs
  35. 5 5
      Terminal.Gui/ViewBase/View.Mouse.cs
  36. 1 0
      Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs
  37. 4 0
      Terminal.sln.DotSettings
  38. 6 5
      Tests/UnitTests/View/Draw/ClipTests.cs
  39. 125 48
      Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
  40. 3 3
      Tests/UnitTestsParallelizable/Drivers/ClipRegionTests.cs
  41. 4 2
      Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
  42. 10 5
      Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs
  43. 544 0
      Tests/UnitTestsParallelizable/Drivers/OutputBufferWideCharTests.cs
  44. 193 0
      Tests/UnitTestsParallelizable/ViewBase/Adornment/BorderArrangementTests.cs
  45. 22 0
      Tests/UnitTestsParallelizable/ViewBase/Adornment/MarginTests.cs
  46. 0 76
      Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowStyletests.cs
  47. 487 0
      Tests/UnitTestsParallelizable/ViewBase/Adornment/ShadowTests.cs
  48. 19 15
      Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs

+ 2 - 1
Examples/UICatalog/Resources/config.json

@@ -9,6 +9,7 @@
   "Themes": [
     {
       "Hot Dog Stand": {
+        "Glyphs.WideGlyphReplacement": "①",
         "Schemes": [
           {
             "Runnable": {
@@ -134,7 +135,7 @@
       }
     },
     {
-      "UI Catalog Theme": {
+      "UI Catalog": {
         "Window.DefaultShadow": "Transparent",
         "Button.DefaultShadow": "None",
         "CheckBox.DefaultHighlightStates": "In, Pressed, PressedOutside",

+ 1 - 0
Examples/UICatalog/Scenarios/Adornments.cs

@@ -19,6 +19,7 @@ public class Adornments : Scenario
 
         var editor = new AdornmentsEditor
         {
+            BorderStyle = LineStyle.Single,
             AutoSelectViewToEdit = true,
 
             // This is for giggles, to show that the editor can be moved around.

+ 19 - 18
Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentEditor.cs

@@ -1,6 +1,4 @@
 #nullable enable
-using System;
-
 namespace UICatalog.Scenarios;
 
 /// <summary>
@@ -57,11 +55,13 @@ public class AdornmentEditor : EditorBase
                 _bottomEdit!.Value = _adornment.Thickness.Bottom;
                 _rightEdit!.Value = _adornment.Thickness.Right;
 
-                _adornment.Initialized += (sender, args) =>
+                _adornment.Initialized += (_, _) =>
                                           {
-                                              Scheme? cs = _adornment.GetScheme ();
-                                              _foregroundColorPicker.SelectedColor = _adornment.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ();
-                                              _backgroundColorPicker.SelectedColor = _adornment.GetAttributeForRole (VisualRole.Normal).Background.GetClosestNamedColor16 ();
+                                              _foregroundColorPicker.SelectedColor =
+                                                  _adornment.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ();
+
+                                              _backgroundColorPicker.SelectedColor =
+                                                  _adornment.GetAttributeForRole (VisualRole.Normal).Background.GetClosestNamedColor16 ();
                                           };
             }
 
@@ -125,12 +125,12 @@ public class AdornmentEditor : EditorBase
         _bottomEdit.ValueChanging += Bottom_ValueChanging;
         Add (_bottomEdit);
 
-        var copyTop = new Button
+        Button copyTop = new ()
         {
             X = Pos.Center (), Y = Pos.Bottom (_bottomEdit), Text = "Cop_y Top"
         };
 
-        copyTop.Accepting += (s, e) =>
+        copyTop.Accepting += (_, _) =>
                              {
                                  AdornmentToEdit!.Thickness = new (_topEdit.Value);
                                  _leftEdit.Value = _rightEdit.Value = _bottomEdit.Value = _topEdit.Value;
@@ -168,9 +168,9 @@ public class AdornmentEditor : EditorBase
             _diagThicknessCheckBox.CheckedState = Diagnostics.FastHasFlags (ViewDiagnosticFlags.Thickness) ? CheckState.Checked : CheckState.UnChecked;
         }
 
-        _diagThicknessCheckBox.CheckedStateChanging += (s, e) =>
+        _diagThicknessCheckBox.CheckedStateChanging += (_, args) =>
                                                        {
-                                                           if (e.Result == CheckState.Checked)
+                                                           if (args.Result == CheckState.Checked)
                                                            {
                                                                AdornmentToEdit!.Diagnostics |= ViewDiagnosticFlags.Thickness;
                                                            }
@@ -194,9 +194,9 @@ public class AdornmentEditor : EditorBase
             _diagRulerCheckBox.CheckedState = Diagnostics.FastHasFlags (ViewDiagnosticFlags.Ruler) ? CheckState.Checked : CheckState.UnChecked;
         }
 
-        _diagRulerCheckBox.CheckedStateChanging += (s, e) =>
+        _diagRulerCheckBox.CheckedStateChanging += (_, args) =>
                                                    {
-                                                       if (e.Result == CheckState.Checked)
+                                                       if (args.Result == CheckState.Checked)
                                                        {
                                                            AdornmentToEdit!.Diagnostics |= ViewDiagnosticFlags.Ruler;
                                                        }
@@ -212,18 +212,19 @@ public class AdornmentEditor : EditorBase
 
     private EventHandler<ResultEventArgs<Color>> ColorPickerColorChanged ()
     {
-        return (o, a) =>
+        return (_, _) =>
                {
                    if (AdornmentToEdit is null)
                    {
                        return;
                    }
 
-                   AdornmentToEdit.SetScheme (new (AdornmentToEdit.GetScheme ())
-                   {
-                       Normal = new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor)
-                   })
-                   ;
+                   AdornmentToEdit.SetScheme (
+                                              new (AdornmentToEdit.GetScheme ())
+                                              {
+                                                  Normal = new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor)
+                                              })
+                       ;
                };
     }
 

+ 9 - 8
Examples/UICatalog/Scenarios/EditorsAndHelpers/AdornmentsEditor.cs

@@ -14,8 +14,6 @@ public class AdornmentsEditor : EditorBase
 
         TabStop = TabBehavior.TabGroup;
 
-        ExpanderButton!.Orientation = Orientation.Horizontal;
-
         Initialized += AdornmentsEditor_Initialized;
 
         SchemeName = "Dialog";
@@ -28,8 +26,6 @@ public class AdornmentsEditor : EditorBase
     /// <inheritdoc/>
     protected override void OnViewToEditChanged ()
     {
-        //Enabled = ViewToEdit is not Adornment;
-
         if (MarginEditor is { })
         {
             MarginEditor.AdornmentToEdit = ViewToEdit?.Margin ?? null;
@@ -47,7 +43,7 @@ public class AdornmentsEditor : EditorBase
 
         if (Padding is { })
         {
-            Padding.Text = $"View: {GetIdentifyingString (ViewToEdit)}";
+            Padding.Text = GetIdentifyingString (ViewToEdit);
         }
     }
 
@@ -92,12 +88,17 @@ public class AdornmentsEditor : EditorBase
 
     private void AdornmentsEditor_Initialized (object? sender, EventArgs e)
     {
+        if (ExpanderButton is { })
+        {
+            ExpanderButton.Orientation = Orientation.Horizontal;
+        }
+
         MarginEditor = new ()
         {
             X = -1,
             Y = 0,
             SuperViewRendersLineCanvas = true,
-            BorderStyle = LineStyle.Single
+            BorderStyle = BorderStyle
         };
         MarginEditor.Border!.Thickness = MarginEditor.Border!.Thickness with { Bottom = 0 };
         Add (MarginEditor);
@@ -107,7 +108,7 @@ public class AdornmentsEditor : EditorBase
             X = Pos.Left (MarginEditor),
             Y = Pos.Bottom (MarginEditor),
             SuperViewRendersLineCanvas = true,
-            BorderStyle = LineStyle.Single
+            BorderStyle = BorderStyle
         };
         BorderEditor.Border!.Thickness = BorderEditor.Border!.Thickness with { Bottom = 0 };
         Add (BorderEditor);
@@ -117,7 +118,7 @@ public class AdornmentsEditor : EditorBase
             X = Pos.Left (BorderEditor),
             Y = Pos.Bottom (BorderEditor),
             SuperViewRendersLineCanvas = true,
-            BorderStyle = LineStyle.Single
+            BorderStyle = BorderStyle
         };
         PaddingEditor.Border!.Thickness = PaddingEditor.Border!.Thickness with { Bottom = 0 };
         Add (PaddingEditor);

+ 6 - 13
Examples/UICatalog/Scenarios/EditorsAndHelpers/AllViewsView.cs

@@ -77,7 +77,7 @@ public class AllViewsView : View
 
         View? previousView = null;
 
-        foreach (Type? type in allClasses)
+        foreach (Type type in allClasses)
         {
             View? view = CreateView (type);
 
@@ -118,15 +118,8 @@ public class AllViewsView : View
                     // Check if the generic parameter has constraints
                     Type [] constraints = arg.GetGenericParameterConstraints ();
 
-                    if (constraints.Length > 0)
-                    {
-                        // Use the first constraint type to satisfy the constraint
-                        typeArguments.Add (constraints [0]);
-                    }
-                    else
-                    {
-                        typeArguments.Add (typeof (object));
-                    }
+                    // Use the first constraint type to satisfy the constraint
+                    typeArguments.Add (constraints.Length > 0 ? constraints [0] : typeof (object));
                 }
             }
 
@@ -193,17 +186,17 @@ public class AllViewsView : View
             return;
         }
 
-        if (view.Width == Dim.Absolute (0) || view.Width is null)
+        if (view.Width == Dim.Absolute (0))
         {
             view.Width = Dim.Fill ();
         }
 
-        if (view.Height == Dim.Absolute (0) || view.Height is null)
+        if (view.Height == Dim.Absolute (0))
         {
             view.Height = MAX_VIEW_FRAME_HEIGHT - 2;
         }
 
-        if (!view.Width!.Has<DimAuto> (out _))
+        if (!view.Width.Has<DimAuto> (out _))
         {
             view.Width = Dim.Fill ();
         }

+ 25 - 22
Examples/UICatalog/Scenarios/EditorsAndHelpers/BorderEditor.cs

@@ -1,7 +1,6 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
-using System.Linq;
+using System.Reflection;
+using Terminal.Gui.ViewBase;
 
 namespace UICatalog.Scenarios;
 
@@ -33,10 +32,10 @@ public class BorderEditor : AdornmentEditor
 
             Y = Pos.Bottom (SubViews.ToArray () [^1]),
             Width = Dim.Fill (),
-            Value = ((Border)AdornmentToEdit!)?.LineStyle ?? LineStyle.None,
+            Value = (AdornmentToEdit as Border)?.LineStyle ?? LineStyle.None,
             BorderStyle = LineStyle.Single,
             Title = "Border St_yle",
-            SuperViewRendersLineCanvas = true,
+            SuperViewRendersLineCanvas = true
         };
         Add (_osBorderStyle);
 
@@ -49,7 +48,7 @@ public class BorderEditor : AdornmentEditor
 
             CheckedState = CheckState.Checked,
             SuperViewRendersLineCanvas = true,
-            Text = "Title",
+            Text = "Title"
         };
 
         _ckbTitle.CheckedStateChanging += OnCkbTitleOnToggle;
@@ -62,7 +61,7 @@ public class BorderEditor : AdornmentEditor
 
             CheckedState = CheckState.Checked,
             SuperViewRendersLineCanvas = true,
-            Text = "Gradient",
+            Text = "Gradient"
         };
 
         _ckbGradient.CheckedStateChanging += OnCkbGradientOnToggle;
@@ -72,51 +71,55 @@ public class BorderEditor : AdornmentEditor
 
         void OnRbBorderStyleOnValueChanged (object? s, EventArgs<LineStyle?> args)
         {
-            LineStyle prevBorderStyle = AdornmentToEdit!.BorderStyle;
-
-            if (args.Value is { })
+            if (AdornmentToEdit is not Border border)
             {
-                ((Border)AdornmentToEdit).LineStyle = (LineStyle)args.Value;
+                return;
             }
 
-            if (((Border)AdornmentToEdit).LineStyle == LineStyle.None)
-            {
-                ((Border)AdornmentToEdit).Thickness = new (0);
-            }
-            else if (prevBorderStyle == LineStyle.None && ((Border)AdornmentToEdit).LineStyle != LineStyle.None)
+            if (args.Value is { })
             {
-                ((Border)AdornmentToEdit).Thickness = new (1);
+                border.LineStyle = (LineStyle)args.Value;
             }
 
-            ((Border)AdornmentToEdit).SetNeedsDraw ();
+            border.SetNeedsDraw ();
             SetNeedsLayout ();
         }
 
         void OnCkbTitleOnToggle (object? _, ResultEventArgs<CheckState> args)
         {
+            if (AdornmentToEdit is not Border border)
+            {
+                return;
+            }
+
             if (args.Result == CheckState.Checked)
 
             {
-                ((Border)AdornmentToEdit!).Settings |= BorderSettings.Title;
+                border.Settings |= BorderSettings.Title;
             }
             else
 
             {
-                ((Border)AdornmentToEdit!).Settings &= ~BorderSettings.Title;
+                border.Settings &= ~BorderSettings.Title;
             }
         }
 
         void OnCkbGradientOnToggle (object? _, ResultEventArgs<CheckState> args)
         {
+            if (AdornmentToEdit is not Border border)
+            {
+                return;
+            }
+
             if (args.Result == CheckState.Checked)
 
             {
-                ((Border)AdornmentToEdit!).Settings |= BorderSettings.Gradient;
+                border.Settings |= BorderSettings.Gradient;
             }
             else
 
             {
-                ((Border)AdornmentToEdit!).Settings &= ~BorderSettings.Gradient;
+                border.Settings &= ~BorderSettings.Gradient;
             }
         }
     }

+ 37 - 34
Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs

@@ -1,8 +1,5 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
 using System.Diagnostics;
-using System.Linq;
 
 namespace UICatalog.Scenarios;
 
@@ -21,7 +18,7 @@ public class DimEditor : EditorBase
     private OptionSelector? _dimOptionSelector;
     private TextField? _valueEdit;
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     protected override void OnViewToEditChanged ()
     {
         if (ViewToEdit is { })
@@ -39,12 +36,11 @@ public class DimEditor : EditorBase
             return;
         }
 
-        Dim? dim;
-        dim = Dimension == Dimension.Width ? ViewToEdit.Width : ViewToEdit.Height;
+        Dim dim = Dimension == Dimension.Width ? ViewToEdit.Width : ViewToEdit.Height;
 
         try
         {
-            _dimOptionSelector!.Value = _dimNames.IndexOf (_dimNames.First (s => dim!.ToString ().StartsWith (s)));
+            _dimOptionSelector!.Value = _dimNames.IndexOf (_dimNames.First (s => dim.ToString ().StartsWith (s)));
         }
         catch (InvalidOperationException e)
         {
@@ -53,31 +49,37 @@ public class DimEditor : EditorBase
         }
 
         _valueEdit!.Enabled = false;
+
         switch (dim)
         {
             case DimAbsolute absolute:
                 _valueEdit.Enabled = true;
                 _value = absolute.Size;
                 _valueEdit!.Text = _value.ToString ();
+
                 break;
             case DimFill fill:
                 var margin = fill.Margin as DimAbsolute;
                 _valueEdit.Enabled = margin is { };
                 _value = margin?.Size ?? 0;
                 _valueEdit!.Text = _value.ToString ();
+
                 break;
             case DimFunc func:
                 _valueEdit.Enabled = true;
                 _value = func.Fn (null);
                 _valueEdit!.Text = _value.ToString ();
+
                 break;
             case DimPercent percent:
                 _valueEdit.Enabled = true;
                 _value = percent.Percentage;
                 _valueEdit!.Text = _value.ToString ();
+
                 break;
             default:
-                _valueEdit!.Text = dim!.ToString ();
+                _valueEdit!.Text = dim.ToString ();
+
                 break;
         }
     }
@@ -94,6 +96,7 @@ public class DimEditor : EditorBase
         Add (label);
         _dimOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = _optionLabels };
         _dimOptionSelector.ValueChanged += OnOptionSelectorOnValueChanged;
+
         _valueEdit = new ()
         {
             X = Pos.Right (label) + 1,
@@ -102,30 +105,30 @@ public class DimEditor : EditorBase
             Text = $"{_value}"
         };
 
-        _valueEdit.Accepting += (s, args) =>
-        {
-            try
-            {
-                _value = int.Parse (_valueEdit.Text);
-                DimChanged ();
-            }
-            catch
-            {
-                // ignored
-            }
-            args.Handled = true;
-        };
+        _valueEdit.Accepting += (_, args) =>
+                                {
+                                    try
+                                    {
+                                        _value = int.Parse (_valueEdit.Text);
+                                        DimChanged ();
+                                    }
+                                    catch
+                                    {
+                                        // ignored
+                                    }
+
+                                    args.Handled = true;
+                                };
         Add (_valueEdit);
 
         Add (_dimOptionSelector);
-
     }
 
     private void OnOptionSelectorOnValueChanged (object? s, EventArgs<int?> selected) { DimChanged (); }
 
-    // These need to have same order 
-    private readonly List<string> _dimNames = ["Absolute", "Auto", "Fill", "Func", "Percent",];
-    private readonly string [] _optionLabels = ["Absolute(n)", "Auto", "Fill(n)", "Func(()=>n)", "Percent(n)",];
+    // These need to have same order
+    private readonly List<string> _dimNames = ["Absolute", "Auto", "Fill", "Func", "Percent"];
+    private readonly string [] _optionLabels = ["Absolute(n)", "Auto", "Fill(n)", "Func(()=>n)", "Percent(n)"];
 
     private void DimChanged ()
     {
@@ -136,15 +139,15 @@ public class DimEditor : EditorBase
 
         try
         {
-            Dim? dim = _dimOptionSelector!.Value switch
-            {
-                0 => Dim.Absolute (_value),
-                1 => Dim.Auto (),
-                2 => Dim.Fill (_value),
-                3 => Dim.Func (_ => _value),
-                4 => Dim.Percent (_value),
-                _ => Dimension == Dimension.Width ? ViewToEdit.Width : ViewToEdit.Height
-            };
+            Dim dim = _dimOptionSelector!.Value switch
+                       {
+                           0 => Dim.Absolute (_value),
+                           1 => Dim.Auto (),
+                           2 => Dim.Fill (_value),
+                           3 => Dim.Func (_ => _value),
+                           4 => Dim.Percent (_value),
+                           _ => Dimension == Dimension.Width ? ViewToEdit.Width : ViewToEdit.Height
+                       };
 
             if (Dimension == Dimension.Width)
             {

+ 21 - 30
Examples/UICatalog/Scenarios/EditorsAndHelpers/EditorBase.cs

@@ -1,8 +1,4 @@
 #nullable enable
-using System;
-using System.Diagnostics;
-using System.Linq;
-
 namespace UICatalog.Scenarios;
 
 public abstract class EditorBase : View
@@ -19,36 +15,21 @@ public abstract class EditorBase : View
             Orientation = Orientation.Vertical
         };
 
-
         TabStop = TabBehavior.TabStop;
 
         Initialized += OnInitialized;
 
         void OnInitialized (object? sender, EventArgs e)
         {
-            if (Border is { })
-            {
-                Border.Add (ExpanderButton);
-
-                if (ExpanderButton.Orientation == Orientation.Vertical)
-                {
-                    ExpanderButton.X = Pos.AnchorEnd () - 1;
-                }
-                else
-                {
-                    ExpanderButton.Y = Pos.AnchorEnd () - 1;
-                }
-            }
-
-            Application.MouseEvent += ApplicationOnMouseEvent;
-            Application.Navigation!.FocusedChanged += NavigationOnFocusedChanged;
+            Border?.Add (ExpanderButton);
 
+            App!.Mouse.MouseEvent += ApplicationOnMouseEvent;
+            App!.Navigation!.FocusedChanged += NavigationOnFocusedChanged;
         }
 
         AddCommand (Command.Accept, () => true);
 
         SchemeName = "Dialog";
-
     }
 
     private readonly ExpanderButton? _expanderButton;
@@ -58,15 +39,16 @@ public abstract class EditorBase : View
         get => _expanderButton;
         init
         {
-            if (_expanderButton == value)
+            if (ReferenceEquals (_expanderButton, value))
             {
                 return;
             }
+
             _expanderButton = value;
         }
     }
 
-    public bool UpdatingLayoutSettings { get; private set; } = false;
+    public bool UpdatingLayoutSettings { get; private set; }
 
     private void View_LayoutComplete (object? sender, LayoutEventArgs e)
     {
@@ -77,7 +59,6 @@ public abstract class EditorBase : View
         UpdatingLayoutSettings = false;
     }
 
-
     private View? _viewToEdit;
 
     public View? ViewToEdit
@@ -90,7 +71,6 @@ public abstract class EditorBase : View
                 return;
             }
 
-
             if (value is null && _viewToEdit is { })
             {
                 _viewToEdit.SubViewsLaidOut -= View_LayoutComplete;
@@ -127,7 +107,6 @@ public abstract class EditorBase : View
     /// </summary>
     public bool AutoSelectAdornments { get; set; }
 
-
     private void NavigationOnFocusedChanged (object? sender, EventArgs e)
     {
         if (AutoSelectSuperView is null)
@@ -135,17 +114,17 @@ public abstract class EditorBase : View
             return;
         }
 
-        if (ApplicationNavigation.IsInHierarchy (this, Application.Navigation!.GetFocused ()))
+        if (ApplicationNavigation.IsInHierarchy (this, App?.Navigation?.GetFocused ()))
         {
             return;
         }
 
-        if (!ApplicationNavigation.IsInHierarchy (AutoSelectSuperView, Application.Navigation!.GetFocused ()))
+        if (!ApplicationNavigation.IsInHierarchy (AutoSelectSuperView, App?.Navigation?.GetFocused ()))
         {
             return;
         }
 
-        ViewToEdit = Application.Navigation!.GetFocused ();
+        ViewToEdit = App!.Navigation!.GetFocused ();
     }
 
     private void ApplicationOnMouseEvent (object? sender, MouseEventArgs e)
@@ -177,4 +156,16 @@ public abstract class EditorBase : View
             ViewToEdit = view;
         }
     }
+
+    /// <inheritdoc />
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing && App is {})
+        {
+            App.Navigation!.FocusedChanged -= NavigationOnFocusedChanged;
+            App.Mouse.MouseEvent -= ApplicationOnMouseEvent;
+        }
+
+        base.Dispose (disposing);
+    }
 }

+ 1 - 3
Examples/UICatalog/Scenarios/EditorsAndHelpers/EventLog.cs

@@ -1,5 +1,4 @@
 #nullable enable
-using System;
 using System.Collections.ObjectModel;
 
 namespace UICatalog.Scenarios;
@@ -19,8 +18,7 @@ public class EventLog : ListView
         X = Pos.AnchorEnd ();
         Y = 0;
 
-        Width = Dim.Func (
-                          _ =>
+        Width = Dim.Func (_ =>
                           {
                               if (!IsInitialized)
                               {

+ 8 - 12
Examples/UICatalog/Scenarios/EditorsAndHelpers/ExpanderButton.cs

@@ -1,5 +1,4 @@
 #nullable enable
-using System;
 using System.Text;
 
 namespace UICatalog.Scenarios;
@@ -43,14 +42,11 @@ public class ExpanderButton : Button
 
         Orientation = Orientation.Vertical;
 
-        HighlightStates = Terminal.Gui.ViewBase.MouseState.None;
+        HighlightStates = MouseState.In;
 
         Initialized += ExpanderButton_Initialized;
 
-        EnabledChanged += (sender, args) =>
-                          {
-                              ShowHide ();
-                          };
+        EnabledChanged += (_, _) => { ShowHide (); };
     }
 
     private void ShowHide ()
@@ -85,7 +81,7 @@ public class ExpanderButton : Button
 
         if (SuperView is Border { } border)
         {
-            border.ThicknessChanged += (o, args) => ShowHide ();
+            border.ThicknessChanged += (_, _) => ShowHide ();
         }
     }
 
@@ -111,7 +107,7 @@ public class ExpanderButton : Button
     /// <returns>True of the event was cancelled.</returns>
     protected virtual bool OnOrientationChanging (Orientation newOrientation)
     {
-        CancelEventArgs<Orientation> args = new CancelEventArgs<Orientation> (in _orientation, ref newOrientation);
+        CancelEventArgs<Orientation> args = new (in _orientation, ref newOrientation);
         OrientationChanging?.Invoke (this, args);
 
         if (!args.Cancel)
@@ -120,7 +116,7 @@ public class ExpanderButton : Button
 
             if (Orientation == Orientation.Vertical)
             {
-                X = Pos.AnchorEnd ();
+                X = Pos.AnchorEnd () - 1;
                 Y = 0;
                 CollapseGlyph = new ('\u21d1'); // ⇑
                 ExpandGlyph = new ('\u21d3'); // ⇓
@@ -128,7 +124,7 @@ public class ExpanderButton : Button
             else
             {
                 X = 0;
-                Y = Pos.AnchorEnd ();
+                Y = Pos.AnchorEnd () - 1;
                 CollapseGlyph = new ('\u21d0'); // ⇐
                 ExpandGlyph = new ('\u21d2'); // ⇒
             }
@@ -222,12 +218,12 @@ public class ExpanderButton : Button
             // Collapse
             if (Orientation == Orientation.Vertical)
             {
-                _previousDim = superView!.Height!;
+                _previousDim = superView.Height;
                 superView.Height = 1;
             }
             else
             {
-                _previousDim = superView!.Width!;
+                _previousDim = superView.Width;
                 superView.Width = 1;
             }
         }

+ 0 - 5
Examples/UICatalog/Scenarios/EditorsAndHelpers/LayoutEditor.cs

@@ -1,8 +1,4 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
 namespace UICatalog.Scenarios;
 
 /// <summary>
@@ -64,7 +60,6 @@ public class LayoutEditor : EditorBase
             X = Pos.Right (_xEditor) + 1
         };
 
-
         _widthEditor = new ()
         {
             Title = "_Width",

+ 5 - 12
Examples/UICatalog/Scenarios/EditorsAndHelpers/MarginEditor.cs

@@ -1,6 +1,4 @@
 #nullable enable
-using System;
-
 namespace UICatalog.Scenarios;
 
 public class MarginEditor : AdornmentEditor
@@ -34,7 +32,7 @@ public class MarginEditor : AdornmentEditor
         _optionsShadow = new ()
         {
             X = 0,
-            Y = Pos.Bottom (SubViews.ElementAt(SubViews.Count-1)),
+            Y = Pos.Bottom (SubViews.ElementAt (SubViews.Count - 1)),
 
             SuperViewRendersLineCanvas = true,
             Title = "_Shadow",
@@ -51,14 +49,14 @@ public class MarginEditor : AdornmentEditor
 
         Add (_optionsShadow);
 
-        _flagSelectorTransparent = new FlagSelector<ViewportSettingsFlags> ()
+        _flagSelectorTransparent = new FlagSelector<ViewportSettingsFlags>
         {
             X = 0,
             Y = Pos.Bottom (_optionsShadow),
 
             SuperViewRendersLineCanvas = true,
             Title = "_ViewportSettings",
-            BorderStyle = LineStyle.Single,
+            BorderStyle = LineStyle.Single
         };
         _flagSelectorTransparent.Values = [(int)ViewportSettingsFlags.Transparent, (int)ViewportSettingsFlags.TransparentMouse];
         _flagSelectorTransparent.Labels = ["Transparent", "TransparentMouse"];
@@ -71,11 +69,6 @@ public class MarginEditor : AdornmentEditor
             _flagSelectorTransparent.Value = (int)((Margin)AdornmentToEdit).ViewportSettings;
         }
 
-        _flagSelectorTransparent.ValueChanged += (_, args) =>
-                                                 {
-                                                     ((Margin)AdornmentToEdit!).ViewportSettings = (ViewportSettingsFlags)args.Value!;
-                                                 };
-
-
+        _flagSelectorTransparent.ValueChanged += (_, args) => { ((Margin)AdornmentToEdit!).ViewportSettings = (ViewportSettingsFlags)args.Value!; };
     }
-}
+}

+ 3 - 6
Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs

@@ -1,8 +1,5 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
 using System.Diagnostics;
-using System.Linq;
 
 namespace UICatalog.Scenarios;
 
@@ -102,7 +99,7 @@ public class PosEditor : EditorBase
             Text = $"{_value}"
         };
 
-        _valueEdit.Accepting += (s, args) =>
+        _valueEdit.Accepting += (_, args) =>
                                 {
                                     try
                                     {
@@ -123,7 +120,7 @@ public class PosEditor : EditorBase
 
     private void OnOptionSelectorOnValueChanged (object? s, EventArgs<int?> selected) { PosChanged (); }
 
-    // These need to have same order 
+    // These need to have same order
     private readonly List<string> _posNames = ["Absolute", "Align", "AnchorEnd", "Center", "Func", "Percent"];
     private readonly string [] _optionLabels = ["Absolute(n)", "Align", "AnchorEnd", "Center", "Func(()=>n)", "Percent(n)"];
 
@@ -136,7 +133,7 @@ public class PosEditor : EditorBase
 
         try
         {
-            Pos? pos = _posOptionSelector!.Value switch
+            Pos pos = _posOptionSelector!.Value switch
                        {
                            0 => Pos.Absolute (_value),
                            1 => Pos.Align (Alignment.Start),

+ 11 - 19
Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewPropertiesEditor.cs

@@ -20,7 +20,7 @@ public class ViewPropertiesEditor : EditorBase
             CheckedState = ViewToEdit is { } ? ViewToEdit.CanFocus ? CheckState.Checked : CheckState.UnChecked : CheckState.UnChecked
         };
 
-        _canFocusCheckBox.CheckedStateChanged += (s, args) =>
+        _canFocusCheckBox.CheckedStateChanged += (_, _) =>
                                                  {
                                                      if (ViewToEdit is { })
                                                      {
@@ -37,7 +37,7 @@ public class ViewPropertiesEditor : EditorBase
             CheckedState = ViewToEdit is { } ? ViewToEdit.Enabled ? CheckState.Checked : CheckState.UnChecked : CheckState.UnChecked
         };
 
-        _enabledCheckBox.CheckedStateChanged += (s, args) =>
+        _enabledCheckBox.CheckedStateChanged += (_, _) =>
                                                 {
                                                     if (ViewToEdit is { })
                                                     {
@@ -55,13 +55,13 @@ public class ViewPropertiesEditor : EditorBase
             Orientation = Orientation.Horizontal
         };
 
-        _orientationOptionSelector.ValueChanged += (s, selected) =>
-                                            {
-                                                if (ViewToEdit is IOrientation orientatedView)
-                                                {
-                                                    orientatedView.Orientation = _orientationOptionSelector.Value!.Value;
-                                                }
-                                            };
+        _orientationOptionSelector.ValueChanged += (_, _) =>
+                                                   {
+                                                       if (ViewToEdit is IOrientation orientatedView)
+                                                       {
+                                                           orientatedView.Orientation = _orientationOptionSelector.Value!.Value;
+                                                       }
+                                                   };
         Add (label, _orientationOptionSelector);
 
         label = new () { X = 0, Y = Pos.Bottom (_orientationOptionSelector), Text = "Text:" };
@@ -75,7 +75,7 @@ public class ViewPropertiesEditor : EditorBase
             Text = "This is demo text"
         };
 
-        _text.ContentsChanged += (s, e) =>
+        _text.ContentsChanged += (_, _) =>
                                  {
                                      if (ViewToEdit is { })
                                      {
@@ -90,15 +90,7 @@ public class ViewPropertiesEditor : EditorBase
 
     public string DemoText
     {
-        get
-        {
-            if (_text is null)
-            {
-                return string.Empty;
-            }
-
-            return _text!.Text;
-        }
+        get => _text is null ? string.Empty : _text!.Text;
         set => _text!.Text = value;
     }
 

+ 50 - 54
Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewportSettingsEditor.cs

@@ -1,6 +1,4 @@
 #nullable enable
-using System;
-
 namespace UICatalog.Scenarios;
 
 /// <summary>
@@ -60,8 +58,8 @@ public sealed class ViewportSettingsEditor : EditorBase
                                                : CheckState.UnChecked;
 
             _cbTransparentMouse!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (ViewportSettingsFlags.TransparentMouse)
-                                               ? CheckState.Checked
-                                               : CheckState.UnChecked;
+                                                    ? CheckState.Checked
+                                                    : CheckState.UnChecked;
 
             _cbVerticalScrollBar!.CheckedState = ViewToEdit.VerticalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked;
             _cbAutoShowVerticalScrollBar!.CheckedState = ViewToEdit.VerticalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked;
@@ -115,27 +113,27 @@ public sealed class ViewportSettingsEditor : EditorBase
 
         Add (_cbAllowXGreaterThanContentWidth);
 
-        void AllowNegativeXToggle (object? sender, ResultEventArgs<CheckState> e)
+        void AllowNegativeXToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowNegativeX;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.AllowNegativeX;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowNegativeX;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.AllowNegativeX;
             }
         }
 
-        void AllowXGreaterThanContentWidthToggle (object? sender, ResultEventArgs<CheckState> e)
+        void AllowXGreaterThanContentWidthToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowXGreaterThanContentWidth;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.AllowXGreaterThanContentWidth;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowXGreaterThanContentWidth;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.AllowXGreaterThanContentWidth;
             }
         }
 
@@ -153,27 +151,27 @@ public sealed class ViewportSettingsEditor : EditorBase
 
         Add (_cbAllowYGreaterThanContentHeight);
 
-        void AllowNegativeYToggle (object? sender, ResultEventArgs<CheckState> e)
+        void AllowNegativeYToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowNegativeY;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.AllowNegativeY;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowNegativeY;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.AllowNegativeY;
             }
         }
 
-        void AllowYGreaterThanContentHeightToggle (object? sender, ResultEventArgs<CheckState> e)
+        void AllowYGreaterThanContentHeightToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowYGreaterThanContentHeight;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.AllowYGreaterThanContentHeight;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.AllowYGreaterThanContentHeight;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.AllowYGreaterThanContentHeight;
             }
         }
 
@@ -193,17 +191,16 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _contentSizeWidth.ValueChanging += ContentSizeWidthValueChanged;
 
-        void ContentSizeWidthValueChanged (object? sender, CancelEventArgs<int> e)
+        void ContentSizeWidthValueChanged (object? sender, CancelEventArgs<int> cea)
         {
-            if (e.NewValue < 0)
+            if (cea.NewValue < 0)
             {
-                e.Cancel = true;
+                cea.Cancel = true;
 
                 return;
             }
 
-            // BUGBUG: set_ContentSize is supposed to be `protected`. 
-            ViewToEdit!.SetContentSize (ViewToEdit.GetContentSize () with { Width = e.NewValue });
+            ViewToEdit!.SetContentSize (ViewToEdit.GetContentSize () with { Width = cea.NewValue });
         }
 
         var labelComma = new Label
@@ -221,17 +218,16 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _contentSizeHeight.ValueChanging += ContentSizeHeightValueChanged;
 
-        void ContentSizeHeightValueChanged (object? sender, CancelEventArgs<int> e)
+        void ContentSizeHeightValueChanged (object? sender, CancelEventArgs<int> cea)
         {
-            if (e.NewValue < 0)
+            if (cea.NewValue < 0)
             {
-                e.Cancel = true;
+                cea.Cancel = true;
 
                 return;
             }
 
-            // BUGBUG: set_ContentSize is supposed to be `protected`. 
-            ViewToEdit?.SetContentSize (ViewToEdit.GetContentSize () with { Height = e.NewValue });
+            ViewToEdit?.SetContentSize (ViewToEdit.GetContentSize () with { Height = cea.NewValue });
         }
 
         _cbClearContentOnly = new ()
@@ -243,15 +239,15 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbClearContentOnly.CheckedStateChanging += ClearContentOnlyToggle;
 
-        void ClearContentOnlyToggle (object? sender, ResultEventArgs<CheckState> e)
+        void ClearContentOnlyToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.ClearContentOnly;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.ClearContentOnly;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.ClearContentOnly;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.ClearContentOnly;
             }
         }
 
@@ -264,15 +260,15 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbClipContentOnly.CheckedStateChanging += ClipContentOnlyToggle;
 
-        void ClipContentOnlyToggle (object? sender, ResultEventArgs<CheckState> e)
+        void ClipContentOnlyToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.ClipContentOnly;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.ClipContentOnly;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.ClipContentOnly;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.ClipContentOnly;
             }
         }
 
@@ -285,15 +281,15 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbTransparent.CheckedStateChanging += TransparentToggle;
 
-        void TransparentToggle (object? sender, ResultEventArgs<CheckState> e)
+        void TransparentToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.Transparent;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.Transparent;
             }
         }
 
@@ -306,15 +302,15 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbTransparentMouse.CheckedStateChanging += TransparentMouseToggle;
 
-        void TransparentMouseToggle (object? sender, ResultEventArgs<CheckState> e)
+        void TransparentMouseToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            if (e.Result == CheckState.Checked)
+            if (rea.Result == CheckState.Checked)
             {
-                ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewBase.ViewportSettingsFlags.TransparentMouse;
+                ViewToEdit!.ViewportSettings |= ViewportSettingsFlags.TransparentMouse;
             }
             else
             {
-                ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewBase.ViewportSettingsFlags.TransparentMouse;
+                ViewToEdit!.ViewportSettings &= ~ViewportSettingsFlags.TransparentMouse;
             }
         }
 
@@ -327,9 +323,9 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbVerticalScrollBar.CheckedStateChanging += VerticalScrollBarToggle;
 
-        void VerticalScrollBarToggle (object? sender, ResultEventArgs<CheckState> e)
+        void VerticalScrollBarToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            ViewToEdit!.VerticalScrollBar.Visible = e.Result == CheckState.Checked;
+            ViewToEdit!.VerticalScrollBar.Visible = rea.Result == CheckState.Checked;
         }
 
         _cbAutoShowVerticalScrollBar = new ()
@@ -341,9 +337,9 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbAutoShowVerticalScrollBar.CheckedStateChanging += AutoShowVerticalScrollBarToggle;
 
-        void AutoShowVerticalScrollBarToggle (object? sender, ResultEventArgs<CheckState> e)
+        void AutoShowVerticalScrollBarToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            ViewToEdit!.VerticalScrollBar.AutoShow = e.Result == CheckState.Checked;
+            ViewToEdit!.VerticalScrollBar.AutoShow = rea.Result == CheckState.Checked;
         }
 
         _cbHorizontalScrollBar = new ()
@@ -355,9 +351,9 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbHorizontalScrollBar.CheckedStateChanging += HorizontalScrollBarToggle;
 
-        void HorizontalScrollBarToggle (object? sender, ResultEventArgs<CheckState> e)
+        void HorizontalScrollBarToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            ViewToEdit!.HorizontalScrollBar.Visible = e.Result == CheckState.Checked;
+            ViewToEdit!.HorizontalScrollBar.Visible = rea.Result == CheckState.Checked;
         }
 
         _cbAutoShowHorizontalScrollBar = new ()
@@ -369,9 +365,9 @@ public sealed class ViewportSettingsEditor : EditorBase
         };
         _cbAutoShowHorizontalScrollBar.CheckedStateChanging += AutoShowHorizontalScrollBarToggle;
 
-        void AutoShowHorizontalScrollBarToggle (object? sender, ResultEventArgs<CheckState> e)
+        void AutoShowHorizontalScrollBarToggle (object? sender, ResultEventArgs<CheckState> rea)
         {
-            ViewToEdit!.HorizontalScrollBar.AutoShow = e.Result == CheckState.Checked;
+            ViewToEdit!.HorizontalScrollBar.AutoShow = rea.Result == CheckState.Checked;
         }
 
         Add (

+ 25 - 2
Examples/UICatalog/Scenarios/ShadowStyles.cs

@@ -62,6 +62,22 @@ public class ShadowStyles : Scenario
         shadowWindow.Add (buttonInWin);
         app.Add (shadowWindow);
 
+        Window shadowWindow2 = new ()
+        {
+
+            Id = "shadowWindow2",
+            X = Pos.Right (editor) + 10,
+            Y = 10,
+            Width = Dim.Percent (30),
+            Height = Dim.Percent (30),
+            Title = "Shadow Window #2",
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped,
+            BorderStyle = LineStyle.Double,
+            ShadowStyle = ShadowStyle.Transparent,
+        };
+        app.Add (shadowWindow2);
+
+
         var button = new Button
         {
             Id = "button",
@@ -69,6 +85,7 @@ public class ShadowStyles : Scenario
             Y = Pos.Center (), Text = "Button",
             ShadowStyle = ShadowStyle.Opaque
         };
+        button.Accepting += ButtonOnAccepting;
 
         ColorPicker colorPicker = new ()
         {
@@ -77,12 +94,12 @@ public class ShadowStyles : Scenario
             Id = "colorPicker16",
             X = Pos.Center (),
             Y = Pos.AnchorEnd (),
-            Width = Dim.Percent(80),
+            Width = Dim.Percent (80),
         };
         colorPicker.ColorChanged += (sender, args) =>
                                     {
                                         var normal = app.GetScheme ().Normal;
-                                        app.SetScheme (app.GetScheme() with {Normal = new Attribute(normal.Foreground, args.Result)});
+                                        app.SetScheme (app.GetScheme () with { Normal = new Attribute (normal.Foreground, args.Result) });
                                     };
         app.Add (button, colorPicker);
 
@@ -96,4 +113,10 @@ public class ShadowStyles : Scenario
         Application.Shutdown ();
 
     }
+
+    private void ButtonOnAccepting (object sender, CommandEventArgs e)
+    {
+        MessageBox.Query ((sender as View)?.App, "Hello", "You pushed the button!");
+        e.Handled = true;
+    }
 }

+ 2 - 0
Examples/UICatalog/Scenarios/ViewportSettings.cs

@@ -108,6 +108,7 @@ public class ViewportSettings : Scenario
 
         var adornmentsEditor = new AdornmentsEditor
         {
+            BorderStyle = LineStyle.Single,
             X = Pos.AnchorEnd (),
             AutoSelectViewToEdit = true,
             ShowViewIdentifier = true
@@ -224,6 +225,7 @@ public class ViewportSettings : Scenario
         view.Initialized += (s, e) =>
                                               {
                                                   viewportSettingsEditor.ViewToEdit = view;
+                                                  adornmentsEditor.ViewToEdit = view;
                                               };
         view.SetFocus ();
         Application.Run (app);

+ 45 - 25
Examples/UICatalog/Scenarios/WideGlyphs.cs

@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
 
 using System.Text;
 
@@ -24,6 +24,27 @@ public sealed class WideGlyphs : Scenario
             BorderStyle = LineStyle.None
         };
 
+        // Add Editors
+
+        AdornmentsEditor adornmentsEditor = new ()
+        {
+            BorderStyle = LineStyle.Single,
+            X = Pos.AnchorEnd (),
+            AutoSelectViewToEdit = true,
+            AutoSelectAdornments = false,
+            ShowViewIdentifier = true
+        };
+        appWindow.Add (adornmentsEditor);
+
+        ViewportSettingsEditor viewportSettingsEditor = new ()
+        {
+            BorderStyle = LineStyle.Single,
+            Y = Pos.AnchorEnd (),
+            X = Pos.AnchorEnd (),
+            AutoSelectViewToEdit = true,
+        };
+        appWindow.Add (viewportSettingsEditor);
+
         // Build the array of codepoints once when subviews are laid out
         appWindow.SubViewsLaidOut += (s, _) =>
         {
@@ -53,7 +74,7 @@ public sealed class WideGlyphs : Scenario
         // Fill the window with the pre-built codepoints array
         // For detailed documentation on the draw code flow from Application.Run to this event,
         // see WideGlyphs.DrawFlow.md in this directory
-        appWindow.DrawingContent += (s, _) =>
+        appWindow.DrawingContent += (s, e) =>
         {
             View? view = s as View;
             if (view is null || _codepoints is null)
@@ -69,28 +90,16 @@ public sealed class WideGlyphs : Scenario
                     Rune codepoint = _codepoints [r, c];
                     if (codepoint != default (Rune))
                     {
-                        view.AddRune (c, r, codepoint);
+                        view.Move (c, r);
+                        Attribute attr = view.GetAttributeForRole (VisualRole.Normal);
+                        view.SetAttribute (attr with { Background = attr.Background + (r * 5) });
+                        view.AddRune (codepoint);
                     }
                 }
             }
+            e.DrawContext?.AddDrawnRectangle (view.Viewport);
         };
 
-        Line verticalLineAtEven = new ()
-        {
-            X = 10,
-            Orientation = Orientation.Vertical,
-            Length = Dim.Fill ()
-        };
-        appWindow.Add (verticalLineAtEven);
-
-        Line verticalLineAtOdd = new ()
-        {
-            X = 25,
-            Orientation = Orientation.Vertical,
-            Length = Dim.Fill ()
-        };
-        appWindow.Add (verticalLineAtOdd);
-
         View arrangeableViewAtEven = new ()
         {
             CanFocus = true,
@@ -99,16 +108,19 @@ public sealed class WideGlyphs : Scenario
             Y = 5,
             Width = 15,
             Height = 5,
-            //BorderStyle = LineStyle.Dashed,
+            //BorderStyle = LineStyle.Dashed
         };
 
+        arrangeableViewAtEven.SetScheme (new () { Normal = new (Color.Black, Color.Green) });
+
         // Proves it's not LineCanvas related
         arrangeableViewAtEven!.Border!.Thickness = new (1);
-        arrangeableViewAtEven.Border.Add(new View () { Height = Dim.Auto(), Width = Dim.Auto(), Text = "Even" });
+        arrangeableViewAtEven.Border.Add (new View () { Height = Dim.Auto (), Width = Dim.Auto (), Text = "Even" });
         appWindow.Add (arrangeableViewAtEven);
 
-        View arrangeableViewAtOdd = new ()
+        Button arrangeableViewAtOdd = new ()
         {
+            Title = $"你 {Glyphs.Apple}",
             CanFocus = true,
             Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
             X = 31,
@@ -116,7 +128,12 @@ public sealed class WideGlyphs : Scenario
             Width = 15,
             Height = 5,
             BorderStyle = LineStyle.Dashed,
+            SchemeName = "error"
         };
+        arrangeableViewAtOdd.Accepting += (sender, args) =>
+                                          {
+                                              MessageBox.Query ((sender as View)?.App, "Button Pressed", "You Pressed it!");
+                                          };
         appWindow.Add (arrangeableViewAtOdd);
 
         var superView = new View
@@ -124,11 +141,14 @@ public sealed class WideGlyphs : Scenario
             CanFocus = true,
             X = 30, // on an even column to start
             Y = Pos.Center (),
-            Width = Dim.Auto () + 4,
-            Height = Dim.Auto () + 1,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
             BorderStyle = LineStyle.Single,
-            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable
+            Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
+            ShadowStyle = ShadowStyle.Transparent,
         };
+        superView.Margin!.ShadowSize = superView.Margin!.ShadowSize with { Width = 2 };
+
 
         Rune codepoint = Glyphs.Apple;
 

+ 1 - 1
Terminal.Gui/App/ApplicationImpl.Run.cs

@@ -36,7 +36,7 @@ internal partial class ApplicationImpl
     public event EventHandler<EventArgs<IApplication?>>? Iteration;
 
     /// <inheritdoc/>
-    public void RaiseIteration () { Iteration?.Invoke (null, new (this)); }
+    public void RaiseIteration () { Iteration?.Invoke (this, new (this)); }
 
     #endregion Main Loop Iteration
 

+ 1 - 1
Terminal.Gui/App/ApplicationNavigation.cs

@@ -89,7 +89,7 @@ public class ApplicationNavigation
 
         _focused = value;
 
-        FocusedChanged?.Invoke (null, EventArgs.Empty);
+        FocusedChanged?.Invoke (this, EventArgs.Empty);
     }
 
     /// <summary>

+ 1 - 0
Terminal.Gui/App/CWP/CWPEventHelper.cs

@@ -49,6 +49,7 @@ public static class CWPEventHelper
             return false;
         }
 
+        // BUGBUG: This should pass this not null; need to test
         eventHandler.Invoke (null, args);
         return args.Handled;
     }

+ 4 - 2
Terminal.Gui/App/CWP/CWPPropertyHelper.cs

@@ -84,6 +84,7 @@ public static class CWPPropertyHelper
             }
         }
 
+        // BUGBUG: This should pass this not null; need to test
         changingEvent?.Invoke (null, args);
 
         if (args.Handled)
@@ -100,13 +101,14 @@ public static class CWPPropertyHelper
         }
 
         finalValue = args.NewValue;
-        
+
         // Do the work (set backing field, update related properties, etc.) BEFORE raising Changed events
         doWork (finalValue);
-        
+
         ValueChangedEventArgs<T> changedArgs = new (currentValue, finalValue);
         currentValue = finalValue;
         onChanged?.Invoke (changedArgs);
+        // BUGBUG: This should pass this not null; need to test
         changedEvent?.Invoke (null, changedArgs);
 
         return true;

+ 2 - 0
Terminal.Gui/App/CWP/CWPWorkflowHelper.cs

@@ -53,6 +53,7 @@ public static class CWPWorkflowHelper
             return true;
         }
 
+        // BUGBUG: This should pass this not null; need to test
         eventHandler?.Invoke (null, args);
         if (args.Handled)
         {
@@ -112,6 +113,7 @@ public static class CWPWorkflowHelper
             return args.Result!;
         }
 
+        // BUGBUG: This should pass this not null; need to test
         eventHandler?.Invoke (null, args);
 
         if (!args.Handled)

+ 2 - 2
Terminal.Gui/App/Keyboard/KeyboardImpl.cs

@@ -160,7 +160,7 @@ internal class KeyboardImpl : IKeyboard, IDisposable
         //#endif
 
         // TODO: This should match standard event patterns
-        KeyDown?.Invoke (null, key);
+        KeyDown?.Invoke (this, key);
 
         if (key.Handled)
         {
@@ -216,7 +216,7 @@ internal class KeyboardImpl : IKeyboard, IDisposable
             return true;
         }
 
-        KeyUp?.Invoke (null, key);
+        KeyUp?.Invoke (this, key);
 
         if (key.Handled)
         {

+ 1 - 1
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -86,7 +86,7 @@ internal class MouseImpl : IMouse, IDisposable
             mouseEvent.View = deepestViewUnderMouse;
         }
 
-        MouseEvent?.Invoke (null, mouseEvent);
+        MouseEvent?.Invoke (this, mouseEvent);
 
         if (mouseEvent.Handled)
         {

+ 5 - 0
Terminal.Gui/Drawing/Glyphs.cs

@@ -26,6 +26,11 @@ public class Glyphs
     // IMPORTANT: Configuration Manager test SaveDefaults uses this class to generate the default config file
     // IMPORTANT: in ./UnitTests/bin/Debug/netX.0/config.json
 
+    /// <summary>Unicode replacement character; used by Drivers when rendering in cases where a wide glyph can't
+    /// be output because it would be clipped. Defaults to ' ' (Space).</summary>
+    [ConfigurationProperty (Scope = typeof (ThemeScope))]
+    public static Rune WideGlyphReplacement { get; set; } = (Rune)' ';
+
     /// <summary>File icon.  Defaults to ☰ (Trigram For Heaven)</summary>
     [ConfigurationProperty (Scope = typeof (ThemeScope))]
     public static Rune File { get; set; } = (Rune)'☰';

+ 3 - 3
Terminal.Gui/Drivers/DriverImpl.cs

@@ -146,6 +146,9 @@ internal class DriverImpl : IDriver
 
     private readonly IOutput _output;
 
+    /// <inheritdoc />
+    public IOutputBuffer GetOutputBuffer () => OutputBuffer;
+
     public IOutput GetOutput () => _output;
 
     private readonly IInputProcessor _inputProcessor;
@@ -330,9 +333,6 @@ internal class DriverImpl : IDriver
     /// <inheritdoc/>
     public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); }
 
-    /// <inheritdoc/>
-    public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); }
-
     /// <inheritdoc/>
     public Attribute SetAttribute (Attribute newAttribute)
     {

+ 7 - 9
Terminal.Gui/Drivers/IDriver.cs

@@ -64,7 +64,13 @@ public interface IDriver : IDisposable
     IInputProcessor GetInputProcessor ();
 
     /// <summary>
-    ///     Gets the output handler responsible for writing to the terminal.
+    ///     Gets the <see cref="IOutputBuffer"/> containing the buffered screen contents.
+    /// </summary>
+    /// <returns></returns>
+    IOutputBuffer GetOutputBuffer ();
+
+    /// <summary>
+    ///     Gets the <see cref="IOutput"/> responsible for writing to the terminal.
     /// </summary>
     IOutput GetOutput ();
 
@@ -257,14 +263,6 @@ public interface IDriver : IDisposable
     /// <param name="rune">The Rune used to fill the rectangle</param>
     void FillRect (Rectangle rect, Rune rune = default);
 
-    /// <summary>
-    ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
-    ///     that calls <see cref="IDriver.FillRect(System.Drawing.Rectangle,System.Text.Rune)"/>.
-    /// </summary>
-    /// <param name="rect"></param>
-    /// <param name="c"></param>
-    void FillRect (Rectangle rect, char c);
-
     /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
     /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
     /// <param name="c">C.</param>

+ 12 - 2
Terminal.Gui/Drivers/IOutputBuffer.cs

@@ -1,5 +1,4 @@
-
-namespace Terminal.Gui.Drivers;
+namespace Terminal.Gui.Drivers;
 
 /// <summary>
 ///     Represents the desired screen state for console rendering. This interface provides methods for building up
@@ -128,4 +127,15 @@ public interface IOutputBuffer
     ///     Changing this may have unexpected consequences.
     /// </summary>
     int Top { get; set; }
+
+    /// <summary>
+    ///     Sets the replacement character that will be used when a wide glyph (double-width character) cannot fit in the
+    ///     available space.
+    ///     If not set, the default will be <see cref="Glyphs.WideGlyphReplacement"/>.
+    /// </summary>
+    /// <param name="column1ReplacementChar">
+    ///     The character used when the first column of a wide character is invalid (for example, when it is overlapped by the
+    ///     trailing half of a previous wide character).
+    /// </param>
+    void SetWideGlyphReplacement (Rune column1ReplacementChar);
 }

+ 7 - 0
Terminal.Gui/Drivers/OutputBase.cs

@@ -127,6 +127,13 @@ public abstract class OutputBase
                     Cell cell = buffer.Contents [row, col];
                     buffer.Contents [row, col].IsDirty = false;
                     AppendCellAnsi (cell, outputStringBuilder, ref redrawAttr, ref _redrawTextStyle, cols, ref col, ref outputWidth);
+
+                    if (col != lastCol)
+                    {
+                        // Was a wide grapheme so mark clean next cell
+                        // See https://github.com/gui-cs/Terminal.Gui/issues/4466
+                        buffer.Contents [row, col].IsDirty = false;
+                    }
                 }
             }
 

+ 85 - 65
Terminal.Gui/Drivers/OutputBufferImpl.cs

@@ -65,6 +65,14 @@ public class OutputBufferImpl : IOutputBuffer
     /// <summary>The topmost row in the terminal.</summary>
     public virtual int Top { get; set; } = 0;
 
+    private Rune _column1ReplacementChar = Glyphs.WideGlyphReplacement;
+
+    /// <inheritdoc />
+    public void SetWideGlyphReplacement (Rune column1ReplacementChar)
+    {
+        _column1ReplacementChar = column1ReplacementChar;
+    }
+
     /// <summary>
     ///     Indicates which lines have been modified and need to be redrawn.
     /// </summary>
@@ -86,7 +94,7 @@ public class OutputBufferImpl : IOutputBuffer
         get => _clip;
         set
         {
-            if (_clip == value)
+            if (ReferenceEquals (_clip, value))
             {
                 return;
             }
@@ -94,10 +102,7 @@ public class OutputBufferImpl : IOutputBuffer
             _clip = value;
 
             // Don't ever let Clip be bigger than Screen
-            if (_clip is { })
-            {
-                _clip.Intersect (Screen);
-            }
+            _clip?.Intersect (Screen);
         }
     }
 
@@ -105,7 +110,7 @@ public class OutputBufferImpl : IOutputBuffer
     /// <remarks>
     ///     <para>
     ///         When the method returns, <see cref="Col"/> will be incremented by the number of columns
-    ///         <paramref name="rune"/> required, even if the new column value is outside of the <see cref="Clip"/> or screen
+    ///         <paramref name="rune"/> required, even if the new column value is outside the <see cref="Clip"/> or screen
     ///         dimensions defined by <see cref="Cols"/>.
     ///     </para>
     ///     <para>
@@ -156,25 +161,19 @@ public class OutputBufferImpl : IOutputBuffer
         Clip ??= new (Screen);
         Rectangle clipRect = Clip!.GetBounds ();
 
-        string text = grapheme;
-        int textWidth = -1;
+        int printableGraphemeWidth = -1;
 
         lock (Contents)
         {
-            bool validLocation = IsValidLocation (text, Col, Row);
-
-            if (validLocation)
+            if (IsValidLocation (grapheme, Col, Row))
             {
-                text = text.MakePrintable ();
-                textWidth = text.GetColumns ();
-
                 // Set attribute and mark dirty for current cell
-                Contents [Row, Col].Attribute = CurrentAttribute;
-                Contents [Row, Col].IsDirty = true;
-
-                InvalidateOverlappedWideGlyph ();
+                SetAttributeAndDirty (Col, Row);
+                InvalidateOverlappedWideGlyph (Col, Row);
 
-                WriteGraphemeByWidth (text, textWidth, clipRect);
+                string printableGrapheme = grapheme.MakePrintable ();
+                printableGraphemeWidth = printableGrapheme.GetColumns ();
+                WriteGraphemeByWidth (Col, Row, printableGrapheme, printableGraphemeWidth, clipRect);
 
                 DirtyLines [Row] = true;
             }
@@ -183,97 +182,121 @@ public class OutputBufferImpl : IOutputBuffer
             // Keep Col/Row updates inside the lock to prevent race conditions
             Col++;
 
-            if (textWidth > 1)
+            if (printableGraphemeWidth > 1)
             {
                 // Skip the second column of a wide character
-                // IMPORTANT: We do NOT modify column N+1's IsDirty or Attribute here.
-                // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+                // See issue: https://github.com/gui-cs/Terminal.Gui/issues/4492
+                // Test: AddStr_WideGlyph_Second_Column_Attribute_Outputs_Correctly
+                // Test: AddStr_WideGlyph_Second_Column_Attribute_Set_When_In_Clip
+                if (Clip.Contains (Col, Row))
+                {
+                    // IMPORTANT: We do NOT modify column N+1's IsDirty or Attribute here.
+                    // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+                    Contents [Row, Col].Attribute = CurrentAttribute;
+                }
+
+                // Advance cursor again for wide character
                 Col++;
             }
         }
     }
 
     /// <summary>
-    ///     If we're writing at an odd column and there's a wide glyph to our left,
+    ///     INTERNAL: Helper to set the attribute and mark the cell as dirty.
+    /// </summary>
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    private void SetAttributeAndDirty (int col, int row)
+    {
+        Contents! [row, col].Attribute = CurrentAttribute;
+        Contents [row, col].IsDirty = true;
+    }
+
+    /// <summary>
+    ///     INTERNAL: If we're writing at an odd column and there's a wide glyph to our left,
     ///     invalidate it since we're overwriting the second half.
     /// </summary>
-    private void InvalidateOverlappedWideGlyph ()
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    private void InvalidateOverlappedWideGlyph (int col, int row)
     {
-        if (Col > 0 && Contents! [Row, Col - 1].Grapheme.GetColumns () > 1)
+        if (col > 0 && Contents! [row, col - 1].Grapheme.GetColumns () > 1)
         {
-            Contents [Row, Col - 1].Grapheme = Rune.ReplacementChar.ToString ();
-            Contents [Row, Col - 1].IsDirty = true;
+            Contents [row, col - 1].Grapheme = _column1ReplacementChar.ToString ();
+            Contents [row, col - 1].IsDirty = true;
         }
     }
 
     /// <summary>
-    ///     Writes a grapheme to the buffer based on its width (0, 1, or 2 columns).
+    ///     INTERNAL: Writes a Grapheme to the buffer based on its width (0, 1, or 2 columns).
     /// </summary>
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
     /// <param name="text">The printable text to write.</param>
     /// <param name="textWidth">The column width of the text.</param>
     /// <param name="clipRect">The clipping rectangle.</param>
-    private void WriteGraphemeByWidth (string text, int textWidth, Rectangle clipRect)
+    private void WriteGraphemeByWidth (int col, int row, string text, int textWidth, Rectangle clipRect)
     {
         switch (textWidth)
         {
             case 0:
             case 1:
-                WriteSingleWidthGrapheme (text, clipRect);
+                WriteGrapheme (col, row, text, clipRect);
 
                 break;
 
             case 2:
-                WriteWideGrapheme (text);
+                WriteWideGrapheme (col, row, text);
 
                 break;
 
             default:
                 // Negative width or non-spacing character (shouldn't normally occur)
-                Contents! [Row, Col].Grapheme = " ";
-                Contents [Row, Col].IsDirty = false;
+                Contents! [row, col].Grapheme = " ";
+                Contents [row, col].IsDirty = false;
 
                 break;
         }
     }
 
     /// <summary>
-    ///     Writes a single-width character (0 or 1 column wide).
+    ///     INTERNAL: Writes a (0 or 1 column wide) Grapheme.
     /// </summary>
-    private void WriteSingleWidthGrapheme (string text, Rectangle clipRect)
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    /// <param name="grapheme">The single-width Grapheme to write.</param>
+    /// <param name="clipRect">The clipping rectangle.</param>
+    private void WriteGrapheme (int col, int row, string grapheme, Rectangle clipRect)
     {
-        Contents! [Row, Col].Grapheme = text;
+        Debug.Assert (grapheme.GetColumns () < 2);
+        Contents! [row, col].Grapheme = grapheme;
 
         // Mark the next cell as dirty to ensure proper rendering of adjacent content
-        if (Col < clipRect.Right - 1 && Col + 1 < Cols)
+        if (col < clipRect.Right - 1 && col + 1 < Cols)
         {
-            Contents [Row, Col + 1].IsDirty = true;
+            Contents [row, col + 1].IsDirty = true;
         }
     }
 
     /// <summary>
-    ///     Writes a wide character (2 columns wide) handling clipping and partial overlap cases.
+    ///     INTERNAL: Writes a wide Grapheme (2 columns wide) handling clipping and partial overlap cases.
     /// </summary>
-    private void WriteWideGrapheme (string text)
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    /// <param name="grapheme">The wide Grapheme to write.</param>
+    private void WriteWideGrapheme (int col, int row, string grapheme)
     {
-        if (!Clip!.Contains (Col + 1, Row))
+        Debug.Assert (grapheme.GetColumns () == 2);
+        if (!Clip!.Contains (col + 1, row))
         {
             // Second column is outside clip - can't fit wide char here
-            Contents! [Row, Col].Grapheme = Rune.ReplacementChar.ToString ();
-        }
-        else if (!Clip.Contains (Col, Row))
-        {
-            // First column is outside clip but second isn't
-            // Mark second column as replacement to indicate partial overlap
-            if (Col + 1 < Cols)
-            {
-                Contents! [Row, Col + 1].Grapheme = Rune.ReplacementChar.ToString ();
-            }
+            Contents! [row, col].Grapheme = _column1ReplacementChar.ToString ();
         }
         else
         {
             // Both columns are in bounds - write the wide character
             // It will naturally render across both columns when output to the terminal
-            Contents! [Row, Col].Grapheme = text;
+            Contents! [row, col].Grapheme = grapheme;
 
             // DO NOT modify column N+1 here!
             // The wide glyph will naturally render across both columns.
@@ -288,7 +311,7 @@ public class OutputBufferImpl : IOutputBuffer
     {
         Contents = new Cell [Rows, Cols];
 
-        //CONCURRENCY: Unsynchronized access to Clip isn't safe.
+        // CONCURRENCY: Unsynchronized access to Clip isn't safe.
         // TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere.
         Clip = new (Screen);
 
@@ -311,9 +334,6 @@ public class OutputBufferImpl : IOutputBuffer
                 DirtyLines [row] = true;
             }
         }
-
-        // TODO: Who uses this and why? I am removing for now - this class is a state class not an events class
-        //ClearedContents?.Invoke (this, EventArgs.Empty);
     }
 
     /// <summary>Tests whether the specified coordinate are valid for drawing the specified Text.</summary>
@@ -342,8 +362,9 @@ public class OutputBufferImpl : IOutputBuffer
     /// <inheritdoc/>
     public void FillRect (Rectangle rect, Rune rune)
     {
+        Rectangle clipBounds = Clip?.GetBounds () ?? Screen;
         // BUGBUG: This should be a method on Region
-        rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen);
+        rect = Rectangle.Intersect (rect, clipBounds);
 
         lock (Contents!)
         {
@@ -356,11 +377,12 @@ public class OutputBufferImpl : IOutputBuffer
                         continue;
                     }
 
-                    Contents [r, c] = new ()
-                    {
-                        Grapheme = rune != default (Rune) ? rune.ToString () : " ",
-                        Attribute = CurrentAttribute, IsDirty = true
-                    };
+                    // We could call AddGrapheme here, but that would acquire the lock again.
+                    // So we inline the logic instead.
+                    SetAttributeAndDirty (c, r);
+                    InvalidateOverlappedWideGlyph (c, r);
+                    string grapheme = rune != default (Rune) ? rune.ToString () : " ";
+                    WriteGraphemeByWidth (c, r, grapheme, grapheme.GetColumns (), clipBounds);
                 }
             }
         }
@@ -379,7 +401,6 @@ public class OutputBufferImpl : IOutputBuffer
         }
     }
 
-    // TODO: Make internal once Menu is upgraded
     /// <summary>
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
     ///     Used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
@@ -393,9 +414,8 @@ public class OutputBufferImpl : IOutputBuffer
     /// </remarks>
     /// <param name="col">Column to move to.</param>
     /// <param name="row">Row to move to.</param>
-    public virtual void Move (int col, int row)
+    public void Move (int col, int row)
     {
-        //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0));
         Col = col;
         Row = row;
     }

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

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

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

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

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

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

+ 5 - 5
Terminal.Gui/ViewBase/View.Mouse.cs

@@ -256,7 +256,7 @@ public partial class View // Mouse APIs
     ///         <item>
     ///             <description>
     ///                 Invokes commands bound to mouse clicks via <see cref="MouseBindings"/>
-    ///                 (default: <see cref="Command.Select"/> → <see cref="Selecting"/> event)
+    ///                 (default: <see cref="Command.Activate"/> → <see cref="Activating"/> event)
     ///             </description>
     ///         </item>
     ///         <item>
@@ -295,7 +295,7 @@ public partial class View // Mouse APIs
     /// <seealso cref="MouseEvent"/>
     /// <seealso cref="OnMouseEvent"/>
     /// <seealso cref="MouseBindings"/>
-    /// <seealso cref="Selecting"/>
+    /// <seealso cref="Activating"/>
     /// <seealso cref="WantContinuousButtonPressed"/>
     /// <seealso cref="HighlightStates"/>
     public bool? NewMouseEvent (MouseEventArgs mouseEvent)
@@ -414,8 +414,8 @@ public partial class View // Mouse APIs
     /// <summary>
     ///     INTERNAL: For cases where the view is grabbed and the mouse is pressed, this method handles the pressed events from
     ///     the driver.
-    ///     When  <see cref="WantContinuousButtonPressed"/> is set, this method will raise the Clicked/Selecting event
-    ///     via <see cref="Command.Select"/> each time it is called (after the first time the mouse is pressed).
+    ///     When  <see cref="WantContinuousButtonPressed"/> is set, this method will raise the Clicked/Activating event
+    ///     via <see cref="Command.Activate"/> each time it is called (after the first time the mouse is pressed).
     /// </summary>
     /// <param name="mouseEvent"></param>
     /// <returns><see langword="true"/>, if processing should stop, <see langword="false"/> otherwise.</returns>
@@ -531,7 +531,7 @@ public partial class View // Mouse APIs
     /// <summary>
     ///     INTERNAL API: Converts mouse click events into <see cref="Command"/>s by invoking the commands bound
     ///     to the mouse button via <see cref="MouseBindings"/>. By default, all mouse clicks are bound to
-    ///     <see cref="Command.Select"/> which raises the <see cref="Selecting"/> event.
+    ///     <see cref="Command.Activate"/> which raises the <see cref="Activating"/> event.
     /// </summary>
     protected bool RaiseCommandsBoundToMouse (MouseEventArgs args)
     {

+ 1 - 0
Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs

@@ -388,6 +388,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
                                );
         }
 
+        _popup.Visible = true;
         _popup.Move (0, 0);
 
         for (var i = 0; i < toRender.Length; i++)

+ 4 - 0
Terminal.sln.DotSettings

@@ -382,6 +382,7 @@
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableEditorConfigSupport/@EntryValue">False</s:Boolean>
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/ShowEditorConfigStatusBarIndicator/@EntryValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/SyncToVisualStudio/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BMP/@EntryIndexedValue">BMP</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CWP/@EntryIndexedValue">CWP</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LL/@EntryIndexedValue">LL</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LR/@EntryIndexedValue">LR</s:String>
@@ -416,6 +417,7 @@
 	<s:Boolean x:Key="/Default/GrammarAndSpelling/GrammarChecking/Exceptions/=Attribute_0020attribute/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=conhost/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Decscusr/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=diag/@EntryIndexedValue">True</s:Boolean>
 	
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Gainsboro/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Gonek/@EntryIndexedValue">True</s:Boolean>
@@ -426,9 +428,11 @@
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Mazing/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=ogonek/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Quattro/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=repro/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Roslynator/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=RRGGBB/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=runnables/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=snek/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Toplevel/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Runnables/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Ungrab/@EntryIndexedValue">True</s:Boolean>

+ 6 - 5
Tests/UnitTests/View/Draw/ClipTests.cs

@@ -52,13 +52,13 @@ public class ClipTests (ITestOutputHelper _output)
         Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme);
 
         // When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
-        view.AddRune (0, 0, Rune.ReplacementChar);
+        view.AddRune (0, 0, Glyphs.WideGlyphReplacement);
         Assert.Equal (" ", Application.Driver?.Contents! [2, 2].Grapheme);
 
-        view.AddRune (-1, -1, Rune.ReplacementChar);
+        view.AddRune (-1, -1, Glyphs.WideGlyphReplacement);
         Assert.Equal ("P", Application.Driver?.Contents! [1, 1].Grapheme);
 
-        view.AddRune (1, 1, Rune.ReplacementChar);
+        view.AddRune (1, 1, Glyphs.WideGlyphReplacement);
         Assert.Equal ("P", Application.Driver?.Contents! [3, 3].Grapheme);
     }
 
@@ -178,6 +178,7 @@ public class ClipTests (ITestOutputHelper _output)
     public void Clipping_Wide_Runes ()
     {
         Application.Driver!.SetScreenSize (30, 1);
+        Application.Driver!.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
 
         var top = new View
         {
@@ -231,9 +232,9 @@ public class ClipTests (ITestOutputHelper _output)
         //                            012 34 56 78 90 12 34 56 78 90 12 34 56 78
         //                            │こ れ  は 広 い  ル ー ン  ラ イ ン で  す 。
         //                            01 2345678901234 56 78 90 12 34 56 
-        //                            │� |0123456989│� ン  ラ イ ン で  す 。
+        //                            │① |0123456989│① ン  ラ イ ン で  す 。
         expectedOutput = """
-                         ││0123456789│ ンラインです。
+                         ││0123456789│ ンラインです。
                          """;
 
         DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output);

+ 125 - 48
Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs

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

+ 3 - 3
Tests/UnitTestsParallelizable/Drivers/ClipRegionTests.cs

@@ -23,12 +23,12 @@ public class ClipRegionTests (ITestOutputHelper output) : FakeDriverBase
         Assert.Equal ("x", driver.Contents [5, 5].Grapheme);
 
         // Clear the contents
-        driver.FillRect (new Rectangle (0, 0, driver.Rows, driver.Cols), ' ');
+        driver.FillRect (new (0, 0, driver.Rows, driver.Cols), new Rune(' '));
         Assert.Equal (" ", driver.Contents [0, 0].Grapheme);
 
         // Setup the region with a single rectangle, fill screen with 'x'
-        driver.Clip = new (new Rectangle (5, 5, 5, 5));
-        driver.FillRect (new Rectangle (0, 0, driver.Rows, driver.Cols), 'x');
+        driver.Clip = new (new (5, 5, 5, 5));
+        driver.FillRect (new (0, 0, driver.Rows, driver.Cols), new Rune ('x'));
         Assert.Equal (" ", driver.Contents [0, 0].Grapheme);
         Assert.Equal (" ", driver.Contents [4, 9].Grapheme);
         Assert.Equal ("x", driver.Contents [5, 5].Grapheme);

+ 4 - 2
Tests/UnitTestsParallelizable/Drivers/DriverTests.cs

@@ -1,4 +1,5 @@
 #nullable enable
+using System.Text;
 using UnitTests;
 using Xunit.Abstractions;
 
@@ -104,6 +105,7 @@ public class DriverTests (ITestOutputHelper output) : FakeDriverBase
         IApplication? app = Application.Create ();
         app.Init (driverName);
         IDriver driver = app.Driver!;
+        driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
 
         // Need to force "windows" driver to override legacy console mode for this test
         driver.IsLegacyConsole = false;
@@ -127,14 +129,14 @@ public class DriverTests (ITestOutputHelper output) : FakeDriverBase
 
         DriverAssert.AssertDriverContentsAre (
                                               """
-                                              ┌─┐🍎
+                                              ┌─┐🍎
                                               """,
                                               output,
                                               driver);
 
         driver.Refresh ();
 
-        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m┌─┐🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
                                            output, driver);
     }
 }

+ 10 - 5
Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs

@@ -1,4 +1,7 @@
-namespace DriverTests;
+using System.Text;
+using Terminal.Gui.Drivers;
+
+namespace DriverTests;
 
 public class OutputBaseTests
 {
@@ -161,6 +164,8 @@ public class OutputBaseTests
         // FakeOutput exposes this because it's in test scope
         var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
         IOutputBuffer buffer = output.GetLastBuffer ()!;
+        buffer.SetWideGlyphReplacement ((Rune)'①');
+
         buffer.SetSize (3, 1);
 
         // Write '🦮' at col 0 and 'A' at col 2
@@ -189,9 +194,9 @@ public class OutputBaseTests
         // Column 0 was written (wide glyph)
         Assert.False (buffer.Contents! [0, 0].IsDirty);
 
-        // Column 1 was skipped by OutputBase.Write because column 0 had a wide glyph
-        // So its dirty flag remains true (it was initialized as dirty by ClearContents)
-        Assert.True (buffer.Contents! [0, 1].IsDirty);
+        // Column 1 was marked as clean by OutputBase.Write when it processed the wide glyph at column 0
+        // See: https://github.com/gui-cs/Terminal.Gui/issues/4466
+        Assert.False (buffer.Contents! [0, 1].IsDirty);
 
         // Column 2 was written ('A')
         Assert.False (buffer.Contents! [0, 2].IsDirty);
@@ -209,7 +214,7 @@ public class OutputBaseTests
 
         output.Write (buffer);
 
-        Assert.Contains ("", output.GetLastOutput ());
+        Assert.Contains ("", output.GetLastOutput ());
         Assert.Contains ("X", output.GetLastOutput ());
 
         // Dirty flags cleared for the written cells

+ 544 - 0
Tests/UnitTestsParallelizable/Drivers/OutputBufferWideCharTests.cs

@@ -0,0 +1,544 @@
+using System.Text;
+
+namespace DriverTests;
+
+/// <summary>
+///     Tests for https://github.com/gui-cs/Terminal.Gui/issues/4466.
+///     These tests validate that FillRect properly handles wide characters when overlapping existing content.
+///     Specifically, they ensure that wide characters are properly invalidated and replaced when a MessageBox border or
+///     similar UI element is drawn over them, preventing visual corruption.
+/// </summary>
+public class OutputBufferWideCharTests
+{
+    /// <summary>
+    ///     Tests that FillRect properly invalidates wide characters when overwriting them.
+    ///     This is the core issue in #4466 - when a MessageBox border is drawn over Chinese text,
+    ///     the wide characters need to be properly invalidated.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void FillRect_OverwritesWideChar_InvalidatesProperly ()
+    {
+        // Arrange - Create a buffer and draw a wide character
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5, Cols = 10,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Draw a Chinese character (2 columns wide) at position 2,1
+        buffer.Move (2, 1);
+        buffer.AddStr ("你"); // Chinese character "you", 2 columns wide
+
+        // Verify the wide character was drawn
+        Assert.Equal ("你", buffer.Contents! [1, 2].Grapheme);
+        Assert.True (buffer.Contents [1, 2].IsDirty);
+
+        // With the fix, the second column should NOT be modified by AddStr
+        // The wide glyph naturally renders across both columns
+        Assert.NotEqual ("你", buffer.Contents [1, 3].Grapheme);
+
+        // Clear dirty flags to test FillRect behavior
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents [r, c].IsDirty = false;
+            }
+        }
+
+        // Act - Fill a rectangle that overlaps the first column of the wide character
+        // This simulates drawing a MessageBox border over Chinese text
+        buffer.FillRect (new (2, 1, 1, 1), new Rune ('│'));
+
+        // Assert
+
+        // With FIXES_4466: FillRect calls AddStr, which properly invalidates the wide character
+        // The wide character at [1,2] should be replaced with replacement char or the new content
+        Assert.Equal ("│", buffer.Contents [1, 2].Grapheme);
+        Assert.True (buffer.Contents [1, 2].IsDirty, "Cell [1,2] should be marked dirty after FillRect");
+
+        // The adjacent cell should also be marked dirty for proper rendering
+        Assert.True (buffer.Contents [1, 3].IsDirty, "Adjacent cell [1,3] should be marked dirty to ensure proper rendering");
+    }
+
+    /// <summary>
+    ///     Tests that FillRect handles overwriting the second column of a wide character.
+    ///     When drawing at an odd column that's the second half of a wide glyph, the
+    ///     wide glyph should be invalidated.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void FillRect_OverwritesSecondColumnOfWideChar_InvalidatesWideChar ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5, Cols = 10,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Draw a wide character at position 2,1
+        buffer.Move (2, 1);
+        buffer.AddStr ("好"); // Chinese character, 2 columns wide
+
+        Assert.Equal ("好", buffer.Contents! [1, 2].Grapheme);
+
+        // Clear dirty flags
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents [r, c].IsDirty = false;
+            }
+        }
+
+        // Act - Fill at the second column of the wide character (position 3)
+        buffer.FillRect (new (3, 1, 1, 1), new Rune ('│'));
+
+        // Assert
+        // With the fix: The original wide character at col 2 should be invalidated
+        // because we're overwriting its second column
+        Assert.True (buffer.Contents [1, 2].IsDirty, "Wide char at col 2 should be invalidated when its second column is overwritten");
+        Assert.Equal (buffer.Contents [1, 2].Grapheme, Glyphs.WideGlyphReplacement.ToString ());
+
+        Assert.Equal ("│", buffer.Contents [1, 3].Grapheme);
+        Assert.True (buffer.Contents [1, 3].IsDirty);
+    }
+
+    /// <summary>
+    ///     Tests the ChineseUI scenario: Drawing a MessageBox with borders over Chinese button text.
+    ///     This simulates the specific repro case from the issue. See: https://github.com/gui-cs/Terminal.Gui/issues/4466
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void ChineseUI_MessageBox_Over_WideChars ()
+    {
+        // Arrange - Simulate the ChineseUI scenario
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 10, Cols = 30,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Draw Chinese button text (like "你好呀")
+        buffer.Move (5, 3);
+        buffer.AddStr ("你好呀"); // 3 Chinese characters, 6 columns total
+
+        // Verify initial state
+        Assert.Equal ("你", buffer.Contents! [3, 5].Grapheme);
+        Assert.Equal ("好", buffer.Contents [3, 7].Grapheme);
+        Assert.Equal ("呀", buffer.Contents [3, 9].Grapheme);
+
+        // Clear dirty flags to simulate the state before MessageBox draws
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents [r, c].IsDirty = false;
+            }
+        }
+
+        // Act - Draw a MessageBox border that partially overlaps the Chinese text
+        // This simulates the mouse moving over the border, causing HighlightState changes
+        // Draw vertical line at column 8 (overlaps second char "好")
+        for (var row = 2; row < 6; row++)
+        {
+            buffer.FillRect (new (8, row, 1, 1), new Rune ('│'));
+        }
+
+        // Assert - The wide characters should be properly handled
+        // With the fix: Wide characters are properly invalidated
+        // The first character "你" at col 5 should be unaffected
+        Assert.Equal ("你", buffer.Contents [3, 5].Grapheme);
+
+        // The second character "好" at col 7 had its second column overwritten
+        // so it should be replaced with replacement char
+        Assert.Equal (buffer.Contents [3, 7].Grapheme, Glyphs.WideGlyphReplacement.ToString ());
+        Assert.True (buffer.Contents [3, 7].IsDirty, "Invalidated wide char should be marked dirty");
+
+        // The border should be drawn at col 8
+        Assert.Equal ("│", buffer.Contents [3, 8].Grapheme);
+        Assert.True (buffer.Contents [3, 8].IsDirty);
+
+        // The third character "呀" at col 9 should be unaffected
+        Assert.Equal ("呀", buffer.Contents [3, 9].Grapheme);
+    }
+
+    /// <summary>
+    ///     Tests that FillRect works correctly with single-width characters (baseline behavior).
+    ///     This should work the same with or without FIXES_4466.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void FillRect_SingleWidthChars_WorksCorrectly ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5, Cols = 10,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Draw some ASCII text
+        buffer.Move (2, 1);
+        buffer.AddStr ("ABC");
+
+        Assert.Equal ("A", buffer.Contents! [1, 2].Grapheme);
+        Assert.Equal ("B", buffer.Contents [1, 3].Grapheme);
+        Assert.Equal ("C", buffer.Contents [1, 4].Grapheme);
+
+        // Clear dirty flags
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents [r, c].IsDirty = false;
+            }
+        }
+
+        // Act - Overwrite with FillRect
+        buffer.FillRect (new (3, 1, 1, 1), new Rune ('X'));
+
+        // Assert - This should work the same regardless of FIXES_4466
+        Assert.Equal ("A", buffer.Contents [1, 2].Grapheme);
+        Assert.Equal ("X", buffer.Contents [1, 3].Grapheme);
+        Assert.True (buffer.Contents [1, 3].IsDirty);
+        Assert.Equal ("C", buffer.Contents [1, 4].Grapheme);
+    }
+
+    /// <summary>
+    ///     Tests FillRect with wide characters at buffer boundaries.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void FillRect_WideChar_AtBufferBoundary ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5, Cols = 10,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Draw a wide character at the right edge (col 8, which would extend to col 9)
+        buffer.Move (8, 1);
+        buffer.AddStr ("山"); // Chinese character "mountain", 2 columns wide
+
+        Assert.Equal ("山", buffer.Contents! [1, 8].Grapheme);
+
+        // Clear dirty flags
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents [r, c].IsDirty = false;
+            }
+        }
+
+        // Act - FillRect at the wide character position
+        buffer.FillRect (new (8, 1, 1, 1), new Rune ('│'));
+
+        // Assert
+        Assert.Equal ("│", buffer.Contents [1, 8].Grapheme);
+        Assert.True (buffer.Contents [1, 8].IsDirty);
+
+        // Adjacent cell should be marked dirty
+        Assert.True (
+                     buffer.Contents [1, 9].IsDirty,
+                     "Cell after wide char replacement should be marked dirty");
+    }
+
+    /// <summary>
+    ///     Tests OutputBase.Write method marks cells dirty correctly for wide characters.
+    ///     This tests the other half of the fix in OutputBase.cs.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void OutputBase_Write_WideChar_MarksCellsDirty ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5, Cols = 20,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Draw a line with wide characters
+        buffer.Move (0, 1);
+        buffer.AddStr ("你好"); // Two wide characters
+
+        // Mark all as not dirty to simulate post-Write state
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents! [r, c].IsDirty = false;
+            }
+        }
+
+        // Verify initial state
+        Assert.Equal ("你", buffer.Contents! [1, 0].Grapheme);
+        Assert.Equal ("好", buffer.Contents [1, 2].Grapheme);
+
+        // Act - Now overwrite the first wide char by writing at its position
+        buffer.Move (0, 1);
+        buffer.AddStr ("A"); // Single width char
+
+        // Assert
+        // With the fix: The first cell is replaced with 'A' and marked dirty
+        Assert.Equal ("A", buffer.Contents [1, 0].Grapheme);
+        Assert.True (buffer.Contents [1, 0].IsDirty);
+
+        // The adjacent cell (col 1) should be marked dirty for proper rendering
+        Assert.True (
+                     buffer.Contents [1, 1].IsDirty,
+                     "Adjacent cell should be marked dirty after writing single-width char over wide char");
+
+        // The second wide char should remain
+        Assert.Equal ("好", buffer.Contents [1, 2].Grapheme);
+    }
+
+    /// <summary>
+    ///     Tests that filling a rectangle with spaces properly handles wide character cleanup.
+    ///     This simulates clearing a region that contains wide characters.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void FillRect_WithSpaces_OverWideChars ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5, Cols = 15,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Draw a line of mixed content
+        buffer.Move (2, 2);
+        buffer.AddStr ("A你B好C");
+
+        // Verify setup
+        Assert.Equal ("A", buffer.Contents! [2, 2].Grapheme);
+        Assert.Equal ("你", buffer.Contents [2, 3].Grapheme);
+        Assert.Equal ("B", buffer.Contents [2, 5].Grapheme);
+        Assert.Equal ("好", buffer.Contents [2, 6].Grapheme);
+        Assert.Equal ("C", buffer.Contents [2, 8].Grapheme);
+
+        // Clear dirty flags
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents [r, c].IsDirty = false;
+            }
+        }
+
+        // Act - Fill the region with spaces (simulating clearing)
+        buffer.FillRect (new (3, 2, 4, 1), new Rune (' '));
+
+        // Assert
+        // With the fix: Wide characters are properly handled
+        Assert.Equal (" ", buffer.Contents [2, 3].Grapheme);
+        Assert.True (buffer.Contents [2, 3].IsDirty);
+
+        // Wide character '你' at col 3 was replaced, so col 4 should be marked dirty
+        Assert.True (
+                     buffer.Contents [2, 4].IsDirty,
+                     "Cell after replaced wide char should be dirty");
+
+        Assert.Equal (" ", buffer.Contents [2, 4].Grapheme);
+        Assert.Equal (" ", buffer.Contents [2, 5].Grapheme);
+        Assert.Equal (" ", buffer.Contents [2, 6].Grapheme);
+
+        // Cell 7 should be dirty because '好' was partially overwritten
+        Assert.True (
+                     buffer.Contents [2, 7].IsDirty,
+                     "Adjacent cell should be dirty after wide char replacement");
+    }
+
+    /// <summary>
+    ///     Tests the edge case where a wide character's first column is outside the clip region
+    ///     but the second column is inside.
+    ///     IMPORTANT: This test documents that the code path in WriteWideGrapheme where:
+    ///     - !Clip.Contains(col, row) is true (first column outside)
+    ///     - Clip.Contains(col + 1, row) is true (second column inside)
+    ///     is CURRENTLY UNREACHABLE because IsValidLocation checks Clip.Contains(col, row) and
+    ///     returns false before WriteWideGrapheme is called. This test verifies the current behavior
+    ///     (nothing is written when first column is outside clip).
+    ///     If the behavior should change to write the second column with a replacement character,
+    ///     the logic in IsValidLocation or AddGrapheme needs to be modified.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void AddStr_WideChar_FirstColumnOutsideClip_SecondColumnInside_CurrentBehavior ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5,
+            Cols = 10,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Set custom replacement characters to verify they're being used
+        Rune customColumn1Replacement = new ('◄');
+        Rune customColumn2Replacement = new ('►');
+        buffer.SetWideGlyphReplacement (customColumn1Replacement);
+
+        // Set clip region that starts at column 3 (odd column)
+        // This creates a scenario where col 2 is outside clip, but col 3 is inside
+        buffer.Clip = new (new (3, 1, 5, 3));
+
+        // Clear initial contents to ensure clean state
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents! [r, c].IsDirty = false;
+                buffer.Contents [r, c].Grapheme = " ";
+            }
+        }
+
+        // Act - Try to draw a wide character at column 2
+        // Column 2 is outside clip, but column 3 is inside clip
+        buffer.Move (2, 1);
+        buffer.AddStr ("你"); // Chinese character "you", 2 columns wide
+
+        // Assert
+        // CURRENT BEHAVIOR: IsValidLocation returns false when col 2 is outside clip,
+        // so NOTHING is written - neither column 2 nor column 3
+        Assert.Equal (" ", buffer.Contents! [1, 2].Grapheme);
+        Assert.False (buffer.Contents [1, 2].IsDirty, "Cell outside clip should not be marked dirty");
+
+        // Column 3 is also not written because IsValidLocation returned false
+        // The code path in WriteWideGrapheme that would write the replacement char
+        // to column 3 is never reached
+        Assert.Equal (" ", buffer.Contents [1, 3].Grapheme);
+
+        Assert.False (
+                      buffer.Contents [1, 3].IsDirty,
+                      "Currently, second column is not written when first column is outside clip");
+
+        // Verify Col has been advanced by only 1 (not by the wide character width)
+        // because the grapheme was not validated/processed when IsValidLocation returned false
+        Assert.Equal (3, buffer.Col);
+    }
+
+    /// <summary>
+    ///     Tests the complementary case: wide character's second column is outside clip
+    ///     but first column is inside. This should use the column 1 replacement character.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void AddStr_WideChar_SecondColumnOutsideClip_FirstColumnInside_UsesColumn1Replacement ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5,
+            Cols = 10,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Set custom replacement characters
+        Rune customColumn1Replacement = new ('◄');
+        Rune customColumn2Replacement = new ('►');
+        buffer.SetWideGlyphReplacement (customColumn1Replacement);
+
+        // Set clip region that ends at column 6 (even column)
+        // This creates a scenario where col 5 is inside, but col 6 is outside
+        buffer.Clip = new (new (0, 1, 6, 3));
+
+        // Clear initial contents
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents! [r, c].IsDirty = false;
+                buffer.Contents [r, c].Grapheme = " ";
+            }
+        }
+
+        // Act - Try to draw a wide character at column 5
+        // Column 5 is inside clip, but column 6 is outside clip
+        buffer.Move (5, 1);
+        buffer.AddStr ("好"); // Chinese character, 2 columns wide
+
+        // Assert
+        // The first column (col 5) is inside clip but second column (6) is outside
+        // Should use column 1 replacement char to indicate it can't fit
+        Assert.Equal (
+                      customColumn1Replacement.ToString (),
+                      buffer.Contents! [1, 5].Grapheme);
+
+        Assert.True (
+                     buffer.Contents [1, 5].IsDirty,
+                     "First column should be marked dirty with replacement char when second column is clipped");
+
+        // The second column is outside clip boundaries entirely
+        Assert.Equal (" ", buffer.Contents [1, 6].Grapheme);
+        Assert.False (buffer.Contents [1, 6].IsDirty, "Cell outside clip should not be modified");
+
+        // Verify Col has been advanced by 2 (wide character width)
+        Assert.Equal (7, buffer.Col);
+    }
+
+    /// <summary>
+    ///     Tests that when both columns of a wide character are inside the clip,
+    ///     the character is drawn normally without replacement characters.
+    /// </summary>
+    [Fact]
+    [Trait ("Category", "Output")]
+    public void AddStr_WideChar_BothColumnsInsideClip_DrawsNormally ()
+    {
+        // Arrange
+        OutputBufferImpl buffer = new ()
+        {
+            Rows = 5,
+            Cols = 10,
+            CurrentAttribute = new (Color.White, Color.Black)
+        };
+
+        // Set custom replacement characters (should NOT be used in this case)
+        Rune customColumn1Replacement = new ('◄');
+        Rune customColumn2Replacement = new ('►');
+        buffer.SetWideGlyphReplacement (customColumn1Replacement);
+
+        // Set clip region that includes columns 2-7
+        buffer.Clip = new (new (2, 1, 6, 3));
+
+        // Clear initial contents
+        for (var r = 0; r < buffer.Rows; r++)
+        {
+            for (var c = 0; c < buffer.Cols; c++)
+            {
+                buffer.Contents! [r, c].IsDirty = false;
+                buffer.Contents [r, c].Grapheme = " ";
+            }
+        }
+
+        // Act - Draw a wide character at column 4 (both 4 and 5 are inside clip)
+        buffer.Move (4, 1);
+        buffer.AddStr ("山"); // Chinese character "mountain", 2 columns wide
+
+        // Assert
+        // Both columns are inside clip, so the wide character should be drawn normally
+        Assert.Equal ("山", buffer.Contents! [1, 4].Grapheme);
+        Assert.True (buffer.Contents [1, 4].IsDirty, "First column should be marked dirty");
+
+        // The second column should NOT be marked dirty by WriteWideGrapheme
+        // The wide glyph naturally renders across both columns without modifying column N+1
+        // See: https://github.com/gui-cs/Terminal.Gui/issues/4258
+        Assert.False (
+                      buffer.Contents [1, 5].IsDirty,
+                      "Adjacent cell should NOT be marked dirty when writing wide char (see #4258)");
+
+        // Verify no replacement characters were used
+        Assert.NotEqual (customColumn1Replacement.ToString (), buffer.Contents [1, 4].Grapheme);
+        Assert.NotEqual (customColumn2Replacement.ToString (), buffer.Contents [1, 5].Grapheme);
+
+        // Verify Col has been advanced by 2
+        Assert.Equal (6, buffer.Col);
+    }
+}

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

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

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

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

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

@@ -1,76 +0,0 @@
-namespace ViewBaseTests.Adornments;
-
-[Collection ("Global Test Setup")]
-
-public class ShadowStyleTests
-{
-    [Fact]
-    public void Default_None ()
-    {
-        var view = new View ();
-        Assert.Equal (ShadowStyle.None, view.ShadowStyle);
-        Assert.Equal (ShadowStyle.None, view.Margin!.ShadowStyle);
-        view.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (ShadowStyle.None)]
-    [InlineData (ShadowStyle.Opaque)]
-    [InlineData (ShadowStyle.Transparent)]
-    public void Set_View_Sets_Margin (ShadowStyle style)
-    {
-        var view = new View ();
-
-        view.ShadowStyle = style;
-        Assert.Equal (style, view.ShadowStyle);
-        Assert.Equal (style, view.Margin!.ShadowStyle);
-        view.Dispose ();
-    }
-
-
-    [Theory]
-    [InlineData (ShadowStyle.None, 0, 0, 0, 0)]
-    [InlineData (ShadowStyle.Opaque, 0, 0, 1, 1)]
-    [InlineData (ShadowStyle.Transparent, 0, 0, 1, 1)]
-    public void ShadowStyle_Margin_Thickness (ShadowStyle style, int expectedLeft, int expectedTop, int expectedRight, int expectedBottom)
-    {
-        var superView = new View
-        {
-            Height = 10, Width = 10
-        };
-
-        View view = new ()
-        {
-            Width = Dim.Auto (),
-            Height = Dim.Auto (),
-            Text = "0123",
-            HighlightStates = MouseState.Pressed,
-            ShadowStyle = style,
-            CanFocus = true
-        };
-
-        superView.Add (view);
-        superView.BeginInit ();
-        superView.EndInit ();
-
-        Assert.Equal (new (expectedLeft, expectedTop, expectedRight, expectedBottom), view.Margin!.Thickness);
-    }
-
-
-    [Theory]
-    [InlineData (ShadowStyle.None, 3)]
-    [InlineData (ShadowStyle.Opaque, 4)]
-    [InlineData (ShadowStyle.Transparent, 4)]
-    public void Style_Changes_Margin_Thickness (ShadowStyle style, int expected)
-    {
-        var view = new View ();
-        view.Margin!.Thickness = new (3);
-        view.ShadowStyle = style;
-        Assert.Equal (new (3, 3, expected, expected), view.Margin.Thickness);
-
-        view.ShadowStyle = ShadowStyle.None;
-        Assert.Equal (new (3), view.Margin.Thickness);
-        view.Dispose ();
-    }
-
-}

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

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

+ 19 - 15
Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingClippingTests.cs

@@ -574,6 +574,7 @@ public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBas
         };
 
         superView.Add (viewWithBorderAtX0, viewWithBorderAtX1, viewWithBorderAtX2);
+        driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
         app.Begin (superView);
         // Begin calls LayoutAndDraw, so no need to call it again here
         // app.LayoutAndDraw();
@@ -585,9 +586,9 @@ public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBas
                                                        ┆viewWithBorderAtX0┆🍎🍎🍎
                                                        └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
                                                        🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
-                                                       ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎
-                                                       ┆viewWithBorderAtX1┆ 🍎🍎
-                                                       └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎
+                                                       ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎
+                                                       ┆viewWithBorderAtX1┆ 🍎🍎
+                                                       └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎
                                                        🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
                                                        🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
                                                        🍎┆viewWithBorderAtX2┆🍎🍎
@@ -597,7 +598,7 @@ public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBas
                                                        output,
                                                        driver);
 
-        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX0┆🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX1┆ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┆viewWithBorderAtX2┆🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX0┆🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m┆viewWithBorderAtX1┆ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ 🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎┆viewWithBorderAtX2┆🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎└╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m    \x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎\x1b[38;2;255;255;255m\x1b[48;2;0;0;0m",
                                            output, driver);
 
         DriverImpl? driverImpl = driver as DriverImpl;
@@ -617,9 +618,9 @@ public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBas
                                               ┆viewWithBorderAtX0┆🍎🍎🍎
                                               └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘🍎🍎🍎
                                               🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
-                                              ┌──────────────────┐ 🍎🍎
-                                              │viewWithBorderAtX1│ 🍎🍎
-                                              └──────────────────┘ 🍎🍎
+                                              ┌──────────────────┐ 🍎🍎
+                                              │viewWithBorderAtX1│ 🍎🍎
+                                              └──────────────────┘ 🍎🍎
                                               🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎
                                               🍎┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐🍎🍎
                                               🍎┆viewWithBorderAtX2┆🍎🍎
@@ -675,18 +676,19 @@ public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBas
         };
 
         superView.Add (viewWithBorder);
+        driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
         app.Begin (superView);
 
         DriverAssert.AssertDriverContentsAre (
                                               """
-                                              ┌─┐🍎
-                                              │X│🍎
-                                              └─┘🍎
+                                              ┌─┐🍎
+                                              │X│🍎
+                                              └─┘🍎
                                               """,
                                               output,
                                               driver);
 
-        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m�┌─┐🍎�│X│🍎�└─┘🍎",
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m①┌─┐🍎①│X│🍎①└─┘🍎",
             output, driver);
 
         DriverImpl? driverImpl = driver as DriverImpl;
@@ -738,19 +740,21 @@ public class ViewDrawingClippingTests (ITestOutputHelper output) : FakeDriverBas
             Height = 3
         };
 
+        driver.GetOutputBuffer ().SetWideGlyphReplacement ((Rune)'①');
+
         superView.Add (viewWithBorder);
         app.Begin (superView);
 
         DriverAssert.AssertDriverContentsAre (
                                               """
-                                              🍎┌─┐
-                                              🍎│X│
-                                              🍎└─┘
+                                              🍎┌─┐
+                                              🍎│X│
+                                              🍎└─┘
                                               """,
                                               output,
                                               driver);
 
-        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎�┌─┐🍎�│X│🍎�└─┘",
+        DriverAssert.AssertDriverOutputIs (@"\x1b[38;2;95;158;160m\x1b[48;2;54;69;79m🍎①┌─┐🍎①│X│🍎①└─┘",
             output, driver);
 
         DriverImpl? driverImpl = driver as DriverImpl;