ComboBox and State pattern
« on: 12 March 2019, 15:10:31 »
Hi! Here is a little example of how I implement State pattern in my program to manage windows. When I click ComboBox item, contex changes its state from Awindow to Bwindow, which will draw loading animationw while loading some resources and return back to A state. The problem is that when I press mouse button, contex changes it's state to B immediadetly and A state does not have time to accept the mouseButtonReleased event. So when contex returned back to the state A, the ComboBox will think that I'm still holding mouse button. How can I solve this problem?
 class BaseWindow;
class myRWindow : public sf::RenderWindow
{
        BaseWindow * state;
public:
        void changeState(BaseWindow *st) { state = st; }
        void handleEvent(Event *event) { state->handleEvent(event); }
        void drawCurrentState() { state->draw(); };
};

class BaseWindow
{
public:
        virtual void draw() = 0;
        virtual void handleEvent(sf::Event *) = 0;
protected:
        myRWindow *contex;
};

class WindowStateA : public BaseWindow
{
private:
        tgui::Gui gui;

        void onComboBoxItemSelected(tgui::Widget::Ptr, const sf::String&)
        {
                /* --- */
                WindowStateB *windB = WindowStateB::getInstance();
                contex->changeState(windB);
                windB->doSomethingAndReturn(this);
        };
public:
        virtual void draw();
        virtual void handleEvent(sf::Event & ev) { gui.handleEvent(ev); };

};

class WindowStateB : public BaseWindow
{
public:
        void doSomethingAndReturn(BaseWindow * wst) // and return to window
        {
                auto contWind = contex;
                auto changeState = [contWind, wst]() {contWind->changeState(wst); };
                /* --- */
                // working in another thread while showong animation. After finishing work  changeState will be called
                /* --- */
        }

        virtual void draw();
        virtual void handleEvent(sf::Event & ev);

};
« Last Edit: 12 March 2019, 15:19:15 by Maksat »

*

texus

  • *****
  • 1365
    • View Profile
    • Texus's Blog
Re: ComboBox and State pattern
« Reply #1 on: 12 March 2019, 18:57:58 »
Are you using a separate Gui instance for A and B states?
If so then you should instead use the same Gui for both states and just put each state in a separate Group widget. When changing state you just have to show one group and hide the other.
Otherwise you should provide some simple example code that I can test here to get a better idea of what you are doing.

Re: ComboBox and State pattern
« Reply #2 on: 13 March 2019, 04:48:08 »
I don't use gui in B state. How can I use same gui for both states? Static gui object in BaseWindow? Is it ok to create static gui objects? As I know, nothing good wil come if you create static SFML objects.

Re: ComboBox and State pattern
« Reply #3 on: 13 March 2019, 06:09:51 »
Why not make an item selection and call a binded function after releasing the mouse button? For example, if you hold down the mouse button on the list of items and move the mouse over them, the items will be selected immediately after the mouse overlaps the element. This may not be convenient if it is binded to a heavy function, and you did not plan to select this element. Selecting an item after releasing the mouse button would solve this problem. And mine, by the way, too =)

here is example code
int main()
{
        sf::RenderWindow window(sf::VideoMode(800, 600), "TGUI window");
        window.setFramerateLimit(60);

        tgui::Gui gui(window);

        try
        {
                tgui::Theme theme{ "../themes/Black.txt" };

                gui.add(tgui::Picture::create("../RedBackground.jpg"));

                auto label = tgui::Label::create();
                label->setRenderer(theme.getRenderer("Label"));
                label->setText("Hold down the mouse button on list box and move the mouse over items");
                label->setPosition(70, 30);
                label->setTextSize(18);
                gui.add(label);

                label = tgui::Label::create();
                label->setRenderer(theme.getRenderer("Label"));
                label->setText("ComboBox");
                label->setPosition(150, 80);
                label->setTextSize(18);
                gui.add(label);
                       
                auto comboBox = tgui::ComboBox::create();
                comboBox->setRenderer(theme.getRenderer("ComboBox"));
                comboBox->setSize(200, 21);
                comboBox->setPosition(150, 100);
                comboBox->connect("ItemSelected", []() { cout << "Item selected" << endl; });
                comboBox->addItem("Item 1");
                comboBox->addItem("Item 2");
                comboBox->addItem("Item 3");
                comboBox->addItem("Item 4");
                comboBox->addItem("Item 5");
                comboBox->addItem("Item 6");
                comboBox->setSelectedItem("Item 2");
                gui.add(comboBox);


                label = tgui::Label::create();
                label->setRenderer(theme.getRenderer("Label"));
                label->setText("ComboBox with heavy functions");
                label->setPosition(450, 80);
                label->setTextSize(18);
                gui.add(label);

                comboBox = tgui::ComboBox::create();
                comboBox->setRenderer(theme.getRenderer("ComboBox"));
                comboBox->setSize(200, 21);
                comboBox->setPosition(450, 100);
                comboBox->connect("ItemSelected", []() {this_thread::sleep_for(500ms); cout << "hard work done" << endl; });
                comboBox->addItem("Item 1");
                comboBox->addItem("Item 2");
                comboBox->addItem("Item 3");
                comboBox->addItem("Item 4");
                comboBox->addItem("Item 5");
                comboBox->addItem("Item 6");
                comboBox->setSelectedItem("Item 2");
                gui.add(comboBox);

       
                auto button = tgui::Button::create();
                button->setRenderer(theme.getRenderer("Button"));
                button->setPosition(window.getSize().x - 115.f, window.getSize().y - 50.f);
                button->setText("Exit");
                button->setSize(100, 40);
                button->connect("pressed", [&]() { window.close(); });
                gui.add(button);
        }
        catch (const tgui::Exception& e)
        {
                std::cerr << "TGUI Exception: " << e.what() << std::endl;
                return EXIT_FAILURE;
        }

        while (window.isOpen())
        {
                sf::Event event;
                while (window.pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window.close();

                        gui.handleEvent(event);
                }

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

        return EXIT_SUCCESS;
}
 

*

texus

  • *****
  • 1365
    • View Profile
    • Texus's Blog
Re: ComboBox and State pattern
« Reply #4 on: 13 March 2019, 19:44:50 »
Using multiple Gui objects is not recommended at all, but I didn't fully understand what you meant and I was thinking the issue had to do with how events were sent.

I don't think it was possible yet to select a different item by holding the mouse in ListBox (which ComboBox uses internally) when the code was originally written like this in ComboBox. So this issue only appeared after an "unrelated" change in ListBox.

I've looked at how combo boxes work on my linux and a windows pc and they indeed only changes the value when the mouse is released. So I have updated my code to do the same. You can find the new version on github.
« Last Edit: 13 March 2019, 19:47:16 by texus »

Re: ComboBox and State pattern
« Reply #5 on: 14 March 2019, 09:44:02 »
Thank you!