Skip to content

Commit 573d6ee

Browse files
committed
Fixed #3786 - Focus
1 parent 675b8bb commit 573d6ee

File tree

11 files changed

+544
-369
lines changed

11 files changed

+544
-369
lines changed

Terminal.Gui/Application/ApplicationNavigation.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ internal void SetFocused (View? value)
7777
{
7878
return;
7979
}
80+
Debug.Assert (value is null or { CanFocus: true, HasFocus: true });
8081

8182
_focused = value;
8283

Terminal.Gui/View/View.Hierarchy.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ public virtual void OnRemoved (SuperViewChangedEventArgs e)
169169
Debug.Assert (!view.HasFocus);
170170

171171
_subviews.Remove (view);
172+
173+
// Clean up focus stuff
174+
_previouslyFocused = null;
175+
if (view._superView is { } && view._superView._previouslyFocused == this)
176+
{
177+
view._superView._previouslyFocused = null;
178+
}
172179
view._superView = null;
173180

174181
SetNeedsLayout ();

Terminal.Gui/View/View.Navigation.cs

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ public bool CanFocus
180180
if (!_canFocus && HasFocus)
181181
{
182182
// If CanFocus is set to false and this view has focus, make it leave focus
183-
HasFocus = false;
183+
// Set traverssingdown so we don't go back up the hierachy...
184+
SetHasFocusFalse (null, traversingDown: false);
184185
}
185186

186187
if (_canFocus && !HasFocus && Visible && SuperView is { Focused: null })
@@ -296,7 +297,12 @@ internal bool RestoreFocus ()
296297

297298
if (Focused is null && _previouslyFocused is { } && indicies.Contains (_previouslyFocused))
298299
{
299-
return _previouslyFocused.SetFocus ();
300+
if (_previouslyFocused.SetFocus ())
301+
{
302+
return true;
303+
}
304+
305+
_previouslyFocused = null;
300306
}
301307

302308
return false;
@@ -412,14 +418,14 @@ public bool SetFocus ()
412418
/// other methods that
413419
/// set or remove focus from a view.
414420
/// </summary>
415-
/// <param name="previousFocusedView">
416-
/// The previously focused view. If <see langword="null"/> there is no previously focused
421+
/// <param name="currentFocusedView">
422+
/// The currently focused view. If <see langword="null"/> there is no previously focused
417423
/// view.
418424
/// </param>
419425
/// <param name="traversingUp"></param>
420426
/// <returns><see langword="true"/> if <see cref="HasFocus"/> was changed to <see langword="true"/>.</returns>
421427
/// <exception cref="InvalidOperationException"></exception>
422-
private (bool focusSet, bool cancelled) SetHasFocusTrue (View? previousFocusedView, bool traversingUp = false)
428+
private (bool focusSet, bool cancelled) SetHasFocusTrue (View? currentFocusedView, bool traversingUp = false)
423429
{
424430
Debug.Assert (SuperView is null || ApplicationNavigation.IsInHierarchy (SuperView, this));
425431

@@ -429,6 +435,11 @@ public bool SetFocus ()
429435
return (false, false);
430436
}
431437

438+
if (currentFocusedView is { HasFocus: false })
439+
{
440+
throw new ArgumentException ("SetHasFocusTrue: currentFocusedView must HasFocus.");
441+
}
442+
432443
var thisAsAdornment = this as Adornment;
433444
View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
434445

@@ -451,7 +462,7 @@ public bool SetFocus ()
451462

452463
bool previousValue = HasFocus;
453464

454-
bool cancelled = NotifyFocusChanging (false, true, previousFocusedView, this);
465+
bool cancelled = NotifyFocusChanging (false, true, currentFocusedView, this);
455466

