Possible bug with layouts and panels

Started by Dincio, 08 March 2017, 00:09:05

Dincio

I've been playing around with layouts and I've found a possible bug. First, I compiled this code (and it worked as expected, drawing both the panel and the text box on the window in the right positions):

Code (cpp) Select

/*! constructor inside a class that handles a text based interface */
mDialogInterface = std::make_shared<tgui::Panel>();
mDialogInterface->setPosition(0, mWindow->getSize().y / 4.f * 3.f);
mDialogInterface->setSize(mWindow->getSize().x, mWindow->getSize().y / 4.f);
mDialogInterface->setBackgroundColor(sf::Color::Red);
auto dialogBox = std::make_shared<tgui::TextBox>();
dialogBox->setText("hello there!");
dialogBox->setSize("parent.width / 4 * 3", "parent.height");
dialogBox->setPosition("parent.left", "parent.top");
mDialogInterface->add(dialogBox, "dialog");


Code (cpp) Select

/*! function that handles drawing (from the same class) */
void *::handleDrawing()
{
    ... //draw other stuff using views
    mWindow->setView(mWindow->getDefaultView());
    mWindow->draw(*mDialogInterface);
}


outcome shown in "right.png"


The problem is that when I change the eighth line of the first segment of code from this:
Code (cpp) Select
dialogBox->setPosition("parent.left", "parent.top");
to this:
Code (cpp) Select
dialogBox->setPosition("parent.width / 4", "parent.top");
which is what I want to do, but really I could change i 8)t to whatever inolves any type of calculation, like:
Code (cpp) Select
dialogBox->setPosition("parent.left + 1", "parent.top");
and still the problem will persist. The problem is that the text box becomes invisible (outcome shown in "wrong.png").

The only way I can seem to solve the issue is by drawing the textbox directly to the window, but this seems just some kind of workaround... so is this a bug or am I doing something wrong?

Also, as a side note, I've tryed receiving input from the panel via panel->handleInput(const sf::Event& event) but the compiler tells me that the function is protected. Is there any other way of doing this without relying on guis (aka, in my case, using panels)?

texus

That is indeed a bug in my layout system, but apparently not how you are expecting it. It turns out that the behavior of "parent.left + 1" is more correct than the one for "parent.left". Positions are already relative to the parent, so to put a widget in the top left corner of its parent you must give it position (0,0). You are placing your dialogBox far below the screen in your code.

QuoteAlso, as a side note, I've tryed receiving input from the panel via panel->handleInput(const sf::Event& event) but the compiler tells me that the function is protected. Is there any other way of doing this without relying on guis (aka, in my case, using panels)?
Apparently not. It seems like you will have to use a Gui if you need to handle events.

Dincio

#2
So the bug is that when I use parent.left alone it uses absolute position, while when I add some calculations in it uses relative position?

Dincio

#3
In this case (and pardon my double post) how would I be able to implement what I had in mind in the first place? That is: having the textbox's x coordinate be exactly one fourth of its parent's width.

texus

The bug is that somehow the expression is not being evaluated when there is no expression. When mWindow->getSize().y is 600 then mDialogInterface->getPosition().y will be 450 and thus parent.top should be 450. The bug is that it is 0. When you put an expression into the x position then it suddenly gets interpreted correctly and the dialogBox is placed on y position 450 instead of y position 0.

What you need is the following:
Code (cpp) Select
dialogBox->setSize("parent.width / 4", 0);

Dincio

Ok I understand the bug now, thanks for the quick reply :)

