diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 296021133..9ebe0c3b8 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -222,7 +222,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) internal static bool PositionCursor (View view) { // Find the most focused view and position the cursor there. - View? mostFocused = view?.MostFocused; + View? mostFocused = view?.GetMostFocused (); if (mostFocused is null) { @@ -858,7 +858,6 @@ public static partial class Application // Run (Begin, Run, End, Stop) if (Current is { HasFocus: false }) { Current.SetFocus (); - Current.RestoreFocus (null); } } diff --git a/Terminal.Gui/Application/ApplicationNavigation.cs b/Terminal.Gui/Application/ApplicationNavigation.cs index 8e4e5ed94..e83526b73 100644 --- a/Terminal.Gui/Application/ApplicationNavigation.cs +++ b/Terminal.Gui/Application/ApplicationNavigation.cs @@ -115,17 +115,17 @@ public class ApplicationNavigation /// internal static void MoveNextView () { - View? old = GetDeepestFocusedSubview (Application.Current!.Focused); + View? old = GetDeepestFocusedSubview (Application.Current!.GetFocused ()); if (!Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop)) { Application.Current.AdvanceFocus (NavigationDirection.Forward, null); } - if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) + if (old != Application.Current.GetFocused () && old != Application.Current.GetFocused ()?.GetFocused ()) { old?.SetNeedsDisplay (); - Application.Current.Focused?.SetNeedsDisplay (); + Application.Current.GetFocused ()?.SetNeedsDisplay (); } else { @@ -147,16 +147,16 @@ public class ApplicationNavigation { Application.Current.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - if (Application.Current.Focused is null) + if (Application.Current.GetFocused () is null) { Application.Current.RestoreFocus (TabBehavior.TabGroup); } } - if (top != Application.Current.Focused && top != Application.Current.Focused?.Focused) + if (top != Application.Current.GetFocused() && top != Application.Current.GetFocused ()?.GetFocused ()) { top?.SetNeedsDisplay (); - Application.Current.Focused?.SetNeedsDisplay (); + Application.Current.GetFocused ()?.SetNeedsDisplay (); } else { @@ -165,7 +165,7 @@ public class ApplicationNavigation //top!.AdvanceFocus (NavigationDirection.Forward); - //if (top.Focused is null) + //if (top.GetFocused () is null) //{ // top.AdvanceFocus (NavigationDirection.Forward); //} @@ -188,17 +188,17 @@ public class ApplicationNavigation /// internal static void MovePreviousView () { - View? old = GetDeepestFocusedSubview (Application.Current!.Focused); + View? old = GetDeepestFocusedSubview (Application.Current!.GetFocused()); if (!Application.Current.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop)) { Application.Current.AdvanceFocus (NavigationDirection.Backward, null); } - if (old != Application.Current.Focused && old != Application.Current.Focused?.Focused) + if (old != Application.Current.GetFocused() && old != Application.Current.GetFocused()?.GetFocused()) { old?.SetNeedsDisplay (); - Application.Current.Focused?.SetNeedsDisplay (); + Application.Current.GetFocused ()?.SetNeedsDisplay (); } else { @@ -208,12 +208,12 @@ public class ApplicationNavigation internal static void MovePreviousViewOrTop () { - if (ApplicationOverlapped.OverlappedTop is null) + if (ApplicationOverlapped.OverlappedTop is null) { Toplevel? top = Application.Current!.Modal ? Application.Current : Application.Top; top!.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup); - if (top.Focused is null) + if (top.GetFocused () is null) { top.AdvanceFocus (NavigationDirection.Backward, null); } diff --git a/Terminal.Gui/Application/ApplicationOverlapped.cs b/Terminal.Gui/Application/ApplicationOverlapped.cs index 14a4163ea..cdbdbe22c 100644 --- a/Terminal.Gui/Application/ApplicationOverlapped.cs +++ b/Terminal.Gui/Application/ApplicationOverlapped.cs @@ -75,7 +75,7 @@ public static class ApplicationOverlapped return; } - View? top = FindTopFromView (Application.Top?.MostFocused); + View? top = FindTopFromView (Application.Top?.GetMostFocused ()); if (top is Toplevel && Application.Top?.Subviews.Count > 1 && Application.Top.Subviews [^1] != top) { @@ -151,7 +151,7 @@ public static class ApplicationOverlapped // QUESTION: AdvanceFocus returns false AND sets Focused to null if no view was found to advance to. Should't we only set focusProcessed if it returned true? focusSet = true; - if (Application.Current.SuperView?.Focused != Application.Current) + if (Application.Current.SuperView?.GetFocused () != Application.Current) { return; } diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs index 92baa9c6b..21a84d345 100644 --- a/Terminal.Gui/View/View.Hierarchy.cs +++ b/Terminal.Gui/View/View.Hierarchy.cs @@ -64,6 +64,16 @@ public partial class View // SuperView/SubView hierarchy management (SuperView, if (view.CanFocus) { + view._tabIndex = _tabIndexes.IndexOf (view); + } + + if (view.Enabled && view.Visible && view.CanFocus) + { + if (HasFocus) + { + view.SetFocus (); + } + #if AUTO_CANFOCUS // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Instead, callers to Add should be explicit about what they want. _addingViewSoCanFocusAlsoUpdatesSuperView = true; @@ -79,7 +89,6 @@ public partial class View // SuperView/SubView hierarchy management (SuperView, CanFocus = true; _addingViewSoCanFocusAlsoUpdatesSuperView = false; #endif - view._tabIndex = _tabIndexes.IndexOf (view); } if (view.Enabled && !Enabled) @@ -184,6 +193,12 @@ 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.LeaveFocus(this, true); + } + Rectangle touched = view.Frame; _subviews.Remove (view); _tabIndexes!.Remove (view); @@ -200,13 +215,13 @@ public partial class View // SuperView/SubView hierarchy management (SuperView, } } - OnRemoved (new (this, view)); - - if (Focused == view) + if (HasFocus) { - Focused = null; + FocusDeepest (TabStop, NavigationDirection.Forward); } + OnRemoved (new (this, view)); + return view; } diff --git a/Terminal.Gui/View/View.Keyboard.cs b/Terminal.Gui/View/View.Keyboard.cs index 73625417a..517e4d12b 100644 --- a/Terminal.Gui/View/View.Keyboard.cs +++ b/Terminal.Gui/View/View.Keyboard.cs @@ -286,7 +286,7 @@ public partial class View // Keyboard APIs // By default the KeyBindingScope is View - if (Focused?.NewKeyDownEvent (keyEvent) == true) + if (GetFocused ()?.NewKeyDownEvent (keyEvent) == true) { return true; } @@ -446,7 +446,7 @@ public partial class View // Keyboard APIs return false; } - if (Focused?.NewKeyUpEvent (keyEvent) == true) + if (GetFocused ()?.NewKeyUpEvent (keyEvent) == true) { return true; } @@ -611,7 +611,7 @@ public partial class View // Keyboard APIs // Now, process any key bindings in the subviews that are tagged to KeyBindingScope.HotKey. foreach (View subview in Subviews) { - if (subview == Focused) + if (subview == GetFocused ()) { continue; } diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs index 42688e810..b21d9ca6d 100644 --- a/Terminal.Gui/View/View.Navigation.cs +++ b/Terminal.Gui/View/View.Navigation.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Reflection.Metadata.Ecma335; +using Microsoft.CodeAnalysis.Operations; using static Terminal.Gui.FakeDriver; namespace Terminal.Gui; @@ -43,7 +45,7 @@ public partial class View // Focus and cross-view navigation management (TabStop { if (value) { - if (EnterFocus (Application.Navigation?.GetFocused ())) + if (EnterFocus (Application.Navigation!.GetFocused ())) { // The change happened // HasFocus is now true @@ -82,12 +84,12 @@ public partial class View // Focus and cross-view navigation management (TabStop // Pre-conditions if (_hasFocus) { - throw new InvalidOperationException ($"EnterFocus should not be called if the view already has focus."); + return false; } - if (CanFocus && SuperView?.CanFocus == false) + if (CanFocus && SuperView is { CanFocus: false }) { - throw new InvalidOperationException ($"It is not possible to EnterFocus if the View's SuperView has CanFocus = false."); + return false; } if (!CanBeVisible (this) || !Enabled) @@ -104,19 +106,8 @@ public partial class View // Focus and cross-view navigation management (TabStop if (!traversingUp) { - // Call the virtual method - if (OnEnter (leavingView)) + if (CancelEnterFocus (leavingView)) { - // The event was cancelled - return false; - } - - var args = new FocusEventArgs (leavingView, this); - Enter?.Invoke (this, args); - - if (args.Cancel) - { - // The event was cancelled return false; } @@ -140,44 +131,72 @@ public partial class View // Focus and cross-view navigation management (TabStop // 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. - if (Focused is { }) - { - // LeaveFocus will recurse down the subview hierarchy and will also set PreviouslyMostFocused - Focused.LeaveFocus (this); - Focused = null; - } + // LeaveFocus will recurse down the subview hierarchy and will also set PreviouslyMostFocused + View focused = GetFocused (); + focused?.LeaveFocus (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.EnterFocus (leavingView, traversingUp)) + if (!sv.EnterFocus (leavingView, true)) { // The change was cancelled return false; } + } - // If we're here, we're the most-focusable view in the application and all superviews up the superview hierarchy have focus. + // If we're here: + // - we're the most-focusable view in the application + // - all superviews up the superview hierarchy have focus. + // - By setting _hasFocus to true we definitively change HasFocus for this view. + + // Get whatever peer has focus, if any + View focusedPeer = SuperView?.GetFocused (); - // By setting _hasFocus to true we definitively change HasFocus for this view. _hasFocus = true; + // Ensure that the peer loses focus + focusedPeer?.LeaveFocus (this); + // We're the most focused view in the application, we need to set the focused view to this view. Application.Navigation?.SetFocused (this); + SetNeedsDisplay (); + // Post-conditions - prove correctness if (HasFocus == previousValue) { throw new InvalidOperationException ($"EnterFocus was not cancelled and the HasFocus value did not change."); } - SetNeedsDisplay (); - return true; } + + private bool CancelEnterFocus (View leavingView) + { + // Call the virtual method + if (OnEnter (leavingView)) + { + // The event was cancelled + return true; + } + + var args = new FocusEventArgs (leavingView, this); + Enter?.Invoke (this, args); + + if (args.Cancel) + { + // The event was cancelled + return true; + } + + return false; + } + /// Virtual method invoked when this view is gaining focus (entering). /// The view that is leaving focus. /// , if the event is to be cancelled, otherwise. @@ -198,25 +217,26 @@ public partial class View // Focus and cross-view navigation management (TabStop /// The previously focused view. If there is no previously focused view. /// if was changed. /// - private void LeaveFocus ([CanBeNull] View enteringView) + private void LeaveFocus ([CanBeNull] View enteringView, bool traversingDown = false) { // Pre-conditions - if (_hasFocus) + if (!_hasFocus) { throw new InvalidOperationException ($"LeaveFocus should not be called if the view does not have focus."); } // If enteringView is null, we need to find the view that should get focus, and SetFocus on it. - if (enteringView is null) + if (enteringView is null && SuperView is { }) { - if (SuperView?.PreviouslyMostFocused != this) + if (SuperView?.PreviouslyMostFocused is { } && SuperView?.PreviouslyMostFocused != this) { SuperView?.PreviouslyMostFocused?.SetFocus (); // The above will cause LeaveFocus, so we can return return; } - else + + if (Application.Navigation is { }) { // Temporarily ensure this view can't get focus bool prevCanFocus = _canFocus; @@ -230,24 +250,24 @@ public partial class View // Focus and cross-view navigation management (TabStop } // Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus. - if (Application.Navigation?.GetFocused () != this) + View mostFocused = GetMostFocused (); + if (mostFocused is { }) { - // Save the most focused view in the subview-hierarchy - View originalBottom = Application.Navigation?.GetFocused (); // Start at the bottom and work our way up to us - View bottom = originalBottom; + View bottom = mostFocused; while (bottom is { } && bottom != this) { if (bottom.HasFocus) { - bottom.LeaveFocus (enteringView); - return ; + bottom.LeaveFocus (enteringView, true); + + break; } bottom = bottom.SuperView; } - PreviouslyMostFocused = originalBottom; + PreviouslyMostFocused = mostFocused; } bool previousValue = HasFocus; @@ -258,17 +278,16 @@ public partial class View // Focus and cross-view navigation management (TabStop var args = new FocusEventArgs (enteringView, this); Leave?.Invoke (this, args); - Focused = null; + // Get whatever peer has focus, if any + View focusedPeer = SuperView?.GetFocused (); _hasFocus = false; - if (Application.Navigation?.GetFocused () != this) + if (!traversingDown) { - PreviouslyMostFocused = null; - - if (SuperView is { }) + // Now ensure all views up the superview-hierarchy are unfocused + if (SuperView is { HasFocus: true } && focusedPeer == this) { - SuperView.Focused = null; - SuperView.PreviouslyMostFocused = this; + SuperView.LeaveFocus (enteringView); } } @@ -330,34 +349,29 @@ public partial class View // Focus and cross-view navigation management (TabStop return false; } - if (Focused is null) + View focused = GetFocused (); + if (focused is null) { - FocusDeepest (behavior, direction); - - return Focused is { }; - } - - if (Focused is { }) - { - if (Focused.AdvanceFocus (direction, behavior)) + View deepest = FindDeepestFocusableView (behavior, direction); + if (deepest is { }) { - // TODO: Temporary hack to make Application.Navigation.FocusChanged work - if (Focused.Focused is null) - { - Application.Navigation?.SetFocused (Focused); - } - return true; + return deepest.SetFocus (); } } - var index = GetScopedTabIndexes (behavior, direction); + if (focused!.AdvanceFocus (direction, behavior)) + { + return true; + } + + View[] index = GetScopedTabIndexes (behavior, direction); if (index.Length == 0) { return false; } - var focusedIndex = index.IndexOf (Focused); + var focusedIndex = index.IndexOf (GetFocused ()); int next = 0; if (focusedIndex < index.Length - 1) @@ -370,7 +384,7 @@ public partial class View // Focus and cross-view navigation management (TabStop { // Go down the subview-hierarchy and leave // BUGBUG: This doesn't seem right - Focused.HasFocus = false; + GetFocused ().HasFocus = false; // TODO: Should we check the return value of SetHasFocus? @@ -382,38 +396,12 @@ public partial class View // Focus and cross-view navigation management (TabStop if (view.HasFocus) { - return true; + // We could not advance + return false; } // The subview does not have focus, but at least one other that can. Can this one be focused? - if (view.CanFocus && view.Visible && view.Enabled) - { - // Make Focused Leave - // BUGBUG: This doesn't seem right - Focused.HasFocus = false; - - view.FocusDeepest (TabBehavior.TabStop, direction); - - // TODO: Temporary hack to make Application.Navigation.FocusChanged work - if (view.Focused is null) - { - Application.Navigation?.SetFocused (view); - } - - return true; - } - - if (Focused is { }) - { - // Leave - // BUGBUG: This doesn't seem right - Focused.HasFocus = false; - - // Signal that nothing is focused, and callers should try a peer-subview - Focused = null; - } - - return false; + return view.EnterFocus (GetFocused ()); } @@ -425,19 +413,40 @@ public partial class View // Focus and cross-view navigation management (TabStop /// internal bool RestoreFocus (TabBehavior? behavior) { - if (Focused is null && _subviews?.Count > 0) + if (GetFocused () is null && _subviews?.Count > 0) { // TODO: Find the previous focused view and set focus to it if (PreviouslyMostFocused is { } && PreviouslyMostFocused.TabStop == behavior) { return PreviouslyMostFocused.SetFocus (); } - return true; + return false; } return false; } + /// + /// Returns the most focused Subview down the subview-hierarchy. + /// + /// The most focused Subview, or if no Subview is focused. + public View GetMostFocused () + { + if (GetFocused () is null) + { + return null; + } + + View most = GetFocused ()!.GetMostFocused (); + + if (most is { }) + { + return most; + } + + return GetFocused (); + } + ///// ///// Internal API that causes to enter focus. ///// must be a subview. @@ -571,13 +580,6 @@ public partial class View // Focus and cross-view navigation management (TabStop get => _canFocus; set { -#if AUTO_CANFOCUS - if (!_addingViewSoCanFocusAlsoUpdatesSuperView && IsInitialized && SuperView?.CanFocus == false && value) - { - throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); - } -#endif - if (_canFocus == value) { return; @@ -585,92 +587,24 @@ public partial class View // Focus and cross-view navigation management (TabStop _canFocus = value; -#if AUTO_CANFOCUS - switch (_canFocus) - { - case false when _tabIndex > -1: - // BUGBUG: This is a poor API design. Automatic behavior like this is non-obvious and should be avoided. Callers should adjust TabIndex explicitly. - //TabIndex = -1; - - break; - - case true when SuperView?.CanFocus == false && _addingViewSoCanFocusAlsoUpdatesSuperView: - SuperView.CanFocus = true; - - break; - } -#endif - if (TabStop is null && _canFocus) { TabStop = TabBehavior.TabStop; } - if (!_canFocus && SuperView?.Focused == this) - { - SuperView.Focused = null; - } - if (!_canFocus && HasFocus) { + // If CanFocus is set to false and this view has focus, make it leave focus HasFocus = false; - SuperView?.RestoreFocus (null); - - // If EnsureFocus () didn't set focus to a view, focus the next focusable view in the application - if (SuperView is { Focused: null }) - { - SuperView.AdvanceFocus (NavigationDirection.Forward, null); - - if (SuperView.Focused is null && Application.Current is { }) - { - Application.Current.AdvanceFocus (NavigationDirection.Forward, null); - } - - ApplicationOverlapped.BringOverlappedTopToFront (); - } } - if (_subviews is { } && IsInitialized) + if (_canFocus && SuperView is { } && SuperView.GetFocused () is null && !HasFocus) { -#if AUTO_CANFOCUS - // Change the CanFocus of all subviews to the same value as this view - // if the CanFocus of the subview is different from the value being set - foreach (View view in _subviews) - { - if (view.CanFocus != value) - { - if (!value) - { - // Cache the old CanFocus and TabIndex so that they can be restored when CanFocus is changed back to true - view._oldCanFocus = view.CanFocus; - view._oldTabIndex = view._tabIndex; - view.CanFocus = false; - - //view._tabIndex = -1; - } - else - { - if (_addingViewSoCanFocusAlsoUpdatesSuperView) - { - view._addingViewSoCanFocusAlsoUpdatesSuperView = true; - } - - // Restore the old CanFocus and TabIndex to the values they held before CanFocus was set to false - view.CanFocus = view._oldCanFocus; - view._tabIndex = view._oldTabIndex; - view._addingViewSoCanFocusAlsoUpdatesSuperView = false; - } - } - } -#endif - if (this is Toplevel && Application.Current!.Focused != this) - { - ApplicationOverlapped.BringOverlappedTopToFront (); - } + // If CanFocus is set to true and this view does not have focus, make it enter focus + SetFocus (); } OnCanFocusChanged (); - SetNeedsDisplay (); } } @@ -680,10 +614,13 @@ public partial class View // Focus and cross-view navigation management (TabStop /// public event EventHandler CanFocusChanged; - /// Returns the currently focused Subview inside this view, or if nothing is focused. + /// Returns the currently focused Subview of this view, or if nothing is focused. /// The currently focused Subview. [CanBeNull] - public View Focused { get; private set; } + public View GetFocused () + { + return Subviews.FirstOrDefault (v => v.HasFocus); + } /// /// Focuses the deepest focusable view in if one exists. If there are no views in @@ -691,19 +628,17 @@ public partial class View // Focus and cross-view navigation management (TabStop /// /// /// - public void FocusDeepest (TabBehavior? behavior, NavigationDirection direction) + /// if a subview other than this was focused. + public bool FocusDeepest (TabBehavior? behavior, NavigationDirection direction) { - if (!CanBeVisible (this)) - { - return; - } - View deepest = FindDeepestFocusableView (behavior, direction); if (deepest is { }) { - deepest.SetFocus (); + return deepest.SetFocus (); } + + return SetFocus (); } [CanBeNull] @@ -727,30 +662,6 @@ public partial class View // Focus and cross-view navigation management (TabStop /// Returns a value indicating if this View is currently on Top (Active) public bool IsCurrentTop => Application.Current == this; - /// - /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus), or - /// if nothing is focused. - /// - /// The most focused Subview. - public View MostFocused - { - get - { - if (Focused is null) - { - return null; - } - - View most = Focused.MostFocused; - - if (most is { }) - { - return most; - } - - return Focused; - } - } /// Invoked when the property from a view is changed. /// diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index a900d060e..35f01bdc7 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -391,7 +391,7 @@ public class MenuBar : View, IDesignable _selected = 0; SetNeedsDisplay (); - _previousFocused = SuperView is null ? Application.Current?.Focused : SuperView.Focused; + _previousFocused = SuperView is null ? Application.Current?.GetFocused () : SuperView.GetFocused (); OpenMenu (_selected); if (!SelectEnabledItem ( @@ -452,7 +452,7 @@ public class MenuBar : View, IDesignable if (_openMenu is null) { - _previousFocused = SuperView is null ? Application.Current?.Focused ?? null : SuperView.Focused; + _previousFocused = SuperView is null ? Application.Current?.GetFocused () ?? null : SuperView.GetFocused (); } OpenMenu (idx, sIdx, subMenu); @@ -755,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 ??= SuperView is null ? Application.Current?.GetMostFocused () : SuperView.GetMostFocused (); if (_openSubMenu is { } && !CloseMenu (false, true)) { diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index 279cfd6cf..df57bd6b0 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -67,7 +67,7 @@ public class NumericUpDown : View where T : notnull Width = Dim.Auto (minimumContentDim: Dim.Func (() => string.Format (Format, Value).Length)), Height = 1, TextAlignment = Alignment.Center, - CanFocus = true + CanFocus = true, }; _up = new () diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index e6959a491..2835820f6 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -77,7 +77,7 @@ public class TabView : View { _contentView.SetFocus (); - return _contentView.Focused is { }; + return _contentView.GetFocused () is { }; } return false; @@ -94,7 +94,7 @@ public class TabView : View { _contentView.SetFocus (); - return _contentView.Focused is { }; + return _contentView.GetFocused () is { }; } return false; @@ -1300,7 +1300,7 @@ public class TabView : View // if tab is the selected one and focus is inside this control if (toRender.IsSelected && _host.HasFocus) { - if (_host.Focused == this) + if (_host.GetFocused () == this) { // if focus is the tab bar itself then show that they can switch tabs prevAttr = ColorScheme.HotFocus; diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 5afbcb22a..b74cf8ced 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -79,7 +79,6 @@ public partial class Toplevel : View /// public override View Add (View view) { - CanFocus = true; AddMenuStatusBar (view); return base.Add (view); @@ -432,7 +431,7 @@ public partial class Toplevel : View { if (!IsOverlappedContainer) { - if (Focused is null) + if (GetFocused () is null) { RestoreFocus (null); } @@ -442,7 +441,7 @@ public partial class Toplevel : View // This code path only happens when the Toplevel is an Overlapped container - if (Focused is null) + if (GetFocused () is null) { // TODO: this is an Overlapped hack foreach (Toplevel top in ApplicationOverlapped.OverlappedChildren!) diff --git a/UICatalog/Scenarios/ConfigurationEditor.cs b/UICatalog/Scenarios/ConfigurationEditor.cs index 1e095a527..d2d80b859 100644 --- a/UICatalog/Scenarios/ConfigurationEditor.cs +++ b/UICatalog/Scenarios/ConfigurationEditor.cs @@ -101,7 +101,7 @@ public class ConfigurationEditor : Scenario } public void Save () { - if (_tileView.MostFocused is ConfigTextView editor) + if (_tileView.GetMostFocused () is ConfigTextView editor) { editor.Save (); } @@ -172,7 +172,7 @@ public class ConfigurationEditor : Scenario private void Reload () { - if (_tileView.MostFocused is ConfigTextView editor) + if (_tileView.GetMostFocused () is ConfigTextView editor) { editor.Read (); } diff --git a/UICatalog/Scenarios/KeyBindings.cs b/UICatalog/Scenarios/KeyBindings.cs index d62444543..1854da2eb 100644 --- a/UICatalog/Scenarios/KeyBindings.cs +++ b/UICatalog/Scenarios/KeyBindings.cs @@ -139,10 +139,10 @@ public sealed class KeyBindings : Scenario private void AppWindow_DrawContent (object sender, DrawEventArgs e) { - _focusedBindingsListView.Title = $"_Focused ({Application.Top.MostFocused.GetType ().Name}) Bindings"; + _focusedBindingsListView.Title = $"_Focused ({Application.Top.GetMostFocused ().GetType ().Name}) Bindings"; _focusedBindings.Clear (); - foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused)) + foreach (var binding in Application.Top.GetMostFocused ().KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused)) { _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}"); } @@ -150,7 +150,7 @@ public sealed class KeyBindings : Scenario private void AppWindow_Leave (object sender, FocusEventArgs e) { - foreach (var binding in Application.Top.MostFocused.KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused)) + foreach (var binding in Application.Top.GetMostFocused ().KeyBindings.Bindings.Where (b => b.Value.Scope == KeyBindingScope.Focused)) { _focusedBindings.Add ($"{binding.Key} -> {binding.Value.Commands [0]}"); } diff --git a/UICatalog/Scenarios/MenuBarScenario.cs b/UICatalog/Scenarios/MenuBarScenario.cs index b9c6bee67..edac4a054 100644 --- a/UICatalog/Scenarios/MenuBarScenario.cs +++ b/UICatalog/Scenarios/MenuBarScenario.cs @@ -107,7 +107,7 @@ public class MenuBarScenario : Scenario }; // There's no focus change event, so this is a bit of a hack. - menuBar.LayoutComplete += (s, e) => { _focusedView.Text = appWindow.MostFocused?.ToString () ?? "None"; }; + menuBar.LayoutComplete += (s, e) => { _focusedView.Text = appWindow.GetMostFocused ()?.ToString () ?? "None"; }; var openBtn = new Button { X = Pos.Center (), Y = 4, Text = "_Open Menu", IsDefault = true }; openBtn.Accept += (s, e) => { menuBar.OpenMenu (); }; diff --git a/UnitTests/FileServices/FileDialogTests.cs b/UnitTests/FileServices/FileDialogTests.cs index 1c0954168..873819f7c 100644 --- a/UnitTests/FileServices/FileDialogTests.cs +++ b/UnitTests/FileServices/FileDialogTests.cs @@ -107,8 +107,8 @@ public class FileDialogTests (ITestOutputHelper output) Application.OnKeyDown (Key.Tab); #endif - Assert.IsType (dlg.MostFocused); - var tf = (TextField)dlg.MostFocused; + Assert.IsType (dlg.GetMostFocused ()); + var tf = (TextField)dlg.GetMostFocused (); Assert.Equal ("Enter Search", tf.Caption); // Dialog has not yet been confirmed with a choice @@ -142,9 +142,9 @@ public class FileDialogTests (ITestOutputHelper output) AssertIsTheStartingDirectory (dlg.Path); - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); Send ('v', ConsoleKey.DownArrow); - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); // ".." should be the first thing selected // ".." should not mess with the displayed path @@ -181,9 +181,9 @@ public class FileDialogTests (ITestOutputHelper output) IReadOnlyCollection eventMultiSelected = null; dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; }; - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); Send ('v', ConsoleKey.DownArrow); - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); // Try to toggle '..' Send (' ', ConsoleKey.Spacebar); @@ -236,9 +236,9 @@ public class FileDialogTests (ITestOutputHelper output) IReadOnlyCollection eventMultiSelected = null; dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; }; - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); Send ('v', ConsoleKey.DownArrow); - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); // Move selection to subfolder Send ('v', ConsoleKey.DownArrow); @@ -288,9 +288,9 @@ public class FileDialogTests (ITestOutputHelper output) IReadOnlyCollection eventMultiSelected = null; dlg.FilesSelected += (s, e) => { eventMultiSelected = e.Dialog.MultiSelected; }; - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); Send ('v', ConsoleKey.DownArrow); - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); // Move selection to subfolder Send ('v', ConsoleKey.DownArrow); @@ -331,9 +331,9 @@ public class FileDialogTests (ITestOutputHelper output) dlg.OpenMode = openModeMixed ? OpenMode.Mixed : OpenMode.Directory; dlg.AllowsMultipleSelection = multiple; - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); Send ('v', ConsoleKey.DownArrow); - Assert.IsType (dlg.MostFocused); + Assert.IsType (dlg.GetMostFocused ()); // Should be selecting .. Send ('v', ConsoleKey.DownArrow); diff --git a/UnitTests/View/Navigation/AddRemoveTests.cs b/UnitTests/View/Navigation/AddRemoveTests.cs new file mode 100644 index 000000000..873f38e21 --- /dev/null +++ b/UnitTests/View/Navigation/AddRemoveTests.cs @@ -0,0 +1,140 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewTests; + +public class AddRemoveNavigationTests (ITestOutputHelper _output) : TestsAllViews +{ + [Fact] + public void Add_Subview_Gets_Focus () + { + View top = new View () + { + Id = "top", + CanFocus = true + }; + + top.SetFocus (); + Assert.True (top.HasFocus); + + int nEnter = 0; + View subView = new View () + { + Id = "subView", + CanFocus = true + }; + subView.Enter += (s, e) => nEnter++; + + top.Add (subView); + + Assert.True (top.HasFocus); + Assert.Equal (subView, top.GetFocused ()); + Assert.True (subView.HasFocus); + Assert.Equal (1, nEnter); + } + + [Fact] + public void Add_Subview_Deepest_Gets_Focus () + { + View top = new View () + { + Id = "top", + CanFocus = true + }; + + top.SetFocus (); + Assert.True (top.HasFocus); + + View subView = new View () + { + Id = "subView", + CanFocus = true + }; + + View subSubView = new View () + { + Id = "subSubView", + CanFocus = true + }; + + subView.Add (subSubView); + + top.Add (subView); + + Assert.True (top.HasFocus); + Assert.Equal (subView, top.GetFocused ()); + Assert.True (subView.HasFocus); + Assert.True (subSubView.HasFocus); + } + + [Fact] + public void Remove_Focused_Subview_Keeps_Focus_And_SubView_Looses_Focus () + { + View top = new View () + { + Id = "top", + CanFocus = true + }; + + int nLeave = 0; + + View subView = new View () + { + Id = "subView", + CanFocus = true + }; + subView.Leave += (s, e) => nLeave++; + + top.Add (subView); + + top.SetFocus (); + Assert.True (top.HasFocus); + Assert.Equal (subView, top.GetFocused ()); + Assert.True (subView.HasFocus); + + top.Remove (subView); + Assert.True (top.HasFocus); + Assert.Equal (null, top.GetFocused ()); + Assert.False (subView.HasFocus); + Assert.Equal (1, nLeave); + } + + [Fact] + public void Remove_Focused_Subview_Keeps_Focus_And_SubView_Looses_Focus_And_Next_Gets_Focus () + { + View top = new View () + { + Id = "top", + CanFocus = true + }; + + int nLeave1 = 0; + + View subView1 = new View () + { + Id = "subView1", + CanFocus = true + }; + subView1.Leave += (s, e) => nLeave1++; + + View subView2 = new View () + { + Id = "subView2", + CanFocus = true + }; + top.Add (subView1, subView2); + + top.SetFocus (); + Assert.True (top.HasFocus); + Assert.Equal (subView1, top.GetFocused ()); + Assert.True (subView1.HasFocus); + Assert.False (subView2.HasFocus); + + top.Remove (subView1); + Assert.True (top.HasFocus); + Assert.True (subView2.HasFocus); + Assert.Equal (subView2, top.GetFocused ()); + Assert.False (subView1.HasFocus); + Assert.Equal (1, nLeave1); + } + +} diff --git a/UnitTests/View/Navigation/AdvanceFocusTests.cs b/UnitTests/View/Navigation/AdvanceFocusTests.cs new file mode 100644 index 000000000..1910daad0 --- /dev/null +++ b/UnitTests/View/Navigation/AdvanceFocusTests.cs @@ -0,0 +1,393 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewTests; + +public class AdvanceFocusTests (ITestOutputHelper _output) +{ + [Fact] + public void Subviews_TabIndexes_AreEqual () + { + var r = new View (); + var v1 = new View { CanFocus = true }; + var v2 = new View { CanFocus = true }; + var v3 = new View { CanFocus = true }; + + r.Add (v1, v2, v3); + + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.Subviews.IndexOf (v2) == 1); + Assert.True (r.Subviews.IndexOf (v3) == 2); + + Assert.True (r.TabIndexes.IndexOf (v1) == 0); + Assert.True (r.TabIndexes.IndexOf (v2) == 1); + Assert.True (r.TabIndexes.IndexOf (v3) == 2); + + Assert.Equal (r.Subviews.IndexOf (v1), r.TabIndexes.IndexOf (v1)); + Assert.Equal (r.Subviews.IndexOf (v2), r.TabIndexes.IndexOf (v2)); + Assert.Equal (r.Subviews.IndexOf (v3), r.TabIndexes.IndexOf (v3)); + r.Dispose (); + } + + [Fact] + public void TabIndex_Invert_Order () + { + var r = new View (); + var v1 = new View { Id = "1", CanFocus = true }; + var v2 = new View { Id = "2", CanFocus = true }; + var v3 = new View { Id = "3", CanFocus = true }; + + r.Add (v1, v2, v3); + + v1.TabIndex = 2; + v2.TabIndex = 1; + v3.TabIndex = 0; + Assert.True (r.TabIndexes.IndexOf (v1) == 2); + Assert.True (r.TabIndexes.IndexOf (v2) == 1); + Assert.True (r.TabIndexes.IndexOf (v3) == 0); + + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.Subviews.IndexOf (v2) == 1); + Assert.True (r.Subviews.IndexOf (v3) == 2); + } + + [Fact] + public void TabIndex_Invert_Order_Added_One_By_One_Does_Not_Do_What_Is_Expected () + { + var r = new View (); + var v1 = new View { Id = "1", CanFocus = true }; + r.Add (v1); + v1.TabIndex = 2; + var v2 = new View { Id = "2", CanFocus = true }; + r.Add (v2); + v2.TabIndex = 1; + var v3 = new View { Id = "3", CanFocus = true }; + r.Add (v3); + v3.TabIndex = 0; + + Assert.False (r.TabIndexes.IndexOf (v1) == 2); + Assert.True (r.TabIndexes.IndexOf (v1) == 1); + Assert.False (r.TabIndexes.IndexOf (v2) == 1); + Assert.True (r.TabIndexes.IndexOf (v2) == 2); + + // Only the last is in the expected index + Assert.True (r.TabIndexes.IndexOf (v3) == 0); + + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.Subviews.IndexOf (v2) == 1); + Assert.True (r.Subviews.IndexOf (v3) == 2); + } + + [Fact] + public void TabIndex_Invert_Order_Mixed () + { + var r = new View (); + var vl1 = new View { Id = "vl1" }; + var v1 = new View { Id = "v1", CanFocus = true }; + var vl2 = new View { Id = "vl2" }; + var v2 = new View { Id = "v2", CanFocus = true }; + var vl3 = new View { Id = "vl3" }; + var v3 = new View { Id = "v3", CanFocus = true }; + + r.Add (vl1, v1, vl2, v2, vl3, v3); + + v1.TabIndex = 2; + v2.TabIndex = 1; + v3.TabIndex = 0; + Assert.True (r.TabIndexes.IndexOf (v1) == 4); + Assert.True (r.TabIndexes.IndexOf (v2) == 2); + Assert.True (r.TabIndexes.IndexOf (v3) == 0); + + Assert.True (r.Subviews.IndexOf (v1) == 1); + Assert.True (r.Subviews.IndexOf (v2) == 3); + Assert.True (r.Subviews.IndexOf (v3) == 5); + } + + [Fact] + public void TabIndex_Set_CanFocus_False () + { + var r = new View (); + var v1 = new View { CanFocus = true }; + var v2 = new View { CanFocus = true }; + var v3 = new View { CanFocus = true }; + + r.Add (v1, v2, v3); + + v1.CanFocus = false; + v1.TabIndex = 0; + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.TabIndexes.IndexOf (v1) == 0); + Assert.NotEqual (-1, v1.TabIndex); + r.Dispose (); + } + + [Fact] + public void TabIndex_Set_CanFocus_False_To_True () + { + var r = new View (); + var v1 = new View (); + var v2 = new View { CanFocus = true }; + var v3 = new View { CanFocus = true }; + + r.Add (v1, v2, v3); + + v1.CanFocus = true; + v1.TabIndex = 1; + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.TabIndexes.IndexOf (v1) == 1); + r.Dispose (); + } + + [Fact] + public void TabIndex_Set_CanFocus_HigherValues () + { + var r = new View (); + var v1 = new View { CanFocus = true }; + var v2 = new View { CanFocus = true }; + var v3 = new View { CanFocus = true }; + + r.Add (v1, v2, v3); + + v1.TabIndex = 3; + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.TabIndexes.IndexOf (v1) == 2); + r.Dispose (); + } + + [Fact] + public void TabIndex_Set_CanFocus_LowerValues () + { + var r = new View (); + var v1 = new View { CanFocus = true }; + var v2 = new View { CanFocus = true }; + var v3 = new View { CanFocus = true }; + + r.Add (v1, v2, v3); + + //v1.TabIndex = -1; + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.TabIndexes.IndexOf (v1) == 0); + r.Dispose (); + } + + [Fact] + public void TabIndex_Set_CanFocus_ValidValues () + { + var r = new View (); + var v1 = new View { CanFocus = true }; + var v2 = new View { CanFocus = true }; + var v3 = new View { CanFocus = true }; + + r.Add (v1, v2, v3); + + v1.TabIndex = 1; + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.TabIndexes.IndexOf (v1) == 1); + + v1.TabIndex = 2; + Assert.True (r.Subviews.IndexOf (v1) == 0); + Assert.True (r.TabIndexes.IndexOf (v1) == 2); + r.Dispose (); + } + + + [Theory] + [CombinatorialData] + public void TabStop_And_CanFocus_Are_Decoupled (bool canFocus, TabBehavior tabStop) + { + var view = new View { CanFocus = canFocus, TabStop = tabStop }; + + Assert.Equal (canFocus, view.CanFocus); + Assert.Equal (tabStop, view.TabStop); + } + + [Fact] + public void AdvanceFocus_With_CanFocus_Are_All_True () + { + var top = new View () { Id = "top", CanFocus = true }; + var v1 = new View { Id = "v1", CanFocus = true }; + var v2 = new View { Id = "v2", CanFocus = true }; + var v3 = new View { Id = "v3", CanFocus = true }; + + top.Add (v1, v2, v3); + + top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.True (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.True (v2.HasFocus); + Assert.False (v3.HasFocus); + top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.True (v3.HasFocus); + top.Dispose (); + } + + [Fact] + public void AdvanceFocus_CanFocus_Mixed () + { + var r = new View (); + var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + var v2 = new View { CanFocus = false, TabStop = TabBehavior.TabStop }; + var v3 = new View { CanFocus = false, TabStop = TabBehavior.NoStop }; + + r.Add (v1, v2, v3); + + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.Dispose (); + } + + [Theory] + [CombinatorialData] + public void AdvanceFocus_Change_CanFocus_Works ([CombinatorialValues (TabBehavior.NoStop, TabBehavior.TabStop, TabBehavior.TabGroup)] TabBehavior behavior) + { + var r = new View () { CanFocus = true }; + var v1 = new View (); + var v2 = new View (); + var v3 = new View (); + Assert.True (r.CanFocus); + Assert.False (v1.CanFocus); + Assert.False (v2.CanFocus); + Assert.False (v3.CanFocus); + + r.Add (v1, v2, v3); + + r.AdvanceFocus (NavigationDirection.Forward, behavior); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + + v1.CanFocus = true; + v1.TabStop = behavior; + r.AdvanceFocus (NavigationDirection.Forward, behavior); + Assert.True (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + + v2.CanFocus = true; + v2.TabStop = behavior; + r.AdvanceFocus (NavigationDirection.Forward, behavior); + Assert.False (v1.HasFocus); + Assert.True (v2.HasFocus); + Assert.False (v3.HasFocus); + + v3.CanFocus = true; + v3.TabStop = behavior; + r.AdvanceFocus (NavigationDirection.Forward, behavior); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.True (v3.HasFocus); + r.Dispose (); + } + + [Fact] + public void AdvanceFocus_NoStop_And_CanFocus_True_No_Focus () + { + var r = new View (); + var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + + r.Add (v1, v2, v3); + + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.Dispose (); + } + + [Fact] + public void AdvanceFocus_NoStop_Change_Enables_Stop () + { + var r = new View { CanFocus = true }; + var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + + r.Add (v1, v2, v3); + + v1.TabStop = TabBehavior.TabStop; + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.True (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + + v2.TabStop = TabBehavior.TabStop; + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.True (v2.HasFocus); + Assert.False (v3.HasFocus); + + v3.TabStop = TabBehavior.TabStop; + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.True (v3.HasFocus); + r.Dispose (); + } + + [Fact] + public void AdvacneFocus_NoStop_Prevents_Stop () + { + var r = new View (); + var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; + + r.Add (v1, v2, v3); + + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + } + + [Fact] + public void AdvanceFocus_Null_And_CanFocus_False_No_Advance () + { + var r = new View (); + var v1 = new View (); + var v2 = new View (); + var v3 = new View (); + Assert.False (v1.CanFocus); + Assert.Null (v1.TabStop); + + r.Add (v1, v2, v3); + + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + Assert.False (v1.HasFocus); + Assert.False (v2.HasFocus); + Assert.False (v3.HasFocus); + r.Dispose (); + } +} diff --git a/UnitTests/View/Navigation/CanFocusTests.cs b/UnitTests/View/Navigation/CanFocusTests.cs new file mode 100644 index 000000000..04a8e5ca6 --- /dev/null +++ b/UnitTests/View/Navigation/CanFocusTests.cs @@ -0,0 +1,580 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewTests; + +public class CanFocusTests (ITestOutputHelper _output) : TestsAllViews +{ + [Fact] + public void CanFocus_False_Prevents_SubSubView_HasFocus () + { + var view = new View { }; + var subView = new View { }; + var subSubView = new View { CanFocus = true }; + + subView.Add (subSubView); + view.Add (subView); + + Assert.False (view.CanFocus); + Assert.False (subView.CanFocus); + Assert.True (subSubView.CanFocus); + + view.SetFocus (); + Assert.False (view.HasFocus); + + subView.SetFocus (); + Assert.False (subView.HasFocus); + + subSubView.SetFocus (); + Assert.False (subSubView.HasFocus); + } + + [Fact] + public void CanFocus_False_Prevents_SubView_HasFocus () + { + var view = new View { }; + var subView = new View { CanFocus = true }; + var subSubView = new View { }; + + subView.Add (subSubView); + view.Add (subView); + + Assert.False (view.CanFocus); + Assert.True (subView.CanFocus); + Assert.False (subSubView.CanFocus); + + view.SetFocus (); + Assert.False (view.HasFocus); + + subView.SetFocus (); + Assert.False (subView.HasFocus); + + subSubView.SetFocus (); + Assert.False (subSubView.HasFocus); + } + + [Fact] + public void CanFocus_Set_True_No_SuperView_Doesnt_Set_HasFocus () + { + var view = new View { }; + + // Act + view.CanFocus = true; + Assert.False (view.HasFocus); + } + + [Fact] + public void CanFocus_Set_True_Sets_HasFocus_To_True () + { + var view = new View { }; + var subView = new View { }; + view.Add (subView); + + Assert.False (view.CanFocus); + Assert.False (subView.CanFocus); + + view.SetFocus (); + Assert.False (view.HasFocus); + Assert.False (subView.HasFocus); + + view.CanFocus = true; + view.SetFocus (); + Assert.True (view.HasFocus); + + // Act + subView.CanFocus = true; + Assert.True (subView.HasFocus); + } + + [Fact] + public void CanFocus_Set_SubView_True_Sets_HasFocus_To_True () + { + var view = new View + { + CanFocus = true + }; + var subView = new View + { + CanFocus = false + }; + var subSubView = new View + { + CanFocus = true + }; + + subView.Add (subSubView); + view.Add (subView); + + view.SetFocus (); + Assert.True (view.HasFocus); + Assert.False (subView.HasFocus); + Assert.False (subSubView.HasFocus); + + // Act + subView.CanFocus = true; + Assert.True (subView.HasFocus); + Assert.True (subSubView.HasFocus); + } + + + [Fact] + public void CanFocus_Set_SubView_True_Does_Not_Change_Focus_If_SuperView_Focused_Is_True () + { + var top = new View + { + Id = "top", + CanFocus = true + }; + var subView = new View + { + Id = "subView", + CanFocus = true + }; + var subSubView = new View + { + Id = "subSubView", + CanFocus = true + }; + + subView.Add (subSubView); + + var subView2 = new View + { + Id = "subView2", + CanFocus = false + }; + + top.Add (subView, subView2); + + top.SetFocus (); + Assert.True (top.HasFocus); + Assert.Equal (subView, top.GetFocused ()); + Assert.True (subView.HasFocus); + Assert.True (subSubView.HasFocus); + + // Act + subView2.CanFocus = true; + Assert.False (subView2.HasFocus); + Assert.True (subView.HasFocus); + Assert.True (subSubView.HasFocus); + } + + [Fact] + public void CanFocus_Set_False_Sets_HasFocus_To_False () + { + var view = new View { CanFocus = true }; + var view2 = new View { CanFocus = true }; + view2.Add (view); + + Assert.True (view.CanFocus); + + view.SetFocus (); + Assert.True (view.HasFocus); + + view.CanFocus = false; + Assert.False (view.CanFocus); + Assert.False (view.HasFocus); + } + + // TODO: Figure out what this test is supposed to be testing + [Fact] + public void CanFocus_Faced_With_Container () + { + var t = new Toplevel (); + var w = new Window (); + var f = new FrameView (); + var v = new View { CanFocus = true }; + f.Add (v); + w.Add (f); + t.Add (w); + + Assert.True (t.CanFocus); + Assert.True (w.CanFocus); + Assert.True (f.CanFocus); + Assert.True (v.CanFocus); + + f.CanFocus = false; + Assert.False (f.CanFocus); + Assert.True (v.CanFocus); + + v.CanFocus = false; + Assert.False (f.CanFocus); + Assert.False (v.CanFocus); + + v.CanFocus = true; + Assert.False (f.CanFocus); + Assert.True (v.CanFocus); + } + + // TODO: Figure out what this test is supposed to be testing + [Fact] + public void CanFocus_Faced_With_Container_Before_Run () + { + Application.Init (new FakeDriver ()); + + Toplevel t = new (); + + var w = new Window (); + var f = new FrameView (); + var v = new View { CanFocus = true }; + f.Add (v); + w.Add (f); + t.Add (w); + + Assert.True (t.CanFocus); + Assert.True (w.CanFocus); + Assert.True (f.CanFocus); + Assert.True (v.CanFocus); + + f.CanFocus = false; + Assert.False (f.CanFocus); + Assert.True (v.CanFocus); + + v.CanFocus = false; + Assert.False (f.CanFocus); + Assert.False (v.CanFocus); + + v.CanFocus = true; + Assert.False (f.CanFocus); + Assert.True (v.CanFocus); + + Application.Iteration += (s, a) => Application.RequestStop (); + + Application.Run (t); + t.Dispose (); + Application.Shutdown (); + } + + [Fact] + public void CanFocus_Set_Changes_TabIndex_And_TabStop () + { + var r = new View (); + var v1 = new View { Text = "1" }; + var v2 = new View { Text = "2" }; + var v3 = new View { Text = "3" }; + + r.Add (v1, v2, v3); + + v2.CanFocus = true; + Assert.Equal (r.TabIndexes.IndexOf (v2), v2.TabIndex); + Assert.Equal (0, v2.TabIndex); + Assert.Equal (TabBehavior.TabStop, v2.TabStop); + + v1.CanFocus = true; + Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); + Assert.Equal (1, v1.TabIndex); + Assert.Equal (TabBehavior.TabStop, v1.TabStop); + + v1.TabIndex = 2; + Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); + Assert.Equal (1, v1.TabIndex); + v3.CanFocus = true; + Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); + Assert.Equal (1, v1.TabIndex); + Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex); + Assert.Equal (2, v3.TabIndex); + Assert.Equal (TabBehavior.TabStop, v3.TabStop); + + v2.CanFocus = false; + Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); + Assert.Equal (1, v1.TabIndex); + Assert.Equal (TabBehavior.TabStop, v1.TabStop); + Assert.Equal (r.TabIndexes.IndexOf (v2), v2.TabIndex); // TabIndex is not changed + Assert.NotEqual (-1, v2.TabIndex); + Assert.Equal (TabBehavior.TabStop, v2.TabStop); // TabStop is not changed + Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex); + Assert.Equal (2, v3.TabIndex); + Assert.Equal (TabBehavior.TabStop, v3.TabStop); + r.Dispose (); + } + + + +#if V2_NEW_FOCUS_IMPL // Bogus test - depends on auto CanFocus behavior + + [Fact] + public void CanFocus_Container_ToFalse_Turns_All_Subviews_ToFalse_Too () + { + Application.Init (new FakeDriver ()); + + Toplevel t = new (); + + var w = new Window (); + var f = new FrameView (); + var v1 = new View { CanFocus = true }; + var v2 = new View { CanFocus = true }; + f.Add (v1, v2); + w.Add (f); + t.Add (w); + + t.Ready += (s, e) => + { + Assert.True (t.CanFocus); + Assert.True (w.CanFocus); + Assert.True (f.CanFocus); + Assert.True (v1.CanFocus); + Assert.True (v2.CanFocus); + + w.CanFocus = false; + Assert.False (w.CanFocus); + Assert.False (f.CanFocus); + Assert.False (v1.CanFocus); + Assert.False (v2.CanFocus); + }; + + Application.Iteration += (s, a) => Application.RequestStop (); + + Application.Run (t); + t.Dispose (); + Application.Shutdown (); + } +#endif + +#if V2_NEW_FOCUS_IMPL // Bogus test - depends on auto CanFocus behavior + + [Fact] + public void CanFocus_Container_Toggling_All_Subviews_To_Old_Value_When_Is_True () + { + Application.Init (new FakeDriver ()); + + Toplevel t = new (); + + var w = new Window (); + var f = new FrameView (); + var v1 = new View (); + var v2 = new View { CanFocus = true }; + f.Add (v1, v2); + w.Add (f); + t.Add (w); + + t.Ready += (s, e) => + { + Assert.True (t.CanFocus); + Assert.True (w.CanFocus); + Assert.True (f.CanFocus); + Assert.False (v1.CanFocus); + Assert.True (v2.CanFocus); + + w.CanFocus = false; + Assert.False (w.CanFocus); + Assert.False (f.CanFocus); + Assert.False (v1.CanFocus); + Assert.False (v2.CanFocus); + + w.CanFocus = true; + Assert.True (w.CanFocus); + Assert.True (f.CanFocus); + Assert.False (v1.CanFocus); + Assert.True (v2.CanFocus); + }; + + Application.Iteration += (s, a) => Application.RequestStop (); + + Application.Run (t); + t.Dispose (); + Application.Shutdown (); + } +#endif +#if V2_NEW_FOCUS_IMPL // Bogus test - depends on auto CanFocus behavior + [Fact] + public void CanFocus_Faced_With_Container_After_Run () + { + Application.Init (new FakeDriver ()); + + Toplevel t = new (); + + var w = new Window (); + var f = new FrameView (); + var v = new View { CanFocus = true }; + f.Add (v); + w.Add (f); + t.Add (w); + + t.Ready += (s, e) => + { + Assert.True (t.CanFocus); + Assert.True (w.CanFocus); + Assert.True (f.CanFocus); + Assert.True (v.CanFocus); + + f.CanFocus = false; + Assert.False (f.CanFocus); + Assert.False (v.CanFocus); + + v.CanFocus = false; + Assert.False (f.CanFocus); + Assert.False (v.CanFocus); + + Assert.Throws (() => v.CanFocus = true); + Assert.False (f.CanFocus); + Assert.False (v.CanFocus); + + f.CanFocus = true; + Assert.True (f.CanFocus); + Assert.True (v.CanFocus); + }; + + Application.Iteration += (s, a) => Application.RequestStop (); + + Application.Run (t); + t.Dispose (); + Application.Shutdown (); + } +#endif +#if V2_NEW_FOCUS_IMPL + + [Fact] + [AutoInitShutdown] + public void CanFocus_Sets_To_False_On_Single_View_Focus_View_On_Another_Toplevel () + { + var view1 = new View { Id = "view1", Width = 10, Height = 1, CanFocus = true }; + var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; + win1.Add (view1); + var view2 = new View { Id = "view2", Width = 20, Height = 2, CanFocus = true }; + var win2 = new Window { Id = "win2", X = Pos.Right (win1), Width = Dim.Fill (), Height = Dim.Fill () }; + win2.Add (view2); + var top = new Toplevel (); + top.Add (win1, win2); + Application.Begin (top); + + Assert.True (view1.CanFocus); + Assert.True (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus + + Assert.True (Application.OnKeyDown (Key.F6)); + Assert.True (view1.CanFocus); + Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus + Assert.True (view2.CanFocus); + Assert.True (view2.HasFocus); + + Assert.True (Application.OnKeyDown (Key.F6)); + Assert.True (view1.CanFocus); + Assert.True (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus + + view1.CanFocus = false; + Assert.False (view1.CanFocus); + Assert.False (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.True (view2.HasFocus); + Assert.Equal (win2, Application.Current.GetFocused ()); + Assert.Equal (view2, Application.Current.GetMostFocused ()); + top.Dispose (); + } + + [Fact] + [AutoInitShutdown] + public void CanFocus_Sets_To_False_On_Toplevel_Focus_View_On_Another_Toplevel () + { + var view1 = new View { Id = "view1", Width = 10, Height = 1, CanFocus = true }; + var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; + win1.Add (view1); + var view2 = new View { Id = "view2", Width = 20, Height = 2, CanFocus = true }; + var win2 = new Window { Id = "win2", X = Pos.Right (win1), Width = Dim.Fill (), Height = Dim.Fill () }; + win2.Add (view2); + var top = new Toplevel (); + top.Add (win1, win2); + Application.Begin (top); + + Assert.True (view1.CanFocus); + Assert.True (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus + + Assert.True (Application.OnKeyDown (Key.F6)); + Assert.True (view1.CanFocus); + Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus + Assert.True (view2.CanFocus); + Assert.True (view2.HasFocus); + + Assert.True (Application.OnKeyDown (Key.F6)); + Assert.True (view1.CanFocus); + Assert.True (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus + + win1.CanFocus = false; + Assert.False (view1.CanFocus); + Assert.False (view1.HasFocus); + Assert.False (win1.CanFocus); + Assert.False (win1.HasFocus); + Assert.True (view2.CanFocus); + Assert.True (view2.HasFocus); + Assert.Equal (win2, Application.Current.GetFocused ()); + Assert.Equal (view2, Application.Current.GetMostFocused ()); + top.Dispose (); + } + + [Fact] + [AutoInitShutdown] + public void CanFocus_Sets_To_False_With_Two_Views_Focus_Another_View_On_The_Same_Toplevel () + { + var view1 = new View { Id = "view1", Width = 10, Height = 1, CanFocus = true }; + + var view12 = new View + { + Id = "view12", + Y = 5, + Width = 10, + Height = 1, + CanFocus = true + }; + var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; + win1.Add (view1, view12); + var view2 = new View { Id = "view2", Width = 20, Height = 2, CanFocus = true }; + var win2 = new Window { Id = "win2", X = Pos.Right (win1), Width = Dim.Fill (), Height = Dim.Fill () }; + win2.Add (view2); + var top = new Toplevel (); + top.Add (win1, win2); + Application.Begin (top); + + Assert.True (view1.CanFocus); + Assert.True (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus + + Assert.True (Application.OnKeyDown (Key.F6)); // move to win2 + Assert.True (view1.CanFocus); + Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus + Assert.True (view2.CanFocus); + Assert.True (view2.HasFocus); + + Assert.True (Application.OnKeyDown (Key.F6)); + Assert.True (view1.CanFocus); + Assert.True (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus + + view1.CanFocus = false; + Assert.False (view1.CanFocus); + Assert.False (view1.HasFocus); + Assert.True (view2.CanFocus); + Assert.False (view2.HasFocus); + Assert.Equal (win1, Application.Current.GetFocused ()); + Assert.Equal (view12, Application.Current.GetMostFocused ()); + top.Dispose (); + } +#endif + + [Fact (Skip = "Causes crash on Ubuntu in Github Action. Bogus test anyway.")] + public void WindowDispose_CanFocusProblem () + { + // Arrange + Application.Init (); + using var top = new Toplevel (); + using var view = new View { X = 0, Y = 1, Text = nameof (WindowDispose_CanFocusProblem) }; + using var window = new Window (); + top.Add (window); + window.Add (view); + + // Act + RunState rs = Application.Begin (top); + Application.End (rs); + top.Dispose (); + Application.Shutdown (); + + // Assert does Not throw NullReferenceException + top.SetFocus (); + } +} diff --git a/UnitTests/View/Navigation/HasFocusTests.cs b/UnitTests/View/Navigation/HasFocusTests.cs new file mode 100644 index 000000000..fbfe0c3f7 --- /dev/null +++ b/UnitTests/View/Navigation/HasFocusTests.cs @@ -0,0 +1,160 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewTests; + +public class HasFocusTests (ITestOutputHelper _output) : TestsAllViews +{ + + [Fact] + public void HasFocus_False_Leaves () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + view.SetFocus (); + Assert.True (view.HasFocus); + + view.HasFocus = false; + Assert.False (view.HasFocus); + } + + [Fact] + public void HasFocus_False_WithSuperView_Leaves_All () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subview = new View () + { + Id = "subview", + CanFocus = true + }; + view.Add (subview); + + view.SetFocus (); + Assert.True (view.HasFocus); + Assert.True (subview.HasFocus); + + subview.HasFocus = false; + Assert.False (view.HasFocus); + Assert.False (subview.HasFocus); + } + + [Fact] + public void HasFocus_False_WithSubview_Leaves_All () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subview = new View () + { + Id = "subview", + CanFocus = true + }; + view.Add (subview); + + view.SetFocus (); + Assert.True (view.HasFocus); + Assert.True (subview.HasFocus); + Assert.Equal (subview, view.GetFocused ()); + + view.HasFocus = false; + Assert.Null (view.GetFocused ()); + Assert.False (view.HasFocus); + Assert.False (subview.HasFocus); + } + + + [Fact] + public void HasFocus_False_Leave_Invoked () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + Assert.True (view.CanFocus); + Assert.False (view.HasFocus); + + int leaveInvoked = 0; + + view.Leave += (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_Invoked_ForAllSubViews () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subview = new View () + { + Id = "subview", + CanFocus = true + }; + view.Add (subview); + + int leaveInvoked = 0; + + view.Leave += (s, e) => leaveInvoked++; + subview.Leave += (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); + } + + + [Fact] + public void Enabled_False_Sets_HasFocus_To_False () + { + var wasClicked = false; + var view = new Button { Text = "Click Me" }; + view.Accept += (s, e) => wasClicked = !wasClicked; + + view.NewKeyDownEvent (Key.Space); + Assert.True (wasClicked); + view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked }); + Assert.False (wasClicked); + Assert.True (view.Enabled); + Assert.True (view.CanFocus); + Assert.True (view.HasFocus); + + view.Enabled = false; + view.NewKeyDownEvent (Key.Space); + Assert.False (wasClicked); + view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked }); + Assert.False (wasClicked); + Assert.False (view.Enabled); + Assert.True (view.CanFocus); + Assert.False (view.HasFocus); + view.SetFocus (); + Assert.False (view.HasFocus); + } + +} diff --git a/UnitTests/View/NavigationTests.cs b/UnitTests/View/Navigation/NavigationTests.cs similarity index 51% rename from UnitTests/View/NavigationTests.cs rename to UnitTests/View/Navigation/NavigationTests.cs index df4d393ed..22d238429 100644 --- a/UnitTests/View/NavigationTests.cs +++ b/UnitTests/View/Navigation/NavigationTests.cs @@ -141,13 +141,18 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews view.Leave += (s, e) => nLeave++; top.Add (view, otherView); + Assert.False (view.HasFocus); + Assert.False (otherView.HasFocus); + Application.Begin (top); + Assert.True (Application.Current!.HasFocus); + Assert.True (top.HasFocus); // Start with the focus on our test view - view.SetFocus (); + Assert.True (view.HasFocus); - //Assert.Equal (1, nEnter); - //Assert.Equal (0, nLeave); + Assert.Equal (1, nEnter); + Assert.Equal (0, nLeave); // Use keyboard to navigate to next view (otherView). if (view is TextView) @@ -189,11 +194,11 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews } } - //Assert.Equal (1, nEnter); - //Assert.Equal (1, nLeave); + Assert.Equal (1, nEnter); + Assert.Equal (1, nLeave); - //Assert.False (view.HasFocus); - //Assert.True (otherView.HasFocus); + Assert.False (view.HasFocus); + Assert.True (otherView.HasFocus); // Now navigate back to our test view switch (view.TabStop) @@ -218,6 +223,12 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews throw new ArgumentOutOfRangeException (); } + Assert.Equal (2, nEnter); + Assert.Equal (1, nLeave); + + Assert.True (view.HasFocus); + Assert.False (otherView.HasFocus); + // Cache state because Shutdown has side effects. // Also ensures other tests can continue running if there's a fail bool otherViewHasFocus = otherView.HasFocus; @@ -371,421 +382,6 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews r.Dispose (); } - [Fact] - public void CanFocus_Container_ToFalse_Turns_All_Subviews_ToFalse_Too () - { - Application.Init (new FakeDriver ()); - - Toplevel t = new (); - - var w = new Window (); - var f = new FrameView (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - f.Add (v1, v2); - w.Add (f); - t.Add (w); - - t.Ready += (s, e) => - { - Assert.True (t.CanFocus); - Assert.True (w.CanFocus); - Assert.True (f.CanFocus); - Assert.True (v1.CanFocus); - Assert.True (v2.CanFocus); - - w.CanFocus = false; - Assert.False (w.CanFocus); - Assert.False (f.CanFocus); - Assert.False (v1.CanFocus); - Assert.False (v2.CanFocus); - }; - - Application.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (t); - t.Dispose (); - Application.Shutdown (); - } - - [Fact] - public void CanFocus_Container_Toggling_All_Subviews_To_Old_Value_When_Is_True () - { - Application.Init (new FakeDriver ()); - - Toplevel t = new (); - - var w = new Window (); - var f = new FrameView (); - var v1 = new View (); - var v2 = new View { CanFocus = true }; - f.Add (v1, v2); - w.Add (f); - t.Add (w); - - t.Ready += (s, e) => - { - Assert.True (t.CanFocus); - Assert.True (w.CanFocus); - Assert.True (f.CanFocus); - Assert.False (v1.CanFocus); - Assert.True (v2.CanFocus); - - w.CanFocus = false; - Assert.False (w.CanFocus); - Assert.False (f.CanFocus); - Assert.False (v1.CanFocus); - Assert.False (v2.CanFocus); - - w.CanFocus = true; - Assert.True (w.CanFocus); - Assert.True (f.CanFocus); - Assert.False (v1.CanFocus); - Assert.True (v2.CanFocus); - }; - - Application.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (t); - t.Dispose (); - Application.Shutdown (); - } - - [Fact] - [AutoInitShutdown] - public void CanFocus_Faced_With_Container () - { - var t = new Toplevel (); - var w = new Window (); - var f = new FrameView (); - var v = new View { CanFocus = true }; - f.Add (v); - w.Add (f); - t.Add (w); - - Assert.True (t.CanFocus); - Assert.True (w.CanFocus); - Assert.True (f.CanFocus); - Assert.True (v.CanFocus); - - f.CanFocus = false; - Assert.False (f.CanFocus); - Assert.True (v.CanFocus); - - v.CanFocus = false; - Assert.False (f.CanFocus); - Assert.False (v.CanFocus); - - v.CanFocus = true; - Assert.False (f.CanFocus); - Assert.True (v.CanFocus); - t.Dispose (); - } - - [Fact] - public void CanFocus_Faced_With_Container_After_Run () - { - Application.Init (new FakeDriver ()); - - Toplevel t = new (); - - var w = new Window (); - var f = new FrameView (); - var v = new View { CanFocus = true }; - f.Add (v); - w.Add (f); - t.Add (w); - - t.Ready += (s, e) => - { - Assert.True (t.CanFocus); - Assert.True (w.CanFocus); - Assert.True (f.CanFocus); - Assert.True (v.CanFocus); - - f.CanFocus = false; - Assert.False (f.CanFocus); - Assert.False (v.CanFocus); - - v.CanFocus = false; - Assert.False (f.CanFocus); - Assert.False (v.CanFocus); - - Assert.Throws (() => v.CanFocus = true); - Assert.False (f.CanFocus); - Assert.False (v.CanFocus); - - f.CanFocus = true; - Assert.True (f.CanFocus); - Assert.True (v.CanFocus); - }; - - Application.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (t); - t.Dispose (); - Application.Shutdown (); - } - - [Fact] - public void CanFocus_Faced_With_Container_Before_Run () - { - Application.Init (new FakeDriver ()); - - Toplevel t = new (); - - var w = new Window (); - var f = new FrameView (); - var v = new View { CanFocus = true }; - f.Add (v); - w.Add (f); - t.Add (w); - - Assert.True (t.CanFocus); - Assert.True (w.CanFocus); - Assert.True (f.CanFocus); - Assert.True (v.CanFocus); - - f.CanFocus = false; - Assert.False (f.CanFocus); - Assert.True (v.CanFocus); - - v.CanFocus = false; - Assert.False (f.CanFocus); - Assert.False (v.CanFocus); - - v.CanFocus = true; - Assert.False (f.CanFocus); - Assert.True (v.CanFocus); - - Application.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (t); - t.Dispose (); - Application.Shutdown (); - } - - [Fact] - public void CanFocus_False_Set_HasFocus_To_False () - { - var view = new View { CanFocus = true }; - var view2 = new View { CanFocus = true }; - view2.Add (view); - - Assert.True (view.CanFocus); - - view.SetFocus (); - Assert.True (view.HasFocus); - - view.CanFocus = false; - Assert.False (view.CanFocus); - Assert.False (view.HasFocus); - } - - [Fact] - public void CanFocus_Set_Changes_TabIndex_And_TabStop () - { - var r = new View (); - var v1 = new View { Text = "1" }; - var v2 = new View { Text = "2" }; - var v3 = new View { Text = "3" }; - - r.Add (v1, v2, v3); - - v2.CanFocus = true; - Assert.Equal (r.TabIndexes.IndexOf (v2), v2.TabIndex); - Assert.Equal (0, v2.TabIndex); - Assert.Equal (TabBehavior.TabStop, v2.TabStop); - - v1.CanFocus = true; - Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); - Assert.Equal (1, v1.TabIndex); - Assert.Equal (TabBehavior.TabStop, v1.TabStop); - - v1.TabIndex = 2; - Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); - Assert.Equal (1, v1.TabIndex); - v3.CanFocus = true; - Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); - Assert.Equal (1, v1.TabIndex); - Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex); - Assert.Equal (2, v3.TabIndex); - Assert.Equal (TabBehavior.TabStop, v3.TabStop); - - v2.CanFocus = false; - Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex); - Assert.Equal (1, v1.TabIndex); - Assert.Equal (TabBehavior.TabStop, v1.TabStop); - Assert.Equal (r.TabIndexes.IndexOf (v2), v2.TabIndex); // TabIndex is not changed - Assert.NotEqual (-1, v2.TabIndex); - Assert.Equal (TabBehavior.TabStop, v2.TabStop); // TabStop is not changed - Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex); - Assert.Equal (2, v3.TabIndex); - Assert.Equal (TabBehavior.TabStop, v3.TabStop); - r.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void CanFocus_Sets_To_False_On_Single_View_Focus_View_On_Another_Toplevel () - { - var view1 = new View { Id = "view1", Width = 10, Height = 1, CanFocus = true }; - var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; - win1.Add (view1); - var view2 = new View { Id = "view2", Width = 20, Height = 2, CanFocus = true }; - var win2 = new Window { Id = "win2", X = Pos.Right (win1), Width = Dim.Fill (), Height = Dim.Fill () }; - win2.Add (view2); - var top = new Toplevel (); - top.Add (win1, win2); - Application.Begin (top); - - Assert.True (view1.CanFocus); - Assert.True (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - - Assert.True (Application.OnKeyDown (Key.F6)); - Assert.True (view1.CanFocus); - Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus - Assert.True (view2.CanFocus); - Assert.True (view2.HasFocus); - - Assert.True (Application.OnKeyDown (Key.F6)); - Assert.True (view1.CanFocus); - Assert.True (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - - view1.CanFocus = false; - Assert.False (view1.CanFocus); - Assert.False (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.True (view2.HasFocus); - Assert.Equal (win2, Application.Current.Focused); - Assert.Equal (view2, Application.Current.MostFocused); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void CanFocus_Sets_To_False_On_Toplevel_Focus_View_On_Another_Toplevel () - { - var view1 = new View { Id = "view1", Width = 10, Height = 1, CanFocus = true }; - var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; - win1.Add (view1); - var view2 = new View { Id = "view2", Width = 20, Height = 2, CanFocus = true }; - var win2 = new Window { Id = "win2", X = Pos.Right (win1), Width = Dim.Fill (), Height = Dim.Fill () }; - win2.Add (view2); - var top = new Toplevel (); - top.Add (win1, win2); - Application.Begin (top); - - Assert.True (view1.CanFocus); - Assert.True (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - - Assert.True (Application.OnKeyDown (Key.F6)); - Assert.True (view1.CanFocus); - Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus - Assert.True (view2.CanFocus); - Assert.True (view2.HasFocus); - - Assert.True (Application.OnKeyDown (Key.F6)); - Assert.True (view1.CanFocus); - Assert.True (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - - win1.CanFocus = false; - Assert.False (view1.CanFocus); - Assert.False (view1.HasFocus); - Assert.False (win1.CanFocus); - Assert.False (win1.HasFocus); - Assert.True (view2.CanFocus); - Assert.True (view2.HasFocus); - Assert.Equal (win2, Application.Current.Focused); - Assert.Equal (view2, Application.Current.MostFocused); - top.Dispose (); - } - - [Fact] - [AutoInitShutdown] - public void CanFocus_Sets_To_False_With_Two_Views_Focus_Another_View_On_The_Same_Toplevel () - { - var view1 = new View { Id = "view1", Width = 10, Height = 1, CanFocus = true }; - - var view12 = new View - { - Id = "view12", - Y = 5, - Width = 10, - Height = 1, - CanFocus = true - }; - var win1 = new Window { Id = "win1", Width = Dim.Percent (50), Height = Dim.Fill () }; - win1.Add (view1, view12); - var view2 = new View { Id = "view2", Width = 20, Height = 2, CanFocus = true }; - var win2 = new Window { Id = "win2", X = Pos.Right (win1), Width = Dim.Fill (), Height = Dim.Fill () }; - win2.Add (view2); - var top = new Toplevel (); - top.Add (win1, win2); - Application.Begin (top); - - Assert.True (view1.CanFocus); - Assert.True (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - - Assert.True (Application.OnKeyDown (Key.F6)); // move to win2 - Assert.True (view1.CanFocus); - Assert.False (view1.HasFocus); // Only one of the most focused toplevels view can have focus - Assert.True (view2.CanFocus); - Assert.True (view2.HasFocus); - - Assert.True (Application.OnKeyDown (Key.F6)); - Assert.True (view1.CanFocus); - Assert.True (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.False (view2.HasFocus); // Only one of the most focused toplevels view can have focus - - view1.CanFocus = false; - Assert.False (view1.CanFocus); - Assert.False (view1.HasFocus); - Assert.True (view2.CanFocus); - Assert.False (view2.HasFocus); - Assert.Equal (win1, Application.Current.Focused); - Assert.Equal (view12, Application.Current.MostFocused); - top.Dispose (); - } - - [Fact] - public void Enabled_False_Sets_HasFocus_To_False () - { - var wasClicked = false; - var view = new Button { Text = "Click Me" }; - view.Accept += (s, e) => wasClicked = !wasClicked; - - view.NewKeyDownEvent (Key.Space); - Assert.True (wasClicked); - view.NewMouseEvent (new() { Flags = MouseFlags.Button1Clicked }); - Assert.False (wasClicked); - Assert.True (view.Enabled); - Assert.True (view.CanFocus); - Assert.True (view.HasFocus); - - view.Enabled = false; - view.NewKeyDownEvent (Key.Space); - Assert.False (wasClicked); - view.NewMouseEvent (new() { Flags = MouseFlags.Button1Clicked }); - Assert.False (wasClicked); - Assert.False (view.Enabled); - Assert.True (view.CanFocus); - Assert.False (view.HasFocus); - view.SetFocus (); - Assert.False (view.HasFocus); - } - [Fact] [AutoInitShutdown] public void Enabled_Sets_Also_Sets_Subviews () @@ -807,7 +403,7 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews win.NewKeyDownEvent (Key.Enter); Assert.True (wasClicked); - button.NewMouseEvent (new() { Flags = MouseFlags.Button1Clicked }); + button.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked }); Assert.False (wasClicked); Assert.True (button.Enabled); Assert.True (button.CanFocus); @@ -819,7 +415,7 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews win.Enabled = false; button.NewKeyDownEvent (Key.Enter); Assert.False (wasClicked); - button.NewMouseEvent (new() { Flags = MouseFlags.Button1Clicked }); + button.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked }); Assert.False (wasClicked); Assert.False (button.Enabled); Assert.True (button.CanFocus); @@ -856,12 +452,12 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews public void Focused_NoSubviews () { var view = new View (); - Assert.Null (view.Focused); + Assert.Null (view.GetFocused ()); view.CanFocus = true; view.SetFocus (); Assert.True (view.HasFocus); - Assert.Null (view.Focused); // BUGBUG: Should be view + Assert.Null (view.GetFocused ()); // BUGBUG: Should be view } [Fact] @@ -881,25 +477,25 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews top.Add (frm); Application.Begin (top); - Assert.Equal ("WindowSubview", top.MostFocused.Text); + Assert.Equal ("WindowSubview", top.GetMostFocused ().Text); Application.OnKeyDown (Key.Tab); - Assert.Equal ("WindowSubview", top.MostFocused.Text); + Assert.Equal ("WindowSubview", top.GetMostFocused ().Text); Application.OnKeyDown (Key.F6); - Assert.Equal ("FrameSubview", top.MostFocused.Text); + Assert.Equal ("FrameSubview", top.GetMostFocused ().Text); Application.OnKeyDown (Key.Tab); - Assert.Equal ("FrameSubview", top.MostFocused.Text); + Assert.Equal ("FrameSubview", top.GetMostFocused ().Text); Application.OnKeyDown (Key.F6); - Assert.Equal ("WindowSubview", top.MostFocused.Text); + Assert.Equal ("WindowSubview", top.GetMostFocused ().Text); Application.OnKeyDown (Key.F6.WithShift); - Assert.Equal ("FrameSubview", top.MostFocused.Text); + Assert.Equal ("FrameSubview", top.GetMostFocused ().Text); Application.OnKeyDown (Key.F6.WithShift); - Assert.Equal ("WindowSubview", top.MostFocused.Text); + Assert.Equal ("WindowSubview", top.GetMostFocused ().Text); top.Dispose (); } @@ -927,7 +523,7 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews if (!removed) { removed = true; - view3 = new() { Id = "view3", Y = 1, Width = 10, Height = 5 }; + view3 = new () { Id = "view3", Y = 1, Width = 10, Height = 5 }; Application.Current.Add (view3); Application.Current.BringSubviewToFront (view3); Assert.False (view3.HasFocus); @@ -964,18 +560,49 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews top1.Dispose (); } - // View.MostFocused - No subviews [Fact] - [Trait ("BUGBUG", "Fix in Issue #3444")] - public void Most_Focused_NoSubviews () + public void GetMostFocused_NoSubviews_Returns_Null () { var view = new View (); - Assert.Null (view.Focused); + Assert.Null (view.GetFocused ()); view.CanFocus = true; + Assert.False (view.HasFocus); view.SetFocus (); Assert.True (view.HasFocus); - Assert.Null (view.MostFocused); // BUGBUG: Should be view + Assert.Null (view.GetMostFocused ()); + } + + [Fact] + public void GetMostFocused_Returns_Most () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subview = new View () + { + Id = "subview", + CanFocus = true + }; + + view.Add (subview); + + view.SetFocus (); + Assert.True (view.HasFocus); + Assert.True (subview.HasFocus); + Assert.Equal (subview, view.GetMostFocused ()); + + var subview2 = new View () + { + Id = "subview2", + CanFocus = true + }; + + view.Add (subview2); + Assert.Equal (subview2, view.GetMostFocused ()); } // [Fact] @@ -1115,7 +742,7 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews Application.Init (new FakeDriver ()); var top = new Toplevel (); - top.Ready += (s, e) => { Assert.Null (top.Focused); }; + top.Ready += (s, e) => { Assert.Null (top.GetFocused ()); }; // Keyboard navigation with tab FakeConsole.MockKeyPresses.Push (new ('\t', ConsoleKey.Tab, false, false, false)); @@ -1127,6 +754,7 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews Application.Shutdown (); } +#if V2_NEW_FOCUS_IMPL // bogus test - Depends on auto setting of CanFocus [Fact] [AutoInitShutdown] public void Remove_Does_Not_Change_Focus () @@ -1167,6 +795,7 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews Assert.False (leave); top.Dispose (); } +#endif [Fact] [AutoInitShutdown] @@ -1454,508 +1083,4 @@ public class NavigationTests (ITestOutputHelper _output) : TestsAllViews Assert.Null (View.FindDeepestView (top, new (24, 4))); top.Dispose (); } - - [Fact] - public void SendSubviewBackwards_Subviews_vs_TabIndexes () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - r.SendSubviewBackwards (v3); - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.Subviews.IndexOf (v2) == 2); - Assert.True (r.Subviews.IndexOf (v3) == 1); - - Assert.True (r.TabIndexes.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v2) == 1); - Assert.True (r.TabIndexes.IndexOf (v3) == 2); - r.Dispose (); - } - - [Fact] - public void SendSubviewToBack_Subviews_vs_TabIndexes () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - r.SendSubviewToBack (v3); - Assert.True (r.Subviews.IndexOf (v1) == 1); - Assert.True (r.Subviews.IndexOf (v2) == 2); - Assert.True (r.Subviews.IndexOf (v3) == 0); - - Assert.True (r.TabIndexes.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v2) == 1); - Assert.True (r.TabIndexes.IndexOf (v3) == 2); - r.Dispose (); - } - - [Fact] - public void SetFocus_With_Null_Superview_Does_Not_Throw_Exception () - { - var view = new View () - { - CanFocus = true - }; - Assert.True (view.CanFocus); - Assert.False (view.HasFocus); - - Exception exception = Record.Exception (() => view.SetFocus()); - Assert.Null (exception); - - Assert.True (view.CanFocus); - Assert.True (view.HasFocus); - } - - [Fact] - [AutoInitShutdown] - public void SetHasFocus_Do_Not_Throws_If_OnLeave_Remove_Focused_Changing_To_Null () - { - var view1Leave = false; - var subView1Leave = false; - var subView1subView1Leave = false; - Toplevel top = new (); - var view1 = new View { CanFocus = true }; - var subView1 = new View { CanFocus = true }; - var subView1subView1 = new View { CanFocus = true }; - view1.Leave += (s, e) => { view1Leave = true; }; - - subView1.Leave += (s, e) => - { - subView1.Remove (subView1subView1); - subView1Leave = true; - }; - view1.Add (subView1); - - subView1subView1.Leave += (s, e) => - { - // This is never invoked - subView1subView1Leave = true; - }; - subView1.Add (subView1subView1); - var view2 = new View { CanFocus = true }; - top.Add (view1, view2); - RunState rs = Application.Begin (top); - - view2.SetFocus (); - Assert.True (view1Leave); - Assert.True (subView1Leave); - Assert.False (subView1subView1Leave); - Application.End (rs); - subView1subView1.Dispose (); - top.Dispose (); - } - - [Fact] - public void Subviews_TabIndexes_AreEqual () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.Subviews.IndexOf (v2) == 1); - Assert.True (r.Subviews.IndexOf (v3) == 2); - - Assert.True (r.TabIndexes.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v2) == 1); - Assert.True (r.TabIndexes.IndexOf (v3) == 2); - - Assert.Equal (r.Subviews.IndexOf (v1), r.TabIndexes.IndexOf (v1)); - Assert.Equal (r.Subviews.IndexOf (v2), r.TabIndexes.IndexOf (v2)); - Assert.Equal (r.Subviews.IndexOf (v3), r.TabIndexes.IndexOf (v3)); - r.Dispose (); - } - - [Fact] - public void TabIndex_Invert_Order () - { - var r = new View (); - var v1 = new View { Id = "1", CanFocus = true }; - var v2 = new View { Id = "2", CanFocus = true }; - var v3 = new View { Id = "3", CanFocus = true }; - - r.Add (v1, v2, v3); - - v1.TabIndex = 2; - v2.TabIndex = 1; - v3.TabIndex = 0; - Assert.True (r.TabIndexes.IndexOf (v1) == 2); - Assert.True (r.TabIndexes.IndexOf (v2) == 1); - Assert.True (r.TabIndexes.IndexOf (v3) == 0); - - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.Subviews.IndexOf (v2) == 1); - Assert.True (r.Subviews.IndexOf (v3) == 2); - } - - [Fact] - public void TabIndex_Invert_Order_Added_One_By_One_Does_Not_Do_What_Is_Expected () - { - var r = new View (); - var v1 = new View { Id = "1", CanFocus = true }; - r.Add (v1); - v1.TabIndex = 2; - var v2 = new View { Id = "2", CanFocus = true }; - r.Add (v2); - v2.TabIndex = 1; - var v3 = new View { Id = "3", CanFocus = true }; - r.Add (v3); - v3.TabIndex = 0; - - Assert.False (r.TabIndexes.IndexOf (v1) == 2); - Assert.True (r.TabIndexes.IndexOf (v1) == 1); - Assert.False (r.TabIndexes.IndexOf (v2) == 1); - Assert.True (r.TabIndexes.IndexOf (v2) == 2); - - // Only the last is in the expected index - Assert.True (r.TabIndexes.IndexOf (v3) == 0); - - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.Subviews.IndexOf (v2) == 1); - Assert.True (r.Subviews.IndexOf (v3) == 2); - } - - [Fact] - public void TabIndex_Invert_Order_Mixed () - { - var r = new View (); - var vl1 = new View { Id = "vl1" }; - var v1 = new View { Id = "v1", CanFocus = true }; - var vl2 = new View { Id = "vl2" }; - var v2 = new View { Id = "v2", CanFocus = true }; - var vl3 = new View { Id = "vl3" }; - var v3 = new View { Id = "v3", CanFocus = true }; - - r.Add (vl1, v1, vl2, v2, vl3, v3); - - v1.TabIndex = 2; - v2.TabIndex = 1; - v3.TabIndex = 0; - Assert.True (r.TabIndexes.IndexOf (v1) == 4); - Assert.True (r.TabIndexes.IndexOf (v2) == 2); - Assert.True (r.TabIndexes.IndexOf (v3) == 0); - - Assert.True (r.Subviews.IndexOf (v1) == 1); - Assert.True (r.Subviews.IndexOf (v2) == 3); - Assert.True (r.Subviews.IndexOf (v3) == 5); - } - - [Fact] - public void TabIndex_Set_CanFocus_False () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - v1.CanFocus = false; - v1.TabIndex = 0; - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v1) == 0); - Assert.NotEqual (-1, v1.TabIndex); - r.Dispose (); - } - - [Fact] - public void TabIndex_Set_CanFocus_False_To_True () - { - var r = new View (); - var v1 = new View (); - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - v1.CanFocus = true; - v1.TabIndex = 1; - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v1) == 1); - r.Dispose (); - } - - [Fact] - public void TabIndex_Set_CanFocus_HigherValues () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - v1.TabIndex = 3; - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v1) == 2); - r.Dispose (); - } - - [Fact] - public void TabIndex_Set_CanFocus_LowerValues () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - //v1.TabIndex = -1; - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v1) == 0); - r.Dispose (); - } - - [Fact] - public void TabIndex_Set_CanFocus_ValidValues () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - v1.TabIndex = 1; - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v1) == 1); - - v1.TabIndex = 2; - Assert.True (r.Subviews.IndexOf (v1) == 0); - Assert.True (r.TabIndexes.IndexOf (v1) == 2); - r.Dispose (); - } - - [Fact] - public void TabStop_And_CanFocus_Are_All_True () - { - var r = new View (); - var v1 = new View { CanFocus = true }; - var v2 = new View { CanFocus = true }; - var v3 = new View { CanFocus = true }; - - r.Add (v1, v2, v3); - - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.True (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.True (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.True (v3.HasFocus); - r.Dispose (); - } - - [Theory] - [CombinatorialData] - public void TabStop_And_CanFocus_Are_Decoupled (bool canFocus, TabBehavior tabStop) - { - var view = new View { CanFocus = canFocus, TabStop = tabStop }; - - Assert.Equal (canFocus, view.CanFocus); - Assert.Equal (tabStop, view.TabStop); - } - - [Fact] - public void TabStop_And_CanFocus_Mixed () - { - var r = new View (); - var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - var v2 = new View { CanFocus = false, TabStop = TabBehavior.TabStop }; - var v3 = new View { CanFocus = false, TabStop = TabBehavior.NoStop }; - - r.Add (v1, v2, v3); - - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.Dispose (); - } - - [Theory] - [CombinatorialData] - public void TabStop_Change_CanFocus_Works ([CombinatorialValues (TabBehavior.NoStop, TabBehavior.TabStop, TabBehavior.TabGroup)] TabBehavior behavior) - { - var r = new View (); - var v1 = new View (); - var v2 = new View (); - var v3 = new View (); - Assert.False (v1.CanFocus); - Assert.False (v2.CanFocus); - Assert.False (v3.CanFocus); - - r.Add (v1, v2, v3); - - r.AdvanceFocus (NavigationDirection.Forward, behavior); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - - v1.CanFocus = true; - v1.TabStop = behavior; - r.AdvanceFocus (NavigationDirection.Forward, behavior); - Assert.True (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - - v2.CanFocus = true; - v2.TabStop = behavior; - r.AdvanceFocus (NavigationDirection.Forward, behavior); - Assert.False (v1.HasFocus); - Assert.True (v2.HasFocus); - Assert.False (v3.HasFocus); - - v3.CanFocus = true; - v3.TabStop = behavior; - r.AdvanceFocus (NavigationDirection.Forward, behavior); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.True (v3.HasFocus); - r.Dispose (); - } - - [Fact] - public void TabStop_NoStop_And_CanFocus_True_No_Focus () - { - var r = new View (); - var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - - r.Add (v1, v2, v3); - - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.Dispose (); - } - - [Fact] - public void TabStop_NoStop_Change_Enables_Stop () - { - var r = new View (); - var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - - r.Add (v1, v2, v3); - - v1.TabStop = TabBehavior.TabStop; - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.True (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - - v2.TabStop = TabBehavior.TabStop; - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.True (v2.HasFocus); - Assert.False (v3.HasFocus); - - v3.TabStop = TabBehavior.TabStop; - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.True (v3.HasFocus); - r.Dispose (); - } - - [Fact] - public void TabStop_NoStop_Prevents_Stop () - { - var r = new View (); - var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop }; - - r.Add (v1, v2, v3); - - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - } - - [Fact] - public void TabStop_Null_And_CanFocus_False_No_Advance () - { - var r = new View (); - var v1 = new View (); - var v2 = new View (); - var v3 = new View (); - Assert.False (v1.CanFocus); - Assert.Null (v1.TabStop); - - r.Add (v1, v2, v3); - - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); - Assert.False (v1.HasFocus); - Assert.False (v2.HasFocus); - Assert.False (v3.HasFocus); - r.Dispose (); - } - - [Fact (Skip = "Causes crash on Ubuntu in Github Action. Bogus test anyway.")] - public void WindowDispose_CanFocusProblem () - { - // Arrange - Application.Init (); - using var top = new Toplevel (); - using var view = new View { X = 0, Y = 1, Text = nameof (WindowDispose_CanFocusProblem) }; - using var window = new Window (); - top.Add (window); - window.Add (view); - - // Act - RunState rs = Application.Begin (top); - Application.End (rs); - top.Dispose (); - Application.Shutdown (); - - // Assert does Not throw NullReferenceException - top.SetFocus (); - } } diff --git a/UnitTests/View/Navigation/SetFocusTests.cs b/UnitTests/View/Navigation/SetFocusTests.cs new file mode 100644 index 000000000..d02c2b243 --- /dev/null +++ b/UnitTests/View/Navigation/SetFocusTests.cs @@ -0,0 +1,226 @@ +using Xunit.Abstractions; + +namespace Terminal.Gui.ViewTests; + +public class SetFocusTests (ITestOutputHelper _output) : TestsAllViews +{ + + [Fact] + public void SetFocus_With_Null_Superview_Does_Not_Throw_Exception () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + Assert.True (view.CanFocus); + Assert.False (view.HasFocus); + + Exception exception = Record.Exception (() => view.SetFocus ()); + Assert.Null (exception); + + Assert.True (view.CanFocus); + Assert.True (view.HasFocus); + } + + [Fact] + public void SetFocus_SetsFocus () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + Assert.True (view.CanFocus); + Assert.False (view.HasFocus); + + view.SetFocus (); + Assert.True (view.HasFocus); + } + + [Fact] + public void SetFocus_NoSubView_Focused_Is_Null () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + Assert.True (view.CanFocus); + Assert.False (view.HasFocus); + + view.SetFocus (); + Assert.True (view.HasFocus); + Assert.Null (view.GetFocused ()); + } + + [Fact] + public void SetFocus_SubView_Focused_Is_Set () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subview = new View () + { + Id = "subview", + CanFocus = true + }; + view.Add (subview); + Assert.True (view.CanFocus); + Assert.False (view.HasFocus); + + view.SetFocus (); + Assert.True (view.HasFocus); + Assert.Equal (subview, view.GetFocused ()); + } + + [Fact] + public void SetFocus_SetsFocus_DeepestSubView () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subview = new View () + { + Id = "subview", + CanFocus = true + }; + view.Add (subview); + + view.SetFocus (); + Assert.True (subview.HasFocus); + Assert.Equal (subview, view.GetFocused ()); + } + + [Fact] + public void SetFocus_SetsFocus_DeepestSubView_CompoundSubView () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subView = new View () + { + Id = "subView", + CanFocus = true + }; + + var subViewSubView1 = new View () + { + Id = "subViewSubView1", + CanFocus = false + }; + + var subViewSubView2 = new View () + { + Id = "subViewSubView2", + CanFocus = true + }; + var subViewSubView3 = new View () + { + Id = "subViewSubView3", + CanFocus = false + }; + subView.Add (subViewSubView1, subViewSubView2, subViewSubView3); + + view.Add (subView); + + view.SetFocus (); + Assert.True (subView.HasFocus); + Assert.Equal (subView, view.GetFocused ()); + Assert.Equal (subViewSubView2, subView.GetFocused ()); + } + + [Fact] + public void SetFocus_Peer_LeavesOther () + { + var view = new View () + { + Id = "view", + CanFocus = true + }; + + var subview1 = new View () + { + Id = "subview1", + CanFocus = true + }; + + var subview2 = new View () + { + Id = "subview2", + CanFocus = true + }; + view.Add (subview1, subview2); + + view.SetFocus (); + Assert.Equal (subview1, view.GetFocused ()); + Assert.True (subview1.HasFocus); + Assert.False (subview2.HasFocus); + + subview2.SetFocus (); + Assert.Equal (subview2, view.GetFocused ()); + Assert.True (subview2.HasFocus); + Assert.False (subview1.HasFocus); + } + + [Fact] + public void SetFocus_Peer_LeavesOthers_Subviews () + { + var top = new View + { + Id = "top", + CanFocus = true + }; + var view1 = new View + { + Id = "view1", + CanFocus = true + }; + + var subView1 = new View + { + Id = "subView1", + CanFocus = true + }; + + view1.Add (subView1); + + var subView1SubView1 = new View + { + Id = "subView1subView1", + CanFocus = true + }; + + subView1.Add (subView1SubView1); + + var view2 = new View + { + Id = "view2", + CanFocus = true + }; + + top.Add (view1, view2); + Assert.False (view1.HasFocus); + Assert.False (view2.HasFocus); + + view1.SetFocus (); + Assert.True (view1.HasFocus); + Assert.True (subView1.HasFocus); + Assert.True (subView1SubView1.HasFocus); + Assert.Equal (subView1, view1.GetFocused ()); + Assert.Equal (subView1SubView1, subView1.GetFocused ()); + + view2.SetFocus (); + Assert.False (view1.HasFocus); + Assert.True (view2.HasFocus); + } +} diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index 9aed13c82..29bf48967 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -765,7 +765,7 @@ At 0,0 Assert.False (r.HasFocus); Assert.Equal (new (0, 0, 0, 0), r.Viewport); Assert.Equal (new (0, 0, 0, 0), r.Frame); - Assert.Null (r.Focused); + Assert.Null (r.GetFocused ()); Assert.Null (r.ColorScheme); Assert.Equal (0, r.Width); Assert.Equal (0, r.Height); @@ -777,7 +777,7 @@ At 0,0 Assert.False (r.WantContinuousButtonPressed); Assert.False (r.WantMousePositionReports); Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); + Assert.Null (r.GetMostFocused ()); Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection); r.Dispose (); @@ -789,7 +789,7 @@ At 0,0 Assert.False (r.HasFocus); Assert.Equal (new (0, 0, 0, 0), r.Viewport); Assert.Equal (new (0, 0, 0, 0), r.Frame); - Assert.Null (r.Focused); + Assert.Null (r.GetFocused ()); Assert.Null (r.ColorScheme); Assert.Equal (0, r.Width); Assert.Equal (0, r.Height); @@ -801,7 +801,7 @@ At 0,0 Assert.False (r.WantContinuousButtonPressed); Assert.False (r.WantMousePositionReports); Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); + Assert.Null (r.GetMostFocused ()); Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection); r.Dispose (); @@ -813,7 +813,7 @@ At 0,0 Assert.False (r.HasFocus); Assert.Equal (new (0, 0, 3, 4), r.Viewport); Assert.Equal (new (1, 2, 3, 4), r.Frame); - Assert.Null (r.Focused); + Assert.Null (r.GetFocused ()); Assert.Null (r.ColorScheme); Assert.Equal (3, r.Width); Assert.Equal (4, r.Height); @@ -825,7 +825,7 @@ At 0,0 Assert.False (r.WantContinuousButtonPressed); Assert.False (r.WantMousePositionReports); Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); + Assert.Null (r.GetMostFocused ()); Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection); r.Dispose (); @@ -846,7 +846,7 @@ At 0,0 Assert.False (r.HasFocus); Assert.Equal (new (0, 0, 1, 13), r.Viewport); Assert.Equal (new (0, 0, 1, 13), r.Frame); - Assert.Null (r.Focused); + Assert.Null (r.GetFocused ()); Assert.Null (r.ColorScheme); Assert.False (r.IsCurrentTop); #if DEBUG @@ -858,7 +858,7 @@ At 0,0 Assert.False (r.WantContinuousButtonPressed); Assert.False (r.WantMousePositionReports); Assert.Null (r.SuperView); - Assert.Null (r.MostFocused); + Assert.Null (r.GetMostFocused ()); Assert.Equal (TextDirection.TopBottom_LeftRight, r.TextDirection); r.Dispose (); } diff --git a/UnitTests/Views/AppendAutocompleteTests.cs b/UnitTests/Views/AppendAutocompleteTests.cs index eaabc43a6..daed47adb 100644 --- a/UnitTests/Views/AppendAutocompleteTests.cs +++ b/UnitTests/Views/AppendAutocompleteTests.cs @@ -26,11 +26,11 @@ public class AppendAutocompleteTests (ITestOutputHelper output) Assert.Equal ("f", tf.Text); // Still has focus though - Assert.Same (tf, Application.Top.Focused); + Assert.Same (tf, Application.Top.GetFocused ()); // But can tab away Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); - Assert.NotSame (tf, Application.Top.Focused); + Assert.NotSame (tf, Application.Top.GetFocused ()); Application.Top.Dispose (); } @@ -201,11 +201,11 @@ public class AppendAutocompleteTests (ITestOutputHelper output) Assert.Equal ("fish", tf.Text); // Tab should autcomplete but not move focus - Assert.Same (tf, Application.Top.Focused); + Assert.Same (tf, Application.Top.GetFocused ()); // Second tab should move focus (nothing to autocomplete) Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); - Assert.NotSame (tf, Application.Top.Focused); + Assert.NotSame (tf, Application.Top.GetFocused ()); Application.Top.Dispose (); } @@ -238,7 +238,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output) Application.Begin (top); - Assert.Same (tf, top.Focused); + Assert.Same (tf, top.GetFocused ()); return tf; } diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs index ad747afa4..5c82ca100 100644 --- a/UnitTests/Views/OverlappedTests.cs +++ b/UnitTests/Views/OverlappedTests.cs @@ -1058,7 +1058,7 @@ public class OverlappedTests win2.Add (lblTf1W2, tf1W2, lblTvW2, tvW2, lblTf2W2, tf2W2); win1.Closing += (s, e) => isRunning = false; - Assert.Null (top.Focused); + Assert.Null (top.GetFocused ()); Assert.Equal (top, Application.Current); Assert.True (top.IsCurrentTop); Assert.Equal (top, ApplicationOverlapped.OverlappedTop); @@ -1071,9 +1071,9 @@ public class OverlappedTests Assert.Equal (win1, Application.Current); Assert.True (win1.IsCurrentTop); Assert.True (ApplicationOverlapped.IsOverlapped(win1)); - Assert.Null (top.Focused); - Assert.Null (top.MostFocused); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Null (top.GetFocused ()); + Assert.Null (top.GetMostFocused ()); + Assert.Equal (tf1W1, win1.GetMostFocused ()); Assert.True (ApplicationOverlapped.IsOverlapped(win1)); Assert.Single (ApplicationOverlapped.OverlappedChildren!); @@ -1085,9 +1085,9 @@ public class OverlappedTests Assert.Equal (win2, Application.Current); Assert.True (win2.IsCurrentTop); Assert.True (ApplicationOverlapped.IsOverlapped(win2)); - Assert.Null (top.Focused); - Assert.Null (top.MostFocused); - Assert.Equal (tf1W2, win2.MostFocused); + Assert.Null (top.GetFocused ()); + Assert.Null (top.GetMostFocused ()); + Assert.Equal (tf1W2, win2.GetMostFocused ()); Assert.Equal (2, ApplicationOverlapped.OverlappedChildren!.Count); ApplicationOverlapped.MoveToOverlappedChild (win1); @@ -1109,7 +1109,7 @@ public class OverlappedTests Assert.True (Application.OnKeyDown (Key.F5)); // refresh Assert.True (win1.IsCurrentTop); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal ($"\tFirst line Win1{Environment.NewLine}Second line Win1", tvW1.Text); @@ -1122,22 +1122,22 @@ public class OverlappedTests Assert.True (Application.OnKeyDown (Key.F6.WithShift)); // move back to win1 Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.Tab)); // text view eats tab Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); tvW1.AllowsTab = false; Assert.True (Application.OnKeyDown (Key.Tab)); // text view eats tab Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorRight)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tf1W1, win1.GetMostFocused ()); #if UNIX_KEY_BINDINGS Assert.True (ApplicationOverlapped.OverlappedChildren [0].ProcessKeyDown (new (Key.I.WithCtrl))); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); @@ -1145,50 +1145,50 @@ public class OverlappedTests #endif Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorLeft)); // The view to the left of tvW1 is tf2W1, but tvW1 is still focused and eats cursor keys Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorUp)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.F6)); // Move to win2 Assert.Equal (win2, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf1W2, win2.MostFocused); + Assert.Equal (tf1W2, win2.GetMostFocused ()); tf2W2.SetFocus (); Assert.True (tf2W2.HasFocus); Assert.True (Application.OnKeyDown (Key.F6.WithShift)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Application.NextTabGroupKey)); Assert.Equal (win2, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf2W2, win2.MostFocused); + Assert.Equal (tf2W2, win2.GetMostFocused ()); Assert.True (Application.OnKeyDown (Application.PrevTabGroupKey)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tf1W1, win1.GetMostFocused ()); #if UNIX_KEY_BINDINGS Assert.True (Application.OnKeyDown (new (Key.B.WithCtrl))); #else Assert.True (Application.OnKeyDown (Key.CursorLeft)); #endif Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf1W1, win1.MostFocused); + Assert.Equal (tf1W1, win1.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorDown)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); Assert.Equal (Point.Empty, tvW1.CursorPosition); Assert.True (Application.OnKeyDown (Key.End.WithCtrl)); Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tvW1, win1.MostFocused); + Assert.Equal (tvW1, win1.GetMostFocused ()); Assert.Equal (new (16, 1), tvW1.CursorPosition); // Last position of the text #if UNIX_KEY_BINDINGS Assert.True (Application.OnKeyDown (new (Key.F.WithCtrl))); @@ -1196,7 +1196,7 @@ public class OverlappedTests Assert.True (Application.OnKeyDown (Key.CursorRight)); // should move to next view w/ in Group (tf2W1) #endif Assert.Equal (win1, ApplicationOverlapped.OverlappedChildren [0]); - Assert.Equal (tf2W1, win1.MostFocused); + Assert.Equal (tf2W1, win1.GetMostFocused ()); #if UNIX_KEY_BINDINGS Assert.True (ApplicationOverlapped.OverlappedChildren [0].ProcessKeyDown (new (Key.L.WithCtrl))); @@ -1227,8 +1227,8 @@ public class OverlappedTests Application.Current = current; Assert.True (current.HasFocus); - Assert.Equal (superView.Focused, current); - Assert.Equal (superView.MostFocused, current); + Assert.Equal (superView.GetFocused (), current); + Assert.Equal (superView.GetMostFocused (), current); // Act ApplicationOverlapped.SetFocusToNextViewWithWrap (Application.Current.SuperView.TabIndexes, NavigationDirection.Forward); diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs index f8f255434..630d7ae5c 100644 --- a/UnitTests/Views/TabViewTests.cs +++ b/UnitTests/Views/TabViewTests.cs @@ -393,9 +393,9 @@ public class TabViewTests (ITestOutputHelper output) // Is the selected tab view hosting focused Assert.Equal (tab1, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); - Assert.Equal (tv.SelectedTab.View, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); + Assert.Equal (tv.SelectedTab.View, top.GetFocused ().GetMostFocused ()); // Press the cursor up key to focus the selected tab Application.OnKeyDown (Key.CursorUp); @@ -403,8 +403,8 @@ public class TabViewTests (ITestOutputHelper output) // Is the selected tab focused Assert.Equal (tab1, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); Tab oldChanged = null; Tab newChanged = null; @@ -421,13 +421,13 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab1, oldChanged); Assert.Equal (tab2, newChanged); Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); // Press the cursor down key. Since the selected tab has no focusable views, the focus should move to the next view in the toplevel Application.OnKeyDown (Key.CursorDown); Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (btn, top.MostFocused); + Assert.Equal (btn, top.GetMostFocused ()); // Add a focusable subview to Selected Tab var btnSubView = new View () @@ -441,26 +441,26 @@ public class TabViewTests (ITestOutputHelper output) // Press cursor up. Should focus the subview in the selected tab. Application.OnKeyDown (Key.CursorUp); Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (btnSubView, top.MostFocused); + Assert.Equal (btnSubView, top.GetMostFocused ()); Application.OnKeyDown (Key.CursorUp); - Assert.Equal (tab2, top.MostFocused); + Assert.Equal (tab2, top.GetMostFocused ()); // Press the cursor down key twice. Application.OnKeyDown (Key.CursorDown); Application.OnKeyDown (Key.CursorDown); - Assert.Equal (btn, top.MostFocused); + Assert.Equal (btn, top.GetMostFocused ()); // Press the cursor down key again will focus next view in the toplevel, whic is the TabView Application.OnKeyDown (Key.CursorDown); Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tab1, tv.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tab1, tv.GetMostFocused ()); // Press the cursor down key to focus the selected tab view hosting again Application.OnKeyDown (Key.CursorDown); Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (btnSubView, top.MostFocused); + Assert.Equal (btnSubView, top.GetMostFocused ()); // Press the cursor up key to focus the selected tab Application.OnKeyDown (Key.CursorUp); @@ -468,8 +468,8 @@ public class TabViewTests (ITestOutputHelper output) // Is the selected tab focused Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); // Press the cursor left key to select the previous tab Application.OnKeyDown (Key.CursorLeft); @@ -477,8 +477,8 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab2, oldChanged); Assert.Equal (tab1, newChanged); Assert.Equal (tab1, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); // Press the end key to select the last tab Application.OnKeyDown (Key.End); @@ -486,8 +486,8 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab1, oldChanged); Assert.Equal (tab2, newChanged); Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); // Press the home key to select the first tab Application.OnKeyDown (Key.Home); @@ -495,8 +495,8 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab2, oldChanged); Assert.Equal (tab1, newChanged); Assert.Equal (tab1, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); // Press the page down key to select the next set of tabs Application.OnKeyDown (Key.PageDown); @@ -504,8 +504,8 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab1, oldChanged); Assert.Equal (tab2, newChanged); Assert.Equal (tab2, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); // Press the page up key to select the previous set of tabs Application.OnKeyDown (Key.PageUp); @@ -513,8 +513,8 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab2, oldChanged); Assert.Equal (tab1, newChanged); Assert.Equal (tab1, tv.SelectedTab); - Assert.Equal (tv, top.Focused); - Assert.Equal (tv.MostFocused, top.Focused.MostFocused); + Assert.Equal (tv, top.GetFocused ()); + Assert.Equal (tv.GetMostFocused (), top.GetFocused ().GetMostFocused ()); top.Dispose (); } diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index 59c9cdd20..168a46b21 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -1948,7 +1948,7 @@ Les Miśerables", Application.Begin (top); - Assert.Same (tf, top.Focused); + Assert.Same (tf, top.GetFocused ()); return tf; } diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 4e3f11101..9c630cf19 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -466,8 +466,8 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (new (0, 0, 40, 25), win1.Frame); Assert.Equal (new (41, 0, 40, 25), win2.Frame); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf1W1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tf1W1, top.GetMostFocused ()); Assert.True (isRunning); Assert.True (Application.OnKeyDown (Application.QuitKey)); @@ -477,71 +477,71 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.True (Application.OnKeyDown (Key.F5)); // refresh Assert.True (Application.OnKeyDown (Key.Tab)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tvW1, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.Tab)); Assert.Equal ($"\tFirst line Win1{Environment.NewLine}Second line Win1", tvW1.Text); Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text); - var prevMostFocusedSubview = top.MostFocused; + var prevMostFocusedSubview = top.GetMostFocused (); Assert.True (Application.OnKeyDown (Key.F6)); // move to next TabGroup (win2) - Assert.Equal (win2, top.Focused); + Assert.Equal (win2, top.GetFocused ()); Assert.True (Application.OnKeyDown (Key.F6.WithShift)); // move to prev TabGroup (win1) - Assert.Equal (win1, top.Focused); - Assert.Equal (tf2W1, top.MostFocused); // BUGBUG: Should be prevMostFocusedSubview - We need to cache the last focused view in the TabGroup somehow + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tf2W1, top.GetMostFocused ()); // BUGBUG: Should be prevMostFocusedSubview - We need to cache the last focused view in the TabGroup somehow prevMostFocusedSubview.SetFocus (); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (tvW1, top.GetMostFocused ()); tf2W1.SetFocus (); Assert.True (Application.OnKeyDown (Key.Tab)); // tf2W1 is last subview in win1 - tabbing should take us to first subview of win1 - Assert.Equal (win1, top.Focused); - Assert.Equal (tf1W1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tf1W1, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorRight)); // move char to right in tf1W1. We're at last char so nav to next view - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tvW1, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorDown)); // move down to next view (tvW1) - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tvW1, top.GetMostFocused ()); #if UNIX_KEY_BINDINGS Assert.True (Application.OnKeyDown (new (Key.I.WithCtrl))); - Assert.Equal (win1, top.Focused); + Assert.Equal (win1, top.GetFocused ()); Assert.Equal (tf2W1, top.MostFocused); #endif Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); // Ignored. TextView eats shift-tab by default - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tvW1, top.GetMostFocused ()); tvW1.AllowsTab = false; Assert.True (Application.OnKeyDown (Key.Tab.WithShift)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf1W1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tf1W1, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorLeft)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf2W1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tf2W1, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorUp)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tvW1, top.GetMostFocused ()); // nav to win2 Assert.True (Application.OnKeyDown (Key.F6)); - Assert.Equal (win2, top.Focused); - Assert.Equal (tf1W2, top.MostFocused); + Assert.Equal (win2, top.GetFocused ()); + Assert.Equal (tf1W2, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.F6.WithShift)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf2W1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tf2W1, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Application.NextTabGroupKey)); - Assert.Equal (win2, top.Focused); - Assert.Equal (tf1W2, top.MostFocused); + Assert.Equal (win2, top.GetFocused ()); + Assert.Equal (tf1W2, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Application.PrevTabGroupKey)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tf2W1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tf2W1, top.GetMostFocused ()); Assert.True (Application.OnKeyDown (Key.CursorUp)); - Assert.Equal (win1, top.Focused); - Assert.Equal (tvW1, top.MostFocused); + Assert.Equal (win1, top.GetFocused ()); + Assert.Equal (tvW1, top.GetMostFocused ()); top.Dispose (); } diff --git a/UnitTests/Views/TreeTableSourceTests.cs b/UnitTests/Views/TreeTableSourceTests.cs index 4e4ba260e..516187206 100644 --- a/UnitTests/Views/TreeTableSourceTests.cs +++ b/UnitTests/Views/TreeTableSourceTests.cs @@ -290,7 +290,7 @@ public class TreeTableSourceTests : IDisposable var top = new Toplevel (); top.Add (tableView); top.RestoreFocus (null); - Assert.Equal (tableView, top.MostFocused); + Assert.Equal (tableView, top.GetMostFocused ()); return tableView; } diff --git a/UnitTests/Views/WindowTests.cs b/UnitTests/Views/WindowTests.cs index a010227f7..8fcfc7dde 100644 --- a/UnitTests/Views/WindowTests.cs +++ b/UnitTests/Views/WindowTests.cs @@ -132,7 +132,7 @@ public class WindowTests Assert.False (defaultWindow.HasFocus); Assert.Equal (new Rectangle (0, 0, Application.Screen.Width - 2, Application.Screen.Height - 2), defaultWindow.Viewport); Assert.Equal (new Rectangle (0, 0, Application.Screen.Width, Application.Screen.Height), defaultWindow.Frame); - Assert.Null (defaultWindow.Focused); + Assert.Null (defaultWindow.GetFocused ()); Assert.NotNull (defaultWindow.ColorScheme); Assert.Equal (0, defaultWindow.X); Assert.Equal (0, defaultWindow.Y); @@ -143,7 +143,7 @@ public class WindowTests Assert.False (defaultWindow.WantContinuousButtonPressed); Assert.False (defaultWindow.WantMousePositionReports); Assert.Null (defaultWindow.SuperView); - Assert.Null (defaultWindow.MostFocused); + Assert.Null (defaultWindow.GetMostFocused ()); Assert.Equal (TextDirection.LeftRight_TopBottom, defaultWindow.TextDirection); // Empty Rect @@ -154,7 +154,7 @@ public class WindowTests Assert.False (windowWithFrameRectEmpty.HasFocus); Assert.Equal (Rectangle.Empty, windowWithFrameRectEmpty.Viewport); Assert.Equal (Rectangle.Empty, windowWithFrameRectEmpty.Frame); - Assert.Null (windowWithFrameRectEmpty.Focused); + Assert.Null (windowWithFrameRectEmpty.GetFocused ()); Assert.NotNull (windowWithFrameRectEmpty.ColorScheme); Assert.Equal (0, windowWithFrameRectEmpty.X); Assert.Equal (0, windowWithFrameRectEmpty.Y); @@ -167,7 +167,7 @@ public class WindowTests Assert.False (windowWithFrameRectEmpty.WantContinuousButtonPressed); Assert.False (windowWithFrameRectEmpty.WantMousePositionReports); Assert.Null (windowWithFrameRectEmpty.SuperView); - Assert.Null (windowWithFrameRectEmpty.MostFocused); + Assert.Null (windowWithFrameRectEmpty.GetMostFocused ()); Assert.Equal (TextDirection.LeftRight_TopBottom, windowWithFrameRectEmpty.TextDirection); // Rect with values @@ -185,7 +185,7 @@ public class WindowTests Assert.False (windowWithFrame1234.HasFocus); Assert.Equal (new (0, 0, 1, 2), windowWithFrame1234.Viewport); Assert.Equal (new (1, 2, 3, 4), windowWithFrame1234.Frame); - Assert.Null (windowWithFrame1234.Focused); + Assert.Null (windowWithFrame1234.GetFocused ()); Assert.NotNull (windowWithFrame1234.ColorScheme); Assert.Equal (1, windowWithFrame1234.X); Assert.Equal (2, windowWithFrame1234.Y); @@ -198,7 +198,7 @@ public class WindowTests Assert.False (windowWithFrame1234.WantContinuousButtonPressed); Assert.False (windowWithFrame1234.WantMousePositionReports); Assert.Null (windowWithFrame1234.SuperView); - Assert.Null (windowWithFrame1234.MostFocused); + Assert.Null (windowWithFrame1234.GetMostFocused ()); Assert.Equal (TextDirection.LeftRight_TopBottom, windowWithFrame1234.TextDirection); }