456467
if (cancelled)
457468
{
@@ -462,7 +473,7 @@ public bool SetFocus ()
462473
// Any of them may cancel gaining focus. In which case we need to back out.
463474
if (superViewOrParent is { HasFocus: false } sv)
464475
{
465-
(bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (previousFocusedView, true);
476+
(bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (currentFocusedView, true);
466477

467478
if (!focusSet)
468479
{
@@ -488,31 +499,33 @@ public bool SetFocus ()
488499

489500
if (!traversingUp)
490501
{
491-
// Restore focus to the previously focused subview
502+
// Restore focus to the previously focused subview, if any
492503
if (!RestoreFocus ())
493504
{
494-
// Couldn't restore focus, so use Advance to navigate to the next focusable subview
495-
if (!AdvanceFocus (NavigationDirection.Forward, null))
496-
{
497-
// Couldn't advance, so we're the most focused view in the application
498-
Application.Navigation?.SetFocused (this);
499-
}
505+
Debug.Assert (_previouslyFocused is null);
506+
// Couldn't restore focus, so use Advance to navigate to the next focusable subview, if any
507+
AdvanceFocus (NavigationDirection.Forward, null);
500508
}
501509
}
502510

503-
if (previousFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (previousFocusedView))
511+
// Now make sure the old focused view loses focus
512+
if (currentFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (currentFocusedView))
504513
{
505-
previousFocusedView.SetHasFocusFalse (this);
514+
currentFocusedView.SetHasFocusFalse (this);
506515
}
507516

508-
_previouslyFocused = null;
517+
if (_previouslyFocused is { })
518+
{
519+
_previouslyFocused = null;
520+
}
509521

510522
if (Arrangement.HasFlag (ViewArrangement.Overlapped))
511523
{
512524
SuperView?.MoveSubviewToEnd (this);
513525
}
514526

515-
NotifyFocusChanged (HasFocus, previousFocusedView, this);
527+
// Focus work is done. Notify.
528+
NotifyFocusChanged (HasFocus, currentFocusedView, this);
516529

517530
SetNeedsDisplay ();
518531

@@ -527,6 +540,9 @@ public bool SetFocus ()
527540

528541
private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused)
529542
{
543+
Debug.Assert (currentFocused is null || currentFocused is { HasFocus: true });
544+
Debug.Assert (newFocused is null || newFocused is { CanFocus: true });
545+
530546
// Call the virtual method
531547
if (OnHasFocusChanging (currentHasFocus, newHasFocus, currentFocused, newFocused))
532548
{
@@ -543,6 +559,13 @@ private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, View?
543559
return true;
544560
}
545561

562+
View? appFocused = Application.Navigation?.GetFocused ();
563+
564+
if (appFocused == currentFocused)
565+
{
566+
Application.Navigation?.SetFocused (null);
567+
}
568+
546569
return false;
547570
}
548571

@@ -586,8 +609,8 @@ private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, View?
586609
/// focused.
587610
/// </param>
588611
/// <param name="traversingDown">
589-
/// Set to true to indicate method is being called recurively, traversing down the focus
590-
/// chain.
612+
/// Set to true to traverse down the focus
613+
/// chain only. If false, the method will attempt to AdvanceFocus on the superview or restorefocus on Application.Navigation.GetFocused().
591614
/// </param>
592615
/// <exception cref="InvalidOperationException"></exception>
593616
private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false)
@@ -598,53 +621,60 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
598621
throw new InvalidOperationException ("SetHasFocusFalse should not be called if the view does not have focus.");
599622
}
600623

624+
if (newFocusedView is { HasFocus: false })
625+
{
626+
throw new InvalidOperationException ("SetHasFocusFalse new focused view does not have focus.");
627+
}
628+
601629
var thisAsAdornment = this as Adornment;
602630
View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
603631

604632
// If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it.
605633
if (!traversingDown && newFocusedView is null)
606634
{
607-
if (superViewOrParent?._previouslyFocused is { })
635+
if (superViewOrParent?._previouslyFocused is { CanFocus: true })
608636
{
609-
if (superViewOrParent._previouslyFocused != this)
637+
if (superViewOrParent._previouslyFocused != this && superViewOrParent._previouslyFocused.SetFocus ())
610638
{
611-
superViewOrParent?._previouslyFocused?.SetFocus ();
612-
613639
// The above will cause SetHasFocusFalse, so we can return
640+
Debug.Assert (!_hasFocus);
614641
return;
615642
}
616643
}
617644

618-
if (superViewOrParent is { })
645+
if (superViewOrParent is { CanFocus: true })
619646
{
620647
if (superViewOrParent.AdvanceFocus (NavigationDirection.Forward, TabStop))
621648
{
622649
// The above will cause SetHasFocusFalse, so we can return
650+
Debug.Assert (!_hasFocus);
623651
return;
624652
}
625653

626-
newFocusedView = superViewOrParent;
654+
if (superViewOrParent is { HasFocus: true, CanFocus: true })
655+
{
656+
newFocusedView = superViewOrParent;
657+
}
627658
}
628659

629-
if (Application.Navigation is { } && Application.Top is { })
660+
if (Application.Navigation is { } && Application.Navigation.GetFocused () is { CanFocus: true })
630661
{
631662
// Temporarily ensure this view can't get focus
632663
bool prevCanFocus = _canFocus;
633664
_canFocus = false;
634-
bool restoredFocus = Application.Top!.RestoreFocus ();
665+
bool restoredFocus = Application.Navigation.GetFocused ()!.RestoreFocus ();
635666
_canFocus = prevCanFocus;
636667

637668
if (restoredFocus)
638669
{
639670
// The above caused SetHasFocusFalse, so we can return
671+
Debug.Assert (!_hasFocus);
640672
return;
641673
}
642674
}
643-
644675
// No other focusable view to be found. Just "leave" us...
645676
}
646677

647-
648678
// Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus.
649679
View? mostFocused = MostFocused;
650680

@@ -665,15 +695,7 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
665695
bottom = bottom.SuperView;
666696
}
667697

668-
if (bottom == this && bottom.SuperView is Adornment a)
669-
{
670-
//a.SetHasFocusFalse (newFocusedView, true);
671-
672-
Debug.Assert (_hasFocus);
673-
}
674-
675698
Debug.Assert (_hasFocus);
676-
677699
}
678700

679701
if (superViewOrParent is { })
@@ -684,24 +706,16 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
684706
bool previousValue = HasFocus;
685707

686708
// Note, can't be cancelled.
687-
NotifyFocusChanging (HasFocus, !HasFocus, newFocusedView, this);
709+
NotifyFocusChanging (HasFocus, !HasFocus, this, newFocusedView);
710+
711+
Debug.Assert (_hasFocus);
688712

689713
// Get whatever peer has focus, if any so we can update our superview's _previouslyMostFocused
690714
View? focusedPeer = superViewOrParent?.Focused;
691715

692716
// Set HasFocus false
693717
_hasFocus = false;
694718

695-
if (Application.Navigation is { })
696-
{
697-
View? appFocused = Application.Navigation.GetFocused ();
698-
699-
if (appFocused is { } || appFocused == this)
700-
{
701-
Application.Navigation.SetFocused (newFocusedView ?? superViewOrParent);
702-
}
703-
}
704-
705719
NotifyFocusChanged (HasFocus, this, newFocusedView);
706720

707721
if (_hasFocus)
@@ -721,6 +735,11 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
721735

722736
private void NotifyFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
723737
{
738+
if (newHasFocus && focusedVew?.Focused is null)
739+
{
740+
Application.Navigation?.SetFocused (focusedVew);
741+
}
742+
724743
// Call the virtual method
725744
OnHasFocusChanged (newHasFocus, previousFocusedView, focusedVew);
726745

0 commit comments

Comments
 (0)