Index: binaries/data/mods/mod/gui/common/modern/styles.xml
===================================================================
--- binaries/data/mods/mod/gui/common/modern/styles.xml
+++ binaries/data/mods/mod/gui/common/modern/styles.xml
@@ -178,4 +178,13 @@
sprite="ModernTabVerticalBackground"
sound_pressed="audio/interface/ui/ui_button_click.ogg"
/>
+
Index: binaries/data/mods/mod/gui/gui.rnc
===================================================================
--- binaries/data/mods/mod/gui/gui.rnc
+++ binaries/data/mods/mod/gui/gui.rnc
@@ -58,6 +58,8 @@
attribute minimum_visible_items { xsd:nonNegativeInteger }?&
attribute enabled { bool }?&
attribute font { text }?&
+ attribute format_x { text }?&
+ attribute format_y { text }?&
attribute fov_wedge_color { ccolor }?&
attribute heading_height { text }?&
attribute hotkey { text }?&
@@ -161,7 +163,7 @@
}
\include =
element include {
- attribute file { text }|
+ attribute file { text }|
attribute directory { text }
}
item =
@@ -236,6 +238,8 @@
attribute name { text }&
attribute sprite { text }?&
attribute anchor { valign }?&
+ attribute axis_color { ccolor }?&
+ attribute axis_width { xsd:decimal { minInclusive = "0" } }?&
attribute buffer_zone { xsd:decimal }?&
attribute font { text }?&
attribute maxwidth { xsd:decimal }?&
Index: binaries/data/mods/mod/gui/gui.rng
===================================================================
--- binaries/data/mods/mod/gui/gui.rng
+++ binaries/data/mods/mod/gui/gui.rng
@@ -180,6 +180,18 @@
+
+
+
+
+
+
+
+ 0
+
+
+
+
@@ -228,6 +240,12 @@
+
+
+
+
+
+
Index: binaries/data/mods/public/gui/summary/layout.js
===================================================================
--- binaries/data/mods/public/gui/summary/layout.js
+++ binaries/data/mods/public/gui/summary/layout.js
@@ -182,7 +182,7 @@
"caption": translate("Miscellaneous"),
"headings": [
{ "identifier": "playername", "caption": translate("Player name"), "yStart": 26, "width": 200 },
- { "identifier": "killDeath", "caption": translate("Kill / Death ratio"), "yStart": 16, "width": 100 },
+ { "identifier": "killDeath", "caption": translate("Kill / Death ratio"), "yStart": 16, "width": 100, "format": "DECIMAL2" },
{ "identifier": "mapControlPeak", "caption": translate("Map control (peak)"), "yStart": 16, "width": 100 },
{ "identifier": "mapControl", "caption": translate("Map control (finish)"), "yStart": 16, "width": 100 },
{ "identifier": "mapExploration", "caption": translate("Map exploration"), "yStart": 16, "width": 100 },
Index: binaries/data/mods/public/gui/summary/summary.js
===================================================================
--- binaries/data/mods/public/gui/summary/summary.js
+++ binaries/data/mods/public/gui/summary/summary.js
@@ -234,8 +234,8 @@
let chartLegend = Engine.GetGUIObjectByName("chartLegend");
chartLegend.caption = g_GameData.sim.playerStates.slice(1).map(
- (state, index) => coloredText("■", player_colors[index]) + state.name
- ).join(" ");
+ (state, index) => coloredText("■", player_colors[index]) + " " + state.name
+ ).join(" ");
let chart1Part = Engine.GetGUIObjectByName("chart[1]Part");
let chart1PartSize = chart1Part.size;
@@ -331,6 +331,12 @@
if (!g_ScorePanelsData[category].counters[itemNumber].fn)
return;
let chart = Engine.GetGUIObjectByName("chart[" + number + "]");
+ let format = g_ScorePanelsData[category].headings[itemNumber + 1].format;
+ if (!format)
+ format = 'INTEGER';
+ chart.format_y = format;
+ let xAxisLabel = Engine.GetGUIObjectByName("chart[" + number + "]XAxisLabel");
+ xAxisLabel.caption = translate('Time elapsed');
let series = [];
for (let j = 1; j <= g_PlayerCount; ++j)
{
Index: binaries/data/mods/public/gui/summary/summary.xml
===================================================================
--- binaries/data/mods/public/gui/summary/summary.xml
+++ binaries/data/mods/public/gui/summary/summary.xml
@@ -201,7 +201,8 @@
-
+
+
Index: source/gui/CChart.h
===================================================================
--- source/gui/CChart.h
+++ source/gui/CChart.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#define INCLUDED_CCHART
#include "GUI.h"
+#include "IGUITextOwner.h"
#include "graphics/Color.h"
#include "maths/Vector2D.h"
#include
@@ -35,7 +36,7 @@
*
* @see IGUIObject
*/
-class CChart : public IGUIObject
+class CChart : public IGUITextOwner
{
GUI_OBJECT(CChart)
@@ -58,13 +59,36 @@
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 function
+ * Helper functions
*/
void DrawLine(const CShaderProgramPtr& shader, const CColor& 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 CColor& 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: source/gui/CChart.cpp
===================================================================
--- source/gui/CChart.cpp
+++ source/gui/CChart.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,16 +19,29 @@
#include "CChart.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()
{
+ AddSetting(GUIST_CColor, "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()
@@ -42,11 +55,14 @@
{
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 CColor& color, const std::vector& vertices) const
@@ -63,6 +79,35 @@
glDisable(GL_LINE_SMOOTH);
}
+void CChart::DrawTriangleStrip(const CShaderProgramPtr& shader, const CColor& 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
+ CColor 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");
@@ -90,29 +135,7 @@
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, transform);
- CVector2D leftBottom, rightTop;
- leftBottom = 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 < leftBottom.X)
- leftBottom.X = point.X;
- if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y < leftBottom.Y)
- leftBottom.Y = point.Y;
-
- if (fabs(point.X) != std::numeric_limits::infinity() && point.X > rightTop.X)
- rightTop.X = point.X;
- if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y > rightTop.Y)
- rightTop.Y = point.Y;
- }
-
- if (rightTop.Y == leftBottom.Y)
- rightTop.Y += 1;
- if (rightTop.X == leftBottom.X)
- rightTop.X += 1;
-
- CVector2D scale(width / (rightTop.X - leftBottom.X), height / (rightTop.Y - leftBottom.Y));
-
+ 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())
@@ -123,8 +146,8 @@
{
if (fabs(point.X) != std::numeric_limits::infinity() && fabs(point.Y) != std::numeric_limits::infinity())
{
- vertices.push_back(rect.left + (point.X - leftBottom.X) * scale.X);
- vertices.push_back(rect.bottom - (point.Y - leftBottom.Y) * scale.Y);
+ 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
@@ -137,15 +160,24 @@
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, CColor(1.f, 1.f, 1.f, 1.f), m_TextPositions[i], bz + 0.5f);
}
CRect CChart::GetChartRect() const
{
- return m_CachedActualSize;
+ return CRect(
+ m_CachedActualSize.TopLeft() + CPos(m_AxisWidth, m_AxisWidth),
+ m_CachedActualSize.BottomRight() - CPos(m_AxisWidth, m_AxisWidth)
+ );
}
void CChart::UpdateSeries()
@@ -167,4 +199,125 @@
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", static_cast(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
+ {
+ 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;
}