Răsfoiți Sursa

Refactoring again... WIP broke

Tig 11 luni în urmă
părinte
comite
794ad0bad7

+ 11 - 13
Terminal.Gui/View/Navigation/FocusEventArgs.cs

@@ -1,25 +1,23 @@
 namespace Terminal.Gui;
 
 /// <summary>Defines the event arguments for <see cref="View.HasFocus"/></summary>
-public class FocusEventArgs : EventArgs
+public class HasFocusEventArgs : CancelEventArgs<bool>
 {
     /// <summary>Initializes a new instance.</summary>
-    /// <param name="leaving">The view that is losing focus.</param>
-    /// <param name="entering">The view that is gaining focus.</param>
-    public FocusEventArgs (View leaving, View entering) {
-        Leaving = leaving;
-        Entering = entering;
+    /// <param name="currentHasFocus">The current value of <see cref="View.HasFocus"/>.</param>
+    /// <param name="newHasFocus">The value <see cref="View.HasFocus"/> will have if the event is not cancelled.</param>
+    /// <param name="currentFocused">The view that is losing focus.</param>
+    /// <param name="newFocused">The view that is gaining focus.</param>
+    public HasFocusEventArgs (bool currentHasFocus, bool newHasFocus, View currentFocused, View newFocused) : base (ref currentHasFocus, ref newHasFocus)
+    {
+        CurrentFocused = currentFocused;
+        NewFocused = newFocused;
     }
 
-    /// <summary>
-    ///    Gets or sets whether the event should be canceled. Set to <see langword="true"/> to prevent the focus change.
-    /// </summary>
-    public bool Cancel { get; set; }
-
     /// <summary>Gets or sets the view that is losing focus.</summary>
-    public View Leaving { get; set; }
+    public View CurrentFocused { get; set; }
 
     /// <summary>Gets or sets the view that is gaining focus.</summary>
-    public View Entering { get; set; }
+    public View NewFocused { get; set; }
 
 }

+ 7 - 6
Terminal.Gui/View/View.Hierarchy.cs

@@ -193,17 +193,18 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
             return view;
         }
 
-        // If a view being removed is focused, it should lose focus.
-        if (view.HasFocus)
-        {
-            view.FocusChanged(this, true);
-        }
-
         Rectangle touched = view.Frame;
         _subviews.Remove (view);
         _tabIndexes!.Remove (view);
         view._superView = null;
         //view._tabIndex = -1;
+
+        // If a view being removed is focused, it should lose focus.
+        if (view.HasFocus)
+        {
+            view.HasFocus = false;
+        }
+
         SetNeedsLayout ();
         SetNeedsDisplay ();
 

+ 80 - 45
Terminal.Gui/View/View.Navigation.cs

@@ -46,7 +46,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 if (value)
                 {
                     // NOTE: If Application.Navigation is null, we pass null to FocusChanging. For unit tests.
-                    if (FocusChanging (Application.Navigation?.GetFocused ()))
+                    if (SetHasFocusTrue (Application.Navigation?.GetFocused ()))
                     {
                         // The change happened
                         // HasFocus is now true
@@ -54,7 +54,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 }
                 else
                 {
-                    FocusChanged (null);
+                    SetHasFocusFalse (null);
                 }
             }
         }
@@ -67,7 +67,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// </summary>
     public bool SetFocus ()
     {
-        return FocusChanging (Application.Navigation?.GetFocused ());
+        return SetHasFocusTrue (Application.Navigation?.GetFocused ());
     }
 
     /// <summary>
@@ -78,7 +78,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
     /// <param name="traversingUp"></param>
     /// <returns><see langword="true"/> if <see cref="HasFocus"/> was changed to <see langword="true"/>.</returns>
     /// <exception cref="InvalidOperationException"></exception>
-    private bool FocusChanging ([CanBeNull] View previousFocusedView, bool traversingUp = false)
+    private bool SetHasFocusTrue ([CanBeNull] View previousFocusedView, bool traversingUp = false)
     {
         Debug.Assert (ApplicationNavigation.IsInHierarchy (SuperView, this));
 
@@ -90,7 +90,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         if (CanFocus && SuperView is { CanFocus: false })
         {
-            Debug.WriteLine($@"WARNING: Attempt to FocusChanging where SuperView.CanFocus == false. {this}");
+            Debug.WriteLine ($@"WARNING: Attempt to FocusChanging where SuperView.CanFocus == false. {this}");
             return false;
         }
 
@@ -108,10 +108,6 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         if (!traversingUp)
         {
-            if (NotifyFocusChanging (previousFocusedView))
-            {
-                return false;
-            }
 
             // If we're here, we can be focused. But we may have subviews.
 
@@ -123,31 +119,35 @@ public partial class View // Focus and cross-view navigation management (TabStop
             }
 
             // Couldn't restore focus, so use Advance to navigate to the next focusable subview
-            if (AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop))
+            if (AdvanceFocus (NavigationDirection.Forward, null))
             {
                 // A subview was focused. We're done because the subview has focus and it recursed up the superview hierarchy.
                 return true;
             }
         }
 
+        if (NotifyFocusChanging (false, true, previousFocusedView, this))
+        {
+            return false;
+        }
+
         // If we're here, we're the most-focusable view in the application OR we're traversing up the superview hierarchy.
 
         // If we previously had a subview with focus (`Focused = subview`), we need to make sure that all subviews down the `subview`-hierarchy LeaveFocus.
         // LeaveFocus will recurse down the subview hierarchy and will also set PreviouslyMostFocused
         View focused = Focused;
-        focused?.FocusChanged (this, true);
+        focused?.SetHasFocusFalse (this, true);
 
         // We need to ensure all superviews up the superview hierarchy have focus.
         // Any of them may cancel gaining focus. In which case we need to back out.
         if (SuperView is { HasFocus: false } sv)
         {
-            // Tell EnterFocus that we're traversing up the superview hierarchy
-            if (!sv.FocusChanging (previousFocusedView, true))
+            // Tell SetHasFocusTrue that we're traversing up the superview hierarchy
+            if (!sv.SetHasFocusTrue (previousFocusedView, true))
             {
-                // The change was cancelled
+                // The change didn't happen.
                 return false;
             }
-
         }
 
         // If we're here:
@@ -161,33 +161,35 @@ public partial class View // Focus and cross-view navigation management (TabStop
         _hasFocus = true;
 
         // Ensure that the peer loses focus
-        focusedPeer?.FocusChanged (this, true);
+        focusedPeer?.SetHasFocusFalse (this, true);
 
         // We're the most focused view in the application, we need to set the focused view to this view.
         Application.Navigation?.SetFocused (this);
 
+        NotifyFocusChanged (HasFocus, previousFocusedView, this);
+
         SetNeedsDisplay ();
 
         // Post-conditions - prove correctness
         if (HasFocus == previousValue)
         {
-            throw new InvalidOperationException ($"FocusChanging was not cancelled and the HasFocus value did not change.");
+            throw new InvalidOperationException ($"NotifyFocusChanging was not cancelled and the HasFocus value did not change.");
         }
 
         return true;
     }
 
 
