Best practice for rendering specific widgets

Started by Hexade, 17 August 2019, 23:56:26

Hexade

Hi,

When drawing the GUI with draw() it draws all widgets stored within the GUI. At the moment my code looks like this:
current_state->render();
gui->render();


Where I have implemented a state machine so that each state has control over the sf::Texts and sf::Sprites being rendered. The issue that I am having is that I am rendering text that needs to go on top of a widget, but since I am rendering the GUI after the text, the widget is being drawn on top of the text preventing you from seeing it. I did get around this in an earlier project by simply creating a second GUI so that I could store the overlapping widgets within the second GUI and render that prior to rendering the text. However I have read in past forum posts that you probably shouldn't be creating multiple instances of tgui::Gui.

So, I am wondering what is the best way to go about controlling when specific widgets are rendered (like how I am controlling the text / sprites being rendered with the state machine) rather than having them all render at the same time. Thanks.

texus

Two simultaneous gui objects is definitely a bad idea as you could get 2 focused widgets and in case of overlapping widgets both guis may believe that the mouse is on their widget.
Rendering only some of the widgets is also not possible.

The only option you really have to mix tgui and sfml rendering is to use the Canvas widget. The canvas is a wrapper around sf::RenderTarget, so you have to clear() it then draw and then call display() on it. When calling gui.draw(), the sfml rendering you did on the canvas will be displayed inbetween the tgui widgets, depending on which widgets are in front and behind the canvas.

billarhos

QuoteRendering only some of the widgets is also not possible.


window.clear();
button->setVisible(false);
gui.draw();

//draw sfml stuff here

button->setVisible(true);
gui.draw();

//draw sfml stuff here

window.display();


Is this valid case?

texus

I never though of that option, but that would work too.

Hexade

I just tried to implement a tgui::Canvas however it looks as though it is made for use with SFML's sf::Drawable class. What I am trying to render is a tgui::Picture so a canvas won't work. Ideally I would be able to create some sort of group of widgets that is attached to the main tgui::Gui, and I would be able to call Gui::draw() on that group specifically so that those widgets are rendered at a different time to the rest of the GUI. Something like:

tgui::Gui gui;
gui.setTarget(window);

tgui::WidgetGroup group_1;
tgui::WidgetGroup group_2;
group_1.add();               // Add widgets to the group similarly to Gui::add()
group_2.add();

gui.draw(group_1);           // Draw the widgets that belong to the given group only
gui.draw(group_2);


The idea is that the groups would store different widgets and you can draw the groups at any time rather than having to draw all widgets at once with Gui::draw(). I am not sure if something like this already exists within TGUI so please point me in the right direction if there is. But this is basically what I am asking to do with the original question.

I have read billarhos' suggestion as well however I haven't tried it because I am assuming calling Gui::draw() twice is going to cause all the visible widgets to get rendered twice per frame which isn't ideal. Also the location where I am calling Gui::draw() is within the main program loop so it is not going to handle setting very specific widgets to visible/invisible since those widgets do not exist from the beginning of the program. I appreciate the suggestions though.


texus

QuoteWhat I am trying to render is a tgui::Picture
I'm a bit confused now. You said you wanted to render SFML inbetween the widgets (which is what the Canvas is for)? Why would drawing a tgui::Picture be a problem, it can be placed between any widgets already?

If you want to have states and show different widgets in different states (e.g. different screens), then you should just put the widgets of each screen in a tgui::Group and hide all groups except for the one you want the show. Then when changing screen, you hide the currently shown group and show a different one.

If within one screen you need to draw SFML inbetween TGUI then you either render the SFML on a Canvas and have that canvas drawn within the other widgets, or you do something like billarhos suggested: create 2 tgui::Group widgets, show the first and call gui.draw() then show the second one and call gui.draw() again. You must have access to the group in your drawing code because in your simplified example code you also had some parameter to pass to gui.draw.

Hexade

tgui::Group definitely seems to be more along the lines of what I am looking for. I have just tried quickly implementing it but I am getting some unusual behaviour that is probably caused by things unrelated to the tgui::Group. I'll play around with it for a while and get back to you if it works out or not. Thanks.

Hexade

Alright, after playing around with tgui::Group for a while it seemed as though Group::draw() is just drawing the stored widgets to the group rather than the window. And it is still the Gui::draw() function that controls when the widgets are actually drawn to the screen. So I created a small demo to test how tgui::Group works a bit and ended up with this:

