Added basic code for creating and handling GUI events - at the moment just (SDL_USEREVENT + 10) using the SDL_USEREVENT union struct. Maybe define a custom struct that is equal in size to SDL_Event (and has type in the same position) and cast when needed? It'd make it a bit easier/nicer than using data1/data2. Cleaned up GuiElement and added margin functionality.

master
kts 2015-03-07 22:56:27 -08:00
parent 549b391c4e
commit b8d6462723
7 changed files with 100 additions and 60 deletions

View File

@ -94,6 +94,7 @@ int Gui::onEvent(SDL_Event event) {
std::vector<GuiElement*>::reverse_iterator element_it, element_end;
GuiElement *element = NULL;
float x, y;
SDL_Event hit_event; // event to send to the events queue if a gui element is hit
switch (event.type) {
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
@ -103,7 +104,14 @@ int Gui::onEvent(SDL_Event event) {
for (element_it = elements.rbegin(), element_end = elements.rend(); element_it != element_end; ++element_it) {
if ((*element_it)->getFlags() & GuiElement::HIDDEN) continue;
element = (*element_it)->getHit(x, y);
if (element != NULL) break;
if (element != NULL) {
hit_event.type = GUI_EVENT;
hit_event.user.code = GUI_HIT;
hit_event.user.data1 = (void*)element;
hit_event.user.data2 = NULL;
SDL_PushEvent(&hit_event);
break;
}
}
break;
case SDL_FINGERDOWN:
@ -114,7 +122,14 @@ int Gui::onEvent(SDL_Event event) {
for (element_it = elements.rbegin(), element_end = elements.rend(); element_it != element_end; ++element_it) {
if ((*element_it)->getFlags() & GuiElement::HIDDEN) continue;
element = (*element_it)->getHit(x, y);
if (element != NULL) break;
if (element != NULL) {
hit_event.type = GUI_EVENT;
hit_event.user.code = GUI_HIT;
hit_event.user.data1 = (void*)element;
hit_event.user.data2 = NULL;
SDL_PushEvent(&hit_event);
break;
}
}
break;
default:
@ -150,7 +165,7 @@ int Gui::addElement(GuiElement *element) {
}*/
if (element->parent == NULL) {
elements.push_back(element);
LOG(LOG_INFO) << FUNC_NAME << " added element " << element->text;
LOG(LOG_INFO) << FUNC_NAME << " added element " << element->name;
}
return elements.size()-1;
}
@ -159,7 +174,7 @@ int Gui::delElement(const char *name) {
std::vector<GuiElement*>::iterator element_it, element_end;
for (element_it = elements.begin(), element_end = elements.end(); element_it != element_end;) {
element = *element_it;
if (strcmp(element->text.c_str(), name) == 0) {
if (strcmp(element->name.c_str(), name) == 0) {
if (element->object != NULL) set_basic->remObject(element->object);
delete element;
element_it = elements.erase(element_it);
@ -192,7 +207,6 @@ RenderCamera* Gui::getChildCamera() {
void Gui::updateElements() {
std::vector<GuiElement*>::iterator element_it, element_end;
GuiElement *p_element = NULL;
GuiElement *c_element = NULL;
for (element_it = elements.begin(), element_end = elements.end(); element_it != element_end; ++element_it) {
p_element = *element_it;
if (p_element->getFlags() & GuiElement::HIDDEN) continue;

View File

@ -11,6 +11,15 @@ Gui.cpp/Gui.hpp provide the class responsible for creating, destroying, and hand
#include "RenderCamera.hpp"
#include "AssetManager.hpp"
#include <vector>
// **** GUI Event ****
// TODO: check if this look up is safe
#define GUI_EVENT SDL_USEREVENT + 10
// NOTE: what type of events do we want?
// PRESS, RELEASE - mouse/touch events
// FOCUS, UNFOCUS - when focusable elements (text input) switch to/from (should also emit press/release?)
// CREATE, DESTROY - maybe unneeded, but when the element is destroyed/created by Gui (would need separate IDs for this)
#define GUI_HIT 1
class Gui {
friend class Core;
public:

View File

@ -1,8 +1,8 @@
#include "GuiButton.hpp"
#include "Log.hpp"
GuiButton::GuiButton(const char *name, Texture *texture_) {
text.assign(name);
GuiButton::GuiButton(const char *name_, Texture *texture_) {
name.assign(name_);
texture = texture_;
}
GuiButton::~GuiButton() {

View File

@ -23,6 +23,12 @@ GuiElement::~GuiElement() {
if (view != NULL) delete view;
if (object != NULL) delete object;
}
void GuiElement::setName(const char *new_name) {
name.assign(new_name);
}
const char* GuiElement::getName() {
return name.c_str();
}
/* ======== Relationships ======== */
int GuiElement::addChild(GuiElement *child) {
// adopt the child!
@ -33,6 +39,7 @@ int GuiElement::addChild(GuiElement *child) {
children.push_back(child);
return children.size()-1;
}
// Disown thine seed!
int GuiElement::remChild(GuiElement *child) {
child->parent = NULL;
std::vector<GuiElement*>::iterator it;
@ -43,33 +50,34 @@ int GuiElement::remChild(GuiElement *child) {
return 1;
}
int GuiElement::setParent(GuiElement *parent_) {
if (parent) parent->remChild(this);
parent = parent_;
// TODO: detach from old parent
return 0;
}
/* ======== Interactions ======== */
/* getHit
This function takes in normalized coordinates and returns either NULL or the element hit if within the element's bounding box. If this element is hit, it will call getHit to all children elements until either a match is found or NULL is returned. If a match is found, that is returned, otherwise this element is returned.
This function takes in absolute X and Y coordinates, originating from the top-left of the display area.
The hit coordinates are checked against the GuiElement's bounds and, if a match is found, it is then first checked against
the GuiElement's own x/y and x+w/x+h coordinates before calling each child's getHit method. If getHit successfully matches,
a pointer to GuiElement is returned.
*/
GuiElement* GuiElement::getHit(float xhit, float yhit) {
GuiElement *element = NULL;
LOG(LOG_INFO) << text << " " << xhit << "x" << yhit << " vs " << bound_l << "x" << bound_t << "|" << bound_r << "x" << bound_b;
LOG(LOG_DEBUG) << name << " " << xhit << "x" << yhit << " vs " << bound_l << "x" << bound_t << "|" << bound_r << "x" << bound_b;
if ( (xhit >= bound_l && xhit <= bound_r) && (yhit >= bound_t && yhit <= bound_b) ) {
LOG(LOG_INFO) << FUNC_NAME << ": HIT " << text <<": " << xhit << "x" << yhit;
LOG(LOG_DEBUG) << FUNC_NAME << ": HIT bounding box";
if ( (xhit >= x && xhit <= x+w) && (yhit >= y && yhit <= y+h) ) {
LOG(LOG_DEBUG) << FUNC_NAME << ": HIT " << name;
return this;
}
// check the children for hits and return them if so
GuiElement *child_element = NULL;
// Start from the end of the vector and walk backwards
std::vector<GuiElement*>::reverse_iterator element_it, element_end;
for (element_it = children.rbegin(), element_end = children.rend(); element_it != element_end; ++element_it) {
child_element = (*element_it)->getHit(xhit, yhit);
if (child_element != NULL) return child_element;
}
if (child_element != NULL) {
element = child_element;
} else {
element = this;
}
} else {
element = NULL;
}
return element;
}
@ -87,7 +95,6 @@ int GuiElement::setGeometry(float x_, float y_, float w_, float h_) {
base_h = h_;
//view->setView(w, h);
object->setScale(base_w, 1.0, base_h);
//object->setTranslation(x, 0.0, y);
object->calcMatrix();
return 0;
}
@ -109,8 +116,8 @@ void GuiElement::setMargin(float l, float r, float t, float b) {
margin_t = t;
margin_b = b;
}
/* ======== Render, eugh ======== */
/* ======== Update ======== */
// TODO: should this embrace int(s) or float(s) all the way? A danger is that float(s) would enable half pixel values thereby making things fuzzy. Maybe use int for building matrix?
int GuiElement::doUpdate(int container_x, int container_y, int container_w, int container_h, int c_width, int c_height) {
int current_x = 0;
int current_y = 0;
@ -127,18 +134,18 @@ int GuiElement::doUpdate(int container_x, int container_y, int container_w, int
}
// get origin
if (origin_flags & LEFT) {
current_x = container_x;
current_x = container_x + margin_l;
} else if (origin_flags & RIGHT) {
current_x = container_x+container_w;
current_x = container_x + container_w + margin_r;
} else if (origin_flags & HCENTER) {
current_x = container_x + (container_w/2);
current_x = container_x + (container_w/2) + ((margin_l+margin_r)/2);
}
if (origin_flags & TOP) {
current_y = container_y;
current_y = container_y + margin_t;
} else if (origin_flags & BOTTOM) {
current_y = container_y+container_h;
current_y = container_y + container_h + margin_b;
} else if (origin_flags & VCENTER) {
current_y = container_y + (container_h/2);
current_y = container_y + (container_h/2) + ((margin_t+margin_b)/2);
}
// set our width and height
current_w = base_w;
@ -246,4 +253,4 @@ void GuiElement::setFlags(int new_flags) {
}
int GuiElement::getFlags() {
return flags;
}
}

View File

@ -9,10 +9,8 @@ This file describes the GuiElement class. This is the base class for further GUI
#include <vector>
#include "RenderObject.hpp"
#include "RenderView.hpp"
#include "RenderCamera.hpp"
class GuiElement {
friend class Gui;
friend class GuiList;
public:
enum Type {
DEFAULT = 0,
@ -44,48 +42,52 @@ class GuiElement {
GuiElement();
virtual ~GuiElement();
//
int setGeometry(float x, float y, float w, float h);
// int setText(const char *string);
virtual int doUpdate(int container_x, int container_y, int container_w, int container_h, int c_width, int c_height);
int doRender();
//
void setName(const char *string);
const char* getName();
GuiElement *getHit(float xhit, float yhit);
void hide();
void show();
// relationship functions
int addChild(GuiElement *child);
int remChild(GuiElement *child);
int setParent(GuiElement *parent);
int setPFlags(int pflags);
RenderView *getView();
virtual int doUpdate(int container_x, int container_y, int container_w, int container_h, int c_width, int c_height);
int doRender();
// getters
float getWidth();
float getHeight();
void setSize(float width, float height);
void setPosition(float newx, float newy);
void setOffset(float offx, float offy);
int getFlags();
RenderView *getView();
// positional/sizing/flag setting functions
int setGeometry(float x, float y, float w, float h);
void setMargin(float l, float r, float t, float b);
void setPadding(float l, float r, float t, float b);
void setBehavior(int behavior_flags);
void setOrigin(int origin_flags);
void setDirection(int direction_flags);
int getFlags();
void setFlags(int new_flags);
void hide();
void show();
protected:
int type; // flag for this type
// behavioral
int origin_flags; // origin flags
int direction_flags;// direction flags
int flags; // flags
std::string text; // text of this element
GuiElement *parent; // parent of this element
std::vector<GuiElement*> children; // children of this element
RenderObject *object;// render object
RenderView *view; // Ugh, we use FBOs for GUI elements cuz otherwise it's too annoying.
// relationships
GuiElement *parent; // parent of this element
std::vector<GuiElement*> children; // children of this element
// rendering data
RenderObject *object; // render object
RenderView *view; // Ugh, we use FBOs for GUI elements cuz otherwise it's too annoying.
// identity
int type; // flag for this type
std::string name; // name of this element
int gid; // group id for this element
int id; // id for this element
//
float x, y, w, h; // position and dimensions for this element
float offset_x, offset_y; // offset x and y
float base_w, base_h;
float margin_l, margin_r, margin_t, margin_b;
float padding_l, padding_r, padding_t, padding_b;
float bound_l, bound_r, bound_t, bound_b;
// positioning
float x, y, w, h; // final position/dimensions for this element, acquired from positioning, offset, margin, padding, and base
float offset_x, offset_y; // offset x and y
float base_w, base_h; // base width and height
float margin_l, margin_r, margin_t, margin_b; // margin from other element(s) to add
float padding_l, padding_r, padding_t, padding_b; // padding to use internally if applicable
float bound_l, bound_r, bound_t, bound_b; // bounding box, indicates the full size(virtual) that this element and its children take
};
#endif

View File

@ -1,8 +1,8 @@
#include "GuiList.hpp"
#include "Log.hpp"
GuiList::GuiList(const char *name) {
text.assign(name);
GuiList::GuiList(const char *name_) {
name.assign(name_);
type = LIST;
dflags = HORIZONTAL|RIGHT;
}

View File

@ -16,6 +16,13 @@ int MenuState::doProcess(unsigned int ticks) {
return 0;
}
int MenuState::onEvent(SDL_Event event) {
switch (event.type) {
case GUI_EVENT:
if (event.user.code == GUI_HIT) {
LOG(LOG_ERROR) << FUNC_NAME << ": recv'd hit from " << ((GuiElement*)event.user.data1)->getName();
}
break;
}
return 0;
}
int MenuState::onCede() {
@ -43,16 +50,17 @@ int MenuState::onRise() {
gbutton->setGeometry(0.0f, 0.0f, 128.0f, 128.0f);
gbutton->setDirection(GuiElement::DOWN);
gbutton->setOrigin(GuiElement::VCENTER|GuiElement::HCENTER);
gbutton->setFlags(GuiElement::CAN_RESIZE);
//gbutton->setFlags(GuiElement::CAN_RESIZE);
GuiButton *gbutton2 = new GuiButton("logo2", core.getTexture("ui/icon_perscube.png"));
gbutton2->setGeometry(0.0f, 0.0f, 128.0f, 128.0f);
gbutton2->setDirection(GuiElement::DOWN|GuiElement::RIGHT);
gbutton2->setOrigin(GuiElement::BOTTOM|GuiElement::RIGHT);
gbutton2->setMargin(25.0f, 25.0f, 25.0f, 25.0f);
gbutton->addChild(gbutton2);
gbutton2 = new GuiButton("logo3", core.getTexture("ui/icon_token.png"));
gbutton2->setGeometry(0.0f, 0.0f, 128.0f, 128.0f);
gbutton2->setDirection(GuiElement::LEFT|GuiElement::UP);
gbutton2->setDirection(GuiElement::RIGHT|GuiElement::UP);
gbutton2->setOrigin(GuiElement::TOP|GuiElement::RIGHT);
gbutton->addChild(gbutton2);