Draggable Panel

Started by Strikerklm96, 08 April 2014, 00:33:39

Strikerklm96

I know that a Child Window can be dragged around with the top part, but I was hoping to get a panel like object, could be a window, that can be dragged around by clicking and dragging any part of the panel that isn't another object. Is something like that available or do I need to implement that myself?

texus

This is not yet available, you will have to implement this yourself.

The simpelest way to check if the mouse is on top of the panel and not on top of any of its widgets is with the mouseOnWidget function:

// Do this when the mouse goes down
if (panel->mouseOnWidget(x, y)
{
    bool mouseOnAnyWidget = false;
    for (auto& widget : panel->getWidgets())
    {
        if (widget->mouseOnWidget(x - panel->getPosition(), y - panel->getPosition()))
        {
            mouseOnAnyWidget = true;
            break;
        }
    }

    // If mouseOnAnyWidget is false here then you should start dragging the panel, until you get a mouse released event
}


This code has a few limitations:
- There should not be an overlapping widget in front of the panel
- The widgets inside the panel should not be overlapping
- Invisible and disabled widgets will react on this code

If you need to work around the second or third limitation (if you use invisisible or disabled widgets, or if widgets in your panel overlap) then just have a look at this code: https://github.com/texus/TGUI/blob/master/src/TGUI/Container.cpp#L1267 (the function is private, but you can almost literaly copy the code.

Strikerklm96

#2
The reason I'm asking is that for a game I wanted to have HUD contents, like health, inventory ect, that can be moved around the screen during the game(for convenience), if the player presses a button, then they go into the moving mode, so they can move the containers of these objects.

1. First idea was panels, but they can't be moved around without me doing stuff with them, and I'd rather just do solution 4 than have to deal with panels that overlap, are invisible, and or are disabled.
2. Second idea was child windows, but they have a top tab that is always showing and would look bad in game.
3. Make child windows transparent and disabled when not in moving mode. Unfortunately when transparent, the child window (logically) makes its contents transparent too.
4. Have them only be able to edit the positions of the panels when not in game, in the game's options.

Are there options I didn't see? If not that's fine, just want to make sure.

texus

QuoteAre there options I didn't see?
I don't see another option either.

Quote3. Make child windows transparent and disabled when not in moving mode. Unfortunately when transparent, the child window (logically) makes its contents transparent too.
There are of course workarounds to avoid making the whole contents transparent (without having to reset the transparency of every single widget in the window). You could have a panel inside the child window and all widgets inside the panel. Then after setting transparency of the child window to 0, you just set the panel to transparency 255 again.
But disabling the child window will also disable the widgets inside it, so this might not even be an option.

Quote1. First idea was panels, but they can't be moved around without me doing stuff with them, and I'd rather just do solution 4 than have to deal with panels that overlap, are invisible, and or are disabled.
If you would use a slightly edited version of the code in Container::mouseOnWhichWidget then it shouldn't be much of a problem. But unlike my suggestion before which didn't handle overlapping, you have to call the code twice: once to find out on which panel the mouse is on, and then on the widgets inside the panel to check if the mouse is on them. The code in that function also takes care of ignoring the invisible and disabled widgets.

But whatever way you choose, it will always be kindof a hack.

texus

As said, option 1 isn't that hard:
#include <TGUI/TGUI.hpp>

///////////////////////////////////////////////////////////////////////

tgui::Widget::Ptr mouseOnWhichWidget(float x, float y, std::vector<tgui::Widget::Ptr>& widgets)
{
    bool widgetFound = false;
    tgui::Widget::Ptr widget = nullptr;

    // Loop through all widgets
    for (std::vector<tgui::Widget::Ptr>::reverse_iterator it = widgets.rbegin(); it != widgets.rend(); ++it)
    {
        // Check if the widget is visible and enabled
        if (((*it)->isVisible()) && ((*it)->isEnabled()))
        {
            if (widgetFound == false)
            {
                // Return the widget if the mouse is on top of it
                if ((*it)->mouseOnWidget(x, y))
                {
                    widget = *it;
                    widgetFound = true;
                }
            }
            else // The widget was already found, so tell the other widgets that the mouse can't be on them
                (*it)->mouseNotOnWidget();
        }
    }

    return widget;
}

///////////////////////////////////////////////////////////////////////

int main()
{
    sf::RenderWindow window(sf::VideoMode(930, 700), "TGUI window");
    tgui::Gui gui(window);

    if (gui.setGlobalFont("TGUI/fonts/DejaVuSans.ttf") == false)
        return 1;

    tgui::Widget::Ptr draggingWidget = nullptr;
    sf::Vector2f draggingPosition;

    tgui::Panel::Ptr panel1(gui);
    tgui::Panel::Ptr panel2(gui);

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
            else
            {
                if (event.type == sf::Event::MouseButtonPressed)
                {
                    tgui::Widget::Ptr widget = mouseOnWhichWidget(event.mouseButton.x, event.mouseButton.y, gui.getWidgets());
                    if ((widget.get() == panel1.get()) || (widget.get() == panel2.get()))
                    {
                        if (mouseOnWhichWidget(event.mouseButton.x, event.mouseButton.y, tgui::Panel::Ptr(widget)->getWidgets()) == nullptr)
                        {
                            draggingWidget = widget;
                            draggingPosition = sf::Vector2f(event.mouseButton.x, event.mouseButton.y);
                        }
                    }
                }
                else if (event.type == sf::Event::MouseButtonReleased)
                {
                    draggingWidget = nullptr;
                }
                else if (event.type == sf::Event::MouseMoved)
                {
                    if (draggingWidget != nullptr)
                    {
                        draggingWidget->setPosition(draggingWidget->getPosition().x + event.mouseMove.x - draggingPosition.x,
                                                    draggingWidget->getPosition().y + event.mouseMove.y - draggingPosition.y);

                        draggingPosition = sf::Vector2f(event.mouseMove.x, event.mouseMove.y);
                    }
                }
            }

            gui.handleEvent(event);
        }

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

        sf::sleep(sf::milliseconds(1));
    }

    return 0;
}


