Terminamos hoy la parte del gestor de eventos exponiendo al detalle el nuevo EventeReceiver.
Para empezar, aquí está la declaración de la clase, que iría en su respectivo archivo de cabecera:#include <irrlicht/irrlicht.h>
#include <map>
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#define AppCB(var) void (*var)(void*, const SEvent&)
#define parGUIE std::pair< EGUI_EVENT_TYPE, int >
#define mapGUI std::map< parGUIE, AppCB() >
#define parKEYE std::pair< EKEY_CODE, bool >
#define mapKEY std::map< parKEYE, AppCB() >
#define mapMOUSE std::map< EMOUSE_INPUT_EVENT, AppCB() >
#define mapLOG std::map< ELOG_LEVEL, AppCB() >
//#define mapUSER AppCB()
class EventReceiver : public IEventReceiver
{
public:
EventReceiver(void* parent);
virtual ~EventReceiver();
virtual bool OnEvent(const SEvent& event);
bool mAddGuiEvent(EGUI_EVENT_TYPE eventType, int id, AppCB(callback));
bool mAddKeyUpEvent(EKEY_CODE code, AppCB(callback));
bool mAddKeyDownEvent(EKEY_CODE code, AppCB(callback));
bool mAddMouseEvent(EMOUSE_INPUT_EVENT input_event, AppCB(callback));
bool mAddLogEvent(ELOG_LEVEL level, AppCB(callback));
protected:
mapGUI f_guiEvents;
mapKEY f_keyEvents;
mapMOUSE f_mouseEvents;
mapLOG f_logEvents;
void* f_parent;
};
Bien, al principio vemos los includes necesarios y las definiciones, que ya explicamos, de las tablas hash y el puntero a función. Mas abajo, dentro de la declaración de la clase, que hereda de IEventReceiver, hemos añadido, además del onEvent, métodos para facilitar la adición de nuevos eventos, de modo que EventReceiver pueda gestionarlos adecuadamente. Cada uno de estos métodos recibe distintos en función del tipo de evento. Tal es el caso de mAddGuiEvent, que recibe el tipo de evento GUI y el id del control que lo provocó. Lo que sí que tienen en común, es el puntero a función o callback a invocar una vez producido el evento.
En la parte protegida podemos observar las tablas hash que contendrán los eventos y un puntero genérico (void*) al padre de la instancia de la clase. Este último campo lo veremos más abajo.
Veamos ahora la implementación. Primero, el contructor y destructor, nada de especial. Como las tablas hash no han sido creadas de manera dinámica, no es necesario ponelas en ninguno de los dos metodos.#include "EventReceiver.h"
EventReceiver::EventReceiver(void* parent)
{
f_parent = parent;
}
EventReceiver::~EventReceiver()
{
f_parent = NULL;
}
Ahora veamos los métodos que nos permiten añadir facilmente callbacks a los eventos. Tampoco tiene ningún secreto, dado que tan sólo agregan el callback a la tabla hash pertinente con la clave recibida.bool
EventReceiver::mAddGuiEvent(EGUI_EVENT_TYPE eventType, int id, AppCB(callback))
{
parGUIE key(eventType,id);
f_guiEvents[key] = callback;
return true;
}
bool
EventReceiver::mAddKeyUpEvent(EKEY_CODE code, AppCB(callback))
{
parKEYE key(code,false);
f_keyEvents[key] = callback;
return true;
}
bool
EventReceiver::mAddKeyDownEvent(EKEY_CODE code, AppCB(callback))
{
parKEYE key(code,true);
f_keyEvents[key] = callback;
return true;
}
bool
EventReceiver::mAddMouseEvent(EMOUSE_INPUT_EVENT input_event, AppCB(callback))
{
f_mouseEvents[input_event] = callback;
return true;
}
bool
EventReceiver::mAddLogEvent(ELOG_LEVEL level, AppCB(callback))
{
f_logEvents[level] = callback;
return true;
}
Y ahora sí, damas y caballeros, aquí viene el metodo onEvent, el cual tiene un switch que, según el evento principal, formará la clave y preguntará a la tabla hash correspondiente si tiene dicha clave, en cuyo caso recuperará el callback y lo invocará. Es aqui cuando hace uso del attributo f_parent. Recordad que el puntero a función debe apuntar a un metodo de clase (estático) y necesitábamos pasarle el objeto a manipular.bool
EventReceiver::OnEvent(const SEvent& event)
{
switch(event.EventType)
{
case EET_GUI_EVENT:
{
s32 id = event.GUIEvent.Caller->getID();
EGUI_EVENT_TYPE eventType = event.GUIEvent.EventType;
parGUIE key(eventType,id);
if(f_guiEvents.find(key) != f_guiEvents.end())
{
AppCB(callback) = f_guiEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_KEY_INPUT_EVENT:
{
EKEY_CODE code = event.KeyInput.Key;
bool pressed = event.KeyInput.PressedDown;
parKEYE key(code,pressed);
if(f_keyEvents.find(key) != f_keyEvents.end())
{
AppCB(callback) = f_keyEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_MOUSE_INPUT_EVENT:
{
EMOUSE_INPUT_EVENT key = event.MouseInput.Event;
if(f_mouseEvents.find(key) != f_mouseEvents.end())
{
AppCB(callback) = f_mouseEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_LOG_TEXT_EVENT:
{
ELOG_LEVEL key = event.LogEvent.Level;
if(f_logEvents.find(key) != f_logEvents.end())
{
AppCB(callback) = f_logEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_USER_EVENT:
default:
{
break;
}
};
return false;
}
Para terminar, un ejemplo de uso del nuevo EventReceiver. Aquí vemos un cacho de código en el que se inicializa una instancia de tipo EventReceiver y se le agregan callbacks. f_eventReceiver = new EventReceiver(this);
f_device->setEventReceiver(f_eventReceiver);
//Inicializacion de controles
f_menu = f_env->addMenu(0, f_genIDs->getNewID());
f_buttonQuit = f_env->addButton(rect<s32>(10,210,100,240), 0, f_genIDs->getNewID(), L"Quit");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonQuit->getID(), MainApplication::mButtonQuit_Click);
f_buttonNewWindow = f_env->addButton(rect<s32>(10,250,100,290), 0, f_genIDs->getNewID(), L"New Window");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonNewWindow->getID(), MainApplication::mButtonNewWindow_Click);
f_buttonFileOpen = f_env->addButton(rect<s32>(10,300,100,340), 0, f_genIDs->getNewID(), L"File Open");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonFileOpen->getID(), MainApplication::mButtonFileOpen_Click);
Y a continuación vemos la implementación de los callbacks añadidos en el codigo anterior. Veis como al principio de cada evento se hace una conversión (cast) al tipo MainApplication, que es el que hace de padre (el valor del atributo f_parent) de f_eventReceiver.void
MainApplication::mButtonQuit_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_device->closeDevice();
// me->f_ImageLogo->remove();
}
void
MainApplication::mButtonNewWindow_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_listBoxLogger->addItem(L"Window created");
IGUIWindow* window = me->f_env->addWindow(
rect<s32>(100, 100, 300, 200),
false, // modal?
L"Test window");
me->f_env->addStaticText(L"SubVentanica",
rect<s32>(35,35,140,50),
true, // border?,
false, // wordwrap?
window);
}
void
MainApplication::mButtonFileOpen_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_listBoxLogger->addItem(L"File open");
me->f_env->addFileOpenDialog(L"Please choose a file.");
}
Bien, mediante los punteros a función hemos conseguido hacer mas mantenible el objeto EventReceiver necesario para gestionar los eventos con Irrlicht. En la proxima entrega de esta serie, empezaremos a pincelar la estructura de las ventanas en Irrlicht.
domingo, 25 de mayo de 2008
El nuevo EventReceiver
sábado, 24 de mayo de 2008
Tipos de eventos y subtipos
En esta breve entrega analizaremos los distintos tipos de eventos que aporta Irrlicht
El método OnEvent recibe un objeto SEvent. Si miramos el api de irrlicht, veremos que hay 5 tipos de eventos:
A su vez, los eventos de controles (SGUIEvent) tiene sub tipos, para poder distinguir, por ejemplo, el click de un boton de FileOpenDialog. Es por ello que cada colección de eventos depende del tipo de evento.
En la proxima entrega veremos las colecciones a emplear.
Ejemplo típico de los tutoriales
He aquí un ejemplo de EventReceiver extraido de uno de los tutoriales.
En él se aprecia claramente que todos los eventos estan programados de manera estática y que añadir, eliminar o modificar, es una ardua tarea.class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
// Escape swaps Camera Input
if (event.EventType == EET_KEY_INPUT_EVENT &&
event.KeyInput.Key == irr::KEY_ESCAPE &&
event.KeyInput.PressedDown == false)
{
if ( Device )
{
scene::ICameraSceneNode * camera = Device->getSceneManager()->getActiveCamera ();
if ( camera )
{
camera->setInputReceiverEnabled ( !camera->isInputReceiverEnabled() );
}
return true;
}
}
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = Device->getGUIEnvironment();
switch(event.GUIEvent.EventType)
{
case EGET_FILE_SELECTED:
{
// load the model file, selected in the file open dialog
IGUIFileOpenDialog* dialog =
(IGUIFileOpenDialog*)event.GUIEvent.Caller;
loadModel(core::stringc(dialog->getFileName()).c_str());
}
case EGET_SCROLL_BAR_CHANGED:
// control skin transparency
if (id == 104)
{
s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
video::SColor col = env->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(pos);
env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}
break;
case EGET_BUTTON_CLICKED:
switch(id)
{
case 1101:
{
// set scale
gui::IGUIElement* root = env->getRootGUIElement();
core::vector3df scale;
core::stringc s;
s = root->getElementFromId(901, true)->getText();
scale.X = (f32)atof(s.c_str());
s = root->getElementFromId(902, true)->getText();
scale.Y = (f32)atof(s.c_str());
s = root->getElementFromId(903, true)->getText();
scale.Z = (f32)atof(s.c_str());
if (Model)
Model->setScale(scale);
}
break;
case 1102:
env->addFileOpenDialog(L"Please select a model file to open");
break;
case 1103:
showAboutText();
break;
case 1104:
createToolBox();
break;
case 1105:
env->addFileOpenDialog(L"Please select your game archive/directory");
break;
}
break;
}
}
return false;
}
};
Si nos fijamos en el subtipo de evento EGET_BUTTON_CLICKED vemos que distingue a cada boton por el id y con valores de enteros directamente. Imagínate que queramos añadir botones dinámicamente segun una configuracion u otra!!
En la proxima entrega examinaremos la jerarquía de eventos que tiene Irrlicht.
Manejo de eventos con IEventReceiver
Como ya hablamos en la introduccion, uno de los inconvenientes en los tutoriales de irrlicht, es la gestión de eventos para los controles (botones, cajas de texto, etc), cámaras, acciones de usuario, etc.
Para su gestión, Irrlicht dispone de la interfaz IEventReceiver. Cualquier Clase que herede de dicha interfaz, debe implementar el método onEvent. Si miráis en cualquiera de los tutoriales, vereis como el metodo onEvent del objeto de tipo MyEventReceiver usado en la aplicación tiene un super-switch múltiple de la muerte con todos los eventos de todos los controles, todo ello puesto a piñón fijo. Si tienes poquitos botones y posibles acciones esta bien, pero no nos sirve cuando la aplicacion crece y quieres que sea mantenible, dinámica y extensible. Lo bonito seria hacerlo al estilo de los lenguajes de nueva generación como Java y la familia .Net, esto es, yo tengo un botón, y un método que quiero que se ejecute cuando se produzca el evento onClick del botón.
Continuamos con un ejemplo típico de los tutoriales.
Estructura de ventanas con Irrlicht
Buenas a todos, en esta entrada trataremos de explicar como sacarle partido a las clases e interfaces que nos provee Irrlicht para montarnos una estructura de menús decente.
Si alguna vez has seguido los tutoriales de irrlicht, como por ejemplo, el User Interface y Mesh Viewer, habréis visto que presentan dos grandes inconvenientes.
El primero es que no deja claro, o no explica, cómo hacer varias ventanas, esto es, cómo tener dividido la aplicación en múltiples ventanas o formularios. Cualquier programa que se precie, tendrá una ventana de configuración, distintas secciones e incluso, subaplicaciones. Esto ocurre en [L], donde tendremos a priori, el menú principal, el editor de escenarios y el simulador en sí.
El segundo gran inconveniente es la gestión de eventos para los controles (botones, cajas de texto, etc), cámaras, acciones de usuario, etc.
Así pues, vamos a organizarnos y a poner las cosas claras. Abordaremos estos dos temas poco a poco y dividiendolo en articulos. He aqui los puntos a tratar:
Puedes ya empezar a leerlos comenzando por el primer punto: Manejo de eventos con IEventReceiver.