-    private bool NotifyFocusChanging (View leavingView)
+    private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, [CanBeNull] View currentFocused, [CanBeNull] View newFocused)
     {
         // Call the virtual method
-        if (OnHasFocusChanging (leavingView))
+        if (OnHasFocusChanging (currentHasFocus, newHasFocus, currentFocused, newFocused))
         {
             // The event was cancelled
             return true;
         }
 
-        var args = new FocusEventArgs (leavingView, this);
+        var args = new HasFocusEventArgs (currentHasFocus, newHasFocus, currentFocused, newFocused);
         HasFocusChanging?.Invoke (this, args);
 
         if (args.Cancel)
@@ -199,23 +201,28 @@ public partial class View // Focus and cross-view navigation management (TabStop
         return false;
     }
 
-    /// <summary>Virtual method invoked when the focus is changing to this View.</summary>
-    /// <param name="currentlyFocusedView">The view that is currently Focused. May be <see langword="null"/>.</param>
-    /// <returns> <see langword="true"/>, if the event is to be cancelled, <see langword="false"/> otherwise.</returns>
-    protected virtual bool OnHasFocusChanging ([CanBeNull] View currentlyFocusedView)
+    /// <summary>
+    ///     Invoked when <see cref="View.HasFocus"/> is about to change. This method is called before the <see cref="HasFocusChanging"/> event is raised.
+    /// </summary>
+    /// <param name="currentHasFocus">The current value of <see cref="View.HasFocus"/>.</param>
+    /// <param name="newHasFocus">The value <see cref="View.HasFocus"/> will have if the event is not cancelled.</param>
+    /// <param name="currentFocused">The view that is currently Focused. May be <see langword="null"/>.</param>
+    /// <param name="newFocused">The view that will be focused. May be <see langword="null"/>.</param>
+    /// <returns> <see langword="true"/>, if the change to <see cref="View.HasFocus"/> is to be cancelled, <see langword="false"/> otherwise.</returns>
+    protected virtual bool OnHasFocusChanging (bool currentHasFocus, bool newHasFocus, [CanBeNull] View currentFocused, [CanBeNull] View newFocused)
     {
         return false;
     }
 
     /// <summary>Raised when the view is gaining (entering) focus. Can be cancelled.</summary>
-    public event EventHandler<FocusEventArgs> HasFocusChanging;
+    public event EventHandler<HasFocusEventArgs> HasFocusChanging;
 
     /// <summary>
     ///     Called when focus has changed to another view.
     /// </summary>
     /// <param name="focusedVew">The view that now has focus. If <see langword="null"/> there is no view that has focus.</param>
     /// <exception cref="InvalidOperationException"></exception>
-    private void FocusChanged ([CanBeNull] View focusedVew, bool traversingDown = false)
+    private void SetHasFocusFalse ([CanBeNull] View focusedVew, bool traversingDown = false)
     {
         // Pre-conditions
         if (!_hasFocus)
@@ -234,7 +241,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 return;
             }
 
-            if (SuperView is {} && SuperView.AdvanceFocus (NavigationDirection.Forward, TabStop))
+            if (SuperView is { } && SuperView.AdvanceFocus (NavigationDirection.Forward, TabStop))
             {
                 // The above will cause FocusChanged, so we can return
                 return;
@@ -245,7 +252,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
             //    // Temporarily ensure this view can't get focus
             //    bool prevCanFocus = _canFocus;
             //    _canFocus = false;
-            //    ApplicationNavigation.MoveNextView ();
+            //    Application.Navigation.;
             //    _canFocus = prevCanFocus;
 
             //    // The above will cause LeaveFocus, so we can return
@@ -266,7 +273,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
             {
                 if (bottom.HasFocus)
                 {
-                    bottom.FocusChanged (focusedVew, true);
+                    bottom.SetHasFocusFalse (focusedVew, true);
                 }
                 bottom = bottom.SuperView;
             }
@@ -275,25 +282,29 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         bool previousValue = HasFocus;
 
-        // Call the virtual method - NOTE: Leave cannot be cancelled
-        OnHasFocusChanged (focusedVew);
-
-        var args = new FocusEventArgs (focusedVew, this);
-        HasFocusChanged?.Invoke (this, args);
+        // Note, can't be cancelled.
+        NotifyFocusChanging (HasFocus, !HasFocus, focusedVew, this);
 
         // Get whatever peer has focus, if any
         View focusedPeer = SuperView?.Focused;
         _hasFocus = false;
 
+        NotifyFocusChanged (HasFocus, this, focusedVew);
+
         if (!traversingDown && CanFocus && Visible && Enabled)
         {
             // Now ensure all views up the superview-hierarchy are unfocused
             if (SuperView is { HasFocus: true } && focusedPeer == this)
             {
-                SuperView.FocusChanged (focusedVew);
+                SuperView.SetHasFocusFalse (focusedVew);
             }
         }
 
+        if (SuperView is { })
+        {
+            SuperView._previouslyMostFocused = this;
+        }
+
         // Post-conditions - prove correctness
         if (HasFocus == previousValue)
         {
@@ -309,15 +320,39 @@ public partial class View // Focus and cross-view navigation management (TabStop
     [CanBeNull]
     private View _previouslyMostFocused;
 
-    /// <summary>Virtual method invoked after another view gets focus. May be <see langword="null"/>.</summary>
-    /// <param name="focusedVew">The view is now focused.</param>
-    protected virtual void OnHasFocusChanged ([CanBeNull] View focusedVew)
+    private void NotifyFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
+    {
+        // Call the virtual method
+        OnHasFocusChanged (newHasFocus, previousFocusedView, focusedVew);
+
+        // Raise the event
+        var args = new HasFocusEventArgs (newHasFocus, newHasFocus, previousFocusedView, focusedVew);
+        HasFocusChanged?.Invoke (this, args);
+    }
+
+    /// <summary>
+    ///     Invoked after <see cref="HasFocus"/> has changed. This method is called before the <see cref="HasFocusChanged"/> event is raised.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This event cannot be cancelled.
+    ///     </para>
+    /// </remarks>
+    /// <param name="newHasFocus">The new value of <see cref="View.HasFocus"/>.</param>
+    /// <param name="previousFocusedView"></param>
+    /// <param name="focusedVew">The view that is now focused. May be <see langword="null"/></param>
+    protected virtual void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
     {
         return;
     }
 
-    /// <summary>Raised when the view is gaining (entering) focus. Can not be cancelled.</summary>
-    public event EventHandler<FocusEventArgs> HasFocusChanged;
+    /// <summary>Raised after <see cref="HasFocus"/> has changed.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This event cannot be cancelled.
+    ///     </para>
+    /// </remarks>
+    public event EventHandler<HasFocusEventArgs> HasFocusChanged;
 
     #endregion HasFocus
 
@@ -351,12 +386,12 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
         View focused = Focused;
 
-        if (focused is {} && focused.AdvanceFocus (direction, behavior))
+        if (focused is { } && focused.AdvanceFocus (direction, behavior))
         {
             return true;
         }
 
-        View[] index = GetScopedTabIndexes (direction, behavior);
+        View [] index = GetScopedTabIndexes (direction, behavior);
 
         if (index.Length == 0)
         {
@@ -389,11 +424,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
         if (view.HasFocus)
         {
             // We could not advance
-            return true;
+            return view == this;
         }
 
         // The subview does not have focus, but at least one other that can. Can this one be focused?
-        return view.FocusChanging (Focused);
+        return view.SetHasFocusTrue (Focused);
     }
 
 
@@ -593,7 +628,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 HasFocus = false;
             }
 
-            if (_canFocus && !HasFocus && Visible && SuperView is { } && SuperView.Focused is null )
+            if (_canFocus && !HasFocus && Visible && SuperView is { } && SuperView.Focused is null)
             {
                 // If CanFocus is set to true and this view does not have focus, make it enter focus
                 SetFocus ();

+ 18 - 18
Terminal.Gui/Views/ComboBox.cs

@@ -300,7 +300,7 @@ public class ComboBox : View, IDesignable
     }
 
     /// <inheritdoc/>
-    protected override bool OnHasFocusChanging (View view)
+    protected override bool OnHasFocusChanging (bool currentHasFocus, bool newHasFocus, [CanBeNull] View currentFocused, [CanBeNull] View newFocused)
     {
         bool cancel = false;
         if (!_search.HasFocus && !_listview.HasFocus)
@@ -310,14 +310,14 @@ public class ComboBox : View, IDesignable
 
         _search.CursorPosition = _search.Text.GetRuneCount ();
 
-        return cancel; 
+        return cancel;
     }
 
     /// <summary>Virtual method which invokes the <see cref="Expanded"/> event.</summary>
     public virtual void OnExpanded () { Expanded?.Invoke (this, EventArgs.Empty); }
 
     /// <inheritdoc/>
-    protected override void OnHasFocusChanged (View view)
+    protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
     {
         if (_source?.Count > 0
             && _selectedItem > -1
@@ -943,25 +943,25 @@ public class ComboBox : View, IDesignable
             }
         }
 
-        protected override bool OnHasFocusChanging (View view)
+        protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
         {
-            if (_hideDropdownListOnClick)
+            if (newHasFocus)
             {
-                _isFocusing = true;
-                _highlighted = _container.SelectedItem;
-                Application.GrabMouse (this);
+                if (_hideDropdownListOnClick)
+                {
+                    _isFocusing = true;
+                    _highlighted = _container.SelectedItem;
+                    Application.GrabMouse (this);
+                }
             }
-
-            return false; // Don't cancel the focus switch
-        }
-
-        protected override void OnHasFocusChanged (View view)
-        {
-            if (_hideDropdownListOnClick)
+            else
             {
-                _isFocusing = false;
-                _highlighted = _container.SelectedItem;
-                Application.UngrabMouse ();
+                if (_hideDropdownListOnClick)
+                {
+                    _isFocusing = false;
+                    _highlighted = _container.SelectedItem;
+                    Application.UngrabMouse ();
+                }
             }
         }
 

+ 1 - 1
Terminal.Gui/Views/ListView.cs

@@ -739,7 +739,7 @@ public class ListView : View, IDesignable
     }
 
     /// <inheritdoc/>
