From 794ad0bad730df42794fe1ae060e7b861bbf8e40 Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 19 Aug 2024 19:30:16 -0600 Subject: [PATCH] Refactoring again... WIP broke --- .../View/Navigation/FocusEventArgs.cs | 24 +- Terminal.Gui/View/View.Hierarchy.cs | 13 +- Terminal.Gui/View/View.Navigation.cs | 125 +-- Terminal.Gui/Views/ComboBox.cs | 36 +- Terminal.Gui/Views/ListView.cs | 2 +- Terminal.Gui/Views/Menu/Menu.cs | 4 +- Terminal.Gui/Views/Menu/MenuBar.cs | 9 +- Terminal.Gui/Views/Shortcut.cs | 14 +- Terminal.Gui/Views/TextField.cs | 2 +- Terminal.Gui/Views/TextView.cs | 2 +- Terminal.Gui/Views/TreeView/TreeView.cs | 12 +- UICatalog/Scenarios/ASCIICustomButton.cs | 31 +- UICatalog/Scenarios/KeyBindings.cs | 16 +- UnitTests/TestHelpers.cs | 1 + .../Navigation/HasFocusChangeEventTests.cs | 740 ++++++++++++++++++ .../View/Navigation/HasFocusEventTests.cs | 378 --------- UnitTests/View/Navigation/NavigationTests.cs | 48 +- .../View/Navigation/RestoreFocusTests.cs | 12 +- UnitTests/Views/ContextMenuTests.cs | 6 + UnitTests/Views/OverlappedTests.cs | 20 +- UnitTests/Views/ScrollViewTests.cs | 29 +- 21 files changed, 959 insertions(+), 565 deletions(-) create mode 100644 UnitTests/View/Navigation/HasFocusChangeEventTests.cs delete mode 100644 UnitTests/View/Navigation/HasFocusEventTests.cs diff --git a/Terminal.Gui/View/Navigation/FocusEventArgs.cs b/Terminal.Gui/View/Navigation/FocusEventArgs.cs index 2af837d14..bbe7b2264 100644 --- a/Terminal.Gui/View/Navigation/FocusEventArgs.cs +++ b/Terminal.Gui/View/Navigation/FocusEventArgs.cs @@ -1,25 +1,23 @@ namespace Terminal.Gui; /// Defines the event arguments for -public class FocusEventArgs : EventArgs +public class HasFocusEventArgs : CancelEventArgs { /// Initializes a new instance. - /// The view that is losing focus. - /// The view that is gaining focus. - public FocusEventArgs (View leaving, View entering) { - Leaving = leaving; - Entering = entering; + /// The current value of . + /// The value will have if the event is not cancelled. + /// The view that is losing focus. + /// The view that is gaining focus. + public HasFocusEventArgs (bool currentHasFocus, bool newHasFocus, View currentFocused, View newFocused) : base (ref currentHasFocus, ref newHasFocus) + { + CurrentFocused = currentFocused; + NewFocused = newFocused; } - /// - /// Gets or sets whether the event should be canceled. Set to to prevent the focus change. - /// - public bool Cancel { get; set; } - /// Gets or sets the view that is losing focus. - public View Leaving { get; set; } + public View CurrentFocused { get; set; } /// Gets or sets the view that is gaining focus. - public View Entering { get; set; } + public View NewFocused { get; set; } } diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs index 8c7eddd22..baa64006d 100644 --- a/Terminal.Gui/View/View.Hierarchy.cs +++ b/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 (); diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs index b861fa855..df8577396 100644 --- a/Terminal.Gui/View/View.Navigation.cs +++ b/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 /// public bool SetFocus () { - return FocusChanging (Application.Navigation?.GetFocused ()); + return SetHasFocusTrue (Application.Navigation?.GetFocused ()); } /// @@ -78,7 +78,7 @@ public partial class View // Focus and cross-view navigation management (TabStop /// /// if was changed to . /// - 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; } - /// Virtual method invoked when the focus is changing to this View. - /// The view that is currently Focused. May be . - /// , if the event is to be cancelled, otherwise. - protected virtual bool OnHasFocusChanging ([CanBeNull] View currentlyFocusedView) + /// + /// Invoked when is about to change. This method is called before the event is raised. + /// + /// The current value of . + /// The value will have if the event is not cancelled. + /// The view that is currently Focused. May be . + /// The view that will be focused. May be . + /// , if the change to is to be cancelled, otherwise. + protected virtual bool OnHasFocusChanging (bool currentHasFocus, bool newHasFocus, [CanBeNull] View currentFocused, [CanBeNull] View newFocused) { return false; } /// Raised when the view is gaining (entering) focus. Can be cancelled. - public event EventHandler HasFocusChanging; + public event EventHandler HasFocusChanging; /// /// Called when focus has changed to another view. /// /// The view that now has focus. If there is no view that has focus. /// - 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; - /// Virtual method invoked after another view gets focus. May be . - /// The view is now focused. - 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); + } + + /// + /// Invoked after has changed. This method is called before the event is raised. + /// + /// + /// + /// This event cannot be cancelled. + /// + /// + /// The new value of . + /// + /// The view that is now focused. May be + protected virtual void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew) { return; } - /// Raised when the view is gaining (entering) focus. Can not be cancelled. - public event EventHandler HasFocusChanged; + /// Raised after has changed. + /// + /// + /// This event cannot be cancelled. + /// + /// + public event EventHandler 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 (); diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index d307e1349..67648948a 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -300,7 +300,7 @@ public class ComboBox : View, IDesignable } /// - 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; } /// Virtual method which invokes the event. public virtual void OnExpanded () { Expanded?.Invoke (this, EventArgs.Empty); } /// - 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 (); + } } } diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index daff465b7..955a21499 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -739,7 +739,7 @@ public class ListView : View, IDesignable } /// - protected override bool OnHasFocusChanging (View view) + protected override bool OnHasFocusChanging (bool currentHasFocus, bool newHasFocus, [CanBeNull] View currentFocused, [CanBeNull] View newFocused) { if (_lastSelectedItem != _selected) { diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 1de4f77b9..308a876d4 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/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 () diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index e1a5f2d18..611da8112 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/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)) { diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 754b89061..7ae3bf485 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -834,22 +834,10 @@ public class Shortcut : View, IOrientation, IDesignable } } - private View _lastFocusedView; - /// - protected override bool OnHasFocusChanging (View view) + protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view) { SetColors (); - _lastFocusedView = view; - - return false; // Don't cancel the focus switch - } - - /// - protected override void OnHasFocusChanged (View view) - { - SetColors (); - _lastFocusedView = this; } #endregion Focus diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index f52b02972..14b62fc63 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1033,7 +1033,7 @@ public class TextField : View } /// - protected override void OnHasFocusChanged (View view) + protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view) { if (Application.MouseGrabView is { } && Application.MouseGrabView == this) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 58b2c75b5..2e9db834e 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -3650,7 +3650,7 @@ public class TextView : View } /// - protected override void OnHasFocusChanged (View view) + protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view) { if (Application.MouseGrabView is { } && Application.MouseGrabView == this) { diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index 9b8da2bae..0468dd873 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -1158,14 +1158,16 @@ public class TreeView : View, ITreeView where T : class } /// - 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 } /// diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index 90f18eb87..e82d66398 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/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; + //} } } diff --git a/UICatalog/Scenarios/KeyBindings.cs b/UICatalog/Scenarios/KeyBindings.cs index 2da18601c..ad38f8a95 100644 --- a/UICatalog/Scenarios/KeyBindings.cs +++ b/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 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]}"); + } } } } diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 784b21da5..70e977a29 100644 --- a/UnitTests/TestHelpers.cs +++ b/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); diff --git a/UnitTests/View/Navigation/HasFocusChangeEventTests.cs b/UnitTests/View/Navigation/HasFocusChangeEventTests.cs new file mode 100644 index 000000000..4a6167182 --- /dev/null +++ b/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); + } +} diff --git a/UnitTests/View/Navigation/HasFocusEventTests.cs b/UnitTests/View/Navigation/HasFocusEventTests.cs deleted file mode 100644 index 35a1e23f0..000000000 --- a/UnitTests/View/Navigation/HasFocusEventTests.cs +++ /dev/null @@ -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); - } -} diff --git a/UnitTests/View/Navigation/NavigationTests.cs b/UnitTests/View/Navigation/NavigationTests.cs index 297de677c..d89b4e0f8 100644 --- a/UnitTests/View/Navigation/NavigationTests.cs +++ b/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; + var hasFocusTrue = 0; + var hasFocusFalse = 0; - view.HasFocusChanging += (s, e) => nEnter++; - view.HasFocusChanged += (s, e) => nLeave++; + 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] diff --git a/UnitTests/View/Navigation/RestoreFocusTests.cs b/UnitTests/View/Navigation/RestoreFocusTests.cs index 6e7a1fdd7..4fe26722e 100644 --- a/UnitTests/View/Navigation/RestoreFocusTests.cs +++ b/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); diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index 99f9e3dfe..f26fff6bd 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/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 (); diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index c0f118fa1..0ce8c16b2 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/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) + protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew) { - IsFocused = true; - - return false; // don't cancel - } - - protected override void OnHasFocusChanged (View view) - { - IsFocused = false; + IsFocused = newHasFocus; } } } diff --git a/UnitTests/Views/ScrollViewTests.cs b/UnitTests/Views/ScrollViewTests.cs index 41e9def18..5a1337b0e 100644 --- a/UnitTests/Views/ScrollViewTests.cs +++ b/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) { - view = this; + Border.LineStyle = LineStyle.None; + Border.Thickness = new (0); + labelFill.Visible = true; + } + else + { + Border.LineStyle = LineStyle.Single; + Border.Thickness = new (1); + labelFill.Visible = false; } } }