Skip to content

Commit 5ecf3a0

Browse files
committed
Fix #5283
Avoid keyboard navigation moving back to the same widget if possible, as in some situations skipping over invisible or disabled widgets could loop back to the starting point and prevent from accessing parts of the screen.
1 parent b4d6253 commit 5ecf3a0

File tree

2 files changed

+27
-35
lines changed

2 files changed

+27
-35
lines changed

src/guiengine/event_handler.cpp

+25-33
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ void EventHandler::sendNavigationEvent(const NavigationDirection nav, const int
446446

447447
if (!handled_by_widget)
448448
{
449-
navigate(nav, playerID);
449+
navigate(nav, GUIEngine::getFocusForPlayer(playerID), playerID);
450450
}
451451
} // sendNavigationEvent
452452

@@ -456,11 +456,9 @@ void EventHandler::sendNavigationEvent(const NavigationDirection nav, const int
456456
*
457457
* \param nav Determine in which direction to navigate
458458
*/
459-
void EventHandler::navigate(const NavigationDirection nav, const int playerID)
459+
void EventHandler::navigate(const NavigationDirection nav, Widget* starting_widget, const int playerID)
460460
{
461-
Widget* w = GUIEngine::getFocusForPlayer(playerID);
462-
463-
int next_id = findIDClosestWidget(nav, playerID, w, false);
461+
int next_id = findIDClosestWidget(nav, playerID, starting_widget, false);
464462

465463
if (next_id != -1)
466464
{
@@ -507,19 +505,19 @@ void EventHandler::navigate(const NavigationDirection nav, const int playerID)
507505
} // navigate
508506

509507
/**
510-
* This function use simple heuristic to find the closest widget
511-
* in the requested direction,
508+
* This function use simple heuristics to find the closest widget
509+
* in the requested direction.
512510
* It prioritize widgets close vertically to widget close horizontally,
513511
* as it is expected behavior in any direction.
514512
* Several hardcoded values are used, having been found to work well
515513
* experimentally while keeping simple heuristics.
516514
*/
517515
int EventHandler::findIDClosestWidget(const NavigationDirection nav, const int playerID,
518-
GUIEngine::Widget* w, bool ignore_disabled, int recursion_counter)
516+
Widget* w, bool ignore_disabled, int recursion_counter)
519517
{
520518
int closest_widget_id = -1;
521519
int distance = 0;
522-
// So that the UI behavior don't change when it is upscaled
520+
// So that the UI behavior doesn't change when it is upscaled
523521
const int BIG_DISTANCE = irr_driver->getActualScreenSize().Width*100;
524522
int smallest_distance = BIG_DISTANCE;
525523
// Used when there is no suitable widget in the requested direction
@@ -539,7 +537,7 @@ int EventHandler::findIDClosestWidget(const NavigationDirection nav, const int p
539537
// - it doesn't match a widget
540538
// - it doesn't match a focusable widget
541539
// - it corresponds to the current widget
542-
// - it corresponds to an invisible or disabled widget
540+
// - it corresponds to an invisible or disabled widget (while ignore_disabled is true)
543541
// - the player is not allowed to select it
544542
// - Its base coordinates are negative (such as buttons within ribbons)
545543
if (w_test == NULL || !Widget::isFocusableId(i) || w == w_test ||
@@ -582,16 +580,13 @@ int EventHandler::findIDClosestWidget(const NavigationDirection nav, const int p
582580

583581
if (nav == NAV_UP || nav == NAV_DOWN)
584582
{
583+
// Compare current top point with other widget lowest point
585584
if (nav == NAV_UP)
586-
{
587-
// Compare current top point with other widget lowest point
588585
distance = w->m_y - (w_test->m_y + w_test->m_h);
589-
}
586+
587+
// compare current lowest point with other widget top point
590588
else
591-
{
592-
// compare current lowest point with other widget top point
593589
distance = w_test->m_y - (w->m_y + w->m_h);
594-
}
595590

596591
// Better select an item on the side that one much higher,
597592
// so make the vertical distance matter much more
@@ -613,16 +608,14 @@ int EventHandler::findIDClosestWidget(const NavigationDirection nav, const int p
613608
}
614609
else if (nav == NAV_LEFT || nav == NAV_RIGHT)
615610
{
611+
// compare current leftmost point with other widget rightmost
616612
if (nav == NAV_LEFT)
617-
{
618-
// compare current leftmost point with other widget rightmost
619613
distance = w->m_x - rightmost;
620-
}
614+
615+
// compare current rightmost point with other widget leftmost
621616
else
622-
{
623-
// compare current rightmost point with other widget leftmost
624617
distance = w_test->m_x - (w->m_x + w->m_w);
625-
}
618+
626619
wrapping_distance = distance;
627620

628621
int down_offset = std::max(0, w_test->m_y - w->m_y);
@@ -677,6 +670,7 @@ int EventHandler::findIDClosestWidget(const NavigationDirection nav, const int p
677670

678671
int closest_id = (smallest_distance < BIG_DISTANCE) ? closest_widget_id :
679672
closest_wrapping_widget_id;
673+
680674
Widget* w_test = GUIEngine::getWidget(closest_id);
681675

682676
if (w_test == NULL)
@@ -687,21 +681,19 @@ int EventHandler::findIDClosestWidget(const NavigationDirection nav, const int p
687681
// This allows to skip over disabled/invisible widgets in a grid
688682
if (!w_test->isVisible() || !w_test->isActivated())
689683
{
690-
// Can skip over at most 3 consecutive disabled/invisible widget
684+
// Can skip over at most 3 consecutive disabled/invisible widgets
691685
if (recursion_counter <=2)
692-
{
693-
recursion_counter++;
694-
return findIDClosestWidget(nav, playerID, w_test, /*ignore disabled*/ false, recursion_counter);
695-
}
696-
// If nothing has been found, do a search ignoring disabled/invisible widgets,
697-
// restarting from the initial focused widget (otherwise, it could lead to weird results)
686+
closest_id = findIDClosestWidget(nav, playerID, w_test, /*ignore disabled*/ false, recursion_counter + 1);
687+
// We have only found disabled/invisible widgets on our path
698688
else if (recursion_counter == 3)
699-
{
700-
Widget* w_focus = GUIEngine::getFocusForPlayer(playerID);
701-
return findIDClosestWidget(nav, playerID, w_focus, /*ignore disabled*/ true, recursion_counter);
702-
}
689+
closest_id = -1;
703690
}
704691

692+
// If nothing has been found or if we looped back to the starting widget (see issue #5283),
693+
// do a search ignoring disabled/invisible widgets, restarting from the initial widget
694+
if (recursion_counter == 0 && (closest_id == -1 || GUIEngine::getWidget(closest_id) == w))
695+
closest_id = findIDClosestWidget(nav, playerID, w, /*ignore disabled*/ true, 1 /* avoid infinite loops */);
696+
705697
return closest_id;
706698
} // findIDClosestWidget
707699

src/guiengine/event_handler.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ namespace GUIEngine
7272
EventPropagation onGUIEvent(const irr::SEvent& event);
7373
EventPropagation onWidgetActivated(Widget* w, const int playerID, Input::InputType type);
7474
void sendNavigationEvent(const NavigationDirection nav, const int playerID);
75-
void navigate(const NavigationDirection nav, const int playerID);
75+
void navigate(const NavigationDirection nav, Widget* starting_widget, const int playerID);
7676

7777
/** \brief send an event to the GUI module user's event callback
7878
* \param widget the widget that triggerred this event
@@ -114,7 +114,7 @@ namespace GUIEngine
114114

115115
void setAcceptEvents(bool value) { m_accept_events = value; }
116116
int findIDClosestWidget(const NavigationDirection nav, const int playerID,
117-
Widget* w, bool ignore_disabled, int recursion_counter=1);
117+
Widget* w, bool ignore_disabled, int recursion_counter=0);
118118
};
119119

120120
}

0 commit comments

Comments
 (0)