Bug: mouse input for Overlapped non-root widgets

scrawl

11-08-2014 02:02:10

I'm trying to use style="Overlapped" for some widgets because I need precise control over their drawing order. For example, I have a Button that's supposed to be displayed on top of an ImageBox. The problem is, when I change the texture of the ImageBox, its LayerItem gets reattached and now it will draw on top of the button (probably a separate bug in itself).
The suggested fix is usually to make the ImageBox a parent of the Button, but there are some cases where you simply can't do that:
1. ImageBox would clip Button.
2. ImageBox is in a ScrollView, but the Button isn't.

So, back to Overlapped widgets, which is what I thought could fix it.

I set the Button style to Overlapped, and now it will always draw on top of the ImageBox, even if ImageBox texture changed. So far, so good.


<Widget type="ScrollView" skin="MW_MapView" position="0 0 284 264" align="Stretch" name="GlobalMap">
<Widget type="ImageBox" skin="ImageBox" position_real="0 0 1 1" align="Stretch" name="GlobalMapImage">
</Widget>
</Widget>

<!-- World button -->
<Widget type="AutoSizedButton" skin="MW_Button" style="Overlapped" position="213 233 61 22" align="Bottom Right" name="WorldButton">
<Property key="ExpandDirection" value="Left"/>
</Widget>


Problem: the Button doesn't properly receive mouse input anymore. Initially, I can see the mouse-over text effect. But once I click on it, the mouse-over effect disappears, the Button does not register the click and will no longer react to input.

I traced it to MyGUI_InputManager.cpp:
// поднимаем пикинг Overlapped окон
Widget* pick = mWidgetMouseFocus;
do
{
// если оверлаппед, то поднимаем пикинг
if (pick->getWidgetStyle() == WidgetStyle::Overlapped)
{
if (pick->getParent()) pick->getParent()->_forcePick(pick);
}

pick = pick->getParent();
}
while (pick);

This will call the _forcePick method for all Overlapped widget's parent. And what does _forcePick do?
void Widget::_forcePick(Widget* _widget)
{
MYGUI_ASSERT(mWidgetClient != this, "mWidgetClient can not be this widget");
if (mWidgetClient != nullptr)
mWidgetClient->_forcePick(_widget);

VectorWidgetPtr::iterator item = std::remove(mWidgetChild.begin(), mWidgetChild.end(), _widget);
if (item != mWidgetChild.end())
{
mWidgetChild.erase(item);
mWidgetChild.insert(mWidgetChild.begin(), _widget);
}
}

It will find the supplied widget in the mWidgetChild vector, and move it to the beginning. Now the widget will no longer receive input if there were widgets behind it, because Widget delegates mouse input to its children in reverse (see Widget::getLayerItemByPoint).
What's the point of this code? I can't figure it out. Was it supposed to move the widget to the end of the vector? That would make slightly more sense, but it's still broken: the widget would be moved to the top of the input stack, but it's LayerNode would not be moved to the top. Thus, there is a mismatch between which widget is rendered on top and which widget receives input.

I commented out the whole _forcePick method and that makes everything work properly, so I could really use some insight on what it's there for.