/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include
#include
#include "GUI.h"
// Types - when including them into the engine.
#include "CButton.h"
#include "CCheckBox.h"
#include "CDropDown.h"
#include "CImage.h"
#include "CInput.h"
#include "CList.h"
#include "COList.h"
#include "CProgressBar.h"
#include "CRadioButton.h"
#include "CText.h"
#include "CTooltip.h"
#include "MiniMap.h"
#include "graphics/FontMetrics.h"
#include "graphics/ShaderManager.h"
#include "graphics/TextRenderer.h"
#include "i18n/L10n.h"
#include "lib/bits.h"
#include "lib/input.h"
#include "lib/sysdep/sysdep.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/Renderer.h"
#include "scripting/ScriptFunctions.h"
#include "scriptinterface/ScriptInterface.h"
extern int g_yres;
const double SELECT_DBLCLICK_RATE = 0.5;
const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion
InReaction CGUI::HandleEvent(const SDL_Event_* ev)
{
InReaction ret = IN_PASS;
if (ev->ev.type == SDL_HOTKEYDOWN)
{
const char* hotkey = static_cast(ev->ev.user.data1);
std::map >::iterator it = m_HotkeyObjects.find(hotkey);
if (it != m_HotkeyObjects.end())
for (IGUIObject* const& obj : it->second)
obj->SendEvent(GUIM_PRESSED, "press");
}
else if (ev->ev.type == SDL_MOUSEMOTION)
{
// Yes the mouse position is stored as float to avoid
// constant conversions when operating in a
// float-based environment.
m_MousePos = CPos((float)ev->ev.motion.x * g_GuiScale, (float)ev->ev.motion.y * g_GuiScale);
SGUIMessage msg(GUIM_MOUSE_MOTION);
GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::HandleMessage,
msg);
}
// Update m_MouseButtons. (BUTTONUP is handled later.)
else if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
case SDL_BUTTON_RIGHT:
case SDL_BUTTON_MIDDLE:
m_MouseButtons |= Bit(ev->ev.button.button);
break;
default:
break;
}
}
// Update m_MousePos (for delayed mouse button events)
CPos oldMousePos = m_MousePos;
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
{
m_MousePos = CPos((float)ev->ev.button.x * g_GuiScale, (float)ev->ev.button.y * g_GuiScale);
}
// Only one object can be hovered
IGUIObject* pNearest = NULL;
// TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress!
try
{
PROFILE("mouse events");
// TODO Gee: Optimizations needed!
// these two recursive function are quite overhead heavy.
// pNearest will after this point at the hovered object, possibly NULL
pNearest = FindObjectUnderMouse();
// 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::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::UpdateMouseOver,
pNearest);
if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
// Focus the clicked object (or focus none if nothing clicked on)
SetFocusedObject(pNearest);
if (pNearest)
ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress");
break;
case SDL_BUTTON_RIGHT:
if (pNearest)
ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress");
break;
#if !SDL_VERSION_ATLEAST(2, 0, 0)
case SDL_BUTTON_WHEELDOWN: // wheel down
if (pNearest)
ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown");
break;
case SDL_BUTTON_WHEELUP: // wheel up
if (pNearest)
ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup");
break;
#endif
default:
break;
}
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
else if (ev->ev.type == SDL_MOUSEWHEEL)
{
if (ev->ev.wheel.y < 0)
{
if (pNearest)
ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown");
}
else if (ev->ev.wheel.y > 0)
{
if (pNearest)
ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup");
}
}
#endif
else if (ev->ev.type == SDL_MOUSEBUTTONUP)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
if (pNearest)
{
double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT];
pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time();
//Double click?
if (timeElapsed < SELECT_DBLCLICK_RATE)
{
ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick");
}
else
{
ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease");
}
}
break;
case SDL_BUTTON_RIGHT:
if (pNearest)
{
double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT];
pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time();
//Double click?
if (timeElapsed < SELECT_DBLCLICK_RATE)
{
ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick");
}
else
{
ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease");
}
}
break;
}
// Reset all states on all visible objects
GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject,
&IGUIObject::ResetStates);
// Since the hover state will have been reset, we reload it.
GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::UpdateMouseOver,
pNearest);
}
}
catch (PSERROR_GUI& e)
{
UNUSED2(e);
debug_warn(L"CGUI::HandleEvent error");
// TODO Gee: Handle
}
// 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->ev.type == SDL_MOUSEBUTTONUP)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
case SDL_BUTTON_RIGHT:
case SDL_BUTTON_MIDDLE:
m_MouseButtons &= ~Bit(ev->ev.button.button);
break;
default:
break;
}
}
// Restore m_MousePos (for delayed mouse button events)
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
{
m_MousePos = oldMousePos;
}
// Handle keys for input boxes
if (GetFocusedObject())
{
if (
(ev->ev.type == SDL_KEYDOWN &&
ev->ev.key.keysym.sym != SDLK_ESCAPE &&
!g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] &&
!g_keys[SDLK_LALT] && !g_keys[SDLK_RALT])
|| ev->ev.type == SDL_HOTKEYDOWN
#if SDL_VERSION_ATLEAST(2, 0, 0)
|| ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING
#endif
)
{
ret = GetFocusedObject()->ManuallyHandleEvent(ev);
}
// else will return IN_PASS because we never used the button.
}
return ret;
}
void CGUI::TickObjects()
{
CStr action = "tick";
GUI::RecurseObject(0, m_BaseObject,
&IGUIObject::ScriptEvent, action);
// Also update tooltips:
m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, this);
}
void CGUI::SendEventToAll(const CStr& EventName)
{
// janwas 2006-03-03: spoke with Ykkrosh about EventName case.
// when registering, case is converted to lower - this avoids surprise
// if someone were to get the case wrong and then not notice their
// handler is never called. however, until now, the other end
// (sending events here) wasn't converting to lower case,
// leading to a similar problem.
// now fixed; case is irrelevant since all are converted to lower.
GUI::RecurseObject(0, m_BaseObject,
&IGUIObject::ScriptEvent, EventName.LowerCase());
}
CGUI::CGUI(const shared_ptr& runtime)
: m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0)
{
m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", runtime));
GuiScriptingInit(*m_ScriptInterface);
m_ScriptInterface->LoadGlobalScripts();
m_BaseObject = new CGUIDummyObject;
m_BaseObject->SetGUI(this);
}
CGUI::~CGUI()
{
Destroy();
if (m_BaseObject)
delete m_BaseObject;
}
IGUIObject* CGUI::ConstructObject(const CStr& str)
{
if (m_ObjectTypes.count(str) > 0)
return (*m_ObjectTypes[str])();
else
{
// Error reporting will be handled with the NULL return.
return NULL;
}
}
void CGUI::Initialize()
{
// Add base types!
// You can also add types outside the GUI to extend the flexibility of the GUI.
// Pyrogenesis though will have all the object types inserted from here.
AddObjectType("empty", &CGUIDummyObject::ConstructObject);
AddObjectType("button", &CButton::ConstructObject);
AddObjectType("image", &CImage::ConstructObject);
AddObjectType("text", &CText::ConstructObject);
AddObjectType("checkbox", &CCheckBox::ConstructObject);
AddObjectType("radiobutton", &CRadioButton::ConstructObject);
AddObjectType("progressbar", &CProgressBar::ConstructObject);
AddObjectType("minimap", &CMiniMap::ConstructObject);
AddObjectType("input", &CInput::ConstructObject);
AddObjectType("list", &CList::ConstructObject);
AddObjectType("olist", &COList::ConstructObject);
AddObjectType("dropdown", &CDropDown::ConstructObject);
AddObjectType("tooltip", &CTooltip::ConstructObject);
}
void CGUI::Draw()
{
// Clear the depth buffer, so the GUI is
// drawn on top of everything else
glClear(GL_DEPTH_BUFFER_BIT);
try
{
// 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 (PSERROR_GUI& e)
{
LOGERROR("GUI draw error: %s", e.what());
}
}
void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping))
{
// If the sprite doesn't exist (name == ""), don't bother drawing anything
if (Sprite.IsEmpty())
return;
// TODO: Clipping?
Sprite.Draw(Rect, CellID, m_Sprites, Z);
}
void CGUI::Destroy()
{
// We can use the map to delete all
// now we don't want to cancel all if one Destroy fails
for (const std::pair& p : m_pAllObjects)
{
try
{
p.second->Destroy();
}
catch (PSERROR_GUI& e)
{
UNUSED2(e);
debug_warn(L"CGUI::Destroy error");
// TODO Gee: Handle
}
delete p.second;
}
m_pAllObjects.clear();
for (const std::pair& p : m_Sprites)
delete p.second;
m_Sprites.clear();
m_Icons.clear();
}
void CGUI::UpdateResolution()
{
// Update ALL cached
GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize);
}
void CGUI::AddObject(IGUIObject* pObject)
{
try
{
// Add CGUI pointer
GUI::RecurseObject(0, pObject, &IGUIObject::SetGUI, this);
// Add child to base object
m_BaseObject->AddChild(pObject); // can throw
// Cache tree
GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize);
// Loaded
SGUIMessage msg(GUIM_LOAD);
GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, msg);
}
catch (PSERROR_GUI&)
{
throw;
}
}
void CGUI::UpdateObjects()
{
// We'll fill a temporary map until we know everything
// succeeded
map_pObjects AllObjects;
try
{
// Fill freshly
GUI::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects);
}
catch (PSERROR_GUI&)
{
// Throw the same error
throw;
}
// Else actually update the real one
m_pAllObjects.swap(AllObjects);
}
bool CGUI::ObjectExists(const CStr& Name) const
{
return m_pAllObjects.count(Name) != 0;
}
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;
}
IGUIObject* CGUI::FindObjectUnderMouse() const
{
IGUIObject* pNearest = NULL;
GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::ChooseMouseOverAndClosest,
pNearest);
return pNearest;
}
void CGUI::SetFocusedObject(IGUIObject* pObject)
{
if (pObject == m_FocusedObject)
return;
if (m_FocusedObject)
{
SGUIMessage msg(GUIM_LOST_FOCUS);
m_FocusedObject->HandleMessage(msg);
}
m_FocusedObject = pObject;
if (m_FocusedObject)
{
SGUIMessage msg(GUIM_GOT_FOCUS);
m_FocusedObject->HandleMessage(msg);
}
}
// private struct used only in GenerateText(...)
struct SGenerateTextImage
{
float m_YFrom, // The image's starting location in Y
m_YTo, // The image's 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 float width, const float y,
const CSize& Size, const CStr& TextureName,
const float BufferZone, const int CellID)
{
// 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_CellID = CellID;
SpriteCall.m_Sprite = 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 CStrW& FontW, const float& Width, const float& BufferZone, const IGUIObject* pObject)
{
SGUIText Text; // object we're generating
CStrIntern Font(FontW.ToUTF8());
if (string.m_Words.empty())
return Text;
float x = BufferZone, y = BufferZone; // drawing pointer
int from = 0;
bool done = false;
bool FirstLine = true; // Necessary because text in the first line is shorter
// (it doesn't count the line spacing)
// Images on the left or the right side.
std::vector 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);
// get the alignment type for the control we are computing the text for since
// we are computing the horizontal alignment in this method in order to not have
// to run through the TextCalls a second time in the CalculateTextPosition method again
EAlign align;
GUI::GetSetting(pObject, "text_align", align);
// Go through string word by word
for (int i = 0; i < (int)string.m_Words.size()-1 && !done; ++i)
{
// 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.
float prelim_line_height = 0.f;
// Width and height of all text calls generated.
string.GenerateTextCall(this, Feedback, Font,
string.m_Words[i], string.m_Words[i+1],
FirstLine);
// 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 (const CStr& imgname : Feedback.m_Images[j])
{
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.
float _y;
if (!Images[j].empty())
_y = std::max(y, Images[j].back().m_YTo);
else
_y = y;
// Get Size from Icon database
SGUIIcon icon = GetIcon(imgname);
CSize size = icon.m_Size;
Image.SetupSpriteCall((j == CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_SpriteName, BufferZone, icon.m_CellID);
// Check if image is the lowest thing.
Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo);
Images[j].push_back(Image);
Text.m_SpriteCalls.push_back(SpriteCall);
}
}
}
pos_last_img = std::max(pos_last_img, i);
x += Feedback.m_Size.cx;
prelim_line_height = std::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 == (int)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;
float width_range[2];
width_range[From] = BufferZone;
width_range[To] = Width - BufferZone;
// Floating images are only applicable 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 noticeably if one
// structures 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 (const SGenerateTextImage& img : Images[j])
{
// We're working with two intervals here, the image's and the line height's.
// let's find the union of these two.
float union_from, union_to;
union_from = std::max(y, img.m_YFrom);
union_to = std::min(y+prelim_line_height, img.m_YTo);
// The union is not empty
if (union_to > union_from)
{
if (j == From)
width_range[From] = std::max(width_range[From], img.m_Indentation);
else
width_range[To] = std::min(width_range[To], Width - img.m_Indentation);
}
}
}
}
// Reset X for the next loop
x = width_range[From];
// Now we'll do another loop to figure out the height and width of
// the line (the height of the largest character and the width is
// the sum of all of the individual widths). 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.
float line_height = 0.f;
float line_width = 0.f;
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;
// Don't attach object, it'll suppress the errors
// we want them to be reported in the final GenerateTextCall()
// so that we don't get duplicates.
string.GenerateTextCall(this, Feedback2, Font,
string.m_Words[j], string.m_Words[j+1],
FirstLine);
// 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 = std::max(line_height, Feedback2.m_Size.cy);
line_width += Feedback2.m_Size.cx;
if (WordWrapping && Feedback2.m_NewLine)
break;
}
float dx = 0.f;
// compute offset based on what kind of alignment
switch (align)
{
case EAlign_Left:
// don't add an offset
dx = 0.f;
break;
case EAlign_Center:
dx = ((width_range[To] - width_range[From]) - line_width) / 2;
break;
case EAlign_Right:
dx = width_range[To] - line_width;
break;
default:
debug_warn(L"Broken EAlign in CGUI::GenerateText()");
break;
}
// Reset x once more
x = width_range[From];
// Move down, because font drawing starts from the baseline
y += line_height;
// 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 one.
CGUIString::SFeedback Feedback2;
// Defaults
string.GenerateTextCall(this, Feedback2, Font,
string.m_Words[j], string.m_Words[j+1],
FirstLine, pObject);
// 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.
float x_pointer = 0.f;
for (SGUIText::STextCall& tc : Feedback2.m_TextCalls)
{
tc.m_Pos = CPos(dx + x + x_pointer, y);
x_pointer += tc.m_Size.cx;
if (tc.m_pSpriteCall)
tc.m_pSpriteCall->m_Area += tc.m_Pos - CSize(0, tc.m_pSpriteCall->m_Area.GetHeight());
}
// Append X value.
x += Feedback2.m_Size.cx;
Text.m_Size.cx = std::max(Text.m_Size.cx, x+BufferZone);
// The first word overrides the width limit, what we
// do, in those cases, are just drawing that word even
// though it'll extend the object.
if (WordWrapping) // only if word-wrapping is applicable
{
if (Feedback2.m_NewLine)
{
from = j+1;
// Sprite call can exist within only a newline segment,
// therefore we need this.
Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
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 == (int)string.m_Words.size()-2)
done = true;
}
// Reset X
x = 0.f;
// Update height of all
Text.m_Size.cy = std::max(Text.m_Size.cy, y+BufferZone);
FirstLine = false;
// 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(SGUIText& Text, const CColor& DefaultColor, const CPos& pos, const float& z, const CRect& clipping)
{
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
tech->BeginPass();
bool isClipped = (clipping != CRect());
if (isClipped)
{
glEnable(GL_SCISSOR_TEST);
glScissor(
clipping.left / g_GuiScale,
g_yres - clipping.bottom / g_GuiScale,
clipping.GetWidth() / g_GuiScale,
clipping.GetHeight() / g_GuiScale);
}
CTextRenderer textRenderer(tech->GetShader());
textRenderer.SetClippingRect(clipping);
textRenderer.Translate(0.0f, 0.0f, z);
for (const SGUIText::STextCall& tc : Text.m_TextCalls)
{
// If this is just a placeholder for a sprite call, continue
if (tc.m_pSpriteCall)
continue;
CColor color = tc.m_UseCustomColor ? tc.m_Color : DefaultColor;
textRenderer.Color(color);
textRenderer.Font(tc.m_Font);
textRenderer.Put((float)(int)(pos.x + tc.m_Pos.x), (float)(int)(pos.y + tc.m_Pos.y), &tc.m_String);
}
textRenderer.Render();
for (const SGUIText::SSpriteCall& sc : Text.m_SpriteCalls)
DrawSprite(sc.m_Sprite, sc.m_CellID, z, sc.m_Area + pos);
if (isClipped)
glDisable(GL_SCISSOR_TEST);
tech->EndPass();
}
bool CGUI::GetPreDefinedColor(const CStr& name, CColor& Output) const
{
std::map::const_iterator cit = m_PreDefinedColors.find(name);
if (cit == m_PreDefinedColors.end())
return false;
Output = cit->second;
return true;
}
/**
* @callgraph
*/
void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set& Paths)
{
Paths.insert(Filename);
CXeromyces XeroFile;
if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK)
// Fail silently
return;
XMBElement node = XeroFile.GetRoot();
// Check root element's (node) name so we know what kind of
// data we'll be expecting
CStr root_name(XeroFile.GetElementString(node.GetNodeName()));
try
{
if (root_name == "objects")
{
Xeromyces_ReadRootObjects(node, &XeroFile, Paths);
// 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
debug_warn(L"CGUI::LoadXmlFile error");
}
catch (PSERROR_GUI& e)
{
LOGERROR("Errors loading GUI file %s (%u)", Filename.string8(), e.getCode());
return;
}
}
//===================================================================
// XML Reading Xeromyces Specific Sub-Routines
//===================================================================
void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, boost::unordered_set& Paths)
{
int el_script = pFile->GetElementID("script");
std::vector > subst;
// Iterate main children
// they should all be