2
\$\begingroup\$

How can I get clickable buttons working in SFML? I've failed figuring it out on my own. At first I tried just putting in text and then I tried an image for the Exit button. Neither of them work in the sf::Event::MouseMoved event. I want the exit button to highlight a different color when I mouse over it but it just flickers every time the mouse moves whether its in the coordinates or not. Also, doing a sf::FloatRect = exitButtonImage.getRect(); seems to not work either.

#include <SFML\Graphics.hpp>
#include <SFML\Audio.hpp>
#include <string>
#include <iostream> 


int main()
{
    sf::RenderWindow window(sf::VideoMode(1024, 622), "Survival Game", sf::Style::Default);

    sf::Texture background;
    sf::Sprite backgroundImage;

    if (!background.loadFromFile("Alaskan Wilderness.png"))
        std::cout << "Error: Could not display Alaskan Wilderness image" << std::endl;

    backgroundImage.setTexture(background);

    while (window.isOpen())
    {
        sf::Font font;
        if (!font.loadFromFile("MATURASC.TTF"))
            std::cout << "Can't find the font file" << std::endl;

        sf::Texture exitButton;
        sf::Sprite exitButtonImage;
        if (!exitButton.loadFromFile("Exit Button.png"))
            std::cout << "Can't find the image" << std::endl;
        exitButtonImage.setPosition(50.0f, 500.0f);

        float exitButtonWidth = exitButtonImage.getLocalBounds().width;
        float exitButtonHeight = exitButtonImage.getLocalBounds().height;

        exitButtonImage.setTexture(exitButton);

        sf::Text startText;
            startText.setFont(font);
            startText.setStyle(sf::Text::Bold);
            startText.setString("Create New Game");
            startText.setFillColor(sf::Color::Black);
            startText.setCharacterSize(48);
            startText.setPosition(50.0f, 320.0f);
        sf::Text loadText;
            loadText.setFont(font);
            loadText.setStyle(sf::Text::Bold);
            loadText.setString("Load Game");
            loadText.setFillColor(sf::Color::Black);
            loadText.setCharacterSize(48);
            loadText.setPosition(50.0f, 380.0f);
        sf::Text settingsText;
            settingsText.setFont(font);
            settingsText.setStyle(sf::Text::Bold);
            settingsText.setString("Settings");
            settingsText.setFillColor(sf::Color::Black);
            settingsText.setCharacterSize(48);
            settingsText.setPosition(50.0f, 440.0f);

        sf::Event Event;
        while (window.pollEvent(Event))
        {
            switch (Event.type)
            {
            case sf::Event::Closed:
                window.close();
                break;
            case sf::Event::MouseMoved:
                sf::Vector2i mousePos = sf::Mouse::getPosition(window);
                bool mousePositionExit = mousePos.x == exitButtonWidth && mousePos.y == exitButtonHeight;
                if (sf::Mouse::getPosition(window).x > exitButtonImage.getGlobalBounds().left &&
                    sf::Mouse::getPosition(window).x < exitButtonImage.getGlobalBounds().left +
                    exitButtonImage.getGlobalBounds().width && sf::Mouse::getPosition().y >
                    exitButtonImage.getGlobalBounds().top && sf::Mouse::getPosition(window).y
                    < (exitButtonImage.getGlobalBounds().top + exitButtonImage.getGlobalBounds().height));
                {
                    exitButtonImage.setColor(sf::Color(250, 20, 20));
                    window.draw(exitButtonImage);
                }
                break;
            }
        }

        window.clear();
        window.draw(backgroundImage);
        window.draw(exitButtonImage);
        window.draw(startText);
        window.draw(loadText);
        window.draw(settingsText);
        window.display();
    }
    return 0;
}
\$\endgroup\$
4
\$\begingroup\$

The first thing to note, is that you have a ; after an if statement, before the code for the true condition. This means that the if line is useless, and that code for the true condition will always be executed.

Removing that semi-column will be our first step.

The second thing to note is that you don't need all that code for the if condition. SFML offers a contains method on the rectangle which returns true if the vector passed is in the rectangle. This really reduces the code and improves readability and reliability.

The third thing to note is that the window.draw( exitButtonImage ); is useless in that if( isOver ) condition because you'll draw it anyway at the end of the while.

The fourth thing to note is that, with the previous changes, the button will become 'red' only when you actually move the mouse over it. We need a way to preserve the state. This is nice because...

The fifth thing to note is that the code is doing a very bad usage of memory allocation and disk access. The creation of your buttons could be done only once, with the objects reused every frame, instead of being recreated every frame.

We'll take the text and image creation outside of the while ( window.isOpen() ). This will allow us to keep the state of the exitButtonImage from frame to frame. If the mouse moves over the exit button, it will set it to 'red', and if we stop moving the mouse, no more mouse event are received, so the last state stays in place. Nice, our button stays put!

But that causes another bug: the button does not get back to its 'pristine' state when we mouse-out.

To fix that, we simply add an else to the 'mouse move and mouse is over the button' if: exitButtonImage.setColor( sf::Color( 255, 255, 255 ) );. This will put the button in it's original state.

With that, you only need to detect the 'click' event, and you'll have something usable.

