Help with GUI

So just a heads up, this isn't with a GUI library like QT or wxWidgets. I've been working on something with SFML, and all I needed were a few buttons mostly. I didn't want to include a whole other library like SFGUI or something of the sort for so little. I made my Button class and I feel I've coded it so that it's fairly easy creating a button. I basically just type

gui.newButton("RefName",sf::Vector2f(position),sf::Vector2f(scale),"Text");

GUI object automatically checks for mouse hovering over the button, clicks, etc. I've had one issue though that I'm not sure of how to go about however.

Not all the buttons are going to do the same thing obviously. I'd like to be able to just type gui.button("RefName").perform(<minimal arguments here>);

This has proven to be a weird problem for me. I've though about function pointers, but I still have the issue of not all the buttons taking the same arguments. From what I can tell C++ 11 still requires you to tell it exactly what kind of function you'll be passing and what arguments it takes. Understandable, just saying gets in the way of what I had in mind. If I went about this, I'd have to overload Button::perform() every time I have a new button with new parameters.

I also had the idea of creating a ButtonAction class. I could set up GUI to make a list of ButtonAction's along with the buttons. There would be a reference name with ButtonAction as well, so when calling perform the GUI could find the matching ButtonAction to the Button and pass it in, then ButtonAction would do the actual calling of the function, thus eliminating the parameters issue. Then I realized those arguments still have to come in somewhere, and I still have to define those functions. In the end it didn't really eliminate my previous problem, just added a little organization feature.

I can see ways to do this, but I feel like they're all poor and sloppy. I'm still rather inexperienced in C++ and realized I hadn't asked a question on here in ages, so thought I'd pay a visit and see if I could get some help.
closed account (Dy7SLyTq)
there are two options i can think of. you could use boost.any, which is a container that literally holds any data type. so for exampe:

any myarray[] = {"Hello, world!", 19, 7.2f, true};

there is also std::function. i havent used it too much but it might be what you are looking for. also could you post your code? i would be interested to see it
What is sounds like you want is C++11's std::function and std::bind (or the same things in the Boost library if you can't use C++11)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <functional>


typedef std::function<void()> callback_t;  // <- your function pointer type

// note this desired callback does not quite match exactly
void buttonpress(int somearg)
{
  // say you want to call this function with somearg=1 when one button is pressed
  //  but you also want to call it with somearg=2 when another button is pressed.
}

// when you set your function's callback
gui.button("RefName").setEvent( std::bind( &buttonpress, 1 ) );  // call it with 1 as the param

gui.button("RefName2").setEvent( std::bind( &buttonpress, 2 ) ); // call it with  2 as the param 




Then... your button code... when it's pressed can just do the simple:
1
2
3
4
callback_t perform;  // <- defined in your button class