-    protected override bool OnHasFocusChanging (View view)
+    protected override bool OnHasFocusChanging (bool currentHasFocus, bool newHasFocus, [CanBeNull] View currentFocused, [CanBeNull] View newFocused)
     {
         if (_lastSelectedItem != _selected)
         {

+ 1 - 3
Terminal.Gui/Views/Menu/Menu.cs

@@ -587,11 +587,9 @@ internal sealed class Menu : View
         _host.Run (action);
     }
 
-    protected override void OnHasFocusChanged (View view)
+    protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
     {
         _host.LostFocus (view);
-
-        return;
     }
 
     private void RunSelected ()

+ 4 - 5
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -392,7 +392,7 @@ public class MenuBar : View, IDesignable
         _selected = 0;
         SetNeedsDisplay ();
 
-        _previousFocused = SuperView is null ? Application.Current?.Focused : SuperView.Focused;
+        _previousFocused = null;//SuperView is null ? Application.Current?.Focused : SuperView.Focused;
         OpenMenu (_selected);
 
         if (!SelectEnabledItem (
@@ -453,7 +453,7 @@ public class MenuBar : View, IDesignable
 
         if (_openMenu is null)
         {
-            _previousFocused = SuperView is null ? Application.Current?.Focused ?? null : SuperView.Focused;
+            _previousFocused = null;//SuperView is null ? Application.Current?.Focused ?? null : SuperView.Focused;
         }
 
         OpenMenu (idx, sIdx, subMenu);
@@ -599,14 +599,13 @@ public class MenuBar : View, IDesignable
                 }
 
                 IsMenuOpen = false;
-
                 break;
 
             case true:
                 _selectedSub = -1;
                 SetNeedsDisplay ();
                 RemoveAllOpensSubMenus ();
-                OpenCurrentMenu._previousSubFocused.SetFocus ();
+                OpenCurrentMenu?._previousSubFocused.SetFocus ();
                 _openSubMenu = null;
                 IsMenuOpen = true;
 
@@ -756,7 +755,7 @@ public class MenuBar : View, IDesignable
         {
             case null:
                 // Open a submenu below a MenuBar
-                _lastFocused ??= SuperView is null ? Application.Current?.MostFocused : SuperView.MostFocused;
+                _lastFocused = null;//Application.Navigation.GetFocused();// ??= SuperView is null ? Application.Current?.MostFocused : SuperView.MostFocused;
 
                 if (_openSubMenu is { } && !CloseMenu (false, true))
                 {

+ 1 - 13
Terminal.Gui/Views/Shortcut.cs

@@ -834,22 +834,10 @@ public class Shortcut : View, IOrientation, IDesignable
         }
     }
 
-    private View _lastFocusedView;
-
-    /// <inheritdoc/>
-    protected override bool OnHasFocusChanging (View view)
-    {
-        SetColors ();
-        _lastFocusedView = view;
-
-        return false; // Don't cancel the focus switch
-    }
-
     /// <inheritdoc/>
-    protected override void OnHasFocusChanged (View view)
+    protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
     {
         SetColors ();
-        _lastFocusedView = this;
     }
 
     #endregion Focus

+ 1 - 1
Terminal.Gui/Views/TextField.cs

@@ -1033,7 +1033,7 @@ public class TextField : View
     }
 
     /// <inheritdoc/>
-    protected override void OnHasFocusChanged (View view)
+    protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
     {
         if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
         {

+ 1 - 1
Terminal.Gui/Views/TextView.cs

@@ -3650,7 +3650,7 @@ public class TextView : View
     }
 
     /// <inheritdoc/>
-    protected override void OnHasFocusChanged (View view)
+    protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
     {
         if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
         {

+ 7 - 5
Terminal.Gui/Views/TreeView/TreeView.cs

@@ -1158,14 +1158,16 @@ public class TreeView<T> : View, ITreeView where T : class
     }
 
     ///<inheritdoc/>
-    protected override bool OnHasFocusChanging (View view)
+    protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View currentFocused, [CanBeNull] View newFocused)
     {
-        if (SelectedObject is null && Objects.Any ())
+        if (newHasFocus)
         {
-            SelectedObject = Objects.First ();
+            // If there is no selected object and there are objects in the tree, select the first one
+            if (SelectedObject is null && Objects.Any ())
+            {
+                SelectedObject = Objects.First ();
+            }
         }
-
-        return false; // Don't cancel the focus switch
     }
 
     /// <inheritdoc/>

+ 16 - 15
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Text;
+using JetBrains.Annotations;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -110,23 +111,23 @@ public class ASCIICustomButtonTest : Scenario
             Add (_border, _fill, title);
         }
 
-        protected override bool OnHasFocusChanging (View view)
+        protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
         {
-            _border.Visible = false;
-            _fill.Visible = true;
-            PointerEnter?.Invoke (this);
-
-            return false; // don't cancel
-        }
-
-        protected override void OnHasFocusChanged (View view)
-        {
-            _border.Visible = true;
-            _fill.Visible = false;
-
-            if (view == null)
+            if (newHasFocus)
             {
-                view = this;
+                _border.Visible = false;
+                _fill.Visible = true;
+                PointerEnter?.Invoke (this);
+            }
+            else
+            {
+                _border.Visible = true;
+                _fill.Visible = false;
+
+                //if (view == null)
+                //{
+                //    view = this;
+                //}
             }
         }
 

+ 9 - 7
UICatalog/Scenarios/KeyBindings.cs

@@ -82,8 +82,8 @@ public sealed class KeyBindings : Scenario
 
         foreach (var appBinding in Application.KeyBindings.Bindings)
         {
-                var commands = Application.KeyBindings.GetCommands (appBinding.Key);
-                appBindings.Add ($"{appBinding.Key} -> {appBinding.Value.BoundView?.GetType ().Name} - {commands [0]}");
+            var commands = Application.KeyBindings.GetCommands (appBinding.Key);
+            appBindings.Add ($"{appBinding.Key} -> {appBinding.Value.BoundView?.GetType ().Name} - {commands [0]}");
         }
 
         ObservableCollection<string> hotkeyBindings = new ();
@@ -125,8 +125,7 @@ public sealed class KeyBindings : Scenario
         };
         appWindow.Add (_focusedBindingsListView);
 
