HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Base classes for a GUI

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
guiforclassesbase

Problem

I am creating a GUI library to better learn C++ and wanted to see if there is anything to improve/simplify.

And I also wanted to be able to call an event of a parent and then for it to iterate all its children and those children to iterate their children and so forth, but I did not want to have a foreach loop in every subclass thus the IterateEvents loop. Is there a better way?

```
#pragma once

#include
#include
#include

#include

namespace bui {
class Element {
public:
int x = 0;
int y = 0;
int width = 0;
int height = 0;
Element* parent;
std::function function;

std::vector children;

Element(int x, int y, int width, int height, Element* parent) {
this->x = x;
this->y = y;
this->width = width;
this->height = height;

if (parent != nullptr) {
parent->children.push_back(this);
}
}

~Element() {

}

template
bool pointInElement(T x, T y) {
return (x > this->x && y > this->y && x x + this->width && this->y y + this->height);
}

virtual void Event() = 0;
virtual void Draw() = 0;

void IterateEvents() {
this->Event();
for (auto &i : children) {
i->Event();
}
}
};

extern std::vector TopLevelWindows;

class TopLevelWindow : public Element {
public:
TopLevelWindow(int x, int y, int width, int height, Element* parent) : Element(x, y, width, height, parent) {
TopLevelWindows.push_back(this);
}

void Event() {
std::cout << "TopLevelWindow\n";
}

void Draw() {
//Draw stuff
}

~TopLevelWindow() {

}
};

class Window : public Element {
public:
Window(int x, int y, int width, int height, Element* parent) : Element(x, y, width, height, pare

Solution

Point and Size structures

I would go even further than @glampert and create Point and Size structures as well as a Rectangle one. Experience has shown that this kind of small classes are extremely useful and can be used at many places in a GUI library (for example, getting the coodinates of the mouse can return a Point, computing the centre of a rectangle too, etc...). Moreover, once you have Point and Size classes, you can overload some of their operators to implement expressive and unambiguous operations such as multiplication by an integer.

In the long run, you will see that it is easier to reason with these objects, and you can even return them from functions while you can't return x and y if they're not in a structure.

Back to the Rectangle

Here is a concrete example of what your class does but should be actually performed by a Rectangle class instead:

template 
bool pointInElement(T x, T y) {
    return (x > this->x && y > this->y && x x + this->width && this->y y + this->height);
}


This should look like this, assuming that you have a Rectangle member named box and that your Rectangle class has a method named contains which takes a Point and returns a bool:

template
bool pointInElement(const Point& point) const {
    return box.contains(point);
}


Constructor initialization list

Whenever possible, try to use the constructor initialization list. As it is now, your code constructs an Element instance, then assigns values to its fields. If you leave the job to the constructor initialization list, your object will be directly constructed with the appropriate values:

Element(int x, int y, int width, int height, Element* parent):
    x(x), y(y),
    width(width), height(height) {

    if (parent != nullptr) {
        parent->children.push_back(this);
    }
}


As you can see, it even allows to drop the this-> needed to differentiate the variables. The syntax only allows the member names on the left and the name resolution picks the parameters first on the right.

About the virtual destructor

I bet that you will want runtime polymorphism with your widgets. In this case, follow @glampert's advice: make the destructor virtual and public. If it does not do anything, the simplest thing to do is to explicitly default it:

virtual ~Element() = default;


Now, if a destructor is public and virtual in a base class, then the derived class automagically have a virtual destructor, you don't have to write anything. Therefore, the simplest thing to do would be to simply remove the destructors from TopLevelWindow, Window and Button if they don't do anything more than Element's destructor; you program will then be correct but simpler.

Code Snippets

template <typename T>
bool pointInElement(T x, T y) {
    return (x > this->x && y > this->y && x < this->x + this->width && this->y < this->y + this->height);
}
template<typename T>
bool pointInElement(const Point<T&>& point) const {
    return box.contains(point);
}
Element(int x, int y, int width, int height, Element* parent):
    x(x), y(y),
    width(width), height(height) {

    if (parent != nullptr) {
        parent->children.push_back(this);
    }
}
virtual ~Element() = default;

Context

StackExchange Code Review Q#107709, answer score: 4

Revisions (0)

No revisions yet.