Added much documentation and did some cleanup to GUI engine (hopefully if I'm hit by a meteorite, others will be able to continue working with the code)

git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/branches/irrlicht@3802 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
auria 2009-08-03 01:47:28 +00:00
parent 86e5f5c71d
commit 6b15d31d6b
9 changed files with 201 additions and 37 deletions

View File

@ -36,9 +36,27 @@ public:
bool isGameState(); bool isGameState();
void reshowTopMostMenu(); void reshowTopMostMenu();
// method to override in children /* ***********************************
* methods to override in children *
*********************************** */
/**
* callback called whenever escape was pressed (or any similar cancel operation)
*/
virtual void escapePressed() = 0; virtual void escapePressed() = 0;
/**
* Called every frame, to allow updating animations if there is any need.
*/
virtual void onUpdate(float elpased_time) = 0; virtual void onUpdate(float elpased_time) = 0;
/**
* will be called everytime sometimes happens.
* Events are generally a widget state change. In this case, a pointer to the said widget is passed along its
* name, so you get its new state and/or act. There are two special events, passed with a NULL widget, and which
* bear the anmes "init" and "tearDown", called respectively when a screen is being made visible and when it's
* being left, allowing for setup/clean-up.
*/
virtual void eventCallback(Widget* widget, const std::string& name) = 0; virtual void eventCallback(Widget* widget, const std::string& name) = 0;
}; };

View File

@ -178,6 +178,20 @@ PROP_SQUARE "square_items"
Valid on Ribbons or RibbonGrids. Can be "true" (omitting it means "false"). Indicates whether the contents Valid on Ribbons or RibbonGrids. Can be "true" (omitting it means "false"). Indicates whether the contents
use rectangular icons (this will affect the type of focus/highlighting used) use rectangular icons (this will affect the type of focus/highlighting used)
+--------------------------+
+ Using the Engine in Code +
+--------------------------+
The first thing to do is to derive a class of your own from AbstractStateManager. There are a few callbacks
you will need to override. Once it's done, you have all AbstractStateManager methods ready to be used to
push/pop/set menus on the screen stack.
Once you have instanciated your state manager class, call GUIEngine::init and pass it as argument.
One of the most important callbacks is 'eventCallback', which will be called everytime sometimes happens.
Events are generally a widget state change. In this case, a pointer to the said widget is passed along its
name, so you get its new state and/or act. There are two special events, passed with a NULL widget, and which
bear the anmes "init" and "tearDown", called respectively when a screen is being made visible and when it's
being left, allowing for setup/clean-up.
*/ */
#ifndef HEADER_ENGINE_HPP #ifndef HEADER_ENGINE_HPP
@ -224,11 +238,8 @@ namespace GUIEngine
void reshowCurrentScreen(); void reshowCurrentScreen();
void render(float dt); void render(float dt);
void transmitEvent(Widget* widget, std::string& name); void transmitEvent(Widget* widget, std::string& name);
void onUpdate(float elapsedTime);
} }
#endif #endif

View File