-        appWindow.HasFocusChanged += AppWindow_Leave;
-        appWindow.HasFocusChanging += AppWindow_Leave;
+        appWindow.HasFocusChanged += AppWindow_HasFocusChanged;
         appWindow.DrawContent += AppWindow_DrawContent;
 
         // Run - Start the application.
@@ -148,11 +147,14 @@ public sealed class KeyBindings : Scenario
         }
     }
 
-    private void AppWindow_Leave (object sender, FocusEventArgs e)
+    private void AppWindow_HasFocusChanged (object sender, HasFocusEventArgs e)
     {
-        foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused))
+        if (e.NewValue)
         {
-            _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}");
+            foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused))
+            {
+                _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}");
+            }
         }
     }
 }

+ 1 - 0
UnitTests/TestHelpers.cs

@@ -240,6 +240,7 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
     public override void Before (MethodInfo methodUnderTest)
     {
         Debug.WriteLine ($"Before: {methodUnderTest.Name}");
+        Application.ResetState ();
         Assert.Null (Application.Driver);
         Application.Driver = new FakeDriver { Rows = 25, Cols = 25 };
         base.Before (methodUnderTest);

+ 740 - 0
UnitTests/View/Navigation/HasFocusChangeEventTests.cs

@@ -0,0 +1,740 @@
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+public class HasFocusChangeEventTests (ITestOutputHelper _output) : TestsAllViews
+{
+    [Fact]
+    public void HasFocusChanging_SetFocus_Raises ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        Assert.True (view.CanFocus);
+        Assert.False (view.HasFocus);
+
+        view.SetFocus ();
+        Assert.True (view.HasFocus);
+        Assert.Equal (1, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+    }
+
+
+    [Fact]
+    public void HasFocusChanging_SetFocus_SubView_SetFocus_Raises ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        var subviewHasFocusTrueCount = 0;
+        var subviewHasFocusFalseCount = 0;
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        subview.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         subviewHasFocusTrueCount++;
+                                     }
+                                     else
+                                     {
+                                         subviewHasFocusFalseCount++;
+                                     }
+                                 };
+
+        view.Add (subview);
+
+        view.SetFocus ();
+
+        Assert.Equal (1, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+
+        Assert.Equal (1, subviewHasFocusTrueCount);
+        Assert.Equal (0, subviewHasFocusFalseCount);
+    }
+
+
+    [Fact]
+    public void HasFocusChanging_SetFocus_On_SubView_SubView_SetFocus_Raises ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        var subviewHasFocusTrueCount = 0;
+        var subviewHasFocusFalseCount = 0;
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        subview.HasFocusChanging += (s, e) =>
+                                    {
+                                        if (e.NewValue)
+                                        {
+                                            subviewHasFocusTrueCount++;
+                                        }
+                                        else
+                                        {
+                                            subviewHasFocusFalseCount++;
+                                        }
+                                    };
+
+        view.Add (subview);
+
+        subview.SetFocus ();
+
+        Assert.Equal (1, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+
+        Assert.Equal (1, subviewHasFocusTrueCount);
+        Assert.Equal (0, subviewHasFocusFalseCount);
+    }
+
+    [Fact]
+    public void HasFocusChanging_SetFocus_CompoundSubView_Raises ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        var subViewEnterCount = 0;
+        var subViewLeaveCount = 0;
+
+        var subView = new View
+        {
+            Id = "subView",
+            CanFocus = true
+        };
+        subView.HasFocusChanging += (s, e) =>
+                                    {
+                                        if (e.NewValue)
+                                        {
+                                            subViewEnterCount++;
+                                        }
+                                        else
+                                        {
+                                            subViewLeaveCount++;
+                                        }
+                                    };
+
+        var subviewSubView1EnterCount = 0;
+        var subviewSubView1LeaveCount = 0;
+
+        var subViewSubView1 = new View
+        {
+            Id = "subViewSubView1",
+            CanFocus = false
+        };
+        subViewSubView1.HasFocusChanging += (s, e) =>
+                                            {
+                                                if (e.NewValue)
+                                                {
+                                                    subviewSubView1EnterCount++;
+                                                }
+                                                else
+                                                {
+                                                    subviewSubView1LeaveCount++;
+                                                }
+                                            };
+
+        var subviewSubView2EnterCount = 0;
+        var subviewSubView2LeaveCount = 0;
+
+        var subViewSubView2 = new View
+        {
+            Id = "subViewSubView2",
+            CanFocus = true
+        };
+        subViewSubView2.HasFocusChanging += (s, e) =>
+                                            {
+                                                if (e.NewValue)
+                                                {
+                                                    subviewSubView2EnterCount++;
+                                                }
+                                                else
+                                                {
+                                                    subviewSubView2EnterCount++;
+                                                }
+                                            };
+
+        var subviewSubView3EnterCount = 0;
+        var subviewSubView3LeaveCount = 0;
+
+        var subViewSubView3 = new View
+        {
+            Id = "subViewSubView3",
+            CanFocus = false
+        };
+        subViewSubView3.HasFocusChanging += (s, e) =>
+                                            {
+                                                if (e.NewValue)
+                                                {
+                                                    subviewSubView3EnterCount++;
+                                                }
+                                                else
+                                                {
+                                                    subviewSubView3LeaveCount++;
+                                                }
+                                            };
+
+        subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
+
+        view.Add (subView);
+
+        view.SetFocus ();
+        Assert.True (view.HasFocus);
+        Assert.True (subView.HasFocus);
+        Assert.False (subViewSubView1.HasFocus);
+        Assert.True (subViewSubView2.HasFocus);
+        Assert.False (subViewSubView3.HasFocus);
+
+        Assert.Equal (1, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+
+        Assert.Equal (1, subViewEnterCount);
+        Assert.Equal (0, subViewLeaveCount);
+
+        Assert.Equal (0, subviewSubView1EnterCount);
+        Assert.Equal (0, subviewSubView1LeaveCount);
+
+        Assert.Equal (1, subviewSubView2EnterCount);
+        Assert.Equal (0, subviewSubView2LeaveCount);
+
+        Assert.Equal (0, subviewSubView3EnterCount);
+        Assert.Equal (0, subviewSubView3LeaveCount);
+    }
+
+    [Fact]
+    public void HasFocusChanging_Can_Cancel ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                         e.Cancel = true;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        var subviewHasFocusTrueCount = 0;
+        var subviewHasFocusFalseCount = 0;
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        subview.HasFocusChanging += (s, e) =>
+                                    {
+                                        if (e.NewValue)
+                                        {
+                                            subviewHasFocusTrueCount++;
+                                        }
+                                        else
+                                        {
+                                            subviewHasFocusFalseCount++;
+                                        }
+                                    };
+
+        view.Add (subview);
+
+        view.SetFocus ();
+
+        Assert.False (view.HasFocus);
+        Assert.False (subview.HasFocus);
+
+        Assert.Equal (1, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+
+        Assert.Equal (0, subviewHasFocusTrueCount);
+        Assert.Equal (0, subviewHasFocusFalseCount);
+    }
+
+    [Fact]
+    public void HasFocusChanging_SetFocus_On_SubView_Can_Cancel ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                         e.Cancel = true;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        var subviewHasFocusTrueCount = 0;
+        var subviewHasFocusFalseCount = 0;
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        subview.HasFocusChanging += (s, e) =>
+                                    {
+                                        if (e.NewValue)
+                                        {
+                                            subviewHasFocusTrueCount++;
+                                        }
+                                        else
+                                        {
+                                            subviewHasFocusFalseCount++;
+                                        }
+                                    };
+
+        view.Add (subview);
+
+        subview.SetFocus ();
+
+        Assert.False (view.HasFocus);
+        Assert.False (subview.HasFocus);
+
+        Assert.Equal (1, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+
+        Assert.Equal (1, subviewHasFocusTrueCount);
+        Assert.Equal (0, subviewHasFocusFalseCount);
+    }
+
+    [Fact]
+    public void HasFocusChanging_SubView_Can_Cancel ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        var subviewHasFocusTrueCount = 0;
+        var subviewHasFocusFalseCount = 0;
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        subview.HasFocusChanging += (s, e) =>
+                                    {
+                                        if (e.NewValue)
+                                        {
+                                            subviewHasFocusTrueCount++;
+                                            e.Cancel = true;
+                                        }
+                                        else
+                                        {
+                                            subviewHasFocusFalseCount++;
+                                        }
+                                    };
+
+        view.Add (subview);
+
+        view.SetFocus ();
+
+        Assert.True (view.HasFocus);
+        Assert.False (subview.HasFocus);
+
+        Assert.Equal (1, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+
+        Assert.Equal (1, subviewHasFocusTrueCount);
+        Assert.Equal (0, subviewHasFocusFalseCount);
+    }
+
+
+    [Fact]
+    public void HasFocusChanging_SetFocus_On_Subview_SubView_Can_Cancel ()
+    {
+        var hasFocusTrueCount = 0;
+        var hasFocusFalseCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) =>
+                                 {
+                                     if (e.NewValue)
+                                     {
+                                         hasFocusTrueCount++;
+                                     }
+                                     else
+                                     {
+                                         hasFocusFalseCount++;
+                                     }
+                                 };
+
+        var subviewHasFocusTrueCount = 0;
+        var subviewHasFocusFalseCount = 0;
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        subview.HasFocusChanging += (s, e) =>
+                                    {
+                                        if (e.NewValue)
+                                        {
+                                            subviewHasFocusTrueCount++;
+                                            e.Cancel = true;
+                                        }
+                                        else
+                                        {
+                                            subviewHasFocusFalseCount++;
+                                        }
+                                    };
+
+        view.Add (subview);
+
+        subview.SetFocus ();
+
+        Assert.False (view.HasFocus);
+        Assert.False (subview.HasFocus);
+
+        Assert.Equal (0, hasFocusTrueCount);
+        Assert.Equal (0, hasFocusFalseCount);
+
+        Assert.Equal (1, subviewHasFocusTrueCount);
+        Assert.Equal (0, subviewHasFocusFalseCount);
+    }
+
+    [Fact]
+    public void RemoveFocus_Raises_HasFocusChanged ()
+    {
+        var nEnter = 0;
+        var nLeave = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) => nEnter++;
+        view.HasFocusChanged += (s, e) => nLeave++;
+
+        Assert.True (view.CanFocus);
+        Assert.False (view.HasFocus);
+
+        view.SetFocus ();
+        Assert.True (view.HasFocus);
+        Assert.Equal (1, nEnter);
+        Assert.Equal (0, nLeave);
+
+        view.HasFocus = false;
+        Assert.Equal (1, nEnter);
+        Assert.Equal (1, nLeave);
+    }
+
+
+    [Fact]
+    public void RemoveFocus_SubView_Raises_HasFocusChanged ()
+    {
+        var viewEnterCount = 0;
+        var viewLeaveCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) => viewEnterCount++;
+        view.HasFocusChanged += (s, e) => viewLeaveCount++;
+
+        var subviewEnterCount = 0;
+        var subviewLeaveCount = 0;
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        subview.HasFocusChanging += (s, e) => subviewEnterCount++;
+        subview.HasFocusChanged += (s, e) => subviewLeaveCount++;
+
+        view.Add (subview);
+
+        view.SetFocus ();
+
+        view.HasFocus = false;
+
+        Assert.Equal (1, viewEnterCount);
+        Assert.Equal (1, viewLeaveCount);
+
+        Assert.Equal (1, subviewEnterCount);
+        Assert.Equal (1, subviewLeaveCount);
+
+        view.SetFocus ();
+
+        Assert.Equal (2, viewEnterCount);
+        Assert.Equal (1, viewLeaveCount);
+
+        Assert.Equal (2, subviewEnterCount);
+        Assert.Equal (1, subviewLeaveCount);
+
+        subview.HasFocus = false;
+
+        Assert.Equal (2, viewEnterCount);
+        Assert.Equal (2, viewLeaveCount);
+
+        Assert.Equal (2, subviewEnterCount);
+        Assert.Equal (2, subviewLeaveCount);
+    }
+
+    [Fact]
+    public void RemoveFocus_CompoundSubView_Raises_HasFocusChanged ()
+    {
+        var viewEnterCount = 0;
+        var viewLeaveCount = 0;
+
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        view.HasFocusChanging += (s, e) => viewEnterCount++;
+        view.HasFocusChanged += (s, e) => viewLeaveCount++;
+
+        var subViewEnterCount = 0;
+        var subViewLeaveCount = 0;
+
+        var subView = new View
+        {
+            Id = "subView",
+            CanFocus = true
+        };
+        subView.HasFocusChanging += (s, e) => subViewEnterCount++;
+        subView.HasFocusChanged += (s, e) => subViewLeaveCount++;
+
+        var subviewSubView1EnterCount = 0;
+        var subviewSubView1LeaveCount = 0;
+
+        var subViewSubView1 = new View
+        {
+            Id = "subViewSubView1",
+            CanFocus = false
+        };
+        subViewSubView1.HasFocusChanging += (s, e) => subviewSubView1EnterCount++;
+        subViewSubView1.HasFocusChanged += (s, e) => subviewSubView1LeaveCount++;
+
+        var subviewSubView2EnterCount = 0;
+        var subviewSubView2LeaveCount = 0;
+
+        var subViewSubView2 = new View
+        {
+            Id = "subViewSubView2",
+            CanFocus = true
+        };
+        subViewSubView2.HasFocusChanging += (s, e) => subviewSubView2EnterCount++;
+        subViewSubView2.HasFocusChanged += (s, e) => subviewSubView2LeaveCount++;
+
+        var subviewSubView3EnterCount = 0;
+        var subviewSubView3LeaveCount = 0;
+
+        var subViewSubView3 = new View
+        {
+            Id = "subViewSubView3",
+            CanFocus = false
+        };
+        subViewSubView3.HasFocusChanging += (s, e) => subviewSubView3EnterCount++;
+        subViewSubView3.HasFocusChanged += (s, e) => subviewSubView3LeaveCount++;
+
+        subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
+
+        view.Add (subView);
+
+        view.SetFocus ();
+        Assert.True (view.HasFocus);
+        Assert.True (subView.HasFocus);
+        Assert.False (subViewSubView1.HasFocus);
+        Assert.True (subViewSubView2.HasFocus);
+        Assert.False (subViewSubView3.HasFocus);
+
+        view.HasFocus = false;
+        Assert.False (view.HasFocus);
+        Assert.False (subView.HasFocus);
+        Assert.False (subViewSubView1.HasFocus);
+        Assert.False (subViewSubView2.HasFocus);
+        Assert.False (subViewSubView3.HasFocus);
+
+        Assert.Equal (1, viewEnterCount);
+        Assert.Equal (1, viewLeaveCount);
+
+        Assert.Equal (1, subViewEnterCount);
+        Assert.Equal (1, subViewLeaveCount);
+
+        Assert.Equal (0, subviewSubView1EnterCount);
+        Assert.Equal (0, subviewSubView1LeaveCount);
+
+        Assert.Equal (1, subviewSubView2EnterCount);
+        Assert.Equal (1, subviewSubView2LeaveCount);
+
+        Assert.Equal (0, subviewSubView3EnterCount);
+        Assert.Equal (0, subviewSubView3LeaveCount);
+    }
+
+    [Fact]
+    public void HasFocus_False_Leave_Raised ()
+    {
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+        Assert.True (view.CanFocus);
+        Assert.False (view.HasFocus);
+
+        var leaveInvoked = 0;
+
+        view.HasFocusChanged += (s, e) => leaveInvoked++;
+
+        view.SetFocus ();
+        Assert.True (view.HasFocus);
+        Assert.Equal (0, leaveInvoked);
+
+        view.HasFocus = false;
+        Assert.False (view.HasFocus);
+        Assert.Equal (1, leaveInvoked);
+    }
+
+    [Fact]
+    public void HasFocus_False_Leave_Raised_ForAllSubViews ()
+    {
+        var view = new View
+        {
+            Id = "view",
+            CanFocus = true
+        };
+
+        var subview = new View
+        {
+            Id = "subview",
+            CanFocus = true
+        };
+        view.Add (subview);
+
+        var leaveInvoked = 0;
+
+        view.HasFocusChanged += (s, e) => leaveInvoked++;
+        subview.HasFocusChanged += (s, e) => leaveInvoked++;
+
+        view.SetFocus ();
+        Assert.True (view.HasFocus);
+        Assert.Equal (0, leaveInvoked);
+
+        view.HasFocus = false;
+        Assert.False (view.HasFocus);
+        Assert.False (subview.HasFocus);
+        Assert.Equal (2, leaveInvoked);
+    }
+}