While writing the code I even found a bug in tgui :). Trying to compare two widget pointers is not possible (function can't access private variable). That is why you have to use the '.get()' like in the above code.

Strikerklm96

#5
Awesome! And thanks for writing that code for me! :) Glad you found a bug before it caused any trouble!

I changed this part though, still look good?:
            if (rEvent.type == sf::Event::MouseButtonPressed)
            {
                tgui::Widget::Ptr widget = MouseOnWhichWidget(rEvent.mouseButton.x, rEvent.mouseButton.y, m_rGui.getWidgets());
                if(widget != nullptr)//======changed this
                {
                    if(widget->getWidgetType() == tgui::WidgetTypes::Type_Panel)//======and changed this
                    {
                        if(MouseOnWhichWidget(rEvent.mouseButton.x, rEvent.mouseButton.y, tgui::Panel::Ptr(widget)->getWidgets()) == nullptr)
                        {
                            m_pDraggingWidget = widget;
                            m_pDraggingPosition = sf::Vector2f(rEvent.mouseButton.x, rEvent.mouseButton.y);
                        }
                    }
                }
            }


Also I don't understand part of this, I commented the part:
tgui::Widget::Ptr MouseOnWhichWidget(float x, float y, std::vector<tgui::Widget::Ptr>& widgets)
{
    bool widgetFound = false;
    tgui::Widget::Ptr widget = nullptr;
    // Loop through all widgets
    for (std::vector<tgui::Widget::Ptr>::reverse_iterator it = widgets.rbegin(); it != widgets.rend(); ++it)
    {
        if (((*it)->isVisible()) && ((*it)->isEnabled()))
        {
            if (widgetFound == false)//  <---why have this? read more down
            {
                if ((*it)->mouseOnWidget(x, y))
                {
                    widget = *it;
                    widgetFound = true;// <---since we already found a widget, why keep going, why not break out of loop; ?
                }
            }
            else
                (*it)->mouseNotOnWidget();
        }
    }
    return widget;
};

Strikerklm96

tgui::Widget::Ptr MouseOnWhichWidget(float x, float y, std::vector<tgui::Widget::Ptr>& widgets)
{
    for (std::vector<tgui::Widget::Ptr>::reverse_iterator it = widgets.rbegin(); it != widgets.rend(); ++it)
        if (((*it)->isVisible()) && ((*it)->isEnabled()) && ((*it)->mouseOnWidget(x, y)))
            return *it;

    return nullptr;
};

texus

#7
QuoteI changed this part though, still look good?
Yep, that still looks good.

Quote// <---since we already found a widget, why keep going, why not break out of loop; ?
As you can see, when the boolean is false, the mouseNotOnWidget() function is called.
Imagine the following situation:
You move the mouse on the background picture, and then continue to move the mouse over a button (which is on top of the picture). Since we are looping over the widgets backwards, we will find the button. But the picture will never be told that the mouse is no longer on top of it. Hence once we find the widget, we tell all remaining ones that the mouse can't be on them.

Edit: I though about it a bit longer and you can indeed use the shorter code. Its only inside tgui that the longer version is required.

Strikerklm96

Works perfectly! Here it is if you want to see it.

            if (rEvent.key.code == sf::Keyboard::F1)
            {
                m_pCPT->setState(PlayerState::Editing);
            }

// somewhere else in the code:

else if(m_pCPT->getState() == PlayerState::Editing)
        {
            if (rEvent.type == sf::Event::MouseButtonPressed)
            {
                tgui::Widget::Ptr widget = f_MouseOnWhichWidget(rEvent.mouseButton.x, rEvent.mouseButton.y, m_rGui.getWidgets());
                if(widget != nullptr)
                {
                    if(widget->getWidgetType() == tgui::WidgetTypes::Type_Panel)
                    {
                        if(f_MouseOnWhichWidget(rEvent.mouseButton.x, rEvent.mouseButton.y, tgui::Panel::Ptr(widget)->getWidgets()) == nullptr)
                        {
                            m_pDraggingWidget = widget;
                            m_pDraggingPosition = sf::Vector2f(rEvent.mouseButton.x, rEvent.mouseButton.y);
                        }
                    }
                }
            }
            else if (rEvent.type == sf::Event::MouseButtonReleased)
            {
                m_pDraggingWidget = nullptr;
            }
            else if (rEvent.type == sf::Event::MouseMoved)
            {
                if (m_pDraggingWidget != nullptr)
                {
                    m_pDraggingWidget->setPosition(m_pDraggingWidget->getPosition().x + rEvent.mouseMove.x - m_pDraggingPosition.x,
                                                   m_pDraggingWidget->getPosition().y + rEvent.mouseMove.y - m_pDraggingPosition.y);

                    m_pDraggingPosition = sf::Vector2f(rEvent.mouseMove.x, rEvent.mouseMove.y);
                }
            }
        }