Pixel and % positions in widgets

luis

13-11-2016 11:36:36

Hi!
I've been MyGUI 3.2.0 for a year now (with Ogre 1.9), I'm really happy with it and as a long time Ogre user I think it is one of the best GUI solutions because of the code, features and most important: Good Layout editor supplied.

But, I'm having problems with widget positions with the Layout Editor. When I switch from pixel to relative (%) there is always a small error in the conversion. Since I'm doing a multi-resolution game, I'm always using relative positions on widgets, the problem is, even if I select a fixed "grid step" in settings and if I save and load again the .layout, there is an error. The same happens as I said before, if I switch from pixel to % (or the other way around) and move/resize the widget (text box, canvas, etc).

I'm trying align thumbnails in a canvas and some text lists/columns, and every time I change my layout there is always a visible error in the layout....this is a real PITA. Is it solved in latest version ? is this bug the first time reported ?

BTW: I've compiled MyGUI & Ogre with floats but the error seems to be really higher than float precision.

Thanks :)

Altren

13-11-2016 19:37:02

% positions are poorly implemented in LayoutEditor and not used much in MyGUI.
The problem is that positions are always "rounded" to pixels in LayoutEditor, so when you set some position in percents it is converted into pixels and then back into percents during same.
Few people use relative positions in LE, so this wasn't reported before.

luis

13-11-2016 20:51:05

Thanks for replying Altren. If you work in pixels how do you solve different resolutions ? I know relative position/size isn't bullet proof but it works better. In fact, the only thing that doesn't resize well are fonts but fonts sizes can be set during runtime.
If you think there is a way to make a layout that will work from 1280x800 to 1920x1200 or even higher please let me know.

Where is the function that rounds pixels ? I could make a hack, for example, alway store positions in pixels and show/save them in %, so at least in the same session with the LE I won't be loosing so much precision.

Are you sure that the rounding function is well implemented ? I know that one of the problems with floats is error propagation (which is different than just loosing precision) but I'm seeing things like: 40 pixels after converting once to % turns into 38 pixel. That is a really high relative error, 38/40 is an error of -5% it is crazy high.......

Altren

14-11-2016 08:07:54

Where is the function that rounds pixels ? I could make a hack, for example, alway store positions in pixels and show/save them in %, so at least in the same session with the LE I won't be loosing so much precision.

Are you sure that the rounding function is well implemented ? I know that one of the problems with floats is error propagation (which is different than just loosing precision) but I'm seeing things like: 40 pixels after converting once to % turns into 38 pixel. That is a really high relative error, 38/40 is an error of -5% it is crazy high.......
There is no rounding function as is. The problem is that in layout editor only "isRelative" flag is saved, while widgets positions are stored in a real widget and MyGUI's widgets use ints internally. So proper fix would be saving user inserted position directly instead of saving only flag.

luis

14-11-2016 09:55:35

I see... I've done a quick look in the code!

As you said the only way should be save it using pixels or, correct me if I'm wrong.. fix the way you're saving & loading values:

WidgetContainer.cpp I think it is called right before saving .layout file

std::string WidgetContainer::position(bool _percent)
{
if (mRelativeMode)
{
MyGUI::FloatCoord coord = MyGUI::CoordConverter::convertToRelative(mWidget->getCoord(), mWidget->getParentSize());
std::ostringstream stream;
if (_percent)
stream << coord.left * 100 << " " << coord.top * 100 << " " << coord.width * 100 << " " << coord.height * 100;
else
stream << coord.left << " " << coord.top << " " << coord.width << " " << coord.height;
return stream.str();
}
return mWidget->getCoord().print();
}


PropertyFieldPosition.cpp Then you load the values with this function:
void PropertyFieldPosition::onAction(const std::string& _value, bool _force)
{
EditorWidgets* ew = &EditorWidgets::getInstance();
WidgetContainer* widgetContainer = ew->find(mCurrentWidget);

bool goodData = onCheckValue();

if (goodData)
{
if (widgetContainer->getRelativeMode())
{
std::istringstream str(_value);
MyGUI::FloatCoord float_coord;
str >> float_coord;
float_coord.left = float_coord.left / 100;
float_coord.top = float_coord.top / 100;
float_coord.width = float_coord.width / 100;
float_coord.height = float_coord.height / 100;
MyGUI::IntCoord coord = MyGUI::CoordConverter::convertFromRelative(float_coord, mCurrentWidget->getParentSize());

mCurrentWidget->setCoord(coord);
EditorWidgets::getInstance().onSetWidgetCoord(mCurrentWidget, mCurrentWidget->getAbsoluteCoord(), "PropertiesPanelView");
}
else
{
widgetContainer->getWidget()->setProperty("Coord", _value);
EditorWidgets::getInstance().onSetWidgetCoord(mCurrentWidget, mCurrentWidget->getAbsoluteCoord(), "PropertiesPanelView");
}
}
}