+ 0 - 378
UnitTests/View/Navigation/HasFocusEventTests.cs

@@ -1,378 +0,0 @@
-using Xunit.Abstractions;
-
-namespace Terminal.Gui.ViewTests;
-
-public class HasFocusEventTests (ITestOutputHelper _output) : TestsAllViews
-{
-    [Fact]
-    public void SetFocus_Raises_HasFocusChanging ()
-    {
-        var nEnter = 0;
-        var nLeave = 0;
-
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-        view.HasFocusChanging += (s, e) => nEnter++;
-        view.HasFocusChanged += (s, e) => nLeave++;
-
-        Assert.True (view.CanFocus);
-        Assert.False (view.HasFocus);
-
-        view.SetFocus ();
-        Assert.True (view.HasFocus);
-        Assert.Equal (1, nEnter);
-        Assert.Equal (0, nLeave);
-    }
-
-    [Fact]
-    public void RemoveFocus_Raises_HasFocusChanged ()
-    {
-        var nEnter = 0;
-        var nLeave = 0;
-
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-        view.HasFocusChanging += (s, e) => nEnter++;
-        view.HasFocusChanged += (s, e) => nLeave++;
-
-        Assert.True (view.CanFocus);
-        Assert.False (view.HasFocus);
-
-        view.SetFocus ();
-        Assert.True (view.HasFocus);
-        Assert.Equal (1, nEnter);
-        Assert.Equal (0, nLeave);
-
-        view.HasFocus = false;
-        Assert.Equal (1, nEnter);
-        Assert.Equal (1, nLeave);
-    }
-
-    [Fact]
-    public void SetFocus_SubView_SetFocus_Raises_HasFocusChanging ()
-    {
-        var viewEnterCount = 0;
-        var viewLeaveCount = 0;
-
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-        view.HasFocusChanging += (s, e) => viewEnterCount++;
-        view.HasFocusChanged += (s, e) => viewLeaveCount++;
-
-        var subviewEnterCount = 0;
-        var subviewLeaveCount = 0;
-
-        var subview = new View
-        {
-            Id = "subview",
-            CanFocus = true
-        };
-        subview.HasFocusChanging += (s, e) => subviewEnterCount++;
-        subview.HasFocusChanged += (s, e) => subviewLeaveCount++;
-
-        view.Add (subview);
-
-        view.SetFocus ();
-
-        Assert.Equal (1, viewEnterCount);
-        Assert.Equal (0, viewLeaveCount);
-
-        Assert.Equal (1, subviewEnterCount);
-        Assert.Equal (0, subviewLeaveCount);
-    }
-
-    [Fact]
-    public void RemoveFocus_SubView_Raises_HasFocusChanged ()
-    {
-        var viewEnterCount = 0;
-        var viewLeaveCount = 0;
-
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-        view.HasFocusChanging += (s, e) => viewEnterCount++;
-        view.HasFocusChanged += (s, e) => viewLeaveCount++;
-
-        var subviewEnterCount = 0;
-        var subviewLeaveCount = 0;
-
-        var subview = new View
-        {
-            Id = "subview",
-            CanFocus = true
-        };
-        subview.HasFocusChanging += (s, e) => subviewEnterCount++;
-        subview.HasFocusChanged += (s, e) => subviewLeaveCount++;
-
-        view.Add (subview);
-
-        view.SetFocus ();
-
-        view.HasFocus = false;
-
-        Assert.Equal (1, viewEnterCount);
-        Assert.Equal (1, viewLeaveCount);
-
-        Assert.Equal (1, subviewEnterCount);
-        Assert.Equal (1, subviewLeaveCount);
-
-        view.SetFocus ();
-
-        Assert.Equal (2, viewEnterCount);
-        Assert.Equal (1, viewLeaveCount);
-
-        Assert.Equal (2, subviewEnterCount);
-        Assert.Equal (1, subviewLeaveCount);
-
-        subview.HasFocus = false;
-
-        Assert.Equal (2, viewEnterCount);
-        Assert.Equal (2, viewLeaveCount);
-
-        Assert.Equal (2, subviewEnterCount);
-        Assert.Equal (2, subviewLeaveCount);
-    }
-
-    [Fact]
-    public void SetFocus_CompoundSubView_Raises_HasFocusChanging ()
-    {
-        var viewEnterCount = 0;
-        var viewLeaveCount = 0;
-
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-        view.HasFocusChanging += (s, e) => viewEnterCount++;
-        view.HasFocusChanged += (s, e) => viewLeaveCount++;
-
-        var subViewEnterCount = 0;
-        var subViewLeaveCount = 0;
-
-        var subView = new View
-        {
-            Id = "subView",
-            CanFocus = true
-        };
-        subView.HasFocusChanging += (s, e) => subViewEnterCount++;
-        subView.HasFocusChanged += (s, e) => subViewLeaveCount++;
-
-        var subviewSubView1EnterCount = 0;
-        var subviewSubView1LeaveCount = 0;
-
-        var subViewSubView1 = new View
-        {
-            Id = "subViewSubView1",
-            CanFocus = false
-        };
-        subViewSubView1.HasFocusChanging += (s, e) => subviewSubView1EnterCount++;
-        subViewSubView1.HasFocusChanged += (s, e) => subviewSubView1LeaveCount++;
-
-        var subviewSubView2EnterCount = 0;
-        var subviewSubView2LeaveCount = 0;
-
-        var subViewSubView2 = new View
-        {
-            Id = "subViewSubView2",
-            CanFocus = true
-        };
-        subViewSubView2.HasFocusChanging += (s, e) => subviewSubView2EnterCount++;
-        subViewSubView2.HasFocusChanged += (s, e) => subviewSubView2LeaveCount++;
-
-        var subviewSubView3EnterCount = 0;
-        var subviewSubView3LeaveCount = 0;
-
-        var subViewSubView3 = new View
-        {
-            Id = "subViewSubView3",
-            CanFocus = false
-        };
-        subViewSubView3.HasFocusChanging += (s, e) => subviewSubView3EnterCount++;
-        subViewSubView3.HasFocusChanged += (s, e) => subviewSubView3LeaveCount++;
-
-        subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
-
-        view.Add (subView);
-
-        view.SetFocus ();
-        Assert.True (view.HasFocus);
-        Assert.True (subView.HasFocus);
-        Assert.False (subViewSubView1.HasFocus);
-        Assert.True (subViewSubView2.HasFocus);
-        Assert.False (subViewSubView3.HasFocus);
-
-        Assert.Equal (1, viewEnterCount);
-        Assert.Equal (0, viewLeaveCount);
-
-        Assert.Equal (1, subViewEnterCount);
-        Assert.Equal (0, subViewLeaveCount);
-
-        Assert.Equal (0, subviewSubView1EnterCount);
-        Assert.Equal (0, subviewSubView1LeaveCount);
-
-        Assert.Equal (1, subviewSubView2EnterCount);
-        Assert.Equal (0, subviewSubView2LeaveCount);
-
-        Assert.Equal (0, subviewSubView3EnterCount);
-        Assert.Equal (0, subviewSubView3LeaveCount);
-    }
-
-    [Fact]
-    public void RemoveFocus_CompoundSubView_Raises_HasFocusChanged ()
-    {
-        var viewEnterCount = 0;
-        var viewLeaveCount = 0;
-
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-        view.HasFocusChanging += (s, e) => viewEnterCount++;
-        view.HasFocusChanged += (s, e) => viewLeaveCount++;
-
-        var subViewEnterCount = 0;
-        var subViewLeaveCount = 0;
-
-        var subView = new View
-        {
-            Id = "subView",
-            CanFocus = true
-        };
-        subView.HasFocusChanging += (s, e) => subViewEnterCount++;
-        subView.HasFocusChanged += (s, e) => subViewLeaveCount++;
-
-        var subviewSubView1EnterCount = 0;
-        var subviewSubView1LeaveCount = 0;
-
-        var subViewSubView1 = new View
-        {
-            Id = "subViewSubView1",
-            CanFocus = false
-        };
-        subViewSubView1.HasFocusChanging += (s, e) => subviewSubView1EnterCount++;
-        subViewSubView1.HasFocusChanged += (s, e) => subviewSubView1LeaveCount++;
-
-        var subviewSubView2EnterCount = 0;
-        var subviewSubView2LeaveCount = 0;
-
-        var subViewSubView2 = new View
-        {
-            Id = "subViewSubView2",
-            CanFocus = true
-        };
-        subViewSubView2.HasFocusChanging += (s, e) => subviewSubView2EnterCount++;
-        subViewSubView2.HasFocusChanged += (s, e) => subviewSubView2LeaveCount++;
-
-        var subviewSubView3EnterCount = 0;
-        var subviewSubView3LeaveCount = 0;
-
-        var subViewSubView3 = new View
-        {
-            Id = "subViewSubView3",
-            CanFocus = false
-        };
-        subViewSubView3.HasFocusChanging += (s, e) => subviewSubView3EnterCount++;
-        subViewSubView3.HasFocusChanged += (s, e) => subviewSubView3LeaveCount++;
-
-        subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
-
-        view.Add (subView);
-
-        view.SetFocus ();
-        Assert.True (view.HasFocus);
-        Assert.True (subView.HasFocus);
-        Assert.False (subViewSubView1.HasFocus);
-        Assert.True (subViewSubView2.HasFocus);
-        Assert.False (subViewSubView3.HasFocus);
-
-        view.HasFocus = false;
-        Assert.False (view.HasFocus);
-        Assert.False (subView.HasFocus);
-        Assert.False (subViewSubView1.HasFocus);
-        Assert.False (subViewSubView2.HasFocus);
-        Assert.False (subViewSubView3.HasFocus);
-
-        Assert.Equal (1, viewEnterCount);
-        Assert.Equal (1, viewLeaveCount);
-
-        Assert.Equal (1, subViewEnterCount);
-        Assert.Equal (1, subViewLeaveCount);
-
-        Assert.Equal (0, subviewSubView1EnterCount);
-        Assert.Equal (0, subviewSubView1LeaveCount);
-
-        Assert.Equal (1, subviewSubView2EnterCount);
-        Assert.Equal (1, subviewSubView2LeaveCount);
-
-        Assert.Equal (0, subviewSubView3EnterCount);
-        Assert.Equal (0, subviewSubView3LeaveCount);
-    }
-
-    [Fact]
-    public void HasFocus_False_Leave_Raised ()
-    {
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-        Assert.True (view.CanFocus);
-        Assert.False (view.HasFocus);
-
-        var leaveInvoked = 0;
-
-        view.HasFocusChanged += (s, e) => leaveInvoked++;
-
-        view.SetFocus ();
-        Assert.True (view.HasFocus);
-        Assert.Equal (0, leaveInvoked);
-
-        view.HasFocus = false;
-        Assert.False (view.HasFocus);
-        Assert.Equal (1, leaveInvoked);
-    }
-
-    [Fact]
-    public void HasFocus_False_Leave_Raised_ForAllSubViews ()
-    {
-        var view = new View
-        {
-            Id = "view",
-            CanFocus = true
-        };
-
-        var subview = new View
-        {
-            Id = "subview",
-            CanFocus = true
-        };
-        view.Add (subview);
-
-        var leaveInvoked = 0;
-
-        view.HasFocusChanged += (s, e) => leaveInvoked++;
-        subview.HasFocusChanged += (s, e) => leaveInvoked++;
-
-        view.SetFocus ();
-        Assert.True (view.HasFocus);
-        Assert.Equal (0, leaveInvoked);
-
-        view.HasFocus = false;
-        Assert.False (view.HasFocus);
-        Assert.False (subview.HasFocus);
-        Assert.Equal (2, leaveInvoked);
-    }
-}