Yes I already resized the textbox that, but what I want to do is to keep the starting position of the text box (its top-left corner) exactly one fourth of the panel's length into the panel. I'll try to draw it to make myself clear. The expression I would iintuitively use to accomplish this would be:
Code (cpp) Select
dialogBox->setPosition("parent.width / 4", "parent.top");
or
Code (cpp) Select
dialogBox->setPosition(bindWidth(mDialogInterface) / 4.f, bindTop(mDialogInterface));
but neither seem to work... even though they should, even with the bug in place (or maybe I still haven't understood everything...)

texus

I just figured out that the bug isn't even in the layout system. It is specific to TextBox (and maybe a few other classes with similar code). It's basically a caching issue.

I told you what to write in my last post: you don't need "parent.top", you just need a 0.
If your panel is located at position (10, 20) and the widget inside the panel is placed at position (30, 40) then on the screen the widget will appear at pixel (40, 60). All positions are relative to the parent, that is why the top of your text box should be 0.

Dincio

oh yes sorry I understand now  ;D I even read it but just didn't notice the 0... ok everything seems to work now, thanks again.

About the bug; is it fixable?

Dincio

#8
The bug seems to persist even when calculations are involved... I did as you said, writing the code:
Code (cpp) Select
dialogBox->setPosition("parent.width / 4", 0);
but the textbox was drawn at position (0, 0) (relative to the parent) instead of (parent.width / 4, 0) as I wanted.

and also, being the expression parent.top - without any calculation - evaluated as 0 relative to the parent and not parent.top relative to the parent (because of the bug), isn't setPosition("parent.width / 4", "parent.top") also correct?

texus

I'll make sure it gets fixed in the next hour. You will have to rebuild tgui yourself afterward though.

A workaround for now is to call dialogBox->setPosition(1,1) before calling dialogBox->setPosition("parent.width / 4", 0).

Dincio

#10
Ok thanks for everything!  ;D

I'll rebuild it as soon as I can, for now I'll just use the workaround, which works perfectly.

Dincio

Also, as a side note, I needed to set the load an external font myself to make the textbox work... I don't know if this is normal or if the textbox is suposed to have a default font active when no font was selected.

texus

The fix has been made in the master branch on github (download zip).

QuoteAlso, as a side note, I needed to set the load an external font myself to make the textbox work... I don't know if this is normal or if the textbox is suposed to have a default font active when no font was selected.
Looks like you found another reason on why the Gui class is useful. All widgets added to the gui come with a build-in font.

You don't have to manually set the font for each widget btw, it gets inherited from the parent. So if you set the font of a panel then all widgets inside that panel will get that font as well.

Dincio

#13
Ok got it... it's just a shame that a Gui cannot be copied, but I'll have to make do :)

texus

I still don't see what you gain from copying it. You would save one line of code as you wouldn't have to call gui.setWindow again, but that's about it.

Dincio

#15
I need a system in which every instance of type entity in my game holds its own gui (I need to do this for multiple reasons: the gui is relative to the position of the entity; shown only when the entity is visible; clickable only when the entity is interactable, etc...). I also use a "model/clone" system in which I load entity models from a file and then clone them when they are spawned, ergo the need to copy their guis...

texus

Having a gui in each entity misses the point of what the gui is about. You should be creating panels inside the entities, not guis. You should store a single Gui instance in a similar way that you are storing a single RenderWindow and that you try to attach the panels to the guis in a similar way as how you manage to "connect" (handle events and draw) your entities to the window.

I was going to make the Gui copyable to not arbitrary limit you but I just found a reason on why I shouldn't make them copyable that I think is good enough and probably was the reason why I designed the code like this in the first place. If the gui is used like intended then making it copyable allows some very hard to spot issues when your code is wrong. If you forget to pass the object by reference and make a copy instead then your code would no longer work as intended. Calls to e.g. handleEvent would be done on the wrong gui. These are difficult issues to find because stuff just doesn't work in complex code but everything is fine in any minimal code. I vaguely remember such an issue.

I also can't make the handleEvent function from Panel public because it looks like it is the Gui which maps events from window to world view. This code can't be moved to Panel because it must only be done once on a place that can't occur inside the hierarchy (Panels could be nested and thus the code would be run multiple times).

TGUI is not made for what you intend to do. The code is designed to have a single Gui where interaction is handled. It is made for menu screens and static HUD, not for being part of your entities. I'm afraid you will either have to adapt your code so that it works with a single gui or else simply not rely on TGUI to do these things.

Dincio

Ok you were completely clear, thanks again for the attention you dedicated to solving my issue. I will think of a way to handle everything based on your statements  :)