Index: ps/trunk/source/gui/CSlider.h
===================================================================
--- ps/trunk/source/gui/CSlider.h (revision 22586)
+++ ps/trunk/source/gui/CSlider.h (revision 22587)
@@ -1,65 +1,65 @@
/* Copyright (C) 2019 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 .
*/
#ifndef INCLUDED_CSLIDER
#define INCLUDED_CSLIDER
#include "GUI.h"
class CSlider : public IGUIObject
{
GUI_OBJECT(CSlider)
public:
- CSlider();
+ CSlider(CGUI* pGUI);
virtual ~CSlider();
protected:
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
virtual void Draw();
/**
* Change settings and send the script event
*/
void UpdateValue();
CRect GetButtonRect();
/**
* @return ratio between the value of the slider and its actual size in the GUI
*/
float GetSliderRatio() const;
void IncrementallyChangeValue(const float value);
float m_MinValue, m_MaxValue, m_Value;
private:
bool m_IsPressed;
CPos m_Mouse;
float m_ButtonSide;
};
#endif // INCLUDED_CSLIDER
Index: ps/trunk/source/gui/CText.h
===================================================================
--- ps/trunk/source/gui/CText.h (revision 22586)
+++ ps/trunk/source/gui/CText.h (revision 22587)
@@ -1,69 +1,69 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
#ifndef INCLUDED_CTEXT
#define INCLUDED_CTEXT
#include "GUI.h"
/**
* Text field that just displays static text.
*
* @see IGUIObject
*/
class CText : public IGUIScrollBarOwner, public IGUITextOwner
{
GUI_OBJECT(CText)
public:
- CText();
+ CText(CGUI* pGUI);
virtual ~CText();
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates() { IGUIScrollBarOwner::ResetStates(); }
/**
* Test if mouse position is over an icon
*/
virtual bool MouseOverIcon();
protected:
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Draws the Text
*/
virtual void Draw();
/**
* Placement of text. Ignored when scrollbars are active.
*/
CPos m_TextPos;
};
#endif // INCLUDED_CTEXT
Index: ps/trunk/source/gui/CTooltip.h
===================================================================
--- ps/trunk/source/gui/CTooltip.h (revision 22586)
+++ ps/trunk/source/gui/CTooltip.h (revision 22587)
@@ -1,45 +1,45 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
#ifndef INCLUDED_CTOOLTIP
#define INCLUDED_CTOOLTIP
#include "IGUITextOwner.h"
/**
* Dynamic tooltips. Similar to CText.
*/
class CTooltip : public IGUITextOwner
{
GUI_OBJECT(CTooltip)
public:
- CTooltip();
+ CTooltip(CGUI* pGUI);
virtual ~CTooltip();
protected:
void SetupText();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
virtual void Draw();
};
#endif // INCLUDED_CTOOLTIP
Index: ps/trunk/source/gui/IGUIObject.cpp
===================================================================
--- ps/trunk/source/gui/IGUIObject.cpp (revision 22586)
+++ ps/trunk/source/gui/IGUIObject.cpp (revision 22587)
@@ -1,575 +1,573 @@
/* Copyright (C) 2019 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 "GUI.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "gui/scripting/JSInterface_IGUIObject.h"
#include "ps/GameSetup/Config.h"
#include "ps/CLogger.h"
#include "ps/Profile.h"
#include "scriptinterface/ScriptInterface.h"
template
void SGUISetting::Init(IGUIObject& pObject, const CStr& Name)
{
m_pSetting = new T();
m_FromJSVal = [Name, &pObject](JSContext* cx, JS::HandleValue v) {
T value;
if (!ScriptInterface::FromJSVal(cx, v, value))
return false;
GUI::SetSetting(&pObject, Name, value);
return true;
};
m_ToJSVal = [Name, this](JSContext* cx, JS::MutableHandleValue v) {
ScriptInterface::ToJSVal(cx, v, *static_cast(m_pSetting));
};
}
-IGUIObject::IGUIObject()
- : m_pGUI(NULL), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime()
+IGUIObject::IGUIObject(CGUI* pGUI)
+ : m_pGUI(pGUI), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime()
{
AddSetting(GUIST_bool, "enabled");
AddSetting(GUIST_bool, "hidden");
AddSetting(GUIST_CClientArea, "size");
AddSetting(GUIST_CStr, "style");
AddSetting(GUIST_CStr, "hotkey");
AddSetting(GUIST_float, "z");
AddSetting(GUIST_bool, "absolute");
AddSetting(GUIST_bool, "ghost");
AddSetting(GUIST_float, "aspectratio");
AddSetting(GUIST_CStrW, "tooltip");
AddSetting(GUIST_CStr, "tooltip_style");
// Setup important defaults
GUI::SetSetting(this, "hidden", false);
GUI::SetSetting(this, "ghost", false);
GUI::SetSetting(this, "enabled", true);
GUI::SetSetting(this, "absolute", true);
}
IGUIObject::~IGUIObject()
{
for (const std::pair& p : m_Settings)
switch (p.second.m_Type)
{
// delete() needs to know the type of the variable - never delete a void*
#define TYPE(t) case GUIST_##t: delete (t*)p.second.m_pSetting; break;
#include "GUItypes.h"
#undef TYPE
default:
debug_warn(L"Invalid setting type");
}
- if (m_pGUI)
+ if (!m_ScriptHandlers.empty())
JS_RemoveExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this);
}
//-------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------
-void IGUIObject::SetGUI(CGUI* const& pGUI)
-{
- if (!m_pGUI)
- JS_AddExtraGCRootsTracer(pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this);
- m_pGUI = pGUI;
-}
void IGUIObject::AddChild(IGUIObject* pChild)
{
// ENSURE(pChild);
pChild->SetParent(this);
m_Children.push_back(pChild);
// If this (not the child) object is already attached
// to a CGUI, it pGUI pointer will be non-null.
// This will mean we'll have to check if we're using
// names already used.
if (pChild->GetGUI())
{
try
{
// Atomic function, if it fails it won't
// have changed anything
//UpdateObjects();
pChild->GetGUI()->UpdateObjects();
}
catch (PSERROR_GUI&)
{
// If anything went wrong, reverse what we did and throw
// an exception telling it never added a child
m_Children.erase(m_Children.end()-1);
throw;
}
}
// else do nothing
}
void IGUIObject::AddToPointersMap(map_pObjects& ObjectMap)
{
// Just don't do anything about the top node
if (m_pParent == NULL)
return;
// Now actually add this one
// notice we won't add it if it's doesn't have any parent
// (i.e. being the base object)
if (m_Name.empty())
{
throw PSERROR_GUI_ObjectNeedsName();
}
if (ObjectMap.count(m_Name) > 0)
{
throw PSERROR_GUI_NameAmbiguity(m_Name.c_str());
}
else
{
ObjectMap[m_Name] = this;
}
}
void IGUIObject::Destroy()
{
// Is there anything besides the children to destroy?
}
void IGUIObject::AddSetting(const EGUISettingType& Type, const CStr& Name)
{
// Is name already taken?
if (m_Settings.count(Name) >= 1)
return;
// Construct, and set type
m_Settings[Name].m_Type = Type;
switch (Type)
{
#define TYPE(type) \
case GUIST_##type: \
m_Settings[Name].Init(*this, Name);\
break;
// Construct the setting.
#include "GUItypes.h"
#undef TYPE
default:
debug_warn(L"IGUIObject::AddSetting failed, type not recognized!");
break;
}
}
bool IGUIObject::MouseOver()
{
if (!GetGUI())
throw PSERROR_GUI_OperationNeedsGUIObject();
return m_CachedActualSize.PointInside(GetMousePos());
}
bool IGUIObject::MouseOverIcon()
{
return false;
}
CPos IGUIObject::GetMousePos() const
{
if (GetGUI())
return GetGUI()->m_MousePos;
return CPos();
}
void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver)
{
if (pMouseOver == this)
{
if (!m_MouseHovering)
SendEvent(GUIM_MOUSE_ENTER, "mouseenter");
m_MouseHovering = true;
SendEvent(GUIM_MOUSE_OVER, "mousemove");
}
else
{
if (m_MouseHovering)
{
m_MouseHovering = false;
SendEvent(GUIM_MOUSE_LEAVE, "mouseleave");
}
}
}
bool IGUIObject::SettingExists(const CStr& Setting) const
{
// Because GetOffsets will direct dynamically defined
// classes with polymorphism to respective ms_SettingsInfo
// we need to make no further updates on this function
// in derived classes.
//return (GetSettingsInfo().count(Setting) >= 1);
return (m_Settings.count(Setting) >= 1);
}
PSRETURN IGUIObject::SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage)
{
if (!SettingExists(Setting))
return PSRETURN_GUI_InvalidSetting;
SGUISetting set = m_Settings[Setting];
#define TYPE(type) \
else if (set.m_Type == GUIST_##type) \
{ \
type _Value; \
if (!GUI::ParseString(Value, _Value)) \
return PSRETURN_GUI_UnableToParse; \
GUI::SetSetting(this, Setting, _Value, SkipMessage); \
}
if (0)
;
#include "GUItypes.h"
#undef TYPE
else
{
// Why does it always fail?
//return PS_FAIL;
return LogInvalidSettings(Setting);
}
return PSRETURN_OK;
}
PSRETURN IGUIObject::GetSettingType(const CStr& Setting, EGUISettingType& Type) const
{
if (!SettingExists(Setting))
return LogInvalidSettings(Setting);
if (m_Settings.find(Setting) == m_Settings.end())
return LogInvalidSettings(Setting);
Type = m_Settings.find(Setting)->second.m_Type;
return PSRETURN_OK;
}
void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject)
{
if (!MouseOver())
return;
// Check if we've got competition at all
if (pObject == NULL)
{
pObject = this;
return;
}
// Or if it's closer
if (GetBufferedZ() >= pObject->GetBufferedZ())
{
pObject = this;
return;
}
}
IGUIObject* IGUIObject::GetParent() const
{
// Important, we're not using GetParent() for these
// checks, that could screw it up
if (m_pParent)
{
if (m_pParent->m_pParent == NULL)
return NULL;
}
return m_pParent;
}
void IGUIObject::ResetStates()
{
// Notify the gui that we aren't hovered anymore
UpdateMouseOver(nullptr);
}
void IGUIObject::UpdateCachedSize()
{
bool absolute;
GUI::GetSetting(this, "absolute", absolute);
float aspectratio = 0.f;
GUI::GetSetting(this, "aspectratio", aspectratio);
CClientArea ca;
GUI::GetSetting(this, "size", ca);
// If absolute="false" and the object has got a parent,
// use its cached size instead of the screen. Notice
// it must have just been cached for it to work.
if (absolute == false && m_pParent && !IsRootObject())
m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize);
else
m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale));
// In a few cases, GUI objects have to resize to fill the screen
// but maintain a constant aspect ratio.
// Adjust the size to be the max possible, centered in the original size:
if (aspectratio)
{
if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight()*aspectratio)
{
float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight()*aspectratio;
m_CachedActualSize.left += delta/2.f;
m_CachedActualSize.right -= delta/2.f;
}
else
{
float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth()/aspectratio;
m_CachedActualSize.bottom -= delta/2.f;
m_CachedActualSize.top += delta/2.f;
}
}
}
void IGUIObject::LoadStyle(CGUI& GUIinstance, const CStr& StyleName)
{
// Fetch style
if (GUIinstance.m_Styles.count(StyleName) == 1)
{
LoadStyle(GUIinstance.m_Styles[StyleName]);
}
else
{
debug_warn(L"IGUIObject::LoadStyle failed");
}
}
void IGUIObject::LoadStyle(const SGUIStyle& Style)
{
// Iterate settings, it won't be able to set them all probably, but that doesn't matter
for (const std::pair& p : Style.m_SettingsDefaults)
{
// Try set setting in object
SetSetting(p.first, p.second);
// It doesn't matter if it fail, it's not suppose to be able to set every setting.
// since it's generic.
// The beauty with styles is that it can contain more settings
// than exists for the objects using it. So if the SetSetting
// fails, don't care.
}
}
float IGUIObject::GetBufferedZ() const
{
bool absolute;
GUI::GetSetting(this, "absolute", absolute);
float Z;
GUI::GetSetting(this, "z", Z);
if (absolute)
return Z;
else
{
if (GetParent())
return GetParent()->GetBufferedZ() + Z;
else
{
// In philosophy, a parentless object shouldn't be able to have a relative sizing,
// but we'll accept it so that absolute can be used as default without a complaint.
// Also, you could consider those objects children to the screen resolution.
return Z;
}
}
}
void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI)
{
if(!GetGUI())
throw PSERROR_GUI_OperationNeedsGUIObject();
JSContext* cx = pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue globalVal(cx, pGUI->GetGlobalObject());
JS::RootedObject globalObj(cx, &globalVal.toObject());
const int paramCount = 1;
const char* paramNames[paramCount] = { "mouse" };
// Location to report errors from
CStr CodeName = GetName()+" "+Action;
// Generate a unique name
static int x = 0;
char buf[64];
sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str());
JS::CompileOptions options(cx);
options.setFileAndLine(CodeName.c_str(), 0);
options.setCompileAndGo(true);
JS::RootedFunction func(cx);
JS::AutoObjectVector emptyScopeChain(cx);
if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func))
{
LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str());
return;
}
JS::RootedObject funcObj(cx, JS_GetFunctionObject(func));
SetScriptHandler(Action, funcObj);
}
void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function)
{
- // m_ScriptHandlers is only rooted after SetGUI() has been called (which sets up the GC trace callbacks),
- // so we can't safely store objects in it if the GUI hasn't been set yet.
ENSURE(m_pGUI && "A GUI must be associated with the GUIObject before adding ScriptHandlers!");
+
+ if (m_ScriptHandlers.empty())
+ JS_AddExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this);
+
m_ScriptHandlers[Action] = JS::Heap(Function);
}
InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName)
{
PROFILE2_EVENT("gui event");
PROFILE2_ATTR("type: %s", EventName.c_str());
PROFILE2_ATTR("object: %s", m_Name.c_str());
SGUIMessage msg(type);
HandleMessage(msg);
ScriptEvent(EventName);
return (msg.skipped ? IN_PASS : IN_HANDLED);
}
void IGUIObject::ScriptEvent(const CStr& Action)
{
std::map >::iterator it = m_ScriptHandlers.find(Action);
if (it == m_ScriptHandlers.end())
return;
JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
// Set up the 'mouse' parameter
JS::RootedValue mouse(cx);
m_pGUI->GetScriptInterface()->CreateObject(
&mouse,
"x", m_pGUI->m_MousePos.x,
"y", m_pGUI->m_MousePos.y,
"buttons", m_pGUI->m_MouseButtons);
JS::AutoValueVector paramData(cx);
paramData.append(mouse);
JS::RootedObject obj(cx, GetJSObject());
JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second));
JS::RootedValue result(cx);
bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result);
if (!ok)
{
// We have no way to propagate the script exception, so just ignore it
// and hope the caller checks JS_IsExceptionPending
}
}
void IGUIObject::ScriptEvent(const CStr& Action, JS::HandleValueArray paramData)
{
std::map >::iterator it = m_ScriptHandlers.find(Action);
if (it == m_ScriptHandlers.end())
return;
JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
JS::RootedObject obj(cx, GetJSObject());
JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second));
JS::RootedValue result(cx);
if (!JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result))
JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str());
}
JSObject* IGUIObject::GetJSObject()
{
JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
// Cache the object when somebody first asks for it, because otherwise
// we end up doing far too much object allocation. TODO: Would be nice to
// not have these objects hang around forever using up memory, though.
if (!m_JSObject.initialized())
{
m_JSObject.init(cx, m_pGUI->GetScriptInterface()->CreateCustomObject("GUIObject"));
JS_SetPrivate(m_JSObject.get(), this);
}
return m_JSObject.get();
}
CStr IGUIObject::GetPresentableName() const
{
// __internal(), must be at least 13 letters to be able to be
// an internal name
if (m_Name.length() <= 12)
return m_Name;
if (m_Name.substr(0, 10) == "__internal")
return CStr("[unnamed object]");
else
return m_Name;
}
void IGUIObject::SetFocus()
{
GetGUI()->m_FocusedObject = this;
}
bool IGUIObject::IsFocused() const
{
return GetGUI()->m_FocusedObject == this;
}
bool IGUIObject::IsRootObject() const
{
return GetGUI() != 0 && m_pParent == GetGUI()->m_BaseObject;
}
void IGUIObject::TraceMember(JSTracer* trc)
{
+ // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced!
+
for (std::pair>& handler : m_ScriptHandlers)
JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers");
}
PSRETURN IGUIObject::LogInvalidSettings(const CStr8& Setting) const
{
LOGWARNING("IGUIObject: setting %s was not found on an object", Setting.c_str());
return PSRETURN_GUI_InvalidSetting;
}
Index: ps/trunk/source/gui/IGUIScrollBar.cpp
===================================================================
--- ps/trunk/source/gui/IGUIScrollBar.cpp (revision 22586)
+++ ps/trunk/source/gui/IGUIScrollBar.cpp (revision 22587)
@@ -1,194 +1,196 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2019 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 "GUI.h"
#include "maths/MathUtil.h"
-IGUIScrollBar::IGUIScrollBar() : m_pStyle(NULL), m_pGUI(NULL),
+IGUIScrollBar::IGUIScrollBar(CGUI* m_pGUI)
+ : m_pGUI(m_pGUI),
+ m_pStyle(NULL),
m_X(300.f), m_Y(300.f),
m_ScrollRange(1.f), m_ScrollSpace(0.f), // MaxPos: not 0, due to division.
m_Length(200.f), m_Width(20.f),
m_BarSize(0.f), m_Pos(0.f),
m_ButtonPlusPressed(false),
m_ButtonMinusPressed(false),
m_ButtonPlusHovered(false),
m_ButtonMinusHovered(false),
m_BarHovered(false),
m_BarPressed(false)
{
}
IGUIScrollBar::~IGUIScrollBar()
{
}
void IGUIScrollBar::SetupBarSize()
{
if (!GetStyle())
return;
float min = GetStyle()->m_MinimumBarSize;
float max = GetStyle()->m_MaximumBarSize;
float length = m_Length;
// Check for edge buttons
if (GetStyle()->m_UseEdgeButtons)
length -= GetStyle()->m_Width * 2.f;
// Check min and max are valid
if (min > length)
min = 0.f;
if (max < min)
max = length;
// Clamp size to not exceed a minimum or maximum.
m_BarSize = clamp(length * std::min((float)m_ScrollSpace / (float)m_ScrollRange, 1.f), min, max);
}
const SGUIScrollBarStyle* IGUIScrollBar::GetStyle() const
{
if (!m_pHostObject)
return NULL;
return m_pHostObject->GetScrollBarStyle(m_ScrollBarStyle);
}
CGUI* IGUIScrollBar::GetGUI() const
{
if (!m_pHostObject)
return NULL;
return m_pHostObject->GetGUI();
}
void IGUIScrollBar::UpdatePosBoundaries()
{
if (m_Pos < 0.f ||
m_ScrollRange < m_ScrollSpace) // <= scrolling not applicable
m_Pos = 0.f;
else if (m_Pos > GetMaxPos())
m_Pos = GetMaxPos();
}
void IGUIScrollBar::HandleMessage(SGUIMessage& Message)
{
switch (Message.type)
{
case GUIM_MOUSE_MOTION:
{
// TODO Gee: Optimizations needed!
CPos mouse = m_pHostObject->GetMousePos();
// If bar is being dragged
if (m_BarPressed)
{
SetPosFromMousePos(mouse);
UpdatePosBoundaries();
}
// check if components are being hovered
m_BarHovered = GetBarRect().PointInside(mouse);
m_ButtonMinusHovered = HoveringButtonMinus(mouse);
m_ButtonPlusHovered = HoveringButtonPlus(mouse);
if (!m_ButtonMinusHovered)
m_ButtonMinusPressed = false;
if (!m_ButtonPlusHovered)
m_ButtonPlusPressed = false;
break;
}
case GUIM_MOUSE_PRESS_LEFT:
{
if (!m_pHostObject)
break;
CPos mouse = m_pHostObject->GetMousePos();
// if bar is pressed
if (GetBarRect().PointInside(mouse))
{
m_BarPressed = true;
m_BarPressedAtPos = mouse;
m_PosWhenPressed = m_Pos;
}
// if button-minus is pressed
else if (m_ButtonMinusHovered)
{
m_ButtonMinusPressed = true;
ScrollMinus();
}
// if button-plus is pressed
else if (m_ButtonPlusHovered)
{
m_ButtonPlusPressed = true;
ScrollPlus();
}
// Pressing the background of the bar, to scroll
// notice the if-sentence alone does not admit that,
// it must be after the above if/elses
else
{
if (GetOuterRect().PointInside(mouse))
{
// Scroll plus or minus a lot, this might change, it doesn't
// have to be fancy though.
if (mouse.y < GetBarRect().top)
ScrollMinusPlenty();
else
ScrollPlusPlenty();
// Simulate mouse movement to see if bar now is hovered
SGUIMessage msg(GUIM_MOUSE_MOTION);
HandleMessage(msg);
}
}
break;
}
case GUIM_MOUSE_RELEASE_LEFT:
m_ButtonMinusPressed = false;
m_ButtonPlusPressed = false;
break;
case GUIM_MOUSE_WHEEL_UP:
{
ScrollMinus();
// Since the scroll was changed, let's simulate a mouse movement
// to check if scrollbar now is hovered
SGUIMessage msg(GUIM_MOUSE_MOTION);
HandleMessage(msg);
break;
}
case GUIM_MOUSE_WHEEL_DOWN:
{
ScrollPlus();
// Since the scroll was changed, let's simulate a mouse movement
// to check if scrollbar now is hovered
SGUIMessage msg(GUIM_MOUSE_MOTION);
HandleMessage(msg);
break;
}
default:
break;
}
}
Index: ps/trunk/source/gui/IGUIScrollBarOwner.h
===================================================================
--- ps/trunk/source/gui/IGUIScrollBarOwner.h (revision 22586)
+++ ps/trunk/source/gui/IGUIScrollBarOwner.h (revision 22587)
@@ -1,87 +1,87 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
#ifndef INCLUDED_IGUISCROLLBAROWNER
#define INCLUDED_IGUISCROLLBAROWNER
#include "GUI.h"
struct SGUIScrollBarStyle;
class IGUIScrollBar;
/**
* Base-class this if you want an object to contain
* one, or several, scroll-bars.
*
* @see IGUIObject
* @see IGUIScrollBar
*/
class IGUIScrollBarOwner : virtual public IGUIObject
{
friend class IGUIScrollBar;
public:
- IGUIScrollBarOwner();
+ IGUIScrollBarOwner(CGUI* pGUI);
virtual ~IGUIScrollBarOwner();
virtual void Draw();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates();
/**
* Interface for the m_ScrollBar to use.
*/
virtual const SGUIScrollBarStyle* GetScrollBarStyle(const CStr& style) const;
/**
* Add a scroll-bar
*/
virtual void AddScrollBar(IGUIScrollBar* scrollbar);
/**
* Get Scroll Bar reference (it should be transparent it's actually
* pointers).
*/
virtual IGUIScrollBar& GetScrollBar(const int& index)
{
return *m_ScrollBars[index];
}
/**
* Get the position of the scroll bar at @param index.
* Equivalent to GetScrollbar(index).GetPos().
*/
virtual float GetScrollBarPos(const int index) const;
protected:
/**
* Predominately you will only have one, but you can have
* as many as you like.
*/
std::vector m_ScrollBars;
};
#endif // INCLUDED_IGUISCROLLBAROWNER
Index: ps/trunk/source/gui/IGUIScrollBar.h
===================================================================
--- ps/trunk/source/gui/IGUIScrollBar.h (revision 22586)
+++ ps/trunk/source/gui/IGUIScrollBar.h (revision 22587)
@@ -1,452 +1,446 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
/*
A GUI ScrollBar
--Overview--
A GUI Scrollbar, this class doesn't present all functionality
to the scrollbar, it just controls the drawing and a wrapper
for interaction with it.
--Usage--
Used in everywhere scrollbars are needed, like in a combobox for instance.
--More info--
Check GUI.h
*/
#ifndef INCLUDED_IGUISCROLLBAR
#define INCLUDED_IGUISCROLLBAR
#include "GUI.h"
/**
* The GUI Scroll-bar style. Tells us how scroll-bars look and feel.
*
* A scroll-bar style can choose whether to support horizontal, vertical
* or both.
*
* @see IGUIScrollBar
*/
struct SGUIScrollBarStyle
{
//--------------------------------------------------------
/** @name General Settings */
//--------------------------------------------------------
//@{
/**
* Width of bar, also both sides of the edge buttons.
*/
float m_Width;
/**
* Scrollable with the wheel.
*/
bool m_ScrollWheel;
/**
* How much (in percent, 0.1f = 10%) to scroll each time
* the wheel is admitted, or the buttons are pressed.
*/
float m_ScrollSpeed;
/**
* Whether or not the edge buttons should appear or not. Sometimes
* you actually don't want them, like perhaps in a combo box.
*/
bool m_ScrollButtons;
/**
* Sometimes there is *a lot* to scroll, but to prevent the scroll "bar"
* from being almost invisible (or ugly), you can set a minimum in pixel
* size.
*/
float m_MinimumBarSize;
/**
* Sometimes you would like your scroll bar to have a fixed maximum size
* so that the texture does not get too stretched, you can set a maximum
* in pixels.
*/
float m_MaximumBarSize;
/**
* True if you want edge buttons, i.e. buttons that can be pressed in order
* to scroll.
*/
bool m_UseEdgeButtons;
//@}
//--------------------------------------------------------
/** @name Vertical Sprites */
//--------------------------------------------------------
//@{
CGUISpriteInstance m_SpriteButtonTop;
CGUISpriteInstance m_SpriteButtonTopPressed;
CGUISpriteInstance m_SpriteButtonTopDisabled;
CGUISpriteInstance m_SpriteButtonTopOver;
CGUISpriteInstance m_SpriteButtonBottom;
CGUISpriteInstance m_SpriteButtonBottomPressed;
CGUISpriteInstance m_SpriteButtonBottomDisabled;
CGUISpriteInstance m_SpriteButtonBottomOver;
CGUISpriteInstance m_SpriteBarVertical;
CGUISpriteInstance m_SpriteBarVerticalOver;
CGUISpriteInstance m_SpriteBarVerticalPressed;
CGUISpriteInstance m_SpriteBackVertical;
//@}
//--------------------------------------------------------
/** @name Horizontal Sprites */
//--------------------------------------------------------
//@{
CGUISpriteInstance m_SpriteButtonLeft;
CGUISpriteInstance m_SpriteButtonLeftPressed;
CGUISpriteInstance m_SpriteButtonLeftDisabled;
CGUISpriteInstance m_SpriteButtonRight;
CGUISpriteInstance m_SpriteButtonRightPressed;
CGUISpriteInstance m_SpriteButtonRightDisabled;
CGUISpriteInstance m_SpriteBackHorizontal;
CGUISpriteInstance m_SpriteBarHorizontal;
//@}
};
/**
* The GUI Scroll-bar, used everywhere there is a scroll-bar in the game.
*
* To include a scroll-bar to an object, inherent the object from
* IGUIScrollBarOwner and call AddScrollBar() to add the scroll-bars.
*
* It's also important that the scrollbar is located within the parent
* object's mouse over area. Otherwise the input won't be sent to the
* scroll-bar.
*
* The class does not provide all functionality to the scroll-bar, many
* things the parent of the scroll-bar, must provide. Like a combo-box.
*/
class IGUIScrollBar
{
public:
- IGUIScrollBar();
+ IGUIScrollBar(CGUI* m_pGUI);
virtual ~IGUIScrollBar();
public:
/**
* Draw the scroll-bar
*/
virtual void Draw() = 0;
/**
* If an object that contains a scrollbar has got messages, send
* them to the scroll-bar and it will see if the message regarded
* itself.
*
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message) = 0;
/**
* Set m_Pos with g_mouse_x/y input, i.e. when draggin.
*/
virtual void SetPosFromMousePos(const CPos& mouse) = 0;
/**
* Hovering the scroll minus button
*
* @param mouse current mouse position
* @return True if mouse positions are hovering the button
*/
virtual bool HoveringButtonMinus(const CPos& UNUSED(mouse)) { return false; }
/**
* Hovering the scroll plus button
*
* @param mouse current mouse position
* @return True if mouse positions are hovering the button
*/
virtual bool HoveringButtonPlus(const CPos& UNUSED(mouse)) { return false; }
/**
* Get scroll-position
*/
float GetPos() const { return m_Pos; }
/**
* Set scroll-position by hand
*/
virtual void SetPos(float f) { m_Pos = f; UpdatePosBoundaries(); }
/**
* Get the value of m_Pos that corresponds to the bottom of the scrollable region
*/
float GetMaxPos() const { return std::max(0.f, m_ScrollRange - m_ScrollSpace); }
/**
* Scrollbars without height shouldn't be visible
*/
bool IsVisible() const { return GetMaxPos() != 0.f; }
/**
* Increase scroll one step
*/
virtual void ScrollPlus() { m_Pos += 30.f; UpdatePosBoundaries(); }
/**
* Decrease scroll one step
*/
virtual void ScrollMinus() { m_Pos -= 30.f; UpdatePosBoundaries(); }
/**
* Increase scroll three steps
*/
virtual void ScrollPlusPlenty() { m_Pos += 90.f; UpdatePosBoundaries(); }
/**
* Decrease scroll three steps
*/
virtual void ScrollMinusPlenty() { m_Pos -= 90.f; UpdatePosBoundaries(); }
/**
* Set host object, must be done almost at creation of scroll bar.
* @param pOwner Pointer to host object.
*/
void SetHostObject(IGUIScrollBarOwner* pOwner) { m_pHostObject = pOwner; }
/**
* Get GUI pointer
* @return CGUI pointer
*/
CGUI* GetGUI() const;
/**
- * Set GUI pointer
- * @param pGUI pointer to CGUI object.
- */
- void SetGUI(CGUI* pGUI) { m_pGUI = pGUI; }
-
- /**
* Set Width
* @param width Width
*/
void SetWidth(float width) { m_Width = width; }
/**
* Set X Position
* @param x Position in this axis
*/
void SetX(float x) { m_X = x; }
/**
* Set Y Position
* @param y Position in this axis
*/
void SetY(float y) { m_Y = y; }
/**
* Set Z Position
* @param z Position in this axis
*/
void SetZ(float z) { m_Z = z; }
/**
* Set Length of scroll bar
* @param length Length
*/
void SetLength(float length) { m_Length = length; }
/**
* Set content length
* @param range Maximum scrollable range
*/
void SetScrollRange(float range) { m_ScrollRange = std::max(range, 1.f); SetupBarSize(); UpdatePosBoundaries(); }
/**
* Set space that is visible in the scrollable control.
* @param space Visible area in the scrollable control.
*/
void SetScrollSpace(float space) { m_ScrollSpace = space; SetupBarSize(); UpdatePosBoundaries(); }
/**
* Set bar pressed
* @param b True if bar is pressed
*/
void SetBarPressed(bool b) { m_BarPressed = b; }
/**
* Set Scroll bar style string
* @param style String with scroll bar style reference name
*/
void SetScrollBarStyle(const CStr& style) { m_ScrollBarStyle = style; }
/**
* Get style used by the scrollbar
* @return Scroll bar style struct.
*/
const SGUIScrollBarStyle* GetStyle() const;
/**
* Get the rectangle of the actual BAR. not the whole scroll-bar.
* @return Rectangle, CRect
*/
virtual CRect GetBarRect() const = 0;
/**
* Get the rectangle of the outline of the scrollbar, every component of the
* scroll-bar should be inside this area.
* @return Rectangle, CRect
*/
virtual CRect GetOuterRect() const = 0;
protected:
/**
* Sets up bar size
*/
void SetupBarSize();
/**
* Call every time m_Pos has been updated.
*/
void UpdatePosBoundaries();
protected:
//@}
//--------------------------------------------------------
/** @name Settings */
//--------------------------------------------------------
//@{
/**
* Width of the scroll bar
*/
float m_Width;
/**
* Absolute X Position
*/
float m_X;
/**
* Absolute Y Position
*/
float m_Y;
/**
* Absolute Z Position
*/
float m_Z;
/**
* Total length of scrollbar, including edge buttons.
*/
float m_Length;
/**
* Content that can be scrolled, in pixels
*/
float m_ScrollRange;
/**
* Content that can be viewed at a time, in pixels
*/
float m_ScrollSpace;
/**
* Use input from the scroll-wheel? True or false.
*/
float m_BarSize;
/**
* Scroll bar style reference name
*/
CStr m_ScrollBarStyle;
/**
* Pointer to scroll bar style used.
*/
SGUIScrollBarStyle *m_pStyle;
/**
* Host object, prerequisite!
*/
IGUIScrollBarOwner *m_pHostObject;
/**
* Reference to CGUI object, these cannot work stand-alone
*/
CGUI *m_pGUI;
/**
* Mouse position when bar was pressed
*/
CPos m_BarPressedAtPos;
//@}
//--------------------------------------------------------
/** @name States */
//--------------------------------------------------------
//@{
/**
* If the bar is currently being pressed and dragged.
*/
bool m_BarPressed;
/**
* Bar being hovered or not
*/
bool m_BarHovered;
/**
* Scroll buttons hovered
*/
bool m_ButtonMinusHovered, m_ButtonPlusHovered;
/**
* Scroll buttons pressed
*/
bool m_ButtonMinusPressed, m_ButtonPlusPressed;
/**
* Position of scroll bar, 0 means scrolled all the way to one side.
* It is measured in pixels, it is up to the host to make it actually
* apply in pixels.
*/
float m_Pos;
/**
* Position from 0.f to 1.f it had when the bar was pressed.
*/
float m_PosWhenPressed;
//@}
};
#endif // INCLUDED_IGUISCROLLBAR
Index: ps/trunk/source/gui/MiniMap.cpp
===================================================================
--- ps/trunk/source/gui/MiniMap.cpp (revision 22586)
+++ ps/trunk/source/gui/MiniMap.cpp (revision 22587)
@@ -1,708 +1,709 @@
/* Copyright (C) 2019 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 "MiniMap.h"
#include "graphics/GameView.h"
#include "graphics/LOSTexture.h"
#include "graphics/MiniPatch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "gui/GUI.h"
#include "gui/GUIManager.h"
#include "lib/bits.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/ogl.h"
#include "lib/timer.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h"
#include "simulation2/system/ParamNode.h"
extern bool g_GameRestarted;
// Set max drawn entities to UINT16_MAX for now, which is more than enough
// TODO: we should be cleverer about drawing them to reduce clutter
const u16 MAX_ENTITIES_DRAWN = 65535;
static unsigned int ScaleColor(unsigned int color, float x)
{
unsigned int r = unsigned(float(color & 0xff) * x);
unsigned int g = unsigned(float((color>>8) & 0xff) * x);
unsigned int b = unsigned(float((color>>16) & 0xff) * x);
return (0xff000000 | b | g<<8 | r<<16);
}
-CMiniMap::CMiniMap() :
+CMiniMap::CMiniMap(CGUI* pGUI) :
+ IGUIObject(pGUI),
m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f),
m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW),
m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0)
{
AddSetting(GUIST_CStrW, "tooltip");
AddSetting(GUIST_CStr, "tooltip_style");
m_Clicking = false;
m_MouseHovering = false;
// Register Relax NG validator
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
// Get the maximum height for unit passage in water.
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
else
m_ShallowPassageHeight = 0.0f;
m_AttributePos.type = GL_FLOAT;
m_AttributePos.elems = 2;
m_VertexArray.AddAttribute(&m_AttributePos);
m_AttributeColor.type = GL_UNSIGNED_BYTE;
m_AttributeColor.elems = 4;
m_VertexArray.AddAttribute(&m_AttributeColor);
m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_VertexArray.Layout();
m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
*index++ = i;
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
{
(*attrColor)[0] = 0;
(*attrColor)[1] = 0;
(*attrColor)[2] = 0;
(*attrColor)[3] = 0;
++attrColor;
(*attrPos)[0] = -10000.0f;
(*attrPos)[1] = -10000.0f;
++attrPos;
}
m_VertexArray.Upload();
double blinkDuration = 1.0;
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
}
m_HalfBlinkDuration = blinkDuration/2;
}
CMiniMap::~CMiniMap()
{
Destroy();
}
void CMiniMap::HandleMessage(SGUIMessage& Message)
{
switch (Message.type)
{
case GUIM_MOUSE_PRESS_LEFT:
if (m_MouseHovering)
{
SetCameraPos();
m_Clicking = true;
}
break;
case GUIM_MOUSE_RELEASE_LEFT:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
case GUIM_MOUSE_DBLCLICK_LEFT:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
case GUIM_MOUSE_ENTER:
m_MouseHovering = true;
break;
case GUIM_MOUSE_LEAVE:
m_Clicking = false;
m_MouseHovering = false;
break;
case GUIM_MOUSE_RELEASE_RIGHT:
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1);
break;
case GUIM_MOUSE_DBLCLICK_RIGHT:
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2);
break;
case GUIM_MOUSE_MOTION:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
break;
case GUIM_MOUSE_WHEEL_DOWN:
case GUIM_MOUSE_WHEEL_UP:
Message.Skip();
break;
default:
break;
}
}
bool CMiniMap::MouseOver()
{
// Get the mouse position.
CPos mousePos = GetMousePos();
// Get the position of the center of the minimap.
CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0);
// Take the magnitude of the difference of the mouse position and minimap center.
double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2));
// If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap.
if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0)
return true;
else
return false;
}
void CMiniMap::GetMouseWorldCoordinates(float& x, float& z)
{
// Determine X and Z according to proportion of mouse position and minimap
CPos mousePos = GetMousePos();
float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth();
float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight();
float angle = GetAngle();
// Scale world coordinates for shrunken square map
x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5);
z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5);
}
void CMiniMap::SetCameraPos()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
GetMouseWorldCoordinates(target.X, target.Z);
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
float CMiniMap::GetAngle()
{
CVector3D cameraIn = m_Camera->m_Orientation.GetIn();
return -atan2(cameraIn.X, cameraIn.Z);
}
void CMiniMap::FireWorldClickEvent(int UNUSED(button), int UNUSED(clicks))
{
JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
float x, z;
GetMouseWorldCoordinates(x, z);
JS::RootedValue coords(cx);
g_GUI->GetActiveGUI()->GetScriptInterface()->CreateObject(&coords, "x", x, "z", z);
JS::AutoValueVector paramData(cx);
paramData.append(coords);
ScriptEvent("worldclick", paramData);
}
// This sets up and draws the rectangle on the minimap
// which represents the view of the camera in the world.
void CMiniMap::DrawViewRect(CMatrix3D transform)
{
// Compute the camera frustum intersected with a fixed-height plane.
// Use the water height as a fixed base height, which should be the lowest we can go
float h = g_Renderer.GetWaterManager()->m_WaterHeight;
const float width = m_CachedActualSize.GetWidth();
const float height = m_CachedActualSize.GetHeight();
const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize);
CVector3D hitPt[4];
hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h);
hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h);
hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h);
hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h);
float ViewRect[4][2];
for (int i = 0; i < 4; ++i)
{
// convert to minimap space
ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize);
ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize);
}
float viewVerts[] = {
ViewRect[0][0], -ViewRect[0][1],
ViewRect[1][0], -ViewRect[1][1],
ViewRect[2][0], -ViewRect[2][1],
ViewRect[3][0], -ViewRect[3][1]
};
// Enable Scissoring to restrict the rectangle to only the minimap.
glScissor(
m_CachedActualSize.left * g_GuiScale,
g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale,
width * g_GuiScale,
height * g_GuiScale);
glEnable(GL_SCISSOR_TEST);
glLineWidth(2.0f);
CShaderDefines lineDefines;
lineDefines.Add(str_MINIMAP_LINE, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, transform);
shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f);
shader->VertexPointer(2, GL_FLOAT, 0, viewVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_LINE_LOOP, 0, 4);
tech->EndPass();
glLineWidth(1.0f);
glDisable(GL_SCISSOR_TEST);
}
struct MinimapUnitVertex
{
u8 r, g, b, a;
float x, y;
};
// Adds a vertex to the passed VertexArray
static void inline addVertex(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.x;
(*attrPos)[1] = v.y;
++attrPos;
}
void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z)
{
// Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m)
// Scale square maps to fit in circular minimap area
const float s = sin(angle) * m_MapScale;
const float c = cos(angle) * m_MapScale;
const float m = coordMax / 2.f;
float quadTex[] = {
m*(-c + s + 1.f), m*(-c + -s + 1.f),
m*(c + s + 1.f), m*(-c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(-c + -s + 1.f), m*(c + -s + 1.f),
m*(-c + s + 1.f), m*(-c + -s + 1.f)
};
float quadVerts[] = {
x, y, z,
x2, y, z,
x2, y2, z,
x2, y2, z,
x, y2, z,
x, y, z
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(3, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_TRIANGLES, 0, 6);
}
// TODO: render the minimap in a framebuffer and just draw the frambuffer texture
// most of the time, updating the framebuffer twice a frame.
// Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling
// (those operations cause a gpu sync, which slows down the way gpu works)
void CMiniMap::Draw()
{
PROFILE3("render minimap");
// The terrain isn't actually initialized until the map is loaded, which
// happens when the game is started, so abort until then.
if (!(GetGUI() && g_Game && g_Game->IsGameStarted()))
return;
CSimulation2* sim = g_Game->GetSimulation2();
CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
// Set our globals in case they hadn't been set before
m_Camera = g_Game->GetView()->GetCamera();
m_Terrain = g_Game->GetWorld()->GetTerrain();
m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left);
m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top);
m_MapSize = m_Terrain->GetVerticesPerSide();
m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize);
m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f);
if (!m_TerrainTexture || g_GameRestarted)
CreateTextures();
// only update 2x / second
// (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow)
// TODO: Update all but camera at same speed as simulation
static double last_time;
const double cur_time = timer_Time();
const bool doUpdate = cur_time - last_time > 0.5;
if (doUpdate)
{
last_time = cur_time;
if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight)
RebuildTerrainTexture();
}
const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
const float z = GetBufferedZ();
const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize;
const float angle = GetAngle();
const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f);
// Disable depth updates to prevent apparent z-fighting-related issues
// with some drivers causing units to get drawn behind the texture.
glDepthMask(0);
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
CShaderDefines baseDefines;
baseDefines.Add(str_MINIMAP_BASE, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines);
tech->BeginPass();
shader = tech->GetShader();
// Draw the main textured quad
shader->BindTexture(str_baseTex, m_TerrainTexture);
const CMatrix3D baseTransform = GetDefaultGuiMatrix();
CMatrix3D baseTextureTransform;
baseTextureTransform.SetIdentity();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, baseTextureTransform);
DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z);
// Draw territory boundaries
glEnable(GL_BLEND);
CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture();
shader->BindTexture(str_baseTex, territoryTexture.GetTexture());
const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *territoryTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
// Draw the LOS quad in black, using alpha values from the LOS texture
CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
CShaderDefines losDefines;
losDefines.Add(str_MINIMAP_LOS, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->BindTexture(str_baseTex, losTexture.GetTexture());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *losTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
glDisable(GL_BLEND);
PROFILE_START("minimap units");
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_pointSize, 3.f);
CMatrix3D unitMatrix;
unitMatrix.SetIdentity();
// Center the minimap on the origin of the axis of rotation.
unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f);
// Rotate the map.
unitMatrix.RotateZ(angle);
// Scale square maps to fit.
unitMatrix.Scale(unitScale, unitScale, 1.f);
// Move the minimap back to it's starting position.
unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f);
// Move the minimap to it's final location.
unitMatrix.Translate(x, y, z);
// Apply the gui matrix.
unitMatrix *= GetDefaultGuiMatrix();
// Load the transform into the shader.
shader->Uniform(str_transform, unitMatrix);
const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap);
if (doUpdate)
{
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
m_EntitiesDrawn = 0;
MinimapUnitVertex v;
std::vector pingingVertices;
pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
if (cur_time > m_NextBlinkTime)
{
m_BlinkState = !m_BlinkState;
m_NextBlinkTime = cur_time + m_HalfBlinkDuration;
}
entity_pos_t posX, posZ;
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
ICmpMinimap* cmpMinimap = static_cast(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer());
if (vis != ICmpRangeManager::VIS_HIDDEN)
{
v.a = 255;
v.x = posX.ToFloat() * sx;
v.y = -posZ.ToFloat() * sy;
// Check minimap pinging to indicate something
if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration))
{
v.r = 255; // ping color is white
v.g = 255;
v.b = 255;
pingingVertices.push_back(v);
}
else
{
addVertex(v, attrColor, attrPos);
++m_EntitiesDrawn;
}
}
}
}
// Add the pinged vertices at the end, so they are drawn on top
for (size_t v = 0; v < pingingVertices.size(); ++v)
{
addVertex(pingingVertices[v], attrColor, attrPos);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
m_VertexArray.Upload();
}
m_VertexArray.PrepareForRendering();
if (m_EntitiesDrawn > 0)
{
#if !CONFIG2_GLES
if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
u8* indexBase = m_IndexArray.Bind();
u8* base = m_VertexArray.Bind();
const GLsizei stride = (GLsizei)m_VertexArray.GetStride();
shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset);
shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase);
g_Renderer.GetStats().m_DrawCalls++;
CVertexBuffer::Unbind();
#if !CONFIG2_GLES
if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
}
tech->EndPass();
DrawViewRect(unitMatrix);
PROFILE_END("minimap units");
// Reset depth mask
glDepthMask(1);
}
void CMiniMap::CreateTextures()
{
Destroy();
// Create terrain texture
glGenTextures(1, &m_TerrainTexture);
g_Renderer.BindTexture(0, m_TerrainTexture);
// Initialise texture with solid black, for the areas we don't
// overwrite with glTexSubImage2D later
u32* texData = new u32[m_TextureSize * m_TextureSize];
for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i)
texData[i] = 0xFF000000;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
delete[] texData;
m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Rebuild and upload both of them
RebuildTerrainTexture();
}
void CMiniMap::RebuildTerrainTexture()
{
u32 x = 0;
u32 y = 0;
u32 w = m_MapSize - 1;
u32 h = m_MapSize - 1;
m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;
m_TerrainDirty = false;
for (u32 j = 0; j < h; ++j)
{
u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
for (u32 i = 0; i < w; ++i)
{
float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i, (int)j+1)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
) / 4.0f;
if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
{
// shallow water
*dataPtr++ = 0xffc09870;
}
else if (avgHeight < m_WaterHeight)
{
// Set water as constant color for consistency on different maps
*dataPtr++ = 0xffa07850;
}
else
{
int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
int val = (hmap / 3) + 170;
u32 color = 0xFFFFFFFF;
CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j);
if (mp)
{
CTerrainTextureEntry* tex = mp->GetTextureEntry();
if (tex)
{
// If the texture can't be loaded yet, set the dirty flags
// so we'll try regenerating the terrain texture again soon
if(!tex->GetTexture()->TryLoad())
m_TerrainDirty = true;
color = tex->GetBaseColor();
}
}
*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
}
}
}
// Upload the texture
g_Renderer.BindTexture(0, m_TerrainTexture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData);
}
void CMiniMap::Destroy()
{
if (m_TerrainTexture)
{
glDeleteTextures(1, &m_TerrainTexture);
m_TerrainTexture = 0;
}
SAFE_ARRAY_DELETE(m_TerrainData);
}
Index: ps/trunk/source/gui/CButton.cpp
===================================================================
--- ps/trunk/source/gui/CButton.cpp (revision 22586)
+++ ps/trunk/source/gui/CButton.cpp (revision 22587)
@@ -1,121 +1,122 @@
/* Copyright (C) 2019 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 "CButton.h"
#include "gui/CGUIColor.h"
#include "lib/ogl.h"
-CButton::CButton()
+CButton::CButton(CGUI* pGUI)
+ : IGUIObject(pGUI)
{
AddSetting(GUIST_float, "buffer_zone");
AddSetting(GUIST_CGUIString, "caption");
AddSetting(GUIST_int, "cell_id");
AddSetting(GUIST_CStrW, "font");
AddSetting(GUIST_CStrW, "sound_disabled");
AddSetting(GUIST_CStrW, "sound_enter");
AddSetting(GUIST_CStrW, "sound_leave");
AddSetting(GUIST_CStrW, "sound_pressed");
AddSetting(GUIST_CStrW, "sound_released");
AddSetting(GUIST_CGUISpriteInstance, "sprite");
AddSetting(GUIST_CGUISpriteInstance, "sprite_over");
AddSetting(GUIST_CGUISpriteInstance, "sprite_pressed");
AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled");
AddSetting(GUIST_EAlign, "text_align");
AddSetting(GUIST_EVAlign, "text_valign");
AddSetting(GUIST_CGUIColor, "textcolor");
AddSetting(GUIST_CGUIColor, "textcolor_over");
AddSetting(GUIST_CGUIColor, "textcolor_pressed");
AddSetting(GUIST_CGUIColor, "textcolor_disabled");
AddSetting(GUIST_CStrW, "tooltip");
AddSetting(GUIST_CStr, "tooltip_style");
// Add text
AddText(new SGUIText());
}
CButton::~CButton()
{
}
void CButton::SetupText()
{
if (!GetGUI())
return;
ENSURE(m_GeneratedTexts.size() == 1);
CStrW font;
if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
// Use the default if none is specified
// TODO Gee: (2004-08-14) Default should not be hard-coded, but be in styles!
font = L"default";
CGUIString caption;
GUI::GetSetting(this, "caption", caption);
float buffer_zone = 0.f;
GUI::GetSetting(this, "buffer_zone", buffer_zone);
*m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, m_CachedActualSize.GetWidth(), buffer_zone, this);
CalculateTextPosition(m_CachedActualSize, m_TextPos, *m_GeneratedTexts[0]);
}
void CButton::HandleMessage(SGUIMessage& Message)
{
// Important
IGUIButtonBehavior::HandleMessage(Message);
IGUITextOwner::HandleMessage(Message);
}
void CButton::Draw()
{
float bz = GetBufferedZ();
CGUISpriteInstance* sprite;
CGUISpriteInstance* sprite_over;
CGUISpriteInstance* sprite_pressed;
CGUISpriteInstance* sprite_disabled;
int cell_id;
// Statically initialise some strings, so we don't have to do
// lots of allocation every time this function is called
static const CStr strSprite("sprite");
static const CStr strSpriteOver("sprite_over");
static const CStr strSpritePressed("sprite_pressed");
static const CStr strSpriteDisabled("sprite_disabled");
static const CStr strCellId("cell_id");
GUI::GetSettingPointer(this, strSprite, sprite);
GUI::GetSettingPointer(this, strSpriteOver, sprite_over);
GUI::GetSettingPointer(this, strSpritePressed, sprite_pressed);
GUI::GetSettingPointer(this, strSpriteDisabled, sprite_disabled);
GUI::GetSetting(this, strCellId, cell_id);
DrawButton(m_CachedActualSize,
bz,
*sprite,
*sprite_over,
*sprite_pressed,
*sprite_disabled,
cell_id);
CGUIColor color = ChooseColor();
DrawText(0, color, m_TextPos, bz+0.1f);
}
Index: ps/trunk/source/gui/CButton.h
===================================================================
--- ps/trunk/source/gui/CButton.h (revision 22586)
+++ ps/trunk/source/gui/CButton.h (revision 22587)
@@ -1,65 +1,65 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
#ifndef INCLUDED_CBUTTON
#define INCLUDED_CBUTTON
#include "GUI.h"
/**
* Button
*
* @see IGUIObject
* @see IGUIButtonBehavior
*/
class CButton : public IGUIButtonBehavior, public IGUITextOwner
{
GUI_OBJECT(CButton)
public:
- CButton();
+ CButton(CGUI* pGUI);
virtual ~CButton();
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates() { IGUIButtonBehavior::ResetStates(); }
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Draws the Button
*/
virtual void Draw();
protected:
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
/**
* Placement of text.
*/
CPos m_TextPos;
};
#endif // INCLUDED_CBUTTON
Index: ps/trunk/source/gui/CChart.cpp
===================================================================
--- ps/trunk/source/gui/CChart.cpp (revision 22586)
+++ ps/trunk/source/gui/CChart.cpp (revision 22587)
@@ -1,331 +1,332 @@
/* Copyright (C) 2019 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 "CChart.h"
#include "gui/CGUIColor.h"
#include "graphics/ShaderManager.h"
#include "i18n/L10n.h"
#include "lib/ogl.h"
#include "ps/CLogger.h"
#include "renderer/Renderer.h"
#include "third_party/cppformat/format.h"
#include
-CChart::CChart()
+CChart::CChart(CGUI* pGUI)
+ : IGUIObject(pGUI)
{
AddSetting(GUIST_CGUIColor, "axis_color");
AddSetting(GUIST_float, "axis_width");
AddSetting(GUIST_float, "buffer_zone");
AddSetting(GUIST_CStrW, "font");
AddSetting(GUIST_CStrW, "format_x");
AddSetting(GUIST_CStrW, "format_y");
AddSetting(GUIST_CGUIList, "series_color");
AddSetting(GUIST_CGUISeries, "series");
AddSetting(GUIST_EAlign, "text_align");
GUI::GetSetting(this, "axis_width", m_AxisWidth);
GUI::GetSetting(this, "format_x", m_FormatX);
GUI::GetSetting(this, "format_y", m_FormatY);
}
CChart::~CChart()
{
}
void CChart::HandleMessage(SGUIMessage& Message)
{
// TODO: implement zoom
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
{
GUI::GetSetting(this, "axis_width", m_AxisWidth);
GUI::GetSetting(this, "format_x", m_FormatX);
GUI::GetSetting(this, "format_y", m_FormatY);
UpdateSeries();
break;
}
}
}
void CChart::DrawLine(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const
{
shader->Uniform(str_color, color);
shader->VertexPointer(3, GL_FLOAT, 0, &vertices[0]);
shader->AssertPointersBound();
glEnable(GL_LINE_SMOOTH);
glLineWidth(1.1f);
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_LINE_STRIP, 0, vertices.size() / 3);
glLineWidth(1.0f);
glDisable(GL_LINE_SMOOTH);
}
void CChart::DrawTriangleStrip(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const
{
shader->Uniform(str_color, color);
shader->VertexPointer(3, GL_FLOAT, 0, &vertices[0]);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size() / 3);
}
void CChart::DrawAxes(const CShaderProgramPtr& shader) const
{
const float bz = GetBufferedZ();
CRect rect = GetChartRect();
std::vector vertices;
vertices.reserve(30);
#define ADD(x, y) vertices.push_back(x); vertices.push_back(y); vertices.push_back(bz + 0.5f);
ADD(m_CachedActualSize.right, m_CachedActualSize.bottom);
ADD(rect.right + m_AxisWidth, rect.bottom);
ADD(m_CachedActualSize.left, m_CachedActualSize.bottom);
ADD(rect.left, rect.bottom);
ADD(m_CachedActualSize.left, m_CachedActualSize.top);
ADD(rect.left, rect.top - m_AxisWidth);
#undef ADD
CGUIColor axis_color(0.5f, 0.5f, 0.5f, 1.f);
GUI::GetSetting(this, "axis_color", axis_color);
DrawTriangleStrip(shader, axis_color, vertices);
}
void CChart::Draw()
{
PROFILE3("render chart");
if (!GetGUI())
return;
if (m_Series.empty())
return;
const float bz = GetBufferedZ();
CRect rect = GetChartRect();
const float width = rect.GetWidth();
const float height = rect.GetHeight();
// Disable depth updates to prevent apparent z-fighting-related issues
// with some drivers causing units to get drawn behind the texture.
glDepthMask(0);
// Setup the render state
CMatrix3D transform = GetDefaultGuiMatrix();
CShaderDefines lineDefines;
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid, g_Renderer.GetSystemShaderDefines(), lineDefines);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, transform);
CVector2D scale(width / (m_RightTop.X - m_LeftBottom.X), height / (m_RightTop.Y - m_LeftBottom.Y));
for (const CChartData& data : m_Series)
{
if (data.m_Points.empty())
continue;
std::vector vertices;
for (const CVector2D& point : data.m_Points)
{
if (fabs(point.X) != std::numeric_limits::infinity() && fabs(point.Y) != std::numeric_limits::infinity())
{
vertices.push_back(rect.left + (point.X - m_LeftBottom.X) * scale.X);
vertices.push_back(rect.bottom - (point.Y - m_LeftBottom.Y) * scale.Y);
vertices.push_back(bz + 0.5f);
}
else
{
DrawLine(shader, data.m_Color, vertices);
vertices.clear();
}
}
if (!vertices.empty())
DrawLine(shader, data.m_Color, vertices);
}
if (m_AxisWidth > 0)
DrawAxes(shader);
tech->EndPass();
// Reset depth mask
glDepthMask(1);
for (size_t i = 0; i < m_TextPositions.size(); ++i)
DrawText(i, CGUIColor(1.f, 1.f, 1.f, 1.f), m_TextPositions[i], bz + 0.5f);
}
CRect CChart::GetChartRect() const
{
return CRect(
m_CachedActualSize.TopLeft() + CPos(m_AxisWidth, m_AxisWidth),
m_CachedActualSize.BottomRight() - CPos(m_AxisWidth, m_AxisWidth)
);
}
void CChart::UpdateSeries()
{
CGUISeries* pSeries;
GUI::GetSettingPointer(this, "series", pSeries);
CGUIList* pSeriesColor;
GUI::GetSettingPointer(this, "series_color", pSeriesColor);
m_Series.clear();
m_Series.resize(pSeries->m_Series.size());
for (size_t i = 0; i < pSeries->m_Series.size(); ++i)
{
CChartData& data = m_Series[i];
if (i < pSeriesColor->m_Items.size() && !GUI::ParseColor(pSeriesColor->m_Items[i].GetOriginalString(), data.m_Color, 0))
LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(pSeriesColor->m_Items[i].GetOriginalString()));
data.m_Points = pSeries->m_Series[i];
}
UpdateBounds();
SetupText();
}
void CChart::SetupText()
{
if (!GetGUI())
return;
for (SGUIText* t : m_GeneratedTexts)
delete t;
m_GeneratedTexts.clear();
m_TextPositions.clear();
if (m_Series.empty())
return;
CStrW font;
if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
font = L"default";
float buffer_zone = 0.f;
GUI::GetSetting(this, "buffer_zone", buffer_zone);
// Add Y-axis
GUI::GetSetting(this, "format_y", m_FormatY);
const float height = GetChartRect().GetHeight();
// TODO: split values depend on the format;
if (m_EqualY)
{
// We don't need to generate many items for equal values
AddFormattedValue(m_FormatY, m_RightTop.Y, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().TopLeft());
}
else
for (int i = 0; i < 3; ++i)
{
AddFormattedValue(m_FormatY, m_RightTop.Y - (m_RightTop.Y - m_LeftBottom.Y) / 3.f * i, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().TopLeft() + CPos(0.f, height / 3.f * i));
}
// Add X-axis
GUI::GetSetting(this, "format_x", m_FormatX);
const float width = GetChartRect().GetWidth();
if (m_EqualX)
{
CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size);
}
else
for (int i = 0; i < 3; ++i)
{
CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X - (m_RightTop.X - m_LeftBottom.X) / 3 * i, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size - CPos(width / 3 * i, 0.f));
}
}
CSize CChart::AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone)
{
// TODO: we need to catch cases with equal formatted values.
CGUIString gui_str;
if (format == L"DECIMAL2")
{
wchar_t buffer[64];
swprintf(buffer, 64, L"%.2f", value);
gui_str.SetValue(buffer);
}
else if (format == L"INTEGER")
{
wchar_t buffer[64];
swprintf(buffer, 64, L"%d", std::lround(value));
gui_str.SetValue(buffer);
}
else if (format == L"DURATION_SHORT")
{
const int seconds = value;
wchar_t buffer[64];
swprintf(buffer, 64, L"%d:%02d", seconds / 60, seconds % 60);
gui_str.SetValue(buffer);
}
else if (format == L"PERCENTAGE")
{
wchar_t buffer[64];
swprintf(buffer, 64, L"%d%%", std::lround(value));
gui_str.SetValue(buffer);
}
else
{
LOGERROR("Unsupported chart format: " + format.EscapeToPrintableASCII());
return CSize();
}
SGUIText* text = new SGUIText();
*text = GetGUI()->GenerateText(gui_str, font, 0, buffer_zone, this);
AddText(text);
return text->m_Size;
}
void CChart::UpdateBounds()
{
if (m_Series.empty() || m_Series[0].m_Points.empty())
{
m_LeftBottom = m_RightTop = CVector2D(0.f, 0.f);
return;
}
m_LeftBottom = m_RightTop = m_Series[0].m_Points[0];
for (const CChartData& data : m_Series)
for (const CVector2D& point : data.m_Points)
{
if (fabs(point.X) != std::numeric_limits::infinity() && point.X < m_LeftBottom.X)
m_LeftBottom.X = point.X;
if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y < m_LeftBottom.Y)
m_LeftBottom.Y = point.Y;
if (fabs(point.X) != std::numeric_limits::infinity() && point.X > m_RightTop.X)
m_RightTop.X = point.X;
if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y > m_RightTop.Y)
m_RightTop.Y = point.Y;
}
m_EqualY = m_RightTop.Y == m_LeftBottom.Y;
if (m_EqualY)
m_RightTop.Y += 1;
m_EqualX = m_RightTop.X == m_LeftBottom.X;
if (m_EqualX)
m_RightTop.X += 1;
}
Index: ps/trunk/source/gui/CChart.h
===================================================================
--- ps/trunk/source/gui/CChart.h (revision 22586)
+++ ps/trunk/source/gui/CChart.h (revision 22587)
@@ -1,94 +1,94 @@
/* Copyright (C) 2019 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 .
*/
#ifndef INCLUDED_CCHART
#define INCLUDED_CCHART
#include "gui/GUI.h"
#include "gui/IGUITextOwner.h"
#include "maths/Vector2D.h"
#include
struct CChartData
{
CGUIColor m_Color;
std::vector m_Points;
};
/**
* Chart for a data visualization as lines or points
*
* @see IGUIObject
*/
class CChart : public IGUITextOwner
{
GUI_OBJECT(CChart)
public:
- CChart();
+ CChart(CGUI* pGUI);
virtual ~CChart();
protected:
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Draws the Chart
*/
virtual void Draw();
virtual CRect GetChartRect() const;
void UpdateSeries();
void SetupText();
std::vector m_Series;
CVector2D m_LeftBottom, m_RightTop;
CStrW m_FormatX, m_FormatY;
std::vector m_TextPositions;
float m_AxisWidth;
bool m_EqualX, m_EqualY;
private:
/**
* Helper functions
*/
void DrawLine(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const;
// Draws the triangle sequence so that the each next triangle has a common edge with the previous one.
// If we need to draw n triangles, we need only n + 2 points.
void DrawTriangleStrip(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const;
// Represents axes as triangles and draws them with DrawTriangleStrip.
void DrawAxes(const CShaderProgramPtr& shader) const;
CSize AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone);
void UpdateBounds();
};
#endif // INCLUDED_CCHART
Index: ps/trunk/source/gui/CCheckBox.cpp
===================================================================
--- ps/trunk/source/gui/CCheckBox.cpp (revision 22586)
+++ ps/trunk/source/gui/CCheckBox.cpp (revision 22587)
@@ -1,149 +1,150 @@
/* Copyright (C) 2019 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 "CCheckBox.h"
#include "gui/CGUIColor.h"
#include "graphics/FontMetrics.h"
#include "ps/CLogger.h"
#include "ps/CStrIntern.h"
/**
* TODO: Since there is no call to DrawText, the checkbox won't render any text.
* Thus the font, caption, textcolor and other settings have no effect.
*/
-CCheckBox::CCheckBox()
+CCheckBox::CCheckBox(CGUI* pGUI)
+ : IGUIObject(pGUI)
{
AddSetting(GUIST_float, "buffer_zone");
AddSetting(GUIST_CGUIString, "caption");
AddSetting(GUIST_int, "cell_id");
AddSetting(GUIST_bool, "checked");
AddSetting(GUIST_CStrW, "font");
AddSetting(GUIST_CStrW, "sound_disabled");
AddSetting(GUIST_CStrW, "sound_enter");
AddSetting(GUIST_CStrW, "sound_leave");
AddSetting(GUIST_CStrW, "sound_pressed");
AddSetting(GUIST_CStrW, "sound_released");
AddSetting(GUIST_CGUISpriteInstance, "sprite");
AddSetting(GUIST_CGUISpriteInstance, "sprite_over");
AddSetting(GUIST_CGUISpriteInstance, "sprite_pressed");
AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled");
AddSetting(GUIST_CGUISpriteInstance, "sprite2");
AddSetting(GUIST_CGUISpriteInstance, "sprite2_over");
AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed");
AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled");
AddSetting(GUIST_float, "square_side");
AddSetting(GUIST_CGUIColor, "textcolor");
AddSetting(GUIST_CGUIColor, "textcolor_over");
AddSetting(GUIST_CGUIColor, "textcolor_pressed");
AddSetting(GUIST_CGUIColor, "textcolor_disabled");
AddSetting(GUIST_CStrW, "tooltip");
AddSetting(GUIST_CStr, "tooltip_style");
AddText(new SGUIText());
}
CCheckBox::~CCheckBox()
{
}
void CCheckBox::SetupText()
{
if (!GetGUI())
return;
ENSURE(m_GeneratedTexts.size() == 1);
CStrW font;
if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
// Use the default if none is specified
// TODO Gee: (2004-08-14) Default should not be hard-coded, but be in styles!
font = L"default";
float square_side;
GUI::GetSetting(this, "square_side", square_side);
CGUIString caption;
GUI::GetSetting(this, "caption", caption);
float buffer_zone = 0.f;
GUI::GetSetting(this, "buffer_zone", buffer_zone);
*m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, m_CachedActualSize.GetWidth()-square_side, 0.f, this);
}
void CCheckBox::HandleMessage(SGUIMessage& Message)
{
// Important
IGUIButtonBehavior::HandleMessage(Message);
IGUITextOwner::HandleMessage(Message);
switch (Message.type)
{
case GUIM_PRESSED:
{
bool checked;
// Switch to opposite.
GUI::GetSetting(this, "checked", checked);
checked = !checked;
GUI::SetSetting(this, "checked", checked);
break;
}
default:
break;
}
}
void CCheckBox::Draw()
{
float bz = GetBufferedZ();
bool checked;
int cell_id;
CGUISpriteInstance* sprite;
CGUISpriteInstance* sprite_over;
CGUISpriteInstance* sprite_pressed;
CGUISpriteInstance* sprite_disabled;
GUI::GetSetting(this, "checked", checked);
GUI::GetSetting(this, "cell_id", cell_id);
if (checked)
{
GUI::GetSettingPointer(this, "sprite2", sprite);
GUI::GetSettingPointer(this, "sprite2_over", sprite_over);
GUI::GetSettingPointer(this, "sprite2_pressed", sprite_pressed);
GUI::GetSettingPointer(this, "sprite2_disabled", sprite_disabled);
}
else
{
GUI::GetSettingPointer(this, "sprite", sprite);
GUI::GetSettingPointer(this, "sprite_over", sprite_over);
GUI::GetSettingPointer(this, "sprite_pressed", sprite_pressed);
GUI::GetSettingPointer(this, "sprite_disabled", sprite_disabled);
}
DrawButton(m_CachedActualSize,
bz,
*sprite,
*sprite_over,
*sprite_pressed,
*sprite_disabled,
cell_id);
}
Index: ps/trunk/source/gui/CCheckBox.h
===================================================================
--- ps/trunk/source/gui/CCheckBox.h (revision 22586)
+++ ps/trunk/source/gui/CCheckBox.h (revision 22587)
@@ -1,61 +1,61 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
#ifndef INCLUDED_CCHECKBOX
#define INCLUDED_CCHECKBOX
#include "GUI.h"
/**
* CheckBox
*
* @see IGUIObject
* @see IGUISettingsObject
* @see IGUIButtonBehavior
*/
class CCheckBox : public IGUIButtonBehavior, public IGUITextOwner
{
GUI_OBJECT(CCheckBox)
public:
- CCheckBox();
+ CCheckBox(CGUI* pGUI);
virtual ~CCheckBox();
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates() { IGUIButtonBehavior::ResetStates(); }
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Draws the control
*/
virtual void Draw();
protected:
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
};
#endif // INCLUDED_CCHECKBOX
Index: ps/trunk/source/gui/CDropDown.cpp
===================================================================
--- ps/trunk/source/gui/CDropDown.cpp (revision 22586)
+++ ps/trunk/source/gui/CDropDown.cpp (revision 22587)
@@ -1,564 +1,565 @@
/* Copyright (C) 2019 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 "CDropDown.h"
#include "gui/CGUIColor.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/ogl.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "soundmanager/ISoundManager.h"
-CDropDown::CDropDown()
- : m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1)
+CDropDown::CDropDown(CGUI* pGUI)
+ : CList(pGUI), IGUIObject(pGUI),
+ m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1)
{
AddSetting(GUIST_float, "button_width");
AddSetting(GUIST_float, "dropdown_size");
AddSetting(GUIST_float, "dropdown_buffer");
AddSetting(GUIST_uint, "minimum_visible_items");
// AddSetting(GUIST_CStrW, "font");
AddSetting(GUIST_CStrW, "sound_closed");
AddSetting(GUIST_CStrW, "sound_disabled");
AddSetting(GUIST_CStrW, "sound_enter");
AddSetting(GUIST_CStrW, "sound_leave");
AddSetting(GUIST_CStrW, "sound_opened");
AddSetting(GUIST_CGUISpriteInstance, "sprite"); // Background that sits around the size
AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled");
AddSetting(GUIST_CGUISpriteInstance, "sprite_list"); // Background of the drop down list
AddSetting(GUIST_CGUISpriteInstance, "sprite2"); // Button that sits to the right
AddSetting(GUIST_CGUISpriteInstance, "sprite2_over");
AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed");
AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled");
AddSetting(GUIST_EVAlign, "text_valign");
// Add these in CList! And implement TODO
//AddSetting(GUIST_CGUIColor, "textcolor_over");
//AddSetting(GUIST_CGUIColor, "textcolor_pressed");
AddSetting(GUIST_CGUIColor, "textcolor_selected");
AddSetting(GUIST_CGUIColor, "textcolor_disabled");
// Scrollbar is forced to be true.
GUI::SetSetting(this, "scrollbar", true);
}
CDropDown::~CDropDown()
{
}
void CDropDown::SetupText()
{
SetupListRect();
CList::SetupText();
}
void CDropDown::UpdateCachedSize()
{
CList::UpdateCachedSize();
SetupText();
}
void CDropDown::HandleMessage(SGUIMessage& Message)
{
// Important
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
{
// Update cached list rect
if (Message.value == "size" ||
Message.value == "absolute" ||
Message.value == "dropdown_size" ||
Message.value == "dropdown_buffer" ||
Message.value == "minimum_visible_items" ||
Message.value == "scrollbar_style" ||
Message.value == "button_width")
{
SetupListRect();
}
break;
}
case GUIM_MOUSE_MOTION:
{
if (!m_Open)
break;
CPos mouse = GetMousePos();
if (!GetListRect().PointInside(mouse))
break;
bool scrollbar;
CGUIList* pList;
GUI::GetSetting(this, "scrollbar", scrollbar);
GUI::GetSettingPointer(this, "list", pList);
float scroll = 0.f;
if (scrollbar)
scroll = GetScrollBar(0).GetPos();
CRect rect = GetListRect();
mouse.y += scroll;
int set = -1;
for (int i = 0; i < (int)pList->m_Items.size(); ++i)
{
if (mouse.y >= rect.top + m_ItemsYPositions[i] &&
mouse.y < rect.top + m_ItemsYPositions[i+1] &&
// mouse is not over scroll-bar
(m_HideScrollBar ||
mouse.x < GetScrollBar(0).GetOuterRect().left ||
mouse.x > GetScrollBar(0).GetOuterRect().right))
{
set = i;
}
}
if (set != -1)
{
m_ElementHighlight = set;
//UpdateAutoScroll();
}
break;
}
case GUIM_MOUSE_ENTER:
{
bool enabled;
GUI::GetSetting(this, "enabled", enabled);
if (!enabled)
break;
CStrW soundPath;
if (g_SoundManager && GUI::GetSetting(this, "sound_enter", soundPath) == PSRETURN_OK && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
break;
}
case GUIM_MOUSE_LEAVE:
{
GUI::GetSetting(this, "selected", m_ElementHighlight);
bool enabled;
GUI::GetSetting(this, "enabled", enabled);
if (!enabled)
break;
CStrW soundPath;
if (g_SoundManager && GUI::GetSetting(this, "sound_leave", soundPath) == PSRETURN_OK && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
break;
}
// We can't inherent this routine from CList, because we need to include
// a mouse click to open the dropdown, also the coordinates are changed.
case GUIM_MOUSE_PRESS_LEFT:
{
bool enabled;
GUI::GetSetting(this, "enabled", enabled);
if (!enabled)
{
CStrW soundPath;
if (g_SoundManager && GUI::GetSetting(this, "sound_disabled", soundPath) == PSRETURN_OK && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
break;
}
if (!m_Open)
{
CGUIList* pList;
GUI::GetSettingPointer(this, "list", pList);
if (pList->m_Items.empty())
return;
m_Open = true;
GetScrollBar(0).SetZ(GetBufferedZ());
GUI::GetSetting(this, "selected", m_ElementHighlight);
// Start at the position of the selected item, if possible.
GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60);
CStrW soundPath;
if (g_SoundManager && GUI::GetSetting(this, "sound_opened", soundPath) == PSRETURN_OK && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
return; // overshadow
}
else
{
CPos mouse = GetMousePos();
// If the regular area is pressed, then abort, and close.
if (m_CachedActualSize.PointInside(mouse))
{
m_Open = false;
GetScrollBar(0).SetZ(GetBufferedZ());
CStrW soundPath;
if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
return; // overshadow
}
if (m_HideScrollBar ||
mouse.x < GetScrollBar(0).GetOuterRect().left ||
mouse.x > GetScrollBar(0).GetOuterRect().right ||
mouse.y < GetListRect().top)
{
m_Open = false;
GetScrollBar(0).SetZ(GetBufferedZ());
}
}
break;
}
case GUIM_MOUSE_WHEEL_DOWN:
{
bool enabled;
GUI::GetSetting(this, "enabled", enabled);
// Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar.
if (m_Open || !enabled)
break;
GUI::GetSetting(this, "selected", m_ElementHighlight);
if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1)
break;
++m_ElementHighlight;
GUI::SetSetting(this, "selected", m_ElementHighlight);
break;
}
case GUIM_MOUSE_WHEEL_UP:
{
bool enabled;
GUI::GetSetting(this, "enabled", enabled);
// Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar.
if (m_Open || !enabled)
break;
GUI::GetSetting(this, "selected", m_ElementHighlight);
if (m_ElementHighlight - 1 < 0)
break;
m_ElementHighlight--;
GUI::SetSetting(this, "selected", m_ElementHighlight);
break;
}
case GUIM_LOST_FOCUS:
{
if (m_Open)
{
CStrW soundPath;
if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
}
m_Open = false;
break;
}
case GUIM_LOAD:
SetupListRect();
break;
default:
break;
}
// Important that this is after, so that overshadowed implementations aren't processed
CList::HandleMessage(Message);
// As HandleMessage functions return void, we need to manually verify
// whether the child list's items were modified.
if (CList::GetModified())
SetupText();
}
InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev)
{
InReaction result = IN_PASS;
bool update_highlight = false;
if (ev->ev.type == SDL_KEYDOWN)
{
int szChar = ev->ev.key.keysym.sym;
switch (szChar)
{
case '\r':
m_Open = false;
result = IN_HANDLED;
break;
case SDLK_HOME:
case SDLK_END:
case SDLK_UP:
case SDLK_DOWN:
case SDLK_PAGEUP:
case SDLK_PAGEDOWN:
if (!m_Open)
return IN_PASS;
// Set current selected item to highlighted, before
// then really processing these in CList::ManuallyHandleEvent()
GUI::SetSetting(this, "selected", m_ElementHighlight);
update_highlight = true;
break;
default:
// If we have inputed a character try to get the closest element to it.
// TODO: not too nice and doesn't deal with dashes.
if (m_Open && ((szChar >= SDLK_a && szChar <= SDLK_z) || szChar == SDLK_SPACE
|| (szChar >= SDLK_0 && szChar <= SDLK_9)
|| (szChar >= SDLK_KP_0 && szChar <= SDLK_KP_9)))
{
// arbitrary 1 second limit to add to string or start fresh.
// maximal amount of characters is 100, which imo is far more than enough.
if (timer_Time() - m_TimeOfLastInput > 1.0 || m_InputBuffer.length() >= 100)
m_InputBuffer = szChar;
else
m_InputBuffer += szChar;
m_TimeOfLastInput = timer_Time();
CGUIList* pList;
GUI::GetSettingPointer(this, "list", pList);
// let's look for the closest element
// basically it's alphabetic order and "as many letters as we can get".
int closest = -1;
int bestIndex = -1;
int difference = 1250;
for (int i = 0; i < (int)pList->m_Items.size(); ++i)
{
int indexOfDifference = 0;
int diff = 0;
for (size_t j = 0; j < m_InputBuffer.length(); ++j)
{
diff = std::abs((int)(pList->m_Items[i].GetRawString().LowerCase()[j]) - (int)m_InputBuffer[j]);
if (diff == 0)
indexOfDifference = j+1;
else
break;
}
if (indexOfDifference > bestIndex || (indexOfDifference >= bestIndex && diff < difference))
{
bestIndex = indexOfDifference;
closest = i;
difference = diff;
}
}
// let's select the closest element. There should basically always be one.
if (closest != -1)
{
GUI::SetSetting(this, "selected", closest);
update_highlight = true;
GetScrollBar(0).SetPos(m_ItemsYPositions[closest] - 60);
}
result = IN_HANDLED;
}
break;
}
}
if (CList::ManuallyHandleEvent(ev) == IN_HANDLED)
result = IN_HANDLED;
if (update_highlight)
GUI::GetSetting(this, "selected", m_ElementHighlight);
return result;
}
void CDropDown::SetupListRect()
{
extern int g_yres;
extern float g_GuiScale;
float size, buffer, yres;
yres = g_yres / g_GuiScale;
u32 minimumVisibleItems;
GUI::GetSetting(this, "dropdown_size", size);
GUI::GetSetting(this, "dropdown_buffer", buffer);
GUI::GetSetting(this, "minimum_visible_items", minimumVisibleItems);
if (m_ItemsYPositions.empty())
{
m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer,
m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size);
m_HideScrollBar = false;
}
// Too many items so use a scrollbar
else if (m_ItemsYPositions.back() > size)
{
// Place items below if at least some items can be placed below
if (m_CachedActualSize.bottom + buffer + size <= yres)
m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer,
m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size);
else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) ||
m_CachedActualSize.top < yres - m_CachedActualSize.bottom)
m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer,
m_CachedActualSize.right, yres);
// Not enough space below, thus place items above
else
m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - size),
m_CachedActualSize.right, m_CachedActualSize.top - buffer);
m_HideScrollBar = false;
}
else
{
// Enough space below, no scrollbar needed
if (m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back() <= yres)
{
m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer,
m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back());
m_HideScrollBar = true;
}
// Enough space below for some items, but not all, so place items below and use a scrollbar
else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) ||
m_CachedActualSize.top < yres - m_CachedActualSize.bottom)
{
m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer,
m_CachedActualSize.right, yres);
m_HideScrollBar = false;
}
// Not enough space below, thus place items above. Hide the scrollbar accordingly
else
{
m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - m_ItemsYPositions.back()),
m_CachedActualSize.right, m_CachedActualSize.top - buffer);
m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + buffer;
}
}
}
CRect CDropDown::GetListRect() const
{
return m_CachedListRect;
}
bool CDropDown::MouseOver()
{
if(!GetGUI())
throw PSERROR_GUI_OperationNeedsGUIObject();
if (m_Open)
{
CRect rect(m_CachedActualSize.left, std::min(m_CachedActualSize.top, GetListRect().top),
m_CachedActualSize.right, std::max(m_CachedActualSize.bottom, GetListRect().bottom));
return rect.PointInside(GetMousePos());
}
else
return m_CachedActualSize.PointInside(GetMousePos());
}
void CDropDown::Draw()
{
if (!GetGUI())
return;
float bz = GetBufferedZ();
float dropdown_size, button_width;
GUI::GetSetting(this, "dropdown_size", dropdown_size);
GUI::GetSetting(this, "button_width", button_width);
CGUISpriteInstance* sprite;
CGUISpriteInstance* sprite2;
CGUISpriteInstance* sprite2_second;
int cell_id, selected = 0;
CGUIColor color;
bool enabled;
GUI::GetSetting(this, "enabled", enabled);
GUI::GetSettingPointer(this, "sprite2", sprite2);
GUI::GetSetting(this, "cell_id", cell_id);
GUI::GetSetting(this, "selected", selected);
GUI::GetSetting(this, enabled ? "textcolor_selected" : "textcolor_disabled", color);
GUI::GetSettingPointer(this, enabled ? "sprite" : "sprite_disabled", sprite);
GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize);
if (button_width > 0.f)
{
CRect rect(m_CachedActualSize.right-button_width, m_CachedActualSize.top,
m_CachedActualSize.right, m_CachedActualSize.bottom);
if (!enabled)
{
GUI::GetSettingPointer(this, "sprite2_disabled", sprite2_second);
GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect);
}
else if (m_Open)
{
GUI::GetSettingPointer(this, "sprite2_pressed", sprite2_second);
GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect);
}
else if (m_MouseHovering)
{
GUI::GetSettingPointer(this, "sprite2_over", sprite2_second);
GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect);
}
else
GetGUI()->DrawSprite(*sprite2, cell_id, bz+0.05f, rect);
}
if (selected != -1) // TODO: Maybe check validity completely?
{
CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top,
m_CachedActualSize.right-button_width, m_CachedActualSize.bottom);
CPos pos(m_CachedActualSize.left, m_CachedActualSize.top);
DrawText(selected, color, pos, bz+0.1f, cliparea);
}
bool* scrollbar = NULL;
bool old;
GUI::GetSettingPointer(this, "scrollbar", scrollbar);
old = *scrollbar;
if (m_Open)
{
if (m_HideScrollBar)
*scrollbar = false;
DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor");
if (m_HideScrollBar)
*scrollbar = old;
}
}
// When a dropdown list is opened, it needs to be visible above all the other
// controls on the page. The only way I can think of to do this is to increase
// its z value when opened, so that it's probably on top.
float CDropDown::GetBufferedZ() const
{
float bz = CList::GetBufferedZ();
if (m_Open)
return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value
else
return bz;
}
Index: ps/trunk/source/gui/CDropDown.h
===================================================================
--- ps/trunk/source/gui/CDropDown.h (revision 22586)
+++ ps/trunk/source/gui/CDropDown.h (revision 22587)
@@ -1,130 +1,130 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
/*
GUI Object - Drop Down (list)
--Overview--
Works just like a list-box, but it hides
all the elements that aren't selected. They
can be brought up by pressing the control.
--More info--
Check GUI.h
*/
#ifndef INCLUDED_CDROPDOWN
#define INCLUDED_CDROPDOWN
#include "GUI.h"
#include "CList.h"
/**
* Drop Down
*
* The control can be pressed, but we will not inherent
* this behavior from IGUIButtonBehavior, because when
* you press this control, the list with elements will
* immediately appear, and not first after release
* (which is the whole gist of the IGUIButtonBehavior).
*/
class CDropDown : public CList
{
GUI_OBJECT(CDropDown)
public:
- CDropDown();
+ CDropDown(CGUI* pGUI);
virtual ~CDropDown();
// virtual void ResetStates() { IGUIButtonBehavior::ResetStates(); }
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Handle events manually to catch keyboard inputting.
*/
virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
/**
* Draws the Button
*/
virtual void Draw();
// This is one of the few classes we actually need to redefine this function
// this is because the size of the control changes whether it is open
// or closed.
virtual bool MouseOver();
virtual float GetBufferedZ() const;
protected:
/**
* If the size changed, the texts have to be updated as
* the word wrapping depends on the size.
*/
virtual void UpdateCachedSize();
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
// Sets up the cached GetListRect. Decided whether it should
// have a scrollbar, and so on.
virtual void SetupListRect();
// Specify a new List rectangle.
virtual CRect GetListRect() const;
/**
* Placement of text.
*/
CPos m_TextPos;
// Is the dropdown opened?
bool m_Open;
// I didn't cache this at first, but it's just as easy as caching
// m_CachedActualSize, so I thought, what the heck it's used a lot.
CRect m_CachedListRect;
// Hide scrollbar when it's not needed
bool m_HideScrollBar;
// Not necessarily the element that is selected, this is just
// which element should be highlighted. When opening the dropdown
// it is set to "selected", but then when moving the mouse it will
// change.
int m_ElementHighlight;
// Stores any text entered by the user for quick access to an element
// (ie if you type "acro" it will take you to acropolis).
std::string m_InputBuffer;
// used to know if we want to restart anew or add to m_inputbuffer.
double m_TimeOfLastInput;
};
#endif // INCLUDED_CDROPDOWN
Index: ps/trunk/source/gui/CGUI.cpp
===================================================================
--- ps/trunk/source/gui/CGUI.cpp (revision 22586)
+++ ps/trunk/source/gui/CGUI.cpp (revision 22587)
@@ -1,1771 +1,1764 @@
/* Copyright (C) 2019 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 "CChart.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 "CSlider.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/GameSetup/Config.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 || ev->ev.type == SDL_HOTKEYUP)
{
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)
{
// Update hotkey status before sending the event,
// else the status will be outdated when processing the GUI event.
HotkeyInputHandler(ev);
ret = IN_HANDLED;
if (ev->ev.type == SDL_HOTKEYDOWN)
obj->SendEvent(GUIM_PRESSED, "press");
else
obj->SendEvent(GUIM_RELEASED, "release");
}
}
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;
default:
break;
}
}
else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest)
{
if (ev->ev.wheel.y < 0)
ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown");
else if (ev->ev.wheel.y > 0)
ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup");
}
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();
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();
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 ||
ev->ev.type == SDL_TEXTINPUT ||
ev->ev.type == SDL_TEXTEDITING)
{
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);
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());
}
void CGUI::SendEventToAll(const CStr& EventName, JS::HandleValueArray paramData)
{
GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase(), paramData);
}
CGUI::CGUI(const shared_ptr& runtime)
: m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0)
{
m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", runtime));
m_ScriptInterface->SetCallbackData(this);
GuiScriptingInit(*m_ScriptInterface);
m_ScriptInterface->LoadGlobalScripts();
- m_BaseObject = new CGUIDummyObject;
- m_BaseObject->SetGUI(this);
+
+ m_BaseObject = new CGUIDummyObject(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;
- }
+ return (*m_ObjectTypes[str])(this);
+
+ // Error reporting will be handled with the nullptr return.
+ return nullptr;
}
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);
AddObjectType("chart", &CChart::ConstructObject);
AddObjectType("slider", &CSlider::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);
-
m_BaseObject->AddChild(pObject);
// Cache tree
GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize);
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;
}
// 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);
}
}
const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const
{
std::map::const_iterator it = m_ScrollBarStyles.find(style);
if (it == m_ScrollBarStyles.end())
return nullptr;
return &it->second;
}
// 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;
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 = EAlign_Left;
if (pObject->SettingExists("text_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(std::move(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)
{
// The calculated width of each word includes the space between the current
// word and the next. When we're wrapping, we need subtract the width of the
// space after the last word on the line before the wrap.
CFontMetrics currentFont(Font);
line_width -= currentFont.GetCharacterWidth(*L" ");
break;
}
// Let line_height be the maximum m_Height we encounter.
line_height = std::max(line_height, Feedback2.m_Size.cy);
// If the current word is an explicit new line ("\n"),
// break now before adding the width of this character.
// ("\n" doesn't have a glyph, thus is given the same width as
// the "missing glyph" character by CFont::GetCharacterWidth().)
if (WordWrapping && Feedback2.m_NewLine)
break;
line_width += Feedback2.m_Size.cx;
}
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;
// 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(),
std::make_move_iterator(Feedback2.m_SpriteCalls.begin()),
std::make_move_iterator(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(),
std::make_move_iterator(Feedback2.m_TextCalls.begin()),
std::make_move_iterator(Feedback2.m_TextCalls.end()));
Text.m_SpriteCalls.insert(
Text.m_SpriteCalls.end(),
std::make_move_iterator(Feedback2.m_SpriteCalls.begin()),
std::make_move_iterator(Feedback2.m_SpriteCalls.end()));
if (j == (int)string.m_Words.size()-2)
done = true;
}
// Reset X
x = BufferZone;
// Update dimensions
Text.m_Size.cx = std::max(Text.m_Size.cx, line_width + BufferZone * 2);
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 CGUIColor& 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;
CGUIColor 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, CGUIColor& 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)
return;
XMBElement node = XeroFile.GetRoot();
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