Index: ps/trunk/source/graphics/Color.cpp
===================================================================
--- ps/trunk/source/graphics/Color.cpp (revision 22693)
+++ ps/trunk/source/graphics/Color.cpp (revision 22694)
@@ -1,147 +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 "graphics/Color.h"
#include "graphics/SColor.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#if HAVE_SSE
# include
# include "lib/sysdep/arch/x86_x64/x86_x64.h"
#endif
static SColor4ub fallback_ConvertRGBColorTo4ub(const RGBColor& src)
{
SColor4ub result;
result.R = clamp(static_cast(src.X * 255), 0, 255);
result.G = clamp(static_cast(src.Y * 255), 0, 255);
result.B = clamp(static_cast(src.Z * 255), 0, 255);
result.A = 255;
return result;
}
// on IA32, this is replaced by an SSE assembly version in ia32.cpp
SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src) = fallback_ConvertRGBColorTo4ub;
// Assembler-optimized function for color conversion
#if HAVE_SSE
static SColor4ub sse_ConvertRGBColorTo4ub(const RGBColor& src)
{
const __m128 zero = _mm_setzero_ps();
const __m128 _255 = _mm_set_ss(255.0f);
__m128 r = _mm_load_ss(&src.X);
__m128 g = _mm_load_ss(&src.Y);
__m128 b = _mm_load_ss(&src.Z);
// C = min(255, 255*max(C, 0)) ( == clamp(255*C, 0, 255) )
r = _mm_max_ss(r, zero);
g = _mm_max_ss(g, zero);
b = _mm_max_ss(b, zero);
r = _mm_mul_ss(r, _255);
g = _mm_mul_ss(g, _255);
b = _mm_mul_ss(b, _255);
r = _mm_min_ss(r, _255);
g = _mm_min_ss(g, _255);
b = _mm_min_ss(b, _255);
// convert to integer and combine channels using bit logic
int ri = _mm_cvtss_si32(r);
int gi = _mm_cvtss_si32(g);
int bi = _mm_cvtss_si32(b);
return SColor4ub(ri, gi, bi, 0xFF);
}
#endif
void ColorActivateFastImpl()
{
if(0)
{
}
#if HAVE_SSE
else if (x86_x64::Cap(x86_x64::CAP_SSE))
{
ConvertRGBColorTo4ub = sse_ConvertRGBColorTo4ub;
}
#endif
else
{
debug_printf("No SSE available. Slow fallback routines will be used.\n");
}
}
+/**
+ * Important: This function does not modify the value if parsing fails.
+ */
bool CColor::ParseString(const CStr8& value, int defaultAlpha)
{
const size_t NUM_VALS = 4;
int values[NUM_VALS] = { 0, 0, 0, defaultAlpha };
std::stringstream stream;
stream.str(value);
// Parse each value
size_t i;
for (i = 0; i < NUM_VALS; ++i)
{
if (stream.eof())
break;
stream >> values[i];
if ((stream.rdstate() & std::stringstream::failbit) != 0)
{
LOGWARNING("Unable to parse CColor parameters. Your input: '%s'", value.c_str());
return false;
}
if (values[i] < 0 || values[i] > 255)
{
LOGWARNING("Invalid value (<0 or >255) when parsing CColor parameters. Your input: '%s'", value.c_str());
return false;
}
}
if (i < 3)
{
LOGWARNING("Not enough parameters when parsing as CColor. Your input: '%s'", value.c_str());
return false;
}
if (!stream.eof())
{
LOGWARNING("Too many parameters when parsing as CColor. Your input: '%s'", value.c_str());
return false;
}
r = values[0] / 255.f;
g = values[1] / 255.f;
b = values[2] / 255.f;
a = values[3] / 255.f;
return true;
}
bool CColor::operator==(const CColor& color) const
{
return
r == color.r &&
g == color.g &&
b == color.b &&
a == color.a;
}
Index: ps/trunk/source/graphics/Color.h
===================================================================
--- ps/trunk/source/graphics/Color.h (revision 22693)
+++ ps/trunk/source/graphics/Color.h (revision 22694)
@@ -1,84 +1,85 @@
/* 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_COLOR
#define INCLUDED_COLOR
#include "graphics/SColor.h"
#include "maths/Vector3D.h"
#include "maths/Vector4D.h"
// Simple defines for 3 and 4 component floating point colors - just map to
// corresponding vector types.
typedef CVector3D RGBColor;
typedef CVector4D RGBAColor;
// Convert float RGB(A) colors to unsigned byte.
// Exposed as function pointer because it is set at init-time to
// one of several implementations depending on CPU caps.
extern SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src);
// call once ia32_Init has run; detects CPU caps and activates the best
// possible codepath.
extern void ColorActivateFastImpl();
class CStr8;
struct CColor
{
CColor() : r(-1.f), g(-1.f), b(-1.f), a(1.f) {}
CColor(float cr, float cg, float cb, float ca) : r(cr), g(cg), b(cb), a(ca) {}
/**
* Returns whether this has been set to a valid color.
*/
operator bool() const
{
return r >= 0 && g >= 0 && b >= 0 && a >= 0;
}
/**
* Try to parse @p Value as a color. Returns true on success, false otherwise.
+ * Leaves the color unchanged if it failed.
* @param value Should be "r g b" or "r g b a" where each value is an integer in [0,255].
* @param defaultAlpha The alpha value that is used if the format of @p Value is "r g b".
*/
bool ParseString(const CStr8& value, int defaultAlpha = 255);
bool operator==(const CColor& color) const;
bool operator!=(const CColor& color) const
{
return !(*this == color);
}
// For passing to glColor[34]fv:
const float* FloatArray() const { return &r; }
// For passing to CRenderer:
SColor4ub AsSColor4ub() const
{
return SColor4ub(
static_cast(r * 255.f),
static_cast(g * 255.f),
static_cast(b * 255.f),
static_cast(a * 255.f)
);
}
float r, g, b, a;
};
#endif // INCLUDED_COLOR
Index: ps/trunk/source/gui/CButton.cpp
===================================================================
--- ps/trunk/source/gui/CButton.cpp (revision 22693)
+++ ps/trunk/source/gui/CButton.cpp (revision 22694)
@@ -1,109 +1,108 @@
/* 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(CGUI* pGUI)
: IGUIObject(pGUI), IGUIButtonBehavior(pGUI), IGUITextOwner(pGUI)
{
AddSetting("buffer_zone");
AddSetting("caption");
AddSetting("cell_id");
AddSetting("font");
AddSetting("sound_disabled");
AddSetting("sound_enter");
AddSetting("sound_leave");
AddSetting("sound_pressed");
AddSetting("sound_released");
AddSetting("sprite");
AddSetting("sprite_over");
AddSetting("sprite_pressed");
AddSetting("sprite_disabled");
AddSetting("text_align");
AddSetting("text_valign");
AddSetting("textcolor");
AddSetting("textcolor_over");
AddSetting("textcolor_pressed");
AddSetting("textcolor_disabled");
AddSetting("tooltip");
AddSetting("tooltip_style");
AddText();
}
CButton::~CButton()
{
}
void CButton::SetupText()
{
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";
const CGUIString& caption = GUI::GetSetting(this, "caption");
float buffer_zone = 0.f;
GUI::GetSetting(this, "buffer_zone", buffer_zone);
m_GeneratedTexts[0] = CGUIText(m_pGUI, 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();
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");
CGUISpriteInstance& sprite = GUI::GetSetting(this, strSprite);
CGUISpriteInstance& sprite_over = GUI::GetSetting(this, strSpriteOver);
CGUISpriteInstance& sprite_pressed = GUI::GetSetting(this, strSpritePressed);
CGUISpriteInstance& sprite_disabled = GUI::GetSetting(this, strSpriteDisabled);
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);
+ DrawText(0, ChooseColor(), m_TextPos, bz + 0.1f);
}
Index: ps/trunk/source/gui/CChart.cpp
===================================================================
--- ps/trunk/source/gui/CChart.cpp (revision 22693)
+++ ps/trunk/source/gui/CChart.cpp (revision 22694)
@@ -1,321 +1,319 @@
/* 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 "gui/CGUIString.h"
#include "gui/GUIMatrix.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(CGUI* pGUI)
: IGUIObject(pGUI), IGUITextOwner(pGUI)
{
AddSetting("axis_color");
AddSetting("axis_width");
AddSetting("buffer_zone");
AddSetting("font");
AddSetting("format_x");
AddSetting("format_y");
AddSetting("series_color");
AddSetting("series");
AddSetting("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);
+ DrawTriangleStrip(shader, GUI::GetSetting(this, "axis_color"), vertices);
}
void CChart::Draw()
{
PROFILE3("render chart");
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()
{
const CGUISeries& pSeries = GUI::GetSetting(this, "series");
const CGUIList& pSeriesColor = GUI::GetSetting(this, "series_color");
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() && !data.m_Color.ParseString(m_pGUI, pSeriesColor.m_Items[i].GetOriginalString().ToUTF8(), 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()
{
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();
}
return AddText(gui_str, font, 0, buffer_zone, this).GetSize();
}
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/CDropDown.cpp
===================================================================
--- ps/trunk/source/gui/CDropDown.cpp (revision 22693)
+++ ps/trunk/source/gui/CDropDown.cpp (revision 22694)
@@ -1,552 +1,552 @@
/* 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(CGUI* pGUI)
: CList(pGUI), IGUIObject(pGUI),
m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1)
{
AddSetting("button_width");
AddSetting("dropdown_size");
AddSetting("dropdown_buffer");
AddSetting("minimum_visible_items");
// AddSetting("sound_closed");
AddSetting("sound_disabled");
AddSetting("sound_enter");
AddSetting("sound_leave");
AddSetting("sound_opened");
AddSetting("sprite"); // Background that sits around the size
AddSetting("sprite_disabled");
AddSetting("sprite_list"); // Background of the drop down list
AddSetting("sprite2"); // Button that sits to the right
AddSetting("sprite2_over");
AddSetting("sprite2_pressed");
AddSetting("sprite2_disabled");
AddSetting("text_valign");
// Add these in CList! And implement TODO
//AddSetting("textcolor_over");
//AddSetting("textcolor_pressed");
AddSetting("textcolor_selected");
AddSetting("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 = m_pGUI->GetMousePos();
if (!GetListRect().PointInside(mouse))
break;
bool scrollbar;
const CGUIList& pList = GUI::GetSetting(this, "list");
GUI::GetSetting(this, "scrollbar", scrollbar);
float scroll = 0.f;
if (scrollbar)
scroll = GetScrollBar(0).GetPos();
CRect rect = GetListRect();
mouse.y += scroll;
int set = -1;
for (int i = 0; i < static_cast(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)
{
const CGUIList& pList = GUI::GetSetting(this, "list");
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
{
const CPos& mouse = m_pGUI->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();
const CGUIList& pList = GUI::GetSetting(this, "list");
// 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 < static_cast(pList.m_Items.size()); ++i)
{
int indexOfDifference = 0;
int diff = 0;
for (size_t j = 0; j < m_InputBuffer.length(); ++j)
{
diff = std::abs(static_cast(pList.m_Items[i].GetRawString().LowerCase()[j]) - static_cast(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 (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(m_pGUI->GetMousePos());
}
else
return m_CachedActualSize.PointInside(m_pGUI->GetMousePos());
}
void CDropDown::Draw()
{
float bz = GetBufferedZ();
float dropdown_size, button_width;
GUI::GetSetting(this, "dropdown_size", dropdown_size);
GUI::GetSetting(this, "button_width", button_width);
int cell_id, selected = 0;
- CGUIColor color;
bool enabled;
GUI::GetSetting(this, "enabled", enabled);
CGUISpriteInstance& sprite = GUI::GetSetting(this, enabled ? "sprite" : "sprite_disabled");
CGUISpriteInstance& sprite2 = GUI::GetSetting(this, "sprite2");
GUI::GetSetting(this, "cell_id", cell_id);
GUI::GetSetting(this, "selected", selected);
- GUI::GetSetting(this, enabled ? "textcolor_selected" : "textcolor_disabled", color);
m_pGUI->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)
{
CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_disabled");
m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect);
}
else if (m_Open)
{
CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_pressed");
m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect);
}
else if (m_MouseHovering)
{
CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_over");
m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect);
}
else
m_pGUI->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);
+ const CGUIColor& color = GUI::GetSetting(this, enabled ? "textcolor_selected" : "textcolor_disabled");
+
CPos pos(m_CachedActualSize.left, m_CachedActualSize.top);
DrawText(selected, color, pos, bz+0.1f, cliparea);
}
// Disable scrollbar during drawing without sending a setting-changed message
bool& scrollbar = GUI::GetSetting(this, "scrollbar");
bool 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/CGUI.cpp
===================================================================
--- ps/trunk/source/gui/CGUI.cpp (revision 22693)
+++ ps/trunk/source/gui/CGUI.cpp (revision 22694)
@@ -1,1373 +1,1356 @@
/* 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 "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(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])(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)
return;
// TODO: Clipping?
Sprite.Draw(this, 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
{
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;
}
-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