diff --git a/source/gui/CChart.cpp b/source/gui/CChart.cpp new file mode 100644 index 0000000000..ddfa845f28 --- /dev/null +++ b/source/gui/CChart.cpp @@ -0,0 +1,144 @@ +/* Copyright (C) 2016 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 "lib/ogl.h" +#include "ps/CLogger.h" +#include "renderer/Renderer.h" + +#include + +CChart::CChart() +{ + AddSetting(GUIST_CGUIList, "series_color"); + AddSetting(GUIST_CGUISeries, "series"); +} + +CChart::~CChart() +{ +} + +void CChart::HandleMessage(SGUIMessage& Message) +{ + // TODO: implement zoom +} + +void CChart::Draw() +{ + PROFILE3("render chart"); + + if (!GetGUI()) + return; + + UpdateSeries(); + + const float bz = GetBufferedZ(); + CRect rect = GetChartRect(); + const float width = rect.GetWidth(); + const float height = rect.GetHeight(); + + if (m_Series.empty()) + return; + + // 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 leftBottom, rightTop; + leftBottom = rightTop = m_Series[0].m_Points[0]; + for (const CChartData& data : m_Series) + for (const CVector2D& point : data.m_Points) + { + if (point.X < leftBottom.X) + leftBottom.X = point.X; + if (point.Y < leftBottom.Y) + leftBottom.Y = point.Y; + + if (point.X > rightTop.X) + rightTop.X = point.X; + if (point.Y > rightTop.Y) + rightTop.Y = point.Y; + } + + CVector2D scale(width / (rightTop.X - leftBottom.X), height / (rightTop.Y - leftBottom.Y)); + + for (const CChartData& data : m_Series) + { + if (data.m_Points.empty()) + continue; + + std::vector vertices; + for (const CVector2D& point : data.m_Points) + { + 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(bz + 0.5f); + } + shader->Uniform(str_color, data.m_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); + } + + tech->EndPass(); + + // Reset depth mask + glDepthMask(1); +} + +CRect CChart::GetChartRect() const +{ + return m_CachedActualSize; +} + +void CChart::UpdateSeries() +{ + CGUISeries* pSeries; + GUI::GetSettingPointer(this, "series", pSeries); + + CGUIList* pSeriesColor; + GUI::GetSettingPointer(this, "series_color", pSeriesColor); + + m_Series.clear(); + for (size_t i = 0; i < pSeries->m_Series.size(); ++i) + { + m_Series.resize(m_Series.size() + 1); + CChartData& data = m_Series.back(); + + 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]; + } +} diff --git a/source/gui/CChart.h b/source/gui/CChart.h new file mode 100644 index 0000000000..7b1574f503 --- /dev/null +++ b/source/gui/CChart.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2016 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.h" +#include "graphics/Color.h" +#include "maths/Vector2D.h" +#include + + +struct CChartData +{ + CColor m_Color; + std::vector m_Points; +}; + +/** +* Chart for a data visualization as lines or points +* +* @see IGUIObject +*/ +class CChart : public IGUIObject +{ + GUI_OBJECT(CChart) + +public: + CChart(); + virtual ~CChart(); + +protected: + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Draws the Chart + */ + virtual void Draw(); + + virtual CRect GetChartRect() const; + + void UpdateSeries(); + + std::vector m_Series; +}; + +#endif // INCLUDED_CCHART diff --git a/source/gui/CGUI.cpp b/source/gui/CGUI.cpp index 09c33868f8..a78d7b1b35 100644 --- a/source/gui/CGUI.cpp +++ b/source/gui/CGUI.cpp @@ -24,6 +24,7 @@ // Types - when including them into the engine. #include "CButton.h" +#include "CChart.h" #include "CCheckBox.h" #include "CDropDown.h" #include "CImage.h" @@ -312,6 +313,7 @@ void CGUI::Initialize() AddObjectType("olist", &COList::ConstructObject); AddObjectType("dropdown", &CDropDown::ConstructObject); AddObjectType("tooltip", &CTooltip::ConstructObject); + AddObjectType("chart", &CChart::ConstructObject); } void CGUI::Draw() diff --git a/source/gui/CGUISeries.h b/source/gui/CGUISeries.h new file mode 100644 index 0000000000..2a0f9e171d --- /dev/null +++ b/source/gui/CGUISeries.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2016 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_CGUISERIES +#define INCLUDED_CGUISERIES + +#include "GUItext.h" +#include "maths/Vector2D.h" + + +class CGUISeries +{ +public: + std::vector> m_Series; +}; + +#endif diff --git a/source/gui/GUI.h b/source/gui/GUI.h index d2c5657507..801f85e3aa 100644 --- a/source/gui/GUI.h +++ b/source/gui/GUI.h @@ -40,6 +40,7 @@ GUI Inclusion file #include "ps/CStr.h" #include "CGUIList.h" +#include "CGUISeries.h" #include "GUIbase.h" #include "GUItext.h" #include "GUIutil.h" diff --git a/source/gui/GUItypes.h b/source/gui/GUItypes.h index 6f363ca2a9..0daab15795 100644 --- a/source/gui/GUItypes.h +++ b/source/gui/GUItypes.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -40,3 +40,4 @@ TYPE(EAlign) TYPE(EVAlign) TYPE(CPos) TYPE(CGUIList) +TYPE(CGUISeries) diff --git a/source/gui/GUIutil.cpp b/source/gui/GUIutil.cpp index 2c0f63f5a4..11aea048f2 100644 --- a/source/gui/GUIutil.cpp +++ b/source/gui/GUIutil.cpp @@ -253,6 +253,11 @@ bool __ParseString(const CStrW& UNUSED(Value), CGUIList& UNUSED(Output return false; } +template <> +bool __ParseString(const CStrW& UNUSED(Value), CGUISeries& UNUSED(Output)) +{ + return false; +} //-------------------------------------------------------- diff --git a/source/gui/scripting/JSInterface_IGUIObject.cpp b/source/gui/scripting/JSInterface_IGUIObject.cpp index cfee79a069..471b00ce12 100644 --- a/source/gui/scripting/JSInterface_IGUIObject.cpp +++ b/source/gui/scripting/JSInterface_IGUIObject.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -298,6 +298,35 @@ bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::Handle break; } + case GUIST_CGUISeries: + { + CGUISeries value; + GUI::GetSetting(e, propName, value); + + JS::RootedObject obj(cx, JS_NewArrayObject(cx, JS::HandleValueArray::empty())); + vp.setObject(*obj); + + for (u32 i = 0; i < value.m_Series.size(); ++i) + { + JS::RootedObject inner_obj(cx, JS_NewArrayObject(cx, JS::HandleValueArray::empty())); + for (u32 j = 0; j < value.m_Series[i].size(); ++j) + { + JS::RootedObject val(cx, JS_NewArrayObject(cx, JS::HandleValueArray::empty())); + + JS::RootedValue val_x(cx), val_y(cx); + ScriptInterface::ToJSVal(cx, &val_x, value.m_Series[i][j].X); + ScriptInterface::ToJSVal(cx, &val_y, value.m_Series[i][j].Y); + JS_SetElement(cx, val, 0, val_x); + JS_SetElement(cx, val, 1, val_y); + + JS_SetElement(cx, inner_obj, j, val); + } + JS_SetElement(cx, obj, i, inner_obj); + } + + break; + } + default: JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str()); DEBUG_WARN_ERR(ERR::LOGIC); @@ -588,6 +617,69 @@ bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::Handle break; } + case GUIST_CGUISeries: + { + u32 length; + if (!vp.isObject() || !JS_GetArrayLength(cx, vpObj, &length)) + { + JS_ReportError(cx, "Table only accepts a GUISeries object"); + return false; + } + + CGUISeries series; + series.m_Series.resize(length); + for (u32 i = 0; i < length; ++i) + { + JS::RootedValue data_value(cx); + if (!JS_GetElement(cx, vpObj, i, &data_value)) + { + JS_ReportError(cx, "Failed to get a data of series"); + return false; + } + + JS::RootedObject data(cx, data_value.toObjectOrNull()); + u32 data_length; + if (!JS_GetArrayLength(cx, data, &data_length)) + { + JS_ReportError(cx, "Series only accepts a chart data"); + return false; + } + + series.m_Series[i].resize(data_length); + for (u32 j = 0; j < data_length; ++j) + { + JS::RootedValue element_value(cx); + if (!JS_GetElement(cx, data, j, &element_value)) + { + JS_ReportError(cx, "Failed to get a chart data element"); + return false; + } + + JS::RootedObject element(cx, element_value.toObjectOrNull()); + u32 element_length; + if (!JS_GetArrayLength(cx, element, &element_length) || element_length < 2) + { + JS_ReportError(cx, "Chart data only accepts a point"); + return false; + } + + JS::RootedValue element_x(cx), element_y(cx); + if (!JS_GetElement(cx, element, 0, &element_x) || !JS_GetElement(cx, element, 1, &element_y)) + { + JS_ReportError(cx, "Failed to get a chart point"); + return false; + } + + if (!ScriptInterface::FromJSVal(cx, element_x, series.m_Series[i][j].X) || + !ScriptInterface::FromJSVal(cx, element_y, series.m_Series[i][j].Y)) + return false; + } + } + + GUI::SetSetting(e, propName, series); + break; + } + default: JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str()); break;