Note that this is a bit of a hackish way of doing things as you'll change the colour of the button every frame the mouse moves, whether you actually need to change it or not, which may take up more resources than needed. I think in your situation it's fine to go that way, though; you can ignore this small issue for now.

That's basically what I had to say about your code :)


For your convenience, here is the code I think you wanted to write:

#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <string>
#include <iostream> 

int main()
{
  sf::RenderWindow window( sf::VideoMode( 1024, 622 ), "Survival Game", sf::Style::Default );

  sf::Texture background;
  sf::Sprite backgroundImage;

  if ( !background.loadFromFile( "Alaskan Wilderness.png" ) )
    std::cout << "Error: Could not display Alaskan Wilderness image" << std::endl;

  backgroundImage.setTexture( background );

  sf::Font font;
  if ( !font.loadFromFile( "MATURASC.TTF" ) )
    std::cout << "Can't find the font file" << std::endl;

  sf::Texture exitButton;
  sf::Sprite exitButtonImage;
  if ( !exitButton.loadFromFile( "Exit Button.png" ) )
    std::cout << "Can't find the image" << std::endl;
  exitButtonImage.setPosition( 50.0f, 500.0f );

  float exitButtonWidth = exitButtonImage.getLocalBounds().width;
  float exitButtonHeight = exitButtonImage.getLocalBounds().height;

  exitButtonImage.setTexture( exitButton );

  sf::Text startText;
  startText.setFont( font );
  startText.setStyle( sf::Text::Bold );
  startText.setString( "Create New Game" );
  startText.setFillColor( sf::Color::Black );
  startText.setCharacterSize( 48 );
  startText.setPosition( 50.0f, 320.0f );
  sf::Text loadText;
  loadText.setFont( font );
  loadText.setStyle( sf::Text::Bold );
  loadText.setString( "Load Game" );
  loadText.setFillColor( sf::Color::Black );
  loadText.setCharacterSize( 48 );
  loadText.setPosition( 50.0f, 380.0f );
  sf::Text settingsText;
  settingsText.setFont( font );
  settingsText.setStyle( sf::Text::Bold );
  settingsText.setString( "Settings" );
  settingsText.setFillColor( sf::Color::Black );
  settingsText.setCharacterSize( 48 );
  settingsText.setPosition( 50.0f, 440.0f );

  while ( window.isOpen() )
  {
    sf::Event Event;
    while ( window.pollEvent( Event ) )
    {
      switch ( Event.type )
      {
      case sf::Event::Closed:
        window.close();
        break;
      case sf::Event::MouseMoved:
        {
          sf::Vector2i mousePos = sf::Mouse::getPosition( window );
          sf::Vector2f mousePosF( static_cast<float>( mousePos.x ), static_cast<float>( mousePos.y ) );
          if ( exitButtonImage.getGlobalBounds().contains( mousePosF ) )
          {
            exitButtonImage.setColor( sf::Color( 250, 20, 20 ) );
          }
          else
          {
            exitButtonImage.setColor( sf::Color( 255, 255, 255 ) );
          }
        }
        break;
      case sf::Event::MouseButtonPressed:
        {
          sf::Vector2i mousePos = sf::Mouse::getPosition( window );
          sf::Vector2f mousePosF( static_cast<float>( mousePos.x ), static_cast<float>( mousePos.y ) );
          if ( exitButtonImage.getGlobalBounds().contains( mousePosF ) )
          {
            std::cout << "Clicked, yay!" << std::endl;
          }
        }
        break;
      }
    }

    window.clear();
    window.draw( backgroundImage );
    window.draw( exitButtonImage );
    window.draw( startText );
    window.draw( loadText );
    window.draw( settingsText );
    window.display();
  }
  return 0;
}

This only manages the exit button that you wanted to create, but a lot of programming is generally done by monkey-see-monkey-do, so I assume you'll be able to adapt the code.


I'll end this answer by noting that it might not be the most optimal way of doing what you want, architecturally speaking, but since you're just starting, you'll find ways to improve your software as you see fit.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

First off, i don't recommend to call loadFromFile() function in the window loop, it'll read the file every frame.

Next, your problem lies if you set the color on the pollEvent() loop. The pollEvent() loop may not execute when there's no event, which in your case cause flickering when you move the mouse. Try it with a bool variable, set it to true when it's over the button and false otherwise. And draw() function should be outside the pollEvent() loop (Your draw function that's inside the pollEvent() loop doesn't matter anyway, because you cleared it again afterwards).

bool mouseOverButton = false;
// load files
while (window.isOpen()) {
    // Setting up buttons
    sf::Event e;
    while (window.pollEvent(e)) {
        switch (Event.type)
        {
        case sf::Event::Closed:
            window.close();
            break;
        case sf::Event::MouseMoved:
            // If mouse is over
                mouseOverButton = true;
            // If not
                mouseOverButton = false;
            break;
        }
    }
    // Check mouseOverButton and setFillColor or setTexture accordingly
    window.draw(button);
    window.display();
}

Edit: Changing the color inside the pollEvent() loop is actually fine if you declare your button outside the window loop to avoid being created each time with default color.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.