0ad/source/gui/CGUI.cpp

1451 lines
34 KiB
C++
Raw Normal View History

/*
CGUI
by Gustav Larsson
gee@pyro.nu
*/
#include "precompiled.h"
#include "GUI.h"
2004-05-29 06:06:50 +02:00
// Types - when including them into the engine.
#include "CButton.h"
#include "CText.h"
#include "CCheckBox.h"
#include "CRadioButton.h"
#include "ps/Xeromyces.h"
2003-11-24 03:18:41 +01:00
2003-12-01 08:06:55 +01:00
#include "Prometheus.h"
2003-11-24 03:18:41 +01:00
#include "input.h"
2004-05-29 06:06:50 +02:00
#include "OverlayText.h"
// TODO Gee: Whatever include CRect/CPos/CSize
#include "Overlay.h"
#include "scripting/ScriptingHost.h"
#include <string>
#include <assert.h>
#include <stdarg.h>
// namespaces used
using namespace std;
#include "ps/CLogger.h"
#define XERO_TIME
// Class for global JavaScript object
JSClass GUIClass = {
"GUIClass", 0,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
};
extern int g_xres, g_yres;
2004-05-29 06:06:50 +02:00
// TODO Gee: how to draw overlays?
void render(COverlayText* overlaytext)
{
}
2003-11-22 16:07:22 +01:00
//-------------------------------------------------------------------
// called from main loop when (input) events are received.
// event is passed to other handlers if false is returned.
// trampoline: we don't want to make the implementation (in CGUI) static
//-------------------------------------------------------------------
int gui_handler(const SDL_Event* ev)
2003-11-05 00:41:42 +01:00
{
return g_GUI.HandleEvent(ev);
2003-11-05 00:41:42 +01:00
}
int CGUI::HandleEvent(const SDL_Event* ev)
{
if(ev->type == SDL_MOUSEMOTION)
{
m_MousePos = CPos(ev->motion.x, ev->motion.y);
// pNearest will after this point at the hovered object, possibly NULL
GUI<SGUIMessage>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::HandleMessage,
SGUIMessage(GUIM_MOUSE_MOTION));
}
2004-05-29 06:06:50 +02:00
// TODO Gee: temp-stuff
// char buf[30];
// sprintf(buf, "type = %d", ev->type);
2004-05-29 06:06:50 +02:00
//TEMPmessage = buf;
// Update m_MouseButtons. (BUTTONUP is handled later.)
if (ev->type == SDL_MOUSEBUTTONDOWN)
{
// (0,1,2) = (LMB,RMB,MMB)
if (ev->button.button < 3)
m_MouseButtons |= (1 << ev->button.button);
}
// JW: (pre|post)process omitted; what're they for? why would we need any special button_released handling?
// Only one object can be hovered
2003-11-24 03:18:41 +01:00
IGUIObject *pNearest = NULL;
2003-11-25 03:47:12 +01:00
try
{
2004-05-29 06:06:50 +02:00
// TODO Gee: Optimizations needed!
// these two recursive function are quite overhead heavy.
2003-11-25 03:47:12 +01:00
// pNearest will after this point at the hovered object, possibly NULL
GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
2003-11-25 03:47:12 +01:00
&IGUIObject::ChooseMouseOverAndClosest,
pNearest);
// Now we'll call UpdateMouseOver on *all* objects,
// we'll input the one hovered, and they will each
// update their own data and send messages accordingly
GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
2003-11-25 03:47:12 +01:00
&IGUIObject::UpdateMouseOver,
pNearest);
if (ev->type == SDL_MOUSEBUTTONDOWN)
2003-11-25 03:47:12 +01:00
{
switch (ev->button.button)
2003-11-25 03:47:12 +01:00
{
2004-05-29 06:06:50 +02:00
case SDL_BUTTON_LEFT:
if (pNearest)
{
2004-05-29 06:06:50 +02:00
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_LEFT));
}
2004-05-29 06:06:50 +02:00
{
// some temp
/* CClientArea ca;
bool hidden;
2004-05-29 06:06:50 +02:00
GUI<CClientArea>::GetSetting(*this, CStr("backdrop43"), CStr("size"), ca);
//hidden = !hidden;
ca.pixel.right -= 3;
2004-05-29 06:06:50 +02:00
GUI<CClientArea>::SetSetting(*this, CStr("backdrop43"), CStr("size"), ca);
*/ }
break;
case SDL_BUTTON_WHEELDOWN: // wheel down
2004-05-29 06:06:50 +02:00
if (pNearest)
{
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_DOWN));
}
2004-05-29 06:06:50 +02:00
break;
case SDL_BUTTON_WHEELUP: // wheel up
2004-05-29 06:06:50 +02:00
if (pNearest)
{
2004-05-29 06:06:50 +02:00
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_UP));
}
2004-05-29 06:06:50 +02:00
break;
// TODO Gee: Just temp
case SDL_BUTTON_RIGHT:
{
CClientArea ca;
GUI<CClientArea>::GetSetting(*this, CStr("backdrop43"), CStr("size"), ca);
//hidden = !hidden;
ca.pixel.right -= 3;
2004-05-29 06:06:50 +02:00
GUI<CClientArea>::SetSetting(*this, CStr("backdrop43"), CStr("size"), ca);
}
break;
2003-11-25 03:47:12 +01:00
2004-05-29 06:06:50 +02:00
default:
break;
}
}
else
if (ev->type == SDL_MOUSEBUTTONUP)
2004-05-29 06:06:50 +02:00
{
if (ev->button.button == SDL_BUTTON_LEFT)
2004-05-29 06:06:50 +02:00
{
if (pNearest)
2004-05-29 06:06:50 +02:00
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_LEFT));
}
2004-05-29 06:06:50 +02:00
// Reset all states on all visible objects
GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject,
&IGUIObject::ResetStates);
// It will have reset the mouse over of the current hovered, so we'll
// have to restore that
if (pNearest)
pNearest->m_MouseHovering = true;
2003-11-25 03:47:12 +01:00
}
}
catch (PS_RESULT e)
{
UNUSED(e);
// TODO Gee: Handle
2003-11-25 03:47:12 +01:00
}
// JW: what's the difference between mPress and mDown? what's the code below responsible for?
/*
// Generally if just mouse is clicked
if (m_pInput->mDown(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT);
}
*/
// BUTTONUP's effect on m_MouseButtons is handled after
// everything else, so that e.g. 'press' handlers (activated
// on button up) see which mouse button had been pressed.
if (ev->type == SDL_MOUSEBUTTONUP)
{
// (0,1,2) = (LMB,RMB,MMB)
if (ev->button.button < 3)
m_MouseButtons &= ~(1 << ev->button.button);
}
return EV_PASS;
}
//-------------------------------------------------------------------
// Constructor / Destructor
//-------------------------------------------------------------------
CGUI::CGUI() : m_InternalNameNumber(0), m_MouseButtons(0)
{
m_BaseObject = new CGUIDummyObject;
m_BaseObject->SetGUI(this);
// Construct the parent object for all GUI JavaScript things
m_ScriptObject = (void*)JS_NewObject(g_ScriptingHost.getContext(), &GUIClass, NULL, NULL);
assert(m_ScriptObject != NULL); // How should it handle errors?
JS_AddRoot(g_ScriptingHost.getContext(), &m_ScriptObject);
// This will make this invisible, not add
//m_BaseObject->SetName(BASE_OBJECT_NAME);
}
CGUI::~CGUI()
{
if (m_BaseObject)
delete m_BaseObject;
if (m_ScriptObject)
// Let it be garbage-collected
JS_RemoveRoot(g_ScriptingHost.getContext(), &m_ScriptObject);
}
2003-11-22 16:07:22 +01:00
//-------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------
IGUIObject *CGUI::ConstructObject(const CStr& str)
{
if (m_ObjectTypes.count(str) > 0)
return (*m_ObjectTypes[str])();
else
2003-11-24 03:18:41 +01:00
{
// TODO Gee: Report in log
return NULL;
2003-11-24 03:18:41 +01:00
}
}
2003-11-24 03:18:41 +01:00
void CGUI::Initialize()
{
// Add base types!
2003-11-24 03:18:41 +01:00
// You can also add types outside the GUI to extend the flexibility of the GUI.
// Prometheus though will have all the object types inserted from here.
2004-05-29 06:06:50 +02:00
AddObjectType("empty", &CGUIDummyObject::ConstructObject);
AddObjectType("button", &CButton::ConstructObject);
AddObjectType("text", &CText::ConstructObject);
AddObjectType("checkbox", &CCheckBox::ConstructObject);
AddObjectType("radiobutton", &CRadioButton::ConstructObject);
}
void CGUI::Process()
{
/*
// TODO Gee: check if m_pInput is valid, otherwise return
/// assert(m_pInput);
// Pre-process all objects
try
{
2003-11-24 03:18:41 +01:00
GUI<EGUIMessage>::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_PREPROCESS);
}
catch (PS_RESULT e)
{
return;
}
// Check mouse over
try
{
// Only one object can be hovered
// check which one it is, if any !
2003-11-24 03:18:41 +01:00
IGUIObject *pNearest = NULL;
2003-11-24 03:18:41 +01:00
GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest);
// Now we'll call UpdateMouseOver on *all* objects,
// we'll input the one hovered, and they will each
// update their own data and send messages accordingly
2003-11-24 03:18:41 +01:00
GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest);
// If pressed
if (m_pInput->mPress(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_PRESS_LEFT);
}
else
// If released
if (m_pInput->mRelease(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_RELEASE_LEFT);
}
// Generally if just mouse is clicked
if (m_pInput->mDown(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT);
}
}
catch (PS_RESULT e)
{
return;
}
// Post-process all objects
try
{
2003-11-24 03:18:41 +01:00
GUI<EGUIMessage>::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_POSTPROCESS);
}
catch (PS_RESULT e)
{
return;
}
*/
}
void CGUI::Draw()
{
// Clear the depth buffer, so the GUI is
// drawn on top of everything else
glClear(GL_DEPTH_BUFFER_BIT);
2003-11-24 03:18:41 +01:00
glPushMatrix();
glLoadIdentity();
// Adapt (origio) to being in top left corner and down
// just like the mouse position
glTranslatef(0.0f, (GLfloat)g_yres, -1000.0f);
2003-11-24 03:18:41 +01:00
glScalef(1.0f, -1.f, 1.0f);
try
{
2003-11-24 03:18:41 +01:00
// Recurse IGUIObject::Draw() with restriction: hidden
// meaning all hidden objects won't call Draw (nor will it recurse its children)
GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw);
}
catch (PS_RESULT e)
{
UNUSED(e);
2003-11-24 03:18:41 +01:00
glPopMatrix();
// TODO Gee: Report error.
return;
}
2003-11-24 03:18:41 +01:00
glPopMatrix();
}
void CGUI::DrawSprite(const CStr& SpriteName,
2003-12-01 08:06:55 +01:00
const float &Z,
const CRect &Rect,
const CRect &Clipping)
{
// This is not an error, it's just a choice not to draw any sprite.
2004-05-29 06:06:50 +02:00
if (SpriteName == CStr())
2003-12-01 08:06:55 +01:00
return;
2004-05-29 06:06:50 +02:00
const char * buf = SpriteName;
bool DoClipping = (Clipping != CRect());
2003-12-01 08:06:55 +01:00
CGUISprite Sprite;
// Fetch real sprite from name
if (m_Sprites.count(SpriteName) == 0)
{
// TODO Gee: Report error
2003-12-01 08:06:55 +01:00
return;
}
else Sprite = m_Sprites[SpriteName];
glPushMatrix();
glTranslatef(0.0f, 0.0f, Z);
// Iterate all images and request them being drawn be the
// CRenderer
std::vector<SGUIImage>::const_iterator cit;
for (cit=Sprite.m_Images.begin(); cit!=Sprite.m_Images.end(); ++cit)
{
if (cit->m_Texture)
{
// HACK: Please ignore all this texture code
glEnable(GL_TEXTURE_2D);
glDisable(GL_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
// Urgh
Handle x = tex_load(cit->m_TextureName);
assert(x>0);
tex_upload(x);
tex_bind(x);
glColor3f(1.0f, 1.0f, 1.0f);
}
else
{
glColor3f(cit->m_BackColor.r , cit->m_BackColor.g, cit->m_BackColor.b);
}
2003-12-01 08:06:55 +01:00
CRect real = cit->m_Size.GetClientArea(Rect);
2004-05-29 06:06:50 +02:00
glPushMatrix();
glTranslatef(0.f, 0.f, cit->m_DeltaZ);
2003-12-01 08:06:55 +01:00
//glColor3f((float)real.right/1000.f, 0.5f, 0.5f);
// Do this
glBegin(GL_QUADS);
//glTexCoord2i(0, 1);
glVertex2i(real.right, real.bottom);
//glTexCoord2i(1, 1);
glVertex2i(real.left, real.bottom);
//glTexCoord2i(1, 0);
glVertex2i(real.left, real.top);
//glTexCoord2i(0, 0);
glVertex2i(real.right, real.top);
2003-12-01 08:06:55 +01:00
glEnd();
2004-05-29 06:06:50 +02:00
glPopMatrix();
if (cit->m_Texture)
{
glDisable(GL_TEXTURE_2D);
}
2003-12-01 08:06:55 +01:00
}
glPopMatrix();
}
void CGUI::Destroy()
{
// We can use the map to delete all
2003-11-22 16:07:22 +01:00
// now we don't want to cancel all if one Destroy fails
map_pObjects::iterator it;
for (it = m_pAllObjects.begin(); it != m_pAllObjects.end(); ++it)
{
try
{
it->second->Destroy();
}
catch (PS_RESULT e)
{
UNUSED(e);
// TODO Gee: Handle
}
delete it->second;
}
// Clear all
m_pAllObjects.clear();
m_Sprites.clear();
}
2003-11-24 18:13:37 +01:00
void CGUI::UpdateResolution()
{
2003-11-25 03:47:12 +01:00
// Update ALL cached
2003-11-24 18:13:37 +01:00
GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize );
}
2003-11-24 03:18:41 +01:00
void CGUI::AddObject(IGUIObject* pObject)
{
2003-12-01 08:06:55 +01:00
try
{
// Add CGUI pointer
2003-11-24 03:18:41 +01:00
GUI<CGUI*>::RecurseObject(0, pObject, &IGUIObject::SetGUI, this);
// Add child to base object
m_BaseObject->AddChild(pObject);
2003-11-25 03:47:12 +01:00
// Cache tree
GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize);
2004-05-29 06:06:50 +02:00
// Loaded
GUI<SGUIMessage>::RecurseObject(0, pObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_LOAD));
2003-12-01 08:06:55 +01:00
}
catch (PS_RESULT e)
{
throw e;
2003-12-01 08:06:55 +01:00
}
}
void CGUI::UpdateObjects()
{
// We'll fill a temporary map until we know everything
// succeeded
map_pObjects AllObjects;
try
{
// Fill freshly
2003-11-24 03:18:41 +01:00
GUI< map_pObjects >::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects );
}
catch (PS_RESULT e)
{
// Throw the same error
throw e;
}
// Else actually update the real one
m_pAllObjects = AllObjects;
}
bool CGUI::ObjectExists(const CStr& Name) const
{
return m_pAllObjects.count(Name) != 0;
2004-05-29 06:06:50 +02:00
}
IGUIObject* CGUI::FindObjectByName(const CStr& Name) const
{
map_pObjects::const_iterator it = m_pAllObjects.find(Name);
if (it == m_pAllObjects.end())
return NULL;
else
return it->second;
}
2004-05-29 06:06:50 +02:00
// private struct used only in GenerateText(...)
struct SGenerateTextImage
2004-05-29 06:06:50 +02:00
{
int m_YFrom, // The images starting location in Y
m_YTo, // The images end location in Y
m_Indentation; // The image width in other words
// Some help functions
// TODO Gee: CRect => CPoint ?
void SetupSpriteCall(const bool &Left, SGUIText::SSpriteCall &SpriteCall,
const int &width, const int &y,
const CSize &Size, const CStr& TextureName,
2004-05-29 06:06:50 +02:00
const int &BufferZone)
{
// TODO Gee: Temp hardcoded values
SpriteCall.m_Area.top = y+BufferZone;
SpriteCall.m_Area.bottom = y+BufferZone + Size.cy;
if (Left)
{
SpriteCall.m_Area.left = BufferZone;
SpriteCall.m_Area.right = Size.cx+BufferZone;
}
else
{
SpriteCall.m_Area.left = width-BufferZone - Size.cx;
SpriteCall.m_Area.right = width-BufferZone;
}
SpriteCall.m_TextureName = TextureName;
m_YFrom = SpriteCall.m_Area.top-BufferZone;
m_YTo = SpriteCall.m_Area.bottom+BufferZone;
m_Indentation = Size.cx+BufferZone*2;
}
};
SGUIText CGUI::GenerateText(const CGUIString &string, /*const CColor &Color, */
const CStr& Font, const int &Width, const int &BufferZone)
2004-05-29 06:06:50 +02:00
{
SGUIText Text; // object we're generating
if (string.m_Words.size() == 0)
return Text;
int x=BufferZone, y=BufferZone; // drawing pointer
int from=0;
bool done=false;
// Images on the left or the right side.
vector<SGenerateTextImage> Images[2];
int pos_last_img=-1; // Position in the string where last img (either left or right) were encountered.
// in order to avoid duplicate processing.
// Easier to read.
bool WordWrapping = (Width != 0);
// Go through string word by word
for (int i=0; i<(int)string.m_Words.size()-1 && !done; ++i)
2004-05-29 06:06:50 +02:00
{
// Pre-process each line one time, so we know which floating images
// will be added for that line.
// Generated stuff is stored in Feedback.
CGUIString::SFeedback Feedback;
// Preliminary line_height, used for word-wrapping with floating images.
int prelim_line_height=0;
// Width and height of all text calls generated.
string.GenerateTextCall(Feedback, Font, /*CColor(),*/
string.m_Words[i], string.m_Words[i+1]);
// Loop through our images queues, to see if images has been added.
// Check if this has already been processed.
// Also, floating images are only applicable if Word-Wrapping is on
if (WordWrapping && i > pos_last_img)
{
// Loop left/right
for (int j=0; j<2; ++j)
{
for (vector<CStr>::const_iterator it = Feedback.m_Images[j].begin();
it != Feedback.m_Images[j].end();
++it)
{
SGUIText::SSpriteCall SpriteCall;
SGenerateTextImage Image;
// Y is if no other floating images is above, y. Else it is placed
// after the last image, like a stack downwards.
int _y;
if (Images[j].size() > 0)
_y = max(y, Images[j].back().m_YTo);
else
_y = y;
// TODO Gee: CSize temp
CSize size; size.cx = 100; size.cy = 100;
Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, CStr("white-border"), BufferZone);
// Check if image is the lowest thing.
Text.m_Size.cy = max(Text.m_Size.cy, Image.m_YTo);
Images[j].push_back(Image);
Text.m_SpriteCalls.push_back(SpriteCall);
}
}
}
pos_last_img = max(pos_last_img, i);
x += Feedback.m_Size.cx;
prelim_line_height = max(prelim_line_height, Feedback.m_Size.cy);
// If Width is 0, then there's no word-wrapping, disable NewLine.
if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == string.m_Words.size()-2)
{
// Change from to i, but first keep a copy of its value.
int temp_from = from;
from = i;
static const int From=0, To=1;
//int width_from=0, width_to=width;
int width_range[2];
width_range[From] = BufferZone;
width_range[To] = Width - BufferZone;
// Floating images are only appicable if word-wrapping is enabled.
if (WordWrapping)
{
// Decide width of the line. We need to iterate our floating images.
// this won't be exact because we're assuming the line_height
// will be as our preliminary calculation said. But that may change,
// although we'd have to add a couple of more loops to try straightening
// this problem out, and it is very unlikely to happen noticably if one
// stuctures his text in a stylistically pure fashion. Even if not, it
// is still quite unlikely it will happen.
// Loop through left and right side, from and to.
for (int j=0; j<2; ++j)
{
for (vector<SGenerateTextImage>::const_iterator it = Images[j].begin();
it != Images[j].end();
++it)
{
// We're working with two intervals here, the image's and the line height's.
// let's find the union of these two.
int union_from, union_to;
union_from = max(y, it->m_YFrom);
union_to = min(y+prelim_line_height, it->m_YTo);
// The union is not �
if (union_to > union_from)
{
if (j == From)
width_range[From] = max(width_range[From], it->m_Indentation);
else
width_range[To] = min(width_range[To], Width - it->m_Indentation);
}
}
}
}
// Reset X for the next loop
x = width_range[From];
// Now we'll do another loop to figure out the height of
// the line (the height of the largest character). This
// couldn't be determined in the first loop (main loop)
// because it didn't regard images, so we don't know
// if all characters processed, will actually be involved
// in that line.
int line_height=0;
for (int j=temp_from; j<=i; ++j)
{
// We don't want to use Feedback now, so we'll have to use
// another.
CGUIString::SFeedback Feedback2;
string.GenerateTextCall(Feedback2, Font, /*CColor(),*/
string.m_Words[j], string.m_Words[j+1]);
// Append X value.
x += Feedback2.m_Size.cx;
if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine)
break;
// Let line_height be the maximum m_Height we encounter.
line_height = max(line_height, Feedback2.m_Size.cy);
if (WordWrapping && Feedback2.m_NewLine)
break;
}
// Reset x once more
x = width_range[From];
// Do the real processing now
for (int j=temp_from; j<=i; ++j)
{
// We don't want to use Feedback now, so we'll have to use
// another.
CGUIString::SFeedback Feedback2;
// Defaults
string.GenerateTextCall(Feedback2, Font, /*Color, */
string.m_Words[j], string.m_Words[j+1]);
// Iterate all and set X/Y values
// Since X values are not set, we need to make an internal
// iteration with an increment that will append the internal
// x, that is what x_pointer is for.
int x_pointer=0;
vector<SGUIText::STextCall>::iterator it;
for (it = Feedback2.m_TextCalls.begin(); it != Feedback2.m_TextCalls.end(); ++it)
{
it->m_Pos = CPos(x + x_pointer, y + line_height - it->m_Size.cy);
x_pointer += it->m_Size.cx;
if (it->m_pSpriteCall)
{
it->m_pSpriteCall->m_Area =
it->m_pSpriteCall->m_Area + it->m_Pos;
}
}
// Append X value.
x += Feedback2.m_Size.cx;
Text.m_Size.cx = max(Text.m_Size.cx, x+BufferZone);
// The first word overrides the width limit, that we
// do in those cases, are just draw that word even
// though it'll extend the object.
if (WordWrapping) // only if word-wrapping is applicable
{
if (Feedback2.m_NewLine)
{
from = j+1;
break;
}
else
if (x > width_range[To] && j==temp_from)
{
from = j+1;
// do not break, since we want it to be added to m_TextCalls
}
else
if (x > width_range[To])
{
from = j;
break;
}
}
// Add the whole Feedback2.m_TextCalls to our m_TextCalls.
Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end());
Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
if (j == string.m_Words.size()-2)
done = true;
}
// Reset X, and append Y.
x = 0;
y += line_height;
// Update height of all
Text.m_Size.cy = max(Text.m_Size.cy, y+BufferZone);
// Now if we entered as from = i, then we want
// i being one minus that, so that it will become
// the same i in the next loop. The difference is that
// we're on a new line now.
i = from-1;
}
}
return Text;
}
void CGUI::DrawText(const SGUIText &Text, const CColor &DefaultColor,
const CPos &pos, const float &z)
{
for (vector<SGUIText::STextCall>::const_iterator it=Text.m_TextCalls.begin();
it!=Text.m_TextCalls.end();
++it)
{
if (it->m_pSpriteCall)
continue;
COverlayText txt((float)pos.x+it->m_Pos.x, (float)pos.y+it->m_Pos.y,
(int)z, it->m_Font, it->m_String,
2004-05-29 06:06:50 +02:00
(it->m_UseCustomColor?it->m_Color:DefaultColor));
render(&txt);
}
for (vector<SGUIText::SSpriteCall>::const_iterator it=Text.m_SpriteCalls.begin();
it!=Text.m_SpriteCalls.end();
++it)
{
DrawSprite(it->m_TextureName, z, it->m_Area + pos);
}
}
void CGUI::ReportParseError(const CStr& str, ...)
{
// Print header
if (m_Errors==0)
{
/// g_nemLog("*** GUI Tree Creation Errors");
}
2004-05-29 06:06:50 +02:00
// Important, set ParseError to true
++m_Errors;
/* TODO Gee: (MEGA)
char buffer[512];
va_list args;
// get arguments
va_start(args, str);
vsprintf(buffer, str.c_str(), args);
va_end(args);
*/
/// g_nemLog(" %s", buffer);
}
2003-11-24 03:18:41 +01:00
/**
* @callgraph
*/
void CGUI::LoadXMLFile(const string &Filename)
{
// Reset parse error
// we can later check if this has increased
m_Errors = 0;
CXeromyces XeroFile;
XeroFile.Load(Filename.c_str());
bool ParseFailed = false;
XMBElement node = XeroFile.getRoot();
2003-11-25 03:47:12 +01:00
// Check root element's (node) name so we know what kind of
// data we'll be expecting
std::string root_name = XeroFile.getElementString(node.getNodeName());
if (root_name == "objects")
{
Xeromyces_ReadRootObjects(node, &XeroFile);
// Re-cache all values so these gets cached too.
//UpdateResolution();
}
else
if (root_name == "sprites")
{
Xeromyces_ReadRootSprites(node, &XeroFile);
}
else
if (root_name == "styles")
{
Xeromyces_ReadRootStyles(node, &XeroFile);
}
else
if (root_name == "setup")
{
Xeromyces_ReadRootSetup(node, &XeroFile);
}
else
{
// TODO Gee: Output in log
}
// Now report if any other errors occured
if (m_Errors > 0)
{
/// g_console.submit("echo GUI Tree Creation Reports %d errors", m_Errors);
}
}
//===================================================================
// XML Reading Xeromyces Specific Sub-Routines
//===================================================================
void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile)
{
// Iterate main children
// they should all be <object> elements
XMBElementList children = Element.getChildNodes();
for (int i=0; i<children.Count; ++i)
{
//debug_out("Object %d\n", i);
XMBElement child = children.item(i);
// Read in this whole object into the GUI
Xeromyces_ReadObject(child, pFile, m_BaseObject);
}
}
void CGUI::Xeromyces_ReadRootSprites(XMBElement Element, CXeromyces* pFile)
{
// Iterate main children
// they should all be <sprite> elements
XMBElementList children = Element.getChildNodes();
for (int i=0; i<children.Count; ++i)
{
XMBElement child = children.item(i);
// Read in this whole object into the GUI
Xeromyces_ReadSprite(child, pFile);
}
}
void CGUI::Xeromyces_ReadRootStyles(XMBElement Element, CXeromyces* pFile)
2003-12-01 08:06:55 +01:00
{
// Iterate main children
// they should all be <styles> elements
XMBElementList children = Element.getChildNodes();
for (int i=0; i<children.Count; ++i)
2003-12-01 08:06:55 +01:00
{
XMBElement child = children.item(i);
2003-12-01 08:06:55 +01:00
// Read in this whole object into the GUI
Xeromyces_ReadStyle(child, pFile);
2003-12-01 08:06:55 +01:00
}
}
void CGUI::Xeromyces_ReadRootSetup(XMBElement Element, CXeromyces* pFile)
{
// Iterate main children
// they should all be <icon>, <scrollbar> or <tooltip>.
XMBElementList children = Element.getChildNodes();
for (int i=0; i<children.Count; ++i)
{
XMBElement child = children.item(i);
// Read in this whole object into the GUI
std::string name = pFile->getElementString(child.getNodeName());
if (name == "scrollbar")
{
Xeromyces_ReadScrollBarStyle(child, pFile);
}
// No need for else, we're using DTD.
}
}
void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject *pParent)
{
assert(pParent);
int i;
// Our object we are going to create
2003-11-24 03:18:41 +01:00
IGUIObject *object = NULL;
XMBAttributeList attributes = Element.getAttributes();
// Well first of all we need to determine the type
utf16string type = attributes.getNamedItem( pFile->getAttributeID("type") );
// Construct object from specified type
// henceforth, we need to do a rollback before aborting.
// i.e. releasing this object
object = ConstructObject((CStr)type);
if (!object)
{
// Report error that object was unsuccessfully loaded
ReportParseError(CStr("Unrecognized type: ") + CStr(type));
delete object;
return;
}
// Cache some IDs for element attribute names, to avoid string comparisons
#define ELMT(x) int elmt_##x = pFile->getElementID(#x)
#define ATTR(x) int attr_##x = pFile->getAttributeID(#x)
ELMT(object);
ELMT(action);
ATTR(style);
ATTR(type);
ATTR(name);
ATTR(z);
ATTR(on);
2003-12-01 08:06:55 +01:00
//
// Read Style and set defaults
//
// If the setting "style" is set, try loading that setting.
//
// Always load default (if it's available) first!
//
CStr argStyle = (CStr)attributes.getNamedItem(attr_style);
2003-12-01 08:06:55 +01:00
if (m_Styles.count(CStr("default")) == 1)
object->LoadStyle(*this, CStr("default"));
if (argStyle != CStr())
2003-12-01 08:06:55 +01:00
{
// additional check
2003-12-01 08:06:55 +01:00
if (m_Styles.count(argStyle) == 0)
{
// TODO Gee: Error
2003-12-01 08:06:55 +01:00
}
else object->LoadStyle(*this, argStyle);
2003-12-01 08:06:55 +01:00
}
//
// Read Attributes
//
bool NameSet = false;
bool ManuallySetZ = false; // if z has been manually set, this turn true
// Now we can iterate all attributes and store
for (i=0; i<attributes.Count; ++i)
{
XMBAttribute attr = attributes.item(i);
2004-05-29 06:06:50 +02:00
// If value is "null", then it is equivalent as never being entered
if ((CStr8)attr.Value == (CStr8)"null")
2004-05-29 06:06:50 +02:00
continue;
2003-12-01 08:06:55 +01:00
// Ignore "type" and "style", we've already checked it
if (attr.Name == attr_type || attr.Name == attr_style )
continue;
// Also the name needs some special attention
if (attr.Name == attr_name)
{
object->SetName((CStr)attr.Value);
NameSet = true;
continue;
}
if (attr.Name == attr_z)
ManuallySetZ = true;
// Try setting the value
try
{
object->SetSetting(pFile->getAttributeString(attr.Name), (CStr)attr.Value);
}
catch (PS_RESULT e)
{
UNUSED(e);
ReportParseError(CStr("Can't set \"") + pFile->getAttributeString(attr.Name) + CStr("\" to \"") + (CStr)attr.Value + CStr("\""));
// This is not a fatal error
}
}
2004-05-29 06:06:50 +02:00
// Check if name isn't set, generate an internal name in that case.
if (!NameSet)
{
2004-05-29 06:06:50 +02:00
object->SetName(CStr("__internal(") + CStr(m_InternalNameNumber) + CStr(")"));
++m_InternalNameNumber;
}
CStr caption = (CStr)Element.getText();
caption.Trim(PS_TRIM_BOTH);
if (caption.Length())
{
try
{
// Set the setting caption to this
object->SetSetting("caption", caption);
}
catch (...)
{
// There is no harm if the object didn't have a "caption"
}
}
//
// Read Children
//
// Iterate children
XMBElementList children = Element.getChildNodes();
for (i=0; i<children.Count; ++i)
{
// Get node
XMBElement child = children.item(i);
// Check what name the elements got
int element_name = child.getNodeName();
if (element_name == elmt_object)
{
// TODO Gee: REPORT ERROR
// Call this function on the child
Xeromyces_ReadObject(child, pFile, object);
}
else if (element_name == elmt_action)
{
CStr action = (CStr)child.getAttributes().getNamedItem(attr_on);
CStr code = (CStr)child.getText();
object->RegisterScriptHandler(action, code, this);
}
2003-12-01 08:06:55 +01:00
}
//
// Check if Z wasn't manually set
//
if (!ManuallySetZ)
{
// Set it automatically to 10 plus its parents
if (pParent==NULL)
{
// TODO Gee: Report error
}
else
{
2004-05-29 06:06:50 +02:00
bool absolute;
GUI<bool>::GetSetting(object, "absolute", absolute);
// If the object is absolute, we'll have to get the parent's Z buffered,
// and add to that!
2004-05-29 06:06:50 +02:00
if (absolute)
{
GUI<float>::SetSetting(object, "z", pParent->GetBufferedZ() + 10.f);
}
else
// If the object is relative, then we'll just store Z as "10"
{
GUI<float>::SetSetting(object, "z", 10.f);
}
}
}
//
// Input Child
//
try
{
if (pParent == m_BaseObject)
AddObject(object);
else
pParent->AddChild(object);
}
catch (PS_RESULT e)
2003-12-01 08:06:55 +01:00
{
ReportParseError(e);
}
}
void CGUI::Xeromyces_ReadSprite(XMBElement Element, CXeromyces* pFile)
{
// Sprite object we're adding
CGUISprite sprite;
// and what will be its reference name
CStr name;
//
// Read Attributes
//
// Get name, we know it exists because of DTD requirements
name = (CStr) Element.getAttributes().getNamedItem( pFile->getAttributeID("name") );
//
// Read Children (the images)
//
// Iterate children
XMBElementList children = Element.getChildNodes();
for (int i=0; i<children.Count; ++i)
{
// Get node
XMBElement child = children.item(i);
// All Elements will be of type "image" by DTD law
// Call this function on the child
Xeromyces_ReadImage(child, pFile, sprite);
}
//
// Add Sprite
//
m_Sprites[name] = sprite;
}
void CGUI::Xeromyces_ReadImage(XMBElement Element, CXeromyces* pFile, CGUISprite &parent)
{
// Image object we're adding
SGUIImage image;
// TODO Gee: Setup defaults here (or maybe they are in the SGUIImage ctor)
//
// Read Attributes
//
// Now we can iterate all attributes and store
XMBAttributeList attributes = Element.getAttributes();
for (int i=0; i<attributes.Count; ++i)
{
XMBAttribute attr = attributes.item(i);
std::string attr_name = pFile->getAttributeString(attr.Name);
CStr attr_value (attr.Value);
// This is the only attribute we want
if (attr_name == "texture")
{
image.m_TextureName = attr_value;
}
else
if (attr_name == "size")
{
2003-12-01 08:06:55 +01:00
CClientArea ca;
if (!GUI<CClientArea>::ParseString(attr_value, ca))
{
// TODO Gee: Error
2003-12-01 08:06:55 +01:00
}
else image.m_Size = ca;
}
else
if (attr_name == "z-level")
2004-05-29 06:06:50 +02:00
{
int z_level;
if (!GUI<int>::ParseString(attr_value, z_level))
{
// TODO Gee: Error
}
else image.m_DeltaZ = (float)z_level/100.f;
}
else
if (attr_name == "backcolor")
2003-12-01 08:06:55 +01:00
{
CColor color;
if (!GUI<CColor>::ParseString(attr_value, color))
{
// TODO Gee: Error
2003-12-01 08:06:55 +01:00
}
else image.m_BackColor = color;
}
else
{
// TODO Gee: Log
2003-12-01 08:06:55 +01:00
//g_console.submit("echo Error attribute " + attr_name + " is not expected in <image>");
return;
}
}
2003-12-01 08:06:55 +01:00
//
// Input
//
parent.AddImage(image);
}
2003-12-01 08:06:55 +01:00
void CGUI::Xeromyces_ReadStyle(XMBElement Element, CXeromyces* pFile)
2003-12-01 08:06:55 +01:00
{
// style object we're adding
SGUIStyle style;
CStr name;
//
// Read Attributes
//
// Now we can iterate all attributes and store
XMBAttributeList attributes = Element.getAttributes();
for (int i=0; i<attributes.Count; ++i)
2003-12-01 08:06:55 +01:00
{
XMBAttribute attr = attributes.item(i);
std::string attr_name = pFile->getAttributeString(attr.Name);
CStr attr_value (attr.Value);
2003-12-01 08:06:55 +01:00
// The "name" setting is actually the name of the style
// and not a new default
if (attr_name == "name")
2003-12-01 08:06:55 +01:00
name = attr_value;
else
style.m_SettingsDefaults[attr_name] = attr_value;
2003-12-01 08:06:55 +01:00
}
//
// Add to CGUI
//
m_Styles[name] = style;
}
void CGUI::Xeromyces_ReadScrollBarStyle(XMBElement Element, CXeromyces* pFile)
{
// style object we're adding
SGUIScrollBarStyle scrollbar;
CStr name;
//
// Read Attributes
//
// Now we can iterate all attributes and store
XMBAttributeList attributes = Element.getAttributes();
for (int i=0; i<attributes.Count; ++i)
{
XMBAttribute attr = attributes.item(i);
std::string attr_name = pFile->getAttributeString(attr.Name);
CStr attr_value (attr.Value);
2004-05-29 06:06:50 +02:00
if (attr_value == CStr("null"))
continue;
if (attr_name == "name")
name = attr_value;
else
if (attr_name == "width")
{
int i;
if (!GUI<int>::ParseString(attr_value, i))
{
// TODO Gee: Report in log file
}
scrollbar.m_Width = i;
}
2004-05-29 06:06:50 +02:00
else
if (attr_name == "minimum-bar-size")
2004-05-29 06:06:50 +02:00
{
int i;
if (!GUI<int>::ParseString(attr_value, i))
{
// TODO Gee: Report in log file
}
scrollbar.m_MinimumBarSize = i;
}
else
if (attr_name == "sprite-button-top")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonTop = attr_value;
else
if (attr_name == "sprite-button-top-pressed")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonTopPressed = attr_value;
else
if (attr_name == "sprite-button-top-disabled")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonTopDisabled = attr_value;
else
if (attr_name == "sprite-button-top-over")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonTopOver = attr_value;
else
if (attr_name == "sprite-button-bottom")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonBottom = attr_value;
else
if (attr_name == "sprite-button-bottom-pressed")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonBottomPressed = attr_value;
else
if (attr_name == "sprite-button-bottom-disabled")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonBottomDisabled = attr_value;
else
if (attr_name == "sprite-button-bottom-over")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteButtonBottomOver = attr_value;
else
if (attr_name == "sprite-back-vertical")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteBackVertical = attr_value;
else
if (attr_name == "sprite-bar-vertical")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteBarVertical = attr_value;
else
if (attr_name == "sprite-bar-vertical-over")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteBarVerticalOver = attr_value;
else
if (attr_name == "sprite-bar-vertical-pressed")
2004-05-29 06:06:50 +02:00
scrollbar.m_SpriteBarVerticalPressed = attr_value;
/*
CStr m_SpriteButtonTop;
CStr m_SpriteButtonTopPressed;
CStr m_SpriteButtonTopDisabled;
CStr m_SpriteButtonBottom;
CStr m_SpriteButtonBottomPressed;
CStr m_SpriteButtonBottomDisabled;
CStr m_SpriteScrollBackHorizontal;
CStr m_SpriteScrollBarHorizontal;
CStr m_SpriteButtonLeft;
CStr m_SpriteButtonLeftPressed;
CStr m_SpriteButtonLeftDisabled;
CStr m_SpriteButtonRight;
CStr m_SpriteButtonRightPressed;
CStr m_SpriteButtonRightDisabled;
CStr m_SpriteScrollBackVertical;
CStr m_SpriteScrollBarVertical;
*/
}
//
// Add to CGUI
//
m_ScrollBarStyles[name] = scrollbar;
}