Index: ps/trunk/binaries/data/mods/public/gui/summary/layout.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/summary/layout.js (revision 22281)
+++ ps/trunk/binaries/data/mods/public/gui/summary/layout.js (revision 22282)
@@ -1,384 +1,384 @@
var getScorePanelsData = () => ({
"score": {
"caption": translate("Score"),
"headings": [
{ "identifier": "playername", "caption": translate("Player name"), "yStart": 26, "width": 200 },
{ "identifier": "totalScore", "caption": translate("Total score"), "yStart": 16, "width": 100 },
{ "identifier": "economyScore", "caption": translate("Economy score"), "yStart": 16, "width": 100 },
{ "identifier": "militaryScore", "caption": translate("Military score"), "yStart": 16, "width": 100 },
{ "identifier": "explorationScore", "caption": translate("Exploration score"), "yStart": 16, "width": 100 }
],
"titleHeadings": [],
"counters": [
{ "width": 100, "fn": calculateScoreTotal, "verticalOffset": 12 },
{ "width": 100, "fn": calculateEconomyScore, "verticalOffset": 12 },
{ "width": 100, "fn": calculateMilitaryScore, "verticalOffset": 12 },
{ "width": 100, "fn": calculateExplorationScore, "verticalOffset": 12 }
],
"teamCounterFn": calculateScoreTeam
},
"buildings": {
"caption": translate("Buildings"),
"headings": [
{ "identifier": "playername", "caption": translate("Player name"), "yStart": 26, "width": 200 },
{ "identifier": "total", "caption": translate("Total"), "yStart": 34, "width": 105 },
{ "identifier": "House", "caption": translate("Houses"), "yStart": 34, "width": 85 },
{ "identifier": "Economic", "caption": translate("Economic"), "yStart": 34, "width": 85 },
{ "identifier": "Outpost", "caption": translate("Outposts"), "yStart": 34, "width": 85 },
{ "identifier": "Military", "caption": translate("Military"), "yStart": 34, "width": 85 },
{ "identifier": "Fortress", "caption": translate("Fortresses"), "yStart": 34, "width": 85 },
{ "identifier": "CivCentre", "caption": translate("Civ centers"), "yStart": 34, "width": 85 },
{ "identifier": "Wonder", "caption": translate("Wonders"), "yStart": 34, "width": 85 }
],
"titleHeadings": [
{
"caption": sprintf(translate("Buildings Statistics (%(constructed)s / %(destroyed)s / %(captured)s / %(lost)s)"),
{
"constructed": getColoredTypeTranslation("constructed"),
"destroyed": getColoredTypeTranslation("destroyed"),
"captured": getColoredTypeTranslation("captured"),
"lost": getColoredTypeTranslation("lost")
}),
"yStart": 16,
"width": 85 * 7 + 105
}, // width = 700
],
"counters": [
{ "width": 105, "fn": calculateBuildings, "verticalOffset": 3 },
{ "width": 85, "fn": calculateBuildings, "verticalOffset": 3 },
{ "width": 85, "fn": calculateBuildings, "verticalOffset": 3 },
{ "width": 85, "fn": calculateBuildings, "verticalOffset": 3 },
{ "width": 85, "fn": calculateBuildings, "verticalOffset": 3 },
{ "width": 85, "fn": calculateBuildings, "verticalOffset": 3 },
{ "width": 85, "fn": calculateBuildings, "verticalOffset": 3 },
{ "width": 85, "fn": calculateBuildings, "verticalOffset": 3 }
],
"teamCounterFn": calculateBuildingsTeam
},
"units": {
"caption": translate("Units"),
"headings": [
{ "identifier": "playername", "caption": translate("Player name"), "yStart": 26, "width": 200 },
{ "identifier": "total", "caption": translate("Total"), "yStart": 34, "width": 105 },
{ "identifier": "Infantry", "caption": translate("Infantry"), "yStart": 34, "width": 85 },
{ "identifier": "Worker", "caption": translate("Worker"), "yStart": 34, "width": 85 },
{ "identifier": "Cavalry", "caption": translate("Cavalry"), "yStart": 34, "width": 85 },
{ "identifier": "Champion", "caption": translate("Champion"), "yStart": 34, "width": 85 },
{ "identifier": "Hero", "caption": translate("Heroes"), "yStart": 34, "width": 85 },
{ "identifier": "Siege", "caption": translate("Siege"), "yStart": 34, "width": 85 },
{ "identifier": "Ship", "caption": translate("Navy"), "yStart": 34, "width": 85 },
{ "identifier": "Trader", "caption": translate("Traders"), "yStart": 34, "width": 85 }
],
"titleHeadings": [
{
"caption": sprintf(translate("Units Statistics (%(trained)s / %(killed)s / %(captured)s / %(lost)s)"),
{
"trained": getColoredTypeTranslation("trained"),
"killed": getColoredTypeTranslation("killed"),
"captured": getColoredTypeTranslation("captured"),
"lost": getColoredTypeTranslation("lost")
}),
"yStart": 16,
"width": 85 * 8 + 105
}, // width = 785
],
"counters": [
{ "width": 105, "fn": calculateUnitsWithCaptured, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnits, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnits, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnits, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnits, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnits, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnitsWithCaptured, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnits, "verticalOffset": 3 },
{ "width": 85, "fn": calculateUnits, "verticalOffset": 3 }
],
"teamCounterFn": calculateUnitsTeam
},
"resources": {
"caption": translate("Resources"),
"headings": [
{ "identifier": "playername", "caption": translate("Player name"), "yStart": 26, "width": 200 },
{ "identifier": "total", "caption": translate("Total"), "yStart": 34, "width": 110 },
...g_ResourceData.GetResources().map(res => ({
"identifier": res.code,
"caption": resourceNameFirstWord(res.code),
"yStart": 34,
"width": 100
})),
{
"identifier": "tributes",
"caption": translate("Tributes"),
"headerCaption": sprintf(translate("Tributes \n(%(sent)s / %(received)s)"),
{
"sent": getColoredTypeTranslation("sent"),
"received": getColoredTypeTranslation("received")
}),
"yStart": 16,
"width": 121
},
{ "identifier": "treasuresCollected", "caption": translate("Treasures collected"), "yStart": 16, "width": 85 },
{ "identifier": "loot", "caption": translate("Loot"), "yStart": 16, "width": 85 },
{ "identifier": "livestock", "caption": translate("Livestock bred"), "yStart": 16, "width": 85 }
],
"titleHeadings": [
{
"caption": sprintf(translate("Resource Statistics (%(gathered)s / %(used)s)"),
{
"gathered": getColoredTypeTranslation("gathered"),
"used": getColoredTypeTranslation("used")
}),
"yStart": 16,
"width": 100 * g_ResourceData.GetCodes().length + 110
},
],
"counters": [
{ "width": 110, "fn": calculateTotalResources, "verticalOffset": 12 },
...g_ResourceData.GetCodes().map(code => ({
"fn": calculateResources,
"verticalOffset": 12,
"width": 100
})),
{ "width": 121, "fn": calculateTributeSent, "verticalOffset": 12 },
{ "width": 85, "fn": calculateTreasureCollected, "verticalOffset": 12 },
{ "width": 85, "fn": calculateLootCollected, "verticalOffset": 12 },
{ "width": 85, "fn": calculateLivestockTrained, "verticalOffset": 12 }
],
"teamCounterFn": calculateResourcesTeam
},
"market": {
"caption": translate("Market"),
"headings": [
{ "identifier": "playername", "caption": translate("Player name"), "yStart": 26, "width": 200 },
{ "identifier": "tradeIncome", "caption": translate("Trade income"), "yStart": 16, "width": 100 },
- { "identifier": "barterEfficency", "caption": translate("Barter efficiency"), "yStart": 16, "width": 100 },
+ { "identifier": "barterEfficency", "caption": translate("Barter efficiency"), "yStart": 16, "width": 100, "format": "PERCENTAGE" },
...g_ResourceData.GetResources().map(res => {
return {
"identifier": res.code,
"caption":
// Translation: use %(resourceWithinSentence)s if needed
sprintf(translate("%(resourceFirstWord)s exchanged"), {
"resourceFirstWord": resourceNameFirstWord(res.code),
"resourceWithinSentence": resourceNameWithinSentence(res.code)
}),
"yStart": 16,
"width": 100
};
})
],
"titleHeadings": [],
"counters": [
{ "width": 100, "fn": calculateTradeIncome, "verticalOffset": 12 },
{ "width": 100, "fn": calculateBarterEfficiency, "verticalOffset": 12 },
...g_ResourceData.GetCodes().map(code => ({
"width": 100,
"fn": calculateResourceExchanged,
"verticalOffset": 12
}))
],
"teamCounterFn": calculateMarketTeam
},
"misc": {
"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, "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 },
- { "identifier": "vegetarianRatio", "caption": translate("Vegetarian ratio"), "yStart": 16, "width": 100 },
- { "identifier": "feminization", "caption": translate("Feminization"), "yStart": 16, "width": 100 },
+ { "identifier": "mapControlPeak", "caption": translate("Map control (peak)"), "yStart": 16, "width": 100, "format": "PERCENTAGE" },
+ { "identifier": "mapControl", "caption": translate("Map control (finish)"), "yStart": 16, "width": 100, "format": "PERCENTAGE" },
+ { "identifier": "mapExploration", "caption": translate("Map exploration"), "yStart": 16, "width": 100, "format": "PERCENTAGE" },
+ { "identifier": "vegetarianRatio", "caption": translate("Vegetarian ratio"), "yStart": 16, "width": 100, "format": "PERCENTAGE" },
+ { "identifier": "feminization", "caption": translate("Feminization"), "yStart": 16, "width": 100, "format": "PERCENTAGE" },
{
"identifier": "bribes",
"caption": translate("Bribes"),
"headerCaption": sprintf(translate("Bribes\n(%(succeeded)s / %(failed)s)"),
{
"succeeded": getColoredTypeTranslation("succeeded"),
"failed": getColoredTypeTranslation("failed")
}),
"yStart": 16,
"width": 139
}
],
"titleHeadings": [],
"counters": [
{ "width": 100, "fn": calculateKillDeathRatio, "verticalOffset": 12 },
{ "width": 100, "fn": calculateMapPeakControl, "verticalOffset": 12 },
{ "width": 100, "fn": calculateMapFinalControl, "verticalOffset": 12 },
{ "width": 100, "fn": calculateMapExploration, "verticalOffset": 12 },
{ "width": 100, "fn": calculateVegetarianRatio, "verticalOffset": 12 },
{ "width": 100, "fn": calculateFeminization, "verticalOffset": 12 },
{ "width": 139, "fn": calculateBribes, "verticalOffset": 12 }
],
"teamCounterFn": calculateMiscellaneousTeam
}
});
function getColoredTypeTranslation(type)
{
return g_SummaryTypes[type].color ?
coloredText(g_SummaryTypes[type].caption, g_SummaryTypes[type].color) :
g_SummaryTypes[type].caption;
}
function resetGeneralPanel()
{
for (let h = 0; h < g_MaxHeadingTitle; ++h)
{
Engine.GetGUIObjectByName("titleHeading[" + h + "]").hidden = true;
Engine.GetGUIObjectByName("Heading[" + h + "]").hidden = true;
for (let p = 0; p < g_MaxPlayers; ++p)
{
Engine.GetGUIObjectByName("valueData[" + p + "][" + h + "]").hidden = true;
for (let t = 0; t < g_MaxTeams; ++t)
{
Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + p + "][" + h + "]").hidden = true;
Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + h + "]").hidden = true;
}
}
}
}
function updateGeneralPanelHeadings(headings)
{
let left = 50;
for (let h in headings)
{
let headerGUIName = "playerNameHeading";
if (h > 0)
headerGUIName = "Heading[" + (h - 1) + "]";
let headerGUI = Engine.GetGUIObjectByName(headerGUIName);
headerGUI.caption = headings[h].headerCaption || headings[h].caption;
headerGUI.size = left + " " + headings[h].yStart + " " + (left + headings[h].width) + " 100%";
headerGUI.hidden = false;
if (headings[h].width < g_LongHeadingWidth)
left += headings[h].width;
}
}
function updateGeneralPanelTitles(titleHeadings)
{
let left = 250;
for (let th in titleHeadings)
{
if (th >= g_MaxHeadingTitle)
break;
if (titleHeadings[th].xOffset)
left += titleHeadings[th].xOffset;
let headerGUI = Engine.GetGUIObjectByName("titleHeading[" + th + "]");
headerGUI.caption = titleHeadings[th].caption;
headerGUI.size = left + " " + titleHeadings[th].yStart + " " + (left + titleHeadings[th].width) + " 100%";
headerGUI.hidden = false;
if (titleHeadings[th].width < g_LongHeadingWidth)
left += titleHeadings[th].width;
}
}
function updateGeneralPanelCounter(counters)
{
let rowPlayerObjectWidth = 0;
let left = 0;
for (let p = 0; p < g_MaxPlayers; ++p)
{
left = 240;
let counterObject;
for (let w in counters)
{
counterObject = Engine.GetGUIObjectByName("valueData[" + p + "][" + w + "]");
counterObject.size = left + " " + counters[w].verticalOffset + " " + (left + counters[w].width) + " 100%";
counterObject.hidden = false;
left += counters[w].width;
}
if (rowPlayerObjectWidth == 0)
rowPlayerObjectWidth = left;
let counterTotalObject;
for (let t = 0; t < g_MaxTeams; ++t)
{
left = 240;
for (let w in counters)
{
counterObject = Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + p + "][" + w + "]");
counterObject.size = left + " " + counters[w].verticalOffset + " " + (left + counters[w].width) + " 100%";
counterObject.hidden = false;
if (g_Teams[t])
{
let yStart = 25 + g_Teams[t].length * (g_PlayerBoxYSize + g_PlayerBoxGap) + 3 + counters[w].verticalOffset;
counterTotalObject = Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + w + "]");
counterTotalObject.size = (left + 20) + " " + yStart + " " + (left + counters[w].width) + " 100%";
counterTotalObject.hidden = false;
}
left += counters[w].width;
}
}
}
return rowPlayerObjectWidth;
}
function updateGeneralPanelTeams()
{
let withoutTeam = !g_Teams[-1] ? 0 : g_Teams[-1].length;
if (!g_Teams || withoutTeam > 0)
Engine.GetGUIObjectByName("noTeamsBox").hidden = false;
if (!g_Teams)
return;
let yStart = g_TeamsBoxYStart + withoutTeam * (g_PlayerBoxYSize + g_PlayerBoxGap) + (withoutTeam ? 30 : 0);
for (let i in g_Teams)
{
if (i == -1)
continue;
let teamBox = Engine.GetGUIObjectByName("teamBoxt["+i+"]");
teamBox.hidden = false;
let teamBoxSize = teamBox.size;
teamBoxSize.top = yStart;
teamBox.size = teamBoxSize;
yStart += 30 + g_Teams[i].length * (g_PlayerBoxYSize + g_PlayerBoxGap) + 32;
Engine.GetGUIObjectByName("teamNameHeadingt[" + i + "]").caption = "Team " + (+i + 1);
let teamHeading = Engine.GetGUIObjectByName("teamHeadingt[" + i + "]");
let yStartTotal = 30 + g_Teams[i].length * (g_PlayerBoxYSize + g_PlayerBoxGap) + 10;
teamHeading.size = "50 " + yStartTotal + " 100% " + (yStartTotal + 20);
teamHeading.caption = translate("Team total");
}
// If there are no players without team, hide "player name" heading
if (!withoutTeam)
Engine.GetGUIObjectByName("playerNameHeading").caption = "";
}
function initPlayerBoxPositions()
{
for (let h = 0; h < g_MaxPlayers; ++h)
{
let playerBox = Engine.GetGUIObjectByName("playerBox[" + h + "]");
let boxSize = playerBox.size;
boxSize.top += h * (g_PlayerBoxYSize + g_PlayerBoxGap);
boxSize.bottom = boxSize.top + g_PlayerBoxYSize;
playerBox.size = boxSize;
for (let i = 0; i < g_MaxTeams; ++i)
{
let playerBoxt = Engine.GetGUIObjectByName("playerBoxt[" + i + "][" + h + "]");
boxSize = playerBoxt.size;
boxSize.top += h * (g_PlayerBoxYSize + g_PlayerBoxGap);
boxSize.bottom = boxSize.top + g_PlayerBoxYSize;
playerBoxt.size = boxSize;
}
}
}
Index: ps/trunk/source/gui/CChart.cpp
===================================================================
--- ps/trunk/source/gui/CChart.cpp (revision 22281)
+++ ps/trunk/source/gui/CChart.cpp (revision 22282)
@@ -1,323 +1,329 @@
-/* Copyright (C) 2018 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 "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()
{
}
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 CColor& 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 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");
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, CColor(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", static_cast(value));
+ 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;
}