+ 31 - 19
UnitTests/View/Navigation/NavigationTests.cs

@@ -1,4 +1,5 @@
-using Xunit.Abstractions;
+using JetBrains.Annotations;
+using Xunit.Abstractions;
 using static System.Net.Mime.MediaTypeNames;
 
 namespace Terminal.Gui.ViewTests;
@@ -87,7 +88,8 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews
 
     [Theory]
     [MemberData (nameof (AllViewTypes))]
-    public void AllViews_Enter_Leave_Events (Type viewType)
+    [SetupFakeDriver]
+    public void AllViews_HasFocus_Changed_Event (Type viewType)
     {
         View view = CreateInstanceIfNotGeneric (viewType);
 
@@ -112,13 +114,13 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews
             return;
         }
 
-        Application.Init (new FakeDriver ());
-
         Toplevel top = new ()
         {
             Height = 10,
             Width = 10
         };
+        Application.Current = top;
+        Application.Navigation = new ApplicationNavigation();
 
         View otherView = new ()
         {
@@ -135,25 +137,34 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews
         view.Width = 10;
         view.Height = 1;
 
-        var nEnter = 0;
-        var nLeave = 0;
-
-        view.HasFocusChanging += (s, e) => nEnter++;
-        view.HasFocusChanged += (s, e) => nLeave++;
+        var hasFocusTrue = 0;
+        var hasFocusFalse = 0;
+
+        view.HasFocusChanged += (s, e) =>
+                                {
+                                    if (e.NewValue)
+                                    {
+                                        hasFocusTrue++;
+                                    }
+                                    else
+                                    {
+                                        hasFocusFalse++;
+                                    }
+                                };
 
         top.Add (view, otherView);
         Assert.False (view.HasFocus);
         Assert.False (otherView.HasFocus);
 
-        Application.Begin (top);
+        Application.Current.SetFocus ();
         Assert.True (Application.Current!.HasFocus);
         Assert.True (top.HasFocus);
 
         // Start with the focus on our test view
         Assert.True (view.HasFocus);
 
-        Assert.Equal (1, nEnter);
-        Assert.Equal (0, nLeave);
+        Assert.Equal (1, hasFocusTrue);
+        Assert.Equal (0, hasFocusFalse);
 
         // Use keyboard to navigate to next view (otherView).
         var tries = 0;
@@ -189,8 +200,8 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews
             }
         }
 
