Multi directional GUI navigation (#3307)
* Allow full up/down/left/right navigation * Update list and ribbon widgets to work well with the new navigation * Fix windows compilation * Improve navigation algorithm to fix the observed limitations * Make windows compilator happy * Fix keyboard navigation issues in networking lobby And make some parts more proportional to screen size
This commit is contained in:
parent
bd16045525
commit
3450984c3e
@ -2,22 +2,21 @@
|
||||
<stkgui>
|
||||
<div x="0" y="0" width="100%" height="100%" layout="vertical-row" >
|
||||
<header id="lobby-text" text_align="center" width="80%" align="center" I18N="In networking lobby" text="Lobby"/>
|
||||
<spacer height="15" width="10"/>
|
||||
<div proportion="4" x="2%" width="96%" layout="vertical-row">
|
||||
<spacer height="1%" width="10"/>
|
||||
<div proportion="9" x="2%" width="96%" layout="vertical-row">
|
||||
<div width="100%" proportion="2" layout="horizontal-row">
|
||||
<box id="info" proportion="2" height="100%" layout="vertical-row">
|
||||
<label word_wrap="true" id="text" proportion="3" width="100%" height="100%" text_valign="top"/>
|
||||
</box>
|
||||
<spacer width="20" height="20"/>
|
||||
<spacer width="3%" height="20"/>
|
||||
<box proportion="1" height="100%" layout="vertical-row">
|
||||
<list id="players" width="100%" height="100%"/>
|
||||
</box>
|
||||
</div>
|
||||
</div>
|
||||
<spacer height="20"/>
|
||||
<div width="100%" proportion="1" layout="horizontal-row">
|
||||
<spacer width="20" height="20"/>
|
||||
<box proportion="2" height="100%" layout="vertical-row">
|
||||
<spacer height="2%"/>
|
||||
<div proportion="2" x="2%" width="96%" layout="horizontal-row">
|
||||
<box proportion="4" height="100%" layout="vertical-row">
|
||||
<textbox id="chat" width="100%" height="30%"/>
|
||||
<spacer height="10"/>
|
||||
<div width="100%" height="30%" proportion="1" layout="horizontal-row">
|
||||
@ -26,15 +25,13 @@
|
||||
<label id="timeout-message" width="80%" height="fit"/>
|
||||
</div>
|
||||
</box>
|
||||
<spacer width="40"/>
|
||||
<buttonbar id="actions" proportion="1" width="75%" height="75%">
|
||||
<icon-button id="start" width="64" height="64" icon="gui/green_check.png" align="center"
|
||||
I18N="In the network lobby" text="Start race"/>
|
||||
<icon-button id="exit" width="64" height="64" icon="gui/main_quit.png" align="center"
|
||||
I18N="In the network lobby" text="Exit"/>
|
||||
</buttonbar>
|
||||
<spacer width="3%"/>
|
||||
<icon-button id="start" proportion="1" width="64" height="64" icon="gui/green_check.png" align="top"
|
||||
I18N="In the network lobby" text="Start race"/>
|
||||
<icon-button id="exit" proportion="1" width="64" height="64" icon="gui/main_quit.png" align="top"
|
||||
I18N="In the network lobby" text="Exit"/>
|
||||
</div>
|
||||
<spacer height="10"/>
|
||||
<spacer height="1%"/>
|
||||
</div>
|
||||
<icon-button id="back" x="0" y="0" height="8%" icon="gui/back.png"/>
|
||||
</stkgui>
|
||||
|
@ -49,6 +49,7 @@
|
||||
|
||||
using GUIEngine::EventHandler;
|
||||
using GUIEngine::EventPropagation;
|
||||
using GUIEngine::NavigationDirection;
|
||||
|
||||
using namespace irr::gui;
|
||||
|
||||
@ -310,68 +311,34 @@ void EventHandler::processGUIAction(const PlayerAction action,
|
||||
case PA_STEER_LEFT:
|
||||
case PA_MENU_LEFT:
|
||||
{
|
||||
Widget* w = GUIEngine::getFocusForPlayer(playerID);
|
||||
if (w == NULL) break;
|
||||
|
||||
Widget* widget_to_call = w;
|
||||
|
||||
/* Find topmost parent. Stop looping if a widget event handler's is itself, to not fall
|
||||
in an infinite loop (this can happen e.g. in checkboxes, where they need to be
|
||||
notified of clicks onto themselves so they can toggle their state. )
|
||||
On the way, also notify everyone in the chain of the left press. */
|
||||
while (widget_to_call->m_event_handler != NULL && widget_to_call->m_event_handler != widget_to_call)
|
||||
{
|
||||
if (widget_to_call->leftPressed(playerID) == EVENT_LET)
|
||||
{
|
||||
sendEventToUser(w, w->m_properties[PROP_ID], playerID);
|
||||
}
|
||||
widget_to_call = widget_to_call->m_event_handler;
|
||||
}
|
||||
|
||||
|
||||
if (widget_to_call->leftPressed(playerID) == EVENT_LET)
|
||||
{
|
||||
sendEventToUser(widget_to_call, widget_to_call->m_properties[PROP_ID], playerID);
|
||||
}
|
||||
sendNavigationEvent(NAV_LEFT, playerID);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_STEER_RIGHT:
|
||||
case PA_MENU_RIGHT:
|
||||
{
|
||||
Widget* w = GUIEngine::getFocusForPlayer(playerID);
|
||||
if (w == NULL) break;
|
||||
|
||||
Widget* widget_to_call = w;
|
||||
/* Find topmost parent. Stop looping if a widget event handler's is itself, to not fall
|
||||
in an infinite loop (this can happen e.g. in checkboxes, where they need to be
|
||||
notified of clicks onto themselves so they can toggle their state. )
|
||||
On the way, also notify everyone in the chain of the right press */
|
||||
while (widget_to_call->m_event_handler != NULL && widget_to_call->m_event_handler != widget_to_call)
|
||||
{
|
||||
if (widget_to_call->rightPressed(playerID) == EVENT_LET)
|
||||
{
|
||||
sendEventToUser(widget_to_call, w->m_properties[PROP_ID], playerID);
|
||||
}
|
||||
widget_to_call = widget_to_call->m_event_handler;
|
||||
}
|
||||
|
||||
if (widget_to_call->rightPressed(playerID) == EVENT_LET)
|
||||
{
|
||||
sendEventToUser(widget_to_call, widget_to_call->m_properties[PROP_ID], playerID);
|
||||
}
|
||||
sendNavigationEvent(NAV_RIGHT, playerID);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_ACCEL:
|
||||
case PA_MENU_UP:
|
||||
navigate(playerID, type, pressedDown, true);
|
||||
{
|
||||
if (type == Input::IT_STICKBUTTON && !pressedDown)
|
||||
break;
|
||||
sendNavigationEvent(NAV_UP, playerID);
|
||||
break;
|
||||
}
|
||||
|
||||
case PA_BRAKE:
|
||||
case PA_MENU_DOWN:
|
||||
navigate(playerID, type, pressedDown, false);
|
||||
{
|
||||
if (type == Input::IT_STICKBUTTON && !pressedDown)
|
||||
break;
|
||||
sendNavigationEvent(NAV_DOWN, playerID);
|
||||
break;
|
||||
}
|
||||
|
||||
case PA_RESCUE:
|
||||
case PA_MENU_CANCEL:
|
||||
@ -427,156 +394,250 @@ const bool NAVIGATION_DEBUG = false;
|
||||
#pragma mark Private methods
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Focus the next widget either downwards or upwards.
|
||||
*
|
||||
* \param reverse True means navigating up, false means down.
|
||||
*/
|
||||
void EventHandler::navigate(const int playerID, Input::InputType type, const bool pressedDown, const bool reverse)
|
||||
void EventHandler::sendNavigationEvent(const NavigationDirection nav, const int playerID)
|
||||
{
|
||||
IGUIElement *el = NULL;
|
||||
|
||||
if (type == Input::IT_STICKBUTTON && !pressedDown)
|
||||
return;
|
||||
|
||||
Widget* w = GUIEngine::getFocusForPlayer(playerID);
|
||||
if (w != NULL)
|
||||
if (w == NULL)
|
||||
{
|
||||
el = w->getIrrlichtElement();
|
||||
Widget* defaultWidget = NULL;
|
||||
Screen* screen = GUIEngine::getCurrentScreen();
|
||||
if (screen == NULL) return;
|
||||
defaultWidget = screen->getFirstWidget();
|
||||
|
||||
if (defaultWidget != NULL)
|
||||
{
|
||||
if (playerID != PLAYER_ID_GAME_MASTER && !defaultWidget->m_supports_multiplayer)
|
||||
return;
|
||||
defaultWidget->setFocusForPlayer(playerID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// list widgets are a bit special, because up/down keys are also used
|
||||
// to navigate between various list items, not only to navigate between
|
||||
// components
|
||||
if (w != NULL && w->m_type == WTYPE_LIST)
|
||||
Widget* widget_to_call = w;
|
||||
|
||||
bool handled_by_widget = false;
|
||||
EventPropagation propagation_state = EVENT_BLOCK;
|
||||
|
||||
/* Find topmost parent. Stop looping if a widget event handler's is itself, to not fall
|
||||
in an infinite loop (this can happen e.g. in checkboxes, where they need to be
|
||||
notified of clicks onto themselves so they can toggle their state. )
|
||||
On the way, also notify everyone in the chain of the press. */
|
||||
do
|
||||
{
|
||||
ListWidget* list = (ListWidget*) w;
|
||||
if (nav == NAV_LEFT)
|
||||
propagation_state = widget_to_call->leftPressed(playerID);
|
||||
else if (nav == NAV_RIGHT)
|
||||
propagation_state = widget_to_call->rightPressed(playerID);
|
||||
else if (nav == NAV_UP)
|
||||
propagation_state = widget_to_call->upPressed(playerID);
|
||||
else if (nav == NAV_DOWN)
|
||||
propagation_state = widget_to_call->downPressed(playerID);
|
||||
|
||||
const bool stay_within_list = reverse ? list->getSelectionID() > 0 :
|
||||
list->getSelectionID() < list->getItemCount() - 1;
|
||||
if (propagation_state == EVENT_LET)
|
||||
sendEventToUser(widget_to_call, widget_to_call->m_properties[PROP_ID], playerID);
|
||||
|
||||
if (stay_within_list)
|
||||
if (propagation_state == EVENT_LET || propagation_state == EVENT_BLOCK_BUT_HANDLED)
|
||||
handled_by_widget = true;
|
||||
|
||||
if (widget_to_call->m_event_handler == NULL)
|
||||
break;
|
||||
|
||||
widget_to_call = widget_to_call->m_event_handler;
|
||||
} while (widget_to_call->m_event_handler != widget_to_call);
|
||||
|
||||
if (!handled_by_widget)
|
||||
{
|
||||
navigate(nav, playerID);
|
||||
}
|
||||
} // sendNavigationEvent
|
||||
|
||||
|
||||
/**
|
||||
* Focus the next widget downards, upwards, leftwards or rightwards.
|
||||
*
|
||||
* \param nav Determine in which direction to navigate
|
||||
*/
|
||||
void EventHandler::navigate(const NavigationDirection nav, const int playerID)
|
||||
{
|
||||
Widget* w = GUIEngine::getFocusForPlayer(playerID);
|
||||
|
||||
int next_id = findIDClosestWidget(nav, playerID, w, false);
|
||||
|
||||
if (next_id != -1)
|
||||
{
|
||||
Widget* closest_widget = GUIEngine::getWidget(next_id);
|
||||
closest_widget->setFocusForPlayer(playerID);
|
||||
|
||||
// A list exception : when entering a list by going down/left/right, select the first item
|
||||
// when focusing a list by going up, select the last item of the list
|
||||
if (closest_widget->m_type == WTYPE_LIST)
|
||||
{
|
||||
if (reverse)
|
||||
list->setSelectionID(list->getSelectionID() - 1);
|
||||
else
|
||||
list->setSelectionID(list->getSelectionID() + 1);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->setSelectionID(-1);
|
||||
ListWidget* list = (ListWidget*) closest_widget;
|
||||
assert(list != NULL);
|
||||
list->setSelectionID(nav == NAV_UP ? list->getItemCount() - 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (w != NULL && ((reverse && w->m_tab_up_root != -1) || (!reverse && w->m_tab_down_root != -1)))
|
||||
return;
|
||||
|
||||
} // navigate
|
||||
|
||||
/* This function use simple heuristic to find the closest widget
|
||||
* in the requested direction,
|
||||
* It prioritize widgets close vertically to widget close horizontally,
|
||||
* as it is expected behavior in any direction.
|
||||
* Several hardcoded values are used, having been found to work well
|
||||
* experimentally while keeping simple heuristics.
|
||||
*/
|
||||
int EventHandler::findIDClosestWidget(const NavigationDirection nav, const int playerID,
|
||||
GUIEngine::Widget* w, bool ignore_disabled, int recursion_counter)
|
||||
{
|
||||
int closest_widget_id = -1;
|
||||
int distance = 0;
|
||||
// So that the UI behavior don't change when it is upscaled
|
||||
const int BIG_DISTANCE = irr_driver->getActualScreenSize().Width*100;
|
||||
int smallest_distance = BIG_DISTANCE;
|
||||
// Used when there is no suitable widget in the requested direction
|
||||
int closest_wrapping_widget_id = -1;
|
||||
int wrapping_distance = 0;
|
||||
int smallest_wrapping_distance = BIG_DISTANCE;
|
||||
|
||||
// In theory, it's better to look recursively in m_widgets
|
||||
// In practice, this is much much simpler and work equally well
|
||||
// Usual widget IDs begin at 100 and there is rarely more
|
||||
// than a few dozen of them - but it's very cheap to have some margin,
|
||||
for (int i=0;i<1000;i++)
|
||||
{
|
||||
Widget* next = GUIEngine::getWidget(reverse ? w->m_tab_up_root : w->m_tab_down_root);
|
||||
assert(next != NULL);
|
||||
el = next->getIrrlichtElement();
|
||||
Widget* w_test = GUIEngine::getWidget(i);
|
||||
|
||||
if (el == NULL)
|
||||
{
|
||||
Log::warn("EventHandler::navigate", "m_tab_down/up_root is set to an ID for which I can't find the widget");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// don't allow navigating to any widget when a dialog is shown; only navigate to widgets in the dialog
|
||||
if (ScreenKeyboard::isActive())
|
||||
{
|
||||
if (!ScreenKeyboard::getCurrent()->isMyIrrChild(el))
|
||||
el = NULL;
|
||||
}
|
||||
else if (ModalDialog::isADialogActive())
|
||||
{
|
||||
if (!ModalDialog::getCurrent()->isMyIrrChild(el))
|
||||
el = NULL;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
// find closest widget
|
||||
if (el != NULL && el->getTabGroup() != NULL)
|
||||
{
|
||||
// Up: if the current widget is e.g. 15, search for widget 14, 13, 12, ... (up to 10 IDs may be missing)
|
||||
// Down: if the current widget is e.g. 5, search for widget 6, 7, 8, 9, ..., 15 (up to 10 IDs may be missing)
|
||||
for (int n = 1; n < 10 && !found; n++)
|
||||
{
|
||||
IGUIElement *closest = GUIEngine::getGUIEnv()->getRootGUIElement()->getElementFromId(el->getTabOrder() + (reverse ? -n : n), true);
|
||||
|
||||
if (closest != NULL && Widget::isFocusableId(closest->getID()))
|
||||
{
|
||||
Widget* closestWidget = GUIEngine::getWidget( closest->getID() );
|
||||
|
||||
if (playerID != PLAYER_ID_GAME_MASTER && !closestWidget->m_supports_multiplayer) return;
|
||||
|
||||
// if a dialog is shown, restrict to items in the dialog
|
||||
if (ScreenKeyboard::isActive())
|
||||
{
|
||||
if (!ScreenKeyboard::getCurrent()->isMyChild(closestWidget))
|
||||
continue;
|
||||
}
|
||||
else if (ModalDialog::isADialogActive())
|
||||
{
|
||||
if (!ModalDialog::getCurrent()->isMyChild(closestWidget))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NAVIGATION_DEBUG)
|
||||
{
|
||||
Log::info("EventHandler", "Navigating %s to %d", (reverse ? "up" : "down"), closest->getID());
|
||||
}
|
||||
|
||||
assert(closestWidget != NULL);
|
||||
|
||||
if (!closestWidget->isVisible() || !closestWidget->isActivated())
|
||||
continue;
|
||||
|
||||
closestWidget->setFocusForPlayer(playerID);
|
||||
|
||||
// another list exception : when entering a list by going down, select the first item
|
||||
// when focusing a list by going up, select the last item of the list
|
||||
if (closestWidget->m_type == WTYPE_LIST)
|
||||
{
|
||||
ListWidget* list = (ListWidget*) closestWidget;
|
||||
assert(list != NULL);
|
||||
list->setSelectionID(reverse ? list->getItemCount() - 1 : 0);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
} // end for
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (NAVIGATION_DEBUG)
|
||||
Log::info( "EventHandler::navigate", "Wrap around");
|
||||
|
||||
// select the last/first widget
|
||||
Widget* wrapWidget = NULL;
|
||||
// The widget id is invalid if :
|
||||
// - it doesn't match a widget
|
||||
// - it doesn't match a focusable widget
|
||||
// - it corresponds to the current widget
|
||||
// - it corresponds to an invisible or disabled widget
|
||||
// - the player is not allowed to select it
|
||||
if (w_test == NULL || !Widget::isFocusableId(i) || w == w_test ||
|
||||
(!w_test->isVisible() && ignore_disabled) ||
|
||||
(!w_test->isActivated() && ignore_disabled) ||
|
||||
(playerID != PLAYER_ID_GAME_MASTER && !w_test->m_supports_multiplayer))
|
||||
continue;
|
||||
|
||||
// if a dialog is shown, restrict to items in the dialog
|
||||
if (ScreenKeyboard::isActive())
|
||||
{
|
||||
wrapWidget = reverse ? ScreenKeyboard::getCurrent()->getLastWidget():
|
||||
ScreenKeyboard::getCurrent()->getFirstWidget();
|
||||
if (!ScreenKeyboard::getCurrent()->isMyChild(w_test))
|
||||
continue;
|
||||
}
|
||||
else if (ModalDialog::isADialogActive())
|
||||
{
|
||||
wrapWidget = reverse ? ModalDialog::getCurrent()->getLastWidget() :
|
||||
ModalDialog::getCurrent()->getFirstWidget();
|
||||
}
|
||||
else
|
||||
{
|
||||
Screen* screen = GUIEngine::getCurrentScreen();
|
||||
if (screen == NULL) return;
|
||||
wrapWidget = reverse ? screen->getLastWidget() :
|
||||
screen->getFirstWidget();
|
||||
if (!ModalDialog::getCurrent()->isMyChild(w_test))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wrapWidget != NULL) wrapWidget->setFocusForPlayer(playerID);
|
||||
int offset = 0;
|
||||
|
||||
if (nav == NAV_UP || nav == NAV_DOWN)
|
||||
{
|
||||
if (nav == NAV_UP)
|
||||
{
|
||||
// Compare current top point with other widget lowest point
|
||||
distance = w->m_y - (w_test->m_y + w_test->m_h);
|
||||
}
|
||||
else
|
||||
{
|
||||
// compare current lowest point with other widget top point
|
||||
distance = w_test->m_y - (w->m_y + w->m_h);
|
||||
}
|
||||
|
||||
// Better select an item on the side that one much higher,
|
||||
// so make the vertical distance matter much more
|
||||
// than the horizontal offset.
|
||||
// The multiplicator of 100 is meant so that the offset will matter
|
||||
// only if there are two or more widget with a (nearly) equal vertical height.
|
||||
distance *= 100;
|
||||
|
||||
wrapping_distance = distance;
|
||||
// if the two widgets are not well aligned, consider them farther
|
||||
// If w's left/right-mosts points are between w_test's,
|
||||
// right_offset and left_offset will be 0.
|
||||
// If w_test's are between w's,
|
||||
// we substract the smaller from the bigger
|
||||
// else, the smaller is 0 and we keep the bigger
|
||||
int right_offset = std::max(0, w_test->m_x - w->m_x);
|
||||
int left_offset = std::max(0, (w->m_x + w->m_w) - (w_test->m_x + w_test->m_w));
|
||||
offset = std::max (right_offset - left_offset, left_offset - right_offset);
|
||||
}
|
||||
else if (nav == NAV_LEFT || nav == NAV_RIGHT)
|
||||
{
|
||||
if (nav == NAV_LEFT)
|
||||
{
|
||||
// compare current leftmost point with other widget rightmost
|
||||
distance = w->m_x - (w_test->m_x + w_test->m_w);
|
||||
}
|
||||
else
|
||||
{
|
||||
// compare current rightmost point with other widget leftmost
|
||||
distance = w_test->m_x - (w->m_x + w->m_w);
|
||||
}
|
||||
wrapping_distance = distance;
|
||||
|
||||
int down_offset = std::max(0, w_test->m_y - w->m_y);
|
||||
int up_offset = std::max(0, (w->m_y + w->m_h) - (w_test->m_y + w_test->m_h));
|
||||
offset = std::max (down_offset - up_offset, up_offset - down_offset);
|
||||
|
||||
// No lateral selection if there is not at least partial alignement
|
||||
// >= because we don't want it to trigger if two widgets touch each other
|
||||
if (offset >= w->m_h)
|
||||
{
|
||||
distance = BIG_DISTANCE;
|
||||
wrapping_distance = BIG_DISTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
// This happens if the tested widget is in the opposite direction
|
||||
if (distance < 0)
|
||||
distance = BIG_DISTANCE;
|
||||
distance += offset;
|
||||
wrapping_distance += offset;
|
||||
|
||||
if (distance < smallest_distance)
|
||||
{
|
||||
smallest_distance = distance;
|
||||
closest_widget_id = i;
|
||||
}
|
||||
if (wrapping_distance < smallest_wrapping_distance)
|
||||
{
|
||||
smallest_wrapping_distance = wrapping_distance;
|
||||
closest_wrapping_widget_id = i;
|
||||
}
|
||||
} // for i < 1000
|
||||
|
||||
int closest_id = (smallest_distance < BIG_DISTANCE) ? closest_widget_id :
|
||||
closest_wrapping_widget_id;
|
||||
Widget* w_test = GUIEngine::getWidget(closest_id);
|
||||
|
||||
// If the newly found focus target is invisible, or not activated,
|
||||
// it is not a good target, search again
|
||||
// This allows to skip over disabled/invisible widgets in a grid
|
||||
if (!w_test->isVisible() || !w_test->isActivated())
|
||||
{
|
||||
// Can skip over at most 3 consecutive disabled/invisible widget
|
||||
if (recursion_counter <=2)
|
||||
{
|
||||
recursion_counter++;
|
||||
return findIDClosestWidget(nav, playerID, w_test, /*ignore disabled*/ false, recursion_counter);
|
||||
}
|
||||
// If nothing has been found, do a search ignoring disabled/invisible widgets,
|
||||
// restarting from the initial focused widget (otherwise, it could lead to weird results)
|
||||
else if (recursion_counter == 3)
|
||||
{
|
||||
Widget* w_focus = GUIEngine::getFocusForPlayer(playerID);
|
||||
return findIDClosestWidget(nav, playerID, w_focus, /*ignore disabled*/ true, recursion_counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closest_id;
|
||||
} // findIDClosestWidget
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@ -584,7 +645,7 @@ void EventHandler::sendEventToUser(GUIEngine::Widget* widget, std::string& name,
|
||||
{
|
||||
if (ScreenKeyboard::isActive())
|
||||
{
|
||||
if (ScreenKeyboard::getCurrent()->processEvent(widget->m_properties[PROP_ID]) == EVENT_BLOCK)
|
||||
if (ScreenKeyboard::getCurrent()->processEvent(widget->m_properties[PROP_ID]) != EVENT_LET)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -592,7 +653,7 @@ void EventHandler::sendEventToUser(GUIEngine::Widget* widget, std::string& name,
|
||||
|
||||
if (ModalDialog::isADialogActive())
|
||||
{
|
||||
if (ModalDialog::getCurrent()->processEvent(widget->m_properties[PROP_ID]) == EVENT_BLOCK)
|
||||
if (ModalDialog::getCurrent()->processEvent(widget->m_properties[PROP_ID]) != EVENT_LET)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -35,9 +35,18 @@ namespace GUIEngine
|
||||
enum EventPropagation
|
||||
{
|
||||
EVENT_BLOCK,
|
||||
EVENT_BLOCK_BUT_HANDLED,
|
||||
EVENT_LET
|
||||
};
|
||||
|
||||
enum NavigationDirection
|
||||
{
|
||||
NAV_LEFT,
|
||||
NAV_RIGHT,
|
||||
NAV_UP,
|
||||
NAV_DOWN
|
||||
};
|
||||
|
||||
class Widget;
|
||||
|
||||
/**
|
||||
@ -62,7 +71,10 @@ namespace GUIEngine
|
||||
|
||||
EventPropagation onGUIEvent(const irr::SEvent& event);
|
||||
EventPropagation onWidgetActivated(Widget* w, const int playerID);
|
||||
void navigate(const int playerID, Input::InputType type, const bool pressedDown, const bool reverse);
|
||||
void sendNavigationEvent(const NavigationDirection nav, const int playerID);
|
||||
void navigate(const NavigationDirection nav, const int playerID);
|
||||
int findIDClosestWidget(const NavigationDirection nav, const int playerID,
|
||||
Widget* w, bool ignore_disabled, int recursion_counter=1);
|
||||
|
||||
/** \brief send an event to the GUI module user's event callback
|
||||
* \param widget the widget that triggerred this event
|
||||
|
@ -189,6 +189,22 @@ namespace GUIEngine
|
||||
*/
|
||||
virtual EventPropagation leftPressed (const int playerID) { return EVENT_BLOCK; }
|
||||
|
||||
/**
|
||||
* called when up key is pressed and focus is on widget.
|
||||
* Returns 'EVENT_LET' if user's event handler should be notified of a change.
|
||||
* Override in children to be notified of up/down events and/or make
|
||||
* the event propagate to the user's event handler.
|
||||
*/
|
||||
virtual EventPropagation upPressed(const int playerID) { return EVENT_BLOCK; }
|
||||
|
||||
/**
|
||||
* called when down key is pressed and focus is on widget.
|
||||
* Returns 'EVENT_LET' if user's event handler should be notified of a change.
|
||||
* Override in children to be notified of up/down events and/or make
|
||||
* the event propagate to the user's event handler.
|
||||
*/
|
||||
virtual EventPropagation downPressed(const int playerID) { return EVENT_BLOCK; }
|
||||
|
||||
/** used when you set eventSupervisors - see m_event_handler explainations below
|
||||
called when one of a widget's children is hovered.
|
||||
\return 'EVENT_LET' if main event handler should be notified of a change, 'EVENT_BLOCK' otherwise */
|
||||
|
@ -487,6 +487,43 @@ EventPropagation ListWidget::transmitEvent(Widget* w,
|
||||
return EVENT_LET;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
EventPropagation ListWidget::upPressed(const int playerID)
|
||||
{
|
||||
return moveToNextItem(/*reverse*/ true);
|
||||
} // upPressed
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
EventPropagation ListWidget::downPressed(const int playerID)
|
||||
{
|
||||
return moveToNextItem(/*reverse*/ false);
|
||||
} // downPressed
|
||||
|
||||
EventPropagation ListWidget::moveToNextItem(const bool reverse)
|
||||
{
|
||||
// if widget is deactivated, do nothing
|
||||
if (m_deactivated) return EVENT_BLOCK;
|
||||
|
||||
const bool stay_within_list = reverse ? getSelectionID() > 0 :
|
||||
getSelectionID() < getItemCount() - 1;
|
||||
|
||||
if (stay_within_list)
|
||||
{
|
||||
if (reverse)
|
||||
setSelectionID(getSelectionID() - 1);
|
||||
else
|
||||
setSelectionID(getSelectionID() + 1);
|
||||
return EVENT_BLOCK_BUT_HANDLED;
|
||||
}
|
||||
else
|
||||
{
|
||||
setSelectionID(-1); // select nothing
|
||||
}
|
||||
return EVENT_BLOCK;
|
||||
} // moveToNextItem
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
int ListWidget::getItemID(const std::string &internalName) const
|
||||
{
|
||||
|
@ -246,6 +246,15 @@ namespace GUIEngine
|
||||
virtual EventPropagation transmitEvent(Widget* w,
|
||||
const std::string& originator,
|
||||
const int playerID);
|
||||
|
||||
/** \brief implementing method from base class Widget */
|
||||
virtual EventPropagation upPressed(const int playerID);
|
||||
|
||||
/** \brief implementing method from base class Widget */
|
||||
virtual EventPropagation downPressed(const int playerID);
|
||||
|
||||
/** \brief implement common core parts of upPressed and downPressed */
|
||||
EventPropagation moveToNextItem(const bool down);
|
||||
|
||||
void setColumnListener(IListWidgetHeaderListener* listener)
|
||||
{
|
||||
|
@ -460,7 +460,7 @@ void RibbonWidget::select(std::string item, const int mousePlayerID)
|
||||
// ----------------------------------------------------------------------------
|
||||
EventPropagation RibbonWidget::rightPressed(const int playerID)
|
||||
{
|
||||
EventPropagation result = m_ribbon_type != RIBBON_TOOLBAR ? EVENT_LET : EVENT_BLOCK;
|
||||
EventPropagation result = m_ribbon_type != RIBBON_TOOLBAR ? EVENT_LET : EVENT_BLOCK_BUT_HANDLED;
|
||||
|
||||
if (m_deactivated) return result;
|
||||
// empty ribbon, or only one item (can't move right)
|
||||
@ -500,7 +500,7 @@ EventPropagation RibbonWidget::rightPressed(const int playerID)
|
||||
// ----------------------------------------------------------------------------
|
||||
EventPropagation RibbonWidget::leftPressed(const int playerID)
|
||||
{
|
||||
EventPropagation result = m_ribbon_type != RIBBON_TOOLBAR ? EVENT_LET : EVENT_BLOCK;
|
||||
EventPropagation result = m_ribbon_type != RIBBON_TOOLBAR ? EVENT_LET : EVENT_BLOCK_BUT_HANDLED;
|
||||
|
||||
if (m_deactivated) return result;
|
||||
// empty ribbon, or only one item (can't move left)
|
||||
|
Loading…
Reference in New Issue
Block a user