// when you want to actually perform it (when the button is pressed):
perform();
Last edited on
Ok, well here is code I have right now DTSCode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Button.h
class Button
{
private:
	sf::FloatRect detectionBox;
	sf::Vector2f position;
	std::string nameRef; // Buttons need to named
	sf::String buttonText;
	sf::Text m_text;
	sf::Font m_font;
	sprite m_sprite;
	sprite m_spritePushed;
	texture * m_texture;
	texture * m_texturePushed;
	bool pushed;
public:
	Button(std::string name,sf::Vector2f position, sf::Vector2f size,std::string buttonText);
	bool mouseHovering(sf::RenderWindow & WINDOW);
	void draw(sf::RenderWindow & WINDOW);
	void perform(std::string ref);
	std::string getRef();
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Button.cpp

#include"Button.h"
#include<iostream>


Button::Button(std::string nameRef,sf::Vector2f position, sf::Vector2f size,std::string button_Text)
{
	Button::m_texture = new texture("GenericButton.png","GenericButton");
	
	if(m_texture->garbage() == false)
		Button::m_sprite.setTexture(*m_texture);

	else
		std::cout << "Error: Button texture is missing" << std::endl;

	Button::m_texturePushed = new texture("GenericButtonPushed.png","GenericButtonPushed");

	if(m_texture->garbage() == false)
		Button::m_spritePushed.setTexture(*Button::m_texturePushed);

	else
		std::cout << "Error:: Pushed button texture is missing" << std::endl;

	Button::nameRef = nameRef;

	Button::position = position;

	Button::detectionBox.top = position.y; Button::detectionBox.left = position.x;
	Button::detectionBox.height = size.y; Button::detectionBox.width = size.x;
	Button::m_sprite.setPosition(position);
	Button::m_spritePushed.setPosition(position);
	sf::Vector2f temp(position);
	temp.x += 1;
	temp.y += 1;
	Button::buttonText = button_Text;
	Button::m_text.setString(buttonText);
	Button::m_text.setColor(sf::Color::Red);
	Button::m_text.setPosition(temp);
	Button::m_font.loadFromFile("sansation.ttf");
	Button::m_text.setFont(Button::m_font);
	sf::Vector2f scale(1.0f,0.5f);
	float textSize = Button::m_text.getString().getSize();
	float divisor = textSize * 4;
	scale.x = (textSize/divisor) * 2;
	Button::m_text.setScale(scale);
	std::cout << scale.x << std::endl;

	float x = 30; float y = 20;
	x = size.x/x;
	y = size.y/y;
	Button::m_sprite.setScale(sf::Vector2f(x,y));
	Button::m_spritePushed.setScale(sf::Vector2f(x,y));

	Button::pushed = false;
}

bool Button::mouseHovering(sf::RenderWindow & WINDOW)
{
	sf::Vector2f MP = static_cast<sf::Vector2f>(sf::Mouse::getPosition(WINDOW));
	if(Button::detectionBox.contains(MP)==true)
	{
		Button::pushed = true;
		return true;
	}
	else
	{
		Button::pushed = false;
		return false;
	}
}

void Button::draw(sf::RenderWindow & WINDOW)
{
	if(pushed == true)
		Button::m_spritePushed.draw(WINDOW);
	else if(pushed == false)
		Button::m_sprite.draw(WINDOW);
	else
		Button::m_sprite.draw(WINDOW);
	WINDOW.draw(Button::m_text);
}

std::string Button::getRef()
{
	return (Button::nameRef);
}

void Button::perform(std::string ref)
{
  // This is just here, existing I suppose
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Gui.h

class GUI
{
private:
	std::vector<Button*> buttons;
	std::vector<Button*>::iterator iter;
public:
	void newButton(std::string nameRef,sf::Vector2f position,sf::Vector2f size, std::string buttonText);
	bool hoveringOnButton(sf::RenderWindow & WINDOW);
	void update(sf::RenderWindow & WINDOW);
	void handleClick(sf::RenderWindow & WINDOW);
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//Gui.cpp

void GUI::newButton(std::string nameRef, sf::Vector2f position, sf::Vector2f size, std::string buttonText)
{
	GUI::buttons.push_back(new Button(nameRef,position,size,buttonText));
	std::cout << "Added new button" << std::endl;
}

bool GUI::hoveringOnButton(sf::RenderWindow & WINDOW)
{
	for(iter = GUI::buttons.begin(); iter != GUI::buttons.end(); iter++)
	{
		if((*(*iter)).mouseHovering(WINDOW) == true)
		{
			return true;
		}
	}
	return false;
}


void GUI::update(sf::RenderWindow & WINDOW)
{
	GUI::hoveringOnButton(WINDOW);
	for(iter = GUI::buttons.begin(); iter != GUI::buttons.end(); iter++)
	{
		(*(*iter)).draw(WINDOW);
	}
}

void GUI::handleClick(sf::RenderWindow & WINDOW)
{
	for(iter = GUI::buttons.begin(); iter != GUI::buttons.end(); iter++)
	{
		if((*(*iter)).mouseHovering(WINDOW) == true)
		{
			(*(*iter)).perform((*(*iter)).getRef());
		}
	}
}


Excuse all the looping, I'm not going to have tons and tons of buttons so I felt it wouldn't be a big deal.

I've used std::function before Disch, haven't looked at bind however. I'm going to check that out.

Problem I see with that though is some buttons will need to take completely different arguments than others. One button will need a sf::Vector2f, another might need the current map object.
Problem I see with that though is some buttons will need to take completely different arguments than others. One button will need a sf::Vector2f, another might need the current map object.


std::bind can handle it. You just have to give the parameters to bind when you bind it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 6 different functions, all with different parameter lists
void a(sf::Vector2f);
void b(int);
void c();
void d(int,int);
void e(int,sf::Vector2f);

class MyClass
{
public:
    void member(int);
};


///////////////////////////////
// bind them all to the same function type:

MyClass obj;

std::function<void()> func[6] = {
    std::bind( &a, sf::Vector2f(16,5) ),
    std::bind( &b, 3 ),
    std::bind( &c );
    std::bind( &d, 1, 2 ),
    std::bind( &e, 8, sf::Vector2f(9,10) ),
    std::bind( &MyClass::member, &obj, 42 )
        };

func[0]();  // calls:  a( sf::Vector2f(16,5) );
func[1]();  // calls:  b( 3 );
func[2]();  // calls:  c();
func[3]();  // calls:  d( 1, 2 );
func[4]();  // calls:  e( 8, sf::Vector2f(9,10) );
func[5]();  // calls:  obj.member( 42 ); 

Oh, that's insanely cool. Didn't know you could even make an array of those lol. Thanks disch, I think this'll work great.
Topic archived. No new replies allowed.