-        Assert.Equal (1, nEnter);
-        Assert.Equal (1, nLeave);
+        Assert.Equal (1, hasFocusTrue);
+        Assert.Equal (1, hasFocusFalse);
 
         Assert.False (view.HasFocus);
         Assert.True (otherView.HasFocus);
@@ -218,8 +229,8 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews
                 throw new ArgumentOutOfRangeException ();
         }
 
-        Assert.Equal (2, nEnter);
-        Assert.Equal (1, nLeave);
+        Assert.Equal (2, hasFocusTrue);
+        Assert.Equal (1, hasFocusFalse);
 
         Assert.True (view.HasFocus);
         Assert.False (otherView.HasFocus);
@@ -229,17 +240,18 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews
         bool otherViewHasFocus = otherView.HasFocus;
         bool viewHasFocus = view.HasFocus;
 
-        int enterCount = nEnter;
-        int leaveCount = nLeave;
+        int enterCount = hasFocusTrue;
+        int leaveCount = hasFocusFalse;
 
         top.Dispose ();
-        Application.Shutdown ();
 
         Assert.False (otherViewHasFocus);
         Assert.True (viewHasFocus);
 
         Assert.Equal (2, enterCount);
         Assert.Equal (1, leaveCount);
+
+        Application.ResetState ();
     }
 
     [Theory]