@ -34,8 +34,13 @@ namespace GUIEngine
class Widget; class Widget;
/** /**
* Class to handle irrLicht events (GUI and input as well) * Class to handle irrLicht events (GUI and input as well : input events will be redirected to the input
* Input events will be redirected to the input module * module in game mode). In menu mode, input is mapped to game actions with the help of the input
* module, then calls are made to move focus / trigger an event / etc.
*
* This is really only the irrLicht events bit, not to be confused with my own simple events dispatched
* mainly through AbstractStateManager, and also to widgets (this class is some kind of bridge between
* the base irrLicht GUI engine and the STK layer on top of it)
*/ */
class EventHandler : public IEventReceiver class EventHandler : public IEventReceiver
{ {

View File

@ -13,6 +13,10 @@ using namespace gui;
namespace GUIEngine namespace GUIEngine
{ {
/**
* The base irrLicht button, doesn't allow to stretch bitmaps inside it, so we "forked" it
* with a patch of our own.
*/
class MyGUIButton : public IGUIButton class MyGUIButton : public IGUIButton
{ {
public: public:

View File

@ -125,7 +125,7 @@ void Screen::calculateLayout(ptr_vector<Widget>& widgets, Widget* parent)
left_space -= (horizontal ? widgets[n].w : widgets[n].h); left_space -= (horizontal ? widgets[n].w : widgets[n].h);
} // next widget } // next widget
// lay widgets in row // ---- lay widgets in row
int x = parent->x, y = parent->y; int x = parent->x, y = parent->y;
for(int n=0; n<widgets_amount; n++) for(int n=0; n<widgets_amount; n++)
{ {
@ -235,6 +235,7 @@ void Screen::calculateLayout(ptr_vector<Widget>& widgets, Widget* parent)
} }
} }
// -----------------------------------------------------------------------------
#if 0 #if 0
#pragma mark - #pragma mark -
#pragma mark Adding/Removing widgets #pragma mark Adding/Removing widgets
@ -306,12 +307,13 @@ void Screen::manualAddWidget(Widget* w)
{ {
m_widgets.push_back(w); m_widgets.push_back(w);
} }
// -----------------------------------------------------------------------------
void Screen::manualRemoveWidget(Widget* w) void Screen::manualRemoveWidget(Widget* w)
{ {
m_widgets.remove(w); m_widgets.remove(w);
} }
// -----------------------------------------------------------------------------
#if 0 #if 0
#pragma mark - #pragma mark -
#pragma mark Getting widgets #pragma mark Getting widgets

View File

@ -41,6 +41,12 @@ namespace GUIEngine
void parseScreenFileDiv(irr::io::IrrXMLReader* xml, ptr_vector<Widget>& append_to); void parseScreenFileDiv(irr::io::IrrXMLReader* xml, ptr_vector<Widget>& append_to);
/**
* Represents a single screen. Mainly responsible of its children widgets; Screen lays them
* out, asks them to add themselves, asks them to remove themselves, etc.
*
* Also initiates the read of GUI files, even though most of that work is done in "screen_loader.cpp"
*/
class Screen class Screen
{ {
friend class Skin; friend class Skin;

View File

@ -33,6 +33,11 @@ using namespace io;
using namespace gui; using namespace gui;
using namespace GUIEngine; using namespace GUIEngine;
/**
* Loads a GUI screen from its XML file. Builds a hierarchy of Widget objects whose
* contents are a direct transcription of the XML file, with little analysis or layout
* performed on them.
*/
namespace GUIEngine namespace GUIEngine
{ {

View File

@ -29,6 +29,75 @@ using namespace video;
using namespace io; using namespace io;
using namespace gui; using namespace gui;
/**
Here lies the skin handling. It loads images and their sizing from a XML file.
Since the irrLicht way of handling skin is quite "boxy" and results in games
looking like Window 95, this class overrides it very much; in pretty much all
callbacks, rather drawing plainly what irrLicht asks it to draw, it first
checks which widget we're asked to render and redirects the call to a more
specific method.
Furthermore, since irrLicht widgets were quite basic, a few widgets were created
by combining several irrLicht widgets (e.g. 2 buttons and a label in a box make
a spinner). Because of this, some jumping through hoops is performed (we get a
callback for each of these sub-widgets, but want to draw the whole thing as a single
block)
= Rendering =
There are two types of images : some will be simply stretched as a whole, others will
have non-stretchable borders (you cannot choose which one you must use, it's hardcoded
for each element type; though, as you will see below, for all "advanced stretching" images
you can easily fake "simple stretch")
All elements will have at least 2 properties :
type="X" sets what you're skinning with this entry
image="skinDirectory/imageName.png" sets which image is used for this element
Most elements also support states :
state="neutral"
state="focused"
state="down"
You can thus give different looks for different states. Not all widgets support all states,
see entries and comments below to know what's supported.
Note that checkboxes are an exception and have the following styles :
"neutral+unchecked"
"neutral+checked"
"focused+unchecked"
"focused+checked"
"Advanced stretching" images are split this way :
+----+--------------------+----+
| | | |
+----+--------------------+----+
| | | |
| | | |
| | | |
+----+--------------------+----+
| | | |
+----+--------------------+----+
The center border will be stretched in all directions. The 4 corners will not stretch at all.
Horizontal borders will stretch horizontally, verticallt borders will stretch vertically.
Use properties left_border="X" right_border="X" top_border="X" bottom_border="X" to specify
the size of each border in pixels (setting all borders to '0' makes the whole image scaled).
In some cases, you may not want vertical stretching to occur (like if the left and right sides
of the image must not be stretched vertically, e.g. for the spinner). In this case, pass
parameter preserve_h_aspect_ratios="true" to make the left and right areas stretch by keeping
their aspect ratio.
Some components may fill the full inner area with stuff; others will only take a smaller
area at the center. To adjust for this, there are properties "hborder_out_portion" and "vborder_out_portion"
that take a float from 0 to 1, representing the percentage of each border that goes out of the widget's
area (this might include stuff like shadows, etc.). The 'h' one is for horizontal borders,
the 'v' one is for vertical borders.
Finnally : the image is split, as shown above, into 9 areas. In osme cases, you may not want
all areas to be rendered. Then you can pass parameter areas="body+left+right+top+bottom"
and explicitely specify which parts you want to see. The 4 corner areas are only visible
when the border that intersect at this corner are enabled.
*/
namespace GUIEngine namespace GUIEngine
{ {

View File

@ -75,6 +75,18 @@ namespace GUIEngine
PROP_SQUARE PROP_SQUARE
}; };
/**
* The nearly-abstract base of all widgets (not fully abstract since a bare Widget
* can be created for the sore goal of containing children widgets in a group)
*
* Provides basic common functionnality, as well as providing a few callbacks
* for children to override if they need to do something special on event.
*
* Each widget may have an irrlicht parent (most often used to put widgets in dialogs)
* and also optionally one or many children.
*
* Each widget also has a set of properties stored in a ma (see enum above)
*/
class Widget : public SkinWidgetContainer class Widget : public SkinWidgetContainer
{ {
friend class EventHandler; friend class EventHandler;
@ -85,6 +97,11 @@ namespace GUIEngine
friend class Skin; friend class Skin;
friend class RibbonGridWidget; friend class RibbonGridWidget;
/**
* These methods provide new unique IDs each time you call them.
* Since IDs are used to determine tabbing order, "non-tabbable"
* objects are being given very different IDs so that they don't interfere.
*/
int getNewID(); int getNewID();
int getNewNoFocusID(); int getNewNoFocusID();
@ -111,10 +128,26 @@ namespace GUIEngine
/** override in children if you need to know when the widget is focused */ /** override in children if you need to know when the widget is focused */
virtual void focused() {} virtual void focused() {}
/**
* The XML loader stored coords in their raw string form inside this widget.
* This method parses the strings. Most notably, expands coords relative to parent
* and calculates percentages.
*/
void readCoords(Widget* parent=NULL); void readCoords(Widget* parent=NULL);
/**
* An irrlicht parent (most often used to put widgets in dialogs)
*/
IGUIElement* m_parent; IGUIElement* m_parent;
/**
* Receives as string the raw property value retrieved from XML file.
* Will try to make sense of it, as an absolute value or a percentage.
*
* Return values :
* Will write to either absolute or percentage, depending on the case.
* Returns false if couldn't convert to either
*/
static bool convertToCoord(std::string& x, int* absolute, int* percentage); static bool convertToCoord(std::string& x, int* absolute, int* percentage);
/** /**
@ -122,6 +155,14 @@ namespace GUIEngine
*/ */
IGUIElement* m_element; IGUIElement* m_element;
// FIXME... i forgot the m_ everywhere ... XD
/** numerical ID used by irrLicht to identify this widget
* (not the same as the string identificator specified in the XML file)
*/
int id;
public: public:
/** /**
* This is set to NULL by default; set to something else in a widget to mean * This is set to NULL by default; set to something else in a widget to mean
@ -132,11 +173,20 @@ namespace GUIEngine
*/ */
Widget* m_event_handler; Widget* m_event_handler;
/** Coordinates of the widget */
int x, y, w, h;
/** Whether to show a bounding box around this widget (used for sections) */
bool m_show_bounding_box;
Widget(); Widget();
virtual ~Widget() {} virtual ~Widget() {}
bool m_show_bounding_box; /**
* Get the underlying irrLicht GUI element, casted to the right type.
*/
template<typename T> T* getIrrlichtElement() template<typename T> T* getIrrlichtElement()
{ {
#if defined(WIN32) || defined(NDEBUG) #if defined(WIN32) || defined(NDEBUG)
@ -149,21 +199,6 @@ namespace GUIEngine
IGUIElement* getIrrlichtElement() { return m_element; } IGUIElement* getIrrlichtElement() { return m_element; }
virtual void update(float delta) { }
/** All widgets, including their parents (m_event_handler) will be notified on event through
this call. Must return whether main (GUI engine user) event callback should be notified or not.
Note that in the case of a hierarchy of widgets (with m_event_handler), only the topmost widget
of the chain decides whether the main handler is notified; return value is not read for others. */
virtual bool transmitEvent(Widget* w, std::string& originator) { return true; }
/**
* Create and add the irrLicht widget(s) associated with this object.
* Call after Widget was read from XML file and laid out.
*/
virtual void add() {}
void setParent(IGUIElement* parent); void setParent(IGUIElement* parent);
/** /**
@ -177,16 +212,6 @@ namespace GUIEngine
/** Type of this widget */ /** Type of this widget */
WidgetType m_type; WidgetType m_type;
// FIXME... i forgot the m_ everywhere ... XD
/** coordinates of the widget */
int x, y, w, h;
/** numerical ID used by irrLicht to identify this widget
* (not the same as the string identificator specified in the XML file)
*/
int id;
/** A map that holds values for all specified widget properties (in the XML file)*/ /** A map that holds values for all specified widget properties (in the XML file)*/
std::map<Property, std::string> m_properties; std::map<Property, std::string> m_properties;
@ -199,6 +224,25 @@ namespace GUIEngine
bool isSelected() const { return m_selected; } bool isSelected() const { return m_selected; }
/**
* Override in children to possibly receive updates (you may need to register to
* them first)
*/
virtual void update(float delta) { }
/** All widgets, including their parents (m_event_handler) will be notified on event through
this call. Must return whether main (GUI engine user) event callback should be notified or not.
Note that in the case of a hierarchy of widgets (with m_event_handler), only the topmost widget
of the chain decides whether the main handler is notified; return value is not read for others. */
virtual bool transmitEvent(Widget* w, std::string& originator) { return true; }
/**
* Create and add the irrLicht widget(s) associated with this object.
* Call after Widget was read from XML file and laid out.
*/
virtual void add() {};
}; };