On the save function you multiply by 100 (loosing 2 digits) and the values looks like:
<Widget type="Canvas" skin="Canvas" position_real="0.177778 0.144144 0.777778 0.111111">
<Widget type="ImageBox" skin="ImageBox" position_real="0 0.027027 0.171429 0.675676">


Wich is strange since you will probably loose accuracy, AFAIK a float has 7 mantissa values so it is not the same:
0.177778 than 1.77778XX e-1 you're loosing values here. Why are you multiplying by 100 and then dividing by 100 in the load ? I would let it "normalized" in range [0,1].

Please let me know if I'm on the right way, I don't know much about MyGUI code... May be the easiest solution would be be increasing precision in std::ostringstream ? or creating a MyGUI::DoubleCoord instead of MyGUI::FloatCoord ?

luis

14-11-2016 10:57:39

It is fixed now ! :)

The function in PropertyFieldPosition.cpp is used only when the user writes a string in the position input box in the LE right ? The real method called when a .layout is loaded is EditorWidgets::parseWidget !

I've made a double version for conversions, increased precision, added scientific notation and changed int constants to double just in case, there are my changes:

WidgetContainer.cpp
std::string WidgetContainer::position(bool _percent)
{
if (mRelativeMode)
{
//MyGUI::FloatCoord coord = MyGUI::CoordConverter::convertToRelative(mWidget->getCoord(), mWidget->getParentSize());
MyGUI::DoubleCoord coord = MyGUI::CoordConverter::convertToRelativeD(mWidget->getCoord(), mWidget->getParentSize());
std::ostringstream stream;
stream.precision(17);
stream << std::scientific;
if (_percent)
stream << coord.left * 100.0 << " " << coord.top * 100.0 << " " << coord.width * 100.0 << " " << coord.height * 100.0;
else
stream << coord.left << " " << coord.top << " " << coord.width << " " << coord.height;
return stream.str();
}
return mWidget->getCoord().print();
}


MyGUI_Types.h
typedef types::TCoord<double> DoubleCoord;

PropertyFieldPosition.cpp
void PropertyFieldPosition::onAction(const std::string& _value, bool _force)
{
EditorWidgets* ew = &EditorWidgets::getInstance();
WidgetContainer* widgetContainer = ew->find(mCurrentWidget);

bool goodData = onCheckValue();

if (goodData)
{
if (widgetContainer->getRelativeMode())
{
std::istringstream str(_value);
MyGUI::DoubleCoord double_coord;
str >> double_coord;
double_coord.left = double_coord.left / 100.0;
double_coord.top = double_coord.top / 100.0;
double_coord.width = double_coord.width / 100.0;
double_coord.height = double_coord.height / 100.0;
MyGUI::IntCoord coord = MyGUI::CoordConverter::convertFromRelative(double_coord, mCurrentWidget->getParentSize());

mCurrentWidget->setCoord(coord);
EditorWidgets::getInstance().onSetWidgetCoord(mCurrentWidget, mCurrentWidget->getAbsoluteCoord(), "PropertiesPanelView");
}
else
{
widgetContainer->getWidget()->setProperty("Coord", _value);
EditorWidgets::getInstance().onSetWidgetCoord(mCurrentWidget, mCurrentWidget->getAbsoluteCoord(), "PropertiesPanelView");
}
}
}


MyGUI_CoordConverter.h
static IntCoord convertFromRelative(const DoubleCoord& _coord, const IntSize& _view)
{
return IntCoord(int(_coord.left * _view.width), int(_coord.top * _view.height), int(_coord.width * _view.width), int(_coord.height * _view.height));
}

static DoubleCoord convertToRelativeD(const IntCoord& _coord, const IntSize& _view)
{
return DoubleCoord(_coord.left / (double)_view.width, _coord.top / (double)_view.height, _coord.width / (double)_view.width, _coord.height / (float)_view.height);
}


void EditorWidgets::parseWidget(MyGUI::xml::ElementEnumerator& _widget, MyGUI::Widget* _parent, bool _testMode)
if (_widget->findAttribute("position_real", position))
{
container->setRelativeMode(true);
SettingsSector* sector = SettingsManager::getInstance().getSector("Workspace");
MyGUI::IntSize size = _testMode ? MyGUI::RenderManager::getInstance().getViewSize() : sector->getPropertyValue<MyGUI::IntSize>("TextureSize");
coord = MyGUI::CoordConverter::convertFromRelative(MyGUI::DoubleCoord::parse(position), _parent == nullptr ? size : _parent->getClientCoord().size());
}



I hope you will add this change in the next version. I'm the one that wrote you in mygui.info asking for a method returning a list with available languages do you remember ?? thank you

Altren

14-11-2016 23:26:05

Thank you for the fix. I pushed this into git after few minor simplifications.

luis

15-11-2016 08:40:56

Thank you Altren !
Once we reach v1.0 in my game I'll upgrade to latest MyGUI version ;)
You've done a very very good library !