+ 8 - 4
UnitTests/View/Navigation/RestoreFocusTests.cs

@@ -106,13 +106,15 @@ public class RestoreFocusTests (ITestOutputHelper _output) : TestsAllViews
         var tabGroup1SubView1 = new View
         {
             Id = "tabGroup1SubView1",
-            CanFocus = true
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
         };
 
         var tabGroup1SubView2 = new View
         {
             Id = "tabGroup1SubView2",
-            CanFocus = true
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
         };
         tabGroup1.Add (tabGroup1SubView1, tabGroup1SubView2);
 
@@ -126,13 +128,15 @@ public class RestoreFocusTests (ITestOutputHelper _output) : TestsAllViews
         var tabGroup2SubView1 = new View
         {
             Id = "tabGroup2SubView1",
-            CanFocus = true
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
         };
 
         var tabGroup2SubView2 = new View
         {
             Id = "tabGroup2SubView2",
-            CanFocus = true
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
         };
         tabGroup2.Add (tabGroup2SubView1, tabGroup2SubView2);
 

+ 6 - 0
UnitTests/Views/ContextMenuTests.cs

@@ -1188,7 +1188,13 @@ public class ContextMenuTests (ITestOutputHelper output)
         };
         Toplevel top = new ();
         RunState rs = Application.Begin (top);
+        top.SetFocus ();
+        Assert.NotNull (Application.Current);
+
         cm.Show ();
+        Assert.True(ContextMenu.IsShow);
+        Assert.True (Application.Top.Subviews [0].HasFocus);
+        Assert.Equal(Application.Top.Subviews [0], Application.Navigation.GetFocused());
         Assert.Equal (new Rectangle (5, 11, 10, 5), Application.Top.Subviews [0].Frame);
         Application.Refresh ();
 

+ 5 - 15
UnitTests/Views/OverlappedTests.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Threading;
+using JetBrains.Annotations;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
@@ -1277,14 +1278,10 @@ public class OverlappedTests
     {
         public bool IsFocused { get; private set; }
 
-        protected override bool OnHasFocusChanging (View view)
+        protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
         {
-            IsFocused = true;
-
-            return false;
+            IsFocused = newHasFocus;
         }
-
-        protected override void OnHasFocusChanged (View view) { IsFocused = false; }
     }
 
     private class TestView : View
@@ -1295,16 +1292,9 @@ public class OverlappedTests
         }
         public bool IsFocused { get; private set; }
 
-        protected override bool OnHasFocusChanging (View view)
-        {
-            IsFocused = true;
-
-            return false; // don't cancel
-        }
-
-        protected override void OnHasFocusChanged (View view)
+        protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
         {
-            IsFocused = false;
+            IsFocused = newHasFocus;
         }
     }
 }

+ 12 - 17
UnitTests/Views/ScrollViewTests.cs

@@ -1,4 +1,5 @@
 using System.Text;
+using JetBrains.Annotations;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
@@ -1119,25 +1120,19 @@ public class ScrollViewTests (ITestOutputHelper output)
             CanFocus = true;
         }
 
-        protected override bool OnHasFocusChanging (View view)
+        protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
         {
-            Border.LineStyle = LineStyle.None;
-            Border.Thickness = new (0);
-            labelFill.Visible = true;
-            view = this;
-
-            return false; // don't cancel
-        }
-
-        protected override void OnHasFocusChanged (View view)
-        {
-            Border.LineStyle = LineStyle.Single;
-            Border.Thickness = new (1);
-            labelFill.Visible = false;
-
-            if (view == null)
+            if (newHasFocus)
+            {
+                Border.LineStyle = LineStyle.None;
+                Border.Thickness = new (0);
+                labelFill.Visible = true;
+            }
+            else
             {
-                view = this;
+                Border.LineStyle = LineStyle.Single;
+                Border.Thickness = new (1);
+                labelFill.Visible = false;
             }
         }
     }