1
\$\begingroup\$

I'm writing a very simple engine which I hope to use to create a game at some point and felt that I needed buttons to allow the user to select options in a menu, so I wrote up a rudimentary button class. Think of this as similar to Windows forms if you like.

Whilst what I've produced works and I can detect the button presses I feel like this is not the simplest way to do it (although note that I would like to do it myself as a learning exercise rather than using an existing library to produce the buttons). One of my biggest problems is that the buttons are done using GLFW rather than GL and so have a top left anchor and working based on window width and height in pixels rather than a center anchor ranging from -1 to 1, needless to say this makes rendering buttons a chore. Any advice on how to improve that alongside general scrutiny would be much obliged.

display.h

#ifndef DISPLAY_H
#define DISPLAY_H

//  GL Includes
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>

//  Other Includes
#include <iostream>
#include "button.h"
#include <vector>

class Display {
public:
    Display(GLuint width, GLuint height, const char* title, GLFWkeyfun keycallback, GLFWmousebuttonfun mousecallback);
    ~Display();
    bool IsClosed();
    void Update(bool draw, bool pollevents);
    void Clear(GLfloat r, GLfloat g, GLfloat b, GLfloat a);
    void AddButton(Button b);
    void CheckButtons(double x, double y);
private:
    GLFWwindow* window;
    std::vector<Button> buttons;
    bool isClosed;
};

#endif

display.cpp

#include "display.h"

Display::Display(GLuint width, GLuint height, const char* title, GLFWkeyfun keycallback, GLFWmousebuttonfun mousecallback) {
    //  Initialize GLFW and setup window config
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    // Create a GLFWwindow
    window = glfwCreateWindow(width, height, title, nullptr, nullptr);
    if (!window) {
        std::cout << "Could not initialize window.";
        std::cin.get();
        exit(1);
    }
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, keycallback);
    glfwSetMouseButtonCallback(window, mousecallback);

    // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
    glewExperimental = GL_TRUE;

    // Initialize GLEW
    glewInit();

    // Set Viewport to fill window
    glViewport(0, 0, width, height);

    isClosed = false;
}

Display::~Display() {
    glfwTerminate();
}

bool Display::IsClosed() {
    return isClosed;
}

void Display::Update(bool draw, bool pollevents) {
    //  Update Display
    if (draw) {
        glfwSwapBuffers(window);
    }

    //  Check for key press etc.
    if (pollevents) {
        glfwPollEvents();
    }

    //  Exit
    if (glfwWindowShouldClose(window)) {
        isClosed = true;
    }
}

void Display::Clear(GLfloat r, GLfloat g, GLfloat b, GLfloat a) {
    glClearColor(r, g, b, a);
    glClear(GL_COLOR_BUFFER_BIT);
}

void Display::AddButton(Button b) {
    buttons.push_back(b);
}

void Display::CheckButtons(double x, double y) {
    for (int i = 0; i < buttons.size(); i++) {
        buttons[i].CheckClick(x, y);
    }
}

button.h

#ifndef BUTTON_H
#define BUTTON_H

//  GL Includes
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>

class Button {
public:
    Button(double top, double left, double width, double height, void(*f)(void));
    ~Button();
    void CheckClick(double x, double y);
private:
    double w;
    double h;
    double t;
    double l;
    double b;
    void* onClick;
};

#endif

button.cpp

#include "button.h"

Button::Button(double top, double left, double width, double height, void(*f)(void)) {
    t = top;
    l = left;
    w = width;
    h = height;
    onClick = f;
}

Button::~Button() {

}

void Button::CheckClick(double x, double y) {
    if (x >= l && x <= l + w && y >= t && y <= t + h) {
        ((void(*)())onClick)();
    }
}

main.cpp

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// Other includes
#include "shader.h"
#include "display.h"
#include "mesh.h"
#include "button.h"
#include <iostream>

// Consts
const GLuint WIDTH = 300, HEIGHT = 300;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void mouse_callback(GLFWwindow* window, int button, int action, int modifier);
void testbutton1();
void testbutton2();
void testbutton3();

Display display(WIDTH, HEIGHT, "OpenGL", key_callback, mouse_callback);

// The MAIN function, from here we start the application and run the game loop
int main()
{
    Button button1(0, 125, 50, 50, testbutton1);
    Button button2(250, 0, 50, 50, testbutton2);
    Button button3(250, 250, 50, 50, testbutton3);
    display.AddButton(button1);
    display.AddButton(button2);
    display.AddButton(button3);

    // Game loop
    while (!display.IsClosed())
    {
        display.Clear(0.2f, 0.3f, 0.3f, 1.0f);

        //drawing code omitted

        display.Update(true, true);
    }

    return 0;
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS){
        glfwSetWindowShouldClose(window, GL_TRUE);
    }
}

// Is called whenever a mouse button is pressed/released via GLFW
void mouse_callback(GLFWwindow* window, int button, int action, int modifier)
{
    double xpos, ypos;
    glfwGetCursorPos(window, &xpos, &ypos);
    if (action == GLFW_PRESS && button == GLFW_MOUSE_BUTTON_1) {
        display.CheckButtons(xpos, ypos);
    }
}

void testbutton1() {
    //omitted
}
void testbutton2() {
    //omitted
}
void testbutton3() {
    //omitted
}
\$\endgroup\$
2
\$\begingroup\$

Some general notes first:

  • I'd advise using more unique include guards; for example for C++ something like PROJECT_NAMESPACE_FILENAME_H, this would reduce the chances of clashing with include guards from somewhere else.
  • Do not include headers that won't be necessary in the file
  • Having your variables named extensively makes you write more yes, but it makes your code much more readable

button.h

Here I'd remove the includes as they are not being used in button.h.

Then I'd typedef a button click callback type:

class Button {
public:
    typedef void (*ClickCallback)();

This will save you a lot of work and error prone situations (such as your declaration of onClick, which has the wrong type).

In the constructor I would rename the last argument to onClick, this makes it clear at a glance what this argument represents:

Button(double top, double left, double width, double height, ClickCallback onClick);
~Button();
void CheckClick(double x, double y);

I'd proceed to make the onClick member variable to public, this way you can change what happens when you click the button at any time, which you might need in the future. Also don't forget to fix it's type:

public:
    ClickCallback onClick;

Regarding the rest of the variables I would name them properly, otherwise it's hard to know what they are for. An example is b, I can't see where it's being used and given it's name I can't know what uses it would have.

private:
    double top;
    double left;
    double width;
    double height;

button.cpp

Now it's just a matter of applying the changes above to the implementation:

#include "button.h"

Button::Button(double top, double left, double width, double height, ClickCallback onClick)
    : top(top)
    , left(left)
    , width(width)
    , height(height)
    , onClick(onClick) {
}

Button::~Button() {
}

void Button::CheckClick(double x, double y) {
    if (x >= left && x <= left + width &&   // x
        y >= top && y <= top + height) {    // y
            onClick();
        }
}

Notice that here I am using Constructors and member initializer lists.


Display && main

I'm not going much into there as I haven't seen much out of order; you should apply the same principles, discussed for Button, here too.


Coordinates

One of my biggest problems is that the buttons are done using GLFW rather than GL and so have a top left anchor and working based on window width and height in pixels rather than a center anchor ranging from -1 to 1, needless to say this makes rendering buttons a chore.

Regarding this situation, take a look into glOrtho; check this Stack Overflow question.

You'll then end up with something like this when you want to render menus:

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// left, right, bottom, top, near, far
glOrtho(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f);
\$\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.