custom gui elements - esp. non-rectangular gui elements
-
- Posts: 19
- Joined: Wed Dec 06, 2006 10:15 am
custom gui elements - esp. non-rectangular gui elements
I was hoping someone could direct me to some good material on creating custom gui elements. I have been learning by looking through the basic tutorial, the API and the source for the built in elements, as well as the forum. I spent some time searching through the forum. I was wondering if anyone knows of a more helpful resource for beginners.
Some questions--
- Is there any documentation about what the return value of OnEvent is used for/ should signify? (API says: returns true if event was processed) What the environment does when false is returned ?
- Does anyone know of any custom gui elements I can get source for that use non-rectangular shapes?
I particularly want to create a diamond shaped button and a circular shaped button for a project I am attempting. I am having trouble with handling situations where two non-rectangular buttons overlap on the edges. The diamond shaped buttons will be tessellated.
Any help would be much appreciated
Some questions--
- Is there any documentation about what the return value of OnEvent is used for/ should signify? (API says: returns true if event was processed) What the environment does when false is returned ?
- Does anyone know of any custom gui elements I can get source for that use non-rectangular shapes?
I particularly want to create a diamond shaped button and a circular shaped button for a project I am attempting. I am having trouble with handling situations where two non-rectangular buttons overlap on the edges. The diamond shaped buttons will be tessellated.
Any help would be much appreciated
~~Captain Pants
Hi, I was playing a little bit with custom gui. Bellow is my cusstom button class code:
and you can create button like:
As you can see in OnEvent function, it simply detect if mouse cursor is inside button rectangle when mouse is clicked and if yes, print "button clicked" in the console. Button is rectangular but it would be quit easy to make it circular. Just keep position of center stored somewhere and instead of testing cursor against rectangle, test its distance from center of button. If it is it smaller than radius of your button, button was clicked.
In fact you dont have to store button center position, with some math, you can get it out of AbsoluteRect together with radius. (note that AbsoluteRect is inherited from IGUIElement).
As for diamond shaped elements I dont have idea. You may split your button in to smaller rectangles. Sort of pixelate your button. In such a way ou can build any shape you wish.
Code: Select all
class MyButton : public gui::IGUIElement
{
video::ITexture *image;
public:
MyButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent, s32 id, core::rect<s32> rectangle)
: IGUIElement(gui::EGUIET_ELEMENT , environment, parent, id, rectangle)
{
image = NULL;
}
~MyButton(){}
virtual void setImage(video::ITexture *img)
{
image = img;
}
virtual void draw()
{
if (!IsVisible)
return;
video::IVideoDriver* driver = Environment->getVideoDriver();
if(image)
{
core::rect<s32> rect = core::rect<s32>(0,0, image->getSize().Width, image->getSize().Height);
core::rect<s32> pos = AbsoluteRect;
driver->draw2DImage(image, pos, rect, 0, 0, true);
}
}
bool OnEvent(SEvent event)
{
if (!IsEnabled)
return Parent ? Parent->OnEvent(event) : false;
switch(event.EventType)
{
case EET_MOUSE_INPUT_EVENT:
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
{
core::rect<s32> pos = AbsoluteRect;
if(pos.isPointInside(core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)) ) cout << "button clicked" << endl;
Environment->setFocus(this);
return true;
}
}
return Parent ? Parent->OnEvent(event) : false;
}
};
Code: Select all
video::IImage *image;
image = driver->createImageFromFile("soldier.bmp");
video::ITexture *texture = driver->addTexture("texture", image);
image->drop();
MyButton *button = new MyButton( guienv, guienv->getRootGUIElement(), -1, core::rect<s32>(0,0,100,100) );
button->drop();
button->setImage(texture);
In fact you dont have to store button center position, with some math, you can get it out of AbsoluteRect together with radius. (note that AbsoluteRect is inherited from IGUIElement).
As for diamond shaped elements I dont have idea. You may split your button in to smaller rectangles. Sort of pixelate your button. In such a way ou can build any shape you wish.
-
- Posts: 19
- Joined: Wed Dec 06, 2006 10:15 am
I refrained from posting my code earlier because of its length, but here is some relevant code:
[CGUIButton2.h]
This is just a demonstration of the problem that I have no idea how to solve:
[main.cpp]
In the above example, everything works except that when the invisible corner of one button is over the top of another, it receives the gui events for the mouse when its in that position.
(put the mouse cursor over the bottom right corner of the left diamond, it doesnt light up)
[CGUIButton2.h]
Code: Select all
#ifndef CGUIBUTTON2_H_INCLUDED
#define CGUIBUTTON2_H_INCLUDED
#include <irr/irrlicht.h>
using namespace irr;
using namespace core;
using namespace gui;
using namespace video;
class CGUIButton2 : public IGUIElement
{
public:
CGUIButton2
(IGUIEnvironment * const & gui_env,
const rect<s32> & shape,
const s32 & id = -1,
IGUIElement * const & parent = NULL)
: IGUIElement
(EGUIET_ELEMENT,
gui_env,
parent ? parent : gui_env->getRootGUIElement(),
id,
shape),
m_down (false),
m_hover(false)
{
}
virtual bool OnEvent(SEvent event)
{
if(event.EventType == EET_MOUSE_INPUT_EVENT)
{
position2d<s32> mouse_pos = position2d<s32>(event.MouseInput.X, event.MouseInput.Y);
switch(event.MouseInput.Event)
{
case EMIE_LMOUSE_PRESSED_DOWN:
if(m_hover)
{
m_down = true;
Environment->setFocus(this);
}
else
{
Environment->removeFocus(this);
}
break;
case EMIE_LMOUSE_LEFT_UP:
if(m_hover && m_down)
{
SEvent new_e;
new_e.EventType = EET_GUI_EVENT;
new_e.GUIEvent.EventType = EGET_BUTTON_CLICKED;
new_e.GUIEvent.Caller = this;
Parent->OnEvent(new_e);
}
m_down = false;
Environment->removeFocus(this);
break;
case EMIE_MOUSE_MOVED:
m_hover = contains(mouse_pos);
break;
default:
break;
}
}
else if(event.EventType == EET_GUI_EVENT)
{
if( event.GUIEvent.Caller == dynamic_cast<IGUIElement *>(this) )
{
switch(event.GUIEvent.EventType)
{
case EGET_ELEMENT_LEFT:
m_hover = false;
break;
case EGET_ELEMENT_HOVERED:
if(!m_hover)
{
return false;
}
break;
default:
break;
}
}
}
return IGUIElement::OnEvent(event);
}
virtual void draw()
{
// draws a diamond
position2d<s32> center = AbsoluteRect.getCenter();
position2d<s32> top (center.X, AbsoluteRect.UpperLeftCorner.Y );
position2d<s32> bottom(center.X, AbsoluteRect.LowerRightCorner.Y-1);
position2d<s32> left (AbsoluteRect.UpperLeftCorner.X, center.Y);
position2d<s32> right (AbsoluteRect.LowerRightCorner.X-1, center.Y);
SColor col = m_hover ? SColor(255, 255, 255, 255) : SColor(255, 0, 0, 0);
IVideoDriver * driver = Environment->getVideoDriver();
driver->draw2DLine(left, top, col);
driver->draw2DLine(top, right, col);
driver->draw2DLine(right, bottom, col);
driver->draw2DLine(bottom, left, col);
}
protected:
virtual bool contains(const position2d<s32> & position) const
{
position2d<s32> relative_pos(position - AbsoluteRect.getCenter());
return
AbsoluteClippingRect.isPointInside(position) &&
abs_( (float)relative_pos.X / RelativeRect.getWidth() ) + abs_( (float)relative_pos.Y / RelativeRect.getHeight() ) < 0.5;
}
bool m_down,
m_hover;
};
#endif // CGUIBUTTON2_H_INCLUDED
This is just a demonstration of the problem that I have no idea how to solve:
[main.cpp]
Code: Select all
#include <irr/irrlicht.h>
using namespace irr;
using namespace core;
using namespace gui;
using namespace video;
#include "CGUIButton2.h"
#include <iostream>
class CReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(SEvent event)
{
if(event.EventType == EET_GUI_EVENT)
{
if(event.GUIEvent.EventType == EGET_BUTTON_CLICKED)
{
std::cout << event.GUIEvent.Caller->getID() << std::endl;
return true;
}
}
return false;
}
};
int main()
{
IrrlichtDevice *device =
createDevice(EDT_SOFTWARE, dimension2d<s32>(512, 384), 16,
false, false, false, 0);
IGUIEnvironment * gui_env = device->getGUIEnvironment();
IVideoDriver * driver = device->getVideoDriver();
(new CGUIButton2(gui_env, rect<s32>(100, 100, 199, 199), 0, NULL))->drop();
(new CGUIButton2(gui_env, rect<s32>(200, 100, 299, 199), 1, NULL))->drop();
(new CGUIButton2(gui_env, rect<s32>(150, 150, 249, 249), 2, NULL))->drop();
CReceiver rec;
device->setEventReceiver(&rec);
while(device->run())
{
driver->beginScene(true, true, SColor(0, 122, 65, 171));
gui_env->drawAll();
driver->endScene();
device->yield();
}
device->drop();
return 0;
}
(put the mouse cursor over the bottom right corner of the left diamond, it doesnt light up)
~~Captain Pants
you return true if your element absorbed the event, otherwise return Parent->OnEvent if you have a parent, or false if not.- Is there any documentation about what the return value of OnEvent is used for/ should signify? (API says: returns true if event was processed) What the environment does when false is returned ?
for example, if you have a scrollbar inside an edit-box, you focus the scrollbar's button and then press the "x" key, the key isn't processed by the button so it is passed to the scrollbar (returns Parent->OnEvent), which also doesn't process it, so it is passed to the editbox which inserts the character x (and returns true).
The problem with non-rectangular elements is that getElementFromPoint is called recursively to identify which element is clicked. unfortunately this method is not virtual, so you can't override it in your custom elements.- Does anyone know of any custom gui elements I can get source for that use non-rectangular shapes?
I guess that the element it's self shouldn't really be concerned with it's shape, the skin should worry about this instead.
If anyone has suggestions on how to resolve this I'm all ears.
What speaks against making getElementFromPoint virtual? I did that here.
Edit: Thinking some more about it, I see that a better solution would to add a virtual method isPointInside to IGUIElement and using that instead of AbsoluteClippingRect.isPointInside(point)
This way it also wouldn't change the existing API but just add something.
Edit: Thinking some more about it, I see that a better solution would to add a virtual method isPointInside to IGUIElement and using that instead of AbsoluteClippingRect.isPointInside(point)
This way it also wouldn't change the existing API but just add something.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
-
- Posts: 19
- Joined: Wed Dec 06, 2006 10:15 am
Thanks guys, I personally went with changing the engine slightly to put this into IGUIElement. I can now override this for non-rectangular buttons.
A further question if anyones interested:
Why exactly is the argument to OnEvent passed by value and not by a const reference (or even non-const)? seeing as it is potentially copied a large number of times on its way up the tree, and it has several data members. (I imagine this is partly because it was done like this originally and noone wanted to change it and cause all gui elements to require the slight change)
Code: Select all
virtual bool isPointInside(const core::position2d<s32> & point) const
{
return AbsoluteClippingRect.isPointInside(point);
}
Why exactly is the argument to OnEvent passed by value and not by a const reference (or even non-const)? seeing as it is potentially copied a large number of times on its way up the tree, and it has several data members. (I imagine this is partly because it was done like this originally and noone wanted to change it and cause all gui elements to require the slight change)
Last edited by CaptainPants on Fri Jul 06, 2007 1:05 pm, edited 2 times in total.
~~Captain Pants
Yeah, hybrid changed it to a const reference for a while back about version 0.14, but changed it back again. I'm pretty sure it was reverted because it broke source compatibility... i.e. users would have to modify and recompile their code to get it to work with the new signature. Unfortunately, if it was done for compatibility reasons, it will have to wait because they just released version 1.3.1 and changes that break source compatibility usually are reserved for minor or major version releases.
Travis
Travis