#include "TGUI/TGUI.hpp"

int main() {

sf::RenderWindow window(sf::VideoMode(800, 600), "Group Test");
tgui::Gui gui(window);

tgui::Button::Ptr button_1 = tgui::Button::create("Button 1");
button_1->setPosition(5, 5);

tgui::Button::Ptr button_2 = tgui::Button::create("Button 2");
button_2->setPosition(5, 30);

tgui::Group::Ptr group = tgui::Group::create();
group->setSize(400, 300);
group->add(button_1);
group->add(button_2);

while (window.isOpen()) {

sf::Event event;

while (window.pollEvent(event)) {

switch (event.type) {

case sf::Event::Closed:
window.close();
break;

}

gui.handleEvent(event);

window.clear();

group->draw(window, sf::RenderStates::Default);
                        gui.draw();

window.display();

}

}

}


Now the main part that I'd like to point your attention to are these two lines:

group->draw(window, sf::RenderStates::Default);
gui.draw();


In their current order the buttons get rendered to the screen as you would expect, changing the order so that Gui::draw() is called before Group::draw() also causes them to be rendered as normal. Commenting out the call to Group::draw() will cause nothing to be drawn to the screen as expected. But if we comment out the call to Gui::draw() whilst leaving in the call to Group::draw(), it will render the buttons to the screen but they are squashed and somewhat distorted.

Based on my first tests when trying to implement tgui::Group I thought that the call to Group::draw() was just drawing the widgets to some texture that is stored by the Group. And that it is not rendered to the screen until we call Gui::draw(). However this little demo shows the buttons are being drawn to the screen by Group::draw(), however they are not being drawn correctly.

So now I am a bit confused as to what happens when we call Group::draw()? And just to be clear I get the squashed buttons when commenting out the Gui::draw() call like so:

group->draw(window, sf::RenderStates::Default);
//gui.draw();

texus

The Group::draw function is not supposed to be called directly. With the current design of the gui, you cannot draw widgets directly, all drawing has to happen via gui.draw().
What you are trying to do isn't supported, the gui always draws everything at once, the only solutions that I can provide are workarounds.

The code that you need looks more like this:
Code (cpp) Select
#include "TGUI/TGUI.hpp"

int main()
{
sf::RenderWindow window(sf::VideoMode(800, 600), "Group Test");
tgui::Gui gui(window);

tgui::Button::Ptr button_1 = tgui::Button::create("Button 1");
button_1->setPosition(5, 5);

tgui::Button::Ptr button_2 = tgui::Button::create("Button 2");
button_2->setPosition(5, 30);

tgui::Group::Ptr group = tgui::Group::create();
group->setSize(400, 300);
group->add(button_1);
gui.add(group);

        tgui::Group::Ptr group2 = tgui::Group::create();
group2->add(button_2);
gui.add(group2);

while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::Closed:
window.close();
break;
}

gui.handleEvent(event);
}

window.clear();

group->setVisible(true);
group2->setVisible(false);
gui.draw();

// SFML rendering here will be between group and group2

group->setVisible(false);
group2->setVisible(true);
gui.draw();

window.display();
}
}


Alternatively, using a canvas:
Code (cpp) Select
#include "TGUI/TGUI.hpp"

int main()
{
sf::RenderWindow window(sf::VideoMode(800, 600), "Group Test");
tgui::Gui gui(window);

tgui::Button::Ptr button_1 = tgui::Button::create("Button 1");
button_1->setPosition(5, 5);

tgui::Button::Ptr button_2 = tgui::Button::create("Button 2");
button_2->setPosition(5, 30);

tgui::Group::Ptr group = tgui::Group::create();
group->setSize(400, 300);
group->add(button_1);
gui.add(group);

// Warning: the default canvas size is (100%,100%) to fill the screen which can cause performance overhead when changing the gui view.
// Set a static size (by passing size to create function or calling setSize) if you don't want it to resize itself when resizing the gui.
tgui::Canvas canvas = tgui::Canvas::create();
        gui.add(canvas);

        tgui::Group::Ptr group2 = tgui::Group::create();
group2->add(button_2);
gui.add(group2);

while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::Closed:
window.close();
break;
}

gui.handleEvent(event);
}

// This clear/draw/display code can be called anywhere, if the contents is static you could even do it before the main loop
canvas->clear(sf::Color::Transparent);
// SFML rendering here will be between group and group2
canvas->display();

window.clear();
gui.draw();
window.display();
}
}

Hexade