Index: ps/trunk/build/premake/premake5.lua =================================================================== --- ps/trunk/build/premake/premake5.lua +++ ps/trunk/build/premake/premake5.lua @@ -759,7 +759,10 @@ source_dirs = { "gui", - "gui/scripting", + "gui/ObjectTypes", + "gui/ObjectBases", + "gui/Scripting", + "gui/SettingTypes", "i18n" } extern_libs = { Index: ps/trunk/source/gui/CButton.h =================================================================== --- ps/trunk/source/gui/CButton.h +++ ps/trunk/source/gui/CButton.h @@ -1,89 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_CBUTTON -#define INCLUDED_CBUTTON - -#include "gui/IGUIButtonBehavior.h" -#include "gui/IGUIObject.h" -#include "gui/IGUITextOwner.h" -#include "gui/CGUISprite.h" -#include "gui/CGUIString.h" - -class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior -{ - GUI_OBJECT(CButton) - -public: - CButton(CGUI& pGUI); - virtual ~CButton(); - - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates(); - - /** - * @see IGUIObject#UpdateCachedSize() - */ - virtual void UpdateCachedSize(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Draws the Button - */ - virtual void Draw(); - -protected: - /** - * Sets up text, should be called every time changes has been - * made that can change the visual. - */ - void SetupText(); - - /** - * Picks the text color depending on current object settings. - */ - const CGUIColor& ChooseColor(); - - /** - * Placement of text. - */ - CPos m_TextPos; - - // Settings - float m_BufferZone; - i32 m_CellID; - CGUIString m_Caption; - CStrW m_Font; - CGUISpriteInstance m_Sprite; - CGUISpriteInstance m_SpriteOver; - CGUISpriteInstance m_SpritePressed; - CGUISpriteInstance m_SpriteDisabled; - EAlign m_TextAlign; - EVAlign m_TextVAlign; - CGUIColor m_TextColor; - CGUIColor m_TextColorOver; - CGUIColor m_TextColorPressed; - CGUIColor m_TextColorDisabled; -}; - -#endif // INCLUDED_CBUTTON Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp +++ ps/trunk/source/gui/CButton.cpp @@ -1,119 +0,0 @@ -/* 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/CGUI.h" -#include "gui/CGUIColor.h" -#include "gui/CGUIText.h" - -CButton::CButton(CGUI& pGUI) - : IGUIObject(pGUI), - IGUIButtonBehavior(*static_cast(this)), - IGUITextOwner(*static_cast(this)), - m_BufferZone(), - m_CellID(), - m_Caption(), - m_Font(), - m_Sprite(), - m_SpriteOver(), - m_SpritePressed(), - m_SpriteDisabled(), - m_TextAlign(), - m_TextVAlign(), - m_TextColor(), - m_TextColorOver(), - m_TextColorPressed(), - m_TextColorDisabled() -{ - RegisterSetting("buffer_zone", m_BufferZone); - RegisterSetting("cell_id", m_CellID); - RegisterSetting("caption", m_Caption); - RegisterSetting("font", m_Font); - RegisterSetting("sprite", m_Sprite); - RegisterSetting("sprite_over", m_SpriteOver); - RegisterSetting("sprite_pressed", m_SpritePressed); - RegisterSetting("sprite_disabled", m_SpriteDisabled); - RegisterSetting("text_align", m_TextAlign); - RegisterSetting("text_valign", m_TextVAlign); - RegisterSetting("textcolor", m_TextColor); - RegisterSetting("textcolor_over", m_TextColorOver); - RegisterSetting("textcolor_pressed", m_TextColorPressed); - RegisterSetting("textcolor_disabled", m_TextColorDisabled); - - AddText(); -} - -CButton::~CButton() -{ -} - -void CButton::SetupText() -{ - ENSURE(m_GeneratedTexts.size() == 1); - - m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, m_CachedActualSize.GetWidth(), m_BufferZone, this); - CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]); -} - -void CButton::ResetStates() -{ - IGUIObject::ResetStates(); - IGUIButtonBehavior::ResetStates(); -} - -void CButton::UpdateCachedSize() -{ - IGUIObject::UpdateCachedSize(); - IGUITextOwner::UpdateCachedSize(); -} - -void CButton::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - IGUIButtonBehavior::HandleMessage(Message); - IGUITextOwner::HandleMessage(Message); -} - -void CButton::Draw() -{ - const float bz = GetBufferedZ(); - - m_pGUI.DrawSprite( - GetButtonSprite(m_Sprite, m_SpriteOver, m_SpritePressed, m_SpriteDisabled), - m_CellID, - bz, - m_CachedActualSize); - - DrawText(0, ChooseColor(), m_TextPos, bz + 0.1f); -} - -const CGUIColor& CButton::ChooseColor() -{ - if (!m_Enabled) - return m_TextColorDisabled || m_TextColor; - - if (!m_MouseHovering) - return m_TextColor; - - if (m_Pressed) - return m_TextColorPressed || m_TextColor; - - return m_TextColorOver || m_TextColor; -} Index: ps/trunk/source/gui/CChart.h =================================================================== --- ps/trunk/source/gui/CChart.h +++ ps/trunk/source/gui/CChart.h @@ -1,111 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_CCHART -#define INCLUDED_CCHART - -#include "graphics/ShaderProgramPtr.h" -#include "gui/CGUIColor.h" -#include "gui/CGUIList.h" -#include "gui/CGUISeries.h" -#include "gui/IGUITextOwner.h" -#include "maths/Vector2D.h" - -#include - -struct CChartData -{ - // Avoid copying the container. - NONCOPYABLE(CChartData); - MOVABLE(CChartData); - CChartData() = default; - - CGUIColor m_Color; - std::vector m_Points; -}; - -/** - * Chart for a data visualization as lines or points - */ -class CChart : public IGUIObject, public IGUITextOwner -{ - GUI_OBJECT(CChart) - -public: - CChart(CGUI& pGUI); - virtual ~CChart(); - -protected: - /** - * @see IGUIObject#UpdateCachedSize() - */ - void UpdateCachedSize(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Draws the Chart - */ - virtual void Draw(); - - virtual CRect GetChartRect() const; - - void UpdateSeries(); - - void SetupText(); - - std::vector m_Series; - - CVector2D m_LeftBottom, m_RightTop; - - std::vector m_TextPositions; - - bool m_EqualX, m_EqualY; - - // Settings - CGUIColor m_AxisColor; - float m_AxisWidth; - float m_BufferZone; - CStrW m_Font; - CStrW m_FormatX; - CStrW m_FormatY; - CGUIList m_SeriesColor; - CGUISeries m_SeriesSetting; - EAlign m_TextAlign; - -private: - /** - * Helper functions - */ - void DrawLine(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const; - - // Draws the triangle sequence so that the each next triangle has a common edge with the previous one. - // If we need to draw n triangles, we need only n + 2 points. - void DrawTriangleStrip(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const; - - // Represents axes as triangles and draws them with DrawTriangleStrip. - void DrawAxes(const CShaderProgramPtr& shader) const; - - CSize AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone); - - void UpdateBounds(); -}; - -#endif // INCLUDED_CCHART Index: ps/trunk/source/gui/CChart.cpp =================================================================== --- ps/trunk/source/gui/CChart.cpp +++ ps/trunk/source/gui/CChart.cpp @@ -1,318 +0,0 @@ -/* 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 "gui/CGUIList.h" -#include "gui/CGUISeries.h" -#include "gui/CGUIString.h" -#include "gui/GUIMatrix.h" -#include "ps/CLogger.h" -#include "ps/Profile.h" -#include "renderer/Renderer.h" - -#include - -CChart::CChart(CGUI& pGUI) - : IGUIObject(pGUI), - IGUITextOwner(*static_cast(this)), - m_AxisColor(), - m_AxisWidth(), - m_BufferZone(), - m_Font(), - m_FormatX(), - m_FormatY(), - m_SeriesColor(), - m_SeriesSetting(), - m_TextAlign() -{ - RegisterSetting("axis_color", m_AxisColor); - RegisterSetting("axis_width", m_AxisWidth); - RegisterSetting("buffer_zone", m_BufferZone); - RegisterSetting("font", m_Font); - RegisterSetting("format_x", m_FormatX); - RegisterSetting("format_y", m_FormatY); - RegisterSetting("series_color", m_SeriesColor); - RegisterSetting("series", m_SeriesSetting); - RegisterSetting("text_align", m_TextAlign); -} - -CChart::~CChart() -{ -} - -void CChart::UpdateCachedSize() -{ - IGUIObject::UpdateCachedSize(); - IGUITextOwner::UpdateCachedSize(); -} - -void CChart::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - // IGUITextOwner::HandleMessage(Message); performed in UpdateSeries - - // TODO: implement zoom - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - { - 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 - DrawTriangleStrip(shader, m_AxisColor, 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() -{ - m_Series.clear(); - m_Series.resize(m_SeriesSetting.m_Series.size()); - - for (size_t i = 0; i < m_SeriesSetting.m_Series.size(); ++i) - { - CChartData& data = m_Series[i]; - - if (i < m_SeriesColor.m_Items.size() && !data.m_Color.ParseString(m_pGUI, m_SeriesColor.m_Items[i].GetOriginalString().ToUTF8(), 0)) - LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(m_SeriesColor.m_Items[i].GetOriginalString())); - - data.m_Points = m_SeriesSetting.m_Series[i]; - } - UpdateBounds(); - - SetupText(); -} - -void CChart::SetupText() -{ - m_GeneratedTexts.clear(); - m_TextPositions.clear(); - - if (m_Series.empty()) - return; - - // Add Y-axis - 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, m_Font, m_BufferZone); - 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, m_Font, m_BufferZone); - m_TextPositions.emplace_back(GetChartRect().TopLeft() + CPos(0.f, height / 3.f * i)); - } - - // Add X-axis - const float width = GetChartRect().GetWidth(); - if (m_EqualX) - { - CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X, m_Font, m_BufferZone); - 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, m_Font, m_BufferZone); - 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).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/CCheckBox.h =================================================================== --- ps/trunk/source/gui/CCheckBox.h +++ ps/trunk/source/gui/CCheckBox.h @@ -1,61 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_CCHECKBOX -#define INCLUDED_CCHECKBOX - -#include "gui/CGUISprite.h" -#include "gui/IGUIButtonBehavior.h" - -class CCheckBox : public IGUIObject, public IGUIButtonBehavior -{ - GUI_OBJECT(CCheckBox) - -public: - CCheckBox(CGUI& pGUI); - virtual ~CCheckBox(); - - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Draws the control - */ - virtual void Draw(); - -protected: - // Settings - i32 m_CellID; - bool m_Checked; - CGUISpriteInstance m_SpriteUnchecked; - CGUISpriteInstance m_SpriteUncheckedOver; - CGUISpriteInstance m_SpriteUncheckedPressed; - CGUISpriteInstance m_SpriteUncheckedDisabled; - CGUISpriteInstance m_SpriteChecked; - CGUISpriteInstance m_SpriteCheckedOver; - CGUISpriteInstance m_SpriteCheckedPressed; - CGUISpriteInstance m_SpriteCheckedDisabled; -}; - -#endif // INCLUDED_CCHECKBOX Index: ps/trunk/source/gui/CCheckBox.cpp =================================================================== --- ps/trunk/source/gui/CCheckBox.cpp +++ ps/trunk/source/gui/CCheckBox.cpp @@ -1,87 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#include "precompiled.h" - -#include "CCheckBox.h" - -#include "gui/CGUI.h" - -CCheckBox::CCheckBox(CGUI& pGUI) - : IGUIObject(pGUI), - IGUIButtonBehavior(*static_cast(this)), - m_CellID(), - m_Checked(), - m_SpriteUnchecked(), - m_SpriteUncheckedOver(), - m_SpriteUncheckedPressed(), - m_SpriteUncheckedDisabled(), - m_SpriteChecked(), - m_SpriteCheckedOver(), - m_SpriteCheckedPressed(), - m_SpriteCheckedDisabled() -{ - RegisterSetting("cell_id", m_CellID); - RegisterSetting("checked", m_Checked), - RegisterSetting("sprite", m_SpriteUnchecked); - RegisterSetting("sprite_over", m_SpriteUncheckedOver); - RegisterSetting("sprite_pressed", m_SpriteUncheckedPressed); - RegisterSetting("sprite_disabled", m_SpriteUncheckedDisabled); - RegisterSetting("sprite2", m_SpriteChecked); - RegisterSetting("sprite2_over", m_SpriteCheckedOver); - RegisterSetting("sprite2_pressed", m_SpriteCheckedPressed); - RegisterSetting("sprite2_disabled", m_SpriteCheckedDisabled); -} - -CCheckBox::~CCheckBox() -{ -} - -void CCheckBox::ResetStates() -{ - IGUIObject::ResetStates(); - IGUIButtonBehavior::ResetStates(); -} - -void CCheckBox::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - IGUIButtonBehavior::HandleMessage(Message); - - switch (Message.type) - { - case GUIM_PRESSED: - { - SetSetting("checked", !m_Checked, true); - break; - } - - default: - break; - } -} - -void CCheckBox::Draw() -{ - m_pGUI.DrawSprite( - m_Checked ? - GetButtonSprite(m_SpriteChecked, m_SpriteCheckedOver, m_SpriteCheckedPressed, m_SpriteCheckedDisabled) : - GetButtonSprite(m_SpriteUnchecked, m_SpriteUncheckedOver, m_SpriteUncheckedPressed, m_SpriteUncheckedDisabled), - m_CellID, - GetBufferedZ(), - m_CachedActualSize); -} Index: ps/trunk/source/gui/CDropDown.h =================================================================== --- ps/trunk/source/gui/CDropDown.h +++ ps/trunk/source/gui/CDropDown.h @@ -1,142 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -/* -GUI Object - Drop Down (list) - ---Overview-- - - Works just like a list-box, but it hides - all the elements that aren't selected. They - can be brought up by pressing the control. -*/ - -#ifndef INCLUDED_CDROPDOWN -#define INCLUDED_CDROPDOWN - -#include "gui/CGUISprite.h" -#include "gui/CList.h" - -#include - -/** - * Drop Down - * - * The control can be pressed, but we will not inherent - * this behavior from IGUIButtonBehavior, because when - * you press this control, the list with elements will - * immediately appear, and not first after release - * (which is the whole gist of the IGUIButtonBehavior). - */ -class CDropDown : public CList -{ - GUI_OBJECT(CDropDown) - -public: - CDropDown(CGUI& pGUI); - virtual ~CDropDown(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Handle events manually to catch keyboard inputting. - */ - virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); - - /** - * Draws the Button - */ - virtual void Draw(); - - // This is one of the few classes we actually need to redefine this function - // this is because the size of the control changes whether it is open - // or closed. - virtual bool IsMouseOver() const; - - virtual float GetBufferedZ() const; - -protected: - /** - * If the size changed, the texts have to be updated as - * the word wrapping depends on the size. - */ - virtual void UpdateCachedSize(); - - /** - * Sets up text, should be called every time changes has been - * made that can change the visual. - */ - void SetupText(); - - // Sets up the cached GetListRect. Decided whether it should - // have a scrollbar, and so on. - virtual void SetupListRect(); - - // Specify a new List rectangle. - virtual CRect GetListRect() const; - - /** - * Placement of text. - */ - CPos m_TextPos; - - // Is the dropdown opened? - bool m_Open; - - // I didn't cache this at first, but it's just as easy as caching - // m_CachedActualSize, so I thought, what the heck it's used a lot. - CRect m_CachedListRect; - - // Hide scrollbar when it's not needed - bool m_HideScrollBar; - - // Not necessarily the element that is selected, this is just - // which element should be highlighted. When opening the dropdown - // it is set to "selected", but then when moving the mouse it will - // change. - int m_ElementHighlight; - - // Stores any text entered by the user for quick access to an element - // (ie if you type "acro" it will take you to acropolis). - std::string m_InputBuffer; - - // used to know if we want to restart anew or add to m_inputbuffer. - double m_TimeOfLastInput; - - // Settings - float m_ButtonWidth; - float m_DropDownSize; - float m_DropDownBuffer; - u32 m_MinimumVisibleItems; - CStrW m_SoundClosed; - CStrW m_SoundEnter; - CStrW m_SoundLeave; - CStrW m_SoundOpened; - CGUISpriteInstance m_SpriteDisabled; - CGUISpriteInstance m_SpriteList; - CGUISpriteInstance m_Sprite2; - CGUISpriteInstance m_Sprite2Over; - CGUISpriteInstance m_Sprite2Pressed; - CGUISpriteInstance m_Sprite2Disabled; - CGUIColor m_TextColorDisabled; - EVAlign m_TextVAlign; -}; - -#endif // INCLUDED_CDROPDOWN Index: ps/trunk/source/gui/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp +++ ps/trunk/source/gui/CDropDown.cpp @@ -1,505 +0,0 @@ -/* 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/CGUI.h" -#include "gui/CGUIColor.h" -#include "gui/CGUIList.h" -#include "gui/IGUIScrollBar.h" -#include "lib/external_libraries/libsdl.h" -#include "lib/timer.h" -#include "ps/Profile.h" - -CDropDown::CDropDown(CGUI& pGUI) - : CList(pGUI), - m_Open(), - m_HideScrollBar(), - m_ElementHighlight(-1), - m_ButtonWidth(), - m_DropDownSize(), - m_DropDownBuffer(), - m_MinimumVisibleItems(), - m_SoundClosed(), - m_SoundEnter(), - m_SoundLeave(), - m_SoundOpened(), - m_SpriteDisabled(), - m_SpriteList(), - m_Sprite2(), - m_Sprite2Over(), - m_Sprite2Pressed(), - m_Sprite2Disabled(), - m_TextColorDisabled(), - m_TextVAlign() -{ - RegisterSetting("button_width", m_ButtonWidth); - RegisterSetting("dropdown_size", m_DropDownSize); - RegisterSetting("dropdown_buffer", m_DropDownBuffer); - RegisterSetting("minimum_visible_items", m_MinimumVisibleItems); - RegisterSetting("sound_closed", m_SoundClosed); - RegisterSetting("sound_enter", m_SoundEnter); - RegisterSetting("sound_leave", m_SoundLeave); - RegisterSetting("sound_opened", m_SoundOpened); - // Setting "sprite" is registered by CList and used as the background - RegisterSetting("sprite_disabled", m_SpriteDisabled); - RegisterSetting("sprite_list", m_SpriteList); // Background of the drop down list - RegisterSetting("sprite2", m_Sprite2); // Button that sits to the right - RegisterSetting("sprite2_over", m_Sprite2Over); - RegisterSetting("sprite2_pressed", m_Sprite2Pressed); - RegisterSetting("sprite2_disabled", m_Sprite2Disabled); - RegisterSetting("textcolor_disabled", m_TextColorDisabled); - RegisterSetting("text_valign", m_TextVAlign); - // Add these in CList! And implement TODO - //RegisterSetting("textcolor_over"); - //RegisterSetting("textcolor_pressed"); - - // Scrollbar is forced to be true. - SetSetting("scrollbar", true, true); -} - -CDropDown::~CDropDown() -{ -} - -void CDropDown::SetupText() -{ - SetupListRect(); - CList::SetupText(); -} - -void CDropDown::UpdateCachedSize() -{ - CList::UpdateCachedSize(); - SetupText(); -} - -void CDropDown::HandleMessage(SGUIMessage& Message) -{ - // CList::HandleMessage(Message); placed after the switch! - - 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; - - const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f; - - CRect rect = GetListRect(); - mouse.y += scroll; - int set = -1; - for (int i = 0; i < static_cast(m_List.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: - { - if (m_Enabled) - PlaySound(m_SoundEnter); - break; - } - - case GUIM_MOUSE_LEAVE: - { - m_ElementHighlight = m_Selected; - - if (m_Enabled) - PlaySound(m_SoundLeave); - 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: - { - if (!m_Enabled) - { - PlaySound(m_SoundDisabled); - break; - } - - if (!m_Open) - { - if (m_List.m_Items.empty()) - return; - - m_Open = true; - GetScrollBar(0).SetZ(GetBufferedZ()); - m_ElementHighlight = m_Selected; - - // Start at the position of the selected item, if possible. - GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); - - PlaySound(m_SoundOpened); - 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()); - PlaySound(m_SoundClosed); - 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: - { - // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. - if (m_Open || !m_Enabled) - break; - - m_ElementHighlight = m_Selected; - - if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) - break; - - ++m_ElementHighlight; - SetSetting("selected", m_ElementHighlight, true); - break; - } - - case GUIM_MOUSE_WHEEL_UP: - { - // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. - if (m_Open || !m_Enabled) - break; - - m_ElementHighlight = m_Selected; - if (m_ElementHighlight - 1 < 0) - break; - - --m_ElementHighlight; - SetSetting("selected", m_ElementHighlight, true); - break; - } - - case GUIM_LOST_FOCUS: - { - if (m_Open) - PlaySound(m_SoundClosed); - - 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() - SetSetting("selected", m_ElementHighlight, true); - 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(); - - // 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(m_List.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(m_List.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) - { - SetSetting("selected", closest, true); - 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) - m_ElementHighlight = m_Selected; - - return result; -} - -void CDropDown::SetupListRect() -{ - extern int g_yres; - extern float g_GuiScale; - const float yres = g_yres / g_GuiScale; - - if (m_ItemsYPositions.empty()) - { - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, - m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize); - m_HideScrollBar = false; - } - // Too many items so use a scrollbar - else if (m_ItemsYPositions.back() > m_DropDownSize) - { - // Place items below if at least some items can be placed below - if (m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize <= yres) - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, - m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize); - else if ((m_ItemsYPositions.size() > m_MinimumVisibleItems && yres - m_CachedActualSize.bottom - m_DropDownBuffer >= m_ItemsYPositions[m_MinimumVisibleItems]) || - m_CachedActualSize.top < yres - m_CachedActualSize.bottom) - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, - 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 - m_DropDownBuffer - m_DropDownSize), - m_CachedActualSize.right, m_CachedActualSize.top - m_DropDownBuffer); - - m_HideScrollBar = false; - } - else - { - // Enough space below, no scrollbar needed - if (m_CachedActualSize.bottom + m_DropDownBuffer + m_ItemsYPositions.back() <= yres) - { - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, - m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + 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() > m_MinimumVisibleItems && yres - m_CachedActualSize.bottom - m_DropDownBuffer >= m_ItemsYPositions[m_MinimumVisibleItems]) || - m_CachedActualSize.top < yres - m_CachedActualSize.bottom) - { - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, - 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 - m_DropDownBuffer - m_ItemsYPositions.back()), - m_CachedActualSize.right, m_CachedActualSize.top - m_DropDownBuffer); - m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + m_DropDownBuffer; - } - } -} - -CRect CDropDown::GetListRect() const -{ - return m_CachedListRect; -} - -bool CDropDown::IsMouseOver() const -{ - 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() -{ - const float bz = GetBufferedZ(); - const CGUISpriteInstance& sprite = m_Enabled ? m_Sprite : m_SpriteDisabled; - - m_pGUI.DrawSprite(sprite, m_CellID, bz, m_CachedActualSize); - - if (m_ButtonWidth > 0.f) - { - CRect rect(m_CachedActualSize.right - m_ButtonWidth, m_CachedActualSize.top, - m_CachedActualSize.right, m_CachedActualSize.bottom); - - if (!m_Enabled) - { - m_pGUI.DrawSprite(m_Sprite2Disabled || m_Sprite2, m_CellID, bz + 0.05f, rect); - } - else if (m_Open) - { - m_pGUI.DrawSprite(m_Sprite2Pressed || m_Sprite2, m_CellID, bz + 0.05f, rect); - } - else if (m_MouseHovering) - { - m_pGUI.DrawSprite(m_Sprite2Over || m_Sprite2, m_CellID, bz + 0.05f, rect); - } - else - m_pGUI.DrawSprite(m_Sprite2, m_CellID, bz + 0.05f, rect); - } - - if (m_Selected != -1) // TODO: Maybe check validity completely? - { - CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, - m_CachedActualSize.right - m_ButtonWidth, m_CachedActualSize.bottom); - - CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); - DrawText(m_Selected, m_Enabled ? m_TextColorSelected : m_TextColorDisabled, pos, bz + 0.1f, cliparea); - } - - // Disable scrollbar during drawing without sending a setting-changed message - bool old = m_ScrollBar; - - if (m_Open) - { - // TODO: drawScrollbar as an argument of DrawList? - if (m_HideScrollBar) - m_ScrollBar = false; - - DrawList(m_ElementHighlight, m_SpriteList, m_SpriteSelectArea, m_TextColor); - - if (m_HideScrollBar) - m_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.h =================================================================== --- ps/trunk/source/gui/CGUI.h +++ ps/trunk/source/gui/CGUI.h @@ -23,9 +23,9 @@ #ifndef INCLUDED_CGUI #define INCLUDED_CGUI -#include "gui/CGUIColor.h" -#include "gui/CGUIDummyObject.h" #include "gui/GUITooltip.h" +#include "gui/ObjectTypes/CGUIDummyObject.h" +#include "gui/SettingTypes/CGUIColor.h" #include "gui/SGUIIcon.h" #include "gui/SGUIStyle.h" #include "lib/input.h" Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp +++ ps/trunk/source/gui/CGUI.cpp @@ -19,23 +19,22 @@ #include "CGUI.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 "gui/IGUIScrollBar.h" +#include "gui/ObjectTypes/CButton.h" +#include "gui/ObjectTypes/CChart.h" +#include "gui/ObjectTypes/CCheckBox.h" +#include "gui/ObjectTypes/CDropDown.h" +#include "gui/ObjectTypes/CImage.h" +#include "gui/ObjectTypes/CInput.h" +#include "gui/ObjectTypes/CList.h" +#include "gui/ObjectTypes/CMiniMap.h" +#include "gui/ObjectTypes/COList.h" +#include "gui/ObjectTypes/CProgressBar.h" +#include "gui/ObjectTypes/CRadioButton.h" +#include "gui/ObjectTypes/CSlider.h" +#include "gui/ObjectTypes/CText.h" +#include "gui/ObjectTypes/CTooltip.h" +#include "gui/Scripting/ScriptFunctions.h" #include "i18n/L10n.h" #include "lib/bits.h" #include "lib/input.h" @@ -51,7 +50,6 @@ #include "ps/Pyrogenesis.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" -#include "scripting/ScriptFunctions.h" #include "scriptinterface/ScriptInterface.h" #include Index: ps/trunk/source/gui/CGUIColor.h =================================================================== --- ps/trunk/source/gui/CGUIColor.h +++ ps/trunk/source/gui/CGUIColor.h @@ -1,61 +0,0 @@ -/* 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_GUICOLOR -#define INCLUDED_GUICOLOR - -#include "graphics/Color.h" -#include "ps/CStr.h" - -class CGUI; - -/** - * Same as the CColor class, but this one can also parse colors predefined in the GUI page (such as "yellow"). - */ -struct CGUIColor : CColor -{ - // Take advantage of compiler warnings if unintentionally copying this - NONCOPYABLE(CGUIColor); - - // Defines move semantics so that the structs using the class can use it. - MOVABLE(CGUIColor); - - CGUIColor() : CColor() {} - - CGUIColor(float r, float g, float b, float a) : CColor(r, g, b, a) {} - - /** - * Returns this color if it has been set, otherwise the given fallback color. - */ - const CGUIColor& operator||(const CGUIColor& fallback) const - { - if (*this) - return *this; - return fallback; - } - - /** - * Load color depending on current GUI page. - */ - bool ParseString(const CGUI& pGUI, const CStr& value, int defaultAlpha = 255); - - /** - * Ensure that all users check for predefined colors. - */ - bool ParseString(const CStr& value, int defaultAlpha = 255) = delete; -}; -#endif // INCLUDED_GUICOLOR Index: ps/trunk/source/gui/CGUIColor.cpp =================================================================== --- ps/trunk/source/gui/CGUIColor.cpp +++ ps/trunk/source/gui/CGUIColor.cpp @@ -1,40 +0,0 @@ -/* 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 "CGUIColor.h" - -#include "gui/CGUI.h" -#include "ps/CStr.h" - -bool CGUIColor::ParseString(const CGUI& pGUI, const CStr& value, int defaultAlpha) -{ - if (pGUI.HasPreDefinedColor(value)) - { - const CGUIColor& color = pGUI.GetPreDefinedColor(value); - - // Explicit copy assignment - r = color.r; - g = color.g; - b = color.b; - a = color.a; - return true; - } - - return CColor::ParseString(value, defaultAlpha); -} Index: ps/trunk/source/gui/CGUIDummyObject.h =================================================================== --- ps/trunk/source/gui/CGUIDummyObject.h +++ ps/trunk/source/gui/CGUIDummyObject.h @@ -1,49 +0,0 @@ -/* 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 . - */ - -/* - * This is the top class of the whole GUI, all objects - * and settings are stored within this class. - */ - -#ifndef INCLUDED_CGUIDUMMYOBJECT -#define INCLUDED_CGUIDUMMYOBJECT - -#include "gui/IGUIObject.h" - -/** - * Dummy object are used for the base object and objects of type "empty". - */ -class CGUIDummyObject : public IGUIObject -{ - GUI_OBJECT(CGUIDummyObject) - -public: - CGUIDummyObject(CGUI& pGUI) : IGUIObject(pGUI) {} - - virtual void Draw() {} - - /** - * Empty can never be hovered. It is only a category. - */ - virtual bool IsMouseOver() const - { - return false; - } -}; - -#endif // INCLUDED_CGUIDUMMYOBJECT Index: ps/trunk/source/gui/CGUIList.h =================================================================== --- ps/trunk/source/gui/CGUIList.h +++ ps/trunk/source/gui/CGUIList.h @@ -1,43 +0,0 @@ -/* 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_CGUILIST -#define INCLUDED_CGUILIST - -#include "gui/CGUIString.h" - -#include - -class CGUIList -{ -public: // struct:ish (but for consistency I call it _C_GUIList, and - // for the same reason it is a class, so that confusion doesn't - // appear when forward declaring. - - // Avoid copying the vector. - NONCOPYABLE(CGUIList); - MOVABLE(CGUIList); - CGUIList() = default; - - /** - * List of items (as text), the post-processed result is stored in - * the IGUITextOwner structure of this class. - */ - std::vector m_Items; -}; - -#endif Index: ps/trunk/source/gui/CGUISeries.h =================================================================== --- ps/trunk/source/gui/CGUISeries.h +++ ps/trunk/source/gui/CGUISeries.h @@ -1,36 +0,0 @@ -/* 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_CGUISERIES -#define INCLUDED_CGUISERIES - -#include "maths/Vector2D.h" - -#include - -class CGUISeries -{ -public: - NONCOPYABLE(CGUISeries); - MOVABLE(CGUISeries); - CGUISeries() = default; - - std::vector> m_Series; -}; - -#endif // INCLUDED_CGUISERIES Index: ps/trunk/source/gui/CGUISetting.h =================================================================== --- ps/trunk/source/gui/CGUISetting.h +++ ps/trunk/source/gui/CGUISetting.h @@ -18,7 +18,7 @@ #ifndef INCLUDED_CGUISETTINGS #define INCLUDED_CGUISETTINGS -#include "gui/IGUIObject.h" +#include "gui/ObjectBases/IGUIObject.h" /** * This setting interface allows GUI objects to call setting function functions without having to know the setting type. Index: ps/trunk/source/gui/CGUISetting.cpp =================================================================== --- ps/trunk/source/gui/CGUISetting.cpp +++ ps/trunk/source/gui/CGUISetting.cpp @@ -84,5 +84,5 @@ #define TYPE(T) \ template class CGUISetting; \ -#include "GUItypes.h" +#include "gui/GUISettingTypes.h" #undef TYPE Index: ps/trunk/source/gui/CGUISize.h =================================================================== --- ps/trunk/source/gui/CGUISize.h +++ ps/trunk/source/gui/CGUISize.h @@ -1,83 +0,0 @@ -/* 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_CGUISIZE -#define INCLUDED_CGUISIZE - -#include "ps/CStr.h" -#include "ps/Errors.h" -#include "ps/Shapes.h" -#include "scriptinterface/ScriptInterface.h" - -/** - * This class represents a rectangle relative to a parent rectangle - * The value can be initialized from a string or JS object. - */ -class CGUISize -{ -public: - // COPYABLE, since there are only primitives involved, making move and copy identical, - // and since some temporaries cannot be avoided. - CGUISize(); - CGUISize(const CRect& pixel, const CRect& percent); - - static CGUISize Full(); - - /// Pixel modifiers - CRect pixel; - - /// Percent modifiers - CRect percent; - - /** - * Get client area rectangle when the parent is given - */ - CRect GetSize(const CRect& parent) const; - - /** - * The value can be set from a string looking like: - * - * "0 0 100% 100%" - * "50%-10 50%-10 50%+10 50%+10" - * - * i.e. First percent modifier, then + or - and the pixel modifier. - * Although you can use just the percent or the pixel modifier. Notice - * though that the percent modifier must always be the first when - * both modifiers are inputted. - * - * @return true if success, otherwise size will remain unchanged. - */ - bool FromString(const CStr& Value); - - bool operator==(const CGUISize& other) const - { - return pixel == other.pixel && percent == other.percent; - } - - void ToJSVal(JSContext* cx, JS::MutableHandleValue ret) const; - bool FromJSVal(JSContext* cx, JS::HandleValue v); -}; - - -ERROR_GROUP(GUI); - -ERROR_TYPE(GUI, InvalidSetting); -ERROR_TYPE(GUI, OperationNeedsGUIObject); -ERROR_TYPE(GUI, NameAmbiguity); -ERROR_TYPE(GUI, ObjectNeedsName); - -#endif // INCLUDED_CGUISIZE Index: ps/trunk/source/gui/CGUISize.cpp =================================================================== --- ps/trunk/source/gui/CGUISize.cpp +++ ps/trunk/source/gui/CGUISize.cpp @@ -1,232 +0,0 @@ -/* 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 "CGUISize.h" - -#include "gui/scripting/JSInterface_GUISize.h" -#include "ps/CLogger.h" - -CGUISize::CGUISize() - : pixel(), percent() -{ -} - -CGUISize::CGUISize(const CRect& pixel, const CRect& percent) - : pixel(pixel), percent(percent) -{ -} - -CGUISize CGUISize::Full() -{ - return CGUISize(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100)); -} - -CRect CGUISize::GetSize(const CRect& parent) const -{ - // If it's a 0 0 100% 100% we need no calculations - if (percent == CRect(0.f, 0.f, 100.f, 100.f) && pixel == CRect()) - return parent; - - CRect client; - - // This should probably be cached and not calculated all the time for every object. - client.left = parent.left + (parent.right-parent.left)*percent.left/100.f + pixel.left; - client.top = parent.top + (parent.bottom-parent.top)*percent.top/100.f + pixel.top; - client.right = parent.left + (parent.right-parent.left)*percent.right/100.f + pixel.right; - client.bottom = parent.top + (parent.bottom-parent.top)*percent.bottom/100.f + pixel.bottom; - - return client; -} - -bool CGUISize::FromString(const CStr& Value) -{ - /* - * GUISizes contain a left, top, right, and bottom - * for example: "50%-150 10%+9 50%+150 10%+25" means - * the left edge is at 50% minus 150 pixels, the top - * edge is at 10% plus 9 pixels, the right edge is at - * 50% plus 150 pixels, and the bottom edge is at 10% - * plus 25 pixels. - * All four coordinates are required and can be - * defined only in pixels, only in percents, or some - * combination of both. - */ - - // Check the input is only numeric - const char* input = Value.c_str(); - CStr buffer = ""; - unsigned int coord = 0; - float pixels[4] = {0, 0, 0, 0}; - float percents[4] = {0, 0, 0, 0}; - for (unsigned int i = 0; i < Value.length(); ++i) - { - switch (input[i]) - { - case '.': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - buffer.push_back(input[i]); - break; - case '+': - pixels[coord] += buffer.ToFloat(); - buffer = "+"; - break; - case '-': - pixels[coord] += buffer.ToFloat(); - buffer = "-"; - break; - case '%': - percents[coord] += buffer.ToFloat(); - buffer = ""; - break; - case ' ': - pixels[coord] += buffer.ToFloat(); - buffer = ""; - ++coord; - break; - default: - LOGERROR("CGUISize definition may only include numbers. Your input: '%s'", Value.c_str()); - return false; - } - if (coord > 3) - { - LOGERROR("Too many CGUISize parameters (4 max). Your input: '%s'", Value.c_str()); - return false; - } - } - - if (coord < 3) - { - LOGERROR("Too few CGUISize parameters (4 min). Your input: '%s'", Value.c_str()); - return false; - } - - // Now that we're at the end of the string, flush the remaining buffer. - pixels[coord] += buffer.ToFloat(); - - // Now store the coords in the right place - pixel.left = pixels[0]; - pixel.top = pixels[1]; - pixel.right = pixels[2]; - pixel.bottom = pixels[3]; - percent.left = percents[0]; - percent.top = percents[1]; - percent.right = percents[2]; - percent.bottom = percents[3]; - return true; -} - -void CGUISize::ToJSVal(JSContext* cx, JS::MutableHandleValue ret) const -{ - JSAutoRequest rq(cx); - ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - ret.setObjectOrNull(pScriptInterface->CreateCustomObject("GUISize")); - - if (!ret.isObject()) - { - JS_ReportError(cx, "CGUISize value is not an Object"); - return; - } - - JS::RootedObject obj(cx, &ret.toObject()); - if (!JS_InstanceOf(cx, obj, &JSI_GUISize::JSI_class, nullptr)) - { - JS_ReportError(cx, "CGUISize value is not a CGUISize class instance"); - return; - } - -#define P(x, y, z)\ - if (!pScriptInterface->SetProperty(ret, #z, x.y)) \ - { \ - JS_ReportError(cx, "Could not SetProperty '%s'", #z); \ - return; \ - } - P(pixel, left, left); - P(pixel, top, top); - P(pixel, right, right); - P(pixel, bottom, bottom); - P(percent, left, rleft); - P(percent, top, rtop); - P(percent, right, rright); - P(percent, bottom, rbottom); -#undef P -} - -bool CGUISize::FromJSVal(JSContext* cx, JS::HandleValue v) -{ - JSAutoRequest rq(cx); - ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - - if (v.isString()) - { - CStrW str; - if (!ScriptInterface::FromJSVal(cx, v, str)) - { - JS_ReportError(cx, "CGUISize could not read JS string"); - return false; - } - - if (!FromString(str.ToUTF8())) - { - JS_ReportError(cx, "CGUISize could not parse JS string"); - return false; - } - return true; - } - - if (!v.isObject()) - { - JS_ReportError(cx, "CGUISize value is not an String, nor Object"); - return false; - } - - JS::RootedObject obj(cx, &v.toObject()); - if (!JS_InstanceOf(cx, obj, &JSI_GUISize::JSI_class, nullptr)) - { - JS_ReportError(cx, "CGUISize value is not a CGUISize class instance"); - return false; - } - -#define P(x, y, z) \ - if (!pScriptInterface->GetProperty(v, #z, x.y))\ - {\ - JS_ReportError(cx, "CGUISize could not get object property '%s'", #z);\ - return false;\ - } - - P(pixel, left, left); - P(pixel, top, top); - P(pixel, right, right); - P(pixel, bottom, bottom); - P(percent, left, rleft); - P(percent, top, rtop); - P(percent, right, rright); - P(percent, bottom, rbottom); -#undef P - - return true; -} Index: ps/trunk/source/gui/CGUISprite.h =================================================================== --- ps/trunk/source/gui/CGUISprite.h +++ ps/trunk/source/gui/CGUISprite.h @@ -23,8 +23,8 @@ #ifndef INCLUDED_CGUISPRITE #define INCLUDED_CGUISPRITE -#include "gui/CGUISize.h" #include "gui/GUIRenderer.h" +#include "gui/SettingTypes/CGUISize.h" #include #include Index: ps/trunk/source/gui/CGUIString.h =================================================================== --- ps/trunk/source/gui/CGUIString.h +++ ps/trunk/source/gui/CGUIString.h @@ -1,219 +0,0 @@ -/* 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_CGUISTRING -#define INCLUDED_CGUISTRING - -#include "gui/CGUIText.h" -#include "ps/CStrIntern.h" - -#include -#include -#include - -class CGUI; - -/** - * String class, substitute for CStr, but that parses - * the tags and builds up a list of all text that will - * be different when outputted. - * - * The difference between CGUIString and CGUIText is that - * CGUIString is a string-class that parses the tags - * when the value is set. The CGUIText is just a container - * which stores the positions and settings of all text-calls - * that will have to be made to the Renderer. - */ -class CGUIString -{ -public: - /** - * A chunk of text that represents one call to the renderer. - * In other words, all text in one chunk, will be drawn - * exactly with the same settings. - */ - struct TextChunk - { - /** - * A tag looks like this "Hello [b]there[/b] little" - */ - struct Tag - { - /** - * Tag Type - */ - enum TagType - { - TAG_B, - TAG_I, - TAG_FONT, - TAG_SIZE, - TAG_COLOR, - TAG_IMGLEFT, - TAG_IMGRIGHT, - TAG_ICON, - TAG_INVALID - }; - - struct TagAttribute - { - std::wstring attrib; - std::wstring value; - }; - - /** - * Set tag from string - * - * @param tagtype TagType by string, like 'img' for [img] - * @return True if m_TagType was set. - */ - bool SetTagType(const CStrW& tagtype); - TagType GetTagType(const CStrW& tagtype) const; - - - /** - * In [b="Hello"][/b] - * m_TagType is TAG_B - */ - TagType m_TagType; - - /** - * In [b="Hello"][/b] - * m_TagValue is 'Hello' - */ - std::wstring m_TagValue; - - /** - * Some tags need an additional attributes - */ - std::vector m_TagAttributes; - }; - - /** - * m_From and m_To is the range of the string - */ - int m_From, m_To; - - /** - * Tags that are present. [a][b] - */ - std::vector m_Tags; - }; - - /** - * All data generated in GenerateTextCall() - */ - struct SFeedback - { - // Avoid copying the vector and list containers. - NONCOPYABLE(SFeedback); - MOVABLE(SFeedback); - SFeedback() = default; - - // Constants - static const int Left = 0; - static const int Right = 1; - - /** - * Reset all member data. - */ - void Reset(); - - /** - * Image stacks, for left and right floating images. - */ - std::array, 2> m_Images; // left and right - - /** - * Text and Sprite Calls. - */ - std::vector m_TextCalls; - - // list for consistent mem addresses so that we can point to elements. - std::list m_SpriteCalls; - - /** - * Width and Height *feedback* - */ - CSize m_Size; - - /** - * If the word inputted was a new line. - */ - bool m_NewLine; - }; - - /** - * Set the value, the string will automatically - * be parsed when set. - */ - void SetValue(const CStrW& str); - - /** - * Get String, with tags - */ - const CStrW& GetOriginalString() const { return m_OriginalString; } - - /** - * Get String, stripped of tags - */ - const CStrW& GetRawString() const { return m_RawString; } - - /** - * Generate Text Call from specified range. The range - * must span only within ONE TextChunk though. Otherwise - * it can't be fit into a single Text Call - * - * Notice it won't make it complete, you will have to add - * X/Y values and such. - * - * @param pGUI Pointer to CGUI object making this call, for e.g. icon retrieval. - * @param Feedback contains all info that is generated. - * @param DefaultFont Default Font - * @param from From character n, - * @param to to character n. - * @param FirstLine Whether this is the first line of text, to calculate its height correctly - * @param pObject Only for Error outputting, optional! If nullptr - * then no Errors will be reported! Useful when you need - * to make several GenerateTextCall in different phases, - * it avoids duplicates. - */ - void GenerateTextCall(const CGUI& pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject = nullptr) const; - - /** - * Words - */ - std::vector m_Words; - -private: - /** - * TextChunks - */ - std::vector m_TextChunks; - - /** - * The full raw string. Stripped of tags. - */ - CStrW m_RawString; - - /** - * The original string value passed to SetValue. - */ - CStrW m_OriginalString; -}; - -#endif // INCLUDED_CGUISTRING Index: ps/trunk/source/gui/CGUIString.cpp =================================================================== --- ps/trunk/source/gui/CGUIString.cpp +++ ps/trunk/source/gui/CGUIString.cpp @@ -1,474 +0,0 @@ -/* 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 "CGUIString.h" - -#include "graphics/FontMetrics.h" -#include "gui/CGUI.h" -#include "lib/utf8.h" -#include "ps/CLogger.h" - -#include -#include - -// List of word delimiter bounds -// The list contains ranges of word delimiters. The odd indexed chars are the start -// of a range, the even are the end of a range. The list must be sorted in INCREASING ORDER -static const int NUM_WORD_DELIMITERS = 4*2; -static const u16 WordDelimiters[NUM_WORD_DELIMITERS] = { - ' ' , ' ', // spaces - '-' , '-', // hyphens - 0x3000, 0x31FF, // ideographic symbols - 0x3400, 0x9FFF -// TODO add unicode blocks of other languages that don't use spaces -}; - -void CGUIString::SFeedback::Reset() -{ - m_Images[Left].clear(); - m_Images[Right].clear(); - m_TextCalls.clear(); - m_SpriteCalls.clear(); - m_Size = CSize(); - m_NewLine = false; -} - -void CGUIString::GenerateTextCall(const CGUI& pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject) const -{ - // Reset width and height, because they will be determined with incrementation - // or comparisons. - Feedback.Reset(); - - // Check out which text chunk this is within. - for (const TextChunk& textChunk : m_TextChunks) - { - // Get the area that is overlapped by both the TextChunk and - // by the from/to inputted. - int _from = std::max(from, textChunk.m_From); - int _to = std::min(to, textChunk.m_To); - - // If from is larger than to, then they are not overlapping - if (_to == _from && textChunk.m_From == textChunk.m_To) - { - // These should never be able to have more than one tag. - ENSURE(textChunk.m_Tags.size() == 1); - - // Icons and images are placed on exactly one position - // in the words-list, and they can be counted twice if placed - // on an edge. But there is always only one logical preference - // that we want. This check filters the unwanted. - - // it's in the end of one word, and the icon - // should really belong to the beginning of the next one - if (_to == to && to >= 1 && to < (int)m_RawString.length()) - { - if (m_RawString[to-1] == ' ' || - m_RawString[to-1] == '-' || - m_RawString[to-1] == '\n') - continue; - } - // This std::string is just a break - if (_from == from && from >= 1) - { - if (m_RawString[from] == '\n' && - m_RawString[from-1] != '\n' && - m_RawString[from-1] != ' ' && - m_RawString[from-1] != '-') - continue; - } - - const TextChunk::Tag& tag = textChunk.m_Tags[0]; - ENSURE(tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT || - tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT || - tag.m_TagType == TextChunk::Tag::TAG_ICON); - - const std::string& path = utf8_from_wstring(tag.m_TagValue); - if (!pGUI.HasIcon(path)) - { - if (pObject) - LOGERROR("Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%s\").", path.c_str()); - continue; - } - - switch (tag.m_TagType) - { - case TextChunk::Tag::TAG_IMGLEFT: - Feedback.m_Images[SFeedback::Left].push_back(path); - break; - case TextChunk::Tag::TAG_IMGRIGHT: - Feedback.m_Images[SFeedback::Right].push_back(path); - break; - case TextChunk::Tag::TAG_ICON: - { - // We'll need to setup a text-call that will point - // to the icon, this is to be able to iterate - // through the text-calls without having to - // complex the structure virtually for nothing more. - CGUIText::STextCall TextCall; - - // Also add it to the sprites being rendered. - CGUIText::SSpriteCall SpriteCall; - - // Get Icon from icon database in pGUI - const SGUIIcon& icon = pGUI.GetIcon(path); - - const CSize& size = icon.m_Size; - - // append width, and make maximum height the height. - Feedback.m_Size.cx += size.cx; - Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); - - // These are also needed later - TextCall.m_Size = size; - SpriteCall.m_Area = size; - - // Handle additional attributes - for (const TextChunk::Tag::TagAttribute& tagAttrib : tag.m_TagAttributes) - { - if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty()) - { - // Displace the sprite - CSize displacement; - // Parse the value - if (!CGUI::ParseString(&pGUI, tagAttrib.value, displacement)) - LOGERROR("Error parsing 'displace' value for tag [ICON]"); - else - SpriteCall.m_Area += displacement; - } - else if (tagAttrib.attrib == L"tooltip") - SpriteCall.m_Tooltip = tagAttrib.value; - else if (tagAttrib.attrib == L"tooltip_style") - SpriteCall.m_TooltipStyle = tagAttrib.value; - } - - SpriteCall.m_Sprite = icon.m_SpriteName; - SpriteCall.m_CellID = icon.m_CellID; - - // Add sprite call - Feedback.m_SpriteCalls.push_back(std::move(SpriteCall)); - - // Finalize text call - TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back(); - - // Add text call - Feedback.m_TextCalls.emplace_back(std::move(TextCall)); - - break; - } - NODEFAULT; - } - } - else if (_to > _from && !Feedback.m_NewLine) - { - CGUIText::STextCall TextCall; - - // Set defaults - TextCall.m_Font = DefaultFont; - TextCall.m_UseCustomColor = false; - - TextCall.m_String = m_RawString.substr(_from, _to-_from); - - // Go through tags and apply changes. - for (const TextChunk::Tag& tag : textChunk.m_Tags) - { - switch (tag.m_TagType) - { - case TextChunk::Tag::TAG_COLOR: - TextCall.m_UseCustomColor = true; - - if (!CGUI::ParseString(&pGUI, tag.m_TagValue, TextCall.m_Color) && pObject) - LOGERROR("Error parsing the value of a [color]-tag in GUI text when reading object \"%s\".", pObject->GetPresentableName().c_str()); - break; - case TextChunk::Tag::TAG_FONT: - // TODO Gee: (2004-08-15) Check if Font exists? - TextCall.m_Font = CStrIntern(utf8_from_wstring(tag.m_TagValue)); - break; - default: - LOGERROR("Encountered unexpected tag applied to text"); - break; - } - } - - // Calculate the size of the font - CSize size; - int cx, cy; - CFontMetrics font (TextCall.m_Font); - font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy); - // For anything other than the first line, the line spacing - // needs to be considered rather than just the height of the text - if (!FirstLine) - cy = font.GetLineSpacing(); - - size.cx = (float)cx; - size.cy = (float)cy; - - // Append width, and make maximum height the height. - Feedback.m_Size.cx += size.cx; - Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); - - // These are also needed later - TextCall.m_Size = size; - - if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n') - Feedback.m_NewLine = true; - - // Add text-chunk - Feedback.m_TextCalls.emplace_back(std::move(TextCall)); - } - } -} - -bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype) -{ - TagType t = GetTagType(tagtype); - if (t == TAG_INVALID) - return false; - - m_TagType = t; - return true; -} - -CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype) const -{ - if (tagtype == L"color") - return TAG_COLOR; - if (tagtype == L"font") - return TAG_FONT; - if (tagtype == L"icon") - return TAG_ICON; - if (tagtype == L"imgleft") - return TAG_IMGLEFT; - if (tagtype == L"imgright") - return TAG_IMGRIGHT; - - return TAG_INVALID; -} - -void CGUIString::SetValue(const CStrW& str) -{ - m_OriginalString = str; - - m_TextChunks.clear(); - m_Words.clear(); - m_RawString.clear(); - - // Current Text Chunk - CGUIString::TextChunk CurrentTextChunk; - CurrentTextChunk.m_From = 0; - - int l = str.length(); - int rawpos = 0; - CStrW tag; - std::vector tags; - bool closing = false; - for (int p = 0; p < l; ++p) - { - TextChunk::Tag tag_; - switch (str[p]) - { - case L'[': - CurrentTextChunk.m_To = rawpos; - // Add the current chunks if it is not empty - if (CurrentTextChunk.m_From != rawpos) - m_TextChunks.push_back(CurrentTextChunk); - CurrentTextChunk.m_From = rawpos; - - closing = false; - if (++p == l) - { - LOGERROR("Partial tag at end of string '%s'", utf8_from_wstring(str)); - break; - } - if (str[p] == L'/') - { - closing = true; - if (tags.empty()) - { - LOGERROR("Encountered closing tag without having any open tags. At %d in '%s'", p, utf8_from_wstring(str)); - break; - } - if (++p == l) - { - LOGERROR("Partial closing tag at end of string '%s'", utf8_from_wstring(str)); - break; - } - } - tag.clear(); - // Parse tag - for (; p < l && str[p] != L']'; ++p) - { - CStrW name, param; - switch (str[p]) - { - case L' ': - if (closing) // We still parse them to make error handling cleaner - LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str)); - - // parse something="something else" - for (++p; p < l && str[p] != L'='; ++p) - name.push_back(str[p]); - - if (p == l) - { - LOGERROR("Parameter without value at pos %d '%s'", p, utf8_from_wstring(str)); - break; - } - FALLTHROUGH; - case L'=': - // parse a quoted parameter - if (closing) // We still parse them to make error handling cleaner - LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str)); - - if (++p == l) - { - LOGERROR("Expected parameter, got end of string '%s'", utf8_from_wstring(str)); - break; - } - if (str[p] != L'"') - { - LOGERROR("Unquoted parameters are not supported (at pos %d '%s')", p, utf8_from_wstring(str)); - break; - } - for (++p; p < l && str[p] != L'"'; ++p) - { - switch (str[p]) - { - case L'\\': - if (++p == l) - { - LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str)); - break; - } - // NOTE: We do not support \n in tag parameters - FALLTHROUGH; - default: - param.push_back(str[p]); - } - } - - if (!name.empty()) - { - TextChunk::Tag::TagAttribute a = {name, param}; - tag_.m_TagAttributes.push_back(a); - } - else - tag_.m_TagValue = param; - break; - default: - tag.push_back(str[p]); - break; - } - } - - if (!tag_.SetTagType(tag)) - { - LOGERROR("Invalid tag '%s' at %d in '%s'", utf8_from_wstring(tag), p, utf8_from_wstring(str)); - break; - } - if (!closing) - { - if (tag_.m_TagType == TextChunk::Tag::TAG_IMGRIGHT - || tag_.m_TagType == TextChunk::Tag::TAG_IMGLEFT - || tag_.m_TagType == TextChunk::Tag::TAG_ICON) - { - TextChunk FreshTextChunk = { rawpos, rawpos }; - FreshTextChunk.m_Tags.push_back(tag_); - m_TextChunks.push_back(FreshTextChunk); - } - else - { - tags.push_back(tag); - CurrentTextChunk.m_Tags.push_back(tag_); - } - } - else - { - if (tag != tags.back()) - { - LOGERROR("Closing tag '%s' does not match last opened tag '%s' at %d in '%s'", utf8_from_wstring(tag), utf8_from_wstring(tags.back()), p, utf8_from_wstring(str)); - break; - } - - tags.pop_back(); - CurrentTextChunk.m_Tags.pop_back(); - } - break; - case L'\\': - if (++p == l) - { - LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str)); - break; - } - if (str[p] == L'n') - { - ++rawpos; - m_RawString.push_back(L'\n'); - break; - } - FALLTHROUGH; - default: - ++rawpos; - m_RawString.push_back(str[p]); - break; - } - } - - // Add the chunk after the last tag - if (CurrentTextChunk.m_From != rawpos) - { - CurrentTextChunk.m_To = rawpos; - m_TextChunks.push_back(CurrentTextChunk); - } - - - // Add a delimiter at start and at end, it helps when - // processing later, because we don't have make exceptions for - // those cases. - m_Words.push_back(0); - - // Add word boundaries in increasing order - for (u32 i = 0; i < m_RawString.length(); ++i) - { - wchar_t c = m_RawString[i]; - if (c == '\n') - { - m_Words.push_back((int)i); - m_Words.push_back((int)i+1); - continue; - } - for (int n = 0; n < NUM_WORD_DELIMITERS; n += 2) - { - if (c <= WordDelimiters[n+1]) - { - if (c >= WordDelimiters[n]) - m_Words.push_back((int)i+1); - // assume the WordDelimiters list is stored in increasing order - break; - } - } - } - - m_Words.push_back((int)m_RawString.length()); - - // Remove duplicates (only if larger than 2) - if (m_Words.size() <= 2) - return; - - m_Words.erase(std::unique(m_Words.begin(), m_Words.end()), m_Words.end()); -} Index: ps/trunk/source/gui/CGUIText.h =================================================================== --- ps/trunk/source/gui/CGUIText.h +++ ps/trunk/source/gui/CGUIText.h @@ -18,9 +18,9 @@ #ifndef INCLUDED_GUITEXT #define INCLUDED_GUITEXT -#include "gui/CGUIColor.h" #include "gui/CGUISprite.h" -#include "gui/EAlign.h" +#include "gui/SettingTypes/CGUIColor.h" +#include "gui/SettingTypes/EAlign.h" #include "ps/CStrIntern.h" #include "ps/Shapes.h" Index: ps/trunk/source/gui/CGUIText.cpp =================================================================== --- ps/trunk/source/gui/CGUIText.cpp +++ ps/trunk/source/gui/CGUIText.cpp @@ -23,8 +23,8 @@ #include "graphics/ShaderManager.h" #include "graphics/TextRenderer.h" #include "gui/CGUI.h" -#include "gui/CGUIString.h" -#include "gui/IGUIObject.h" +#include "gui/ObjectBases/IGUIObject.h" +#include "gui/SettingTypes/CGUIString.h" #include "renderer/Renderer.h" #include Index: ps/trunk/source/gui/CImage.h =================================================================== --- ps/trunk/source/gui/CImage.h +++ ps/trunk/source/gui/CImage.h @@ -1,53 +0,0 @@ -/* 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_CIMAGE -#define INCLUDED_CIMAGE - -#include "gui/CGUISprite.h" -#include "gui/IGUIObject.h" - -/** - * Object just for drawing a sprite. Like CText, without the - * possibility to draw text. - * - * Created, because I've seen the user being indecisive about - * what control to use in these situations. I've seen button - * without functionality used, and that is a lot of unnecessary - * overhead. That's why I thought I'd go with an intuitive - * control. - */ -class CImage : public IGUIObject -{ - GUI_OBJECT(CImage) - -public: - CImage(CGUI& pGUI); - virtual ~CImage(); - -protected: - /** - * Draws the Image - */ - virtual void Draw(); - - // Settings - CGUISpriteInstance m_Sprite; - i32 m_CellID; -}; - -#endif // INCLUDED_CIMAGE Index: ps/trunk/source/gui/CImage.cpp =================================================================== --- ps/trunk/source/gui/CImage.cpp +++ ps/trunk/source/gui/CImage.cpp @@ -1,40 +0,0 @@ -/* 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 "CImage.h" - -#include "gui/CGUI.h" - -CImage::CImage(CGUI& pGUI) - : IGUIObject(pGUI), - m_Sprite(), - m_CellID() -{ - RegisterSetting("sprite", m_Sprite); - RegisterSetting("cell_id", m_CellID); -} - -CImage::~CImage() -{ -} - -void CImage::Draw() -{ - m_pGUI.DrawSprite(m_Sprite, m_CellID, GetBufferedZ(), m_CachedActualSize); -} Index: ps/trunk/source/gui/CInput.h =================================================================== --- ps/trunk/source/gui/CInput.h +++ ps/trunk/source/gui/CInput.h @@ -1,212 +0,0 @@ -/* 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_CINPUT -#define INCLUDED_CINPUT - -#include "gui/IGUIScrollBarOwner.h" -#include "gui/CGUISprite.h" -#include "lib/external_libraries/libsdl.h" - -#include - -/** - * Text field where you can input and edit the text. - * - * It doesn't use IGUITextOwner, because we don't need - * any other features than word-wrapping, and we need to be - * able to rapidly change the string. - */ -class CInput : public IGUIObject, public IGUIScrollBarOwner -{ - GUI_OBJECT(CInput) - -protected: // forwards - struct SRow; - -public: - CInput(CGUI& pGUI); - virtual ~CInput(); - - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates(); - - // Check where the mouse is hovering, and get the appropriate text position. - // return is the text-position index. - int GetMouseHoveringTextPosition() const; - - // Same as above, but only on one row in X, and a given value, not the mouse's. - // wanted is filled with x if the row didn't extend as far as the mouse pos. - int GetXTextPosition(const std::list::const_iterator& c, const float& x, float& wanted) const; - -protected: - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Handle events manually to catch keyboard inputting. - */ - virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); - - /** - * Handle events manually to catch keys which change the text. - */ - virtual void ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode); - - /** - * Handle events manually to catch keys which don't change the text. - */ - virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode); - - /** - * Handle hotkey events (called by ManuallyHandleEvent) - */ - virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev); - - /** - * @see IGUIObject#UpdateCachedSize() - */ - virtual void UpdateCachedSize(); - - /** - * Draws the Text - */ - virtual void Draw(); - - /** - * Calculate m_CharacterPosition - * the main task for this function is to perfom word-wrapping - * You input from which character it has been changed, because - * if we add a character to the very last end, we don't want - * process everything all over again! Also notice you can - * specify a 'to' also, it will only be used though if a '\n' - * appears, because then the word-wrapping won't change after - * that. - */ - void UpdateText(int from = 0, int to_before = -1, int to_after = -1); - - /** - * Delete the current selection. Also places the pointer at the - * crack between the two segments kept. - */ - void DeleteCurSelection(); - - /** - * Is text selected? It can be denote two ways, m_iBufferPos_Tail - * being -1 or the same as m_iBufferPos. This makes for clearer - * code. - */ - bool SelectingText() const; - - /// Get area of where text can be drawn. - float GetTextAreaWidth(); - - /// Called every time the auto-scrolling should be checked. - void UpdateAutoScroll(); - - /// Clear composed IME input when supported (SDL2 only). - void ClearComposedText(); - - /// Updates the buffer (cursor) position exposed to JS. - void UpdateBufferPositionSetting(); -protected: - /// Cursor position - int m_iBufferPos; - /// Cursor position we started to select from. (-1 if not selecting) - /// (NB: Can be larger than m_iBufferPos if selecting from back to front.) - int m_iBufferPos_Tail; - - /// If we're composing text with an IME - bool m_ComposingText; - /// The length and position of the current IME composition - int m_iComposedLength, m_iComposedPos; - /// The position to insert committed text - int m_iInsertPos; - - // the outer vector is lines, and the inner is X positions - // in a row. So that we can determine where characters are - // placed. It's important because we need to know where the - // pointer should be placed when the input control is pressed. - struct SRow - { - // Where the Row starts - int m_ListStart; - - // List of X values for each character. - std::vector m_ListOfX; - }; - - /** - * List of rows to ease changing its size, so iterators stay valid. - * For one-liners only one row is used. - */ - std::list m_CharacterPositions; - - // *** Things for a multi-lined input control *** // - - /** - * When you change row with up/down, and the row you jump to does - * not have anything at that X position, then it will keep the - * m_WantedX position in mind when switching to the next row. - * It will keep on being used until it reach a row which meets the - * requirements. - * 0.0f means not in use. - */ - float m_WantedX; - - /** - * If we are in the process of selecting a larger selection of text - * using the mouse click (hold) and drag, this is true. - */ - bool m_SelectingText; - - // *** Things for one-line input control *** // - float m_HorizontalScroll; - - /// Used to store the previous time for flashing the cursor. - double m_PrevTime; - - /// Cursor blink rate in seconds, if greater than 0.0. - double m_CursorBlinkRate; - - /// If the cursor should be drawn or not. - bool m_CursorVisState; - - // Settings - i32 m_BufferPosition; - float m_BufferZone; - CStrW m_Caption; - i32 m_CellID; - CStrW m_Font; - CStrW m_MaskChar; - bool m_Mask; - i32 m_MaxLength; - bool m_MultiLine; - bool m_Readonly; - bool m_ScrollBar; - CStr m_ScrollBarStyle; - CGUISpriteInstance m_Sprite; - CGUISpriteInstance m_SpriteSelectArea; - CGUIColor m_TextColor; - CGUIColor m_TextColorSelected; -}; - -#endif // INCLUDED_CINPUT Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp +++ ps/trunk/source/gui/CInput.cpp @@ -1,2051 +0,0 @@ -/* 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 "CInput.h" - -#include "gui/CGUI.h" -#include "gui/CGUIScrollBarVertical.h" -#include "graphics/FontMetrics.h" -#include "graphics/ShaderManager.h" -#include "graphics/TextRenderer.h" -#include "lib/sysdep/clipboard.h" -#include "lib/timer.h" -#include "lib/utf8.h" -#include "ps/ConfigDB.h" -#include "ps/GameSetup/Config.h" -#include "ps/Globals.h" -#include "ps/Hotkey.h" -#include "renderer/Renderer.h" - -#include - -extern int g_yres; - -CInput::CInput(CGUI& pGUI) - : - IGUIObject(pGUI), - IGUIScrollBarOwner(*static_cast(this)), - m_iBufferPos(-1), - m_iBufferPos_Tail(-1), - m_SelectingText(), - m_HorizontalScroll(), - m_PrevTime(), - m_CursorVisState(true), - m_CursorBlinkRate(0.5), - m_ComposingText(), - m_iComposedLength(), - m_iComposedPos(), - m_iInsertPos(), - m_BufferPosition(), - m_BufferZone(), - m_Caption(), - m_CellID(), - m_Font(), - m_MaskChar(), - m_Mask(), - m_MaxLength(), - m_MultiLine(), - m_Readonly(), - m_ScrollBar(), - m_ScrollBarStyle(), - m_Sprite(), - m_SpriteSelectArea(), - m_TextColor(), - m_TextColorSelected() -{ - RegisterSetting("buffer_position", m_BufferPosition); - RegisterSetting("buffer_zone", m_BufferZone); - RegisterSetting("caption", m_Caption); - RegisterSetting("cell_id", m_CellID); - RegisterSetting("font", m_Font); - RegisterSetting("mask_char", m_MaskChar); - RegisterSetting("mask", m_Mask); - RegisterSetting("max_length", m_MaxLength); - RegisterSetting("multiline", m_MultiLine); - RegisterSetting("readonly", m_Readonly); - RegisterSetting("scrollbar", m_ScrollBar); - RegisterSetting("scrollbar_style", m_ScrollBarStyle); - RegisterSetting("sprite", m_Sprite); - RegisterSetting("sprite_selectarea", m_SpriteSelectArea); - RegisterSetting("textcolor", m_TextColor); - RegisterSetting("textcolor_selected", m_TextColorSelected); - - CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate); - - CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); - bar->SetRightAligned(true); - AddScrollBar(bar); -} - -CInput::~CInput() -{ -} - -void CInput::UpdateBufferPositionSetting() -{ - SetSetting("buffer_position", m_iBufferPos, false); -} - -void CInput::ClearComposedText() -{ - m_Caption.erase(m_iInsertPos, m_iComposedLength); - m_iBufferPos = m_iInsertPos; - UpdateBufferPositionSetting(); - m_iComposedLength = 0; - m_iComposedPos = 0; -} - -InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) -{ - ENSURE(m_iBufferPos != -1); - - switch (ev->ev.type) - { - case SDL_HOTKEYDOWN: - { - if (m_ComposingText) - return IN_HANDLED; - - return ManuallyHandleHotkeyEvent(ev); - } - // SDL2 has a new method of text input that better supports Unicode and CJK - // see https://wiki.libsdl.org/Tutorials/TextInput - case SDL_TEXTINPUT: - { - if (m_Readonly) - return IN_PASS; - - // Text has been committed, either single key presses or through an IME - std::wstring text = wstring_from_utf8(ev->ev.text.text); - - m_WantedX = 0.0f; - - if (SelectingText()) - DeleteCurSelection(); - - if (m_ComposingText) - { - ClearComposedText(); - m_ComposingText = false; - } - - if (m_iBufferPos == static_cast(m_Caption.length())) - m_Caption.append(text); - else - m_Caption.insert(m_iBufferPos, text); - - UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); - - m_iBufferPos += text.length(); - UpdateBufferPositionSetting(); - m_iBufferPos_Tail = -1; - - UpdateAutoScroll(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - - return IN_HANDLED; - } - case SDL_TEXTEDITING: - { - if (m_Readonly) - return IN_PASS; - - // Text is being composed with an IME - // TODO: indicate this by e.g. underlining the uncommitted text - const char* rawText = ev->ev.edit.text; - int rawLength = strlen(rawText); - std::wstring wtext = wstring_from_utf8(rawText); - - debug_printf("SDL_TEXTEDITING: text=%s, start=%d, length=%d\n", rawText, ev->ev.edit.start, ev->ev.edit.length); - m_WantedX = 0.0f; - - if (SelectingText()) - DeleteCurSelection(); - - // Remember cursor position when text composition begins - if (!m_ComposingText) - m_iInsertPos = m_iBufferPos; - else - { - // Composed text is replaced each time - ClearComposedText(); - } - - m_ComposingText = ev->ev.edit.start != 0 || rawLength != 0; - if (m_ComposingText) - { - m_Caption.insert(m_iInsertPos, wtext); - - // The text buffer is limited to SDL_TEXTEDITINGEVENT_TEXT_SIZE bytes, yet start - // increases without limit, so don't let it advance beyond the composed text length - m_iComposedLength = wtext.length(); - m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength; - m_iBufferPos = m_iInsertPos + m_iComposedPos; - - // TODO: composed text selection - what does ev.edit.length do? - m_iBufferPos_Tail = -1; - } - - UpdateBufferPositionSetting(); - UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); - - UpdateAutoScroll(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - - return IN_HANDLED; - } - case SDL_KEYDOWN: - { - if (m_ComposingText) - return IN_HANDLED; - - // Since the GUI framework doesn't handle to set settings - // in Unicode (CStrW), we'll simply retrieve the actual - // pointer and edit that. - SDL_Keycode keyCode = ev->ev.key.keysym.sym; - - ManuallyImmutableHandleKeyDownEvent(keyCode); - ManuallyMutableHandleKeyDownEvent(keyCode); - - UpdateBufferPositionSetting(); - return IN_HANDLED; - } - default: - { - return IN_PASS; - } - } -} - -void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode) -{ - if (m_Readonly) - return; - - wchar_t cooked = 0; - - switch (keyCode) - { - case SDLK_TAB: - { - SendEvent(GUIM_TAB, "tab"); - // Don't send a textedit event, because it should only - // be sent if the GUI control changes the text - break; - } - case SDLK_BACKSPACE: - { - m_WantedX = 0.0f; - - if (SelectingText()) - DeleteCurSelection(); - else - { - m_iBufferPos_Tail = -1; - - if (m_Caption.empty() || m_iBufferPos == 0) - break; - - if (m_iBufferPos == static_cast(m_Caption.length())) - m_Caption = m_Caption.Left(static_cast(m_Caption.length()) - 1); - else - m_Caption = - m_Caption.Left(m_iBufferPos - 1) + - m_Caption.Right(static_cast(m_Caption.length()) - m_iBufferPos); - - --m_iBufferPos; - - UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); - } - - UpdateAutoScroll(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - break; - } - case SDLK_DELETE: - { - m_WantedX = 0.0f; - - if (SelectingText()) - DeleteCurSelection(); - else - { - if (m_Caption.empty() || m_iBufferPos == static_cast(m_Caption.length())) - break; - - m_Caption = - m_Caption.Left(m_iBufferPos) + - m_Caption.Right(static_cast(m_Caption.length()) - (m_iBufferPos + 1)); - - UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); - } - - UpdateAutoScroll(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - break; - } - case SDLK_KP_ENTER: - case SDLK_RETURN: - { - // 'Return' should do a Press event for single liners (e.g. submitting forms) - // otherwise a '\n' character will be added. - if (!m_MultiLine) - { - SendEvent(GUIM_PRESSED, "press"); - break; - } - - cooked = '\n'; // Change to '\n' and do default: - FALLTHROUGH; - } - default: // Insert a character - { - // In SDL2, we no longer get Unicode wchars via SDL_Keysym - // we use text input events instead and they provide UTF-8 chars - if (cooked == 0) - return; - - // check max length - if (m_MaxLength != 0 && static_cast(m_Caption.length()) >= m_MaxLength) - break; - - m_WantedX = 0.0f; - - if (SelectingText()) - DeleteCurSelection(); - m_iBufferPos_Tail = -1; - - if (m_iBufferPos == static_cast(m_Caption.length())) - m_Caption += cooked; - else - m_Caption = - m_Caption.Left(m_iBufferPos) + cooked + - m_Caption.Right(static_cast(m_Caption.length()) - m_iBufferPos); - - UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1); - - ++m_iBufferPos; - - UpdateAutoScroll(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - break; - } - } -} - -void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode) -{ - bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; - - switch (keyCode) - { - case SDLK_HOME: - { - // If there's not a selection, we should create one now - if (!shiftKeyPressed) - { - // Make sure a selection isn't created. - m_iBufferPos_Tail = -1; - } - else if (!SelectingText()) - { - // Place tail at the current point: - m_iBufferPos_Tail = m_iBufferPos; - } - - m_iBufferPos = 0; - m_WantedX = 0.0f; - - UpdateAutoScroll(); - break; - } - case SDLK_END: - { - // If there's not a selection, we should create one now - if (!shiftKeyPressed) - { - // Make sure a selection isn't created. - m_iBufferPos_Tail = -1; - } - else if (!SelectingText()) - { - // Place tail at the current point: - m_iBufferPos_Tail = m_iBufferPos; - } - - m_iBufferPos = static_cast(m_Caption.length()); - m_WantedX = 0.0f; - - UpdateAutoScroll(); - break; - } - /** - * Conventions for Left/Right when text is selected: - * - * References: - * - * Visual Studio - * Visual Studio has the 'newer' approach, used by newer versions of - * things, and in newer applications. A left press will always place - * the pointer on the left edge of the selection, and then of course - * remove the selection. Right will do the exact same thing. - * If you have the pointer on the right edge and press right, it will - * in other words just remove the selection. - * - * Windows (eg. Notepad) - * A left press always takes the pointer a step to the left and - * removes the selection as if it were never there in the first place. - * Right of course does the same thing but to the right. - * - * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN - * Messenger. - */ - case SDLK_LEFT: - { - m_WantedX = 0.f; - - if (shiftKeyPressed || !SelectingText()) - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; - - if (m_iBufferPos > 0) - --m_iBufferPos; - } - else - { - if (m_iBufferPos_Tail < m_iBufferPos) - m_iBufferPos = m_iBufferPos_Tail; - - m_iBufferPos_Tail = -1; - } - - UpdateAutoScroll(); - break; - } - case SDLK_RIGHT: - { - m_WantedX = 0.0f; - - if (shiftKeyPressed || !SelectingText()) - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; - - if (m_iBufferPos < static_cast(m_Caption.length())) - ++m_iBufferPos; - } - else - { - if (m_iBufferPos_Tail > m_iBufferPos) - m_iBufferPos = m_iBufferPos_Tail; - - m_iBufferPos_Tail = -1; - } - - UpdateAutoScroll(); - break; - } - /** - * Conventions for Up/Down when text is selected: - * - * References: - * - * Visual Studio - * Visual Studio has a very strange approach, down takes you below the - * selection to the next row, and up to the one prior to the whole - * selection. The weird part is that it is always aligned as the - * 'pointer'. I decided this is to much work for something that is - * a bit arbitrary - * - * Windows (eg. Notepad) - * Just like with left/right, the selection is destroyed and it moves - * just as if there never were a selection. - * - * I chose the Notepad convention even though I use the VS convention with - * left/right. - */ - case SDLK_UP: - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; - - std::list::iterator current = m_CharacterPositions.begin(); - while (current != m_CharacterPositions.end()) - { - if (m_iBufferPos >= current->m_ListStart && - m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) - break; - - ++current; - } - - float pos_x; - if (m_iBufferPos - current->m_ListStart == 0) - pos_x = 0.f; - else - pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; - - if (m_WantedX > pos_x) - pos_x = m_WantedX; - - // Now change row: - if (current != m_CharacterPositions.begin()) - { - --current; - - // Find X-position: - m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); - } - // else we can't move up - - UpdateAutoScroll(); - break; - } - case SDLK_DOWN: - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; - - std::list::iterator current = m_CharacterPositions.begin(); - while (current != m_CharacterPositions.end()) - { - if (m_iBufferPos >= current->m_ListStart && - m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) - break; - - ++current; - } - - float pos_x; - - if (m_iBufferPos - current->m_ListStart == 0) - pos_x = 0.f; - else - pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; - - if (m_WantedX > pos_x) - pos_x = m_WantedX; - - // Now change row: - // Add first, so we can check if it's .end() - ++current; - if (current != m_CharacterPositions.end()) - { - // Find X-position: - m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); - } - // else we can't move up - - UpdateAutoScroll(); - break; - } - case SDLK_PAGEUP: - { - GetScrollBar(0).ScrollMinusPlenty(); - UpdateAutoScroll(); - break; - } - case SDLK_PAGEDOWN: - { - GetScrollBar(0).ScrollPlusPlenty(); - UpdateAutoScroll(); - break; - } - default: - { - break; - } - } -} - -InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev) -{ - bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; - - std::string hotkey = static_cast(ev->ev.user.data1); - - if (hotkey == "paste") - { - if (m_Readonly) - return IN_PASS; - - m_WantedX = 0.0f; - - wchar_t* text = sys_clipboard_get(); - if (text) - { - if (SelectingText()) - DeleteCurSelection(); - - if (m_iBufferPos == static_cast(m_Caption.length())) - m_Caption += text; - else - m_Caption = - m_Caption.Left(m_iBufferPos) + text + - m_Caption.Right(static_cast(m_Caption.length()) - m_iBufferPos); - - UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); - - m_iBufferPos += (int)wcslen(text); - UpdateAutoScroll(); - UpdateBufferPositionSetting(); - - sys_clipboard_free(text); - - SendEvent(GUIM_TEXTEDIT, "textedit"); - } - - return IN_HANDLED; - } - else if (hotkey == "copy" || hotkey == "cut") - { - if (m_Readonly && hotkey == "cut") - return IN_PASS; - - m_WantedX = 0.0f; - - if (SelectingText()) - { - int virtualFrom; - int virtualTo; - - if (m_iBufferPos_Tail >= m_iBufferPos) - { - virtualFrom = m_iBufferPos; - virtualTo = m_iBufferPos_Tail; - } - else - { - virtualFrom = m_iBufferPos_Tail; - virtualTo = m_iBufferPos; - } - - CStrW text = m_Caption.Left(virtualTo).Right(virtualTo - virtualFrom); - - sys_clipboard_set(&text[0]); - - if (hotkey == "cut") - { - DeleteCurSelection(); - UpdateAutoScroll(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - } - } - - return IN_HANDLED; - } - else if (hotkey == "text.delete.left") - { - if (m_Readonly) - return IN_PASS; - - m_WantedX = 0.0f; - - if (SelectingText()) - DeleteCurSelection(); - - if (!m_Caption.empty() && m_iBufferPos != 0) - { - m_iBufferPos_Tail = m_iBufferPos; - CStrW searchString = m_Caption.Left(m_iBufferPos); - - // If we are starting in whitespace, adjust position until we get a non whitespace - while (m_iBufferPos > 0) - { - if (!iswspace(searchString[m_iBufferPos - 1])) - break; - - m_iBufferPos--; - } - - // If we end up on a punctuation char we just delete it (treat punct like a word) - if (iswpunct(searchString[m_iBufferPos - 1])) - m_iBufferPos--; - else - { - // Now we are on a non white space character, adjust position to char after next whitespace char is found - while (m_iBufferPos > 0) - { - if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) - break; - - m_iBufferPos--; - } - } - - UpdateBufferPositionSetting(); - DeleteCurSelection(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - } - UpdateAutoScroll(); - return IN_HANDLED; - } - else if (hotkey == "text.delete.right") - { - if (m_Readonly) - return IN_PASS; - - m_WantedX = 0.0f; - - if (SelectingText()) - DeleteCurSelection(); - - if (!m_Caption.empty() && m_iBufferPos < static_cast(m_Caption.length())) - { - // Delete the word to the right of the cursor - m_iBufferPos_Tail = m_iBufferPos; - - // Delete chars to the right unit we hit whitespace - while (++m_iBufferPos < static_cast(m_Caption.length())) - { - if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos])) - break; - } - - // Eliminate any whitespace behind the word we just deleted - while (m_iBufferPos < static_cast(m_Caption.length())) - { - if (!iswspace(m_Caption[m_iBufferPos])) - break; - - ++m_iBufferPos; - } - UpdateBufferPositionSetting(); - DeleteCurSelection(); - } - UpdateAutoScroll(); - SendEvent(GUIM_TEXTEDIT, "textedit"); - return IN_HANDLED; - } - else if (hotkey == "text.move.left") - { - m_WantedX = 0.0f; - - if (shiftKeyPressed || !SelectingText()) - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; - - if (!m_Caption.empty() && m_iBufferPos != 0) - { - CStrW searchString = m_Caption.Left(m_iBufferPos); - - // If we are starting in whitespace, adjust position until we get a non whitespace - while (m_iBufferPos > 0) - { - if (!iswspace(searchString[m_iBufferPos - 1])) - break; - - m_iBufferPos--; - } - - // If we end up on a puctuation char we just select it (treat punct like a word) - if (iswpunct(searchString[m_iBufferPos - 1])) - m_iBufferPos--; - else - { - // Now we are on a non white space character, adjust position to char after next whitespace char is found - while (m_iBufferPos > 0) - { - if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) - break; - - m_iBufferPos--; - } - } - } - } - else - { - if (m_iBufferPos_Tail < m_iBufferPos) - m_iBufferPos = m_iBufferPos_Tail; - - m_iBufferPos_Tail = -1; - } - - UpdateBufferPositionSetting(); - UpdateAutoScroll(); - - return IN_HANDLED; - } - else if (hotkey == "text.move.right") - { - m_WantedX = 0.0f; - - if (shiftKeyPressed || !SelectingText()) - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; - - if (!m_Caption.empty() && m_iBufferPos < static_cast(m_Caption.length())) - { - // Select chars to the right until we hit whitespace - while (++m_iBufferPos < static_cast(m_Caption.length())) - { - if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos])) - break; - } - - // Also select any whitespace following the word we just selected - while (m_iBufferPos < static_cast(m_Caption.length())) - { - if (!iswspace(m_Caption[m_iBufferPos])) - break; - - ++m_iBufferPos; - } - } - } - else - { - if (m_iBufferPos_Tail > m_iBufferPos) - m_iBufferPos = m_iBufferPos_Tail; - - m_iBufferPos_Tail = -1; - } - - UpdateBufferPositionSetting(); - UpdateAutoScroll(); - - return IN_HANDLED; - } - - return IN_PASS; -} - -void CInput::ResetStates() -{ - IGUIObject::ResetStates(); - IGUIScrollBarOwner::ResetStates(); -} - -void CInput::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - IGUIScrollBarOwner::HandleMessage(Message); - - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - { - // Update scroll-bar - // TODO Gee: (2004-09-01) Is this really updated each time it should? - if (m_ScrollBar && - (Message.value == "size" || - Message.value == "z" || - Message.value == "absolute")) - { - GetScrollBar(0).SetX(m_CachedActualSize.right); - GetScrollBar(0).SetY(m_CachedActualSize.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); - } - - // Update scrollbar - if (Message.value == "scrollbar_style") - GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); - - if (Message.value == "buffer_position") - { - m_iBufferPos = m_BufferPosition; - m_iBufferPos_Tail = -1; // position change resets selection - } - - if (Message.value == "size" || - Message.value == "z" || - Message.value == "font" || - Message.value == "absolute" || - Message.value == "caption" || - Message.value == "scrollbar" || - Message.value == "scrollbar_style") - { - UpdateText(); - } - - if (Message.value == "multiline") - { - if (!m_MultiLine) - GetScrollBar(0).SetLength(0.f); - else - GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); - - UpdateText(); - } - - UpdateAutoScroll(); - - break; - } - case GUIM_MOUSE_PRESS_LEFT: - { - // Check if we're selecting the scrollbar - if (m_ScrollBar && - m_MultiLine && - GetScrollBar(0).GetStyle()) - { - if (m_pGUI.GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width) - break; - } - - if (m_ComposingText) - break; - - // Okay, this section is about pressing the mouse and - // choosing where the point should be placed. For - // instance, if we press between a and b, the point - // should of course be placed accordingly. Other - // special cases are handled like the input box norms. - if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]) - m_iBufferPos = GetMouseHoveringTextPosition(); - else - m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); - - m_SelectingText = true; - - UpdateAutoScroll(); - - // If we immediately release the button it will just be seen as a click - // for the user though. - break; - } - case GUIM_MOUSE_DBLCLICK_LEFT: - { - if (m_ComposingText) - break; - - if (m_Caption.empty()) - break; - - m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); - - if (m_iBufferPos >= (int)m_Caption.length()) - m_iBufferPos = m_iBufferPos_Tail = m_Caption.length() - 1; - - // See if we are clicking over whitespace - if (iswspace(m_Caption[m_iBufferPos])) - { - // see if we are in a section of whitespace greater than one character - if ((m_iBufferPos + 1 < (int) m_Caption.length() && iswspace(m_Caption[m_iBufferPos + 1])) || - (m_iBufferPos - 1 > 0 && iswspace(m_Caption[m_iBufferPos - 1]))) - { - // - // We are clicking in an area with more than one whitespace character - // so we select both the word to the left and then the word to the right - // - // [1] First the left - // skip the whitespace - while (m_iBufferPos > 0) - { - if (!iswspace(m_Caption[m_iBufferPos - 1])) - break; - - m_iBufferPos--; - } - // now go until we hit white space or punctuation - while (m_iBufferPos > 0) - { - if (iswspace(m_Caption[m_iBufferPos - 1])) - break; - - m_iBufferPos--; - - if (iswpunct(m_Caption[m_iBufferPos])) - break; - } - - // [2] Then the right - // go right until we are not in whitespace - while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) - { - if (!iswspace(m_Caption[m_iBufferPos_Tail])) - break; - } - - if (m_iBufferPos_Tail == static_cast(m_Caption.length())) - break; - - // now go to the right until we hit whitespace or punctuation - while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) - { - if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail])) - break; - } - } - else - { - // single whitespace so select word to the right - while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) - { - if (!iswspace(m_Caption[m_iBufferPos_Tail])) - break; - } - - if (m_iBufferPos_Tail == static_cast(m_Caption.length())) - break; - - // Don't include the leading whitespace - m_iBufferPos = m_iBufferPos_Tail; - - // now go to the right until we hit whitespace or punctuation - while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) - { - if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail])) - break; - } - } - } - else - { - // clicked on non-whitespace so select current word - // go until we hit white space or punctuation - while (m_iBufferPos > 0) - { - if (iswspace(m_Caption[m_iBufferPos - 1])) - break; - - m_iBufferPos--; - - if (iswpunct(m_Caption[m_iBufferPos])) - break; - } - // go to the right until we hit whitespace or punctuation - while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) - if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail])) - break; - } - UpdateAutoScroll(); - break; - } - case GUIM_MOUSE_RELEASE_LEFT: - { - if (m_SelectingText) - m_SelectingText = false; - break; - } - case GUIM_MOUSE_MOTION: - { - // If we just pressed down and started to move before releasing - // this is one way of selecting larger portions of text. - if (m_SelectingText) - { - // Actually, first we need to re-check that the mouse button is - // really pressed (it can be released while outside the control. - if (!g_mouse_buttons[SDL_BUTTON_LEFT]) - m_SelectingText = false; - else - m_iBufferPos = GetMouseHoveringTextPosition(); - UpdateAutoScroll(); - } - break; - } - case GUIM_LOAD: - { - GetScrollBar(0).SetX(m_CachedActualSize.right); - GetScrollBar(0).SetY(m_CachedActualSize.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); - GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); - - UpdateText(); - UpdateAutoScroll(); - - break; - } - case GUIM_GOT_FOCUS: - { - m_iBufferPos = 0; - m_PrevTime = 0.0; - m_CursorVisState = false; - - // Tell the IME where to draw the candidate list - SDL_Rect rect; - rect.h = m_CachedActualSize.GetSize().cy; - rect.w = m_CachedActualSize.GetSize().cx; - rect.x = m_CachedActualSize.TopLeft().x; - rect.y = m_CachedActualSize.TopLeft().y; - SDL_SetTextInputRect(&rect); - SDL_StartTextInput(); - break; - } - case GUIM_LOST_FOCUS: - { - if (m_ComposingText) - { - // Simulate a final text editing event to clear the composition - SDL_Event_ evt; - evt.ev.type = SDL_TEXTEDITING; - evt.ev.edit.length = 0; - evt.ev.edit.start = 0; - evt.ev.edit.text[0] = 0; - ManuallyHandleEvent(&evt); - } - SDL_StopTextInput(); - - m_iBufferPos = -1; - m_iBufferPos_Tail = -1; - break; - } - default: - { - break; - } - } - UpdateBufferPositionSetting(); -} - -void CInput::UpdateCachedSize() -{ - // If an ancestor's size changed, this will let us intercept the change and - // update our scrollbar positions - - IGUIObject::UpdateCachedSize(); - - if (m_ScrollBar) - { - GetScrollBar(0).SetX(m_CachedActualSize.right); - GetScrollBar(0).SetY(m_CachedActualSize.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); - } -} - -void CInput::Draw() -{ - float bz = GetBufferedZ(); - - if (m_CursorBlinkRate > 0.0) - { - // check if the cursor visibility state needs to be changed - double currTime = timer_Time(); - if (currTime - m_PrevTime >= m_CursorBlinkRate) - { - m_CursorVisState = !m_CursorVisState; - m_PrevTime = currTime; - } - } - else - // should always be visible - m_CursorVisState = true; - - // First call draw on ScrollBarOwner - if (m_ScrollBar && m_MultiLine) - IGUIScrollBarOwner::Draw(); - - CStrIntern font_name(m_Font.ToUTF8()); - - wchar_t mask_char = L'*'; - if (m_Mask && m_MaskChar.length() > 0) - mask_char = m_MaskChar[0]; - - m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, m_CachedActualSize); - - float scroll = 0.f; - if (m_ScrollBar && m_MultiLine) - scroll = GetScrollBar(0).GetPos(); - - CFontMetrics font(font_name); - - // We'll have to setup clipping manually, since we're doing the rendering manually. - CRect cliparea(m_CachedActualSize); - - // First we'll figure out the clipping area, which is the cached actual size - // substracted by an optional scrollbar - if (m_ScrollBar) - { - scroll = GetScrollBar(0).GetPos(); - - // substract scrollbar from cliparea - if (cliparea.right > GetScrollBar(0).GetOuterRect().left && - cliparea.right <= GetScrollBar(0).GetOuterRect().right) - cliparea.right = GetScrollBar(0).GetOuterRect().left; - - if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && - cliparea.left < GetScrollBar(0).GetOuterRect().right) - cliparea.left = GetScrollBar(0).GetOuterRect().right; - } - - if (cliparea != CRect()) - { - glEnable(GL_SCISSOR_TEST); - glScissor( - cliparea.left * g_GuiScale, - g_yres - cliparea.bottom * g_GuiScale, - cliparea.GetWidth() * g_GuiScale, - cliparea.GetHeight() * g_GuiScale); - } - - // These are useful later. - int VirtualFrom, VirtualTo; - - if (m_iBufferPos_Tail >= m_iBufferPos) - { - VirtualFrom = m_iBufferPos; - VirtualTo = m_iBufferPos_Tail; - } - else - { - VirtualFrom = m_iBufferPos_Tail; - VirtualTo = m_iBufferPos; - } - - // Get the height of this font. - float h = (float)font.GetHeight(); - float ls = (float)font.GetLineSpacing(); - - CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); - - CTextRenderer textRenderer(tech->GetShader()); - textRenderer.Font(font_name); - - // Set the Z to somewhat more, so we can draw a selected area between the - // the control and the text. - textRenderer.Translate( - (float)(int)(m_CachedActualSize.left) + m_BufferZone, - (float)(int)(m_CachedActualSize.top+h) + m_BufferZone, - bz+0.1f); - - // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE - // (sort of like a | which is aligned to the left of most characters) - - float buffered_y = -scroll + m_BufferZone; - - // When selecting larger areas, we need to draw a rectangle box - // around it, and this is to keep track of where the box - // started, because we need to follow the iteration until we - // reach the end, before we can actually draw it. - bool drawing_box = false; - float box_x = 0.f; - - float x_pointer = 0.f; - - // If we have a selecting box (i.e. when you have selected letters, not just when - // the pointer is between two letters) we need to process all letters once - // before we do it the second time and render all the text. We can't do it - // in the same loop because text will have been drawn, so it will disappear when - // drawn behind the text that has already been drawn. Confusing, well it's necessary - // (I think). - - if (SelectingText()) - { - // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos, - // just like you can select from right to left, as you can - // left to right. Is there a difference? Yes, the pointer - // be placed accordingly, so that if you select shift and - // expand this selection, it will expand on appropriate side. - // Anyway, since the drawing procedure needs "To" to be - // greater than from, we need virtual values that might switch - // place. - - int VirtualFrom, VirtualTo; - - if (m_iBufferPos_Tail >= m_iBufferPos) - { - VirtualFrom = m_iBufferPos; - VirtualTo = m_iBufferPos_Tail; - } - else - { - VirtualFrom = m_iBufferPos_Tail; - VirtualTo = m_iBufferPos; - } - - - bool done = false; - for (std::list::const_iterator it = m_CharacterPositions.begin(); - it != m_CharacterPositions.end(); - ++it, buffered_y += ls, x_pointer = 0.f) - { - if (m_MultiLine && buffered_y > m_CachedActualSize.GetHeight()) - break; - - // We might as well use 'i' here to iterate, because we need it - // (often compared against ints, so don't make it size_t) - for (int i = 0; i < (int)it->m_ListOfX.size()+2; ++i) - { - if (it->m_ListStart + i == VirtualFrom) - { - // we won't actually draw it now, because we don't - // know the width of each glyph to that position. - // we need to go along with the iteration, and - // make a mark where the box started: - drawing_box = true; // will turn false when finally rendered. - - // Get current x position - box_x = x_pointer; - } - - const bool at_end = (i == (int)it->m_ListOfX.size()+1); - - if (drawing_box && (it->m_ListStart + i == VirtualTo || at_end)) - { - // Depending on if it's just a row change, or if it's - // the end of the select box, do slightly different things. - if (at_end) - { - if (it->m_ListStart + i != VirtualFrom) - // and actually add a white space! yes, this is done in any common input - x_pointer += font.GetCharacterWidth(L' '); - } - else - { - drawing_box = false; - done = true; - } - - CRect rect; - // Set 'rect' depending on if it's a multiline control, or a one-line control - if (m_MultiLine) - { - rect = CRect( - m_CachedActualSize.left + box_x + m_BufferZone, - m_CachedActualSize.top + buffered_y + (h - ls) / 2, - m_CachedActualSize.left + x_pointer + m_BufferZone, - m_CachedActualSize.top + buffered_y + (h + ls) / 2); - - if (rect.bottom < m_CachedActualSize.top) - continue; - - if (rect.top < m_CachedActualSize.top) - rect.top = m_CachedActualSize.top; - - if (rect.bottom > m_CachedActualSize.bottom) - rect.bottom = m_CachedActualSize.bottom; - } - else // if one-line - { - rect = CRect( - m_CachedActualSize.left + box_x + m_BufferZone - m_HorizontalScroll, - m_CachedActualSize.top + buffered_y + (h - ls) / 2, - m_CachedActualSize.left + x_pointer + m_BufferZone - m_HorizontalScroll, - m_CachedActualSize.top + buffered_y + (h + ls) / 2); - - if (rect.left < m_CachedActualSize.left) - rect.left = m_CachedActualSize.left; - - if (rect.right > m_CachedActualSize.right) - rect.right = m_CachedActualSize.right; - } - - m_pGUI.DrawSprite(m_SpriteSelectArea, m_CellID, bz + 0.05f, rect); - } - - if (i < (int)it->m_ListOfX.size()) - { - if (!m_Mask) - x_pointer += font.GetCharacterWidth(m_Caption[it->m_ListStart + i]); - else - x_pointer += font.GetCharacterWidth(mask_char); - } - } - - if (done) - break; - - // If we're about to draw a box, and all of a sudden changes - // line, we need to draw that line's box, and then reset - // the box drawing to the beginning of the new line. - if (drawing_box) - box_x = 0.f; - } - } - - // Reset some from previous run - buffered_y = -scroll; - - // Setup initial color (then it might change and change back, when drawing selected area) - textRenderer.Color(m_TextColor); - - tech->BeginPass(); - - bool using_selected_color = false; - - for (std::list::const_iterator it = m_CharacterPositions.begin(); - it != m_CharacterPositions.end(); - ++it, buffered_y += ls) - { - if (buffered_y + m_BufferZone >= -ls || !m_MultiLine) - { - if (m_MultiLine && buffered_y + m_BufferZone > m_CachedActualSize.GetHeight()) - break; - - CMatrix3D savedTransform = textRenderer.GetTransform(); - - // Text must always be drawn in integer values. So we have to convert scroll - if (m_MultiLine) - textRenderer.Translate(0.f, -(float)(int)scroll, 0.f); - else - textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f, 0.f); - - // We might as well use 'i' here, because we need it - // (often compared against ints, so don't make it size_t) - for (int i = 0; i < (int)it->m_ListOfX.size()+1; ++i) - { - if (!m_MultiLine && i < (int)it->m_ListOfX.size()) - { - if (it->m_ListOfX[i] - m_HorizontalScroll < -m_BufferZone) - { - // We still need to translate the OpenGL matrix - if (i == 0) - textRenderer.Translate(it->m_ListOfX[i], 0.f, 0.f); - else - textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f); - - continue; - } - } - - // End of selected area, change back color - if (SelectingText() && it->m_ListStart + i == VirtualTo) - { - using_selected_color = false; - textRenderer.Color(m_TextColor); - } - - // selecting only one, then we need only to draw a cursor. - if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos && m_CursorVisState) - textRenderer.Put(0.0f, 0.0f, L"_"); - - // Drawing selected area - if (SelectingText() && - it->m_ListStart + i >= VirtualFrom && - it->m_ListStart + i < VirtualTo && - !using_selected_color) - { - using_selected_color = true; - textRenderer.Color(m_TextColorSelected); - } - - if (i != (int)it->m_ListOfX.size()) - { - if (!m_Mask) - textRenderer.PrintfAdvance(L"%lc", m_Caption[it->m_ListStart + i]); - else - textRenderer.PrintfAdvance(L"%lc", mask_char); - } - - // check it's now outside a one-liner, then we'll break - if (!m_MultiLine && i < (int)it->m_ListOfX.size() && - it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth() - m_BufferZone) - break; - } - - if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos) - { - textRenderer.Color(m_TextColor); - if (m_CursorVisState) - textRenderer.PutAdvance(L"_"); - - if (using_selected_color) - textRenderer.Color(m_TextColorSelected); - } - - textRenderer.SetTransform(savedTransform); - } - - textRenderer.Translate(0.f, ls, 0.f); - } - - textRenderer.Render(); - - if (cliparea != CRect()) - glDisable(GL_SCISSOR_TEST); - - tech->EndPass(); -} - -void CInput::UpdateText(int from, int to_before, int to_after) -{ - CStrIntern font_name(m_Font.ToUTF8()); - - wchar_t mask_char = L'*'; - if (m_Mask && m_MaskChar.length() > 0) - mask_char = m_MaskChar[0]; - - // Ensure positions are valid after caption changes - m_iBufferPos = std::min(m_iBufferPos, static_cast(m_Caption.size())); - m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, static_cast(m_Caption.size())); - UpdateBufferPositionSetting(); - - if (font_name.empty()) - { - // Destroy everything stored, there's no font, so there can be no data. - m_CharacterPositions.clear(); - return; - } - - SRow row; - row.m_ListStart = 0; - - int to = 0; // make sure it's initialized - - if (to_before == -1) - to = static_cast(m_Caption.length()); - - CFontMetrics font(font_name); - - std::list::iterator current_line; - - // Used to ... TODO - int check_point_row_start = -1; - int check_point_row_end = -1; - - // Reset - if (from == 0 && to_before == -1) - { - m_CharacterPositions.clear(); - current_line = m_CharacterPositions.begin(); - } - else - { - ENSURE(to_before != -1); - - std::list::iterator destroy_row_from; - std::list::iterator destroy_row_to; - // Used to check if the above has been set to anything, - // previously a comparison like: - // destroy_row_from == std::list::iterator() - // ... was used, but it didn't work with GCC. - bool destroy_row_from_used = false; - bool destroy_row_to_used = false; - - // Iterate, and remove everything between 'from' and 'to_before' - // actually remove the entire lines they are on, it'll all have - // to be redone. And when going along, we'll delete a row at a time - // when continuing to see how much more after 'to' we need to remake. - int i = 0; - for (std::list::iterator it = m_CharacterPositions.begin(); - it != m_CharacterPositions.end(); - ++it, ++i) - { - if (!destroy_row_from_used && it->m_ListStart > from) - { - // Destroy the previous line, and all to 'to_before' - destroy_row_from = it; - --destroy_row_from; - - destroy_row_from_used = true; - - // For the rare case that we might remove characters to a word - // so that it suddenly fits on the previous row, - // we need to by standards re-do the whole previous line too - // (if one exists) - if (destroy_row_from != m_CharacterPositions.begin()) - --destroy_row_from; - } - - if (!destroy_row_to_used && it->m_ListStart > to_before) - { - destroy_row_to = it; - destroy_row_to_used = true; - - // If it isn't the last row, we'll add another row to delete, - // just so we can see if the last restorted line is - // identical to what it was before. If it isn't, then we'll - // have to continue. - // 'check_point_row_start' is where we store how the that - // line looked. - if (destroy_row_to != m_CharacterPositions.end()) - { - check_point_row_start = destroy_row_to->m_ListStart; - check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); - if (destroy_row_to->m_ListOfX.empty()) - ++check_point_row_end; - } - - ++destroy_row_to; - break; - } - } - - if (!destroy_row_from_used) - { - destroy_row_from = m_CharacterPositions.end(); - --destroy_row_from; - - // As usual, let's destroy another row back - if (destroy_row_from != m_CharacterPositions.begin()) - --destroy_row_from; - - current_line = destroy_row_from; - } - - if (!destroy_row_to_used) - { - destroy_row_to = m_CharacterPositions.end(); - check_point_row_start = -1; - } - - // set 'from' to the row we'll destroy from - // and 'to' to the row we'll destroy to - from = destroy_row_from->m_ListStart; - - if (destroy_row_to != m_CharacterPositions.end()) - to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to. - else - to = static_cast(m_Caption.length()); - - - // Setup the first row - row.m_ListStart = destroy_row_from->m_ListStart; - - std::list::iterator temp_it = destroy_row_to; - --temp_it; - - current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to); - - // If there has been a change in number of characters - // we need to change all m_ListStart that comes after - // the interval we just destroyed. We'll change all - // values with the delta change of the string length. - int delta = to_after - to_before; - if (delta != 0) - { - for (std::list::iterator it = current_line; - it != m_CharacterPositions.end(); - ++it) - it->m_ListStart += delta; - - // Update our check point too! - check_point_row_start += delta; - check_point_row_end += delta; - - if (to != static_cast(m_Caption.length())) - to += delta; - } - } - - int last_word_started = from; - float x_pos = 0.f; - - //if (to_before != -1) - // return; - - for (int i = from; i < to; ++i) - { - if (m_Caption[i] == L'\n' && m_MultiLine) - { - if (i == to-1 && to != static_cast(m_Caption.length())) - break; // it will be added outside - - current_line = m_CharacterPositions.insert(current_line, row); - ++current_line; - - // Setup the next row: - row.m_ListOfX.clear(); - row.m_ListStart = i+1; - x_pos = 0.f; - } - else - { - if (m_Caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix. - m_Caption[i] == L'-'*/) - last_word_started = i+1; - - if (!m_Mask) - x_pos += font.GetCharacterWidth(m_Caption[i]); - else - x_pos += font.GetCharacterWidth(mask_char); - - if (x_pos >= GetTextAreaWidth() && m_MultiLine) - { - // The following decides whether it will word-wrap a word, - // or if it's only one word on the line, where it has to - // break the word apart. - if (last_word_started == row.m_ListStart) - { - last_word_started = i; - row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started)); - //row.m_ListOfX.push_back(x_pos); - //continue; - } - else - { - // regular word-wrap - row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1)); - } - - // Now, create a new line: - // notice: when we enter a newline, you can stand with the cursor - // both before and after that character, being on different - // rows. With automatic word-wrapping, that is not possible. Which - // is intuitively correct. - - current_line = m_CharacterPositions.insert(current_line, row); - ++current_line; - - // Setup the next row: - row.m_ListOfX.clear(); - row.m_ListStart = last_word_started; - - i = last_word_started-1; - - x_pos = 0.f; - } - else - // Get width of this character: - row.m_ListOfX.push_back(x_pos); - } - - // Check if it's the last iteration, and we're not revising the whole string - // because in that case, more word-wrapping might be needed. - // also check if the current line isn't the end - if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end()) - { - // check all rows and see if any existing - if (row.m_ListStart != check_point_row_start) - { - std::list::iterator destroy_row_from; - std::list::iterator destroy_row_to; - // Are used to check if the above has been set to anything, - // previously a comparison like: - // destroy_row_from == std::list::iterator() - // was used, but it didn't work with GCC. - bool destroy_row_from_used = false; - bool destroy_row_to_used = false; - - // Iterate, and remove everything between 'from' and 'to_before' - // actually remove the entire lines they are on, it'll all have - // to be redone. And when going along, we'll delete a row at a time - // when continuing to see how much more after 'to' we need to remake. - int i = 0; - for (std::list::iterator it = m_CharacterPositions.begin(); - it != m_CharacterPositions.end(); - ++it, ++i) - { - if (!destroy_row_from_used && it->m_ListStart > check_point_row_start) - { - // Destroy the previous line, and all to 'to_before' - //if (i >= 2) - // destroy_row_from = it-2; - //else - // destroy_row_from = it-1; - destroy_row_from = it; - destroy_row_from_used = true; - //--destroy_row_from; - } - - if (!destroy_row_to_used && it->m_ListStart > check_point_row_end) - { - destroy_row_to = it; - destroy_row_to_used = true; - - // If it isn't the last row, we'll add another row to delete, - // just so we can see if the last restorted line is - // identical to what it was before. If it isn't, then we'll - // have to continue. - // 'check_point_row_start' is where we store how the that - // line looked. - if (destroy_row_to != m_CharacterPositions.end()) - { - check_point_row_start = destroy_row_to->m_ListStart; - check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); - if (destroy_row_to->m_ListOfX.empty()) - ++check_point_row_end; - } - else - check_point_row_start = check_point_row_end = -1; - - ++destroy_row_to; - break; - } - } - - if (!destroy_row_from_used) - { - destroy_row_from = m_CharacterPositions.end(); - --destroy_row_from; - - current_line = destroy_row_from; - } - - if (!destroy_row_to_used) - { - destroy_row_to = m_CharacterPositions.end(); - check_point_row_start = check_point_row_end = -1; - } - - if (destroy_row_to != m_CharacterPositions.end()) - to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to. - else - to = static_cast(m_Caption.length()); - - - // Set current line, new rows will be added before current_line, so - // we'll choose the destroy_row_to, because it won't be deleted - // in the coming erase. - current_line = destroy_row_to; - - m_CharacterPositions.erase(destroy_row_from, destroy_row_to); - } - // else, the for loop will end naturally. - } - } - // This is kind of special, when we renew a some lines, then the last - // one will sometimes end with a space (' '), that really should - // be omitted when word-wrapping. So we'll check if the last row - // we'll add has got the same value as the next row. - if (current_line != m_CharacterPositions.end()) - { - if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart) - row.m_ListOfX.resize(row.m_ListOfX.size()-1); - } - - // add the final row (even if empty) - m_CharacterPositions.insert(current_line, row); - - if (m_ScrollBar) - { - GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + m_BufferZone * 2.f); - GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); - } -} - -int CInput::GetMouseHoveringTextPosition() const -{ - if (m_CharacterPositions.empty()) - return 0; - - // Return position - int retPosition; - - std::list::const_iterator current = m_CharacterPositions.begin(); - - CPos mouse = m_pGUI.GetMousePos(); - - if (m_MultiLine) - { - float scroll = 0.f; - if (m_ScrollBar) - scroll = GetScrollBarPos(0); - - // Now get the height of the font. - // TODO: Get the real font - CFontMetrics font(CStrIntern(m_Font.ToUTF8())); - float spacing = (float)font.GetLineSpacing(); - - // Change mouse position relative to text. - mouse -= m_CachedActualSize.TopLeft(); - mouse.x -= m_BufferZone; - mouse.y += scroll - m_BufferZone; - - int row = (int)((mouse.y) / spacing); - - if (row < 0) - row = 0; - - if (row > (int)m_CharacterPositions.size()-1) - row = (int)m_CharacterPositions.size()-1; - - // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to - // be able to get the specific element here. This is hopefully a temporary hack. - - for (int i = 0; i < row; ++i) - ++current; - } - else - { - // current is already set to begin, - // but we'll change the mouse.x to fit our horizontal scrolling - mouse -= m_CachedActualSize.TopLeft(); - mouse.x -= m_BufferZone - m_HorizontalScroll; - // mouse.y is moot - } - - retPosition = current->m_ListStart; - - // Okay, now loop through the glyphs to find the appropriate X position - float dummy; - retPosition += GetXTextPosition(current, mouse.x, dummy); - - return retPosition; -} - -// Does not process horizontal scrolling, 'x' must be modified before inputted. -int CInput::GetXTextPosition(const std::list::const_iterator& current, const float& x, float& wanted) const -{ - int ret = 0; - float previous = 0.f; - int i = 0; - - for (std::vector::const_iterator it = current->m_ListOfX.begin(); - it != current->m_ListOfX.end(); - ++it, ++i) - { - if (*it >= x) - { - if (x - previous >= *it - x) - ret += i+1; - else - ret += i; - - break; - } - previous = *it; - } - // If a position wasn't found, we will assume the last - // character of that line. - if (i == (int)current->m_ListOfX.size()) - { - ret += i; - wanted = x; - } - else - wanted = 0.f; - - return ret; -} - -void CInput::DeleteCurSelection() -{ - int virtualFrom; - int virtualTo; - - if (m_iBufferPos_Tail >= m_iBufferPos) - { - virtualFrom = m_iBufferPos; - virtualTo = m_iBufferPos_Tail; - } - else - { - virtualFrom = m_iBufferPos_Tail; - virtualTo = m_iBufferPos; - } - - m_Caption = - m_Caption.Left(virtualFrom) + - m_Caption.Right(static_cast(m_Caption.length()) - virtualTo); - - UpdateText(virtualFrom, virtualTo, virtualFrom); - - // Remove selection - m_iBufferPos_Tail = -1; - m_iBufferPos = virtualFrom; - UpdateBufferPositionSetting(); -} - -bool CInput::SelectingText() const -{ - return m_iBufferPos_Tail != -1 && - m_iBufferPos_Tail != m_iBufferPos; -} - -float CInput::GetTextAreaWidth() -{ - if (m_ScrollBar && GetScrollBar(0).GetStyle()) - return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f - GetScrollBar(0).GetStyle()->m_Width; - - return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f; -} - -void CInput::UpdateAutoScroll() -{ - // Autoscrolling up and down - if (m_MultiLine) - { - if (!m_ScrollBar) - return; - - const float scroll = GetScrollBar(0).GetPos(); - - // Now get the height of the font. - // TODO: Get the real font - CFontMetrics font(CStrIntern(m_Font.ToUTF8())); - float spacing = (float)font.GetLineSpacing(); - //float height = font.GetHeight(); - - // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to - // be able to get the specific element here. This is hopefully a temporary hack. - - std::list::iterator current = m_CharacterPositions.begin(); - int row = 0; - while (current != m_CharacterPositions.end()) - { - if (m_iBufferPos >= current->m_ListStart && - m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) - break; - - ++current; - ++row; - } - - // If scrolling down - if (-scroll + static_cast(row + 1) * spacing + m_BufferZone * 2.f > m_CachedActualSize.GetHeight()) - { - // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge. - GetScrollBar(0).SetPos(static_cast(row + 1) * spacing - m_CachedActualSize.GetHeight() + m_BufferZone * 2.f); - } - // If scrolling up - else if (-scroll + (float)row * spacing < 0.f) - { - // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge. - GetScrollBar(0).SetPos((float)row * spacing); - } - } - else // autoscrolling left and right - { - // Get X position of position: - if (m_CharacterPositions.empty()) - return; - - float x_position = 0.f; - float x_total = 0.f; - if (!m_CharacterPositions.begin()->m_ListOfX.empty()) - { - - // Get position of m_iBufferPos - if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos && - m_iBufferPos > 0) - x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1]; - - // Get complete length: - x_total = m_CharacterPositions.begin()->m_ListOfX[m_CharacterPositions.begin()->m_ListOfX.size()-1]; - } - - // Check if outside to the right - if (x_position - m_HorizontalScroll + m_BufferZone * 2.f > m_CachedActualSize.GetWidth()) - m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f; - - // Check if outside to the left - if (x_position - m_HorizontalScroll < 0.f) - m_HorizontalScroll = x_position; - - // Check if the text doesn't even fill up to the right edge even though scrolling is done. - if (m_HorizontalScroll != 0.f && - x_total - m_HorizontalScroll + m_BufferZone * 2.f < m_CachedActualSize.GetWidth()) - m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f; - - // Now this is the fail-safe, if x_total isn't even the length of the control, - // remove all scrolling - if (x_total + m_BufferZone * 2.f < m_CachedActualSize.GetWidth()) - m_HorizontalScroll = 0.f; - } -} Index: ps/trunk/source/gui/CList.h =================================================================== --- ps/trunk/source/gui/CList.h +++ ps/trunk/source/gui/CList.h @@ -1,151 +0,0 @@ -/* 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_CLIST -#define INCLUDED_CLIST - -#include "gui/CGUIList.h" -#include "gui/CGUISprite.h" -#include "gui/IGUIScrollBarOwner.h" -#include "gui/IGUITextOwner.h" - -#include - -/** - * Create a list of elements, where one can be selected - * by the user. The control will use a pre-processed - * text-object for each element, which will be managed - * by the IGUITextOwner structure. - * - * A scroll-bar will appear when needed. This will be - * achieved with the IGUIScrollBarOwner structure. - */ -class CList : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner -{ - GUI_OBJECT(CList) - -public: - CList(CGUI& pGUI); - virtual ~CList(); - - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates(); - - /** - * @see IGUIObject#UpdateCachedSize() - */ - virtual void UpdateCachedSize(); - - /** - * Adds an item last to the list. - */ - virtual void AddItem(const CStrW& str, const CStrW& data); - -protected: - - /** - * Sets up text, should be called every time changes has been - * made that can change the visual. - */ - virtual void SetupText(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Handle events manually to catch keyboard inputting. - */ - virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); - - /** - * Draws the List box - */ - virtual void Draw(); - - /** - * Easy select elements functions - */ - virtual void SelectNextElement(); - virtual void SelectPrevElement(); - virtual void SelectFirstElement(); - virtual void SelectLastElement(); - - /** - * Handle the \ tag. - */ - virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); - - // Called every time the auto-scrolling should be checked. - void UpdateAutoScroll(); - - // Extended drawing interface, this is so that classes built on the this one - // can use other sprite names. - virtual void DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor); - - // Get the area of the list. This is so that it can easily be changed, like in CDropDown - // where the area is not equal to m_CachedActualSize. - virtual CRect GetListRect() const { return m_CachedActualSize; } - - // Returns whether SetupText() has run since the last message was received - // (and thus whether list items have possibly changed). - virtual bool GetModified() const { return m_Modified; } - - /** - * List of each element's relative y position. Will be - * one larger than m_Items, because it will end with the - * bottom of the last element. First element will always - * be zero, but still stored for easy handling. - */ - std::vector m_ItemsYPositions; - - virtual int GetHoveredItem(); - - // Settings - float m_BufferZone; - CStrW m_Font; - bool m_ScrollBar; - CStr m_ScrollBarStyle; - CStrW m_SoundDisabled; - CStrW m_SoundSelected; - CGUISpriteInstance m_Sprite; - CGUISpriteInstance m_SpriteSelectArea; - i32 m_CellID; - EAlign m_TextAlign; - CGUIColor m_TextColor; - CGUIColor m_TextColorSelected; - i32 m_Selected; - bool m_AutoScroll; - i32 m_Hovered; - CGUIList m_List; - CGUIList m_ListData; - -private: - // Whether the list's items have been modified since last handling a message. - bool m_Modified; - - // Used for doubleclick registration - int m_PrevSelectedItem; - - // Last time a click on an item was issued - double m_LastItemClickTime; -}; - -#endif // INCLUDED_CLIST Index: ps/trunk/source/gui/CList.cpp =================================================================== --- ps/trunk/source/gui/CList.cpp +++ ps/trunk/source/gui/CList.cpp @@ -1,483 +0,0 @@ -/* 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 "CList.h" - -#include "gui/CGUI.h" -#include "gui/CGUIColor.h" -#include "gui/CGUIList.h" -#include "gui/CGUIScrollBarVertical.h" -#include "lib/external_libraries/libsdl.h" -#include "lib/timer.h" - -CList::CList(CGUI& pGUI) - : IGUIObject(pGUI), - IGUITextOwner(*static_cast(this)), - IGUIScrollBarOwner(*static_cast(this)), - m_Modified(false), - m_PrevSelectedItem(-1), - m_LastItemClickTime(0), - m_BufferZone(), - m_Font(), - m_ScrollBar(), - m_ScrollBarStyle(), - m_SoundDisabled(), - m_SoundSelected(), - m_Sprite(), - m_SpriteSelectArea(), - m_CellID(), - m_TextAlign(), - m_TextColor(), - m_TextColorSelected(), - m_Selected(), - m_AutoScroll(), - m_Hovered(), - m_List(), - m_ListData() -{ - RegisterSetting("buffer_zone", m_BufferZone); - RegisterSetting("font", m_Font); - RegisterSetting("scrollbar", m_ScrollBar); - RegisterSetting("scrollbar_style", m_ScrollBarStyle); - RegisterSetting("sound_disabled", m_SoundDisabled); - RegisterSetting("sound_selected", m_SoundSelected); - RegisterSetting("sprite", m_Sprite); - // Add sprite_disabled! TODO - RegisterSetting("sprite_selectarea", m_SpriteSelectArea); - RegisterSetting("cell_id", m_CellID); - RegisterSetting("text_align", m_TextAlign); - RegisterSetting("textcolor", m_TextColor); - RegisterSetting("textcolor_selected", m_TextColorSelected); - RegisterSetting("selected", m_Selected); // Index selected. -1 is none. - RegisterSetting("auto_scroll", m_AutoScroll); - RegisterSetting("hovered", m_Hovered); - // Each list item has both a name (in 'list') and an associated data string (in 'list_data') - RegisterSetting("list", m_List); - RegisterSetting("list_data", m_ListData); - - SetSetting("scrollbar", false, true); - SetSetting("selected", -1, true); - SetSetting("hovered", -1, true); - SetSetting("auto_scroll", false, true); - - // Add scroll-bar - CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); - bar->SetRightAligned(true); - AddScrollBar(bar); -} - -CList::~CList() -{ -} - -void CList::SetupText() -{ - m_Modified = true; - - m_ItemsYPositions.resize(m_List.m_Items.size() + 1); - - // Delete all generated texts. Some could probably be saved, - // but this is easier, and this function will never be called - // continuously, or even often, so it'll probably be okay. - m_GeneratedTexts.clear(); - - float width = GetListRect().GetWidth(); - // remove scrollbar if applicable - if (m_ScrollBar && GetScrollBar(0).GetStyle()) - width -= GetScrollBar(0).GetStyle()->m_Width; - - // Generate texts - float buffered_y = 0.f; - - for (size_t i = 0; i < m_List.m_Items.size(); ++i) - { - CGUIText* text; - - if (!m_List.m_Items[i].GetOriginalString().empty()) - text = &AddText(m_List.m_Items[i], m_Font, width, m_BufferZone); - else - { - // Minimum height of a space character of the current font size - CGUIString align_string; - align_string.SetValue(L" "); - text = &AddText(align_string, m_Font, width, m_BufferZone); - } - - m_ItemsYPositions[i] = buffered_y; - buffered_y += text->GetSize().cy; - } - - m_ItemsYPositions[m_List.m_Items.size()] = buffered_y; - - // Setup scrollbar - if (m_ScrollBar) - { - GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); - GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight()); - - CRect rect = GetListRect(); - GetScrollBar(0).SetX(rect.right); - GetScrollBar(0).SetY(rect.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(rect.bottom - rect.top); - } -} - -void CList::ResetStates() -{ - IGUIObject::ResetStates(); - IGUIScrollBarOwner::ResetStates(); -} - -void CList::UpdateCachedSize() -{ - IGUIObject::UpdateCachedSize(); - IGUITextOwner::UpdateCachedSize(); -} - -void CList::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - IGUIScrollBarOwner::HandleMessage(Message); - //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! - - m_Modified = false; - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - if (Message.value == "list") - SetupText(); - - // If selected is changed, call "SelectionChange" - if (Message.value == "selected") - { - // TODO: Check range - - if (m_AutoScroll) - UpdateAutoScroll(); - - // TODO only works if lower-case, shouldn't it be made case sensitive instead? - ScriptEvent("selectionchange"); - } - - if (Message.value == "scrollbar") - SetupText(); - - // Update scrollbar - if (Message.value == "scrollbar_style") - { - GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); - SetupText(); - } - - break; - - case GUIM_MOUSE_PRESS_LEFT: - { - if (!m_Enabled) - { - PlaySound(m_SoundDisabled); - break; - } - - int hovered = GetHoveredItem(); - if (hovered == -1) - break; - SetSetting("selected", hovered, true); - UpdateAutoScroll(); - PlaySound(m_SoundSelected); - - if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem) - this->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, "mouseleftdoubleclickitem"); - else - this->SendEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, "mouseleftclickitem"); - - m_LastItemClickTime = timer_Time(); - m_PrevSelectedItem = hovered; - break; - } - - case GUIM_MOUSE_LEAVE: - { - if (m_Hovered == -1) - break; - - SetSetting("hovered", -1, true); - ScriptEvent("hoverchange"); - break; - } - - case GUIM_MOUSE_OVER: - { - int hovered = GetHoveredItem(); - if (hovered == m_Hovered) - break; - - SetSetting("hovered", hovered, true); - ScriptEvent("hoverchange"); - break; - } - - case GUIM_LOAD: - { - GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); - break; - } - - default: - break; - } - - IGUITextOwner::HandleMessage(Message); -} - -InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev) -{ - InReaction result = IN_PASS; - - if (ev->ev.type == SDL_KEYDOWN) - { - int szChar = ev->ev.key.keysym.sym; - - switch (szChar) - { - case SDLK_HOME: - SelectFirstElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_END: - SelectLastElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_UP: - SelectPrevElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_DOWN: - SelectNextElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_PAGEUP: - GetScrollBar(0).ScrollMinusPlenty(); - result = IN_HANDLED; - break; - - case SDLK_PAGEDOWN: - GetScrollBar(0).ScrollPlusPlenty(); - result = IN_HANDLED; - break; - - default: // Do nothing - result = IN_PASS; - } - } - - return result; -} - -void CList::Draw() -{ - DrawList(m_Selected, m_Sprite, m_SpriteSelectArea, m_TextColor); -} - -void CList::DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selectarea, const CGUIColor& textcolor) -{ - float bz = GetBufferedZ(); - - // First call draw on ScrollBarOwner - if (m_ScrollBar) - IGUIScrollBarOwner::Draw(); - - { - CRect rect = GetListRect(); - - m_pGUI.DrawSprite(sprite, m_CellID, bz, rect); - - float scroll = 0.f; - if (m_ScrollBar) - scroll = GetScrollBar(0).GetPos(); - - if (selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()) - { - // Get rectangle of selection: - CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, - rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); - - if (rect_sel.top <= rect.bottom && - rect_sel.bottom >= rect.top) - { - if (rect_sel.bottom > rect.bottom) - rect_sel.bottom = rect.bottom; - if (rect_sel.top < rect.top) - rect_sel.top = rect.top; - - if (m_ScrollBar) - { - // Remove any overlapping area of the scrollbar. - if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && - rect_sel.right <= GetScrollBar(0).GetOuterRect().right) - rect_sel.right = GetScrollBar(0).GetOuterRect().left; - - if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && - rect_sel.left < GetScrollBar(0).GetOuterRect().right) - rect_sel.left = GetScrollBar(0).GetOuterRect().right; - } - - m_pGUI.DrawSprite(sprite_selectarea, m_CellID, bz + 0.05f, rect_sel); - } - } - - for (size_t i = 0; i < m_List.m_Items.size(); ++i) - { - if (m_ItemsYPositions[i+1] - scroll < 0 || - m_ItemsYPositions[i] - scroll > rect.GetHeight()) - continue; - - // Clipping area (we'll have to substract the scrollbar) - CRect cliparea = GetListRect(); - - if (m_ScrollBar) - { - if (cliparea.right > GetScrollBar(0).GetOuterRect().left && - cliparea.right <= GetScrollBar(0).GetOuterRect().right) - cliparea.right = GetScrollBar(0).GetOuterRect().left; - - if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && - cliparea.left < GetScrollBar(0).GetOuterRect().right) - cliparea.left = GetScrollBar(0).GetOuterRect().right; - } - - DrawText(i, textcolor, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz + 0.1f, cliparea); - } - } -} - -void CList::AddItem(const CStrW& str, const CStrW& data) -{ - CGUIString gui_string; - gui_string.SetValue(str); - - // Do not send a settings-changed message - m_List.m_Items.push_back(gui_string); - - CGUIString data_string; - data_string.SetValue(data); - - m_ListData.m_Items.push_back(data_string); - - // TODO Temp - SetupText(); -} - -bool CList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) -{ - int elmt_item = pFile->GetElementID("item"); - - if (child.GetNodeName() == elmt_item) - { - AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); - return true; - } - - return false; -} - -void CList::SelectNextElement() -{ - if (m_Selected != static_cast(m_List.m_Items.size()) - 1) - { - SetSetting("selected", m_Selected + 1, true); - PlaySound(m_SoundSelected); - } -} - -void CList::SelectPrevElement() -{ - if (m_Selected > 0) - { - SetSetting("selected", m_Selected - 1, true); - PlaySound(m_SoundSelected); - } -} - -void CList::SelectFirstElement() -{ - if (m_Selected >= 0) - SetSetting("selected", 0, true); -} - -void CList::SelectLastElement() -{ - const int index = static_cast(m_List.m_Items.size()) - 1; - - if (m_Selected != index) - SetSetting("selected", index, true); -} - -void CList::UpdateAutoScroll() -{ - // No scrollbar, no scrolling (at least it's not made to work properly). - if (!m_ScrollBar || m_Selected < 0 || static_cast(m_Selected) >= m_ItemsYPositions.size()) - return; - - float scroll = GetScrollBar(0).GetPos(); - - // Check upper boundary - if (m_ItemsYPositions[m_Selected] < scroll) - { - GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected]); - return; // this means, if it wants to align both up and down at the same time - // this will have precedence. - } - - // Check lower boundary - CRect rect = GetListRect(); - if (m_ItemsYPositions[m_Selected+1]-rect.GetHeight() > scroll) - GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected+1]-rect.GetHeight()); -} - -int CList::GetHoveredItem() -{ - const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f; - - const CRect& rect = GetListRect(); - CPos mouse = m_pGUI.GetMousePos(); - mouse.y += scroll; - - // Mouse is over scrollbar - if (m_ScrollBar && GetScrollBar(0).IsVisible() && - mouse.x >= GetScrollBar(0).GetOuterRect().left && - mouse.x <= GetScrollBar(0).GetOuterRect().right) - return -1; - - for (size_t i = 0; i < m_List.m_Items.size(); ++i) - if (mouse.y >= rect.top + m_ItemsYPositions[i] && - mouse.y < rect.top + m_ItemsYPositions[i + 1]) - return i; - - return -1; -} Index: ps/trunk/source/gui/COList.h =================================================================== --- ps/trunk/source/gui/COList.h +++ ps/trunk/source/gui/COList.h @@ -1,91 +0,0 @@ -/* 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_COLIST -#define INCLUDED_COLIST - -#include "CList.h" -#include "gui/CGUIColor.h" - -#include - -/** - * Represents a column. - */ -struct COListColumn -{ - // Avoid copying the strings. - NONCOPYABLE(COListColumn); - MOVABLE(COListColumn); - COListColumn() : m_Width(0), m_Hidden(false) {} - CGUIColor m_TextColor; - CStr m_Id; - float m_Width; - CStrW m_Heading; // CGUIString?? - CGUIList m_List; - bool m_Hidden; -}; - -/** - * Multi-column list. One row can be selected by the user. - * Individual cells are clipped if the contained text is too long. - * - * The list can be sorted dynamically by JS code when a - * heading is clicked. - * A scroll-bar will appear when needed. - */ -class COList : public CList -{ - GUI_OBJECT(COList) - -public: - COList(CGUI& pGUI); - -protected: - void SetupText(); - void HandleMessage(SGUIMessage& Message); - - /** - * Handle the \ tag. - */ - virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); - virtual void AdditionalChildrenHandled(); - - void DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor); - - virtual CRect GetListRect() const; - - /** - * Available columns. - */ - std::vector m_Columns; - - // Settings - CGUISpriteInstance m_SpriteHeading; - bool m_Sortable; - CStr m_SelectedColumn; - i32 m_SelectedColumnOrder; - CGUISpriteInstance m_SpriteAsc; - CGUISpriteInstance m_SpriteDesc; - CGUISpriteInstance m_SpriteNotSorted; - -private: - // Width of space available for columns - float m_TotalAvailableColumnWidth; - float m_HeadingHeight; -}; - -#endif // INCLUDED_COLIST Index: ps/trunk/source/gui/COList.cpp =================================================================== --- ps/trunk/source/gui/COList.cpp +++ ps/trunk/source/gui/COList.cpp @@ -1,442 +0,0 @@ -/* 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 "COList.h" - -#include "gui/CGUI.h" -#include "gui/CGUIColor.h" -#include "gui/CGUIList.h" -#include "gui/IGUIScrollBar.h" -#include "i18n/L10n.h" -#include "ps/CLogger.h" - -const float SORT_SPRITE_DIM = 16.0f; -const CPos COLUMN_SHIFT = CPos(0, 4); - -COList::COList(CGUI& pGUI) - : CList(pGUI), - m_SpriteHeading(), - m_Sortable(), - m_SelectedColumn(), - m_SelectedColumnOrder(), - m_SpriteAsc(), - m_SpriteDesc(), - m_SpriteNotSorted() -{ - RegisterSetting("sprite_heading", m_SpriteHeading); - RegisterSetting("sortable", m_Sortable); // The actual sorting is done in JS for more versatility - RegisterSetting("selected_column", m_SelectedColumn); - RegisterSetting("selected_column_order", m_SelectedColumnOrder); - RegisterSetting("sprite_asc", m_SpriteAsc); // Show the order of sorting - RegisterSetting("sprite_desc", m_SpriteDesc); - RegisterSetting("sprite_not_sorted", m_SpriteNotSorted); -} - -void COList::SetupText() -{ - m_ItemsYPositions.resize(m_List.m_Items.size() + 1); - - // Delete all generated texts. Some could probably be saved, - // but this is easier, and this function will never be called - // continuously, or even often, so it'll probably be okay. - m_GeneratedTexts.clear(); - - m_TotalAvailableColumnWidth = GetListRect().GetWidth(); - // remove scrollbar if applicable - if (m_ScrollBar && GetScrollBar(0).GetStyle()) - m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width; - - m_HeadingHeight = SORT_SPRITE_DIM; // At least the size of the sorting sprite - - for (const COListColumn& column : m_Columns) - { - float width = column.m_Width; - if (column.m_Width > 0 && column.m_Width < 1) - width *= m_TotalAvailableColumnWidth; - - CGUIString gui_string; - gui_string.SetValue(column.m_Heading); - - const CGUIText& text = AddText(gui_string, m_Font, width, m_BufferZone); - m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().cy + COLUMN_SHIFT.y); - } - - // Generate texts - float buffered_y = 0.f; - - for (size_t i = 0; i < m_List.m_Items.size(); ++i) - { - m_ItemsYPositions[i] = buffered_y; - float shift = 0.0f; - for (const COListColumn& column : m_Columns) - { - float width = column.m_Width; - if (column.m_Width > 0 && column.m_Width < 1) - width *= m_TotalAvailableColumnWidth; - - CGUIText* text; - if (!column.m_List.m_Items[i].GetOriginalString().empty()) - text = &AddText(column.m_List.m_Items[i], m_Font, width, m_BufferZone); - else - { - // Minimum height of a space character of the current font size - CGUIString align_string; - align_string.SetValue(L" "); - text = &AddText(align_string, m_Font, width, m_BufferZone); - } - shift = std::max(shift, text->GetSize().cy); - } - buffered_y += shift; - } - - m_ItemsYPositions[m_List.m_Items.size()] = buffered_y; - - if (m_ScrollBar) - { - CRect rect = GetListRect(); - GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); - GetScrollBar(0).SetScrollSpace(rect.GetHeight()); - - GetScrollBar(0).SetX(rect.right); - GetScrollBar(0).SetY(rect.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(rect.bottom - rect.top); - } -} - -CRect COList::GetListRect() const -{ - return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0); -} - -void COList::HandleMessage(SGUIMessage& Message) -{ - CList::HandleMessage(Message); - - switch (Message.type) - { - // If somebody clicks on the column heading - case GUIM_MOUSE_PRESS_LEFT: - { - if (!m_Sortable) - return; - - const CPos& mouse = m_pGUI.GetMousePos(); - if (!m_CachedActualSize.PointInside(mouse)) - return; - - float xpos = 0; - for (const COListColumn& column : m_Columns) - { - if (column.m_Hidden) - continue; - - float width = column.m_Width; - // Check if it's a decimal value, and if so, assume relative positioning. - if (column.m_Width < 1 && column.m_Width > 0) - width *= m_TotalAvailableColumnWidth; - CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); - if (mouse.x >= leftTopCorner.x && - mouse.x < leftTopCorner.x + width && - mouse.y < leftTopCorner.y + m_HeadingHeight) - { - if (column.m_Id != m_SelectedColumn) - { - SetSetting("selected_column_order", -1, true); - CStr selected_column = column.m_Id; - SetSetting("selected_column", selected_column, true); - } - else - SetSetting("selected_column_order", -m_SelectedColumnOrder, true); - - ScriptEvent("selectioncolumnchange"); - PlaySound(m_SoundSelected); - return; - } - xpos += width; - } - return; - } - default: - return; - } -} - -bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) -{ - #define ELMT(x) int elmt_##x = pFile->GetElementID(#x) - #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x) - ELMT(item); - ELMT(column); - ELMT(translatableAttribute); - ATTR(id); - ATTR(context); - - if (child.GetNodeName() == elmt_item) - { - AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); - return true; - } - else if (child.GetNodeName() == elmt_column) - { - COListColumn column; - - for (XMBAttribute attr : child.GetAttributes()) - { - CStr attr_name(pFile->GetAttributeString(attr.Name)); - CStr attr_value(attr.Value); - - if (attr_name == "color") - { - if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), column.m_TextColor)) - LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); - } - else if (attr_name == "id") - { - column.m_Id = attr_value; - } - else if (attr_name == "hidden") - { - bool hidden = false; - if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), hidden)) - LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); - else - column.m_Hidden = hidden; - } - else if (attr_name == "width") - { - float width; - if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), width)) - LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); - else - { - // Check if it's a relative value, and save as decimal if so. - if (attr_value.find("%") != std::string::npos) - width = width / 100.f; - column.m_Width = width; - } - } - else if (attr_name == "heading") - { - column.m_Heading = attr_value.FromUTF8(); - } - } - - for (XMBElement grandchild : child.GetChildNodes()) - { - if (grandchild.GetNodeName() != elmt_translatableAttribute) - continue; - - CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id)); - // only the heading is translatable for list column - if (attributeName.empty() || attributeName != "heading") - { - LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str()); - continue; - } - - CStr value(grandchild.GetText()); - if (value.empty()) - continue; - - CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any. - if (!context.empty()) - { - CStr translatedValue(g_L10n.TranslateWithContext(context, value)); - column.m_Heading = translatedValue.FromUTF8(); - } - else - { - CStr translatedValue(g_L10n.Translate(value)); - column.m_Heading = translatedValue.FromUTF8(); - } - } - - m_Columns.emplace_back(std::move(column)); - return true; - } - - return false; -} - -void COList::AdditionalChildrenHandled() -{ - SetupText(); - - // Do this after the last push_back call to avoid iterator invalidation - for (COListColumn& column : m_Columns) - { - RegisterSetting("list_" + column.m_Id, column.m_List); - RegisterSetting("hidden_" + column.m_Id, column.m_Hidden); - } -} - -void COList::DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor) -{ - const float bz = GetBufferedZ(); - - if (m_ScrollBar) - IGUIScrollBarOwner::Draw(); - - CRect rect = GetListRect(); - - m_pGUI.DrawSprite(sprite, m_CellID, bz, rect); - - float scroll = 0.f; - if (m_ScrollBar) - scroll = GetScrollBar(0).GetPos(); - - // Draw item selection - if (selected != -1) - { - ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()); - - // Get rectangle of selection: - CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, - rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); - - if (rect_sel.top <= rect.bottom && - rect_sel.bottom >= rect.top) - { - if (rect_sel.bottom > rect.bottom) - rect_sel.bottom = rect.bottom; - if (rect_sel.top < rect.top) - rect_sel.top = rect.top; - - if (m_ScrollBar) - { - // Remove any overlapping area of the scrollbar. - if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && - rect_sel.right <= GetScrollBar(0).GetOuterRect().right) - rect_sel.right = GetScrollBar(0).GetOuterRect().left; - - if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && - rect_sel.left < GetScrollBar(0).GetOuterRect().right) - rect_sel.left = GetScrollBar(0).GetOuterRect().right; - } - - // Draw item selection - m_pGUI.DrawSprite(sprite_selected, m_CellID, bz + 0.05f, rect_sel); - } - } - - // Draw line above column header - CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, - m_CachedActualSize.top + m_HeadingHeight); - m_pGUI.DrawSprite(m_SpriteHeading, m_CellID, bz, rect_head); - - // Draw column headers - float xpos = 0; - size_t col = 0; - for (const COListColumn& column : m_Columns) - { - if (column.m_Hidden) - { - ++col; - continue; - } - - // Check if it's a decimal value, and if so, assume relative positioning. - float width = column.m_Width; - if (column.m_Width < 1 && column.m_Width > 0) - width *= m_TotalAvailableColumnWidth; - - CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); - - // Draw sort arrows in colum header - if (m_Sortable) - { - const CGUISpriteInstance* sprite; - if (m_SelectedColumn == column.m_Id) - { - if (m_SelectedColumnOrder == 0) - LOGERROR("selected_column_order must not be 0"); - - if (m_SelectedColumnOrder != -1) - sprite = &m_SpriteAsc; - else - sprite = &m_SpriteDesc; - } - else - sprite = &m_SpriteNotSorted; - - m_pGUI.DrawSprite(*sprite, m_CellID, bz + 0.1f, CRect(leftTopCorner + CPos(width - SORT_SPRITE_DIM, 0), leftTopCorner + CPos(width, SORT_SPRITE_DIM))); - } - - // Draw column header text - DrawText(col, textcolor, leftTopCorner + COLUMN_SHIFT, bz + 0.1f, rect_head); - xpos += width; - ++col; - } - - // Draw list items for each column - const size_t objectsCount = m_Columns.size(); - for (size_t i = 0; i < m_List.m_Items.size(); ++i) - { - if (m_ItemsYPositions[i+1] - scroll < 0 || - m_ItemsYPositions[i] - scroll > rect.GetHeight()) - continue; - - const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i]; - - // Clipping area (we'll have to substract the scrollbar) - CRect cliparea = GetListRect(); - - if (m_ScrollBar) - { - if (cliparea.right > GetScrollBar(0).GetOuterRect().left && - cliparea.right <= GetScrollBar(0).GetOuterRect().right) - cliparea.right = GetScrollBar(0).GetOuterRect().left; - - if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && - cliparea.left < GetScrollBar(0).GetOuterRect().right) - cliparea.left = GetScrollBar(0).GetOuterRect().right; - } - - // Draw all items for that column - xpos = 0; - size_t col = 0; - for (const COListColumn& column : m_Columns) - { - if (column.m_Hidden) - { - ++col; - continue; - } - - // Determine text position and width - const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]); - - float width = column.m_Width; - // Check if it's a decimal value, and if so, assume relative positioning. - if (column.m_Width < 1 && column.m_Width > 0) - width *= m_TotalAvailableColumnWidth; - - // Clip text to the column (to prevent drawing text into the neighboring column) - CRect cliparea2 = cliparea; - cliparea2.right = std::min(cliparea2.right, textPos.x + width); - cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + rowHeight); - - // Draw list item - DrawText(objectsCount * (i +/*Heading*/1) + col, column.m_TextColor, textPos, bz + 0.1f, cliparea2); - xpos += width; - ++col; - } - } -} Index: ps/trunk/source/gui/CProgressBar.h =================================================================== --- ps/trunk/source/gui/CProgressBar.h +++ ps/trunk/source/gui/CProgressBar.h @@ -1,53 +0,0 @@ -/* 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_CPROGRESSBAR -#define INCLUDED_CPROGRESSBAR - -#include "gui/IGUIObject.h" -#include "gui/CGUISprite.h" - -/** - * Object used to draw a value (e.g. progress) from 0 to 100 visually. - */ -class CProgressBar : public IGUIObject -{ - GUI_OBJECT(CProgressBar) - -public: - CProgressBar(CGUI& pGUI); - virtual ~CProgressBar(); - -protected: - /** - * Draws the progress bar - */ - virtual void Draw(); - - // If caption is set, make sure it's within the interval 0-100 - /** - * @see IGUIObject#HandleMessage() - */ - void HandleMessage(SGUIMessage& Message); - - // Settings - CGUISpriteInstance m_SpriteBackground; - CGUISpriteInstance m_SpriteBar; - float m_Caption; -}; - -#endif // INCLUDED_CPROGRESSBAR Index: ps/trunk/source/gui/CProgressBar.cpp =================================================================== --- ps/trunk/source/gui/CProgressBar.cpp +++ ps/trunk/source/gui/CProgressBar.cpp @@ -1,73 +0,0 @@ -/* 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 "CProgressBar.h" - -#include "gui/CGUI.h" - -CProgressBar::CProgressBar(CGUI& pGUI) - : IGUIObject(pGUI), - m_SpriteBackground(), - m_SpriteBar(), - m_Caption() -{ - RegisterSetting("sprite_background", m_SpriteBackground); - RegisterSetting("sprite_bar", m_SpriteBar); - RegisterSetting("caption", m_Caption); // aka value from 0 to 100 -} - -CProgressBar::~CProgressBar() -{ -} - -void CProgressBar::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - // Update scroll-bar - // TODO Gee: (2004-09-01) Is this really updated each time it should? - if (Message.value == "caption") - { - if (m_Caption > 100.f) - SetSetting("caption", 100.f, true); - else if (m_Caption < 0.f) - SetSetting("caption", 0.f, true); - } - break; - default: - break; - } -} - -void CProgressBar::Draw() -{ - float bz = GetBufferedZ(); - - int cell_id = 0; - - m_pGUI.DrawSprite(m_SpriteBackground, cell_id, bz, m_CachedActualSize); - - // Get size of bar (notice it is drawn slightly closer, to appear above the background) - CRect bar_size(m_CachedActualSize.left, m_CachedActualSize.top, - m_CachedActualSize.left+m_CachedActualSize.GetWidth()*(m_Caption/100.f), m_CachedActualSize.bottom); - m_pGUI.DrawSprite(m_SpriteBar, cell_id, bz+0.01f, bar_size); -} Index: ps/trunk/source/gui/CRadioButton.h =================================================================== --- ps/trunk/source/gui/CRadioButton.h +++ ps/trunk/source/gui/CRadioButton.h @@ -1,42 +0,0 @@ -/* 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_CRADIOBUTTON -#define INCLUDED_CRADIOBUTTON - -#include "CCheckBox.h" - -/** - * Just like a check box, but it'll nullify its siblings (of the same kind), - * and it won't switch itself. - * - * @see CCheckBox - */ -class CRadioButton : public CCheckBox -{ - GUI_OBJECT(CRadioButton) - -public: - CRadioButton(CGUI& pGUI); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); -}; - -#endif // INCLUDED_CRADIOBUTTON Index: ps/trunk/source/gui/CRadioButton.cpp =================================================================== --- ps/trunk/source/gui/CRadioButton.cpp +++ ps/trunk/source/gui/CRadioButton.cpp @@ -1,48 +0,0 @@ -/* 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 "CRadioButton.h" - -CRadioButton::CRadioButton(CGUI& pGUI) - : CCheckBox(pGUI) -{ -} - -void CRadioButton::HandleMessage(SGUIMessage& Message) -{ - IGUIButtonBehavior::HandleMessage(Message); - - switch (Message.type) - { - case GUIM_PRESSED: - for (IGUIObject* const& obj : GetParent()->GetChildren()) - { - // Notice, if you use other objects within the parent object that has got - // the setting "checked", it too will change. Hence NO OTHER OBJECTS THAN - // RADIO BUTTONS SHOULD BE WITHIN IT! - obj->SetSetting("checked", false, true); - } - - SetSetting("checked", true, true); - break; - - default: - break; - } -} Index: ps/trunk/source/gui/CSlider.h =================================================================== --- ps/trunk/source/gui/CSlider.h +++ ps/trunk/source/gui/CSlider.h @@ -1,69 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_CSLIDER -#define INCLUDED_CSLIDER - -#include "gui/IGUIObject.h" -#include "gui/CGUISprite.h" - -class CSlider : public IGUIObject -{ - GUI_OBJECT(CSlider) - -public: - CSlider(CGUI& pGUI); - virtual ~CSlider(); - -protected: - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - virtual void Draw(); - - /** - * Change settings and send the script event - */ - void UpdateValue(); - - CRect GetButtonRect() const; - - /** - * @return ratio between the value of the slider and its actual size in the GUI - */ - float GetSliderRatio() const; - - void IncrementallyChangeValue(const float value); - - // Settings - float m_ButtonSide; - i32 m_CellID; - float m_MinValue; - float m_MaxValue; - CGUISpriteInstance m_Sprite; - CGUISpriteInstance m_SpriteBar; - float m_Value; - -private: - bool m_IsPressed; - CPos m_Mouse; -}; - -#endif // INCLUDED_CSLIDER Index: ps/trunk/source/gui/CSlider.cpp =================================================================== --- ps/trunk/source/gui/CSlider.cpp +++ ps/trunk/source/gui/CSlider.cpp @@ -1,139 +0,0 @@ -/* 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 "CSlider.h" - -#include "gui/CGUI.h" -#include "maths/MathUtil.h" - -CSlider::CSlider(CGUI& pGUI) - : IGUIObject(pGUI), - m_IsPressed(), - m_ButtonSide(), - m_CellID(), - m_MaxValue(), - m_MinValue(), - m_Sprite(), - m_SpriteBar(), - m_Value() -{ - RegisterSetting("button_width", m_ButtonSide); - RegisterSetting("cell_id", m_CellID); - RegisterSetting("max_value", m_MaxValue); - RegisterSetting("min_value", m_MinValue); - RegisterSetting("sprite", m_Sprite); - RegisterSetting("sprite_bar", m_SpriteBar); - RegisterSetting("value", m_Value); - - m_Value = Clamp(m_Value, m_MinValue, m_MaxValue); -} - -CSlider::~CSlider() -{ -} - -float CSlider::GetSliderRatio() const -{ - return (m_MaxValue - m_MinValue) / (m_CachedActualSize.GetWidth() - m_ButtonSide); -} - -void CSlider::IncrementallyChangeValue(const float difference) -{ - m_Value = Clamp(m_Value + difference, m_MinValue, m_MaxValue); - UpdateValue(); -} - -void CSlider::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - { - m_Value = Clamp(m_Value, m_MinValue, m_MaxValue); - break; - } - case GUIM_MOUSE_WHEEL_DOWN: - { - if (m_IsPressed) - break; - IncrementallyChangeValue(-0.01f); - break; - } - case GUIM_MOUSE_WHEEL_UP: - { - if (m_IsPressed) - break; - IncrementallyChangeValue(0.01f); - break; - } - case GUIM_MOUSE_PRESS_LEFT: - { - m_Mouse = m_pGUI.GetMousePos(); - m_IsPressed = true; - - IncrementallyChangeValue((m_Mouse.x - GetButtonRect().CenterPoint().x) * GetSliderRatio()); - break; - } - case GUIM_MOUSE_RELEASE_LEFT: - { - m_IsPressed = false; - break; - } - case GUIM_MOUSE_MOTION: - { - if (!IsMouseOver()) - m_IsPressed = false; - if (m_IsPressed) - { - float difference = float(m_pGUI.GetMousePos().x - m_Mouse.x) * GetSliderRatio(); - m_Mouse = m_pGUI.GetMousePos(); - IncrementallyChangeValue(difference); - } - break; - } - default: - break; - } -} - -void CSlider::Draw() -{ - CRect slider_line(m_CachedActualSize); - slider_line.left += m_ButtonSide / 2.0f; - slider_line.right -= m_ButtonSide / 2.0f; - float bz = GetBufferedZ(); - m_pGUI.DrawSprite(m_SpriteBar, m_CellID, bz, slider_line); - m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, GetButtonRect()); -} - -void CSlider::UpdateValue() -{ - SetSetting("value", m_Value, true); - ScriptEvent("valuechange"); -} - -CRect CSlider::GetButtonRect() const -{ - float ratio = m_MaxValue > m_MinValue ? (m_Value - m_MinValue) / (m_MaxValue - m_MinValue) : 0.0f; - float x = m_CachedActualSize.left + ratio * (m_CachedActualSize.GetWidth() - m_ButtonSide); - float y = m_CachedActualSize.top + (m_CachedActualSize.GetHeight() - m_ButtonSide) / 2.0; - return CRect(x, y, x + m_ButtonSide, y + m_ButtonSide); -} Index: ps/trunk/source/gui/CText.h =================================================================== --- ps/trunk/source/gui/CText.h +++ ps/trunk/source/gui/CText.h @@ -1,102 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_CTEXT -#define INCLUDED_CTEXT - -#include "gui/CGUISprite.h" -#include "gui/CGUIString.h" -#include "gui/IGUIScrollBarOwner.h" -#include "gui/IGUITextOwner.h" - -/** - * Text field that just displays static text. - */ -class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner -{ - GUI_OBJECT(CText) - -public: - CText(CGUI& pGUI); - virtual ~CText(); - - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates(); - - /** - * @see IGUIObject#UpdateCachedSize() - */ - virtual void UpdateCachedSize(); - - /** - * Test if mouse position is over an icon - */ - virtual bool MouseOverIcon(); - -protected: - /** - * Sets up text, should be called every time changes has been - * made that can change the visual. - */ - void SetupText(); - - virtual void RegisterScriptFunctions(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Draws the Text - */ - virtual void Draw(); - - /** - * Script accessors to this GUI object. - */ - static JSFunctionSpec JSI_methods[]; - - static bool GetTextSize(JSContext* cx, uint argc, JS::Value* vp); - - /** - * Placement of text. Ignored when scrollbars are active. - */ - CPos m_TextPos; - - // Settings - float m_BufferZone; - CGUIString m_Caption; - i32 m_CellID; - bool m_Clip; - CStrW m_Font; - bool m_ScrollBar; - CStr m_ScrollBarStyle; - bool m_ScrollBottom; - bool m_ScrollTop; - CGUISpriteInstance m_Sprite; - EAlign m_TextAlign; - EVAlign m_TextVAlign; - CGUIColor m_TextColor; - CGUIColor m_TextColorDisabled; - CStrW m_IconTooltip; - CStr m_IconTooltipStyle; -}; - -#endif // INCLUDED_CTEXT Index: ps/trunk/source/gui/CText.cpp =================================================================== --- ps/trunk/source/gui/CText.cpp +++ ps/trunk/source/gui/CText.cpp @@ -1,284 +0,0 @@ -/* 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 "CText.h" - -#include "gui/CGUI.h" -#include "gui/CGUIScrollBarVertical.h" -#include "gui/CGUIText.h" -#include "scriptinterface/ScriptInterface.h" - -CText::CText(CGUI& pGUI) - : IGUIObject(pGUI), - IGUIScrollBarOwner(*static_cast(this)), - IGUITextOwner(*static_cast(this)), - m_BufferZone(), - m_Caption(), - m_CellID(), - m_Clip(), - m_Font(), - m_ScrollBar(), - m_ScrollBarStyle(), - m_ScrollBottom(), - m_ScrollTop(), - m_Sprite(), - m_TextAlign(), - m_TextVAlign(), - m_TextColor(), - m_TextColorDisabled(), - m_IconTooltip(), - m_IconTooltipStyle() -{ - RegisterSetting("buffer_zone", m_BufferZone); - RegisterSetting("caption", m_Caption); - RegisterSetting("cell_id", m_CellID); - RegisterSetting("clip", m_Clip); - RegisterSetting("font", m_Font); - RegisterSetting("scrollbar", m_ScrollBar); - RegisterSetting("scrollbar_style", m_ScrollBarStyle); - RegisterSetting("scroll_bottom", m_ScrollBottom); - RegisterSetting("scroll_top", m_ScrollTop); - RegisterSetting("sprite", m_Sprite); - RegisterSetting("text_align", m_TextAlign); - RegisterSetting("text_valign", m_TextVAlign); - RegisterSetting("textcolor", m_TextColor); - RegisterSetting("textcolor_disabled", m_TextColorDisabled); - // Private settings - RegisterSetting("_icon_tooltip", m_IconTooltip); - RegisterSetting("_icon_tooltip_style", m_IconTooltipStyle); - - //SetSetting("ghost", true, true); - SetSetting("scrollbar", false, true); - SetSetting("clip", true, true); - - // Add scroll-bar - CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); - bar->SetRightAligned(true); - AddScrollBar(bar); - - // Add text - AddText(); -} - -CText::~CText() -{ -} - -void CText::SetupText() -{ - if (m_GeneratedTexts.empty()) - return; - - float width = m_CachedActualSize.GetWidth(); - // remove scrollbar if applicable - if (m_ScrollBar && GetScrollBar(0).GetStyle()) - width -= GetScrollBar(0).GetStyle()->m_Width; - - m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, width, m_BufferZone, this); - - if (!m_ScrollBar) - CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]); - - // Setup scrollbar - if (m_ScrollBar) - { - // If we are currently scrolled to the bottom of the text, - // then add more lines of text, update the scrollbar so we - // stick to the bottom. - // (Use 1.5px delta so this triggers the first time caption is set) - bool bottom = false; - if (m_ScrollBottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f) - bottom = true; - - GetScrollBar(0).SetScrollRange(m_GeneratedTexts[0].GetSize().cy); - GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); - - GetScrollBar(0).SetX(m_CachedActualSize.right); - GetScrollBar(0).SetY(m_CachedActualSize.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); - - if (bottom) - GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos()); - - if (m_ScrollTop) - GetScrollBar(0).SetPos(0.0f); - } -} - -void CText::ResetStates() -{ - IGUIObject::ResetStates(); - IGUIScrollBarOwner::ResetStates(); -} - -void CText::UpdateCachedSize() -{ - IGUIObject::UpdateCachedSize(); - IGUITextOwner::UpdateCachedSize(); -} - -void CText::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - IGUIScrollBarOwner::HandleMessage(Message); - //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! - - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - if (Message.value == "scrollbar") - SetupText(); - - // Update scrollbar - if (Message.value == "scrollbar_style") - { - GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); - SetupText(); - } - - break; - - case GUIM_MOUSE_WHEEL_DOWN: - { - GetScrollBar(0).ScrollPlus(); - // Since the scroll was changed, let's simulate a mouse movement - // to check if scrollbar now is hovered - SGUIMessage msg(GUIM_MOUSE_MOTION); - HandleMessage(msg); - break; - } - case GUIM_MOUSE_WHEEL_UP: - { - GetScrollBar(0).ScrollMinus(); - // Since the scroll was changed, let's simulate a mouse movement - // to check if scrollbar now is hovered - SGUIMessage msg(GUIM_MOUSE_MOTION); - HandleMessage(msg); - break; - } - case GUIM_LOAD: - { - GetScrollBar(0).SetX(m_CachedActualSize.right); - GetScrollBar(0).SetY(m_CachedActualSize.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); - GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); - break; - } - - default: - break; - } - - IGUITextOwner::HandleMessage(Message); -} - -void CText::Draw() -{ - float bz = GetBufferedZ(); - - if (m_ScrollBar) - IGUIScrollBarOwner::Draw(); - - m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, m_CachedActualSize); - - float scroll = 0.f; - if (m_ScrollBar) - scroll = GetScrollBar(0).GetPos(); - - // Clipping area (we'll have to subtract the scrollbar) - CRect cliparea; - if (m_Clip) - { - cliparea = m_CachedActualSize; - - if (m_ScrollBar) - { - // subtract scrollbar from cliparea - if (cliparea.right > GetScrollBar(0).GetOuterRect().left && - cliparea.right <= GetScrollBar(0).GetOuterRect().right) - cliparea.right = GetScrollBar(0).GetOuterRect().left; - - if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && - cliparea.left < GetScrollBar(0).GetOuterRect().right) - cliparea.left = GetScrollBar(0).GetOuterRect().right; - } - } - - const CGUIColor& color = m_Enabled ? m_TextColor : m_TextColorDisabled; - - if (m_ScrollBar) - DrawText(0, color, m_CachedActualSize.TopLeft() - CPos(0.f, scroll), bz + 0.1f, cliparea); - else - DrawText(0, color, m_TextPos, bz + 0.1f, cliparea); -} - -bool CText::MouseOverIcon() -{ - for (const CGUIText& guitext : m_GeneratedTexts) - for (const CGUIText::SSpriteCall& spritecall : guitext.GetSpriteCalls()) - { - // Check mouse over sprite - if (!spritecall.m_Area.PointInside(m_pGUI.GetMousePos() - m_CachedActualSize.TopLeft())) - continue; - - // If tooltip exists, set the property - if (!spritecall.m_Tooltip.empty()) - { - SetSettingFromString("_icon_tooltip_style", spritecall.m_TooltipStyle, true); - SetSettingFromString("_icon_tooltip", spritecall.m_Tooltip, true); - } - - return true; - } - - return false; -} - -void CText::RegisterScriptFunctions() -{ - JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); - JSAutoRequest rq(cx); - JS_DefineFunctions(cx, m_JSObject, CText::JSI_methods); -} - -JSFunctionSpec CText::JSI_methods[] = -{ - JS_FN("getTextSize", CText::GetTextSize, 0, 0), - JS_FS_END -}; - -bool CText::GetTextSize(JSContext* cx, uint argc, JS::Value* vp) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - CText* thisObj = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!thisObj) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "This is not a CText object!"); - return false; - } - - thisObj->UpdateText(); - - ScriptInterface::ToJSVal(cx, args.rval(), thisObj->m_GeneratedTexts[0].GetSize()); - return true; -} Index: ps/trunk/source/gui/CTooltip.h =================================================================== --- ps/trunk/source/gui/CTooltip.h +++ ps/trunk/source/gui/CTooltip.h @@ -1,68 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_CTOOLTIP -#define INCLUDED_CTOOLTIP - -#include "gui/IGUITextOwner.h" -#include "gui/CGUISprite.h" -#include "gui/CGUIString.h" - -/** - * Dynamic tooltips. Similar to CText. - */ -class CTooltip : public IGUIObject, public IGUITextOwner -{ - GUI_OBJECT(CTooltip) - -public: - CTooltip(CGUI& pGUI); - virtual ~CTooltip(); - -protected: - void SetupText(); - - /** - * @see IGUIObject#UpdateCachedSize() - */ - void UpdateCachedSize(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - virtual void Draw(); - - // Settings - float m_BufferZone; - CGUIString m_Caption; - CStrW m_Font; - CGUISpriteInstance m_Sprite; - i32 m_Delay; - CGUIColor m_TextColor; - float m_MaxWidth; - CPos m_Offset; - EVAlign m_Anchor; - EAlign m_TextAlign; - bool m_Independent; - CPos m_MousePos; - CStr m_UseObject; - bool m_HideObject; -}; - -#endif // INCLUDED_CTOOLTIP Index: ps/trunk/source/gui/CTooltip.cpp =================================================================== --- ps/trunk/source/gui/CTooltip.cpp +++ ps/trunk/source/gui/CTooltip.cpp @@ -1,163 +0,0 @@ -/* 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 "CTooltip.h" - -#include "gui/CGUI.h" -#include "gui/CGUIString.h" -#include "gui/CGUIText.h" - -#include - -CTooltip::CTooltip(CGUI& pGUI) - : IGUIObject(pGUI), - IGUITextOwner(*static_cast(this)), - m_BufferZone(), - m_Caption(), - m_Font(), - m_Sprite(), - m_Delay(), - m_TextColor(), - m_MaxWidth(), - m_Offset(), - m_Anchor(), - m_TextAlign(), - m_Independent(), - m_MousePos(), - m_UseObject(), - m_HideObject() -{ - // If the tooltip is an object by itself: - RegisterSetting("buffer_zone", m_BufferZone); - RegisterSetting("caption", m_Caption); - RegisterSetting("font", m_Font); - RegisterSetting("sprite", m_Sprite); - RegisterSetting("delay", m_Delay); // in milliseconds - RegisterSetting("textcolor", m_TextColor); - RegisterSetting("maxwidth", m_MaxWidth); - RegisterSetting("offset", m_Offset); - RegisterSetting("anchor", m_Anchor); - RegisterSetting("text_align", m_TextAlign); - // This is used for tooltips that are hidden/revealed manually by scripts, rather than through the standard tooltip display mechanism - RegisterSetting("independent", m_Independent); - // Private settings: - // This is set by GUITooltip - RegisterSetting("_mousepos", m_MousePos); - // If the tooltip is just a reference to another object: - RegisterSetting("use_object", m_UseObject); - RegisterSetting("hide_object", m_HideObject); - - // Defaults - SetSetting("delay", 500, true); - SetSetting("anchor", EVAlign_Bottom, true); - SetSetting("text_align", EAlign_Left, true); - - // Set up a blank piece of text, to be replaced with a more - // interesting message later - AddText(); -} - -CTooltip::~CTooltip() -{ -} - -void CTooltip::SetupText() -{ - ENSURE(m_GeneratedTexts.size() == 1); - - m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, m_MaxWidth, m_BufferZone, this); - - // Position the tooltip relative to the mouse: - - const CPos& mousepos = m_Independent ? m_pGUI.GetMousePos() : m_MousePos; - - float textwidth = m_GeneratedTexts[0].GetSize().cx; - float textheight = m_GeneratedTexts[0].GetSize().cy; - - CGUISize size; - size.pixel.left = mousepos.x + m_Offset.x; - size.pixel.right = size.pixel.left + textwidth; - - switch (m_Anchor) - { - case EVAlign_Top: - size.pixel.top = mousepos.y + m_Offset.y; - size.pixel.bottom = size.pixel.top + textheight; - break; - case EVAlign_Bottom: - size.pixel.bottom = mousepos.y + m_Offset.y; - size.pixel.top = size.pixel.bottom - textheight; - break; - case EVAlign_Center: - size.pixel.top = mousepos.y + m_Offset.y - textheight/2.f; - size.pixel.bottom = size.pixel.top + textwidth; - break; - default: - debug_warn(L"Invalid EVAlign!"); - } - - - // Reposition the tooltip if it's falling off the screen: - - extern int g_xres, g_yres; - extern float g_GuiScale; - float screenw = g_xres / g_GuiScale; - float screenh = g_yres / g_GuiScale; - - if (size.pixel.top < 0.f) - size.pixel.bottom -= size.pixel.top, size.pixel.top = 0.f; - else if (size.pixel.bottom > screenh) - size.pixel.top -= (size.pixel.bottom-screenh), size.pixel.bottom = screenh; - - if (size.pixel.left < 0.f) - size.pixel.right -= size.pixel.left, size.pixel.left = 0.f; - else if (size.pixel.right > screenw) - size.pixel.left -= (size.pixel.right-screenw), size.pixel.right = screenw; - - SetSetting("size", size, true); -} - -void CTooltip::UpdateCachedSize() -{ - IGUIObject::UpdateCachedSize(); - IGUITextOwner::UpdateCachedSize(); -} - -void CTooltip::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - IGUITextOwner::HandleMessage(Message); -} - -void CTooltip::Draw() -{ - float z = 900.f; // TODO: Find a nicer way of putting the tooltip on top of everything else - - // Normally IGUITextOwner will handle this updating but since SetupText can modify the position - // we need to call it now *before* we do the rest of the drawing - if (!m_GeneratedTextsValid) - { - SetupText(); - m_GeneratedTextsValid = true; - } - - m_pGUI.DrawSprite(m_Sprite, 0, z, m_CachedActualSize); - - DrawText(0, m_TextColor, m_CachedActualSize.TopLeft(), z + 0.1f); -} Index: ps/trunk/source/gui/EAlign.h =================================================================== --- ps/trunk/source/gui/EAlign.h +++ ps/trunk/source/gui/EAlign.h @@ -1,24 +0,0 @@ -/* 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_EALIGN -#define INCLUDED_EALIGN - -enum EAlign { EAlign_Left, EAlign_Right, EAlign_Center }; -enum EVAlign { EVAlign_Top, EVAlign_Bottom, EVAlign_Center }; - -#endif // INCLUDED_EALIGN Index: ps/trunk/source/gui/GUIRenderer.h =================================================================== --- ps/trunk/source/gui/GUIRenderer.h +++ ps/trunk/source/gui/GUIRenderer.h @@ -20,7 +20,7 @@ #include "graphics/ShaderTechnique.h" #include "graphics/Texture.h" -#include "gui/CGUIColor.h" +#include "gui/SettingTypes/CGUIColor.h" #include "lib/res/handle.h" #include "ps/CStr.h" #include "ps/Shapes.h" Index: ps/trunk/source/gui/GUIRenderer.cpp =================================================================== --- ps/trunk/source/gui/GUIRenderer.cpp +++ ps/trunk/source/gui/GUIRenderer.cpp @@ -22,9 +22,9 @@ #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "gui/CGUI.h" -#include "gui/CGUIColor.h" #include "gui/CGUISprite.h" #include "gui/GUIMatrix.h" +#include "gui/SettingTypes/CGUIColor.h" #include "i18n/L10n.h" #include "lib/ogl.h" #include "lib/res/h_mgr.h" Index: ps/trunk/source/gui/GUISettingTypes.h =================================================================== --- ps/trunk/source/gui/GUISettingTypes.h +++ ps/trunk/source/gui/GUISettingTypes.h @@ -0,0 +1,50 @@ +/* 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 . + */ + +/* +This file is used by all bits of GUI code that need to repeat some code +for a variety of types (to avoid repeating the list of types in half a dozen +places, and to make it much easier to add a new type). Just do + #define TYPE(T) your_code_involving_T; + #include "gui/SettingTypes/GUISettingTypes.h" + #undef TYPE +to handle every possible type. +*/ + +#ifndef GUITYPE_IGNORE_COPYABLE +#include "gui/SettingTypes/EAlign.h" +TYPE(bool) +TYPE(i32) +TYPE(u32) +TYPE(float) +TYPE(EAlign) +TYPE(EVAlign) +TYPE(CPos) +#endif + +#ifndef GUITYPE_IGNORE_NONCOPYABLE +#include "gui/SettingTypes/CGUIList.h" +#include "gui/SettingTypes/CGUISeries.h" +TYPE(CGUISize) +TYPE(CGUIColor) +TYPE(CGUIList) +TYPE(CGUISeries) +TYPE(CGUISpriteInstance) +TYPE(CGUIString) +TYPE(CStr) +TYPE(CStrW) +#endif Index: ps/trunk/source/gui/GUIStringConversions.cpp =================================================================== --- ps/trunk/source/gui/GUIStringConversions.cpp +++ ps/trunk/source/gui/GUIStringConversions.cpp @@ -18,7 +18,7 @@ #include "precompiled.h" #include "gui/CGUI.h" -#include "gui/CGUIString.h" +#include "gui/SettingTypes/CGUIString.h" #include "ps/CLogger.h" class CGUIList; Index: ps/trunk/source/gui/GUITooltip.cpp =================================================================== --- ps/trunk/source/gui/GUITooltip.cpp +++ ps/trunk/source/gui/GUITooltip.cpp @@ -20,7 +20,7 @@ #include "GUITooltip.h" #include "gui/CGUI.h" -#include "gui/IGUIObject.h" +#include "gui/ObjectBases/IGUIObject.h" #include "lib/timer.h" #include "ps/CLogger.h" Index: ps/trunk/source/gui/GUItypes.h =================================================================== --- ps/trunk/source/gui/GUItypes.h +++ ps/trunk/source/gui/GUItypes.h @@ -1,50 +0,0 @@ -/* 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 . - */ - -/* -This file is used by all bits of GUI code that need to repeat some code -for a variety of types (to avoid repeating the list of types in half a dozen -places, and to make it much easier to add a new type). Just do - #define TYPE(T) your_code_involving_T; - #include "GUItypes.h" - #undef TYPE -to handle every possible type. -*/ - -#ifndef GUITYPE_IGNORE_COPYABLE -#include "gui/EAlign.h" -TYPE(bool) -TYPE(i32) -TYPE(u32) -TYPE(float) -TYPE(EAlign) -TYPE(EVAlign) -TYPE(CPos) -#endif - -#ifndef GUITYPE_IGNORE_NONCOPYABLE -#include "gui/CGUIList.h" -#include "gui/CGUISeries.h" -TYPE(CGUISize) -TYPE(CGUIColor) -TYPE(CGUIList) -TYPE(CGUISeries) -TYPE(CGUISpriteInstance) -TYPE(CGUIString) -TYPE(CStr) -TYPE(CStrW) -#endif Index: ps/trunk/source/gui/IGUIButtonBehavior.h =================================================================== --- ps/trunk/source/gui/IGUIButtonBehavior.h +++ ps/trunk/source/gui/IGUIButtonBehavior.h @@ -1,93 +0,0 @@ -/* 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 . - */ - -/* - Interface class that enhance the IGUIObject with - buttony behavior (click and release to click a button), - and the GUI message GUIM_PRESSED. - When creating a class with extended settings and - buttony behavior, just do a multiple inheritance. -*/ - -#ifndef INCLUDED_IGUIBUTTONBEHAVIOR -#define INCLUDED_IGUIBUTTONBEHAVIOR - -#include "gui/IGUIObject.h" - -class CGUISpriteInstance; - -/** - * Appends button behaviours to the IGUIObject. - * Can be used with multiple inheritance alongside - * IGUISettingsObject and such. - */ -class IGUIButtonBehavior -{ - NONCOPYABLE(IGUIButtonBehavior); - -public: - IGUIButtonBehavior(IGUIObject& pObject); - virtual ~IGUIButtonBehavior(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * This is a function that lets a button being drawn, - * it regards if it's over, disabled, pressed and such. - * - * @param sprite Sprite drawn when not pressed, hovered or disabled - * @param sprite_over Sprite drawn when m_MouseHovering is true - * @param sprite_pressed Sprite drawn when m_Pressed is true - * @param sprite_disabled Sprite drawn when "enabled" is false - */ - const CGUISpriteInstance& GetButtonSprite(const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_over, const CGUISpriteInstance& sprite_pressed, const CGUISpriteInstance& sprite_disabled) const; - -protected: - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates(); - - /** - * Everybody knows how a button works, you don't simply press it, - * you have to first press the button, and then release it... - * in between those two steps you can actually leave the button - * area, as long as you release it within the button area... Anyway - * this lets us know we are done with step one (clicking). - */ - bool m_Pressed; - bool m_PressedRight; - - // Settings - CStrW m_SoundDisabled; - CStrW m_SoundEnter; - CStrW m_SoundLeave; - CStrW m_SoundPressed; - CStrW m_SoundReleased; - -private: - /** - * Reference to the IGUIObject. - * Private, because we don't want to inherit it in multiple classes. - */ - IGUIObject& m_pObject; -}; - -#endif // INCLUDED_IGUIBUTTONBEHAVIOR Index: ps/trunk/source/gui/IGUIButtonBehavior.cpp =================================================================== --- ps/trunk/source/gui/IGUIButtonBehavior.cpp +++ ps/trunk/source/gui/IGUIButtonBehavior.cpp @@ -1,148 +0,0 @@ -/* 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 "IGUIButtonBehavior.h" - -#include "gui/CGUISprite.h" - -IGUIButtonBehavior::IGUIButtonBehavior(IGUIObject& pObject) - : m_pObject(pObject), - m_Pressed(), - m_PressedRight(), - m_SoundDisabled(), - m_SoundEnter(), - m_SoundLeave(), - m_SoundPressed(), - m_SoundReleased() -{ - m_pObject.RegisterSetting("sound_disabled", m_SoundDisabled); - m_pObject.RegisterSetting("sound_enter", m_SoundEnter); - m_pObject.RegisterSetting("sound_leave", m_SoundLeave); - m_pObject.RegisterSetting("sound_pressed", m_SoundPressed); - m_pObject.RegisterSetting("sound_released", m_SoundReleased); -} - -IGUIButtonBehavior::~IGUIButtonBehavior() -{ -} - -void IGUIButtonBehavior::ResetStates() -{ - m_Pressed = false; - m_PressedRight = false; -} - -void IGUIButtonBehavior::HandleMessage(SGUIMessage& Message) -{ - // TODO Gee: easier access functions - switch (Message.type) - { - case GUIM_MOUSE_ENTER: - if (m_pObject.IsEnabled()) - m_pObject.PlaySound(m_SoundEnter); - break; - - case GUIM_MOUSE_LEAVE: - if (m_pObject.IsEnabled()) - m_pObject.PlaySound(m_SoundLeave); - break; - - case GUIM_MOUSE_DBLCLICK_LEFT: - if (!m_pObject.IsEnabled()) - break; - - // Since GUIM_MOUSE_PRESS_LEFT also gets called twice in a - // doubleclick event, we let it handle playing sounds. - m_pObject.SendEvent(GUIM_DOUBLE_PRESSED, "doublepress"); - break; - - case GUIM_MOUSE_PRESS_LEFT: - if (!m_pObject.IsEnabled()) - { - m_pObject.PlaySound(m_SoundDisabled); - break; - } - - m_pObject.PlaySound(m_SoundPressed); - m_pObject.SendEvent(GUIM_PRESSED, "press"); - m_Pressed = true; - break; - - case GUIM_MOUSE_DBLCLICK_RIGHT: - if (!m_pObject.IsEnabled()) - break; - - // Since GUIM_MOUSE_PRESS_RIGHT also gets called twice in a - // doubleclick event, we let it handle playing sounds. - m_pObject.SendEvent(GUIM_DOUBLE_PRESSED_MOUSE_RIGHT, "doublepressright"); - break; - - case GUIM_MOUSE_PRESS_RIGHT: - if (!m_pObject.IsEnabled()) - { - m_pObject.PlaySound(m_SoundDisabled); - break; - } - - // Button was right-clicked - m_pObject.PlaySound(m_SoundPressed); - m_pObject.SendEvent(GUIM_PRESSED_MOUSE_RIGHT, "pressright"); - m_PressedRight = true; - break; - - case GUIM_MOUSE_RELEASE_RIGHT: - if (!m_pObject.IsEnabled()) - break; - - if (m_PressedRight) - { - m_PressedRight = false; - m_pObject.PlaySound(m_SoundReleased); - } - break; - - case GUIM_MOUSE_RELEASE_LEFT: - if (!m_pObject.IsEnabled()) - break; - - if (m_Pressed) - { - m_Pressed = false; - m_pObject.PlaySound(m_SoundReleased); - } - break; - - default: - break; - } -} - -const CGUISpriteInstance& IGUIButtonBehavior::GetButtonSprite(const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_over, const CGUISpriteInstance& sprite_pressed, const CGUISpriteInstance& sprite_disabled) const -{ - if (!m_pObject.IsEnabled()) - return sprite_disabled || sprite; - - if (!m_pObject.IsMouseOver()) - return sprite; - - if (m_Pressed) - return sprite_pressed || sprite; - - return sprite_over || sprite; -} Index: ps/trunk/source/gui/IGUIObject.h =================================================================== --- ps/trunk/source/gui/IGUIObject.h +++ ps/trunk/source/gui/IGUIObject.h @@ -1,536 +0,0 @@ -/* 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 . - */ - -/* - * The base class of an object. - * All objects are derived from this class. - * It's an abstract data type, so it can't be used per se. - * Also contains a Dummy object which is used for completely blank objects. - */ - -#ifndef INCLUDED_IGUIOBJECT -#define INCLUDED_IGUIOBJECT - -#include "gui/CGUISize.h" -#include "gui/scripting/JSInterface_IGUIObject.h" -#include "gui/SGUIMessage.h" -#include "lib/input.h" // just for IN_PASS -#include "ps/XML/Xeromyces.h" - -#include -#include -#include - -class CGUI; -class IGUIObject; -class IGUISetting; - -using map_pObjects = std::map; - -#define GUI_OBJECT(obj) \ -public: \ - static IGUIObject* ConstructObject(CGUI& pGUI) \ - { return new obj(pGUI); } - -/** - * GUI object such as a button or an input-box. - * Abstract data type ! - */ -class IGUIObject -{ - friend class CGUI; - - // Allow getProperty to access things like GetParent() - friend bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); - friend bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); - friend bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp); - -public: - NONCOPYABLE(IGUIObject); - - IGUIObject(CGUI& pGUI); - virtual ~IGUIObject(); - - /** - * Checks if mouse is hovering this object. - * The mouse position is cached in CGUI. - * - * This function checks if the mouse is hovering the - * rectangle that the base setting "size" makes. - * Although it is virtual, so one could derive - * an object from CButton, which changes only this - * to checking the circle that "size" makes. - * - * @return true if mouse is hovering - */ - virtual bool IsMouseOver() const; - - /** - * Test if mouse position is over an icon - */ - virtual bool MouseOverIcon(); - - //-------------------------------------------------------- - /** @name Leaf Functions */ - //-------------------------------------------------------- - //@{ - - /// Get object name, name is unique - const CStr& GetName() const { return m_Name; } - - /// Get object name - void SetName(const CStr& Name) { m_Name = Name; } - - // Get Presentable name. - // Will change all internally set names to something like "" - CStr GetPresentableName() const; - - /** - * Adds object and its children to the map, it's name being the - * first part, and the second being itself. - * - * @param ObjectMap Adds this to the map_pObjects. - * - * @throws PSERROR_GUI_ObjectNeedsName Name is missing - * @throws PSERROR_GUI_NameAmbiguity Name is already taken - */ - void AddToPointersMap(map_pObjects& ObjectMap); - - /** - * Notice nothing will be returned or thrown if the child hasn't - * been inputted into the GUI yet. This is because that's were - * all is checked. Now we're just linking two objects, but - * it's when we're inputting them into the GUI we'll check - * validity! Notice also when adding it to the GUI this function - * will inevitably have been called by CGUI::AddObject which - * will catch the throw and return the error code. - * i.e. The user will never put in the situation wherein a throw - * must be caught, the GUI's internal error handling will be - * completely transparent to the interfacially sequential model. - * - * @param pChild Child to add - * - * @throws PSERROR_GUI from CGUI::UpdateObjects(). - */ - void AddChild(IGUIObject* pChild); - - /** - * Return all child objects of the current object. - */ - const std::vector& GetChildren() const { return m_Children; } - - //@} - //-------------------------------------------------------- - /** @name Settings Management */ - //-------------------------------------------------------- - //@{ - - /** - * Registers the given setting variables with the GUI object. - * Enable XML and JS to modify the given variable. - * - * @param Type Setting type - * @param Name Setting reference name - */ - template - void RegisterSetting(const CStr& Name, T& Value); - - /** - * Returns whether there is a setting with the given name registered. - * - * @param Setting setting name - * @return True if settings exist. - */ - bool SettingExists(const CStr& Setting) const; - - /** - * Get a mutable reference to the setting. - * If no such setting exists, an exception of type std::out_of_range is thrown. - * If the value is modified, there is no GUIM_SETTINGS_UPDATED message sent. - */ - template - T& GetSetting(const CStr& Setting); - - template - const T& GetSetting(const CStr& Setting) const; - - /** - * Set a setting by string, regardless of what type it is. - * Used to parse setting values from XML files. - * For example a CRect(10,10,20,20) is created from "10 10 20 20". - * Returns false if the conversion fails, otherwise true. - */ - bool SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage); - - /** - * Assigns the given value to the setting identified by the given name. - * Uses move semantics, so do not read from Value after this call. - * - * @param SendMessage If true, a GUIM_SETTINGS_UPDATED message will be broadcasted to all GUI objects. - */ - template - void SetSetting(const CStr& Setting, T& Value, const bool SendMessage); - - /** - * This variant will copy the value. - */ - template - void SetSetting(const CStr& Setting, const T& Value, const bool SendMessage); - - /** - * Returns whether this object is set to be hidden or ghost. - */ - bool IsEnabled() const; - - /** - * Returns whether this is object is set to be hidden. - */ - bool IsHidden() const; - - /** - * Returns whether this object is set to be hidden or ghost. - */ - bool IsHiddenOrGhost() const; - - /** - * Retrieves the configured sound filename from the given setting name and plays that once. - */ - void PlaySound(const CStrW& soundPath) const; - - /** - * Send event to this GUI object (HandleMessage and ScriptEvent) - * - * @param type Type of GUI message to be handled - * @param EventName String representation of event name - * @return IN_HANDLED if event was handled, or IN_PASS if skipped - */ - InReaction SendEvent(EGUIMessageType type, const CStr& EventName); - - /** - * All sizes are relative to resolution, and the calculation - * is not wanted in real time, therefore it is cached, update - * the cached size with this function. - */ - virtual void UpdateCachedSize(); - - /** - * Reset internal state of this object. - */ - virtual void ResetStates(); - - /** - * Set the script handler for a particular object-specific action - * - * @param Action Name of action - * @param Code Javascript code to execute when the action occurs - * @param pGUI GUI instance to associate the script with - */ - void RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI& pGUI); - - /** - * Inheriting classes may append JS functions to the JS object representing this class. - */ - virtual void RegisterScriptFunctions() {} - - /** - * Retrieves the JSObject representing this GUI object. - */ - JSObject* GetJSObject(); - - //@} -protected: - //-------------------------------------------------------- - /** @name Called by CGUI and friends - * - * Methods that the CGUI will call using - * its friendship, these should not - * be called by user. - * These functions' security are a lot - * what constitutes the GUI's - */ - //-------------------------------------------------------- - //@{ - -public: - /** - * This function is called with different messages - * for instance when the mouse enters the object. - * - * @param Message GUI Message - */ - virtual void HandleMessage(SGUIMessage& UNUSED(Message)) {} - - /** - * Calls an IGUIObject member function recursively on this object and its children. - * Aborts recursion at IGUIObjects that have the isRestricted function return true. - * The arguments of the callback function must be references. - */ - template - void RecurseObject(bool(IGUIObject::*isRestricted)() const, void(IGUIObject::*callbackFunction)(Args... args), Args&&... args) - { - if (!IsBaseObject()) - { - if (isRestricted && (this->*isRestricted)()) - return; - - (this->*callbackFunction)(args...); - } - - for (IGUIObject* const& obj : m_Children) - obj->RecurseObject(isRestricted, callbackFunction, args...); - } - -protected: - /** - * Draws the object. - * - * @throws PSERROR if any. But this will mostlikely be - * very rare since if an object is drawn unsuccessfully - * it'll probably only output in the Error log, and not - * disrupt the whole GUI drawing. - */ - virtual void Draw() = 0; - - /** - * Some objects need to handle the SDL_Event_ manually. - * For instance the input box. - * - * Only the object with focus will have this function called. - * - * Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then - * the key won't be passed on and processed by other handlers. - * This is used for keys that the GUI uses. - */ - virtual InReaction ManuallyHandleEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; } - - /** - * Loads a style. - */ - void LoadStyle(const CStr& StyleName); - - /** - * Returns not the Z value, but the actual buffered Z value, i.e. if it's - * defined relative, then it will check its parent's Z value and add - * the relativity. - * - * @return Actual Z value on the screen. - */ - virtual float GetBufferedZ() const; - - /** - * Set parent of this object - */ - void SetParent(IGUIObject* pParent) { m_pParent = pParent; } - -public: - - CGUI& GetGUI() { return m_pGUI; } - const CGUI& GetGUI() const { return m_pGUI; } - - /** - * Take focus! - */ - void SetFocus(); - -protected: - /** - * Check if object is focused. - */ - bool IsFocused() const; - - /** - * NOTE! This will not just return m_pParent, when that is - * need use it! There is one exception to it, when the parent is - * the top-node (the object that isn't a real object), this - * will return nullptr, so that the top-node's children are - * seemingly parentless. - * - * @return Pointer to parent - */ - IGUIObject* GetParent() const; - - /** - * Handle additional children to the \-tag. In IGUIObject, this function does - * nothing. In CList and CDropDown, it handles the \, used to build the data. - * - * Returning false means the object doesn't recognize the child. Should be reported. - * Notice 'false' is default, because an object not using this function, should not - * have any additional children (and this function should never be called). - */ - virtual bool HandleAdditionalChildren(const XMBElement& UNUSED(child), CXeromyces* UNUSED(pFile)) - { - return false; - } - - /** - * Allow the GUI object to process after all child items were handled. - * Useful to avoid iterator invalidation with push_back calls. - */ - virtual void AdditionalChildrenHandled() {} - - /** - * Cached size, real size m_Size is actually dependent on resolution - * and can have different *real* outcomes, this is the real outcome - * cached to avoid slow calculations in real time. - */ - CRect m_CachedActualSize; - - /** - * Execute the script for a particular action. - * Does nothing if no script has been registered for that action. - * The mouse coordinates will be passed as the first argument. - * - * @param Action Name of action - */ - void ScriptEvent(const CStr& Action); - - /** - * Execute the script for a particular action. - * Does nothing if no script has been registered for that action. - * - * @param Action Name of action - * @param paramData JS::HandleValueArray arguments to pass to the event. - */ - void ScriptEvent(const CStr& Action, const JS::HandleValueArray& paramData); - - void SetScriptHandler(const CStr& Action, JS::HandleObject Function); - - /** - * Inputes the object that is currently hovered, this function - * updates this object accordingly (i.e. if it's the object - * being inputted one thing happens, and not, another). - * - * @param pMouseOver Object that is currently hovered, can be nullptr too! - */ - void UpdateMouseOver(IGUIObject* const& pMouseOver); - - //@} -private: - //-------------------------------------------------------- - /** @name Internal functions */ - //-------------------------------------------------------- - //@{ - - /** - * Creates the JS object representing this page upon first use. - */ - void CreateJSObject(); - - /** - * Updates some internal data depending on the setting changed. - */ - void PreSettingChange(const CStr& Setting); - void SettingChanged(const CStr& Setting, const bool SendMessage); - - /** - * Inputs a reference pointer, checks if the new inputted object - * if hovered, if so, then check if this's Z value is greater - * than the inputted object... If so then the object is closer - * and we'll replace the pointer with this. - * Also Notice input can be nullptr, which means the Z value demand - * is out. NOTICE you can't input nullptr as const so you'll have - * to set an object to nullptr. - * - * @param pObject Object pointer, can be either the old one, or - * the new one. - */ - void ChooseMouseOverAndClosest(IGUIObject*& pObject); - - /** - * Returns whether this is the object all other objects are descendants of. - */ - bool IsBaseObject() const; - - /** - * Returns whether this object is a child of the base object. - */ - bool IsRootObject() const; - - static void Trace(JSTracer* trc, void* data) - { - reinterpret_cast(data)->TraceMember(trc); - } - - void TraceMember(JSTracer* trc); - -// Variables -protected: - // Name of object - CStr m_Name; - - // Constructed on the heap, will be destroyed along with the the CGUI - std::vector m_Children; - - // Pointer to parent - IGUIObject* m_pParent; - - //This represents the last click time for each mouse button - double m_LastClickTime[6]; - - /** - * This is an array of true or false, each element is associated with - * a string representing a setting. Number of elements is equal to - * number of settings. - * - * A true means the setting has been manually set in the file when - * read. This is important to know because I don't want to force - * the user to include its \-XML-files first, so somehow - * the GUI needs to know which settings were set, and which is meant - * to. - */ - - // More variables - - // Is mouse hovering the object? used with the function IsMouseOver() - bool m_MouseHovering; - - /** - * Settings pool, all an object's settings are located here - * If a derived object has got more settings that the base - * settings, it's because they have a new version of the - * function SetupSettings(). - * - * @see SetupSettings() - */ -public: - std::map m_Settings; - -protected: - // An object can't function stand alone - CGUI& m_pGUI; - - // Internal storage for registered script handlers. - std::map > m_ScriptHandlers; - - // Cached JSObject representing this GUI object - JS::PersistentRootedObject m_JSObject; - - // Cache references to settings for performance - bool m_Enabled; - bool m_Hidden; - CGUISize m_Size; - CStr m_Style; - CStr m_Hotkey; - float m_Z; - bool m_Absolute; - bool m_Ghost; - float m_AspectRatio; - CStrW m_Tooltip; - CStr m_TooltipStyle; -}; - -#endif // INCLUDED_IGUIOBJECT Index: ps/trunk/source/gui/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/IGUIObject.cpp +++ ps/trunk/source/gui/IGUIObject.cpp @@ -1,550 +0,0 @@ -/* 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 "IGUIObject.h" - -#include "gui/CGUI.h" -#include "gui/CGUISetting.h" -#include "ps/CLogger.h" -#include "ps/GameSetup/Config.h" -#include "ps/Profile.h" -#include "scriptinterface/ScriptInterface.h" -#include "soundmanager/ISoundManager.h" - -IGUIObject::IGUIObject(CGUI& pGUI) - : m_pGUI(pGUI), - m_pParent(), - m_MouseHovering(), - m_LastClickTime(), - m_Enabled(), - m_Hidden(), - m_Size(), - m_Style(), - m_Hotkey(), - m_Z(), - m_Absolute(), - m_Ghost(), - m_AspectRatio(), - m_Tooltip(), - m_TooltipStyle() -{ - RegisterSetting("enabled", m_Enabled); - RegisterSetting("hidden", m_Hidden); - RegisterSetting("size", m_Size); - RegisterSetting("style", m_Style); - RegisterSetting("hotkey", m_Hotkey); - RegisterSetting("z", m_Z); - RegisterSetting("absolute", m_Absolute); - RegisterSetting("ghost", m_Ghost); - RegisterSetting("aspectratio", m_AspectRatio); - RegisterSetting("tooltip", m_Tooltip); - RegisterSetting("tooltip_style", m_TooltipStyle); - - // Setup important defaults - // TODO: Should be in the default style? - SetSetting("hidden", false, true); - SetSetting("ghost", false, true); - SetSetting("enabled", true, true); - SetSetting("absolute", true, true); -} - -IGUIObject::~IGUIObject() -{ - for (const std::pair& p : m_Settings) - delete p.second; - - if (!m_ScriptHandlers.empty()) - JS_RemoveExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetJSRuntime(), Trace, this); -} - -//------------------------------------------------------------------- -// Functions -//------------------------------------------------------------------- - -void IGUIObject::AddChild(IGUIObject* pChild) -{ -// ENSURE(pChild); - - pChild->SetParent(this); - - m_Children.push_back(pChild); - - { - try - { - // Atomic function, if it fails it won't - // have changed anything - //UpdateObjects(); - pChild->GetGUI().UpdateObjects(); - } - catch (PSERROR_GUI&) - { - // If anything went wrong, reverse what we did and throw - // an exception telling it never added a child - m_Children.erase(m_Children.end()-1); - - throw; - } - } - // else do nothing -} - -void IGUIObject::AddToPointersMap(map_pObjects& ObjectMap) -{ - // Just don't do anything about the top node - if (m_pParent == nullptr) - return; - - // Now actually add this one - // notice we won't add it if it's doesn't have any parent - // (i.e. being the base object) - if (m_Name.empty()) - { - throw PSERROR_GUI_ObjectNeedsName(); - } - - if (ObjectMap.find(m_Name) != ObjectMap.end()) - { - throw PSERROR_GUI_NameAmbiguity(m_Name.c_str()); - } - else - { - ObjectMap[m_Name] = this; - } -} - -template -void IGUIObject::RegisterSetting(const CStr& Name, T& Value) -{ - if (SettingExists(Name)) - LOGERROR("The setting '%s' already exists on the object '%s'!", Name.c_str(), GetPresentableName().c_str()); - else - m_Settings.emplace(Name, new CGUISetting(*this, Name, Value)); -} - -bool IGUIObject::SettingExists(const CStr& Setting) const -{ - return m_Settings.find(Setting) != m_Settings.end(); -} - -template -T& IGUIObject::GetSetting(const CStr& Setting) -{ - return static_cast* >(m_Settings.at(Setting))->m_pSetting; -} - -template -const T& IGUIObject::GetSetting(const CStr& Setting) const -{ - return static_cast* >(m_Settings.at(Setting))->m_pSetting; -} - -bool IGUIObject::SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage) -{ - const std::map::iterator it = m_Settings.find(Setting); - if (it == m_Settings.end()) - { - LOGERROR("GUI object '%s' has no property called '%s', can't set parse and set value '%s'", GetPresentableName().c_str(), Setting.c_str(), Value.ToUTF8().c_str()); - return false; - } - return it->second->FromString(Value, SendMessage); -} - -template -void IGUIObject::SetSetting(const CStr& Setting, T& Value, const bool SendMessage) -{ - PreSettingChange(Setting); - static_cast* >(m_Settings.at(Setting))->m_pSetting = std::move(Value); - SettingChanged(Setting, SendMessage); -} - -template -void IGUIObject::SetSetting(const CStr& Setting, const T& Value, const bool SendMessage) -{ - PreSettingChange(Setting); - static_cast* >(m_Settings.at(Setting))->m_pSetting = Value; - SettingChanged(Setting, SendMessage); -} - -void IGUIObject::PreSettingChange(const CStr& Setting) -{ - if (Setting == "hotkey") - m_pGUI.UnsetObjectHotkey(this, GetSetting(Setting)); -} - -void IGUIObject::SettingChanged(const CStr& Setting, const bool SendMessage) -{ - if (Setting == "size") - { - // If setting was "size", we need to re-cache itself and all children - RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); - } - else if (Setting == "hidden") - { - // Hiding an object requires us to reset it and all children - if (m_Hidden) - RecurseObject(nullptr, &IGUIObject::ResetStates); - } - else if (Setting == "hotkey") - m_pGUI.SetObjectHotkey(this, GetSetting(Setting)); - - if (SendMessage) - { - SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting); - HandleMessage(msg); - } -} - -bool IGUIObject::IsMouseOver() const -{ - return m_CachedActualSize.PointInside(m_pGUI.GetMousePos()); -} - -bool IGUIObject::MouseOverIcon() -{ - return false; -} - -void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver) -{ - if (pMouseOver == this) - { - if (!m_MouseHovering) - SendEvent(GUIM_MOUSE_ENTER, "mouseenter"); - - m_MouseHovering = true; - - SendEvent(GUIM_MOUSE_OVER, "mousemove"); - } - else - { - if (m_MouseHovering) - { - m_MouseHovering = false; - SendEvent(GUIM_MOUSE_LEAVE, "mouseleave"); - } - } -} - -void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject) -{ - if (!IsMouseOver()) - return; - - // Check if we've got competition at all - if (pObject == nullptr) - { - pObject = this; - return; - } - - // Or if it's closer - if (GetBufferedZ() >= pObject->GetBufferedZ()) - { - pObject = this; - return; - } -} - -IGUIObject* IGUIObject::GetParent() const -{ - // Important, we're not using GetParent() for these - // checks, that could screw it up - if (m_pParent && m_pParent->m_pParent == nullptr) - return nullptr; - - return m_pParent; -} - -void IGUIObject::ResetStates() -{ - // Notify the gui that we aren't hovered anymore - UpdateMouseOver(nullptr); -} - -void IGUIObject::UpdateCachedSize() -{ - // If absolute="false" and the object has got a parent, - // use its cached size instead of the screen. Notice - // it must have just been cached for it to work. - if (!m_Absolute && m_pParent && !IsRootObject()) - m_CachedActualSize = m_Size.GetSize(m_pParent->m_CachedActualSize); - else - m_CachedActualSize = m_Size.GetSize(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale)); - - // In a few cases, GUI objects have to resize to fill the screen - // but maintain a constant aspect ratio. - // Adjust the size to be the max possible, centered in the original size: - if (m_AspectRatio) - { - if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight() * m_AspectRatio) - { - float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight() * m_AspectRatio; - m_CachedActualSize.left += delta/2.f; - m_CachedActualSize.right -= delta/2.f; - } - else - { - float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth() / m_AspectRatio; - m_CachedActualSize.bottom -= delta/2.f; - m_CachedActualSize.top += delta/2.f; - } - } -} - -void IGUIObject::LoadStyle(const CStr& StyleName) -{ - if (!m_pGUI.HasStyle(StyleName)) - debug_warn(L"IGUIObject::LoadStyle failed"); - - // The default style may specify settings for any GUI object. - // Other styles are reported if they specify a Setting that does not exist, - // so that the XML author is informed and can correct the style. - - for (const std::pair& p : m_pGUI.GetStyle(StyleName).m_SettingsDefaults) - { - if (SettingExists(p.first)) - SetSettingFromString(p.first, p.second, true); - else if (StyleName != "default") - LOGWARNING("GUI object has no setting \"%s\", but the style \"%s\" defines it", p.first, StyleName.c_str()); - } -} - -float IGUIObject::GetBufferedZ() const -{ - if (m_Absolute) - return m_Z; - - if (GetParent()) - return GetParent()->GetBufferedZ() + m_Z; - - // In philosophy, a parentless object shouldn't be able to have a relative sizing, - // but we'll accept it so that absolute can be used as default without a complaint. - // Also, you could consider those objects children to the screen resolution. - return m_Z; -} - -void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI& pGUI) -{ - JSContext* cx = pGUI.GetScriptInterface()->GetContext(); - JSAutoRequest rq(cx); - JS::RootedValue globalVal(cx, pGUI.GetGlobalObject()); - JS::RootedObject globalObj(cx, &globalVal.toObject()); - - const int paramCount = 1; - const char* paramNames[paramCount] = { "mouse" }; - - // Location to report errors from - CStr CodeName = GetName()+" "+Action; - - // Generate a unique name - static int x = 0; - char buf[64]; - sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str()); - - JS::CompileOptions options(cx); - options.setFileAndLine(CodeName.c_str(), 0); - options.setIsRunOnce(false); - - JS::RootedFunction func(cx); - JS::AutoObjectVector emptyScopeChain(cx); - if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func)) - { - LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str()); - return; - } - - JS::RootedObject funcObj(cx, JS_GetFunctionObject(func)); - SetScriptHandler(Action, funcObj); -} - -void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function) -{ - if (m_ScriptHandlers.empty()) - JS_AddExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetJSRuntime(), Trace, this); - - m_ScriptHandlers[Action] = JS::Heap(Function); -} - -InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName) -{ - PROFILE2_EVENT("gui event"); - PROFILE2_ATTR("type: %s", EventName.c_str()); - PROFILE2_ATTR("object: %s", m_Name.c_str()); - - SGUIMessage msg(type); - HandleMessage(msg); - - ScriptEvent(EventName); - - return (msg.skipped ? IN_PASS : IN_HANDLED); -} - -void IGUIObject::ScriptEvent(const CStr& Action) -{ - std::map >::iterator it = m_ScriptHandlers.find(Action); - if (it == m_ScriptHandlers.end()) - return; - - JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); - JSAutoRequest rq(cx); - - // Set up the 'mouse' parameter - JS::RootedValue mouse(cx); - - const CPos& mousePos = m_pGUI.GetMousePos(); - - ScriptInterface::CreateObject( - cx, - &mouse, - "x", mousePos.x, - "y", mousePos.y, - "buttons", m_pGUI.GetMouseButtons()); - - JS::AutoValueVector paramData(cx); - paramData.append(mouse); - JS::RootedObject obj(cx, GetJSObject()); - JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); - JS::RootedValue result(cx); - bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result); - if (!ok) - { - // We have no way to propagate the script exception, so just ignore it - // and hope the caller checks JS_IsExceptionPending - } -} - -void IGUIObject::ScriptEvent(const CStr& Action, const JS::HandleValueArray& paramData) -{ - std::map >::iterator it = m_ScriptHandlers.find(Action); - if (it == m_ScriptHandlers.end()) - return; - - JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); - JSAutoRequest rq(cx); - JS::RootedObject obj(cx, GetJSObject()); - JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); - JS::RootedValue result(cx); - - if (!JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result)) - JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str()); -} - -void IGUIObject::CreateJSObject() -{ - JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); - JSAutoRequest rq(cx); - - m_JSObject.init(cx, m_pGUI.GetScriptInterface()->CreateCustomObject("GUIObject")); - JS_SetPrivate(m_JSObject.get(), this); - - RegisterScriptFunctions(); -} - -JSObject* IGUIObject::GetJSObject() -{ - // Cache the object when somebody first asks for it, because otherwise - // we end up doing far too much object allocation. - if (!m_JSObject.initialized()) - CreateJSObject(); - - return m_JSObject.get(); -} - -bool IGUIObject::IsEnabled() const -{ - return m_Enabled; -} - -bool IGUIObject::IsHidden() const -{ - return m_Hidden; -} - -bool IGUIObject::IsHiddenOrGhost() const -{ - return m_Hidden || m_Ghost; -} - -void IGUIObject::PlaySound(const CStrW& soundPath) const -{ - if (g_SoundManager && !soundPath.empty()) - g_SoundManager->PlayAsUI(soundPath.c_str(), false); -} - -CStr IGUIObject::GetPresentableName() const -{ - // __internal(), must be at least 13 letters to be able to be - // an internal name - if (m_Name.length() <= 12) - return m_Name; - - if (m_Name.substr(0, 10) == "__internal") - return CStr("[unnamed object]"); - else - return m_Name; -} - -void IGUIObject::SetFocus() -{ - m_pGUI.SetFocusedObject(this); -} - -bool IGUIObject::IsFocused() const -{ - return m_pGUI.GetFocusedObject() == this; -} - -bool IGUIObject::IsBaseObject() const -{ - return this == &m_pGUI.GetBaseObject(); -} - -bool IGUIObject::IsRootObject() const -{ - return m_pParent == &m_pGUI.GetBaseObject(); -} - -void IGUIObject::TraceMember(JSTracer* trc) -{ - // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced! - - for (std::pair>& handler : m_ScriptHandlers) - JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers"); -} - -// Instantiate templated functions: -// These functions avoid copies by working with a reference and move semantics. -#define TYPE(T) \ - template void IGUIObject::RegisterSetting(const CStr& Name, T& Value); \ - template T& IGUIObject::GetSetting(const CStr& Setting); \ - template const T& IGUIObject::GetSetting(const CStr& Setting) const; \ - template void IGUIObject::SetSetting(const CStr& Setting, T& Value, const bool SendMessage); \ - -#include "gui/GUItypes.h" -#undef TYPE - -// Copying functions - discouraged except for primitives. -#define TYPE(T) \ - template void IGUIObject::SetSetting(const CStr& Setting, const T& Value, const bool SendMessage); \ - -#define GUITYPE_IGNORE_NONCOPYABLE -#include "gui/GUItypes.h" -#undef GUITYPE_IGNORE_NONCOPYABLE -#undef TYPE Index: ps/trunk/source/gui/IGUIScrollBar.cpp =================================================================== --- ps/trunk/source/gui/IGUIScrollBar.cpp +++ ps/trunk/source/gui/IGUIScrollBar.cpp @@ -19,8 +19,8 @@ #include "IGUIScrollBar.h" -#include "gui/IGUIScrollBarOwner.h" #include "gui/CGUI.h" +#include "gui/ObjectBases/IGUIScrollBarOwner.h" #include "maths/MathUtil.h" IGUIScrollBar::IGUIScrollBar(CGUI& pGUI) Index: ps/trunk/source/gui/IGUIScrollBarOwner.h =================================================================== --- ps/trunk/source/gui/IGUIScrollBarOwner.h +++ ps/trunk/source/gui/IGUIScrollBarOwner.h @@ -1,94 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#ifndef INCLUDED_IGUISCROLLBAROWNER -#define INCLUDED_IGUISCROLLBAROWNER - -#include "gui/IGUIObject.h" - -#include - -struct SGUIScrollBarStyle; -class IGUIScrollBar; - -/** - * Base-class this if you want an object to contain - * one, or several, scroll-bars. - */ -class IGUIScrollBarOwner -{ - NONCOPYABLE(IGUIScrollBarOwner); - - friend class IGUIScrollBar; - -public: - IGUIScrollBarOwner(IGUIObject& m_pObject); - virtual ~IGUIScrollBarOwner(); - - virtual void Draw(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates(); - - /** - * Interface for the m_ScrollBar to use. - */ - virtual const SGUIScrollBarStyle* GetScrollBarStyle(const CStr& style) const; - - /** - * Add a scroll-bar - */ - virtual void AddScrollBar(IGUIScrollBar* scrollbar); - - /** - * Get Scroll Bar reference (it should be transparent it's actually - * pointers). - */ - virtual IGUIScrollBar& GetScrollBar(const int& index) - { - return *m_ScrollBars[index]; - } - - /** - * Get the position of the scroll bar at @param index. - * Equivalent to GetScrollbar(index).GetPos(). - */ - virtual float GetScrollBarPos(const int index) const; - -protected: - /** - * Predominately you will only have one, but you can have - * as many as you like. - */ - std::vector m_ScrollBars; - -private: - /** - * Reference to the IGUIObject. - * Private, because we don't want to inherit it in multiple classes. - */ - IGUIObject& m_pObject; -}; - -#endif // INCLUDED_IGUISCROLLBAROWNER Index: ps/trunk/source/gui/IGUIScrollBarOwner.cpp =================================================================== --- ps/trunk/source/gui/IGUIScrollBarOwner.cpp +++ ps/trunk/source/gui/IGUIScrollBarOwner.cpp @@ -1,68 +0,0 @@ -/* 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 "IGUIScrollBarOwner.h" - -#include "gui/CGUI.h" -#include "gui/IGUIScrollBar.h" - -IGUIScrollBarOwner::IGUIScrollBarOwner(IGUIObject& pObject) - : m_pObject(pObject) -{ -} - -IGUIScrollBarOwner::~IGUIScrollBarOwner() -{ - for (IGUIScrollBar* const& sb : m_ScrollBars) - delete sb; -} - -void IGUIScrollBarOwner::ResetStates() -{ - for (IGUIScrollBar* const& sb : m_ScrollBars) - sb->SetBarPressed(false); -} - -void IGUIScrollBarOwner::AddScrollBar(IGUIScrollBar* scrollbar) -{ - scrollbar->SetHostObject(this); - m_ScrollBars.push_back(scrollbar); -} - -const SGUIScrollBarStyle* IGUIScrollBarOwner::GetScrollBarStyle(const CStr& style) const -{ - return m_pObject.GetGUI().GetScrollBarStyle(style); -} - -void IGUIScrollBarOwner::HandleMessage(SGUIMessage& msg) -{ - for (IGUIScrollBar* const& sb : m_ScrollBars) - sb->HandleMessage(msg); -} - -void IGUIScrollBarOwner::Draw() -{ - for (IGUIScrollBar* const& sb : m_ScrollBars) - sb->Draw(); -} - -float IGUIScrollBarOwner::GetScrollBarPos(const int index) const -{ - return m_ScrollBars[index]->GetPos(); -} Index: ps/trunk/source/gui/IGUITextOwner.h =================================================================== --- ps/trunk/source/gui/IGUITextOwner.h +++ ps/trunk/source/gui/IGUITextOwner.h @@ -1,123 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -/* -GUI Object Base - Text Owner - ---Overview-- - - Interface class that enhance the IGUIObject with - cached CGUIStrings. This class is not at all needed, - and many controls that will use CGUIStrings might - not use this, but does help for regular usage such - as a text-box, a button, a radio button etc. -*/ - -#ifndef INCLUDED_IGUITEXTOWNER -#define INCLUDED_IGUITEXTOWNER - -#include "gui/IGUIObject.h" - -#include - -struct CGUIColor; -class CGUIText; -class CGUIString; - -/** - * Framework for handling Output text. - */ -class IGUITextOwner -{ - NONCOPYABLE(IGUITextOwner); - -public: - IGUITextOwner(IGUIObject& pObject); - virtual ~IGUITextOwner(); - - /** - * Adds a text object. - */ - CGUIText& AddText(); - - /** - * Adds a text generated by the given arguments. - */ - CGUIText& AddText(const CGUIString& Text, const CStrW& Font, const float& Width, const float& BufferZone); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * @see IGUIObject#UpdateCachedSize() - */ - virtual void UpdateCachedSize(); - - /** - * Draws the Text. - * - * @param index Index value of text. Mostly this will be 0 - * @param color - * @param pos Position - * @param z Z value - * @param clipping Clipping rectangle, don't even add a parameter - * to get no clipping. - */ - virtual void DrawText(size_t index, const CGUIColor& color, const CPos& pos, float z, const CRect& clipping = CRect()); - - /** - * Test if mouse position is over an icon - */ - virtual bool MouseOverIcon(); - -protected: - /** - * Setup texts. Functions that sets up all texts when changes have been made. - */ - virtual void SetupText() = 0; - - /** - * Regenerate the text in case it is invalid. Should only be called when inevitable. - */ - virtual void UpdateText(); - - /** - * Whether the cached text is currently valid (if not then SetupText will be called by Draw) - */ - bool m_GeneratedTextsValid; - - /** - * Texts that are generated and ready to be rendered. - */ - std::vector m_GeneratedTexts; - - /** - * Calculate the position for the text, based on the alignment. - */ - void CalculateTextPosition(CRect& ObjSize, CPos& TextPos, CGUIText& Text); - -private: - /** - * Reference to the IGUIObject. - * Private, because we don't want to inherit it in multiple classes. - */ - IGUIObject& m_pObject; -}; - -#endif // INCLUDED_IGUITEXTOWNER Index: ps/trunk/source/gui/IGUITextOwner.cpp =================================================================== --- ps/trunk/source/gui/IGUITextOwner.cpp +++ ps/trunk/source/gui/IGUITextOwner.cpp @@ -1,126 +0,0 @@ -/* 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 "IGUITextOwner.h" - -#include "gui/CGUI.h" -#include "gui/CGUIString.h" - -#include - -IGUITextOwner::IGUITextOwner(IGUIObject& pObject) - : m_pObject(pObject), - m_GeneratedTextsValid() -{ -} - -IGUITextOwner::~IGUITextOwner() -{ -} - -CGUIText& IGUITextOwner::AddText() -{ - m_GeneratedTexts.emplace_back(); - return m_GeneratedTexts.back(); -} - -CGUIText& IGUITextOwner::AddText(const CGUIString& Text, const CStrW& Font, const float& Width, const float& BufferZone) -{ - // Avoids a move constructor - m_GeneratedTexts.emplace_back(m_pObject.GetGUI(), Text, Font, Width, BufferZone, &m_pObject); - return m_GeneratedTexts.back(); -} - -void IGUITextOwner::HandleMessage(SGUIMessage& Message) -{ - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - // Everything that can change the visual appearance. - // it is assumed that the text of the object will be dependent on - // these. Although that is not certain, but one will have to manually - // change it and disregard this function. - // TODO Gee: (2004-09-07) Make sure this is all options that can affect the text. - if (Message.value == "size" || Message.value == "z" || - Message.value == "absolute" || Message.value == "caption" || - Message.value == "font" || Message.value == "textcolor" || - Message.value == "text_align" || Message.value == "text_valign" || - Message.value == "buffer_zone") - { - m_GeneratedTextsValid = false; - } - break; - - default: - break; - } -} - -void IGUITextOwner::UpdateCachedSize() -{ - // update our text positions - m_GeneratedTextsValid = false; -} - -void IGUITextOwner::UpdateText() -{ - if (!m_GeneratedTextsValid) - { - SetupText(); - m_GeneratedTextsValid = true; - } -} - -void IGUITextOwner::DrawText(size_t index, const CGUIColor& color, const CPos& pos, float z, const CRect& clipping) -{ - UpdateText(); - - ENSURE(index < m_GeneratedTexts.size() && "Trying to draw a Text Index within a IGUITextOwner that doesn't exist"); - - m_GeneratedTexts.at(index).Draw(m_pObject.GetGUI(), color, pos, z, clipping); -} - -void IGUITextOwner::CalculateTextPosition(CRect& ObjSize, CPos& TextPos, CGUIText& Text) -{ - // The horizontal Alignment is now computed in GenerateText in order to not have to - // loop through all of the TextCall objects again. - TextPos.x = ObjSize.left; - - switch (m_pObject.GetSetting("text_valign")) - { - case EVAlign_Top: - TextPos.y = ObjSize.top; - break; - case EVAlign_Center: - // Round to integer pixel values, else the fonts look awful - TextPos.y = floorf(ObjSize.CenterPoint().y - Text.GetSize().cy / 2.f); - break; - case EVAlign_Bottom: - TextPos.y = ObjSize.bottom - Text.GetSize().cy; - break; - default: - debug_warn(L"Broken EVAlign in CButton::SetupText()"); - break; - } -} - -bool IGUITextOwner::MouseOverIcon() -{ - return false; -} Index: ps/trunk/source/gui/MiniMap.h =================================================================== --- ps/trunk/source/gui/MiniMap.h +++ ps/trunk/source/gui/MiniMap.h @@ -1,115 +0,0 @@ -/* 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_MINIMAP -#define INCLUDED_MINIMAP - -#include "gui/IGUIObject.h" -#include "graphics/ShaderProgramPtr.h" -#include "renderer/VertexArray.h" - -class CCamera; -class CMatrix3D; -class CTerrain; - -class CMiniMap : public IGUIObject -{ - GUI_OBJECT(CMiniMap) -public: - CMiniMap(CGUI& pGUI); - virtual ~CMiniMap(); -protected: - virtual void Draw(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * @see IGUIObject#IsMouseOver() - */ - virtual bool IsMouseOver() const; - - // create the minimap textures - void CreateTextures(); - - // rebuild the terrain texture map - void RebuildTerrainTexture(); - - // destroy and free any memory and textures - void Destroy(); - - void SetCameraPos(); - - void FireWorldClickEvent(int button, int clicks); - - // the terrain we are mini-mapping - const CTerrain* m_Terrain; - - const CCamera* m_Camera; - - //Whether or not the mouse is currently down - bool m_Clicking; - - // minimap texture handles - GLuint m_TerrainTexture; - - // texture data - u32* m_TerrainData; - - // whether we need to regenerate the terrain texture - bool m_TerrainDirty; - - ssize_t m_Width, m_Height; - - // map size - ssize_t m_MapSize; - - // texture size - GLsizei m_TextureSize; - - // 1.f if map is circular or 1.414f if square (to shrink it inside the circle) - float m_MapScale; - - // maximal water height to allow the passage of a unit (for underwater shallows). - float m_ShallowPassageHeight; - - float m_WaterHeight; - - void DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const; - - void DrawViewRect(CMatrix3D transform) const; - - void GetMouseWorldCoordinates(float& x, float& z) const; - - float GetAngle() const; - - VertexIndexArray m_IndexArray; - VertexArray m_VertexArray; - VertexArray::Attribute m_AttributePos; - VertexArray::Attribute m_AttributeColor; - - size_t m_EntitiesDrawn; - - double m_PingDuration; - double m_HalfBlinkDuration; - double m_NextBlinkTime; - bool m_BlinkState; -}; - -#endif // INCLUDED_MINIMAP Index: ps/trunk/source/gui/MiniMap.cpp =================================================================== --- ps/trunk/source/gui/MiniMap.cpp +++ ps/trunk/source/gui/MiniMap.cpp @@ -1,711 +0,0 @@ -/* 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 "MiniMap.h" - -#include "graphics/GameView.h" -#include "graphics/LOSTexture.h" -#include "graphics/MiniPatch.h" -#include "graphics/Terrain.h" -#include "graphics/TerrainTextureEntry.h" -#include "graphics/TerrainTextureManager.h" -#include "graphics/TerritoryTexture.h" -#include "gui/CGUI.h" -#include "gui/GUIManager.h" -#include "gui/GUIMatrix.h" -#include "lib/bits.h" -#include "lib/external_libraries/libsdl.h" -#include "lib/ogl.h" -#include "lib/timer.h" -#include "ps/ConfigDB.h" -#include "ps/Filesystem.h" -#include "ps/Game.h" -#include "ps/GameSetup/Config.h" -#include "ps/Profile.h" -#include "ps/World.h" -#include "ps/XML/Xeromyces.h" -#include "renderer/Renderer.h" -#include "renderer/RenderingOptions.h" -#include "renderer/WaterManager.h" -#include "scriptinterface/ScriptInterface.h" -#include "simulation2/components/ICmpMinimap.h" -#include "simulation2/Simulation2.h" -#include "simulation2/system/ParamNode.h" - -#include - -extern bool g_GameRestarted; - -// Set max drawn entities to UINT16_MAX for now, which is more than enough -// TODO: we should be cleverer about drawing them to reduce clutter -const u16 MAX_ENTITIES_DRAWN = 65535; - -static unsigned int ScaleColor(unsigned int color, float x) -{ - unsigned int r = unsigned(float(color & 0xff) * x); - unsigned int g = unsigned(float((color>>8) & 0xff) * x); - unsigned int b = unsigned(float((color>>16) & 0xff) * x); - return (0xff000000 | b | g<<8 | r<<16); -} - -CMiniMap::CMiniMap(CGUI& pGUI) : - IGUIObject(pGUI), - m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f), - m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), - m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0) -{ - m_Clicking = false; - m_MouseHovering = false; - - // Register Relax NG validator - CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); - - // Get the maximum height for unit passage in water. - CParamNode externalParamNode; - CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); - const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); - if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) - m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); - else - m_ShallowPassageHeight = 0.0f; - - m_AttributePos.type = GL_FLOAT; - m_AttributePos.elems = 2; - m_VertexArray.AddAttribute(&m_AttributePos); - - m_AttributeColor.type = GL_UNSIGNED_BYTE; - m_AttributeColor.elems = 4; - m_VertexArray.AddAttribute(&m_AttributeColor); - - m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN); - m_VertexArray.Layout(); - - m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN); - m_IndexArray.Layout(); - VertexArrayIterator index = m_IndexArray.GetIterator(); - for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) - *index++ = i; - m_IndexArray.Upload(); - m_IndexArray.FreeBackingStore(); - - - VertexArrayIterator attrPos = m_AttributePos.GetIterator(); - VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); - for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) - { - (*attrColor)[0] = 0; - (*attrColor)[1] = 0; - (*attrColor)[2] = 0; - (*attrColor)[3] = 0; - ++attrColor; - - (*attrPos)[0] = -10000.0f; - (*attrPos)[1] = -10000.0f; - - ++attrPos; - - } - m_VertexArray.Upload(); - - double blinkDuration = 1.0; - - // Tests won't have config initialised - if (CConfigDB::IsInitialised()) - { - CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); - CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); - } - m_HalfBlinkDuration = blinkDuration/2; -} - -CMiniMap::~CMiniMap() -{ - Destroy(); -} - -void CMiniMap::HandleMessage(SGUIMessage& Message) -{ - IGUIObject::HandleMessage(Message); - switch (Message.type) - { - case GUIM_MOUSE_PRESS_LEFT: - if (m_MouseHovering) - { - SetCameraPos(); - m_Clicking = true; - } - break; - case GUIM_MOUSE_RELEASE_LEFT: - if (m_MouseHovering && m_Clicking) - SetCameraPos(); - m_Clicking = false; - break; - case GUIM_MOUSE_DBLCLICK_LEFT: - if (m_MouseHovering && m_Clicking) - SetCameraPos(); - m_Clicking = false; - break; - case GUIM_MOUSE_ENTER: - m_MouseHovering = true; - break; - case GUIM_MOUSE_LEAVE: - m_Clicking = false; - m_MouseHovering = false; - break; - case GUIM_MOUSE_RELEASE_RIGHT: - CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); - break; - case GUIM_MOUSE_DBLCLICK_RIGHT: - CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); - break; - case GUIM_MOUSE_MOTION: - if (m_MouseHovering && m_Clicking) - SetCameraPos(); - break; - case GUIM_MOUSE_WHEEL_DOWN: - case GUIM_MOUSE_WHEEL_UP: - Message.Skip(); - break; - - default: - break; - } -} - -bool CMiniMap::IsMouseOver() const -{ - // Get the mouse position. - const CPos& mousePos = m_pGUI.GetMousePos(); - // Get the position of the center of the minimap. - CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0); - // Take the magnitude of the difference of the mouse position and minimap center. - double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2)); - // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap. - if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0) - return true; - else - return false; -} - -void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) const -{ - // Determine X and Z according to proportion of mouse position and minimap - - const CPos& mousePos = m_pGUI.GetMousePos(); - - float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth(); - float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight(); - - float angle = GetAngle(); - - // Scale world coordinates for shrunken square map - x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5); - z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5); -} - -void CMiniMap::SetCameraPos() -{ - CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); - - CVector3D target; - GetMouseWorldCoordinates(target.X, target.Z); - target.Y = terrain->GetExactGroundLevel(target.X, target.Z); - g_Game->GetView()->MoveCameraTarget(target); -} - -float CMiniMap::GetAngle() const -{ - CVector3D cameraIn = m_Camera->m_Orientation.GetIn(); - return -atan2(cameraIn.X, cameraIn.Z); -} - -void CMiniMap::FireWorldClickEvent(int UNUSED(button), int UNUSED(clicks)) -{ - JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext(); - JSAutoRequest rq(cx); - - float x, z; - GetMouseWorldCoordinates(x, z); - - JS::RootedValue coords(cx); - ScriptInterface::CreateObject(cx, &coords, "x", x, "z", z); - - JS::AutoValueVector paramData(cx); - paramData.append(coords); - - ScriptEvent("worldclick", paramData); -} - -// This sets up and draws the rectangle on the minimap -// which represents the view of the camera in the world. -void CMiniMap::DrawViewRect(CMatrix3D transform) const -{ - // Compute the camera frustum intersected with a fixed-height plane. - // Use the water height as a fixed base height, which should be the lowest we can go - float h = g_Renderer.GetWaterManager()->m_WaterHeight; - const float width = m_CachedActualSize.GetWidth(); - const float height = m_CachedActualSize.GetHeight(); - const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize); - - CVector3D hitPt[4]; - hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h); - hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h); - hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h); - hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h); - - float ViewRect[4][2]; - for (int i = 0; i < 4; ++i) - { - // convert to minimap space - ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize); - ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize); - } - - float viewVerts[] = { - ViewRect[0][0], -ViewRect[0][1], - ViewRect[1][0], -ViewRect[1][1], - ViewRect[2][0], -ViewRect[2][1], - ViewRect[3][0], -ViewRect[3][1] - }; - - // Enable Scissoring to restrict the rectangle to only the minimap. - glScissor( - m_CachedActualSize.left * g_GuiScale, - g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale, - width * g_GuiScale, - height * g_GuiScale); - glEnable(GL_SCISSOR_TEST); - glLineWidth(2.0f); - - CShaderDefines lineDefines; - lineDefines.Add(str_MINIMAP_LINE, str_1); - CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines); - tech->BeginPass(); - CShaderProgramPtr shader = tech->GetShader(); - shader->Uniform(str_transform, transform); - shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f); - - shader->VertexPointer(2, GL_FLOAT, 0, viewVerts); - shader->AssertPointersBound(); - - if (!g_Renderer.m_SkipSubmit) - glDrawArrays(GL_LINE_LOOP, 0, 4); - - tech->EndPass(); - - glLineWidth(1.0f); - glDisable(GL_SCISSOR_TEST); -} - -struct MinimapUnitVertex -{ - // This struct is copyable for convenience and because to move is to copy for primitives. - u8 r, g, b, a; - float x, y; -}; - -// Adds a vertex to the passed VertexArray -static void inline addVertex(const MinimapUnitVertex& v, - VertexArrayIterator& attrColor, - VertexArrayIterator& attrPos) -{ - (*attrColor)[0] = v.r; - (*attrColor)[1] = v.g; - (*attrColor)[2] = v.b; - (*attrColor)[3] = v.a; - ++attrColor; - - (*attrPos)[0] = v.x; - (*attrPos)[1] = v.y; - - ++attrPos; -} - - -void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const -{ - // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m) - // Scale square maps to fit in circular minimap area - const float s = sin(angle) * m_MapScale; - const float c = cos(angle) * m_MapScale; - const float m = coordMax / 2.f; - - float quadTex[] = { - m*(-c + s + 1.f), m*(-c + -s + 1.f), - m*(c + s + 1.f), m*(-c + s + 1.f), - m*(c + -s + 1.f), m*(c + s + 1.f), - - m*(c + -s + 1.f), m*(c + s + 1.f), - m*(-c + -s + 1.f), m*(c + -s + 1.f), - m*(-c + s + 1.f), m*(-c + -s + 1.f) - }; - float quadVerts[] = { - x, y, z, - x2, y, z, - x2, y2, z, - - x2, y2, z, - x, y2, z, - x, y, z - }; - - shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); - shader->VertexPointer(3, GL_FLOAT, 0, quadVerts); - shader->AssertPointersBound(); - - if (!g_Renderer.m_SkipSubmit) - glDrawArrays(GL_TRIANGLES, 0, 6); -} - -// TODO: render the minimap in a framebuffer and just draw the frambuffer texture -// most of the time, updating the framebuffer twice a frame. -// Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling -// (those operations cause a gpu sync, which slows down the way gpu works) -void CMiniMap::Draw() -{ - PROFILE3("render minimap"); - - // The terrain isn't actually initialized until the map is loaded, which - // happens when the game is started, so abort until then. - if (!g_Game || !g_Game->IsGameStarted()) - return; - - CSimulation2* sim = g_Game->GetSimulation2(); - CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); - ENSURE(cmpRangeManager); - - // Set our globals in case they hadn't been set before - m_Camera = g_Game->GetView()->GetCamera(); - m_Terrain = g_Game->GetWorld()->GetTerrain(); - m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); - m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); - m_MapSize = m_Terrain->GetVerticesPerSide(); - m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); - m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); - - if (!m_TerrainTexture || g_GameRestarted) - CreateTextures(); - - - // only update 2x / second - // (note: since units only move a few pixels per second on the minimap, - // we can get away with infrequent updates; this is slow) - // TODO: Update all but camera at same speed as simulation - static double last_time; - const double cur_time = timer_Time(); - const bool doUpdate = cur_time - last_time > 0.5; - if (doUpdate) - { - last_time = cur_time; - if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight) - RebuildTerrainTexture(); - } - - const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; - const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; - const float z = GetBufferedZ(); - const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize; - const float angle = GetAngle(); - const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f); - - // Disable depth updates to prevent apparent z-fighting-related issues - // with some drivers causing units to get drawn behind the texture. - glDepthMask(0); - - CShaderProgramPtr shader; - CShaderTechniquePtr tech; - - CShaderDefines baseDefines; - baseDefines.Add(str_MINIMAP_BASE, str_1); - tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines); - tech->BeginPass(); - shader = tech->GetShader(); - - // Draw the main textured quad - shader->BindTexture(str_baseTex, m_TerrainTexture); - const CMatrix3D baseTransform = GetDefaultGuiMatrix(); - CMatrix3D baseTextureTransform; - baseTextureTransform.SetIdentity(); - shader->Uniform(str_transform, baseTransform); - shader->Uniform(str_textureTransform, baseTextureTransform); - - DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z); - - // Draw territory boundaries - glEnable(GL_BLEND); - - CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); - - shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); - const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix(); - shader->Uniform(str_transform, baseTransform); - shader->Uniform(str_textureTransform, *territoryTransform); - - DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); - tech->EndPass(); - - // Draw the LOS quad in black, using alpha values from the LOS texture - CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); - - CShaderDefines losDefines; - losDefines.Add(str_MINIMAP_LOS, str_1); - tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines); - tech->BeginPass(); - shader = tech->GetShader(); - shader->BindTexture(str_baseTex, losTexture.GetTexture()); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix(); - shader->Uniform(str_transform, baseTransform); - shader->Uniform(str_textureTransform, *losTransform); - - DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); - tech->EndPass(); - - glDisable(GL_BLEND); - - PROFILE_START("minimap units"); - - CShaderDefines pointDefines; - pointDefines.Add(str_MINIMAP_POINT, str_1); - tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines); - tech->BeginPass(); - shader = tech->GetShader(); - shader->Uniform(str_transform, baseTransform); - shader->Uniform(str_pointSize, 3.f); - - CMatrix3D unitMatrix; - unitMatrix.SetIdentity(); - // Center the minimap on the origin of the axis of rotation. - unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f); - // Rotate the map. - unitMatrix.RotateZ(angle); - // Scale square maps to fit. - unitMatrix.Scale(unitScale, unitScale, 1.f); - // Move the minimap back to it's starting position. - unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f); - // Move the minimap to it's final location. - unitMatrix.Translate(x, y, z); - // Apply the gui matrix. - unitMatrix *= GetDefaultGuiMatrix(); - // Load the transform into the shader. - shader->Uniform(str_transform, unitMatrix); - - const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); - const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); - - CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap); - - if (doUpdate) - { - VertexArrayIterator attrPos = m_AttributePos.GetIterator(); - VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); - - m_EntitiesDrawn = 0; - MinimapUnitVertex v; - std::vector pingingVertices; - pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); - - if (cur_time > m_NextBlinkTime) - { - m_BlinkState = !m_BlinkState; - m_NextBlinkTime = cur_time + m_HalfBlinkDuration; - } - - entity_pos_t posX, posZ; - for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) - { - ICmpMinimap* cmpMinimap = static_cast(it->second); - if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) - { - ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer()); - if (vis != ICmpRangeManager::VIS_HIDDEN) - { - v.a = 255; - v.x = posX.ToFloat() * sx; - v.y = -posZ.ToFloat() * sy; - - // Check minimap pinging to indicate something - if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration)) - { - v.r = 255; // ping color is white - v.g = 255; - v.b = 255; - pingingVertices.push_back(v); - } - else - { - addVertex(v, attrColor, attrPos); - ++m_EntitiesDrawn; - } - } - } - } - - // Add the pinged vertices at the end, so they are drawn on top - for (size_t v = 0; v < pingingVertices.size(); ++v) - { - addVertex(pingingVertices[v], attrColor, attrPos); - ++m_EntitiesDrawn; - } - - ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); - m_VertexArray.Upload(); - } - - m_VertexArray.PrepareForRendering(); - - if (m_EntitiesDrawn > 0) - { -#if !CONFIG2_GLES - if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) - glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); -#endif - - u8* indexBase = m_IndexArray.Bind(); - u8* base = m_VertexArray.Bind(); - const GLsizei stride = (GLsizei)m_VertexArray.GetStride(); - - shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset); - shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset); - shader->AssertPointersBound(); - - if (!g_Renderer.m_SkipSubmit) - glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase); - - g_Renderer.GetStats().m_DrawCalls++; - CVertexBuffer::Unbind(); - -#if !CONFIG2_GLES - if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) - glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); -#endif - } - - tech->EndPass(); - - DrawViewRect(unitMatrix); - - PROFILE_END("minimap units"); - - // Reset depth mask - glDepthMask(1); -} - -void CMiniMap::CreateTextures() -{ - Destroy(); - - // Create terrain texture - glGenTextures(1, &m_TerrainTexture); - g_Renderer.BindTexture(0, m_TerrainTexture); - - // Initialise texture with solid black, for the areas we don't - // overwrite with glTexSubImage2D later - u32* texData = new u32[m_TextureSize * m_TextureSize]; - for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i) - texData[i] = 0xFF000000; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); - delete[] texData; - - m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - // Rebuild and upload both of them - RebuildTerrainTexture(); -} - - -void CMiniMap::RebuildTerrainTexture() -{ - u32 x = 0; - u32 y = 0; - u32 w = m_MapSize - 1; - u32 h = m_MapSize - 1; - m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; - - m_TerrainDirty = false; - - for (u32 j = 0; j < h; ++j) - { - u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; - for (u32 i = 0; i < w; ++i) - { - float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j) - + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j) - + m_Terrain->GetVertexGroundLevel((int)i, (int)j+1) - + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1) - ) / 4.0f; - - if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) - { - // shallow water - *dataPtr++ = 0xffc09870; - } - else if (avgHeight < m_WaterHeight) - { - // Set water as constant color for consistency on different maps - *dataPtr++ = 0xffa07850; - } - else - { - int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; - int val = (hmap / 3) + 170; - - u32 color = 0xFFFFFFFF; - - CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j); - if (mp) - { - CTerrainTextureEntry* tex = mp->GetTextureEntry(); - if (tex) - { - // If the texture can't be loaded yet, set the dirty flags - // so we'll try regenerating the terrain texture again soon - if(!tex->GetTexture()->TryLoad()) - m_TerrainDirty = true; - - color = tex->GetBaseColor(); - } - } - - *dataPtr++ = ScaleColor(color, float(val) / 255.0f); - } - } - } - - // Upload the texture - g_Renderer.BindTexture(0, m_TerrainTexture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData); -} - -void CMiniMap::Destroy() -{ - if (m_TerrainTexture) - { - glDeleteTextures(1, &m_TerrainTexture); - m_TerrainTexture = 0; - } - - SAFE_ARRAY_DELETE(m_TerrainData); -} Index: ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.h =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.h +++ ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.h @@ -0,0 +1,93 @@ +/* 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 . + */ + +/* + Interface class that enhance the IGUIObject with + buttony behavior (click and release to click a button), + and the GUI message GUIM_PRESSED. + When creating a class with extended settings and + buttony behavior, just do a multiple inheritance. +*/ + +#ifndef INCLUDED_IGUIBUTTONBEHAVIOR +#define INCLUDED_IGUIBUTTONBEHAVIOR + +#include "gui/ObjectBases/IGUIObject.h" + +class CGUISpriteInstance; + +/** + * Appends button behaviours to the IGUIObject. + * Can be used with multiple inheritance alongside + * IGUISettingsObject and such. + */ +class IGUIButtonBehavior +{ + NONCOPYABLE(IGUIButtonBehavior); + +public: + IGUIButtonBehavior(IGUIObject& pObject); + virtual ~IGUIButtonBehavior(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * This is a function that lets a button being drawn, + * it regards if it's over, disabled, pressed and such. + * + * @param sprite Sprite drawn when not pressed, hovered or disabled + * @param sprite_over Sprite drawn when m_MouseHovering is true + * @param sprite_pressed Sprite drawn when m_Pressed is true + * @param sprite_disabled Sprite drawn when "enabled" is false + */ + const CGUISpriteInstance& GetButtonSprite(const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_over, const CGUISpriteInstance& sprite_pressed, const CGUISpriteInstance& sprite_disabled) const; + +protected: + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates(); + + /** + * Everybody knows how a button works, you don't simply press it, + * you have to first press the button, and then release it... + * in between those two steps you can actually leave the button + * area, as long as you release it within the button area... Anyway + * this lets us know we are done with step one (clicking). + */ + bool m_Pressed; + bool m_PressedRight; + + // Settings + CStrW m_SoundDisabled; + CStrW m_SoundEnter; + CStrW m_SoundLeave; + CStrW m_SoundPressed; + CStrW m_SoundReleased; + +private: + /** + * Reference to the IGUIObject. + * Private, because we don't want to inherit it in multiple classes. + */ + IGUIObject& m_pObject; +}; + +#endif // INCLUDED_IGUIBUTTONBEHAVIOR Index: ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.cpp =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.cpp +++ ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.cpp @@ -0,0 +1,148 @@ +/* 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 "IGUIButtonBehavior.h" + +#include "gui/CGUISprite.h" + +IGUIButtonBehavior::IGUIButtonBehavior(IGUIObject& pObject) + : m_pObject(pObject), + m_Pressed(), + m_PressedRight(), + m_SoundDisabled(), + m_SoundEnter(), + m_SoundLeave(), + m_SoundPressed(), + m_SoundReleased() +{ + m_pObject.RegisterSetting("sound_disabled", m_SoundDisabled); + m_pObject.RegisterSetting("sound_enter", m_SoundEnter); + m_pObject.RegisterSetting("sound_leave", m_SoundLeave); + m_pObject.RegisterSetting("sound_pressed", m_SoundPressed); + m_pObject.RegisterSetting("sound_released", m_SoundReleased); +} + +IGUIButtonBehavior::~IGUIButtonBehavior() +{ +} + +void IGUIButtonBehavior::ResetStates() +{ + m_Pressed = false; + m_PressedRight = false; +} + +void IGUIButtonBehavior::HandleMessage(SGUIMessage& Message) +{ + // TODO Gee: easier access functions + switch (Message.type) + { + case GUIM_MOUSE_ENTER: + if (m_pObject.IsEnabled()) + m_pObject.PlaySound(m_SoundEnter); + break; + + case GUIM_MOUSE_LEAVE: + if (m_pObject.IsEnabled()) + m_pObject.PlaySound(m_SoundLeave); + break; + + case GUIM_MOUSE_DBLCLICK_LEFT: + if (!m_pObject.IsEnabled()) + break; + + // Since GUIM_MOUSE_PRESS_LEFT also gets called twice in a + // doubleclick event, we let it handle playing sounds. + m_pObject.SendEvent(GUIM_DOUBLE_PRESSED, "doublepress"); + break; + + case GUIM_MOUSE_PRESS_LEFT: + if (!m_pObject.IsEnabled()) + { + m_pObject.PlaySound(m_SoundDisabled); + break; + } + + m_pObject.PlaySound(m_SoundPressed); + m_pObject.SendEvent(GUIM_PRESSED, "press"); + m_Pressed = true; + break; + + case GUIM_MOUSE_DBLCLICK_RIGHT: + if (!m_pObject.IsEnabled()) + break; + + // Since GUIM_MOUSE_PRESS_RIGHT also gets called twice in a + // doubleclick event, we let it handle playing sounds. + m_pObject.SendEvent(GUIM_DOUBLE_PRESSED_MOUSE_RIGHT, "doublepressright"); + break; + + case GUIM_MOUSE_PRESS_RIGHT: + if (!m_pObject.IsEnabled()) + { + m_pObject.PlaySound(m_SoundDisabled); + break; + } + + // Button was right-clicked + m_pObject.PlaySound(m_SoundPressed); + m_pObject.SendEvent(GUIM_PRESSED_MOUSE_RIGHT, "pressright"); + m_PressedRight = true; + break; + + case GUIM_MOUSE_RELEASE_RIGHT: + if (!m_pObject.IsEnabled()) + break; + + if (m_PressedRight) + { + m_PressedRight = false; + m_pObject.PlaySound(m_SoundReleased); + } + break; + + case GUIM_MOUSE_RELEASE_LEFT: + if (!m_pObject.IsEnabled()) + break; + + if (m_Pressed) + { + m_Pressed = false; + m_pObject.PlaySound(m_SoundReleased); + } + break; + + default: + break; + } +} + +const CGUISpriteInstance& IGUIButtonBehavior::GetButtonSprite(const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_over, const CGUISpriteInstance& sprite_pressed, const CGUISpriteInstance& sprite_disabled) const +{ + if (!m_pObject.IsEnabled()) + return sprite_disabled || sprite; + + if (!m_pObject.IsMouseOver()) + return sprite; + + if (m_Pressed) + return sprite_pressed || sprite; + + return sprite_over || sprite; +} Index: ps/trunk/source/gui/ObjectBases/IGUIObject.h =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIObject.h +++ ps/trunk/source/gui/ObjectBases/IGUIObject.h @@ -0,0 +1,536 @@ +/* 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 . + */ + +/* + * The base class of an object. + * All objects are derived from this class. + * It's an abstract data type, so it can't be used per se. + * Also contains a Dummy object which is used for completely blank objects. + */ + +#ifndef INCLUDED_IGUIOBJECT +#define INCLUDED_IGUIOBJECT + +#include "gui/Scripting/JSInterface_IGUIObject.h" +#include "gui/SettingTypes/CGUISize.h" +#include "gui/SGUIMessage.h" +#include "lib/input.h" // just for IN_PASS +#include "ps/XML/Xeromyces.h" + +#include +#include +#include + +class CGUI; +class IGUIObject; +class IGUISetting; + +using map_pObjects = std::map; + +#define GUI_OBJECT(obj) \ +public: \ + static IGUIObject* ConstructObject(CGUI& pGUI) \ + { return new obj(pGUI); } + +/** + * GUI object such as a button or an input-box. + * Abstract data type ! + */ +class IGUIObject +{ + friend class CGUI; + + // Allow getProperty to access things like GetParent() + friend bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); + friend bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); + friend bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp); + +public: + NONCOPYABLE(IGUIObject); + + IGUIObject(CGUI& pGUI); + virtual ~IGUIObject(); + + /** + * Checks if mouse is hovering this object. + * The mouse position is cached in CGUI. + * + * This function checks if the mouse is hovering the + * rectangle that the base setting "size" makes. + * Although it is virtual, so one could derive + * an object from CButton, which changes only this + * to checking the circle that "size" makes. + * + * @return true if mouse is hovering + */ + virtual bool IsMouseOver() const; + + /** + * Test if mouse position is over an icon + */ + virtual bool MouseOverIcon(); + + //-------------------------------------------------------- + /** @name Leaf Functions */ + //-------------------------------------------------------- + //@{ + + /// Get object name, name is unique + const CStr& GetName() const { return m_Name; } + + /// Get object name + void SetName(const CStr& Name) { m_Name = Name; } + + // Get Presentable name. + // Will change all internally set names to something like "" + CStr GetPresentableName() const; + + /** + * Adds object and its children to the map, it's name being the + * first part, and the second being itself. + * + * @param ObjectMap Adds this to the map_pObjects. + * + * @throws PSERROR_GUI_ObjectNeedsName Name is missing + * @throws PSERROR_GUI_NameAmbiguity Name is already taken + */ + void AddToPointersMap(map_pObjects& ObjectMap); + + /** + * Notice nothing will be returned or thrown if the child hasn't + * been inputted into the GUI yet. This is because that's were + * all is checked. Now we're just linking two objects, but + * it's when we're inputting them into the GUI we'll check + * validity! Notice also when adding it to the GUI this function + * will inevitably have been called by CGUI::AddObject which + * will catch the throw and return the error code. + * i.e. The user will never put in the situation wherein a throw + * must be caught, the GUI's internal error handling will be + * completely transparent to the interfacially sequential model. + * + * @param pChild Child to add + * + * @throws PSERROR_GUI from CGUI::UpdateObjects(). + */ + void AddChild(IGUIObject* pChild); + + /** + * Return all child objects of the current object. + */ + const std::vector& GetChildren() const { return m_Children; } + + //@} + //-------------------------------------------------------- + /** @name Settings Management */ + //-------------------------------------------------------- + //@{ + + /** + * Registers the given setting variables with the GUI object. + * Enable XML and JS to modify the given variable. + * + * @param Type Setting type + * @param Name Setting reference name + */ + template + void RegisterSetting(const CStr& Name, T& Value); + + /** + * Returns whether there is a setting with the given name registered. + * + * @param Setting setting name + * @return True if settings exist. + */ + bool SettingExists(const CStr& Setting) const; + + /** + * Get a mutable reference to the setting. + * If no such setting exists, an exception of type std::out_of_range is thrown. + * If the value is modified, there is no GUIM_SETTINGS_UPDATED message sent. + */ + template + T& GetSetting(const CStr& Setting); + + template + const T& GetSetting(const CStr& Setting) const; + + /** + * Set a setting by string, regardless of what type it is. + * Used to parse setting values from XML files. + * For example a CRect(10,10,20,20) is created from "10 10 20 20". + * Returns false if the conversion fails, otherwise true. + */ + bool SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage); + + /** + * Assigns the given value to the setting identified by the given name. + * Uses move semantics, so do not read from Value after this call. + * + * @param SendMessage If true, a GUIM_SETTINGS_UPDATED message will be broadcasted to all GUI objects. + */ + template + void SetSetting(const CStr& Setting, T& Value, const bool SendMessage); + + /** + * This variant will copy the value. + */ + template + void SetSetting(const CStr& Setting, const T& Value, const bool SendMessage); + + /** + * Returns whether this object is set to be hidden or ghost. + */ + bool IsEnabled() const; + + /** + * Returns whether this is object is set to be hidden. + */ + bool IsHidden() const; + + /** + * Returns whether this object is set to be hidden or ghost. + */ + bool IsHiddenOrGhost() const; + + /** + * Retrieves the configured sound filename from the given setting name and plays that once. + */ + void PlaySound(const CStrW& soundPath) const; + + /** + * Send event to this GUI object (HandleMessage and ScriptEvent) + * + * @param type Type of GUI message to be handled + * @param EventName String representation of event name + * @return IN_HANDLED if event was handled, or IN_PASS if skipped + */ + InReaction SendEvent(EGUIMessageType type, const CStr& EventName); + + /** + * All sizes are relative to resolution, and the calculation + * is not wanted in real time, therefore it is cached, update + * the cached size with this function. + */ + virtual void UpdateCachedSize(); + + /** + * Reset internal state of this object. + */ + virtual void ResetStates(); + + /** + * Set the script handler for a particular object-specific action + * + * @param Action Name of action + * @param Code Javascript code to execute when the action occurs + * @param pGUI GUI instance to associate the script with + */ + void RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI& pGUI); + + /** + * Inheriting classes may append JS functions to the JS object representing this class. + */ + virtual void RegisterScriptFunctions() {} + + /** + * Retrieves the JSObject representing this GUI object. + */ + JSObject* GetJSObject(); + + //@} +protected: + //-------------------------------------------------------- + /** @name Called by CGUI and friends + * + * Methods that the CGUI will call using + * its friendship, these should not + * be called by user. + * These functions' security are a lot + * what constitutes the GUI's + */ + //-------------------------------------------------------- + //@{ + +public: + /** + * This function is called with different messages + * for instance when the mouse enters the object. + * + * @param Message GUI Message + */ + virtual void HandleMessage(SGUIMessage& UNUSED(Message)) {} + + /** + * Calls an IGUIObject member function recursively on this object and its children. + * Aborts recursion at IGUIObjects that have the isRestricted function return true. + * The arguments of the callback function must be references. + */ + template + void RecurseObject(bool(IGUIObject::*isRestricted)() const, void(IGUIObject::*callbackFunction)(Args... args), Args&&... args) + { + if (!IsBaseObject()) + { + if (isRestricted && (this->*isRestricted)()) + return; + + (this->*callbackFunction)(args...); + } + + for (IGUIObject* const& obj : m_Children) + obj->RecurseObject(isRestricted, callbackFunction, args...); + } + +protected: + /** + * Draws the object. + * + * @throws PSERROR if any. But this will mostlikely be + * very rare since if an object is drawn unsuccessfully + * it'll probably only output in the Error log, and not + * disrupt the whole GUI drawing. + */ + virtual void Draw() = 0; + + /** + * Some objects need to handle the SDL_Event_ manually. + * For instance the input box. + * + * Only the object with focus will have this function called. + * + * Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then + * the key won't be passed on and processed by other handlers. + * This is used for keys that the GUI uses. + */ + virtual InReaction ManuallyHandleEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; } + + /** + * Loads a style. + */ + void LoadStyle(const CStr& StyleName); + + /** + * Returns not the Z value, but the actual buffered Z value, i.e. if it's + * defined relative, then it will check its parent's Z value and add + * the relativity. + * + * @return Actual Z value on the screen. + */ + virtual float GetBufferedZ() const; + + /** + * Set parent of this object + */ + void SetParent(IGUIObject* pParent) { m_pParent = pParent; } + +public: + + CGUI& GetGUI() { return m_pGUI; } + const CGUI& GetGUI() const { return m_pGUI; } + + /** + * Take focus! + */ + void SetFocus(); + +protected: + /** + * Check if object is focused. + */ + bool IsFocused() const; + + /** + * NOTE! This will not just return m_pParent, when that is + * need use it! There is one exception to it, when the parent is + * the top-node (the object that isn't a real object), this + * will return nullptr, so that the top-node's children are + * seemingly parentless. + * + * @return Pointer to parent + */ + IGUIObject* GetParent() const; + + /** + * Handle additional children to the \-tag. In IGUIObject, this function does + * nothing. In CList and CDropDown, it handles the \, used to build the data. + * + * Returning false means the object doesn't recognize the child. Should be reported. + * Notice 'false' is default, because an object not using this function, should not + * have any additional children (and this function should never be called). + */ + virtual bool HandleAdditionalChildren(const XMBElement& UNUSED(child), CXeromyces* UNUSED(pFile)) + { + return false; + } + + /** + * Allow the GUI object to process after all child items were handled. + * Useful to avoid iterator invalidation with push_back calls. + */ + virtual void AdditionalChildrenHandled() {} + + /** + * Cached size, real size m_Size is actually dependent on resolution + * and can have different *real* outcomes, this is the real outcome + * cached to avoid slow calculations in real time. + */ + CRect m_CachedActualSize; + + /** + * Execute the script for a particular action. + * Does nothing if no script has been registered for that action. + * The mouse coordinates will be passed as the first argument. + * + * @param Action Name of action + */ + void ScriptEvent(const CStr& Action); + + /** + * Execute the script for a particular action. + * Does nothing if no script has been registered for that action. + * + * @param Action Name of action + * @param paramData JS::HandleValueArray arguments to pass to the event. + */ + void ScriptEvent(const CStr& Action, const JS::HandleValueArray& paramData); + + void SetScriptHandler(const CStr& Action, JS::HandleObject Function); + + /** + * Inputes the object that is currently hovered, this function + * updates this object accordingly (i.e. if it's the object + * being inputted one thing happens, and not, another). + * + * @param pMouseOver Object that is currently hovered, can be nullptr too! + */ + void UpdateMouseOver(IGUIObject* const& pMouseOver); + + //@} +private: + //-------------------------------------------------------- + /** @name Internal functions */ + //-------------------------------------------------------- + //@{ + + /** + * Creates the JS object representing this page upon first use. + */ + void CreateJSObject(); + + /** + * Updates some internal data depending on the setting changed. + */ + void PreSettingChange(const CStr& Setting); + void SettingChanged(const CStr& Setting, const bool SendMessage); + + /** + * Inputs a reference pointer, checks if the new inputted object + * if hovered, if so, then check if this's Z value is greater + * than the inputted object... If so then the object is closer + * and we'll replace the pointer with this. + * Also Notice input can be nullptr, which means the Z value demand + * is out. NOTICE you can't input nullptr as const so you'll have + * to set an object to nullptr. + * + * @param pObject Object pointer, can be either the old one, or + * the new one. + */ + void ChooseMouseOverAndClosest(IGUIObject*& pObject); + + /** + * Returns whether this is the object all other objects are descendants of. + */ + bool IsBaseObject() const; + + /** + * Returns whether this object is a child of the base object. + */ + bool IsRootObject() const; + + static void Trace(JSTracer* trc, void* data) + { + reinterpret_cast(data)->TraceMember(trc); + } + + void TraceMember(JSTracer* trc); + +// Variables +protected: + // Name of object + CStr m_Name; + + // Constructed on the heap, will be destroyed along with the the CGUI + std::vector m_Children; + + // Pointer to parent + IGUIObject* m_pParent; + + //This represents the last click time for each mouse button + double m_LastClickTime[6]; + + /** + * This is an array of true or false, each element is associated with + * a string representing a setting. Number of elements is equal to + * number of settings. + * + * A true means the setting has been manually set in the file when + * read. This is important to know because I don't want to force + * the user to include its \-XML-files first, so somehow + * the GUI needs to know which settings were set, and which is meant + * to. + */ + + // More variables + + // Is mouse hovering the object? used with the function IsMouseOver() + bool m_MouseHovering; + + /** + * Settings pool, all an object's settings are located here + * If a derived object has got more settings that the base + * settings, it's because they have a new version of the + * function SetupSettings(). + * + * @see SetupSettings() + */ +public: + std::map m_Settings; + +protected: + // An object can't function stand alone + CGUI& m_pGUI; + + // Internal storage for registered script handlers. + std::map > m_ScriptHandlers; + + // Cached JSObject representing this GUI object + JS::PersistentRootedObject m_JSObject; + + // Cache references to settings for performance + bool m_Enabled; + bool m_Hidden; + CGUISize m_Size; + CStr m_Style; + CStr m_Hotkey; + float m_Z; + bool m_Absolute; + bool m_Ghost; + float m_AspectRatio; + CStrW m_Tooltip; + CStr m_TooltipStyle; +}; + +#endif // INCLUDED_IGUIOBJECT Index: ps/trunk/source/gui/ObjectBases/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIObject.cpp +++ ps/trunk/source/gui/ObjectBases/IGUIObject.cpp @@ -0,0 +1,550 @@ +/* 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 "IGUIObject.h" + +#include "gui/CGUI.h" +#include "gui/CGUISetting.h" +#include "ps/CLogger.h" +#include "ps/GameSetup/Config.h" +#include "ps/Profile.h" +#include "scriptinterface/ScriptInterface.h" +#include "soundmanager/ISoundManager.h" + +IGUIObject::IGUIObject(CGUI& pGUI) + : m_pGUI(pGUI), + m_pParent(), + m_MouseHovering(), + m_LastClickTime(), + m_Enabled(), + m_Hidden(), + m_Size(), + m_Style(), + m_Hotkey(), + m_Z(), + m_Absolute(), + m_Ghost(), + m_AspectRatio(), + m_Tooltip(), + m_TooltipStyle() +{ + RegisterSetting("enabled", m_Enabled); + RegisterSetting("hidden", m_Hidden); + RegisterSetting("size", m_Size); + RegisterSetting("style", m_Style); + RegisterSetting("hotkey", m_Hotkey); + RegisterSetting("z", m_Z); + RegisterSetting("absolute", m_Absolute); + RegisterSetting("ghost", m_Ghost); + RegisterSetting("aspectratio", m_AspectRatio); + RegisterSetting("tooltip", m_Tooltip); + RegisterSetting("tooltip_style", m_TooltipStyle); + + // Setup important defaults + // TODO: Should be in the default style? + SetSetting("hidden", false, true); + SetSetting("ghost", false, true); + SetSetting("enabled", true, true); + SetSetting("absolute", true, true); +} + +IGUIObject::~IGUIObject() +{ + for (const std::pair& p : m_Settings) + delete p.second; + + if (!m_ScriptHandlers.empty()) + JS_RemoveExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetJSRuntime(), Trace, this); +} + +//------------------------------------------------------------------- +// Functions +//------------------------------------------------------------------- + +void IGUIObject::AddChild(IGUIObject* pChild) +{ +// ENSURE(pChild); + + pChild->SetParent(this); + + m_Children.push_back(pChild); + + { + try + { + // Atomic function, if it fails it won't + // have changed anything + //UpdateObjects(); + pChild->GetGUI().UpdateObjects(); + } + catch (PSERROR_GUI&) + { + // If anything went wrong, reverse what we did and throw + // an exception telling it never added a child + m_Children.erase(m_Children.end()-1); + + throw; + } + } + // else do nothing +} + +void IGUIObject::AddToPointersMap(map_pObjects& ObjectMap) +{ + // Just don't do anything about the top node + if (m_pParent == nullptr) + return; + + // Now actually add this one + // notice we won't add it if it's doesn't have any parent + // (i.e. being the base object) + if (m_Name.empty()) + { + throw PSERROR_GUI_ObjectNeedsName(); + } + + if (ObjectMap.find(m_Name) != ObjectMap.end()) + { + throw PSERROR_GUI_NameAmbiguity(m_Name.c_str()); + } + else + { + ObjectMap[m_Name] = this; + } +} + +template +void IGUIObject::RegisterSetting(const CStr& Name, T& Value) +{ + if (SettingExists(Name)) + LOGERROR("The setting '%s' already exists on the object '%s'!", Name.c_str(), GetPresentableName().c_str()); + else + m_Settings.emplace(Name, new CGUISetting(*this, Name, Value)); +} + +bool IGUIObject::SettingExists(const CStr& Setting) const +{ + return m_Settings.find(Setting) != m_Settings.end(); +} + +template +T& IGUIObject::GetSetting(const CStr& Setting) +{ + return static_cast* >(m_Settings.at(Setting))->m_pSetting; +} + +template +const T& IGUIObject::GetSetting(const CStr& Setting) const +{ + return static_cast* >(m_Settings.at(Setting))->m_pSetting; +} + +bool IGUIObject::SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage) +{ + const std::map::iterator it = m_Settings.find(Setting); + if (it == m_Settings.end()) + { + LOGERROR("GUI object '%s' has no property called '%s', can't set parse and set value '%s'", GetPresentableName().c_str(), Setting.c_str(), Value.ToUTF8().c_str()); + return false; + } + return it->second->FromString(Value, SendMessage); +} + +template +void IGUIObject::SetSetting(const CStr& Setting, T& Value, const bool SendMessage) +{ + PreSettingChange(Setting); + static_cast* >(m_Settings.at(Setting))->m_pSetting = std::move(Value); + SettingChanged(Setting, SendMessage); +} + +template +void IGUIObject::SetSetting(const CStr& Setting, const T& Value, const bool SendMessage) +{ + PreSettingChange(Setting); + static_cast* >(m_Settings.at(Setting))->m_pSetting = Value; + SettingChanged(Setting, SendMessage); +} + +void IGUIObject::PreSettingChange(const CStr& Setting) +{ + if (Setting == "hotkey") + m_pGUI.UnsetObjectHotkey(this, GetSetting(Setting)); +} + +void IGUIObject::SettingChanged(const CStr& Setting, const bool SendMessage) +{ + if (Setting == "size") + { + // If setting was "size", we need to re-cache itself and all children + RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); + } + else if (Setting == "hidden") + { + // Hiding an object requires us to reset it and all children + if (m_Hidden) + RecurseObject(nullptr, &IGUIObject::ResetStates); + } + else if (Setting == "hotkey") + m_pGUI.SetObjectHotkey(this, GetSetting(Setting)); + + if (SendMessage) + { + SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting); + HandleMessage(msg); + } +} + +bool IGUIObject::IsMouseOver() const +{ + return m_CachedActualSize.PointInside(m_pGUI.GetMousePos()); +} + +bool IGUIObject::MouseOverIcon() +{ + return false; +} + +void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver) +{ + if (pMouseOver == this) + { + if (!m_MouseHovering) + SendEvent(GUIM_MOUSE_ENTER, "mouseenter"); + + m_MouseHovering = true; + + SendEvent(GUIM_MOUSE_OVER, "mousemove"); + } + else + { + if (m_MouseHovering) + { + m_MouseHovering = false; + SendEvent(GUIM_MOUSE_LEAVE, "mouseleave"); + } + } +} + +void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject) +{ + if (!IsMouseOver()) + return; + + // Check if we've got competition at all + if (pObject == nullptr) + { + pObject = this; + return; + } + + // Or if it's closer + if (GetBufferedZ() >= pObject->GetBufferedZ()) + { + pObject = this; + return; + } +} + +IGUIObject* IGUIObject::GetParent() const +{ + // Important, we're not using GetParent() for these + // checks, that could screw it up + if (m_pParent && m_pParent->m_pParent == nullptr) + return nullptr; + + return m_pParent; +} + +void IGUIObject::ResetStates() +{ + // Notify the gui that we aren't hovered anymore + UpdateMouseOver(nullptr); +} + +void IGUIObject::UpdateCachedSize() +{ + // If absolute="false" and the object has got a parent, + // use its cached size instead of the screen. Notice + // it must have just been cached for it to work. + if (!m_Absolute && m_pParent && !IsRootObject()) + m_CachedActualSize = m_Size.GetSize(m_pParent->m_CachedActualSize); + else + m_CachedActualSize = m_Size.GetSize(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale)); + + // In a few cases, GUI objects have to resize to fill the screen + // but maintain a constant aspect ratio. + // Adjust the size to be the max possible, centered in the original size: + if (m_AspectRatio) + { + if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight() * m_AspectRatio) + { + float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight() * m_AspectRatio; + m_CachedActualSize.left += delta/2.f; + m_CachedActualSize.right -= delta/2.f; + } + else + { + float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth() / m_AspectRatio; + m_CachedActualSize.bottom -= delta/2.f; + m_CachedActualSize.top += delta/2.f; + } + } +} + +void IGUIObject::LoadStyle(const CStr& StyleName) +{ + if (!m_pGUI.HasStyle(StyleName)) + debug_warn(L"IGUIObject::LoadStyle failed"); + + // The default style may specify settings for any GUI object. + // Other styles are reported if they specify a Setting that does not exist, + // so that the XML author is informed and can correct the style. + + for (const std::pair& p : m_pGUI.GetStyle(StyleName).m_SettingsDefaults) + { + if (SettingExists(p.first)) + SetSettingFromString(p.first, p.second, true); + else if (StyleName != "default") + LOGWARNING("GUI object has no setting \"%s\", but the style \"%s\" defines it", p.first, StyleName.c_str()); + } +} + +float IGUIObject::GetBufferedZ() const +{ + if (m_Absolute) + return m_Z; + + if (GetParent()) + return GetParent()->GetBufferedZ() + m_Z; + + // In philosophy, a parentless object shouldn't be able to have a relative sizing, + // but we'll accept it so that absolute can be used as default without a complaint. + // Also, you could consider those objects children to the screen resolution. + return m_Z; +} + +void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI& pGUI) +{ + JSContext* cx = pGUI.GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue globalVal(cx, pGUI.GetGlobalObject()); + JS::RootedObject globalObj(cx, &globalVal.toObject()); + + const int paramCount = 1; + const char* paramNames[paramCount] = { "mouse" }; + + // Location to report errors from + CStr CodeName = GetName()+" "+Action; + + // Generate a unique name + static int x = 0; + char buf[64]; + sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str()); + + JS::CompileOptions options(cx); + options.setFileAndLine(CodeName.c_str(), 0); + options.setIsRunOnce(false); + + JS::RootedFunction func(cx); + JS::AutoObjectVector emptyScopeChain(cx); + if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func)) + { + LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str()); + return; + } + + JS::RootedObject funcObj(cx, JS_GetFunctionObject(func)); + SetScriptHandler(Action, funcObj); +} + +void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function) +{ + if (m_ScriptHandlers.empty()) + JS_AddExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetJSRuntime(), Trace, this); + + m_ScriptHandlers[Action] = JS::Heap(Function); +} + +InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName) +{ + PROFILE2_EVENT("gui event"); + PROFILE2_ATTR("type: %s", EventName.c_str()); + PROFILE2_ATTR("object: %s", m_Name.c_str()); + + SGUIMessage msg(type); + HandleMessage(msg); + + ScriptEvent(EventName); + + return (msg.skipped ? IN_PASS : IN_HANDLED); +} + +void IGUIObject::ScriptEvent(const CStr& Action) +{ + std::map >::iterator it = m_ScriptHandlers.find(Action); + if (it == m_ScriptHandlers.end()) + return; + + JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + + // Set up the 'mouse' parameter + JS::RootedValue mouse(cx); + + const CPos& mousePos = m_pGUI.GetMousePos(); + + ScriptInterface::CreateObject( + cx, + &mouse, + "x", mousePos.x, + "y", mousePos.y, + "buttons", m_pGUI.GetMouseButtons()); + + JS::AutoValueVector paramData(cx); + paramData.append(mouse); + JS::RootedObject obj(cx, GetJSObject()); + JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); + JS::RootedValue result(cx); + bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result); + if (!ok) + { + // We have no way to propagate the script exception, so just ignore it + // and hope the caller checks JS_IsExceptionPending + } +} + +void IGUIObject::ScriptEvent(const CStr& Action, const JS::HandleValueArray& paramData) +{ + std::map >::iterator it = m_ScriptHandlers.find(Action); + if (it == m_ScriptHandlers.end()) + return; + + JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + JS::RootedObject obj(cx, GetJSObject()); + JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); + JS::RootedValue result(cx); + + if (!JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result)) + JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str()); +} + +void IGUIObject::CreateJSObject() +{ + JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + + m_JSObject.init(cx, m_pGUI.GetScriptInterface()->CreateCustomObject("GUIObject")); + JS_SetPrivate(m_JSObject.get(), this); + + RegisterScriptFunctions(); +} + +JSObject* IGUIObject::GetJSObject() +{ + // Cache the object when somebody first asks for it, because otherwise + // we end up doing far too much object allocation. + if (!m_JSObject.initialized()) + CreateJSObject(); + + return m_JSObject.get(); +} + +bool IGUIObject::IsEnabled() const +{ + return m_Enabled; +} + +bool IGUIObject::IsHidden() const +{ + return m_Hidden; +} + +bool IGUIObject::IsHiddenOrGhost() const +{ + return m_Hidden || m_Ghost; +} + +void IGUIObject::PlaySound(const CStrW& soundPath) const +{ + if (g_SoundManager && !soundPath.empty()) + g_SoundManager->PlayAsUI(soundPath.c_str(), false); +} + +CStr IGUIObject::GetPresentableName() const +{ + // __internal(), must be at least 13 letters to be able to be + // an internal name + if (m_Name.length() <= 12) + return m_Name; + + if (m_Name.substr(0, 10) == "__internal") + return CStr("[unnamed object]"); + else + return m_Name; +} + +void IGUIObject::SetFocus() +{ + m_pGUI.SetFocusedObject(this); +} + +bool IGUIObject::IsFocused() const +{ + return m_pGUI.GetFocusedObject() == this; +} + +bool IGUIObject::IsBaseObject() const +{ + return this == &m_pGUI.GetBaseObject(); +} + +bool IGUIObject::IsRootObject() const +{ + return m_pParent == &m_pGUI.GetBaseObject(); +} + +void IGUIObject::TraceMember(JSTracer* trc) +{ + // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced! + + for (std::pair>& handler : m_ScriptHandlers) + JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers"); +} + +// Instantiate templated functions: +// These functions avoid copies by working with a reference and move semantics. +#define TYPE(T) \ + template void IGUIObject::RegisterSetting(const CStr& Name, T& Value); \ + template T& IGUIObject::GetSetting(const CStr& Setting); \ + template const T& IGUIObject::GetSetting(const CStr& Setting) const; \ + template void IGUIObject::SetSetting(const CStr& Setting, T& Value, const bool SendMessage); \ + +#include "gui/GUISettingTypes.h" +#undef TYPE + +// Copying functions - discouraged except for primitives. +#define TYPE(T) \ + template void IGUIObject::SetSetting(const CStr& Setting, const T& Value, const bool SendMessage); \ + +#define GUITYPE_IGNORE_NONCOPYABLE +#include "gui/GUISettingTypes.h" +#undef GUITYPE_IGNORE_NONCOPYABLE +#undef TYPE Index: ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.h =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.h +++ ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.h @@ -0,0 +1,94 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_IGUISCROLLBAROWNER +#define INCLUDED_IGUISCROLLBAROWNER + +#include "gui/ObjectBases/IGUIObject.h" + +#include + +struct SGUIScrollBarStyle; +class IGUIScrollBar; + +/** + * Base-class this if you want an object to contain + * one, or several, scroll-bars. + */ +class IGUIScrollBarOwner +{ + NONCOPYABLE(IGUIScrollBarOwner); + + friend class IGUIScrollBar; + +public: + IGUIScrollBarOwner(IGUIObject& m_pObject); + virtual ~IGUIScrollBarOwner(); + + virtual void Draw(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates(); + + /** + * Interface for the m_ScrollBar to use. + */ + virtual const SGUIScrollBarStyle* GetScrollBarStyle(const CStr& style) const; + + /** + * Add a scroll-bar + */ + virtual void AddScrollBar(IGUIScrollBar* scrollbar); + + /** + * Get Scroll Bar reference (it should be transparent it's actually + * pointers). + */ + virtual IGUIScrollBar& GetScrollBar(const int& index) + { + return *m_ScrollBars[index]; + } + + /** + * Get the position of the scroll bar at @param index. + * Equivalent to GetScrollbar(index).GetPos(). + */ + virtual float GetScrollBarPos(const int index) const; + +protected: + /** + * Predominately you will only have one, but you can have + * as many as you like. + */ + std::vector m_ScrollBars; + +private: + /** + * Reference to the IGUIObject. + * Private, because we don't want to inherit it in multiple classes. + */ + IGUIObject& m_pObject; +}; + +#endif // INCLUDED_IGUISCROLLBAROWNER Index: ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.cpp =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.cpp +++ ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.cpp @@ -0,0 +1,68 @@ +/* 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 "IGUIScrollBarOwner.h" + +#include "gui/CGUI.h" +#include "gui/IGUIScrollBar.h" + +IGUIScrollBarOwner::IGUIScrollBarOwner(IGUIObject& pObject) + : m_pObject(pObject) +{ +} + +IGUIScrollBarOwner::~IGUIScrollBarOwner() +{ + for (IGUIScrollBar* const& sb : m_ScrollBars) + delete sb; +} + +void IGUIScrollBarOwner::ResetStates() +{ + for (IGUIScrollBar* const& sb : m_ScrollBars) + sb->SetBarPressed(false); +} + +void IGUIScrollBarOwner::AddScrollBar(IGUIScrollBar* scrollbar) +{ + scrollbar->SetHostObject(this); + m_ScrollBars.push_back(scrollbar); +} + +const SGUIScrollBarStyle* IGUIScrollBarOwner::GetScrollBarStyle(const CStr& style) const +{ + return m_pObject.GetGUI().GetScrollBarStyle(style); +} + +void IGUIScrollBarOwner::HandleMessage(SGUIMessage& msg) +{ + for (IGUIScrollBar* const& sb : m_ScrollBars) + sb->HandleMessage(msg); +} + +void IGUIScrollBarOwner::Draw() +{ + for (IGUIScrollBar* const& sb : m_ScrollBars) + sb->Draw(); +} + +float IGUIScrollBarOwner::GetScrollBarPos(const int index) const +{ + return m_ScrollBars[index]->GetPos(); +} Index: ps/trunk/source/gui/ObjectBases/IGUITextOwner.h =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUITextOwner.h +++ ps/trunk/source/gui/ObjectBases/IGUITextOwner.h @@ -0,0 +1,123 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +/* +GUI Object Base - Text Owner + +--Overview-- + + Interface class that enhance the IGUIObject with + cached CGUIStrings. This class is not at all needed, + and many controls that will use CGUIStrings might + not use this, but does help for regular usage such + as a text-box, a button, a radio button etc. +*/ + +#ifndef INCLUDED_IGUITEXTOWNER +#define INCLUDED_IGUITEXTOWNER + +#include "gui/ObjectBases/IGUIObject.h" + +#include + +struct CGUIColor; +class CGUIText; +class CGUIString; + +/** + * Framework for handling Output text. + */ +class IGUITextOwner +{ + NONCOPYABLE(IGUITextOwner); + +public: + IGUITextOwner(IGUIObject& pObject); + virtual ~IGUITextOwner(); + + /** + * Adds a text object. + */ + CGUIText& AddText(); + + /** + * Adds a text generated by the given arguments. + */ + CGUIText& AddText(const CGUIString& Text, const CStrW& Font, const float& Width, const float& BufferZone); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * @see IGUIObject#UpdateCachedSize() + */ + virtual void UpdateCachedSize(); + + /** + * Draws the Text. + * + * @param index Index value of text. Mostly this will be 0 + * @param color + * @param pos Position + * @param z Z value + * @param clipping Clipping rectangle, don't even add a parameter + * to get no clipping. + */ + virtual void DrawText(size_t index, const CGUIColor& color, const CPos& pos, float z, const CRect& clipping = CRect()); + + /** + * Test if mouse position is over an icon + */ + virtual bool MouseOverIcon(); + +protected: + /** + * Setup texts. Functions that sets up all texts when changes have been made. + */ + virtual void SetupText() = 0; + + /** + * Regenerate the text in case it is invalid. Should only be called when inevitable. + */ + virtual void UpdateText(); + + /** + * Whether the cached text is currently valid (if not then SetupText will be called by Draw) + */ + bool m_GeneratedTextsValid; + + /** + * Texts that are generated and ready to be rendered. + */ + std::vector m_GeneratedTexts; + + /** + * Calculate the position for the text, based on the alignment. + */ + void CalculateTextPosition(CRect& ObjSize, CPos& TextPos, CGUIText& Text); + +private: + /** + * Reference to the IGUIObject. + * Private, because we don't want to inherit it in multiple classes. + */ + IGUIObject& m_pObject; +}; + +#endif // INCLUDED_IGUITEXTOWNER Index: ps/trunk/source/gui/ObjectBases/IGUITextOwner.cpp =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUITextOwner.cpp +++ ps/trunk/source/gui/ObjectBases/IGUITextOwner.cpp @@ -0,0 +1,126 @@ +/* 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 "IGUITextOwner.h" + +#include "gui/CGUI.h" +#include "gui/SettingTypes/CGUIString.h" + +#include + +IGUITextOwner::IGUITextOwner(IGUIObject& pObject) + : m_pObject(pObject), + m_GeneratedTextsValid() +{ +} + +IGUITextOwner::~IGUITextOwner() +{ +} + +CGUIText& IGUITextOwner::AddText() +{ + m_GeneratedTexts.emplace_back(); + return m_GeneratedTexts.back(); +} + +CGUIText& IGUITextOwner::AddText(const CGUIString& Text, const CStrW& Font, const float& Width, const float& BufferZone) +{ + // Avoids a move constructor + m_GeneratedTexts.emplace_back(m_pObject.GetGUI(), Text, Font, Width, BufferZone, &m_pObject); + return m_GeneratedTexts.back(); +} + +void IGUITextOwner::HandleMessage(SGUIMessage& Message) +{ + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + // Everything that can change the visual appearance. + // it is assumed that the text of the object will be dependent on + // these. Although that is not certain, but one will have to manually + // change it and disregard this function. + // TODO Gee: (2004-09-07) Make sure this is all options that can affect the text. + if (Message.value == "size" || Message.value == "z" || + Message.value == "absolute" || Message.value == "caption" || + Message.value == "font" || Message.value == "textcolor" || + Message.value == "text_align" || Message.value == "text_valign" || + Message.value == "buffer_zone") + { + m_GeneratedTextsValid = false; + } + break; + + default: + break; + } +} + +void IGUITextOwner::UpdateCachedSize() +{ + // update our text positions + m_GeneratedTextsValid = false; +} + +void IGUITextOwner::UpdateText() +{ + if (!m_GeneratedTextsValid) + { + SetupText(); + m_GeneratedTextsValid = true; + } +} + +void IGUITextOwner::DrawText(size_t index, const CGUIColor& color, const CPos& pos, float z, const CRect& clipping) +{ + UpdateText(); + + ENSURE(index < m_GeneratedTexts.size() && "Trying to draw a Text Index within a IGUITextOwner that doesn't exist"); + + m_GeneratedTexts.at(index).Draw(m_pObject.GetGUI(), color, pos, z, clipping); +} + +void IGUITextOwner::CalculateTextPosition(CRect& ObjSize, CPos& TextPos, CGUIText& Text) +{ + // The horizontal Alignment is now computed in GenerateText in order to not have to + // loop through all of the TextCall objects again. + TextPos.x = ObjSize.left; + + switch (m_pObject.GetSetting("text_valign")) + { + case EVAlign_Top: + TextPos.y = ObjSize.top; + break; + case EVAlign_Center: + // Round to integer pixel values, else the fonts look awful + TextPos.y = floorf(ObjSize.CenterPoint().y - Text.GetSize().cy / 2.f); + break; + case EVAlign_Bottom: + TextPos.y = ObjSize.bottom - Text.GetSize().cy; + break; + default: + debug_warn(L"Broken EVAlign in CButton::SetupText()"); + break; + } +} + +bool IGUITextOwner::MouseOverIcon() +{ + return false; +} Index: ps/trunk/source/gui/ObjectTypes/CButton.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CButton.h +++ ps/trunk/source/gui/ObjectTypes/CButton.h @@ -0,0 +1,89 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CBUTTON +#define INCLUDED_CBUTTON + +#include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIButtonBehavior.h" +#include "gui/ObjectBases/IGUIObject.h" +#include "gui/ObjectBases/IGUITextOwner.h" +#include "gui/SettingTypes/CGUIString.h" + +class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior +{ + GUI_OBJECT(CButton) + +public: + CButton(CGUI& pGUI); + virtual ~CButton(); + + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates(); + + /** + * @see IGUIObject#UpdateCachedSize() + */ + virtual void UpdateCachedSize(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Draws the Button + */ + virtual void Draw(); + +protected: + /** + * Sets up text, should be called every time changes has been + * made that can change the visual. + */ + void SetupText(); + + /** + * Picks the text color depending on current object settings. + */ + const CGUIColor& ChooseColor(); + + /** + * Placement of text. + */ + CPos m_TextPos; + + // Settings + float m_BufferZone; + i32 m_CellID; + CGUIString m_Caption; + CStrW m_Font; + CGUISpriteInstance m_Sprite; + CGUISpriteInstance m_SpriteOver; + CGUISpriteInstance m_SpritePressed; + CGUISpriteInstance m_SpriteDisabled; + EAlign m_TextAlign; + EVAlign m_TextVAlign; + CGUIColor m_TextColor; + CGUIColor m_TextColorOver; + CGUIColor m_TextColorPressed; + CGUIColor m_TextColorDisabled; +}; + +#endif // INCLUDED_CBUTTON Index: ps/trunk/source/gui/ObjectTypes/CButton.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CButton.cpp +++ ps/trunk/source/gui/ObjectTypes/CButton.cpp @@ -0,0 +1,119 @@ +/* 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/CGUI.h" +#include "gui/CGUIText.h" +#include "gui/SettingTypes/CGUIColor.h" + +CButton::CButton(CGUI& pGUI) + : IGUIObject(pGUI), + IGUIButtonBehavior(*static_cast(this)), + IGUITextOwner(*static_cast(this)), + m_BufferZone(), + m_CellID(), + m_Caption(), + m_Font(), + m_Sprite(), + m_SpriteOver(), + m_SpritePressed(), + m_SpriteDisabled(), + m_TextAlign(), + m_TextVAlign(), + m_TextColor(), + m_TextColorOver(), + m_TextColorPressed(), + m_TextColorDisabled() +{ + RegisterSetting("buffer_zone", m_BufferZone); + RegisterSetting("cell_id", m_CellID); + RegisterSetting("caption", m_Caption); + RegisterSetting("font", m_Font); + RegisterSetting("sprite", m_Sprite); + RegisterSetting("sprite_over", m_SpriteOver); + RegisterSetting("sprite_pressed", m_SpritePressed); + RegisterSetting("sprite_disabled", m_SpriteDisabled); + RegisterSetting("text_align", m_TextAlign); + RegisterSetting("text_valign", m_TextVAlign); + RegisterSetting("textcolor", m_TextColor); + RegisterSetting("textcolor_over", m_TextColorOver); + RegisterSetting("textcolor_pressed", m_TextColorPressed); + RegisterSetting("textcolor_disabled", m_TextColorDisabled); + + AddText(); +} + +CButton::~CButton() +{ +} + +void CButton::SetupText() +{ + ENSURE(m_GeneratedTexts.size() == 1); + + m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, m_CachedActualSize.GetWidth(), m_BufferZone, this); + CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]); +} + +void CButton::ResetStates() +{ + IGUIObject::ResetStates(); + IGUIButtonBehavior::ResetStates(); +} + +void CButton::UpdateCachedSize() +{ + IGUIObject::UpdateCachedSize(); + IGUITextOwner::UpdateCachedSize(); +} + +void CButton::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + IGUIButtonBehavior::HandleMessage(Message); + IGUITextOwner::HandleMessage(Message); +} + +void CButton::Draw() +{ + const float bz = GetBufferedZ(); + + m_pGUI.DrawSprite( + GetButtonSprite(m_Sprite, m_SpriteOver, m_SpritePressed, m_SpriteDisabled), + m_CellID, + bz, + m_CachedActualSize); + + DrawText(0, ChooseColor(), m_TextPos, bz + 0.1f); +} + +const CGUIColor& CButton::ChooseColor() +{ + if (!m_Enabled) + return m_TextColorDisabled || m_TextColor; + + if (!m_MouseHovering) + return m_TextColor; + + if (m_Pressed) + return m_TextColorPressed || m_TextColor; + + return m_TextColorOver || m_TextColor; +} Index: ps/trunk/source/gui/ObjectTypes/CChart.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CChart.h +++ ps/trunk/source/gui/ObjectTypes/CChart.h @@ -0,0 +1,111 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CCHART +#define INCLUDED_CCHART + +#include "graphics/ShaderProgramPtr.h" +#include "gui/ObjectBases/IGUITextOwner.h" +#include "gui/SettingTypes/CGUIColor.h" +#include "gui/SettingTypes/CGUIList.h" +#include "gui/SettingTypes/CGUISeries.h" +#include "maths/Vector2D.h" + +#include + +struct CChartData +{ + // Avoid copying the container. + NONCOPYABLE(CChartData); + MOVABLE(CChartData); + CChartData() = default; + + CGUIColor m_Color; + std::vector m_Points; +}; + +/** + * Chart for a data visualization as lines or points + */ +class CChart : public IGUIObject, public IGUITextOwner +{ + GUI_OBJECT(CChart) + +public: + CChart(CGUI& pGUI); + virtual ~CChart(); + +protected: + /** + * @see IGUIObject#UpdateCachedSize() + */ + void UpdateCachedSize(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Draws the Chart + */ + virtual void Draw(); + + virtual CRect GetChartRect() const; + + void UpdateSeries(); + + void SetupText(); + + std::vector m_Series; + + CVector2D m_LeftBottom, m_RightTop; + + std::vector m_TextPositions; + + bool m_EqualX, m_EqualY; + + // Settings + CGUIColor m_AxisColor; + float m_AxisWidth; + float m_BufferZone; + CStrW m_Font; + CStrW m_FormatX; + CStrW m_FormatY; + CGUIList m_SeriesColor; + CGUISeries m_SeriesSetting; + EAlign m_TextAlign; + +private: + /** + * Helper functions + */ + void DrawLine(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const; + + // Draws the triangle sequence so that the each next triangle has a common edge with the previous one. + // If we need to draw n triangles, we need only n + 2 points. + void DrawTriangleStrip(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const; + + // Represents axes as triangles and draws them with DrawTriangleStrip. + void DrawAxes(const CShaderProgramPtr& shader) const; + + CSize AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone); + + void UpdateBounds(); +}; + +#endif // INCLUDED_CCHART Index: ps/trunk/source/gui/ObjectTypes/CChart.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CChart.cpp +++ ps/trunk/source/gui/ObjectTypes/CChart.cpp @@ -0,0 +1,318 @@ +/* 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 "gui/GUIMatrix.h" +#include "gui/SettingTypes/CGUIList.h" +#include "gui/SettingTypes/CGUISeries.h" +#include "gui/SettingTypes/CGUIString.h" +#include "ps/CLogger.h" +#include "ps/Profile.h" +#include "renderer/Renderer.h" + +#include + +CChart::CChart(CGUI& pGUI) + : IGUIObject(pGUI), + IGUITextOwner(*static_cast(this)), + m_AxisColor(), + m_AxisWidth(), + m_BufferZone(), + m_Font(), + m_FormatX(), + m_FormatY(), + m_SeriesColor(), + m_SeriesSetting(), + m_TextAlign() +{ + RegisterSetting("axis_color", m_AxisColor); + RegisterSetting("axis_width", m_AxisWidth); + RegisterSetting("buffer_zone", m_BufferZone); + RegisterSetting("font", m_Font); + RegisterSetting("format_x", m_FormatX); + RegisterSetting("format_y", m_FormatY); + RegisterSetting("series_color", m_SeriesColor); + RegisterSetting("series", m_SeriesSetting); + RegisterSetting("text_align", m_TextAlign); +} + +CChart::~CChart() +{ +} + +void CChart::UpdateCachedSize() +{ + IGUIObject::UpdateCachedSize(); + IGUITextOwner::UpdateCachedSize(); +} + +void CChart::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + // IGUITextOwner::HandleMessage(Message); performed in UpdateSeries + + // TODO: implement zoom + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + { + 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 + DrawTriangleStrip(shader, m_AxisColor, 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() +{ + m_Series.clear(); + m_Series.resize(m_SeriesSetting.m_Series.size()); + + for (size_t i = 0; i < m_SeriesSetting.m_Series.size(); ++i) + { + CChartData& data = m_Series[i]; + + if (i < m_SeriesColor.m_Items.size() && !data.m_Color.ParseString(m_pGUI, m_SeriesColor.m_Items[i].GetOriginalString().ToUTF8(), 0)) + LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(m_SeriesColor.m_Items[i].GetOriginalString())); + + data.m_Points = m_SeriesSetting.m_Series[i]; + } + UpdateBounds(); + + SetupText(); +} + +void CChart::SetupText() +{ + m_GeneratedTexts.clear(); + m_TextPositions.clear(); + + if (m_Series.empty()) + return; + + // Add Y-axis + 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, m_Font, m_BufferZone); + 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, m_Font, m_BufferZone); + m_TextPositions.emplace_back(GetChartRect().TopLeft() + CPos(0.f, height / 3.f * i)); + } + + // Add X-axis + const float width = GetChartRect().GetWidth(); + if (m_EqualX) + { + CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X, m_Font, m_BufferZone); + 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, m_Font, m_BufferZone); + 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).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/ObjectTypes/CCheckBox.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CCheckBox.h +++ ps/trunk/source/gui/ObjectTypes/CCheckBox.h @@ -0,0 +1,61 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CCHECKBOX +#define INCLUDED_CCHECKBOX + +#include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIButtonBehavior.h" + +class CCheckBox : public IGUIObject, public IGUIButtonBehavior +{ + GUI_OBJECT(CCheckBox) + +public: + CCheckBox(CGUI& pGUI); + virtual ~CCheckBox(); + + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Draws the control + */ + virtual void Draw(); + +protected: + // Settings + i32 m_CellID; + bool m_Checked; + CGUISpriteInstance m_SpriteUnchecked; + CGUISpriteInstance m_SpriteUncheckedOver; + CGUISpriteInstance m_SpriteUncheckedPressed; + CGUISpriteInstance m_SpriteUncheckedDisabled; + CGUISpriteInstance m_SpriteChecked; + CGUISpriteInstance m_SpriteCheckedOver; + CGUISpriteInstance m_SpriteCheckedPressed; + CGUISpriteInstance m_SpriteCheckedDisabled; +}; + +#endif // INCLUDED_CCHECKBOX Index: ps/trunk/source/gui/ObjectTypes/CCheckBox.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CCheckBox.cpp +++ ps/trunk/source/gui/ObjectTypes/CCheckBox.cpp @@ -0,0 +1,87 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "CCheckBox.h" + +#include "gui/CGUI.h" + +CCheckBox::CCheckBox(CGUI& pGUI) + : IGUIObject(pGUI), + IGUIButtonBehavior(*static_cast(this)), + m_CellID(), + m_Checked(), + m_SpriteUnchecked(), + m_SpriteUncheckedOver(), + m_SpriteUncheckedPressed(), + m_SpriteUncheckedDisabled(), + m_SpriteChecked(), + m_SpriteCheckedOver(), + m_SpriteCheckedPressed(), + m_SpriteCheckedDisabled() +{ + RegisterSetting("cell_id", m_CellID); + RegisterSetting("checked", m_Checked), + RegisterSetting("sprite", m_SpriteUnchecked); + RegisterSetting("sprite_over", m_SpriteUncheckedOver); + RegisterSetting("sprite_pressed", m_SpriteUncheckedPressed); + RegisterSetting("sprite_disabled", m_SpriteUncheckedDisabled); + RegisterSetting("sprite2", m_SpriteChecked); + RegisterSetting("sprite2_over", m_SpriteCheckedOver); + RegisterSetting("sprite2_pressed", m_SpriteCheckedPressed); + RegisterSetting("sprite2_disabled", m_SpriteCheckedDisabled); +} + +CCheckBox::~CCheckBox() +{ +} + +void CCheckBox::ResetStates() +{ + IGUIObject::ResetStates(); + IGUIButtonBehavior::ResetStates(); +} + +void CCheckBox::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + IGUIButtonBehavior::HandleMessage(Message); + + switch (Message.type) + { + case GUIM_PRESSED: + { + SetSetting("checked", !m_Checked, true); + break; + } + + default: + break; + } +} + +void CCheckBox::Draw() +{ + m_pGUI.DrawSprite( + m_Checked ? + GetButtonSprite(m_SpriteChecked, m_SpriteCheckedOver, m_SpriteCheckedPressed, m_SpriteCheckedDisabled) : + GetButtonSprite(m_SpriteUnchecked, m_SpriteUncheckedOver, m_SpriteUncheckedPressed, m_SpriteUncheckedDisabled), + m_CellID, + GetBufferedZ(), + m_CachedActualSize); +} Index: ps/trunk/source/gui/ObjectTypes/CDropDown.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CDropDown.h +++ ps/trunk/source/gui/ObjectTypes/CDropDown.h @@ -0,0 +1,142 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +/* +GUI Object - Drop Down (list) + +--Overview-- + + Works just like a list-box, but it hides + all the elements that aren't selected. They + can be brought up by pressing the control. +*/ + +#ifndef INCLUDED_CDROPDOWN +#define INCLUDED_CDROPDOWN + +#include "gui/CGUISprite.h" +#include "gui/ObjectTypes/CList.h" + +#include + +/** + * Drop Down + * + * The control can be pressed, but we will not inherent + * this behavior from IGUIButtonBehavior, because when + * you press this control, the list with elements will + * immediately appear, and not first after release + * (which is the whole gist of the IGUIButtonBehavior). + */ +class CDropDown : public CList +{ + GUI_OBJECT(CDropDown) + +public: + CDropDown(CGUI& pGUI); + virtual ~CDropDown(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Handle events manually to catch keyboard inputting. + */ + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + + /** + * Draws the Button + */ + virtual void Draw(); + + // This is one of the few classes we actually need to redefine this function + // this is because the size of the control changes whether it is open + // or closed. + virtual bool IsMouseOver() const; + + virtual float GetBufferedZ() const; + +protected: + /** + * If the size changed, the texts have to be updated as + * the word wrapping depends on the size. + */ + virtual void UpdateCachedSize(); + + /** + * Sets up text, should be called every time changes has been + * made that can change the visual. + */ + void SetupText(); + + // Sets up the cached GetListRect. Decided whether it should + // have a scrollbar, and so on. + virtual void SetupListRect(); + + // Specify a new List rectangle. + virtual CRect GetListRect() const; + + /** + * Placement of text. + */ + CPos m_TextPos; + + // Is the dropdown opened? + bool m_Open; + + // I didn't cache this at first, but it's just as easy as caching + // m_CachedActualSize, so I thought, what the heck it's used a lot. + CRect m_CachedListRect; + + // Hide scrollbar when it's not needed + bool m_HideScrollBar; + + // Not necessarily the element that is selected, this is just + // which element should be highlighted. When opening the dropdown + // it is set to "selected", but then when moving the mouse it will + // change. + int m_ElementHighlight; + + // Stores any text entered by the user for quick access to an element + // (ie if you type "acro" it will take you to acropolis). + std::string m_InputBuffer; + + // used to know if we want to restart anew or add to m_inputbuffer. + double m_TimeOfLastInput; + + // Settings + float m_ButtonWidth; + float m_DropDownSize; + float m_DropDownBuffer; + u32 m_MinimumVisibleItems; + CStrW m_SoundClosed; + CStrW m_SoundEnter; + CStrW m_SoundLeave; + CStrW m_SoundOpened; + CGUISpriteInstance m_SpriteDisabled; + CGUISpriteInstance m_SpriteList; + CGUISpriteInstance m_Sprite2; + CGUISpriteInstance m_Sprite2Over; + CGUISpriteInstance m_Sprite2Pressed; + CGUISpriteInstance m_Sprite2Disabled; + CGUIColor m_TextColorDisabled; + EVAlign m_TextVAlign; +}; + +#endif // INCLUDED_CDROPDOWN Index: ps/trunk/source/gui/ObjectTypes/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CDropDown.cpp +++ ps/trunk/source/gui/ObjectTypes/CDropDown.cpp @@ -0,0 +1,505 @@ +/* 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/CGUI.h" +#include "gui/IGUIScrollBar.h" +#include "gui/SettingTypes/CGUIColor.h" +#include "gui/SettingTypes/CGUIList.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/timer.h" +#include "ps/Profile.h" + +CDropDown::CDropDown(CGUI& pGUI) + : CList(pGUI), + m_Open(), + m_HideScrollBar(), + m_ElementHighlight(-1), + m_ButtonWidth(), + m_DropDownSize(), + m_DropDownBuffer(), + m_MinimumVisibleItems(), + m_SoundClosed(), + m_SoundEnter(), + m_SoundLeave(), + m_SoundOpened(), + m_SpriteDisabled(), + m_SpriteList(), + m_Sprite2(), + m_Sprite2Over(), + m_Sprite2Pressed(), + m_Sprite2Disabled(), + m_TextColorDisabled(), + m_TextVAlign() +{ + RegisterSetting("button_width", m_ButtonWidth); + RegisterSetting("dropdown_size", m_DropDownSize); + RegisterSetting("dropdown_buffer", m_DropDownBuffer); + RegisterSetting("minimum_visible_items", m_MinimumVisibleItems); + RegisterSetting("sound_closed", m_SoundClosed); + RegisterSetting("sound_enter", m_SoundEnter); + RegisterSetting("sound_leave", m_SoundLeave); + RegisterSetting("sound_opened", m_SoundOpened); + // Setting "sprite" is registered by CList and used as the background + RegisterSetting("sprite_disabled", m_SpriteDisabled); + RegisterSetting("sprite_list", m_SpriteList); // Background of the drop down list + RegisterSetting("sprite2", m_Sprite2); // Button that sits to the right + RegisterSetting("sprite2_over", m_Sprite2Over); + RegisterSetting("sprite2_pressed", m_Sprite2Pressed); + RegisterSetting("sprite2_disabled", m_Sprite2Disabled); + RegisterSetting("textcolor_disabled", m_TextColorDisabled); + RegisterSetting("text_valign", m_TextVAlign); + // Add these in CList! And implement TODO + //RegisterSetting("textcolor_over"); + //RegisterSetting("textcolor_pressed"); + + // Scrollbar is forced to be true. + SetSetting("scrollbar", true, true); +} + +CDropDown::~CDropDown() +{ +} + +void CDropDown::SetupText() +{ + SetupListRect(); + CList::SetupText(); +} + +void CDropDown::UpdateCachedSize() +{ + CList::UpdateCachedSize(); + SetupText(); +} + +void CDropDown::HandleMessage(SGUIMessage& Message) +{ + // CList::HandleMessage(Message); placed after the switch! + + 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; + + const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f; + + CRect rect = GetListRect(); + mouse.y += scroll; + int set = -1; + for (int i = 0; i < static_cast(m_List.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: + { + if (m_Enabled) + PlaySound(m_SoundEnter); + break; + } + + case GUIM_MOUSE_LEAVE: + { + m_ElementHighlight = m_Selected; + + if (m_Enabled) + PlaySound(m_SoundLeave); + 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: + { + if (!m_Enabled) + { + PlaySound(m_SoundDisabled); + break; + } + + if (!m_Open) + { + if (m_List.m_Items.empty()) + return; + + m_Open = true; + GetScrollBar(0).SetZ(GetBufferedZ()); + m_ElementHighlight = m_Selected; + + // Start at the position of the selected item, if possible. + GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); + + PlaySound(m_SoundOpened); + 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()); + PlaySound(m_SoundClosed); + 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: + { + // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. + if (m_Open || !m_Enabled) + break; + + m_ElementHighlight = m_Selected; + + if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) + break; + + ++m_ElementHighlight; + SetSetting("selected", m_ElementHighlight, true); + break; + } + + case GUIM_MOUSE_WHEEL_UP: + { + // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. + if (m_Open || !m_Enabled) + break; + + m_ElementHighlight = m_Selected; + if (m_ElementHighlight - 1 < 0) + break; + + --m_ElementHighlight; + SetSetting("selected", m_ElementHighlight, true); + break; + } + + case GUIM_LOST_FOCUS: + { + if (m_Open) + PlaySound(m_SoundClosed); + + 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() + SetSetting("selected", m_ElementHighlight, true); + 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(); + + // 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(m_List.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(m_List.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) + { + SetSetting("selected", closest, true); + 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) + m_ElementHighlight = m_Selected; + + return result; +} + +void CDropDown::SetupListRect() +{ + extern int g_yres; + extern float g_GuiScale; + const float yres = g_yres / g_GuiScale; + + if (m_ItemsYPositions.empty()) + { + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, + m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize); + m_HideScrollBar = false; + } + // Too many items so use a scrollbar + else if (m_ItemsYPositions.back() > m_DropDownSize) + { + // Place items below if at least some items can be placed below + if (m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize <= yres) + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, + m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize); + else if ((m_ItemsYPositions.size() > m_MinimumVisibleItems && yres - m_CachedActualSize.bottom - m_DropDownBuffer >= m_ItemsYPositions[m_MinimumVisibleItems]) || + m_CachedActualSize.top < yres - m_CachedActualSize.bottom) + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, + 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 - m_DropDownBuffer - m_DropDownSize), + m_CachedActualSize.right, m_CachedActualSize.top - m_DropDownBuffer); + + m_HideScrollBar = false; + } + else + { + // Enough space below, no scrollbar needed + if (m_CachedActualSize.bottom + m_DropDownBuffer + m_ItemsYPositions.back() <= yres) + { + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, + m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + 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() > m_MinimumVisibleItems && yres - m_CachedActualSize.bottom - m_DropDownBuffer >= m_ItemsYPositions[m_MinimumVisibleItems]) || + m_CachedActualSize.top < yres - m_CachedActualSize.bottom) + { + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer, + 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 - m_DropDownBuffer - m_ItemsYPositions.back()), + m_CachedActualSize.right, m_CachedActualSize.top - m_DropDownBuffer); + m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + m_DropDownBuffer; + } + } +} + +CRect CDropDown::GetListRect() const +{ + return m_CachedListRect; +} + +bool CDropDown::IsMouseOver() const +{ + 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() +{ + const float bz = GetBufferedZ(); + const CGUISpriteInstance& sprite = m_Enabled ? m_Sprite : m_SpriteDisabled; + + m_pGUI.DrawSprite(sprite, m_CellID, bz, m_CachedActualSize); + + if (m_ButtonWidth > 0.f) + { + CRect rect(m_CachedActualSize.right - m_ButtonWidth, m_CachedActualSize.top, + m_CachedActualSize.right, m_CachedActualSize.bottom); + + if (!m_Enabled) + { + m_pGUI.DrawSprite(m_Sprite2Disabled || m_Sprite2, m_CellID, bz + 0.05f, rect); + } + else if (m_Open) + { + m_pGUI.DrawSprite(m_Sprite2Pressed || m_Sprite2, m_CellID, bz + 0.05f, rect); + } + else if (m_MouseHovering) + { + m_pGUI.DrawSprite(m_Sprite2Over || m_Sprite2, m_CellID, bz + 0.05f, rect); + } + else + m_pGUI.DrawSprite(m_Sprite2, m_CellID, bz + 0.05f, rect); + } + + if (m_Selected != -1) // TODO: Maybe check validity completely? + { + CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, + m_CachedActualSize.right - m_ButtonWidth, m_CachedActualSize.bottom); + + CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); + DrawText(m_Selected, m_Enabled ? m_TextColorSelected : m_TextColorDisabled, pos, bz + 0.1f, cliparea); + } + + // Disable scrollbar during drawing without sending a setting-changed message + bool old = m_ScrollBar; + + if (m_Open) + { + // TODO: drawScrollbar as an argument of DrawList? + if (m_HideScrollBar) + m_ScrollBar = false; + + DrawList(m_ElementHighlight, m_SpriteList, m_SpriteSelectArea, m_TextColor); + + if (m_HideScrollBar) + m_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/ObjectTypes/CGUIDummyObject.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CGUIDummyObject.h +++ ps/trunk/source/gui/ObjectTypes/CGUIDummyObject.h @@ -0,0 +1,49 @@ +/* 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 . + */ + +/* + * This is the top class of the whole GUI, all objects + * and settings are stored within this class. + */ + +#ifndef INCLUDED_CGUIDUMMYOBJECT +#define INCLUDED_CGUIDUMMYOBJECT + +#include "gui/ObjectBases/IGUIObject.h" + +/** + * Dummy object are used for the base object and objects of type "empty". + */ +class CGUIDummyObject : public IGUIObject +{ + GUI_OBJECT(CGUIDummyObject) + +public: + CGUIDummyObject(CGUI& pGUI) : IGUIObject(pGUI) {} + + virtual void Draw() {} + + /** + * Empty can never be hovered. It is only a category. + */ + virtual bool IsMouseOver() const + { + return false; + } +}; + +#endif // INCLUDED_CGUIDUMMYOBJECT Index: ps/trunk/source/gui/ObjectTypes/CImage.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CImage.h +++ ps/trunk/source/gui/ObjectTypes/CImage.h @@ -0,0 +1,53 @@ +/* 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_CIMAGE +#define INCLUDED_CIMAGE + +#include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIObject.h" + +/** + * Object just for drawing a sprite. Like CText, without the + * possibility to draw text. + * + * Created, because I've seen the user being indecisive about + * what control to use in these situations. I've seen button + * without functionality used, and that is a lot of unnecessary + * overhead. That's why I thought I'd go with an intuitive + * control. + */ +class CImage : public IGUIObject +{ + GUI_OBJECT(CImage) + +public: + CImage(CGUI& pGUI); + virtual ~CImage(); + +protected: + /** + * Draws the Image + */ + virtual void Draw(); + + // Settings + CGUISpriteInstance m_Sprite; + i32 m_CellID; +}; + +#endif // INCLUDED_CIMAGE Index: ps/trunk/source/gui/ObjectTypes/CImage.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CImage.cpp +++ ps/trunk/source/gui/ObjectTypes/CImage.cpp @@ -0,0 +1,40 @@ +/* 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 "CImage.h" + +#include "gui/CGUI.h" + +CImage::CImage(CGUI& pGUI) + : IGUIObject(pGUI), + m_Sprite(), + m_CellID() +{ + RegisterSetting("sprite", m_Sprite); + RegisterSetting("cell_id", m_CellID); +} + +CImage::~CImage() +{ +} + +void CImage::Draw() +{ + m_pGUI.DrawSprite(m_Sprite, m_CellID, GetBufferedZ(), m_CachedActualSize); +} Index: ps/trunk/source/gui/ObjectTypes/CInput.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CInput.h +++ ps/trunk/source/gui/ObjectTypes/CInput.h @@ -0,0 +1,212 @@ +/* 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_CINPUT +#define INCLUDED_CINPUT + +#include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIScrollBarOwner.h" +#include "lib/external_libraries/libsdl.h" + +#include + +/** + * Text field where you can input and edit the text. + * + * It doesn't use IGUITextOwner, because we don't need + * any other features than word-wrapping, and we need to be + * able to rapidly change the string. + */ +class CInput : public IGUIObject, public IGUIScrollBarOwner +{ + GUI_OBJECT(CInput) + +protected: // forwards + struct SRow; + +public: + CInput(CGUI& pGUI); + virtual ~CInput(); + + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates(); + + // Check where the mouse is hovering, and get the appropriate text position. + // return is the text-position index. + int GetMouseHoveringTextPosition() const; + + // Same as above, but only on one row in X, and a given value, not the mouse's. + // wanted is filled with x if the row didn't extend as far as the mouse pos. + int GetXTextPosition(const std::list::const_iterator& c, const float& x, float& wanted) const; + +protected: + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Handle events manually to catch keyboard inputting. + */ + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + + /** + * Handle events manually to catch keys which change the text. + */ + virtual void ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode); + + /** + * Handle events manually to catch keys which don't change the text. + */ + virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode); + + /** + * Handle hotkey events (called by ManuallyHandleEvent) + */ + virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev); + + /** + * @see IGUIObject#UpdateCachedSize() + */ + virtual void UpdateCachedSize(); + + /** + * Draws the Text + */ + virtual void Draw(); + + /** + * Calculate m_CharacterPosition + * the main task for this function is to perfom word-wrapping + * You input from which character it has been changed, because + * if we add a character to the very last end, we don't want + * process everything all over again! Also notice you can + * specify a 'to' also, it will only be used though if a '\n' + * appears, because then the word-wrapping won't change after + * that. + */ + void UpdateText(int from = 0, int to_before = -1, int to_after = -1); + + /** + * Delete the current selection. Also places the pointer at the + * crack between the two segments kept. + */ + void DeleteCurSelection(); + + /** + * Is text selected? It can be denote two ways, m_iBufferPos_Tail + * being -1 or the same as m_iBufferPos. This makes for clearer + * code. + */ + bool SelectingText() const; + + /// Get area of where text can be drawn. + float GetTextAreaWidth(); + + /// Called every time the auto-scrolling should be checked. + void UpdateAutoScroll(); + + /// Clear composed IME input when supported (SDL2 only). + void ClearComposedText(); + + /// Updates the buffer (cursor) position exposed to JS. + void UpdateBufferPositionSetting(); +protected: + /// Cursor position + int m_iBufferPos; + /// Cursor position we started to select from. (-1 if not selecting) + /// (NB: Can be larger than m_iBufferPos if selecting from back to front.) + int m_iBufferPos_Tail; + + /// If we're composing text with an IME + bool m_ComposingText; + /// The length and position of the current IME composition + int m_iComposedLength, m_iComposedPos; + /// The position to insert committed text + int m_iInsertPos; + + // the outer vector is lines, and the inner is X positions + // in a row. So that we can determine where characters are + // placed. It's important because we need to know where the + // pointer should be placed when the input control is pressed. + struct SRow + { + // Where the Row starts + int m_ListStart; + + // List of X values for each character. + std::vector m_ListOfX; + }; + + /** + * List of rows to ease changing its size, so iterators stay valid. + * For one-liners only one row is used. + */ + std::list m_CharacterPositions; + + // *** Things for a multi-lined input control *** // + + /** + * When you change row with up/down, and the row you jump to does + * not have anything at that X position, then it will keep the + * m_WantedX position in mind when switching to the next row. + * It will keep on being used until it reach a row which meets the + * requirements. + * 0.0f means not in use. + */ + float m_WantedX; + + /** + * If we are in the process of selecting a larger selection of text + * using the mouse click (hold) and drag, this is true. + */ + bool m_SelectingText; + + // *** Things for one-line input control *** // + float m_HorizontalScroll; + + /// Used to store the previous time for flashing the cursor. + double m_PrevTime; + + /// Cursor blink rate in seconds, if greater than 0.0. + double m_CursorBlinkRate; + + /// If the cursor should be drawn or not. + bool m_CursorVisState; + + // Settings + i32 m_BufferPosition; + float m_BufferZone; + CStrW m_Caption; + i32 m_CellID; + CStrW m_Font; + CStrW m_MaskChar; + bool m_Mask; + i32 m_MaxLength; + bool m_MultiLine; + bool m_Readonly; + bool m_ScrollBar; + CStr m_ScrollBarStyle; + CGUISpriteInstance m_Sprite; + CGUISpriteInstance m_SpriteSelectArea; + CGUIColor m_TextColor; + CGUIColor m_TextColorSelected; +}; + +#endif // INCLUDED_CINPUT Index: ps/trunk/source/gui/ObjectTypes/CInput.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CInput.cpp +++ ps/trunk/source/gui/ObjectTypes/CInput.cpp @@ -0,0 +1,2051 @@ +/* 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 "CInput.h" + +#include "graphics/FontMetrics.h" +#include "graphics/ShaderManager.h" +#include "graphics/TextRenderer.h" +#include "gui/CGUI.h" +#include "gui/CGUIScrollBarVertical.h" +#include "lib/sysdep/clipboard.h" +#include "lib/timer.h" +#include "lib/utf8.h" +#include "ps/ConfigDB.h" +#include "ps/GameSetup/Config.h" +#include "ps/Globals.h" +#include "ps/Hotkey.h" +#include "renderer/Renderer.h" + +#include + +extern int g_yres; + +CInput::CInput(CGUI& pGUI) + : + IGUIObject(pGUI), + IGUIScrollBarOwner(*static_cast(this)), + m_iBufferPos(-1), + m_iBufferPos_Tail(-1), + m_SelectingText(), + m_HorizontalScroll(), + m_PrevTime(), + m_CursorVisState(true), + m_CursorBlinkRate(0.5), + m_ComposingText(), + m_iComposedLength(), + m_iComposedPos(), + m_iInsertPos(), + m_BufferPosition(), + m_BufferZone(), + m_Caption(), + m_CellID(), + m_Font(), + m_MaskChar(), + m_Mask(), + m_MaxLength(), + m_MultiLine(), + m_Readonly(), + m_ScrollBar(), + m_ScrollBarStyle(), + m_Sprite(), + m_SpriteSelectArea(), + m_TextColor(), + m_TextColorSelected() +{ + RegisterSetting("buffer_position", m_BufferPosition); + RegisterSetting("buffer_zone", m_BufferZone); + RegisterSetting("caption", m_Caption); + RegisterSetting("cell_id", m_CellID); + RegisterSetting("font", m_Font); + RegisterSetting("mask_char", m_MaskChar); + RegisterSetting("mask", m_Mask); + RegisterSetting("max_length", m_MaxLength); + RegisterSetting("multiline", m_MultiLine); + RegisterSetting("readonly", m_Readonly); + RegisterSetting("scrollbar", m_ScrollBar); + RegisterSetting("scrollbar_style", m_ScrollBarStyle); + RegisterSetting("sprite", m_Sprite); + RegisterSetting("sprite_selectarea", m_SpriteSelectArea); + RegisterSetting("textcolor", m_TextColor); + RegisterSetting("textcolor_selected", m_TextColorSelected); + + CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate); + + CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); + bar->SetRightAligned(true); + AddScrollBar(bar); +} + +CInput::~CInput() +{ +} + +void CInput::UpdateBufferPositionSetting() +{ + SetSetting("buffer_position", m_iBufferPos, false); +} + +void CInput::ClearComposedText() +{ + m_Caption.erase(m_iInsertPos, m_iComposedLength); + m_iBufferPos = m_iInsertPos; + UpdateBufferPositionSetting(); + m_iComposedLength = 0; + m_iComposedPos = 0; +} + +InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) +{ + ENSURE(m_iBufferPos != -1); + + switch (ev->ev.type) + { + case SDL_HOTKEYDOWN: + { + if (m_ComposingText) + return IN_HANDLED; + + return ManuallyHandleHotkeyEvent(ev); + } + // SDL2 has a new method of text input that better supports Unicode and CJK + // see https://wiki.libsdl.org/Tutorials/TextInput + case SDL_TEXTINPUT: + { + if (m_Readonly) + return IN_PASS; + + // Text has been committed, either single key presses or through an IME + std::wstring text = wstring_from_utf8(ev->ev.text.text); + + m_WantedX = 0.0f; + + if (SelectingText()) + DeleteCurSelection(); + + if (m_ComposingText) + { + ClearComposedText(); + m_ComposingText = false; + } + + if (m_iBufferPos == static_cast(m_Caption.length())) + m_Caption.append(text); + else + m_Caption.insert(m_iBufferPos, text); + + UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); + + m_iBufferPos += text.length(); + UpdateBufferPositionSetting(); + m_iBufferPos_Tail = -1; + + UpdateAutoScroll(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + + return IN_HANDLED; + } + case SDL_TEXTEDITING: + { + if (m_Readonly) + return IN_PASS; + + // Text is being composed with an IME + // TODO: indicate this by e.g. underlining the uncommitted text + const char* rawText = ev->ev.edit.text; + int rawLength = strlen(rawText); + std::wstring wtext = wstring_from_utf8(rawText); + + debug_printf("SDL_TEXTEDITING: text=%s, start=%d, length=%d\n", rawText, ev->ev.edit.start, ev->ev.edit.length); + m_WantedX = 0.0f; + + if (SelectingText()) + DeleteCurSelection(); + + // Remember cursor position when text composition begins + if (!m_ComposingText) + m_iInsertPos = m_iBufferPos; + else + { + // Composed text is replaced each time + ClearComposedText(); + } + + m_ComposingText = ev->ev.edit.start != 0 || rawLength != 0; + if (m_ComposingText) + { + m_Caption.insert(m_iInsertPos, wtext); + + // The text buffer is limited to SDL_TEXTEDITINGEVENT_TEXT_SIZE bytes, yet start + // increases without limit, so don't let it advance beyond the composed text length + m_iComposedLength = wtext.length(); + m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength; + m_iBufferPos = m_iInsertPos + m_iComposedPos; + + // TODO: composed text selection - what does ev.edit.length do? + m_iBufferPos_Tail = -1; + } + + UpdateBufferPositionSetting(); + UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); + + UpdateAutoScroll(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + + return IN_HANDLED; + } + case SDL_KEYDOWN: + { + if (m_ComposingText) + return IN_HANDLED; + + // Since the GUI framework doesn't handle to set settings + // in Unicode (CStrW), we'll simply retrieve the actual + // pointer and edit that. + SDL_Keycode keyCode = ev->ev.key.keysym.sym; + + ManuallyImmutableHandleKeyDownEvent(keyCode); + ManuallyMutableHandleKeyDownEvent(keyCode); + + UpdateBufferPositionSetting(); + return IN_HANDLED; + } + default: + { + return IN_PASS; + } + } +} + +void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode) +{ + if (m_Readonly) + return; + + wchar_t cooked = 0; + + switch (keyCode) + { + case SDLK_TAB: + { + SendEvent(GUIM_TAB, "tab"); + // Don't send a textedit event, because it should only + // be sent if the GUI control changes the text + break; + } + case SDLK_BACKSPACE: + { + m_WantedX = 0.0f; + + if (SelectingText()) + DeleteCurSelection(); + else + { + m_iBufferPos_Tail = -1; + + if (m_Caption.empty() || m_iBufferPos == 0) + break; + + if (m_iBufferPos == static_cast(m_Caption.length())) + m_Caption = m_Caption.Left(static_cast(m_Caption.length()) - 1); + else + m_Caption = + m_Caption.Left(m_iBufferPos - 1) + + m_Caption.Right(static_cast(m_Caption.length()) - m_iBufferPos); + + --m_iBufferPos; + + UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); + } + + UpdateAutoScroll(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + break; + } + case SDLK_DELETE: + { + m_WantedX = 0.0f; + + if (SelectingText()) + DeleteCurSelection(); + else + { + if (m_Caption.empty() || m_iBufferPos == static_cast(m_Caption.length())) + break; + + m_Caption = + m_Caption.Left(m_iBufferPos) + + m_Caption.Right(static_cast(m_Caption.length()) - (m_iBufferPos + 1)); + + UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); + } + + UpdateAutoScroll(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + break; + } + case SDLK_KP_ENTER: + case SDLK_RETURN: + { + // 'Return' should do a Press event for single liners (e.g. submitting forms) + // otherwise a '\n' character will be added. + if (!m_MultiLine) + { + SendEvent(GUIM_PRESSED, "press"); + break; + } + + cooked = '\n'; // Change to '\n' and do default: + FALLTHROUGH; + } + default: // Insert a character + { + // In SDL2, we no longer get Unicode wchars via SDL_Keysym + // we use text input events instead and they provide UTF-8 chars + if (cooked == 0) + return; + + // check max length + if (m_MaxLength != 0 && static_cast(m_Caption.length()) >= m_MaxLength) + break; + + m_WantedX = 0.0f; + + if (SelectingText()) + DeleteCurSelection(); + m_iBufferPos_Tail = -1; + + if (m_iBufferPos == static_cast(m_Caption.length())) + m_Caption += cooked; + else + m_Caption = + m_Caption.Left(m_iBufferPos) + cooked + + m_Caption.Right(static_cast(m_Caption.length()) - m_iBufferPos); + + UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1); + + ++m_iBufferPos; + + UpdateAutoScroll(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + break; + } + } +} + +void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode) +{ + bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; + + switch (keyCode) + { + case SDLK_HOME: + { + // If there's not a selection, we should create one now + if (!shiftKeyPressed) + { + // Make sure a selection isn't created. + m_iBufferPos_Tail = -1; + } + else if (!SelectingText()) + { + // Place tail at the current point: + m_iBufferPos_Tail = m_iBufferPos; + } + + m_iBufferPos = 0; + m_WantedX = 0.0f; + + UpdateAutoScroll(); + break; + } + case SDLK_END: + { + // If there's not a selection, we should create one now + if (!shiftKeyPressed) + { + // Make sure a selection isn't created. + m_iBufferPos_Tail = -1; + } + else if (!SelectingText()) + { + // Place tail at the current point: + m_iBufferPos_Tail = m_iBufferPos; + } + + m_iBufferPos = static_cast(m_Caption.length()); + m_WantedX = 0.0f; + + UpdateAutoScroll(); + break; + } + /** + * Conventions for Left/Right when text is selected: + * + * References: + * + * Visual Studio + * Visual Studio has the 'newer' approach, used by newer versions of + * things, and in newer applications. A left press will always place + * the pointer on the left edge of the selection, and then of course + * remove the selection. Right will do the exact same thing. + * If you have the pointer on the right edge and press right, it will + * in other words just remove the selection. + * + * Windows (eg. Notepad) + * A left press always takes the pointer a step to the left and + * removes the selection as if it were never there in the first place. + * Right of course does the same thing but to the right. + * + * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN + * Messenger. + */ + case SDLK_LEFT: + { + m_WantedX = 0.f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; + + if (m_iBufferPos > 0) + --m_iBufferPos; + } + else + { + if (m_iBufferPos_Tail < m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateAutoScroll(); + break; + } + case SDLK_RIGHT: + { + m_WantedX = 0.0f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; + + if (m_iBufferPos < static_cast(m_Caption.length())) + ++m_iBufferPos; + } + else + { + if (m_iBufferPos_Tail > m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateAutoScroll(); + break; + } + /** + * Conventions for Up/Down when text is selected: + * + * References: + * + * Visual Studio + * Visual Studio has a very strange approach, down takes you below the + * selection to the next row, and up to the one prior to the whole + * selection. The weird part is that it is always aligned as the + * 'pointer'. I decided this is to much work for something that is + * a bit arbitrary + * + * Windows (eg. Notepad) + * Just like with left/right, the selection is destroyed and it moves + * just as if there never were a selection. + * + * I chose the Notepad convention even though I use the VS convention with + * left/right. + */ + case SDLK_UP: + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; + + std::list::iterator current = m_CharacterPositions.begin(); + while (current != m_CharacterPositions.end()) + { + if (m_iBufferPos >= current->m_ListStart && + m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) + break; + + ++current; + } + + float pos_x; + if (m_iBufferPos - current->m_ListStart == 0) + pos_x = 0.f; + else + pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; + + if (m_WantedX > pos_x) + pos_x = m_WantedX; + + // Now change row: + if (current != m_CharacterPositions.begin()) + { + --current; + + // Find X-position: + m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); + } + // else we can't move up + + UpdateAutoScroll(); + break; + } + case SDLK_DOWN: + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; + + std::list::iterator current = m_CharacterPositions.begin(); + while (current != m_CharacterPositions.end()) + { + if (m_iBufferPos >= current->m_ListStart && + m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) + break; + + ++current; + } + + float pos_x; + + if (m_iBufferPos - current->m_ListStart == 0) + pos_x = 0.f; + else + pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; + + if (m_WantedX > pos_x) + pos_x = m_WantedX; + + // Now change row: + // Add first, so we can check if it's .end() + ++current; + if (current != m_CharacterPositions.end()) + { + // Find X-position: + m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); + } + // else we can't move up + + UpdateAutoScroll(); + break; + } + case SDLK_PAGEUP: + { + GetScrollBar(0).ScrollMinusPlenty(); + UpdateAutoScroll(); + break; + } + case SDLK_PAGEDOWN: + { + GetScrollBar(0).ScrollPlusPlenty(); + UpdateAutoScroll(); + break; + } + default: + { + break; + } + } +} + +InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev) +{ + bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; + + std::string hotkey = static_cast(ev->ev.user.data1); + + if (hotkey == "paste") + { + if (m_Readonly) + return IN_PASS; + + m_WantedX = 0.0f; + + wchar_t* text = sys_clipboard_get(); + if (text) + { + if (SelectingText()) + DeleteCurSelection(); + + if (m_iBufferPos == static_cast(m_Caption.length())) + m_Caption += text; + else + m_Caption = + m_Caption.Left(m_iBufferPos) + text + + m_Caption.Right(static_cast(m_Caption.length()) - m_iBufferPos); + + UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); + + m_iBufferPos += (int)wcslen(text); + UpdateAutoScroll(); + UpdateBufferPositionSetting(); + + sys_clipboard_free(text); + + SendEvent(GUIM_TEXTEDIT, "textedit"); + } + + return IN_HANDLED; + } + else if (hotkey == "copy" || hotkey == "cut") + { + if (m_Readonly && hotkey == "cut") + return IN_PASS; + + m_WantedX = 0.0f; + + if (SelectingText()) + { + int virtualFrom; + int virtualTo; + + if (m_iBufferPos_Tail >= m_iBufferPos) + { + virtualFrom = m_iBufferPos; + virtualTo = m_iBufferPos_Tail; + } + else + { + virtualFrom = m_iBufferPos_Tail; + virtualTo = m_iBufferPos; + } + + CStrW text = m_Caption.Left(virtualTo).Right(virtualTo - virtualFrom); + + sys_clipboard_set(&text[0]); + + if (hotkey == "cut") + { + DeleteCurSelection(); + UpdateAutoScroll(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + } + } + + return IN_HANDLED; + } + else if (hotkey == "text.delete.left") + { + if (m_Readonly) + return IN_PASS; + + m_WantedX = 0.0f; + + if (SelectingText()) + DeleteCurSelection(); + + if (!m_Caption.empty() && m_iBufferPos != 0) + { + m_iBufferPos_Tail = m_iBufferPos; + CStrW searchString = m_Caption.Left(m_iBufferPos); + + // If we are starting in whitespace, adjust position until we get a non whitespace + while (m_iBufferPos > 0) + { + if (!iswspace(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + + // If we end up on a punctuation char we just delete it (treat punct like a word) + if (iswpunct(searchString[m_iBufferPos - 1])) + m_iBufferPos--; + else + { + // Now we are on a non white space character, adjust position to char after next whitespace char is found + while (m_iBufferPos > 0) + { + if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + } + + UpdateBufferPositionSetting(); + DeleteCurSelection(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + } + UpdateAutoScroll(); + return IN_HANDLED; + } + else if (hotkey == "text.delete.right") + { + if (m_Readonly) + return IN_PASS; + + m_WantedX = 0.0f; + + if (SelectingText()) + DeleteCurSelection(); + + if (!m_Caption.empty() && m_iBufferPos < static_cast(m_Caption.length())) + { + // Delete the word to the right of the cursor + m_iBufferPos_Tail = m_iBufferPos; + + // Delete chars to the right unit we hit whitespace + while (++m_iBufferPos < static_cast(m_Caption.length())) + { + if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos])) + break; + } + + // Eliminate any whitespace behind the word we just deleted + while (m_iBufferPos < static_cast(m_Caption.length())) + { + if (!iswspace(m_Caption[m_iBufferPos])) + break; + + ++m_iBufferPos; + } + UpdateBufferPositionSetting(); + DeleteCurSelection(); + } + UpdateAutoScroll(); + SendEvent(GUIM_TEXTEDIT, "textedit"); + return IN_HANDLED; + } + else if (hotkey == "text.move.left") + { + m_WantedX = 0.0f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; + + if (!m_Caption.empty() && m_iBufferPos != 0) + { + CStrW searchString = m_Caption.Left(m_iBufferPos); + + // If we are starting in whitespace, adjust position until we get a non whitespace + while (m_iBufferPos > 0) + { + if (!iswspace(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + + // If we end up on a puctuation char we just select it (treat punct like a word) + if (iswpunct(searchString[m_iBufferPos - 1])) + m_iBufferPos--; + else + { + // Now we are on a non white space character, adjust position to char after next whitespace char is found + while (m_iBufferPos > 0) + { + if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + } + } + } + else + { + if (m_iBufferPos_Tail < m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateBufferPositionSetting(); + UpdateAutoScroll(); + + return IN_HANDLED; + } + else if (hotkey == "text.move.right") + { + m_WantedX = 0.0f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; + + if (!m_Caption.empty() && m_iBufferPos < static_cast(m_Caption.length())) + { + // Select chars to the right until we hit whitespace + while (++m_iBufferPos < static_cast(m_Caption.length())) + { + if (iswspace(m_Caption[m_iBufferPos]) || iswpunct(m_Caption[m_iBufferPos])) + break; + } + + // Also select any whitespace following the word we just selected + while (m_iBufferPos < static_cast(m_Caption.length())) + { + if (!iswspace(m_Caption[m_iBufferPos])) + break; + + ++m_iBufferPos; + } + } + } + else + { + if (m_iBufferPos_Tail > m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateBufferPositionSetting(); + UpdateAutoScroll(); + + return IN_HANDLED; + } + + return IN_PASS; +} + +void CInput::ResetStates() +{ + IGUIObject::ResetStates(); + IGUIScrollBarOwner::ResetStates(); +} + +void CInput::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + IGUIScrollBarOwner::HandleMessage(Message); + + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + { + // Update scroll-bar + // TODO Gee: (2004-09-01) Is this really updated each time it should? + if (m_ScrollBar && + (Message.value == "size" || + Message.value == "z" || + Message.value == "absolute")) + { + GetScrollBar(0).SetX(m_CachedActualSize.right); + GetScrollBar(0).SetY(m_CachedActualSize.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); + } + + // Update scrollbar + if (Message.value == "scrollbar_style") + GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); + + if (Message.value == "buffer_position") + { + m_iBufferPos = m_BufferPosition; + m_iBufferPos_Tail = -1; // position change resets selection + } + + if (Message.value == "size" || + Message.value == "z" || + Message.value == "font" || + Message.value == "absolute" || + Message.value == "caption" || + Message.value == "scrollbar" || + Message.value == "scrollbar_style") + { + UpdateText(); + } + + if (Message.value == "multiline") + { + if (!m_MultiLine) + GetScrollBar(0).SetLength(0.f); + else + GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); + + UpdateText(); + } + + UpdateAutoScroll(); + + break; + } + case GUIM_MOUSE_PRESS_LEFT: + { + // Check if we're selecting the scrollbar + if (m_ScrollBar && + m_MultiLine && + GetScrollBar(0).GetStyle()) + { + if (m_pGUI.GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width) + break; + } + + if (m_ComposingText) + break; + + // Okay, this section is about pressing the mouse and + // choosing where the point should be placed. For + // instance, if we press between a and b, the point + // should of course be placed accordingly. Other + // special cases are handled like the input box norms. + if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]) + m_iBufferPos = GetMouseHoveringTextPosition(); + else + m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); + + m_SelectingText = true; + + UpdateAutoScroll(); + + // If we immediately release the button it will just be seen as a click + // for the user though. + break; + } + case GUIM_MOUSE_DBLCLICK_LEFT: + { + if (m_ComposingText) + break; + + if (m_Caption.empty()) + break; + + m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); + + if (m_iBufferPos >= (int)m_Caption.length()) + m_iBufferPos = m_iBufferPos_Tail = m_Caption.length() - 1; + + // See if we are clicking over whitespace + if (iswspace(m_Caption[m_iBufferPos])) + { + // see if we are in a section of whitespace greater than one character + if ((m_iBufferPos + 1 < (int) m_Caption.length() && iswspace(m_Caption[m_iBufferPos + 1])) || + (m_iBufferPos - 1 > 0 && iswspace(m_Caption[m_iBufferPos - 1]))) + { + // + // We are clicking in an area with more than one whitespace character + // so we select both the word to the left and then the word to the right + // + // [1] First the left + // skip the whitespace + while (m_iBufferPos > 0) + { + if (!iswspace(m_Caption[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + // now go until we hit white space or punctuation + while (m_iBufferPos > 0) + { + if (iswspace(m_Caption[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + + if (iswpunct(m_Caption[m_iBufferPos])) + break; + } + + // [2] Then the right + // go right until we are not in whitespace + while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) + { + if (!iswspace(m_Caption[m_iBufferPos_Tail])) + break; + } + + if (m_iBufferPos_Tail == static_cast(m_Caption.length())) + break; + + // now go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) + { + if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail])) + break; + } + } + else + { + // single whitespace so select word to the right + while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) + { + if (!iswspace(m_Caption[m_iBufferPos_Tail])) + break; + } + + if (m_iBufferPos_Tail == static_cast(m_Caption.length())) + break; + + // Don't include the leading whitespace + m_iBufferPos = m_iBufferPos_Tail; + + // now go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) + { + if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail])) + break; + } + } + } + else + { + // clicked on non-whitespace so select current word + // go until we hit white space or punctuation + while (m_iBufferPos > 0) + { + if (iswspace(m_Caption[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + + if (iswpunct(m_Caption[m_iBufferPos])) + break; + } + // go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < static_cast(m_Caption.length())) + if (iswspace(m_Caption[m_iBufferPos_Tail]) || iswpunct(m_Caption[m_iBufferPos_Tail])) + break; + } + UpdateAutoScroll(); + break; + } + case GUIM_MOUSE_RELEASE_LEFT: + { + if (m_SelectingText) + m_SelectingText = false; + break; + } + case GUIM_MOUSE_MOTION: + { + // If we just pressed down and started to move before releasing + // this is one way of selecting larger portions of text. + if (m_SelectingText) + { + // Actually, first we need to re-check that the mouse button is + // really pressed (it can be released while outside the control. + if (!g_mouse_buttons[SDL_BUTTON_LEFT]) + m_SelectingText = false; + else + m_iBufferPos = GetMouseHoveringTextPosition(); + UpdateAutoScroll(); + } + break; + } + case GUIM_LOAD: + { + GetScrollBar(0).SetX(m_CachedActualSize.right); + GetScrollBar(0).SetY(m_CachedActualSize.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); + GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); + + UpdateText(); + UpdateAutoScroll(); + + break; + } + case GUIM_GOT_FOCUS: + { + m_iBufferPos = 0; + m_PrevTime = 0.0; + m_CursorVisState = false; + + // Tell the IME where to draw the candidate list + SDL_Rect rect; + rect.h = m_CachedActualSize.GetSize().cy; + rect.w = m_CachedActualSize.GetSize().cx; + rect.x = m_CachedActualSize.TopLeft().x; + rect.y = m_CachedActualSize.TopLeft().y; + SDL_SetTextInputRect(&rect); + SDL_StartTextInput(); + break; + } + case GUIM_LOST_FOCUS: + { + if (m_ComposingText) + { + // Simulate a final text editing event to clear the composition + SDL_Event_ evt; + evt.ev.type = SDL_TEXTEDITING; + evt.ev.edit.length = 0; + evt.ev.edit.start = 0; + evt.ev.edit.text[0] = 0; + ManuallyHandleEvent(&evt); + } + SDL_StopTextInput(); + + m_iBufferPos = -1; + m_iBufferPos_Tail = -1; + break; + } + default: + { + break; + } + } + UpdateBufferPositionSetting(); +} + +void CInput::UpdateCachedSize() +{ + // If an ancestor's size changed, this will let us intercept the change and + // update our scrollbar positions + + IGUIObject::UpdateCachedSize(); + + if (m_ScrollBar) + { + GetScrollBar(0).SetX(m_CachedActualSize.right); + GetScrollBar(0).SetY(m_CachedActualSize.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); + } +} + +void CInput::Draw() +{ + float bz = GetBufferedZ(); + + if (m_CursorBlinkRate > 0.0) + { + // check if the cursor visibility state needs to be changed + double currTime = timer_Time(); + if (currTime - m_PrevTime >= m_CursorBlinkRate) + { + m_CursorVisState = !m_CursorVisState; + m_PrevTime = currTime; + } + } + else + // should always be visible + m_CursorVisState = true; + + // First call draw on ScrollBarOwner + if (m_ScrollBar && m_MultiLine) + IGUIScrollBarOwner::Draw(); + + CStrIntern font_name(m_Font.ToUTF8()); + + wchar_t mask_char = L'*'; + if (m_Mask && m_MaskChar.length() > 0) + mask_char = m_MaskChar[0]; + + m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, m_CachedActualSize); + + float scroll = 0.f; + if (m_ScrollBar && m_MultiLine) + scroll = GetScrollBar(0).GetPos(); + + CFontMetrics font(font_name); + + // We'll have to setup clipping manually, since we're doing the rendering manually. + CRect cliparea(m_CachedActualSize); + + // First we'll figure out the clipping area, which is the cached actual size + // substracted by an optional scrollbar + if (m_ScrollBar) + { + scroll = GetScrollBar(0).GetPos(); + + // substract scrollbar from cliparea + if (cliparea.right > GetScrollBar(0).GetOuterRect().left && + cliparea.right <= GetScrollBar(0).GetOuterRect().right) + cliparea.right = GetScrollBar(0).GetOuterRect().left; + + if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && + cliparea.left < GetScrollBar(0).GetOuterRect().right) + cliparea.left = GetScrollBar(0).GetOuterRect().right; + } + + if (cliparea != CRect()) + { + glEnable(GL_SCISSOR_TEST); + glScissor( + cliparea.left * g_GuiScale, + g_yres - cliparea.bottom * g_GuiScale, + cliparea.GetWidth() * g_GuiScale, + cliparea.GetHeight() * g_GuiScale); + } + + // These are useful later. + int VirtualFrom, VirtualTo; + + if (m_iBufferPos_Tail >= m_iBufferPos) + { + VirtualFrom = m_iBufferPos; + VirtualTo = m_iBufferPos_Tail; + } + else + { + VirtualFrom = m_iBufferPos_Tail; + VirtualTo = m_iBufferPos; + } + + // Get the height of this font. + float h = (float)font.GetHeight(); + float ls = (float)font.GetLineSpacing(); + + CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); + + CTextRenderer textRenderer(tech->GetShader()); + textRenderer.Font(font_name); + + // Set the Z to somewhat more, so we can draw a selected area between the + // the control and the text. + textRenderer.Translate( + (float)(int)(m_CachedActualSize.left) + m_BufferZone, + (float)(int)(m_CachedActualSize.top+h) + m_BufferZone, + bz+0.1f); + + // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE + // (sort of like a | which is aligned to the left of most characters) + + float buffered_y = -scroll + m_BufferZone; + + // When selecting larger areas, we need to draw a rectangle box + // around it, and this is to keep track of where the box + // started, because we need to follow the iteration until we + // reach the end, before we can actually draw it. + bool drawing_box = false; + float box_x = 0.f; + + float x_pointer = 0.f; + + // If we have a selecting box (i.e. when you have selected letters, not just when + // the pointer is between two letters) we need to process all letters once + // before we do it the second time and render all the text. We can't do it + // in the same loop because text will have been drawn, so it will disappear when + // drawn behind the text that has already been drawn. Confusing, well it's necessary + // (I think). + + if (SelectingText()) + { + // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos, + // just like you can select from right to left, as you can + // left to right. Is there a difference? Yes, the pointer + // be placed accordingly, so that if you select shift and + // expand this selection, it will expand on appropriate side. + // Anyway, since the drawing procedure needs "To" to be + // greater than from, we need virtual values that might switch + // place. + + int VirtualFrom, VirtualTo; + + if (m_iBufferPos_Tail >= m_iBufferPos) + { + VirtualFrom = m_iBufferPos; + VirtualTo = m_iBufferPos_Tail; + } + else + { + VirtualFrom = m_iBufferPos_Tail; + VirtualTo = m_iBufferPos; + } + + + bool done = false; + for (std::list::const_iterator it = m_CharacterPositions.begin(); + it != m_CharacterPositions.end(); + ++it, buffered_y += ls, x_pointer = 0.f) + { + if (m_MultiLine && buffered_y > m_CachedActualSize.GetHeight()) + break; + + // We might as well use 'i' here to iterate, because we need it + // (often compared against ints, so don't make it size_t) + for (int i = 0; i < (int)it->m_ListOfX.size()+2; ++i) + { + if (it->m_ListStart + i == VirtualFrom) + { + // we won't actually draw it now, because we don't + // know the width of each glyph to that position. + // we need to go along with the iteration, and + // make a mark where the box started: + drawing_box = true; // will turn false when finally rendered. + + // Get current x position + box_x = x_pointer; + } + + const bool at_end = (i == (int)it->m_ListOfX.size()+1); + + if (drawing_box && (it->m_ListStart + i == VirtualTo || at_end)) + { + // Depending on if it's just a row change, or if it's + // the end of the select box, do slightly different things. + if (at_end) + { + if (it->m_ListStart + i != VirtualFrom) + // and actually add a white space! yes, this is done in any common input + x_pointer += font.GetCharacterWidth(L' '); + } + else + { + drawing_box = false; + done = true; + } + + CRect rect; + // Set 'rect' depending on if it's a multiline control, or a one-line control + if (m_MultiLine) + { + rect = CRect( + m_CachedActualSize.left + box_x + m_BufferZone, + m_CachedActualSize.top + buffered_y + (h - ls) / 2, + m_CachedActualSize.left + x_pointer + m_BufferZone, + m_CachedActualSize.top + buffered_y + (h + ls) / 2); + + if (rect.bottom < m_CachedActualSize.top) + continue; + + if (rect.top < m_CachedActualSize.top) + rect.top = m_CachedActualSize.top; + + if (rect.bottom > m_CachedActualSize.bottom) + rect.bottom = m_CachedActualSize.bottom; + } + else // if one-line + { + rect = CRect( + m_CachedActualSize.left + box_x + m_BufferZone - m_HorizontalScroll, + m_CachedActualSize.top + buffered_y + (h - ls) / 2, + m_CachedActualSize.left + x_pointer + m_BufferZone - m_HorizontalScroll, + m_CachedActualSize.top + buffered_y + (h + ls) / 2); + + if (rect.left < m_CachedActualSize.left) + rect.left = m_CachedActualSize.left; + + if (rect.right > m_CachedActualSize.right) + rect.right = m_CachedActualSize.right; + } + + m_pGUI.DrawSprite(m_SpriteSelectArea, m_CellID, bz + 0.05f, rect); + } + + if (i < (int)it->m_ListOfX.size()) + { + if (!m_Mask) + x_pointer += font.GetCharacterWidth(m_Caption[it->m_ListStart + i]); + else + x_pointer += font.GetCharacterWidth(mask_char); + } + } + + if (done) + break; + + // If we're about to draw a box, and all of a sudden changes + // line, we need to draw that line's box, and then reset + // the box drawing to the beginning of the new line. + if (drawing_box) + box_x = 0.f; + } + } + + // Reset some from previous run + buffered_y = -scroll; + + // Setup initial color (then it might change and change back, when drawing selected area) + textRenderer.Color(m_TextColor); + + tech->BeginPass(); + + bool using_selected_color = false; + + for (std::list::const_iterator it = m_CharacterPositions.begin(); + it != m_CharacterPositions.end(); + ++it, buffered_y += ls) + { + if (buffered_y + m_BufferZone >= -ls || !m_MultiLine) + { + if (m_MultiLine && buffered_y + m_BufferZone > m_CachedActualSize.GetHeight()) + break; + + CMatrix3D savedTransform = textRenderer.GetTransform(); + + // Text must always be drawn in integer values. So we have to convert scroll + if (m_MultiLine) + textRenderer.Translate(0.f, -(float)(int)scroll, 0.f); + else + textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f, 0.f); + + // We might as well use 'i' here, because we need it + // (often compared against ints, so don't make it size_t) + for (int i = 0; i < (int)it->m_ListOfX.size()+1; ++i) + { + if (!m_MultiLine && i < (int)it->m_ListOfX.size()) + { + if (it->m_ListOfX[i] - m_HorizontalScroll < -m_BufferZone) + { + // We still need to translate the OpenGL matrix + if (i == 0) + textRenderer.Translate(it->m_ListOfX[i], 0.f, 0.f); + else + textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f); + + continue; + } + } + + // End of selected area, change back color + if (SelectingText() && it->m_ListStart + i == VirtualTo) + { + using_selected_color = false; + textRenderer.Color(m_TextColor); + } + + // selecting only one, then we need only to draw a cursor. + if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos && m_CursorVisState) + textRenderer.Put(0.0f, 0.0f, L"_"); + + // Drawing selected area + if (SelectingText() && + it->m_ListStart + i >= VirtualFrom && + it->m_ListStart + i < VirtualTo && + !using_selected_color) + { + using_selected_color = true; + textRenderer.Color(m_TextColorSelected); + } + + if (i != (int)it->m_ListOfX.size()) + { + if (!m_Mask) + textRenderer.PrintfAdvance(L"%lc", m_Caption[it->m_ListStart + i]); + else + textRenderer.PrintfAdvance(L"%lc", mask_char); + } + + // check it's now outside a one-liner, then we'll break + if (!m_MultiLine && i < (int)it->m_ListOfX.size() && + it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth() - m_BufferZone) + break; + } + + if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos) + { + textRenderer.Color(m_TextColor); + if (m_CursorVisState) + textRenderer.PutAdvance(L"_"); + + if (using_selected_color) + textRenderer.Color(m_TextColorSelected); + } + + textRenderer.SetTransform(savedTransform); + } + + textRenderer.Translate(0.f, ls, 0.f); + } + + textRenderer.Render(); + + if (cliparea != CRect()) + glDisable(GL_SCISSOR_TEST); + + tech->EndPass(); +} + +void CInput::UpdateText(int from, int to_before, int to_after) +{ + CStrIntern font_name(m_Font.ToUTF8()); + + wchar_t mask_char = L'*'; + if (m_Mask && m_MaskChar.length() > 0) + mask_char = m_MaskChar[0]; + + // Ensure positions are valid after caption changes + m_iBufferPos = std::min(m_iBufferPos, static_cast(m_Caption.size())); + m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, static_cast(m_Caption.size())); + UpdateBufferPositionSetting(); + + if (font_name.empty()) + { + // Destroy everything stored, there's no font, so there can be no data. + m_CharacterPositions.clear(); + return; + } + + SRow row; + row.m_ListStart = 0; + + int to = 0; // make sure it's initialized + + if (to_before == -1) + to = static_cast(m_Caption.length()); + + CFontMetrics font(font_name); + + std::list::iterator current_line; + + // Used to ... TODO + int check_point_row_start = -1; + int check_point_row_end = -1; + + // Reset + if (from == 0 && to_before == -1) + { + m_CharacterPositions.clear(); + current_line = m_CharacterPositions.begin(); + } + else + { + ENSURE(to_before != -1); + + std::list::iterator destroy_row_from; + std::list::iterator destroy_row_to; + // Used to check if the above has been set to anything, + // previously a comparison like: + // destroy_row_from == std::list::iterator() + // ... was used, but it didn't work with GCC. + bool destroy_row_from_used = false; + bool destroy_row_to_used = false; + + // Iterate, and remove everything between 'from' and 'to_before' + // actually remove the entire lines they are on, it'll all have + // to be redone. And when going along, we'll delete a row at a time + // when continuing to see how much more after 'to' we need to remake. + int i = 0; + for (std::list::iterator it = m_CharacterPositions.begin(); + it != m_CharacterPositions.end(); + ++it, ++i) + { + if (!destroy_row_from_used && it->m_ListStart > from) + { + // Destroy the previous line, and all to 'to_before' + destroy_row_from = it; + --destroy_row_from; + + destroy_row_from_used = true; + + // For the rare case that we might remove characters to a word + // so that it suddenly fits on the previous row, + // we need to by standards re-do the whole previous line too + // (if one exists) + if (destroy_row_from != m_CharacterPositions.begin()) + --destroy_row_from; + } + + if (!destroy_row_to_used && it->m_ListStart > to_before) + { + destroy_row_to = it; + destroy_row_to_used = true; + + // If it isn't the last row, we'll add another row to delete, + // just so we can see if the last restorted line is + // identical to what it was before. If it isn't, then we'll + // have to continue. + // 'check_point_row_start' is where we store how the that + // line looked. + if (destroy_row_to != m_CharacterPositions.end()) + { + check_point_row_start = destroy_row_to->m_ListStart; + check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); + if (destroy_row_to->m_ListOfX.empty()) + ++check_point_row_end; + } + + ++destroy_row_to; + break; + } + } + + if (!destroy_row_from_used) + { + destroy_row_from = m_CharacterPositions.end(); + --destroy_row_from; + + // As usual, let's destroy another row back + if (destroy_row_from != m_CharacterPositions.begin()) + --destroy_row_from; + + current_line = destroy_row_from; + } + + if (!destroy_row_to_used) + { + destroy_row_to = m_CharacterPositions.end(); + check_point_row_start = -1; + } + + // set 'from' to the row we'll destroy from + // and 'to' to the row we'll destroy to + from = destroy_row_from->m_ListStart; + + if (destroy_row_to != m_CharacterPositions.end()) + to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to. + else + to = static_cast(m_Caption.length()); + + + // Setup the first row + row.m_ListStart = destroy_row_from->m_ListStart; + + std::list::iterator temp_it = destroy_row_to; + --temp_it; + + current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to); + + // If there has been a change in number of characters + // we need to change all m_ListStart that comes after + // the interval we just destroyed. We'll change all + // values with the delta change of the string length. + int delta = to_after - to_before; + if (delta != 0) + { + for (std::list::iterator it = current_line; + it != m_CharacterPositions.end(); + ++it) + it->m_ListStart += delta; + + // Update our check point too! + check_point_row_start += delta; + check_point_row_end += delta; + + if (to != static_cast(m_Caption.length())) + to += delta; + } + } + + int last_word_started = from; + float x_pos = 0.f; + + //if (to_before != -1) + // return; + + for (int i = from; i < to; ++i) + { + if (m_Caption[i] == L'\n' && m_MultiLine) + { + if (i == to-1 && to != static_cast(m_Caption.length())) + break; // it will be added outside + + current_line = m_CharacterPositions.insert(current_line, row); + ++current_line; + + // Setup the next row: + row.m_ListOfX.clear(); + row.m_ListStart = i+1; + x_pos = 0.f; + } + else + { + if (m_Caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix. + m_Caption[i] == L'-'*/) + last_word_started = i+1; + + if (!m_Mask) + x_pos += font.GetCharacterWidth(m_Caption[i]); + else + x_pos += font.GetCharacterWidth(mask_char); + + if (x_pos >= GetTextAreaWidth() && m_MultiLine) + { + // The following decides whether it will word-wrap a word, + // or if it's only one word on the line, where it has to + // break the word apart. + if (last_word_started == row.m_ListStart) + { + last_word_started = i; + row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started)); + //row.m_ListOfX.push_back(x_pos); + //continue; + } + else + { + // regular word-wrap + row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1)); + } + + // Now, create a new line: + // notice: when we enter a newline, you can stand with the cursor + // both before and after that character, being on different + // rows. With automatic word-wrapping, that is not possible. Which + // is intuitively correct. + + current_line = m_CharacterPositions.insert(current_line, row); + ++current_line; + + // Setup the next row: + row.m_ListOfX.clear(); + row.m_ListStart = last_word_started; + + i = last_word_started-1; + + x_pos = 0.f; + } + else + // Get width of this character: + row.m_ListOfX.push_back(x_pos); + } + + // Check if it's the last iteration, and we're not revising the whole string + // because in that case, more word-wrapping might be needed. + // also check if the current line isn't the end + if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end()) + { + // check all rows and see if any existing + if (row.m_ListStart != check_point_row_start) + { + std::list::iterator destroy_row_from; + std::list::iterator destroy_row_to; + // Are used to check if the above has been set to anything, + // previously a comparison like: + // destroy_row_from == std::list::iterator() + // was used, but it didn't work with GCC. + bool destroy_row_from_used = false; + bool destroy_row_to_used = false; + + // Iterate, and remove everything between 'from' and 'to_before' + // actually remove the entire lines they are on, it'll all have + // to be redone. And when going along, we'll delete a row at a time + // when continuing to see how much more after 'to' we need to remake. + int i = 0; + for (std::list::iterator it = m_CharacterPositions.begin(); + it != m_CharacterPositions.end(); + ++it, ++i) + { + if (!destroy_row_from_used && it->m_ListStart > check_point_row_start) + { + // Destroy the previous line, and all to 'to_before' + //if (i >= 2) + // destroy_row_from = it-2; + //else + // destroy_row_from = it-1; + destroy_row_from = it; + destroy_row_from_used = true; + //--destroy_row_from; + } + + if (!destroy_row_to_used && it->m_ListStart > check_point_row_end) + { + destroy_row_to = it; + destroy_row_to_used = true; + + // If it isn't the last row, we'll add another row to delete, + // just so we can see if the last restorted line is + // identical to what it was before. If it isn't, then we'll + // have to continue. + // 'check_point_row_start' is where we store how the that + // line looked. + if (destroy_row_to != m_CharacterPositions.end()) + { + check_point_row_start = destroy_row_to->m_ListStart; + check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); + if (destroy_row_to->m_ListOfX.empty()) + ++check_point_row_end; + } + else + check_point_row_start = check_point_row_end = -1; + + ++destroy_row_to; + break; + } + } + + if (!destroy_row_from_used) + { + destroy_row_from = m_CharacterPositions.end(); + --destroy_row_from; + + current_line = destroy_row_from; + } + + if (!destroy_row_to_used) + { + destroy_row_to = m_CharacterPositions.end(); + check_point_row_start = check_point_row_end = -1; + } + + if (destroy_row_to != m_CharacterPositions.end()) + to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to. + else + to = static_cast(m_Caption.length()); + + + // Set current line, new rows will be added before current_line, so + // we'll choose the destroy_row_to, because it won't be deleted + // in the coming erase. + current_line = destroy_row_to; + + m_CharacterPositions.erase(destroy_row_from, destroy_row_to); + } + // else, the for loop will end naturally. + } + } + // This is kind of special, when we renew a some lines, then the last + // one will sometimes end with a space (' '), that really should + // be omitted when word-wrapping. So we'll check if the last row + // we'll add has got the same value as the next row. + if (current_line != m_CharacterPositions.end()) + { + if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart) + row.m_ListOfX.resize(row.m_ListOfX.size()-1); + } + + // add the final row (even if empty) + m_CharacterPositions.insert(current_line, row); + + if (m_ScrollBar) + { + GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + m_BufferZone * 2.f); + GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); + } +} + +int CInput::GetMouseHoveringTextPosition() const +{ + if (m_CharacterPositions.empty()) + return 0; + + // Return position + int retPosition; + + std::list::const_iterator current = m_CharacterPositions.begin(); + + CPos mouse = m_pGUI.GetMousePos(); + + if (m_MultiLine) + { + float scroll = 0.f; + if (m_ScrollBar) + scroll = GetScrollBarPos(0); + + // Now get the height of the font. + // TODO: Get the real font + CFontMetrics font(CStrIntern(m_Font.ToUTF8())); + float spacing = (float)font.GetLineSpacing(); + + // Change mouse position relative to text. + mouse -= m_CachedActualSize.TopLeft(); + mouse.x -= m_BufferZone; + mouse.y += scroll - m_BufferZone; + + int row = (int)((mouse.y) / spacing); + + if (row < 0) + row = 0; + + if (row > (int)m_CharacterPositions.size()-1) + row = (int)m_CharacterPositions.size()-1; + + // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to + // be able to get the specific element here. This is hopefully a temporary hack. + + for (int i = 0; i < row; ++i) + ++current; + } + else + { + // current is already set to begin, + // but we'll change the mouse.x to fit our horizontal scrolling + mouse -= m_CachedActualSize.TopLeft(); + mouse.x -= m_BufferZone - m_HorizontalScroll; + // mouse.y is moot + } + + retPosition = current->m_ListStart; + + // Okay, now loop through the glyphs to find the appropriate X position + float dummy; + retPosition += GetXTextPosition(current, mouse.x, dummy); + + return retPosition; +} + +// Does not process horizontal scrolling, 'x' must be modified before inputted. +int CInput::GetXTextPosition(const std::list::const_iterator& current, const float& x, float& wanted) const +{ + int ret = 0; + float previous = 0.f; + int i = 0; + + for (std::vector::const_iterator it = current->m_ListOfX.begin(); + it != current->m_ListOfX.end(); + ++it, ++i) + { + if (*it >= x) + { + if (x - previous >= *it - x) + ret += i+1; + else + ret += i; + + break; + } + previous = *it; + } + // If a position wasn't found, we will assume the last + // character of that line. + if (i == (int)current->m_ListOfX.size()) + { + ret += i; + wanted = x; + } + else + wanted = 0.f; + + return ret; +} + +void CInput::DeleteCurSelection() +{ + int virtualFrom; + int virtualTo; + + if (m_iBufferPos_Tail >= m_iBufferPos) + { + virtualFrom = m_iBufferPos; + virtualTo = m_iBufferPos_Tail; + } + else + { + virtualFrom = m_iBufferPos_Tail; + virtualTo = m_iBufferPos; + } + + m_Caption = + m_Caption.Left(virtualFrom) + + m_Caption.Right(static_cast(m_Caption.length()) - virtualTo); + + UpdateText(virtualFrom, virtualTo, virtualFrom); + + // Remove selection + m_iBufferPos_Tail = -1; + m_iBufferPos = virtualFrom; + UpdateBufferPositionSetting(); +} + +bool CInput::SelectingText() const +{ + return m_iBufferPos_Tail != -1 && + m_iBufferPos_Tail != m_iBufferPos; +} + +float CInput::GetTextAreaWidth() +{ + if (m_ScrollBar && GetScrollBar(0).GetStyle()) + return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f - GetScrollBar(0).GetStyle()->m_Width; + + return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f; +} + +void CInput::UpdateAutoScroll() +{ + // Autoscrolling up and down + if (m_MultiLine) + { + if (!m_ScrollBar) + return; + + const float scroll = GetScrollBar(0).GetPos(); + + // Now get the height of the font. + // TODO: Get the real font + CFontMetrics font(CStrIntern(m_Font.ToUTF8())); + float spacing = (float)font.GetLineSpacing(); + //float height = font.GetHeight(); + + // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to + // be able to get the specific element here. This is hopefully a temporary hack. + + std::list::iterator current = m_CharacterPositions.begin(); + int row = 0; + while (current != m_CharacterPositions.end()) + { + if (m_iBufferPos >= current->m_ListStart && + m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) + break; + + ++current; + ++row; + } + + // If scrolling down + if (-scroll + static_cast(row + 1) * spacing + m_BufferZone * 2.f > m_CachedActualSize.GetHeight()) + { + // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge. + GetScrollBar(0).SetPos(static_cast(row + 1) * spacing - m_CachedActualSize.GetHeight() + m_BufferZone * 2.f); + } + // If scrolling up + else if (-scroll + (float)row * spacing < 0.f) + { + // Scroll so the selected row is shown completely, also with m_BufferZone length to the edge. + GetScrollBar(0).SetPos((float)row * spacing); + } + } + else // autoscrolling left and right + { + // Get X position of position: + if (m_CharacterPositions.empty()) + return; + + float x_position = 0.f; + float x_total = 0.f; + if (!m_CharacterPositions.begin()->m_ListOfX.empty()) + { + + // Get position of m_iBufferPos + if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos && + m_iBufferPos > 0) + x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1]; + + // Get complete length: + x_total = m_CharacterPositions.begin()->m_ListOfX[m_CharacterPositions.begin()->m_ListOfX.size()-1]; + } + + // Check if outside to the right + if (x_position - m_HorizontalScroll + m_BufferZone * 2.f > m_CachedActualSize.GetWidth()) + m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f; + + // Check if outside to the left + if (x_position - m_HorizontalScroll < 0.f) + m_HorizontalScroll = x_position; + + // Check if the text doesn't even fill up to the right edge even though scrolling is done. + if (m_HorizontalScroll != 0.f && + x_total - m_HorizontalScroll + m_BufferZone * 2.f < m_CachedActualSize.GetWidth()) + m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f; + + // Now this is the fail-safe, if x_total isn't even the length of the control, + // remove all scrolling + if (x_total + m_BufferZone * 2.f < m_CachedActualSize.GetWidth()) + m_HorizontalScroll = 0.f; + } +} Index: ps/trunk/source/gui/ObjectTypes/CList.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CList.h +++ ps/trunk/source/gui/ObjectTypes/CList.h @@ -0,0 +1,151 @@ +/* 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_CLIST +#define INCLUDED_CLIST + +#include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIScrollBarOwner.h" +#include "gui/ObjectBases/IGUITextOwner.h" +#include "gui/SettingTypes/CGUIList.h" + +#include + +/** + * Create a list of elements, where one can be selected + * by the user. The control will use a pre-processed + * text-object for each element, which will be managed + * by the IGUITextOwner structure. + * + * A scroll-bar will appear when needed. This will be + * achieved with the IGUIScrollBarOwner structure. + */ +class CList : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner +{ + GUI_OBJECT(CList) + +public: + CList(CGUI& pGUI); + virtual ~CList(); + + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates(); + + /** + * @see IGUIObject#UpdateCachedSize() + */ + virtual void UpdateCachedSize(); + + /** + * Adds an item last to the list. + */ + virtual void AddItem(const CStrW& str, const CStrW& data); + +protected: + + /** + * Sets up text, should be called every time changes has been + * made that can change the visual. + */ + virtual void SetupText(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Handle events manually to catch keyboard inputting. + */ + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + + /** + * Draws the List box + */ + virtual void Draw(); + + /** + * Easy select elements functions + */ + virtual void SelectNextElement(); + virtual void SelectPrevElement(); + virtual void SelectFirstElement(); + virtual void SelectLastElement(); + + /** + * Handle the \ tag. + */ + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); + + // Called every time the auto-scrolling should be checked. + void UpdateAutoScroll(); + + // Extended drawing interface, this is so that classes built on the this one + // can use other sprite names. + virtual void DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor); + + // Get the area of the list. This is so that it can easily be changed, like in CDropDown + // where the area is not equal to m_CachedActualSize. + virtual CRect GetListRect() const { return m_CachedActualSize; } + + // Returns whether SetupText() has run since the last message was received + // (and thus whether list items have possibly changed). + virtual bool GetModified() const { return m_Modified; } + + /** + * List of each element's relative y position. Will be + * one larger than m_Items, because it will end with the + * bottom of the last element. First element will always + * be zero, but still stored for easy handling. + */ + std::vector m_ItemsYPositions; + + virtual int GetHoveredItem(); + + // Settings + float m_BufferZone; + CStrW m_Font; + bool m_ScrollBar; + CStr m_ScrollBarStyle; + CStrW m_SoundDisabled; + CStrW m_SoundSelected; + CGUISpriteInstance m_Sprite; + CGUISpriteInstance m_SpriteSelectArea; + i32 m_CellID; + EAlign m_TextAlign; + CGUIColor m_TextColor; + CGUIColor m_TextColorSelected; + i32 m_Selected; + bool m_AutoScroll; + i32 m_Hovered; + CGUIList m_List; + CGUIList m_ListData; + +private: + // Whether the list's items have been modified since last handling a message. + bool m_Modified; + + // Used for doubleclick registration + int m_PrevSelectedItem; + + // Last time a click on an item was issued + double m_LastItemClickTime; +}; + +#endif // INCLUDED_CLIST Index: ps/trunk/source/gui/ObjectTypes/CList.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CList.cpp +++ ps/trunk/source/gui/ObjectTypes/CList.cpp @@ -0,0 +1,483 @@ +/* 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 "CList.h" + +#include "gui/CGUI.h" +#include "gui/CGUIScrollBarVertical.h" +#include "gui/SettingTypes/CGUIColor.h" +#include "gui/SettingTypes/CGUIList.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/timer.h" + +CList::CList(CGUI& pGUI) + : IGUIObject(pGUI), + IGUITextOwner(*static_cast(this)), + IGUIScrollBarOwner(*static_cast(this)), + m_Modified(false), + m_PrevSelectedItem(-1), + m_LastItemClickTime(0), + m_BufferZone(), + m_Font(), + m_ScrollBar(), + m_ScrollBarStyle(), + m_SoundDisabled(), + m_SoundSelected(), + m_Sprite(), + m_SpriteSelectArea(), + m_CellID(), + m_TextAlign(), + m_TextColor(), + m_TextColorSelected(), + m_Selected(), + m_AutoScroll(), + m_Hovered(), + m_List(), + m_ListData() +{ + RegisterSetting("buffer_zone", m_BufferZone); + RegisterSetting("font", m_Font); + RegisterSetting("scrollbar", m_ScrollBar); + RegisterSetting("scrollbar_style", m_ScrollBarStyle); + RegisterSetting("sound_disabled", m_SoundDisabled); + RegisterSetting("sound_selected", m_SoundSelected); + RegisterSetting("sprite", m_Sprite); + // Add sprite_disabled! TODO + RegisterSetting("sprite_selectarea", m_SpriteSelectArea); + RegisterSetting("cell_id", m_CellID); + RegisterSetting("text_align", m_TextAlign); + RegisterSetting("textcolor", m_TextColor); + RegisterSetting("textcolor_selected", m_TextColorSelected); + RegisterSetting("selected", m_Selected); // Index selected. -1 is none. + RegisterSetting("auto_scroll", m_AutoScroll); + RegisterSetting("hovered", m_Hovered); + // Each list item has both a name (in 'list') and an associated data string (in 'list_data') + RegisterSetting("list", m_List); + RegisterSetting("list_data", m_ListData); + + SetSetting("scrollbar", false, true); + SetSetting("selected", -1, true); + SetSetting("hovered", -1, true); + SetSetting("auto_scroll", false, true); + + // Add scroll-bar + CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); + bar->SetRightAligned(true); + AddScrollBar(bar); +} + +CList::~CList() +{ +} + +void CList::SetupText() +{ + m_Modified = true; + + m_ItemsYPositions.resize(m_List.m_Items.size() + 1); + + // Delete all generated texts. Some could probably be saved, + // but this is easier, and this function will never be called + // continuously, or even often, so it'll probably be okay. + m_GeneratedTexts.clear(); + + float width = GetListRect().GetWidth(); + // remove scrollbar if applicable + if (m_ScrollBar && GetScrollBar(0).GetStyle()) + width -= GetScrollBar(0).GetStyle()->m_Width; + + // Generate texts + float buffered_y = 0.f; + + for (size_t i = 0; i < m_List.m_Items.size(); ++i) + { + CGUIText* text; + + if (!m_List.m_Items[i].GetOriginalString().empty()) + text = &AddText(m_List.m_Items[i], m_Font, width, m_BufferZone); + else + { + // Minimum height of a space character of the current font size + CGUIString align_string; + align_string.SetValue(L" "); + text = &AddText(align_string, m_Font, width, m_BufferZone); + } + + m_ItemsYPositions[i] = buffered_y; + buffered_y += text->GetSize().cy; + } + + m_ItemsYPositions[m_List.m_Items.size()] = buffered_y; + + // Setup scrollbar + if (m_ScrollBar) + { + GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); + GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight()); + + CRect rect = GetListRect(); + GetScrollBar(0).SetX(rect.right); + GetScrollBar(0).SetY(rect.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(rect.bottom - rect.top); + } +} + +void CList::ResetStates() +{ + IGUIObject::ResetStates(); + IGUIScrollBarOwner::ResetStates(); +} + +void CList::UpdateCachedSize() +{ + IGUIObject::UpdateCachedSize(); + IGUITextOwner::UpdateCachedSize(); +} + +void CList::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + IGUIScrollBarOwner::HandleMessage(Message); + //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! + + m_Modified = false; + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + if (Message.value == "list") + SetupText(); + + // If selected is changed, call "SelectionChange" + if (Message.value == "selected") + { + // TODO: Check range + + if (m_AutoScroll) + UpdateAutoScroll(); + + // TODO only works if lower-case, shouldn't it be made case sensitive instead? + ScriptEvent("selectionchange"); + } + + if (Message.value == "scrollbar") + SetupText(); + + // Update scrollbar + if (Message.value == "scrollbar_style") + { + GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); + SetupText(); + } + + break; + + case GUIM_MOUSE_PRESS_LEFT: + { + if (!m_Enabled) + { + PlaySound(m_SoundDisabled); + break; + } + + int hovered = GetHoveredItem(); + if (hovered == -1) + break; + SetSetting("selected", hovered, true); + UpdateAutoScroll(); + PlaySound(m_SoundSelected); + + if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem) + this->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, "mouseleftdoubleclickitem"); + else + this->SendEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, "mouseleftclickitem"); + + m_LastItemClickTime = timer_Time(); + m_PrevSelectedItem = hovered; + break; + } + + case GUIM_MOUSE_LEAVE: + { + if (m_Hovered == -1) + break; + + SetSetting("hovered", -1, true); + ScriptEvent("hoverchange"); + break; + } + + case GUIM_MOUSE_OVER: + { + int hovered = GetHoveredItem(); + if (hovered == m_Hovered) + break; + + SetSetting("hovered", hovered, true); + ScriptEvent("hoverchange"); + break; + } + + case GUIM_LOAD: + { + GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); + break; + } + + default: + break; + } + + IGUITextOwner::HandleMessage(Message); +} + +InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev) +{ + InReaction result = IN_PASS; + + if (ev->ev.type == SDL_KEYDOWN) + { + int szChar = ev->ev.key.keysym.sym; + + switch (szChar) + { + case SDLK_HOME: + SelectFirstElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_END: + SelectLastElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_UP: + SelectPrevElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_DOWN: + SelectNextElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_PAGEUP: + GetScrollBar(0).ScrollMinusPlenty(); + result = IN_HANDLED; + break; + + case SDLK_PAGEDOWN: + GetScrollBar(0).ScrollPlusPlenty(); + result = IN_HANDLED; + break; + + default: // Do nothing + result = IN_PASS; + } + } + + return result; +} + +void CList::Draw() +{ + DrawList(m_Selected, m_Sprite, m_SpriteSelectArea, m_TextColor); +} + +void CList::DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selectarea, const CGUIColor& textcolor) +{ + float bz = GetBufferedZ(); + + // First call draw on ScrollBarOwner + if (m_ScrollBar) + IGUIScrollBarOwner::Draw(); + + { + CRect rect = GetListRect(); + + m_pGUI.DrawSprite(sprite, m_CellID, bz, rect); + + float scroll = 0.f; + if (m_ScrollBar) + scroll = GetScrollBar(0).GetPos(); + + if (selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()) + { + // Get rectangle of selection: + CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, + rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); + + if (rect_sel.top <= rect.bottom && + rect_sel.bottom >= rect.top) + { + if (rect_sel.bottom > rect.bottom) + rect_sel.bottom = rect.bottom; + if (rect_sel.top < rect.top) + rect_sel.top = rect.top; + + if (m_ScrollBar) + { + // Remove any overlapping area of the scrollbar. + if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && + rect_sel.right <= GetScrollBar(0).GetOuterRect().right) + rect_sel.right = GetScrollBar(0).GetOuterRect().left; + + if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && + rect_sel.left < GetScrollBar(0).GetOuterRect().right) + rect_sel.left = GetScrollBar(0).GetOuterRect().right; + } + + m_pGUI.DrawSprite(sprite_selectarea, m_CellID, bz + 0.05f, rect_sel); + } + } + + for (size_t i = 0; i < m_List.m_Items.size(); ++i) + { + if (m_ItemsYPositions[i+1] - scroll < 0 || + m_ItemsYPositions[i] - scroll > rect.GetHeight()) + continue; + + // Clipping area (we'll have to substract the scrollbar) + CRect cliparea = GetListRect(); + + if (m_ScrollBar) + { + if (cliparea.right > GetScrollBar(0).GetOuterRect().left && + cliparea.right <= GetScrollBar(0).GetOuterRect().right) + cliparea.right = GetScrollBar(0).GetOuterRect().left; + + if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && + cliparea.left < GetScrollBar(0).GetOuterRect().right) + cliparea.left = GetScrollBar(0).GetOuterRect().right; + } + + DrawText(i, textcolor, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz + 0.1f, cliparea); + } + } +} + +void CList::AddItem(const CStrW& str, const CStrW& data) +{ + CGUIString gui_string; + gui_string.SetValue(str); + + // Do not send a settings-changed message + m_List.m_Items.push_back(gui_string); + + CGUIString data_string; + data_string.SetValue(data); + + m_ListData.m_Items.push_back(data_string); + + // TODO Temp + SetupText(); +} + +bool CList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + int elmt_item = pFile->GetElementID("item"); + + if (child.GetNodeName() == elmt_item) + { + AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); + return true; + } + + return false; +} + +void CList::SelectNextElement() +{ + if (m_Selected != static_cast(m_List.m_Items.size()) - 1) + { + SetSetting("selected", m_Selected + 1, true); + PlaySound(m_SoundSelected); + } +} + +void CList::SelectPrevElement() +{ + if (m_Selected > 0) + { + SetSetting("selected", m_Selected - 1, true); + PlaySound(m_SoundSelected); + } +} + +void CList::SelectFirstElement() +{ + if (m_Selected >= 0) + SetSetting("selected", 0, true); +} + +void CList::SelectLastElement() +{ + const int index = static_cast(m_List.m_Items.size()) - 1; + + if (m_Selected != index) + SetSetting("selected", index, true); +} + +void CList::UpdateAutoScroll() +{ + // No scrollbar, no scrolling (at least it's not made to work properly). + if (!m_ScrollBar || m_Selected < 0 || static_cast(m_Selected) >= m_ItemsYPositions.size()) + return; + + float scroll = GetScrollBar(0).GetPos(); + + // Check upper boundary + if (m_ItemsYPositions[m_Selected] < scroll) + { + GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected]); + return; // this means, if it wants to align both up and down at the same time + // this will have precedence. + } + + // Check lower boundary + CRect rect = GetListRect(); + if (m_ItemsYPositions[m_Selected+1]-rect.GetHeight() > scroll) + GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected+1]-rect.GetHeight()); +} + +int CList::GetHoveredItem() +{ + const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f; + + const CRect& rect = GetListRect(); + CPos mouse = m_pGUI.GetMousePos(); + mouse.y += scroll; + + // Mouse is over scrollbar + if (m_ScrollBar && GetScrollBar(0).IsVisible() && + mouse.x >= GetScrollBar(0).GetOuterRect().left && + mouse.x <= GetScrollBar(0).GetOuterRect().right) + return -1; + + for (size_t i = 0; i < m_List.m_Items.size(); ++i) + if (mouse.y >= rect.top + m_ItemsYPositions[i] && + mouse.y < rect.top + m_ItemsYPositions[i + 1]) + return i; + + return -1; +} Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.h +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.h @@ -0,0 +1,115 @@ +/* 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_MINIMAP +#define INCLUDED_MINIMAP + +#include "graphics/ShaderProgramPtr.h" +#include "gui/ObjectBases/IGUIObject.h" +#include "renderer/VertexArray.h" + +class CCamera; +class CMatrix3D; +class CTerrain; + +class CMiniMap : public IGUIObject +{ + GUI_OBJECT(CMiniMap) +public: + CMiniMap(CGUI& pGUI); + virtual ~CMiniMap(); +protected: + virtual void Draw(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * @see IGUIObject#IsMouseOver() + */ + virtual bool IsMouseOver() const; + + // create the minimap textures + void CreateTextures(); + + // rebuild the terrain texture map + void RebuildTerrainTexture(); + + // destroy and free any memory and textures + void Destroy(); + + void SetCameraPos(); + + void FireWorldClickEvent(int button, int clicks); + + // the terrain we are mini-mapping + const CTerrain* m_Terrain; + + const CCamera* m_Camera; + + //Whether or not the mouse is currently down + bool m_Clicking; + + // minimap texture handles + GLuint m_TerrainTexture; + + // texture data + u32* m_TerrainData; + + // whether we need to regenerate the terrain texture + bool m_TerrainDirty; + + ssize_t m_Width, m_Height; + + // map size + ssize_t m_MapSize; + + // texture size + GLsizei m_TextureSize; + + // 1.f if map is circular or 1.414f if square (to shrink it inside the circle) + float m_MapScale; + + // maximal water height to allow the passage of a unit (for underwater shallows). + float m_ShallowPassageHeight; + + float m_WaterHeight; + + void DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const; + + void DrawViewRect(CMatrix3D transform) const; + + void GetMouseWorldCoordinates(float& x, float& z) const; + + float GetAngle() const; + + VertexIndexArray m_IndexArray; + VertexArray m_VertexArray; + VertexArray::Attribute m_AttributePos; + VertexArray::Attribute m_AttributeColor; + + size_t m_EntitiesDrawn; + + double m_PingDuration; + double m_HalfBlinkDuration; + double m_NextBlinkTime; + bool m_BlinkState; +}; + +#endif // INCLUDED_MINIMAP Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp @@ -0,0 +1,711 @@ +/* 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 "CMiniMap.h" + +#include "graphics/GameView.h" +#include "graphics/LOSTexture.h" +#include "graphics/MiniPatch.h" +#include "graphics/Terrain.h" +#include "graphics/TerrainTextureEntry.h" +#include "graphics/TerrainTextureManager.h" +#include "graphics/TerritoryTexture.h" +#include "gui/CGUI.h" +#include "gui/GUIManager.h" +#include "gui/GUIMatrix.h" +#include "lib/bits.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/ogl.h" +#include "lib/timer.h" +#include "ps/ConfigDB.h" +#include "ps/Filesystem.h" +#include "ps/Game.h" +#include "ps/GameSetup/Config.h" +#include "ps/Profile.h" +#include "ps/World.h" +#include "ps/XML/Xeromyces.h" +#include "renderer/Renderer.h" +#include "renderer/RenderingOptions.h" +#include "renderer/WaterManager.h" +#include "scriptinterface/ScriptInterface.h" +#include "simulation2/components/ICmpMinimap.h" +#include "simulation2/Simulation2.h" +#include "simulation2/system/ParamNode.h" + +#include + +extern bool g_GameRestarted; + +// Set max drawn entities to UINT16_MAX for now, which is more than enough +// TODO: we should be cleverer about drawing them to reduce clutter +const u16 MAX_ENTITIES_DRAWN = 65535; + +static unsigned int ScaleColor(unsigned int color, float x) +{ + unsigned int r = unsigned(float(color & 0xff) * x); + unsigned int g = unsigned(float((color>>8) & 0xff) * x); + unsigned int b = unsigned(float((color>>16) & 0xff) * x); + return (0xff000000 | b | g<<8 | r<<16); +} + +CMiniMap::CMiniMap(CGUI& pGUI) : + IGUIObject(pGUI), + m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f), + m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), + m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0) +{ + m_Clicking = false; + m_MouseHovering = false; + + // Register Relax NG validator + CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); + + // Get the maximum height for unit passage in water. + CParamNode externalParamNode; + CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); + const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); + if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) + m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); + else + m_ShallowPassageHeight = 0.0f; + + m_AttributePos.type = GL_FLOAT; + m_AttributePos.elems = 2; + m_VertexArray.AddAttribute(&m_AttributePos); + + m_AttributeColor.type = GL_UNSIGNED_BYTE; + m_AttributeColor.elems = 4; + m_VertexArray.AddAttribute(&m_AttributeColor); + + m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN); + m_VertexArray.Layout(); + + m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN); + m_IndexArray.Layout(); + VertexArrayIterator index = m_IndexArray.GetIterator(); + for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) + *index++ = i; + m_IndexArray.Upload(); + m_IndexArray.FreeBackingStore(); + + + VertexArrayIterator attrPos = m_AttributePos.GetIterator(); + VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); + for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) + { + (*attrColor)[0] = 0; + (*attrColor)[1] = 0; + (*attrColor)[2] = 0; + (*attrColor)[3] = 0; + ++attrColor; + + (*attrPos)[0] = -10000.0f; + (*attrPos)[1] = -10000.0f; + + ++attrPos; + + } + m_VertexArray.Upload(); + + double blinkDuration = 1.0; + + // Tests won't have config initialised + if (CConfigDB::IsInitialised()) + { + CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); + CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); + } + m_HalfBlinkDuration = blinkDuration/2; +} + +CMiniMap::~CMiniMap() +{ + Destroy(); +} + +void CMiniMap::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + switch (Message.type) + { + case GUIM_MOUSE_PRESS_LEFT: + if (m_MouseHovering) + { + SetCameraPos(); + m_Clicking = true; + } + break; + case GUIM_MOUSE_RELEASE_LEFT: + if (m_MouseHovering && m_Clicking) + SetCameraPos(); + m_Clicking = false; + break; + case GUIM_MOUSE_DBLCLICK_LEFT: + if (m_MouseHovering && m_Clicking) + SetCameraPos(); + m_Clicking = false; + break; + case GUIM_MOUSE_ENTER: + m_MouseHovering = true; + break; + case GUIM_MOUSE_LEAVE: + m_Clicking = false; + m_MouseHovering = false; + break; + case GUIM_MOUSE_RELEASE_RIGHT: + CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); + break; + case GUIM_MOUSE_DBLCLICK_RIGHT: + CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); + break; + case GUIM_MOUSE_MOTION: + if (m_MouseHovering && m_Clicking) + SetCameraPos(); + break; + case GUIM_MOUSE_WHEEL_DOWN: + case GUIM_MOUSE_WHEEL_UP: + Message.Skip(); + break; + + default: + break; + } +} + +bool CMiniMap::IsMouseOver() const +{ + // Get the mouse position. + const CPos& mousePos = m_pGUI.GetMousePos(); + // Get the position of the center of the minimap. + CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0); + // Take the magnitude of the difference of the mouse position and minimap center. + double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2)); + // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap. + if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0) + return true; + else + return false; +} + +void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) const +{ + // Determine X and Z according to proportion of mouse position and minimap + + const CPos& mousePos = m_pGUI.GetMousePos(); + + float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth(); + float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight(); + + float angle = GetAngle(); + + // Scale world coordinates for shrunken square map + x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5); + z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5); +} + +void CMiniMap::SetCameraPos() +{ + CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + + CVector3D target; + GetMouseWorldCoordinates(target.X, target.Z); + target.Y = terrain->GetExactGroundLevel(target.X, target.Z); + g_Game->GetView()->MoveCameraTarget(target); +} + +float CMiniMap::GetAngle() const +{ + CVector3D cameraIn = m_Camera->m_Orientation.GetIn(); + return -atan2(cameraIn.X, cameraIn.Z); +} + +void CMiniMap::FireWorldClickEvent(int UNUSED(button), int UNUSED(clicks)) +{ + JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + + float x, z; + GetMouseWorldCoordinates(x, z); + + JS::RootedValue coords(cx); + ScriptInterface::CreateObject(cx, &coords, "x", x, "z", z); + + JS::AutoValueVector paramData(cx); + paramData.append(coords); + + ScriptEvent("worldclick", paramData); +} + +// This sets up and draws the rectangle on the minimap +// which represents the view of the camera in the world. +void CMiniMap::DrawViewRect(CMatrix3D transform) const +{ + // Compute the camera frustum intersected with a fixed-height plane. + // Use the water height as a fixed base height, which should be the lowest we can go + float h = g_Renderer.GetWaterManager()->m_WaterHeight; + const float width = m_CachedActualSize.GetWidth(); + const float height = m_CachedActualSize.GetHeight(); + const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize); + + CVector3D hitPt[4]; + hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h); + hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h); + hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h); + hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h); + + float ViewRect[4][2]; + for (int i = 0; i < 4; ++i) + { + // convert to minimap space + ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize); + ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize); + } + + float viewVerts[] = { + ViewRect[0][0], -ViewRect[0][1], + ViewRect[1][0], -ViewRect[1][1], + ViewRect[2][0], -ViewRect[2][1], + ViewRect[3][0], -ViewRect[3][1] + }; + + // Enable Scissoring to restrict the rectangle to only the minimap. + glScissor( + m_CachedActualSize.left * g_GuiScale, + g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale, + width * g_GuiScale, + height * g_GuiScale); + glEnable(GL_SCISSOR_TEST); + glLineWidth(2.0f); + + CShaderDefines lineDefines; + lineDefines.Add(str_MINIMAP_LINE, str_1); + CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines); + tech->BeginPass(); + CShaderProgramPtr shader = tech->GetShader(); + shader->Uniform(str_transform, transform); + shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f); + + shader->VertexPointer(2, GL_FLOAT, 0, viewVerts); + shader->AssertPointersBound(); + + if (!g_Renderer.m_SkipSubmit) + glDrawArrays(GL_LINE_LOOP, 0, 4); + + tech->EndPass(); + + glLineWidth(1.0f); + glDisable(GL_SCISSOR_TEST); +} + +struct MinimapUnitVertex +{ + // This struct is copyable for convenience and because to move is to copy for primitives. + u8 r, g, b, a; + float x, y; +}; + +// Adds a vertex to the passed VertexArray +static void inline addVertex(const MinimapUnitVertex& v, + VertexArrayIterator& attrColor, + VertexArrayIterator& attrPos) +{ + (*attrColor)[0] = v.r; + (*attrColor)[1] = v.g; + (*attrColor)[2] = v.b; + (*attrColor)[3] = v.a; + ++attrColor; + + (*attrPos)[0] = v.x; + (*attrPos)[1] = v.y; + + ++attrPos; +} + + +void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const +{ + // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m) + // Scale square maps to fit in circular minimap area + const float s = sin(angle) * m_MapScale; + const float c = cos(angle) * m_MapScale; + const float m = coordMax / 2.f; + + float quadTex[] = { + m*(-c + s + 1.f), m*(-c + -s + 1.f), + m*(c + s + 1.f), m*(-c + s + 1.f), + m*(c + -s + 1.f), m*(c + s + 1.f), + + m*(c + -s + 1.f), m*(c + s + 1.f), + m*(-c + -s + 1.f), m*(c + -s + 1.f), + m*(-c + s + 1.f), m*(-c + -s + 1.f) + }; + float quadVerts[] = { + x, y, z, + x2, y, z, + x2, y2, z, + + x2, y2, z, + x, y2, z, + x, y, z + }; + + shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); + shader->VertexPointer(3, GL_FLOAT, 0, quadVerts); + shader->AssertPointersBound(); + + if (!g_Renderer.m_SkipSubmit) + glDrawArrays(GL_TRIANGLES, 0, 6); +} + +// TODO: render the minimap in a framebuffer and just draw the frambuffer texture +// most of the time, updating the framebuffer twice a frame. +// Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling +// (those operations cause a gpu sync, which slows down the way gpu works) +void CMiniMap::Draw() +{ + PROFILE3("render minimap"); + + // The terrain isn't actually initialized until the map is loaded, which + // happens when the game is started, so abort until then. + if (!g_Game || !g_Game->IsGameStarted()) + return; + + CSimulation2* sim = g_Game->GetSimulation2(); + CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); + ENSURE(cmpRangeManager); + + // Set our globals in case they hadn't been set before + m_Camera = g_Game->GetView()->GetCamera(); + m_Terrain = g_Game->GetWorld()->GetTerrain(); + m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); + m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); + m_MapSize = m_Terrain->GetVerticesPerSide(); + m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); + m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); + + if (!m_TerrainTexture || g_GameRestarted) + CreateTextures(); + + + // only update 2x / second + // (note: since units only move a few pixels per second on the minimap, + // we can get away with infrequent updates; this is slow) + // TODO: Update all but camera at same speed as simulation + static double last_time; + const double cur_time = timer_Time(); + const bool doUpdate = cur_time - last_time > 0.5; + if (doUpdate) + { + last_time = cur_time; + if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight) + RebuildTerrainTexture(); + } + + const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; + const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; + const float z = GetBufferedZ(); + const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize; + const float angle = GetAngle(); + const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f); + + // Disable depth updates to prevent apparent z-fighting-related issues + // with some drivers causing units to get drawn behind the texture. + glDepthMask(0); + + CShaderProgramPtr shader; + CShaderTechniquePtr tech; + + CShaderDefines baseDefines; + baseDefines.Add(str_MINIMAP_BASE, str_1); + tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines); + tech->BeginPass(); + shader = tech->GetShader(); + + // Draw the main textured quad + shader->BindTexture(str_baseTex, m_TerrainTexture); + const CMatrix3D baseTransform = GetDefaultGuiMatrix(); + CMatrix3D baseTextureTransform; + baseTextureTransform.SetIdentity(); + shader->Uniform(str_transform, baseTransform); + shader->Uniform(str_textureTransform, baseTextureTransform); + + DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z); + + // Draw territory boundaries + glEnable(GL_BLEND); + + CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); + + shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); + const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix(); + shader->Uniform(str_transform, baseTransform); + shader->Uniform(str_textureTransform, *territoryTransform); + + DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); + tech->EndPass(); + + // Draw the LOS quad in black, using alpha values from the LOS texture + CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); + + CShaderDefines losDefines; + losDefines.Add(str_MINIMAP_LOS, str_1); + tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines); + tech->BeginPass(); + shader = tech->GetShader(); + shader->BindTexture(str_baseTex, losTexture.GetTexture()); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix(); + shader->Uniform(str_transform, baseTransform); + shader->Uniform(str_textureTransform, *losTransform); + + DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); + tech->EndPass(); + + glDisable(GL_BLEND); + + PROFILE_START("minimap units"); + + CShaderDefines pointDefines; + pointDefines.Add(str_MINIMAP_POINT, str_1); + tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines); + tech->BeginPass(); + shader = tech->GetShader(); + shader->Uniform(str_transform, baseTransform); + shader->Uniform(str_pointSize, 3.f); + + CMatrix3D unitMatrix; + unitMatrix.SetIdentity(); + // Center the minimap on the origin of the axis of rotation. + unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f); + // Rotate the map. + unitMatrix.RotateZ(angle); + // Scale square maps to fit. + unitMatrix.Scale(unitScale, unitScale, 1.f); + // Move the minimap back to it's starting position. + unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f); + // Move the minimap to it's final location. + unitMatrix.Translate(x, y, z); + // Apply the gui matrix. + unitMatrix *= GetDefaultGuiMatrix(); + // Load the transform into the shader. + shader->Uniform(str_transform, unitMatrix); + + const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); + const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); + + CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap); + + if (doUpdate) + { + VertexArrayIterator attrPos = m_AttributePos.GetIterator(); + VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); + + m_EntitiesDrawn = 0; + MinimapUnitVertex v; + std::vector pingingVertices; + pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); + + if (cur_time > m_NextBlinkTime) + { + m_BlinkState = !m_BlinkState; + m_NextBlinkTime = cur_time + m_HalfBlinkDuration; + } + + entity_pos_t posX, posZ; + for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) + { + ICmpMinimap* cmpMinimap = static_cast(it->second); + if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) + { + ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer()); + if (vis != ICmpRangeManager::VIS_HIDDEN) + { + v.a = 255; + v.x = posX.ToFloat() * sx; + v.y = -posZ.ToFloat() * sy; + + // Check minimap pinging to indicate something + if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration)) + { + v.r = 255; // ping color is white + v.g = 255; + v.b = 255; + pingingVertices.push_back(v); + } + else + { + addVertex(v, attrColor, attrPos); + ++m_EntitiesDrawn; + } + } + } + } + + // Add the pinged vertices at the end, so they are drawn on top + for (size_t v = 0; v < pingingVertices.size(); ++v) + { + addVertex(pingingVertices[v], attrColor, attrPos); + ++m_EntitiesDrawn; + } + + ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); + m_VertexArray.Upload(); + } + + m_VertexArray.PrepareForRendering(); + + if (m_EntitiesDrawn > 0) + { +#if !CONFIG2_GLES + if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); +#endif + + u8* indexBase = m_IndexArray.Bind(); + u8* base = m_VertexArray.Bind(); + const GLsizei stride = (GLsizei)m_VertexArray.GetStride(); + + shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset); + shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset); + shader->AssertPointersBound(); + + if (!g_Renderer.m_SkipSubmit) + glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase); + + g_Renderer.GetStats().m_DrawCalls++; + CVertexBuffer::Unbind(); + +#if !CONFIG2_GLES + if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); +#endif + } + + tech->EndPass(); + + DrawViewRect(unitMatrix); + + PROFILE_END("minimap units"); + + // Reset depth mask + glDepthMask(1); +} + +void CMiniMap::CreateTextures() +{ + Destroy(); + + // Create terrain texture + glGenTextures(1, &m_TerrainTexture); + g_Renderer.BindTexture(0, m_TerrainTexture); + + // Initialise texture with solid black, for the areas we don't + // overwrite with glTexSubImage2D later + u32* texData = new u32[m_TextureSize * m_TextureSize]; + for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i) + texData[i] = 0xFF000000; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); + delete[] texData; + + m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Rebuild and upload both of them + RebuildTerrainTexture(); +} + + +void CMiniMap::RebuildTerrainTexture() +{ + u32 x = 0; + u32 y = 0; + u32 w = m_MapSize - 1; + u32 h = m_MapSize - 1; + m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; + + m_TerrainDirty = false; + + for (u32 j = 0; j < h; ++j) + { + u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; + for (u32 i = 0; i < w; ++i) + { + float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j) + + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j) + + m_Terrain->GetVertexGroundLevel((int)i, (int)j+1) + + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1) + ) / 4.0f; + + if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) + { + // shallow water + *dataPtr++ = 0xffc09870; + } + else if (avgHeight < m_WaterHeight) + { + // Set water as constant color for consistency on different maps + *dataPtr++ = 0xffa07850; + } + else + { + int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; + int val = (hmap / 3) + 170; + + u32 color = 0xFFFFFFFF; + + CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j); + if (mp) + { + CTerrainTextureEntry* tex = mp->GetTextureEntry(); + if (tex) + { + // If the texture can't be loaded yet, set the dirty flags + // so we'll try regenerating the terrain texture again soon + if(!tex->GetTexture()->TryLoad()) + m_TerrainDirty = true; + + color = tex->GetBaseColor(); + } + } + + *dataPtr++ = ScaleColor(color, float(val) / 255.0f); + } + } + } + + // Upload the texture + g_Renderer.BindTexture(0, m_TerrainTexture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData); +} + +void CMiniMap::Destroy() +{ + if (m_TerrainTexture) + { + glDeleteTextures(1, &m_TerrainTexture); + m_TerrainTexture = 0; + } + + SAFE_ARRAY_DELETE(m_TerrainData); +} Index: ps/trunk/source/gui/ObjectTypes/COList.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/COList.h +++ ps/trunk/source/gui/ObjectTypes/COList.h @@ -0,0 +1,91 @@ +/* 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_COLIST +#define INCLUDED_COLIST + +#include "gui/ObjectTypes/CList.h" +#include "gui/SettingTypes/CGUIColor.h" + +#include + +/** + * Represents a column. + */ +struct COListColumn +{ + // Avoid copying the strings. + NONCOPYABLE(COListColumn); + MOVABLE(COListColumn); + COListColumn() : m_Width(0), m_Hidden(false) {} + CGUIColor m_TextColor; + CStr m_Id; + float m_Width; + CStrW m_Heading; // CGUIString?? + CGUIList m_List; + bool m_Hidden; +}; + +/** + * Multi-column list. One row can be selected by the user. + * Individual cells are clipped if the contained text is too long. + * + * The list can be sorted dynamically by JS code when a + * heading is clicked. + * A scroll-bar will appear when needed. + */ +class COList : public CList +{ + GUI_OBJECT(COList) + +public: + COList(CGUI& pGUI); + +protected: + void SetupText(); + void HandleMessage(SGUIMessage& Message); + + /** + * Handle the \ tag. + */ + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); + virtual void AdditionalChildrenHandled(); + + void DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor); + + virtual CRect GetListRect() const; + + /** + * Available columns. + */ + std::vector m_Columns; + + // Settings + CGUISpriteInstance m_SpriteHeading; + bool m_Sortable; + CStr m_SelectedColumn; + i32 m_SelectedColumnOrder; + CGUISpriteInstance m_SpriteAsc; + CGUISpriteInstance m_SpriteDesc; + CGUISpriteInstance m_SpriteNotSorted; + +private: + // Width of space available for columns + float m_TotalAvailableColumnWidth; + float m_HeadingHeight; +}; + +#endif // INCLUDED_COLIST Index: ps/trunk/source/gui/ObjectTypes/COList.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/COList.cpp +++ ps/trunk/source/gui/ObjectTypes/COList.cpp @@ -0,0 +1,442 @@ +/* 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 "COList.h" + +#include "gui/CGUI.h" +#include "gui/IGUIScrollBar.h" +#include "gui/SettingTypes/CGUIColor.h" +#include "gui/SettingTypes/CGUIList.h" +#include "i18n/L10n.h" +#include "ps/CLogger.h" + +const float SORT_SPRITE_DIM = 16.0f; +const CPos COLUMN_SHIFT = CPos(0, 4); + +COList::COList(CGUI& pGUI) + : CList(pGUI), + m_SpriteHeading(), + m_Sortable(), + m_SelectedColumn(), + m_SelectedColumnOrder(), + m_SpriteAsc(), + m_SpriteDesc(), + m_SpriteNotSorted() +{ + RegisterSetting("sprite_heading", m_SpriteHeading); + RegisterSetting("sortable", m_Sortable); // The actual sorting is done in JS for more versatility + RegisterSetting("selected_column", m_SelectedColumn); + RegisterSetting("selected_column_order", m_SelectedColumnOrder); + RegisterSetting("sprite_asc", m_SpriteAsc); // Show the order of sorting + RegisterSetting("sprite_desc", m_SpriteDesc); + RegisterSetting("sprite_not_sorted", m_SpriteNotSorted); +} + +void COList::SetupText() +{ + m_ItemsYPositions.resize(m_List.m_Items.size() + 1); + + // Delete all generated texts. Some could probably be saved, + // but this is easier, and this function will never be called + // continuously, or even often, so it'll probably be okay. + m_GeneratedTexts.clear(); + + m_TotalAvailableColumnWidth = GetListRect().GetWidth(); + // remove scrollbar if applicable + if (m_ScrollBar && GetScrollBar(0).GetStyle()) + m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width; + + m_HeadingHeight = SORT_SPRITE_DIM; // At least the size of the sorting sprite + + for (const COListColumn& column : m_Columns) + { + float width = column.m_Width; + if (column.m_Width > 0 && column.m_Width < 1) + width *= m_TotalAvailableColumnWidth; + + CGUIString gui_string; + gui_string.SetValue(column.m_Heading); + + const CGUIText& text = AddText(gui_string, m_Font, width, m_BufferZone); + m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().cy + COLUMN_SHIFT.y); + } + + // Generate texts + float buffered_y = 0.f; + + for (size_t i = 0; i < m_List.m_Items.size(); ++i) + { + m_ItemsYPositions[i] = buffered_y; + float shift = 0.0f; + for (const COListColumn& column : m_Columns) + { + float width = column.m_Width; + if (column.m_Width > 0 && column.m_Width < 1) + width *= m_TotalAvailableColumnWidth; + + CGUIText* text; + if (!column.m_List.m_Items[i].GetOriginalString().empty()) + text = &AddText(column.m_List.m_Items[i], m_Font, width, m_BufferZone); + else + { + // Minimum height of a space character of the current font size + CGUIString align_string; + align_string.SetValue(L" "); + text = &AddText(align_string, m_Font, width, m_BufferZone); + } + shift = std::max(shift, text->GetSize().cy); + } + buffered_y += shift; + } + + m_ItemsYPositions[m_List.m_Items.size()] = buffered_y; + + if (m_ScrollBar) + { + CRect rect = GetListRect(); + GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); + GetScrollBar(0).SetScrollSpace(rect.GetHeight()); + + GetScrollBar(0).SetX(rect.right); + GetScrollBar(0).SetY(rect.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(rect.bottom - rect.top); + } +} + +CRect COList::GetListRect() const +{ + return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0); +} + +void COList::HandleMessage(SGUIMessage& Message) +{ + CList::HandleMessage(Message); + + switch (Message.type) + { + // If somebody clicks on the column heading + case GUIM_MOUSE_PRESS_LEFT: + { + if (!m_Sortable) + return; + + const CPos& mouse = m_pGUI.GetMousePos(); + if (!m_CachedActualSize.PointInside(mouse)) + return; + + float xpos = 0; + for (const COListColumn& column : m_Columns) + { + if (column.m_Hidden) + continue; + + float width = column.m_Width; + // Check if it's a decimal value, and if so, assume relative positioning. + if (column.m_Width < 1 && column.m_Width > 0) + width *= m_TotalAvailableColumnWidth; + CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); + if (mouse.x >= leftTopCorner.x && + mouse.x < leftTopCorner.x + width && + mouse.y < leftTopCorner.y + m_HeadingHeight) + { + if (column.m_Id != m_SelectedColumn) + { + SetSetting("selected_column_order", -1, true); + CStr selected_column = column.m_Id; + SetSetting("selected_column", selected_column, true); + } + else + SetSetting("selected_column_order", -m_SelectedColumnOrder, true); + + ScriptEvent("selectioncolumnchange"); + PlaySound(m_SoundSelected); + return; + } + xpos += width; + } + return; + } + default: + return; + } +} + +bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + #define ELMT(x) int elmt_##x = pFile->GetElementID(#x) + #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x) + ELMT(item); + ELMT(column); + ELMT(translatableAttribute); + ATTR(id); + ATTR(context); + + if (child.GetNodeName() == elmt_item) + { + AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); + return true; + } + else if (child.GetNodeName() == elmt_column) + { + COListColumn column; + + for (XMBAttribute attr : child.GetAttributes()) + { + CStr attr_name(pFile->GetAttributeString(attr.Name)); + CStr attr_value(attr.Value); + + if (attr_name == "color") + { + if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), column.m_TextColor)) + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + } + else if (attr_name == "id") + { + column.m_Id = attr_value; + } + else if (attr_name == "hidden") + { + bool hidden = false; + if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), hidden)) + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + else + column.m_Hidden = hidden; + } + else if (attr_name == "width") + { + float width; + if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), width)) + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + else + { + // Check if it's a relative value, and save as decimal if so. + if (attr_value.find("%") != std::string::npos) + width = width / 100.f; + column.m_Width = width; + } + } + else if (attr_name == "heading") + { + column.m_Heading = attr_value.FromUTF8(); + } + } + + for (XMBElement grandchild : child.GetChildNodes()) + { + if (grandchild.GetNodeName() != elmt_translatableAttribute) + continue; + + CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id)); + // only the heading is translatable for list column + if (attributeName.empty() || attributeName != "heading") + { + LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str()); + continue; + } + + CStr value(grandchild.GetText()); + if (value.empty()) + continue; + + CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any. + if (!context.empty()) + { + CStr translatedValue(g_L10n.TranslateWithContext(context, value)); + column.m_Heading = translatedValue.FromUTF8(); + } + else + { + CStr translatedValue(g_L10n.Translate(value)); + column.m_Heading = translatedValue.FromUTF8(); + } + } + + m_Columns.emplace_back(std::move(column)); + return true; + } + + return false; +} + +void COList::AdditionalChildrenHandled() +{ + SetupText(); + + // Do this after the last push_back call to avoid iterator invalidation + for (COListColumn& column : m_Columns) + { + RegisterSetting("list_" + column.m_Id, column.m_List); + RegisterSetting("hidden_" + column.m_Id, column.m_Hidden); + } +} + +void COList::DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor) +{ + const float bz = GetBufferedZ(); + + if (m_ScrollBar) + IGUIScrollBarOwner::Draw(); + + CRect rect = GetListRect(); + + m_pGUI.DrawSprite(sprite, m_CellID, bz, rect); + + float scroll = 0.f; + if (m_ScrollBar) + scroll = GetScrollBar(0).GetPos(); + + // Draw item selection + if (selected != -1) + { + ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()); + + // Get rectangle of selection: + CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, + rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); + + if (rect_sel.top <= rect.bottom && + rect_sel.bottom >= rect.top) + { + if (rect_sel.bottom > rect.bottom) + rect_sel.bottom = rect.bottom; + if (rect_sel.top < rect.top) + rect_sel.top = rect.top; + + if (m_ScrollBar) + { + // Remove any overlapping area of the scrollbar. + if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && + rect_sel.right <= GetScrollBar(0).GetOuterRect().right) + rect_sel.right = GetScrollBar(0).GetOuterRect().left; + + if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && + rect_sel.left < GetScrollBar(0).GetOuterRect().right) + rect_sel.left = GetScrollBar(0).GetOuterRect().right; + } + + // Draw item selection + m_pGUI.DrawSprite(sprite_selected, m_CellID, bz + 0.05f, rect_sel); + } + } + + // Draw line above column header + CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, + m_CachedActualSize.top + m_HeadingHeight); + m_pGUI.DrawSprite(m_SpriteHeading, m_CellID, bz, rect_head); + + // Draw column headers + float xpos = 0; + size_t col = 0; + for (const COListColumn& column : m_Columns) + { + if (column.m_Hidden) + { + ++col; + continue; + } + + // Check if it's a decimal value, and if so, assume relative positioning. + float width = column.m_Width; + if (column.m_Width < 1 && column.m_Width > 0) + width *= m_TotalAvailableColumnWidth; + + CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); + + // Draw sort arrows in colum header + if (m_Sortable) + { + const CGUISpriteInstance* sprite; + if (m_SelectedColumn == column.m_Id) + { + if (m_SelectedColumnOrder == 0) + LOGERROR("selected_column_order must not be 0"); + + if (m_SelectedColumnOrder != -1) + sprite = &m_SpriteAsc; + else + sprite = &m_SpriteDesc; + } + else + sprite = &m_SpriteNotSorted; + + m_pGUI.DrawSprite(*sprite, m_CellID, bz + 0.1f, CRect(leftTopCorner + CPos(width - SORT_SPRITE_DIM, 0), leftTopCorner + CPos(width, SORT_SPRITE_DIM))); + } + + // Draw column header text + DrawText(col, textcolor, leftTopCorner + COLUMN_SHIFT, bz + 0.1f, rect_head); + xpos += width; + ++col; + } + + // Draw list items for each column + const size_t objectsCount = m_Columns.size(); + for (size_t i = 0; i < m_List.m_Items.size(); ++i) + { + if (m_ItemsYPositions[i+1] - scroll < 0 || + m_ItemsYPositions[i] - scroll > rect.GetHeight()) + continue; + + const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i]; + + // Clipping area (we'll have to substract the scrollbar) + CRect cliparea = GetListRect(); + + if (m_ScrollBar) + { + if (cliparea.right > GetScrollBar(0).GetOuterRect().left && + cliparea.right <= GetScrollBar(0).GetOuterRect().right) + cliparea.right = GetScrollBar(0).GetOuterRect().left; + + if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && + cliparea.left < GetScrollBar(0).GetOuterRect().right) + cliparea.left = GetScrollBar(0).GetOuterRect().right; + } + + // Draw all items for that column + xpos = 0; + size_t col = 0; + for (const COListColumn& column : m_Columns) + { + if (column.m_Hidden) + { + ++col; + continue; + } + + // Determine text position and width + const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]); + + float width = column.m_Width; + // Check if it's a decimal value, and if so, assume relative positioning. + if (column.m_Width < 1 && column.m_Width > 0) + width *= m_TotalAvailableColumnWidth; + + // Clip text to the column (to prevent drawing text into the neighboring column) + CRect cliparea2 = cliparea; + cliparea2.right = std::min(cliparea2.right, textPos.x + width); + cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + rowHeight); + + // Draw list item + DrawText(objectsCount * (i +/*Heading*/1) + col, column.m_TextColor, textPos, bz + 0.1f, cliparea2); + xpos += width; + ++col; + } + } +} Index: ps/trunk/source/gui/ObjectTypes/CProgressBar.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CProgressBar.h +++ ps/trunk/source/gui/ObjectTypes/CProgressBar.h @@ -0,0 +1,53 @@ +/* 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_CPROGRESSBAR +#define INCLUDED_CPROGRESSBAR + +#include "gui/ObjectBases/IGUIObject.h" +#include "gui/CGUISprite.h" + +/** + * Object used to draw a value (e.g. progress) from 0 to 100 visually. + */ +class CProgressBar : public IGUIObject +{ + GUI_OBJECT(CProgressBar) + +public: + CProgressBar(CGUI& pGUI); + virtual ~CProgressBar(); + +protected: + /** + * Draws the progress bar + */ + virtual void Draw(); + + // If caption is set, make sure it's within the interval 0-100 + /** + * @see IGUIObject#HandleMessage() + */ + void HandleMessage(SGUIMessage& Message); + + // Settings + CGUISpriteInstance m_SpriteBackground; + CGUISpriteInstance m_SpriteBar; + float m_Caption; +}; + +#endif // INCLUDED_CPROGRESSBAR Index: ps/trunk/source/gui/ObjectTypes/CProgressBar.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CProgressBar.cpp +++ ps/trunk/source/gui/ObjectTypes/CProgressBar.cpp @@ -0,0 +1,73 @@ +/* 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 "CProgressBar.h" + +#include "gui/CGUI.h" + +CProgressBar::CProgressBar(CGUI& pGUI) + : IGUIObject(pGUI), + m_SpriteBackground(), + m_SpriteBar(), + m_Caption() +{ + RegisterSetting("sprite_background", m_SpriteBackground); + RegisterSetting("sprite_bar", m_SpriteBar); + RegisterSetting("caption", m_Caption); // aka value from 0 to 100 +} + +CProgressBar::~CProgressBar() +{ +} + +void CProgressBar::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + // Update scroll-bar + // TODO Gee: (2004-09-01) Is this really updated each time it should? + if (Message.value == "caption") + { + if (m_Caption > 100.f) + SetSetting("caption", 100.f, true); + else if (m_Caption < 0.f) + SetSetting("caption", 0.f, true); + } + break; + default: + break; + } +} + +void CProgressBar::Draw() +{ + float bz = GetBufferedZ(); + + int cell_id = 0; + + m_pGUI.DrawSprite(m_SpriteBackground, cell_id, bz, m_CachedActualSize); + + // Get size of bar (notice it is drawn slightly closer, to appear above the background) + CRect bar_size(m_CachedActualSize.left, m_CachedActualSize.top, + m_CachedActualSize.left+m_CachedActualSize.GetWidth()*(m_Caption/100.f), m_CachedActualSize.bottom); + m_pGUI.DrawSprite(m_SpriteBar, cell_id, bz+0.01f, bar_size); +} Index: ps/trunk/source/gui/ObjectTypes/CRadioButton.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CRadioButton.h +++ ps/trunk/source/gui/ObjectTypes/CRadioButton.h @@ -0,0 +1,42 @@ +/* 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_CRADIOBUTTON +#define INCLUDED_CRADIOBUTTON + +#include "gui/ObjectTypes/CCheckBox.h" + +/** + * Just like a check box, but it'll nullify its siblings (of the same kind), + * and it won't switch itself. + * + * @see CCheckBox + */ +class CRadioButton : public CCheckBox +{ + GUI_OBJECT(CRadioButton) + +public: + CRadioButton(CGUI& pGUI); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); +}; + +#endif // INCLUDED_CRADIOBUTTON Index: ps/trunk/source/gui/ObjectTypes/CRadioButton.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CRadioButton.cpp +++ ps/trunk/source/gui/ObjectTypes/CRadioButton.cpp @@ -0,0 +1,48 @@ +/* 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 "CRadioButton.h" + +CRadioButton::CRadioButton(CGUI& pGUI) + : CCheckBox(pGUI) +{ +} + +void CRadioButton::HandleMessage(SGUIMessage& Message) +{ + IGUIButtonBehavior::HandleMessage(Message); + + switch (Message.type) + { + case GUIM_PRESSED: + for (IGUIObject* const& obj : GetParent()->GetChildren()) + { + // Notice, if you use other objects within the parent object that has got + // the setting "checked", it too will change. Hence NO OTHER OBJECTS THAN + // RADIO BUTTONS SHOULD BE WITHIN IT! + obj->SetSetting("checked", false, true); + } + + SetSetting("checked", true, true); + break; + + default: + break; + } +} Index: ps/trunk/source/gui/ObjectTypes/CSlider.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CSlider.h +++ ps/trunk/source/gui/ObjectTypes/CSlider.h @@ -0,0 +1,69 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CSLIDER +#define INCLUDED_CSLIDER + +#include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIObject.h" + +class CSlider : public IGUIObject +{ + GUI_OBJECT(CSlider) + +public: + CSlider(CGUI& pGUI); + virtual ~CSlider(); + +protected: + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + virtual void Draw(); + + /** + * Change settings and send the script event + */ + void UpdateValue(); + + CRect GetButtonRect() const; + + /** + * @return ratio between the value of the slider and its actual size in the GUI + */ + float GetSliderRatio() const; + + void IncrementallyChangeValue(const float value); + + // Settings + float m_ButtonSide; + i32 m_CellID; + float m_MinValue; + float m_MaxValue; + CGUISpriteInstance m_Sprite; + CGUISpriteInstance m_SpriteBar; + float m_Value; + +private: + bool m_IsPressed; + CPos m_Mouse; +}; + +#endif // INCLUDED_CSLIDER Index: ps/trunk/source/gui/ObjectTypes/CSlider.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CSlider.cpp +++ ps/trunk/source/gui/ObjectTypes/CSlider.cpp @@ -0,0 +1,139 @@ +/* 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 "CSlider.h" + +#include "gui/CGUI.h" +#include "maths/MathUtil.h" + +CSlider::CSlider(CGUI& pGUI) + : IGUIObject(pGUI), + m_IsPressed(), + m_ButtonSide(), + m_CellID(), + m_MaxValue(), + m_MinValue(), + m_Sprite(), + m_SpriteBar(), + m_Value() +{ + RegisterSetting("button_width", m_ButtonSide); + RegisterSetting("cell_id", m_CellID); + RegisterSetting("max_value", m_MaxValue); + RegisterSetting("min_value", m_MinValue); + RegisterSetting("sprite", m_Sprite); + RegisterSetting("sprite_bar", m_SpriteBar); + RegisterSetting("value", m_Value); + + m_Value = Clamp(m_Value, m_MinValue, m_MaxValue); +} + +CSlider::~CSlider() +{ +} + +float CSlider::GetSliderRatio() const +{ + return (m_MaxValue - m_MinValue) / (m_CachedActualSize.GetWidth() - m_ButtonSide); +} + +void CSlider::IncrementallyChangeValue(const float difference) +{ + m_Value = Clamp(m_Value + difference, m_MinValue, m_MaxValue); + UpdateValue(); +} + +void CSlider::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + { + m_Value = Clamp(m_Value, m_MinValue, m_MaxValue); + break; + } + case GUIM_MOUSE_WHEEL_DOWN: + { + if (m_IsPressed) + break; + IncrementallyChangeValue(-0.01f); + break; + } + case GUIM_MOUSE_WHEEL_UP: + { + if (m_IsPressed) + break; + IncrementallyChangeValue(0.01f); + break; + } + case GUIM_MOUSE_PRESS_LEFT: + { + m_Mouse = m_pGUI.GetMousePos(); + m_IsPressed = true; + + IncrementallyChangeValue((m_Mouse.x - GetButtonRect().CenterPoint().x) * GetSliderRatio()); + break; + } + case GUIM_MOUSE_RELEASE_LEFT: + { + m_IsPressed = false; + break; + } + case GUIM_MOUSE_MOTION: + { + if (!IsMouseOver()) + m_IsPressed = false; + if (m_IsPressed) + { + float difference = float(m_pGUI.GetMousePos().x - m_Mouse.x) * GetSliderRatio(); + m_Mouse = m_pGUI.GetMousePos(); + IncrementallyChangeValue(difference); + } + break; + } + default: + break; + } +} + +void CSlider::Draw() +{ + CRect slider_line(m_CachedActualSize); + slider_line.left += m_ButtonSide / 2.0f; + slider_line.right -= m_ButtonSide / 2.0f; + float bz = GetBufferedZ(); + m_pGUI.DrawSprite(m_SpriteBar, m_CellID, bz, slider_line); + m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, GetButtonRect()); +} + +void CSlider::UpdateValue() +{ + SetSetting("value", m_Value, true); + ScriptEvent("valuechange"); +} + +CRect CSlider::GetButtonRect() const +{ + float ratio = m_MaxValue > m_MinValue ? (m_Value - m_MinValue) / (m_MaxValue - m_MinValue) : 0.0f; + float x = m_CachedActualSize.left + ratio * (m_CachedActualSize.GetWidth() - m_ButtonSide); + float y = m_CachedActualSize.top + (m_CachedActualSize.GetHeight() - m_ButtonSide) / 2.0; + return CRect(x, y, x + m_ButtonSide, y + m_ButtonSide); +} Index: ps/trunk/source/gui/ObjectTypes/CText.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CText.h +++ ps/trunk/source/gui/ObjectTypes/CText.h @@ -0,0 +1,102 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CTEXT +#define INCLUDED_CTEXT + +#include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIScrollBarOwner.h" +#include "gui/ObjectBases/IGUITextOwner.h" +#include "gui/SettingTypes/CGUIString.h" + +/** + * Text field that just displays static text. + */ +class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner +{ + GUI_OBJECT(CText) + +public: + CText(CGUI& pGUI); + virtual ~CText(); + + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates(); + + /** + * @see IGUIObject#UpdateCachedSize() + */ + virtual void UpdateCachedSize(); + + /** + * Test if mouse position is over an icon + */ + virtual bool MouseOverIcon(); + +protected: + /** + * Sets up text, should be called every time changes has been + * made that can change the visual. + */ + void SetupText(); + + virtual void RegisterScriptFunctions(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Draws the Text + */ + virtual void Draw(); + + /** + * Script accessors to this GUI object. + */ + static JSFunctionSpec JSI_methods[]; + + static bool GetTextSize(JSContext* cx, uint argc, JS::Value* vp); + + /** + * Placement of text. Ignored when scrollbars are active. + */ + CPos m_TextPos; + + // Settings + float m_BufferZone; + CGUIString m_Caption; + i32 m_CellID; + bool m_Clip; + CStrW m_Font; + bool m_ScrollBar; + CStr m_ScrollBarStyle; + bool m_ScrollBottom; + bool m_ScrollTop; + CGUISpriteInstance m_Sprite; + EAlign m_TextAlign; + EVAlign m_TextVAlign; + CGUIColor m_TextColor; + CGUIColor m_TextColorDisabled; + CStrW m_IconTooltip; + CStr m_IconTooltipStyle; +}; + +#endif // INCLUDED_CTEXT Index: ps/trunk/source/gui/ObjectTypes/CText.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CText.cpp +++ ps/trunk/source/gui/ObjectTypes/CText.cpp @@ -0,0 +1,284 @@ +/* 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 "CText.h" + +#include "gui/CGUI.h" +#include "gui/CGUIScrollBarVertical.h" +#include "gui/CGUIText.h" +#include "scriptinterface/ScriptInterface.h" + +CText::CText(CGUI& pGUI) + : IGUIObject(pGUI), + IGUIScrollBarOwner(*static_cast(this)), + IGUITextOwner(*static_cast(this)), + m_BufferZone(), + m_Caption(), + m_CellID(), + m_Clip(), + m_Font(), + m_ScrollBar(), + m_ScrollBarStyle(), + m_ScrollBottom(), + m_ScrollTop(), + m_Sprite(), + m_TextAlign(), + m_TextVAlign(), + m_TextColor(), + m_TextColorDisabled(), + m_IconTooltip(), + m_IconTooltipStyle() +{ + RegisterSetting("buffer_zone", m_BufferZone); + RegisterSetting("caption", m_Caption); + RegisterSetting("cell_id", m_CellID); + RegisterSetting("clip", m_Clip); + RegisterSetting("font", m_Font); + RegisterSetting("scrollbar", m_ScrollBar); + RegisterSetting("scrollbar_style", m_ScrollBarStyle); + RegisterSetting("scroll_bottom", m_ScrollBottom); + RegisterSetting("scroll_top", m_ScrollTop); + RegisterSetting("sprite", m_Sprite); + RegisterSetting("text_align", m_TextAlign); + RegisterSetting("text_valign", m_TextVAlign); + RegisterSetting("textcolor", m_TextColor); + RegisterSetting("textcolor_disabled", m_TextColorDisabled); + // Private settings + RegisterSetting("_icon_tooltip", m_IconTooltip); + RegisterSetting("_icon_tooltip_style", m_IconTooltipStyle); + + //SetSetting("ghost", true, true); + SetSetting("scrollbar", false, true); + SetSetting("clip", true, true); + + // Add scroll-bar + CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); + bar->SetRightAligned(true); + AddScrollBar(bar); + + // Add text + AddText(); +} + +CText::~CText() +{ +} + +void CText::SetupText() +{ + if (m_GeneratedTexts.empty()) + return; + + float width = m_CachedActualSize.GetWidth(); + // remove scrollbar if applicable + if (m_ScrollBar && GetScrollBar(0).GetStyle()) + width -= GetScrollBar(0).GetStyle()->m_Width; + + m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, width, m_BufferZone, this); + + if (!m_ScrollBar) + CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]); + + // Setup scrollbar + if (m_ScrollBar) + { + // If we are currently scrolled to the bottom of the text, + // then add more lines of text, update the scrollbar so we + // stick to the bottom. + // (Use 1.5px delta so this triggers the first time caption is set) + bool bottom = false; + if (m_ScrollBottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f) + bottom = true; + + GetScrollBar(0).SetScrollRange(m_GeneratedTexts[0].GetSize().cy); + GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); + + GetScrollBar(0).SetX(m_CachedActualSize.right); + GetScrollBar(0).SetY(m_CachedActualSize.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); + + if (bottom) + GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos()); + + if (m_ScrollTop) + GetScrollBar(0).SetPos(0.0f); + } +} + +void CText::ResetStates() +{ + IGUIObject::ResetStates(); + IGUIScrollBarOwner::ResetStates(); +} + +void CText::UpdateCachedSize() +{ + IGUIObject::UpdateCachedSize(); + IGUITextOwner::UpdateCachedSize(); +} + +void CText::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + IGUIScrollBarOwner::HandleMessage(Message); + //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! + + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + if (Message.value == "scrollbar") + SetupText(); + + // Update scrollbar + if (Message.value == "scrollbar_style") + { + GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); + SetupText(); + } + + break; + + case GUIM_MOUSE_WHEEL_DOWN: + { + GetScrollBar(0).ScrollPlus(); + // Since the scroll was changed, let's simulate a mouse movement + // to check if scrollbar now is hovered + SGUIMessage msg(GUIM_MOUSE_MOTION); + HandleMessage(msg); + break; + } + case GUIM_MOUSE_WHEEL_UP: + { + GetScrollBar(0).ScrollMinus(); + // Since the scroll was changed, let's simulate a mouse movement + // to check if scrollbar now is hovered + SGUIMessage msg(GUIM_MOUSE_MOTION); + HandleMessage(msg); + break; + } + case GUIM_LOAD: + { + GetScrollBar(0).SetX(m_CachedActualSize.right); + GetScrollBar(0).SetY(m_CachedActualSize.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); + GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); + break; + } + + default: + break; + } + + IGUITextOwner::HandleMessage(Message); +} + +void CText::Draw() +{ + float bz = GetBufferedZ(); + + if (m_ScrollBar) + IGUIScrollBarOwner::Draw(); + + m_pGUI.DrawSprite(m_Sprite, m_CellID, bz, m_CachedActualSize); + + float scroll = 0.f; + if (m_ScrollBar) + scroll = GetScrollBar(0).GetPos(); + + // Clipping area (we'll have to subtract the scrollbar) + CRect cliparea; + if (m_Clip) + { + cliparea = m_CachedActualSize; + + if (m_ScrollBar) + { + // subtract scrollbar from cliparea + if (cliparea.right > GetScrollBar(0).GetOuterRect().left && + cliparea.right <= GetScrollBar(0).GetOuterRect().right) + cliparea.right = GetScrollBar(0).GetOuterRect().left; + + if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && + cliparea.left < GetScrollBar(0).GetOuterRect().right) + cliparea.left = GetScrollBar(0).GetOuterRect().right; + } + } + + const CGUIColor& color = m_Enabled ? m_TextColor : m_TextColorDisabled; + + if (m_ScrollBar) + DrawText(0, color, m_CachedActualSize.TopLeft() - CPos(0.f, scroll), bz + 0.1f, cliparea); + else + DrawText(0, color, m_TextPos, bz + 0.1f, cliparea); +} + +bool CText::MouseOverIcon() +{ + for (const CGUIText& guitext : m_GeneratedTexts) + for (const CGUIText::SSpriteCall& spritecall : guitext.GetSpriteCalls()) + { + // Check mouse over sprite + if (!spritecall.m_Area.PointInside(m_pGUI.GetMousePos() - m_CachedActualSize.TopLeft())) + continue; + + // If tooltip exists, set the property + if (!spritecall.m_Tooltip.empty()) + { + SetSettingFromString("_icon_tooltip_style", spritecall.m_TooltipStyle, true); + SetSettingFromString("_icon_tooltip", spritecall.m_Tooltip, true); + } + + return true; + } + + return false; +} + +void CText::RegisterScriptFunctions() +{ + JSContext* cx = m_pGUI.GetScriptInterface()->GetContext(); + JSAutoRequest rq(cx); + JS_DefineFunctions(cx, m_JSObject, CText::JSI_methods); +} + +JSFunctionSpec CText::JSI_methods[] = +{ + JS_FN("getTextSize", CText::GetTextSize, 0, 0), + JS_FS_END +}; + +bool CText::GetTextSize(JSContext* cx, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + CText* thisObj = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); + if (!thisObj) + { + JSAutoRequest rq(cx); + JS_ReportError(cx, "This is not a CText object!"); + return false; + } + + thisObj->UpdateText(); + + ScriptInterface::ToJSVal(cx, args.rval(), thisObj->m_GeneratedTexts[0].GetSize()); + return true; +} Index: ps/trunk/source/gui/ObjectTypes/CTooltip.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CTooltip.h +++ ps/trunk/source/gui/ObjectTypes/CTooltip.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_CTOOLTIP +#define INCLUDED_CTOOLTIP + +#include "gui/ObjectBases/IGUITextOwner.h" +#include "gui/CGUISprite.h" +#include "gui/SettingTypes/CGUIString.h" + +/** + * Dynamic tooltips. Similar to CText. + */ +class CTooltip : public IGUIObject, public IGUITextOwner +{ + GUI_OBJECT(CTooltip) + +public: + CTooltip(CGUI& pGUI); + virtual ~CTooltip(); + +protected: + void SetupText(); + + /** + * @see IGUIObject#UpdateCachedSize() + */ + void UpdateCachedSize(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + virtual void Draw(); + + // Settings + float m_BufferZone; + CGUIString m_Caption; + CStrW m_Font; + CGUISpriteInstance m_Sprite; + i32 m_Delay; + CGUIColor m_TextColor; + float m_MaxWidth; + CPos m_Offset; + EVAlign m_Anchor; + EAlign m_TextAlign; + bool m_Independent; + CPos m_MousePos; + CStr m_UseObject; + bool m_HideObject; +}; + +#endif // INCLUDED_CTOOLTIP Index: ps/trunk/source/gui/ObjectTypes/CTooltip.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CTooltip.cpp +++ ps/trunk/source/gui/ObjectTypes/CTooltip.cpp @@ -0,0 +1,163 @@ +/* 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 "CTooltip.h" + +#include "gui/CGUI.h" +#include "gui/SettingTypes/CGUIString.h" +#include "gui/CGUIText.h" + +#include + +CTooltip::CTooltip(CGUI& pGUI) + : IGUIObject(pGUI), + IGUITextOwner(*static_cast(this)), + m_BufferZone(), + m_Caption(), + m_Font(), + m_Sprite(), + m_Delay(), + m_TextColor(), + m_MaxWidth(), + m_Offset(), + m_Anchor(), + m_TextAlign(), + m_Independent(), + m_MousePos(), + m_UseObject(), + m_HideObject() +{ + // If the tooltip is an object by itself: + RegisterSetting("buffer_zone", m_BufferZone); + RegisterSetting("caption", m_Caption); + RegisterSetting("font", m_Font); + RegisterSetting("sprite", m_Sprite); + RegisterSetting("delay", m_Delay); // in milliseconds + RegisterSetting("textcolor", m_TextColor); + RegisterSetting("maxwidth", m_MaxWidth); + RegisterSetting("offset", m_Offset); + RegisterSetting("anchor", m_Anchor); + RegisterSetting("text_align", m_TextAlign); + // This is used for tooltips that are hidden/revealed manually by scripts, rather than through the standard tooltip display mechanism + RegisterSetting("independent", m_Independent); + // Private settings: + // This is set by GUITooltip + RegisterSetting("_mousepos", m_MousePos); + // If the tooltip is just a reference to another object: + RegisterSetting("use_object", m_UseObject); + RegisterSetting("hide_object", m_HideObject); + + // Defaults + SetSetting("delay", 500, true); + SetSetting("anchor", EVAlign_Bottom, true); + SetSetting("text_align", EAlign_Left, true); + + // Set up a blank piece of text, to be replaced with a more + // interesting message later + AddText(); +} + +CTooltip::~CTooltip() +{ +} + +void CTooltip::SetupText() +{ + ENSURE(m_GeneratedTexts.size() == 1); + + m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, m_MaxWidth, m_BufferZone, this); + + // Position the tooltip relative to the mouse: + + const CPos& mousepos = m_Independent ? m_pGUI.GetMousePos() : m_MousePos; + + float textwidth = m_GeneratedTexts[0].GetSize().cx; + float textheight = m_GeneratedTexts[0].GetSize().cy; + + CGUISize size; + size.pixel.left = mousepos.x + m_Offset.x; + size.pixel.right = size.pixel.left + textwidth; + + switch (m_Anchor) + { + case EVAlign_Top: + size.pixel.top = mousepos.y + m_Offset.y; + size.pixel.bottom = size.pixel.top + textheight; + break; + case EVAlign_Bottom: + size.pixel.bottom = mousepos.y + m_Offset.y; + size.pixel.top = size.pixel.bottom - textheight; + break; + case EVAlign_Center: + size.pixel.top = mousepos.y + m_Offset.y - textheight/2.f; + size.pixel.bottom = size.pixel.top + textwidth; + break; + default: + debug_warn(L"Invalid EVAlign!"); + } + + + // Reposition the tooltip if it's falling off the screen: + + extern int g_xres, g_yres; + extern float g_GuiScale; + float screenw = g_xres / g_GuiScale; + float screenh = g_yres / g_GuiScale; + + if (size.pixel.top < 0.f) + size.pixel.bottom -= size.pixel.top, size.pixel.top = 0.f; + else if (size.pixel.bottom > screenh) + size.pixel.top -= (size.pixel.bottom-screenh), size.pixel.bottom = screenh; + + if (size.pixel.left < 0.f) + size.pixel.right -= size.pixel.left, size.pixel.left = 0.f; + else if (size.pixel.right > screenw) + size.pixel.left -= (size.pixel.right-screenw), size.pixel.right = screenw; + + SetSetting("size", size, true); +} + +void CTooltip::UpdateCachedSize() +{ + IGUIObject::UpdateCachedSize(); + IGUITextOwner::UpdateCachedSize(); +} + +void CTooltip::HandleMessage(SGUIMessage& Message) +{ + IGUIObject::HandleMessage(Message); + IGUITextOwner::HandleMessage(Message); +} + +void CTooltip::Draw() +{ + float z = 900.f; // TODO: Find a nicer way of putting the tooltip on top of everything else + + // Normally IGUITextOwner will handle this updating but since SetupText can modify the position + // we need to call it now *before* we do the rest of the drawing + if (!m_GeneratedTextsValid) + { + SetupText(); + m_GeneratedTextsValid = true; + } + + m_pGUI.DrawSprite(m_Sprite, 0, z, m_CachedActualSize); + + DrawText(0, m_TextColor, m_CachedActualSize.TopLeft(), z + 0.1f); +} Index: ps/trunk/source/gui/Scripting/GuiScriptConversions.cpp =================================================================== --- ps/trunk/source/gui/Scripting/GuiScriptConversions.cpp +++ ps/trunk/source/gui/Scripting/GuiScriptConversions.cpp @@ -0,0 +1,369 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "gui/ObjectBases/IGUIObject.h" +#include "gui/SettingTypes/CGUIColor.h" +#include "gui/SettingTypes/CGUIList.h" +#include "gui/SettingTypes/CGUISeries.h" +#include "gui/SettingTypes/CGUISize.h" +#include "lib/external_libraries/libsdl.h" +#include "maths/Vector2D.h" +#include "ps/Hotkey.h" +#include "scriptinterface/ScriptConversions.h" + +#include + +#define SET(obj, name, value) STMT(JS::RootedValue v_(cx); AssignOrToJSVal(cx, &v_, (value)); JS_SetProperty(cx, obj, (name), v_)) + // ignore JS_SetProperty return value, because errors should be impossible + // and we can't do anything useful in the case of errors anyway + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, SDL_Event_ const& val) +{ + JSAutoRequest rq(cx); + const char* typeName; + + switch (val.ev.type) + { + case SDL_WINDOWEVENT: typeName = "windowevent"; break; + case SDL_KEYDOWN: typeName = "keydown"; break; + case SDL_KEYUP: typeName = "keyup"; break; + case SDL_MOUSEMOTION: typeName = "mousemotion"; break; + case SDL_MOUSEBUTTONDOWN: typeName = "mousebuttondown"; break; + case SDL_MOUSEBUTTONUP: typeName = "mousebuttonup"; break; + case SDL_QUIT: typeName = "quit"; break; + case SDL_HOTKEYDOWN: typeName = "hotkeydown"; break; + case SDL_HOTKEYUP: typeName = "hotkeyup"; break; + default: typeName = "(unknown)"; break; + } + + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) + { + ret.setUndefined(); + return; + } + + SET(obj, "type", typeName); + + switch (val.ev.type) + { + case SDL_KEYDOWN: + case SDL_KEYUP: + { + // SET(obj, "which", (int)val.ev.key.which); // (not in wsdl.h) + // SET(obj, "state", (int)val.ev.key.state); // (not in wsdl.h) + + JS::RootedObject keysym(cx, JS_NewPlainObject(cx)); + if (!keysym) + { + ret.setUndefined(); + return; + } + JS::RootedValue keysymVal(cx, JS::ObjectValue(*keysym)); + JS_SetProperty(cx, obj, "keysym", keysymVal); + + // SET(keysym, "scancode", (int)val.ev.key.keysym.scancode); // (not in wsdl.h) + SET(keysym, "sym", (int)val.ev.key.keysym.sym); + // SET(keysym, "mod", (int)val.ev.key.keysym.mod); // (not in wsdl.h) + { + SET(keysym, "unicode", JS::UndefinedHandleValue); + } + // TODO: scripts have no idea what all the key/mod enum values are; + // we should probably expose them as constants if we expect scripts to use them + + break; + } + case SDL_MOUSEMOTION: + { + // SET(obj, "which", (int)val.ev.motion.which); // (not in wsdl.h) + // SET(obj, "state", (int)val.ev.motion.state); // (not in wsdl.h) + SET(obj, "x", (int)val.ev.motion.x); + SET(obj, "y", (int)val.ev.motion.y); + // SET(obj, "xrel", (int)val.ev.motion.xrel); // (not in wsdl.h) + // SET(obj, "yrel", (int)val.ev.motion.yrel); // (not in wsdl.h) + break; + } + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + // SET(obj, "which", (int)val.ev.button.which); // (not in wsdl.h) + SET(obj, "button", (int)val.ev.button.button); + SET(obj, "state", (int)val.ev.button.state); + SET(obj, "x", (int)val.ev.button.x); + SET(obj, "y", (int)val.ev.button.y); + SET(obj, "clicks", (int)val.ev.button.clicks); + break; + } + case SDL_HOTKEYDOWN: + case SDL_HOTKEYUP: + { + SET(obj, "hotkey", static_cast(val.ev.user.data1)); + break; + } + } + + ret.setObject(*obj); +} + +template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, IGUIObject* const& val) +{ + if (val == nullptr) + ret.setNull(); + else + ret.setObject(*val->GetJSObject()); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIString& val) +{ + ScriptInterface::ToJSVal(cx, ret, val.GetOriginalString()); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIString& out) +{ + std::wstring val; + if (!FromJSVal(cx, v, val)) + return false; + out.SetValue(val); + return true; +} + +JSVAL_VECTOR(CVector2D) +JSVAL_VECTOR(std::vector) +JSVAL_VECTOR(CGUIString) + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIColor& val) +{ + ToJSVal(cx, ret, val); +} + +/** + * The color depends on the predefined color database stored in the current GUI page. + */ +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIColor& out) = delete; + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CSize& val) +{ + CreateObject(cx, ret, "width", val.cx, "height", val.cy); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CSize& out) +{ + if (!v.isObject()) + { + JSAutoRequest rq(cx); + JS_ReportError(cx, "CSize value must be an object!"); + return false; + } + + if (!FromJSProperty(cx, v, "width", out.cx)) + { + JSAutoRequest rq(cx); + JS_ReportError(cx, "Failed to get CSize.cx property"); + return false; + } + + if (!FromJSProperty(cx, v, "height", out.cy)) + { + JSAutoRequest rq(cx); + JS_ReportError(cx, "Failed to get CSize.cy property"); + return false; + } + + return true; +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CPos& val) +{ + CreateObject(cx, ret, "x", val.x, "y", val.y); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CPos& out) +{ + if (!v.isObject()) + { + JSAutoRequest rq(cx); + JS_ReportError(cx, "CPos value must be an object!"); + return false; + } + + if (!FromJSProperty(cx, v, "x", out.x)) + { + JSAutoRequest rq(cx); + JS_ReportError(cx, "Failed to get CPos.x property"); + return false; + } + + if (!FromJSProperty(cx, v, "y", out.y)) + { + JSAutoRequest rq(cx); + JS_ReportError(cx, "Failed to get CPos.y property"); + return false; + } + + return true; +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CRect& val) +{ + CreateObject( + cx, + ret, + "left", val.left, + "right", val.right, + "top", val.top, + "bottom", val.bottom); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISize& val) +{ + val.ToJSVal(cx, ret); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISize& out) +{ + return out.FromJSVal(cx, v); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIList& val) +{ + ToJSVal(cx, ret, val.m_Items); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIList& out) +{ + return FromJSVal(cx, v, out.m_Items); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISeries& val) +{ + ToJSVal(cx, ret, val.m_Series); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISeries& out) +{ + return FromJSVal(cx, v, out.m_Series); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const EVAlign& val) +{ + std::string word; + switch (val) + { + case EVAlign_Top: + word = "top"; + break; + + case EVAlign_Bottom: + word = "bottom"; + break; + + case EVAlign_Center: + word = "center"; + break; + + default: + word = "error"; + JSAutoRequest rq(cx); + JS_ReportError(cx, "Invalid EVAlign"); + break; + } + ToJSVal(cx, ret, word); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, EVAlign& out) +{ + std::string word; + FromJSVal(cx, v, word); + + if (word == "top") + out = EVAlign_Top; + else if (word == "bottom") + out = EVAlign_Bottom; + else if (word == "center") + out = EVAlign_Center; + else + { + out = EVAlign_Top; + JSAutoRequest rq(cx); + JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')"); + return false; + } + return true; +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const EAlign& val) +{ + std::string word; + switch (val) + { + case EAlign_Left: + word = "left"; + break; + case EAlign_Right: + word = "right"; + break; + case EAlign_Center: + word = "center"; + break; + default: + word = "error"; + JSAutoRequest rq(cx); + JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')"); + break; + } + ToJSVal(cx, ret, word); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, EAlign& out) +{ + std::string word; + FromJSVal(cx, v, word); + + if (word == "left") + out = EAlign_Left; + else if (word == "right") + out = EAlign_Right; + else if (word == "center") + out = EAlign_Center; + else + { + out = EAlign_Left; + JSAutoRequest rq(cx); + JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')"); + return false; + } + return true; +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISpriteInstance& val) +{ + ToJSVal(cx, ret, val.GetName()); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISpriteInstance& out) +{ + std::string name; + if (!FromJSVal(cx, v, name)) + return false; + + out.SetName(name); + return true; +} + +#undef SET Index: ps/trunk/source/gui/Scripting/JSInterface_GUIManager.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUIManager.h +++ ps/trunk/source/gui/Scripting/JSInterface_GUIManager.h @@ -0,0 +1,40 @@ +/* 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_JSI_GUIMANAGER +#define INCLUDED_JSI_GUIMANAGER + +#include "scriptinterface/ScriptInterface.h" +#include "simulation2/system/ParamNode.h" + +namespace JSI_GUIManager +{ + void PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData, JS::HandleValue callbackFunction); + void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData); + void PopGuiPage(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue args); + JS::Value GetGUIObjectByName(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); + void SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, JS::HandleValue function); + void UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag); + std::wstring SetCursor(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name); + void ResetCursor(ScriptInterface::CxPrivate* pCxPrivate); + bool TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); + CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); + + void RegisterScriptFunctions(const ScriptInterface& scriptInterface); +} + +#endif // INCLUDED_JSI_GUIMANAGER Index: ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp +++ ps/trunk/source/gui/Scripting/JSInterface_GUIManager.cpp @@ -0,0 +1,110 @@ +/* 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 "JSInterface_GUIManager.h" + +#include "gui/CGUI.h" +#include "gui/GUIManager.h" +#include "gui/ObjectBases/IGUIObject.h" +#include "ps/GameSetup/Config.h" +#include "scriptinterface/ScriptInterface.h" + +// Note that the initData argument may only contain clonable data. +// Functions aren't supported for example! +void JSI_GUIManager::PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData, JS::HandleValue callbackFunction) +{ + g_GUI->PushPage(name, pCxPrivate->pScriptInterface->WriteStructuredClone(initData), callbackFunction); +} + +void JSI_GUIManager::SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData) +{ + g_GUI->SwitchPage(name, pCxPrivate->pScriptInterface, initData); +} + +void JSI_GUIManager::PopGuiPage(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue args) +{ + if (g_GUI->GetPageCount() < 2) + { + JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); + JSAutoRequest rq(cx); + JS_ReportError(cx, "Can't pop GUI pages when less than two pages are opened!"); + return; + } + + g_GUI->PopPage(pCxPrivate->pScriptInterface->WriteStructuredClone(args)); +} + +JS::Value JSI_GUIManager::GetGUIObjectByName(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) +{ + CGUI* guiPage = static_cast(pCxPrivate->pCBData); + + IGUIObject* guiObj = guiPage->FindObjectByName(name); + if (!guiObj) + return JS::UndefinedValue(); + + return JS::ObjectValue(*guiObj->GetJSObject()); +} + +void JSI_GUIManager::SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, JS::HandleValue function) +{ + CGUI* guiPage = static_cast(pCxPrivate->pCBData); + guiPage->SetGlobalHotkey(hotkeyTag, function); +} + +void JSI_GUIManager::UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag) +{ + CGUI* guiPage = static_cast(pCxPrivate->pCBData); + guiPage->UnsetGlobalHotkey(hotkeyTag); +} + +std::wstring JSI_GUIManager::SetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name) +{ + std::wstring old = g_CursorName; + g_CursorName = name; + return old; +} + +void JSI_GUIManager::ResetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) +{ + g_GUI->ResetCursor(); +} + +bool JSI_GUIManager::TemplateExists(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName) +{ + return g_GUI->TemplateExists(templateName); +} + +CParamNode JSI_GUIManager::GetTemplate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName) +{ + return g_GUI->GetTemplate(templateName); +} + +void JSI_GUIManager::RegisterScriptFunctions(const ScriptInterface& scriptInterface) +{ + scriptInterface.RegisterFunction("PushGuiPage"); + scriptInterface.RegisterFunction("SwitchGuiPage"); + scriptInterface.RegisterFunction("SetGlobalHotkey"); + scriptInterface.RegisterFunction("UnsetGlobalHotkey"); + scriptInterface.RegisterFunction("PopGuiPage"); + scriptInterface.RegisterFunction("GetGUIObjectByName"); + scriptInterface.RegisterFunction("SetCursor"); + scriptInterface.RegisterFunction("ResetCursor"); + scriptInterface.RegisterFunction("TemplateExists"); + scriptInterface.RegisterFunction("GetTemplate"); +} Index: ps/trunk/source/gui/Scripting/JSInterface_GUISize.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUISize.h +++ ps/trunk/source/gui/Scripting/JSInterface_GUISize.h @@ -0,0 +1,38 @@ +/* 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_JSI_GUISIZE +#define INCLUDED_JSI_GUISIZE + +#include "scriptinterface/ScriptInterface.h" +#include "ps/CStr.h" + +namespace JSI_GUISize +{ + extern JSClass JSI_class; + extern JSPropertySpec JSI_props[]; + extern JSFunctionSpec JSI_methods[]; + + void RegisterScriptClass(ScriptInterface& scriptInterface); + + bool construct(JSContext* cx, uint argc, JS::Value* vp); + bool toString(JSContext* cx, uint argc, JS::Value* vp); + + CStr ToPercentString(double pix, double per); +} + +#endif // INCLUDED_JSI_GUISIZE Index: ps/trunk/source/gui/Scripting/JSInterface_GUISize.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUISize.cpp +++ ps/trunk/source/gui/Scripting/JSInterface_GUISize.cpp @@ -0,0 +1,125 @@ +/* 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 "JSInterface_GUISize.h" + +#include "ps/CStr.h" +#include "scriptinterface/ScriptInterface.h" + +JSClass JSI_GUISize::JSI_class = { + "GUISize", 0, + nullptr, nullptr, + nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, JSI_GUISize::construct, nullptr +}; + +JSFunctionSpec JSI_GUISize::JSI_methods[] = +{ + JS_FN("toString", JSI_GUISize::toString, 0, 0), + JS_FS_END +}; + +void JSI_GUISize::RegisterScriptClass(ScriptInterface& scriptInterface) +{ + scriptInterface.DefineCustomObjectType(&JSI_GUISize::JSI_class, JSI_GUISize::construct, 0, nullptr, JSI_GUISize::JSI_methods, nullptr, nullptr); +} + +bool JSI_GUISize::construct(JSContext* cx, uint argc, JS::Value* vp) +{ + JSAutoRequest rq(cx); + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; + JS::RootedObject obj(cx, pScriptInterface->CreateCustomObject("GUISize")); + + if (args.length() == 8) + { + JS_SetProperty(cx, obj, "left", args[0]); + JS_SetProperty(cx, obj, "top", args[1]); + JS_SetProperty(cx, obj, "right", args[2]); + JS_SetProperty(cx, obj, "bottom", args[3]); + JS_SetProperty(cx, obj, "rleft", args[4]); + JS_SetProperty(cx, obj, "rtop", args[5]); + JS_SetProperty(cx, obj, "rright", args[6]); + JS_SetProperty(cx, obj, "rbottom", args[7]); + } + else if (args.length() == 4) + { + JS::RootedValue zero(cx, JS::NumberValue(0)); + JS_SetProperty(cx, obj, "left", args[0]); + JS_SetProperty(cx, obj, "top", args[1]); + JS_SetProperty(cx, obj, "right", args[2]); + JS_SetProperty(cx, obj, "bottom", args[3]); + JS_SetProperty(cx, obj, "rleft", zero); + JS_SetProperty(cx, obj, "rtop", zero); + JS_SetProperty(cx, obj, "rright", zero); + JS_SetProperty(cx, obj, "rbottom", zero); + } + else + { + JS::RootedValue zero(cx, JS::NumberValue(0)); + JS_SetProperty(cx, obj, "left", zero); + JS_SetProperty(cx, obj, "top", zero); + JS_SetProperty(cx, obj, "right", zero); + JS_SetProperty(cx, obj, "bottom", zero); + JS_SetProperty(cx, obj, "rleft", zero); + JS_SetProperty(cx, obj, "rtop", zero); + JS_SetProperty(cx, obj, "rright", zero); + JS_SetProperty(cx, obj, "rbottom", zero); + } + + args.rval().setObject(*obj); + return true; +} + +// Produces "10", "-10", "50%", "50%-10", "50%+10", etc +CStr JSI_GUISize::ToPercentString(double pix, double per) +{ + if (per == 0) + return CStr::FromDouble(pix); + + return CStr::FromDouble(per)+"%"+(pix == 0.0 ? CStr() : pix > 0.0 ? CStr("+")+CStr::FromDouble(pix) : CStr::FromDouble(pix)); +} + +bool JSI_GUISize::toString(JSContext* cx, uint argc, JS::Value* vp) +{ + // JSAutoRequest not needed for the calls below + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + CStr buffer; + + ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; + double val, valr; + +#define SIDE(side) \ + pScriptInterface->GetProperty(args.thisv(), #side, val); \ + pScriptInterface->GetProperty(args.thisv(), "r"#side, valr); \ + buffer += ToPercentString(val, valr); + + SIDE(left); + buffer += " "; + SIDE(top); + buffer += " "; + SIDE(right); + buffer += " "; + SIDE(bottom); +#undef SIDE + + ScriptInterface::ToJSVal(cx, args.rval(), buffer); + return true; +} Index: ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.h +++ ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.h @@ -0,0 +1,39 @@ +/* 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_JSI_IGUIOBJECT +#define INCLUDED_JSI_IGUIOBJECT + +#include "scriptinterface/ScriptInterface.h" + +namespace JSI_IGUIObject +{ + extern JSClass JSI_class; + extern JSFunctionSpec JSI_methods[]; + + void RegisterScriptClass(ScriptInterface& scriptInterface); + + bool getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); + bool setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); + bool toString(JSContext* cx, uint argc, JS::Value* vp); + bool focus(JSContext* cx, uint argc, JS::Value* vp); + bool blur(JSContext* cx, uint argc, JS::Value* vp); + bool getComputedSize(JSContext* cx, uint argc, JS::Value* vp); + bool getTextSize(JSContext* cx, uint argc, JS::Value* vp); +} + +#endif // INCLUDED_JSI_IGUIOBJECT Index: ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp +++ ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp @@ -0,0 +1,228 @@ +/* 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 "JSInterface_IGUIObject.h" + +#include "gui/CGUI.h" +#include "gui/CGUISetting.h" +#include "gui/ObjectBases/IGUIObject.h" +#include "scriptinterface/ScriptExtraHeaders.h" +#include "scriptinterface/ScriptInterface.h" + +JSClass JSI_IGUIObject::JSI_class = { + "GUIObject", JSCLASS_HAS_PRIVATE, + nullptr, nullptr, + JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr +}; + +JSFunctionSpec JSI_IGUIObject::JSI_methods[] = +{ + JS_FN("toString", JSI_IGUIObject::toString, 0, 0), + JS_FN("focus", JSI_IGUIObject::focus, 0, 0), + JS_FN("blur", JSI_IGUIObject::blur, 0, 0), + JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), + JS_FS_END +}; + +void JSI_IGUIObject::RegisterScriptClass(ScriptInterface& scriptInterface) +{ + scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 0, nullptr, JSI_methods, nullptr, nullptr); +} + +bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) +{ + JSAutoRequest rq(cx); + ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; + + IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); + if (!e) + return false; + + JS::RootedValue idval(cx); + if (!JS_IdToValue(cx, id, &idval)) + return false; + + std::string propName; + if (!ScriptInterface::FromJSVal(cx, idval, propName)) + return false; + + // Skip registered functions and inherited properties + // including JSInterfaces of derived classes + if (propName == "constructor" || + propName == "prototype" || + propName == "toString" || + propName == "toJSON" || + propName == "focus" || + propName == "blur" || + propName == "getTextSize" || + propName == "getComputedSize" + ) + return true; + + // Use onWhatever to access event handlers + if (propName.substr(0, 2) == "on") + { + CStr eventName(CStr(propName.substr(2)).LowerCase()); + std::map>::iterator it = e->m_ScriptHandlers.find(eventName); + if (it == e->m_ScriptHandlers.end()) + vp.setNull(); + else + vp.setObject(*it->second.get()); + return true; + } + + if (propName == "parent") + { + IGUIObject* parent = e->GetParent(); + + if (parent) + vp.set(JS::ObjectValue(*parent->GetJSObject())); + else + vp.set(JS::NullValue()); + + return true; + } + else if (propName == "children") + { + ScriptInterface::CreateArray(cx, vp); + + for (size_t i = 0; i < e->m_Children.size(); ++i) + pScriptInterface->SetPropertyInt(vp, i, e->m_Children[i]); + + return true; + } + else if (propName == "name") + { + ScriptInterface::ToJSVal(cx, vp, e->GetName()); + return true; + } + else if (e->SettingExists(propName)) + { + e->m_Settings[propName]->ToJSVal(cx, vp); + return true; + } + + JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); + return false; +} + +bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) +{ + IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); + if (!e) + return result.fail(JSMSG_NOT_NONNULL_OBJECT); + + JSAutoRequest rq(cx); + JS::RootedValue idval(cx); + if (!JS_IdToValue(cx, id, &idval)) + return result.fail(JSMSG_NOT_NONNULL_OBJECT); + + std::string propName; + if (!ScriptInterface::FromJSVal(cx, idval, propName)) + return result.fail(JSMSG_UNDEFINED_PROP); + + if (propName == "name") + { + std::string value; + if (!ScriptInterface::FromJSVal(cx, vp, value)) + return result.fail(JSMSG_UNDEFINED_PROP); + e->SetName(value); + return result.succeed(); + } + + JS::RootedObject vpObj(cx); + if (vp.isObject()) + vpObj = &vp.toObject(); + + // Use onWhatever to set event handlers + if (propName.substr(0, 2) == "on") + { + if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject())) + { + JS_ReportError(cx, "on- event-handlers must be functions"); + return result.fail(JSMSG_NOT_FUNCTION); + } + + CStr eventName(CStr(propName.substr(2)).LowerCase()); + e->SetScriptHandler(eventName, vpObj); + + return result.succeed(); + } + + if (e->SettingExists(propName)) + return e->m_Settings[propName]->FromJSVal(cx, vp, true) ? result.succeed() : result.fail(JSMSG_TYPE_ERR_BAD_ARGS); + + JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); + return result.fail(JSMSG_UNDEFINED_PROP); +} + +bool JSI_IGUIObject::toString(JSContext* cx, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); + if (!e) + return false; + + ScriptInterface::ToJSVal(cx, args.rval(), "[GUIObject: " + e->GetName() + "]"); + return true; +} + +bool JSI_IGUIObject::focus(JSContext* cx, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); + if (!e) + return false; + + e->GetGUI().SetFocusedObject(e); + args.rval().setUndefined(); + return true; +} + +bool JSI_IGUIObject::blur(JSContext* cx, uint argc, JS::Value* vp) +{ + // No JSAutoRequest needed for these calls + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); + if (!e) + return false; + + e->GetGUI().SetFocusedObject(nullptr); + args.rval().setUndefined(); + return true; +} + +bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp) +{ + JSAutoRequest rq(cx); + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); + if (!e) + return false; + + e->UpdateCachedSize(); + ScriptInterface::ToJSVal(cx, args.rval(), e->m_CachedActualSize); + + return true; +} Index: ps/trunk/source/gui/Scripting/ScriptFunctions.h =================================================================== --- ps/trunk/source/gui/Scripting/ScriptFunctions.h +++ ps/trunk/source/gui/Scripting/ScriptFunctions.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2010 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_GUI_SCRIPTFUNCTIONS +#define INCLUDED_GUI_SCRIPTFUNCTIONS + +class ScriptInterface; + +void GuiScriptingInit(ScriptInterface& scriptInterface); + +#endif // INCLUDED_GUI_SCRIPTFUNCTIONS Index: ps/trunk/source/gui/Scripting/ScriptFunctions.cpp =================================================================== --- ps/trunk/source/gui/Scripting/ScriptFunctions.cpp +++ ps/trunk/source/gui/Scripting/ScriptFunctions.cpp @@ -0,0 +1,75 @@ +/* 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 "ScriptFunctions.h" + +#include "graphics/scripting/JSInterface_GameView.h" +#include "gui/Scripting/JSInterface_GUIManager.h" +#include "gui/Scripting/JSInterface_GUISize.h" +#include "gui/Scripting/JSInterface_IGUIObject.h" +#include "i18n/scripting/JSInterface_L10n.h" +#include "lobby/scripting/JSInterface_Lobby.h" +#include "network/scripting/JSInterface_Network.h" +#include "ps/scripting/JSInterface_ConfigDB.h" +#include "ps/scripting/JSInterface_Console.h" +#include "ps/scripting/JSInterface_Debug.h" +#include "ps/scripting/JSInterface_Game.h" +#include "ps/scripting/JSInterface_Main.h" +#include "ps/scripting/JSInterface_Mod.h" +#include "ps/scripting/JSInterface_ModIo.h" +#include "ps/scripting/JSInterface_SavedGame.h" +#include "ps/scripting/JSInterface_UserReport.h" +#include "ps/scripting/JSInterface_VFS.h" +#include "ps/scripting/JSInterface_VisualReplay.h" +#include "renderer/scripting/JSInterface_Renderer.h" +#include "scriptinterface/ScriptInterface.h" +#include "simulation2/scripting/JSInterface_Simulation.h" +#include "soundmanager/scripting/JSInterface_Sound.h" + +/* + * This file defines a set of functions that are available to GUI scripts, to allow + * interaction with the rest of the engine. + * Functions are exposed to scripts within the global object 'Engine', so + * scripts should call "Engine.FunctionName(...)" etc. + */ +void GuiScriptingInit(ScriptInterface& scriptInterface) +{ + JSI_GUISize::RegisterScriptClass(scriptInterface); + JSI_IGUIObject::RegisterScriptClass(scriptInterface); + + JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); + JSI_Console::RegisterScriptFunctions(scriptInterface); + JSI_Debug::RegisterScriptFunctions(scriptInterface); + JSI_GUIManager::RegisterScriptFunctions(scriptInterface); + JSI_Game::RegisterScriptFunctions(scriptInterface); + JSI_GameView::RegisterScriptFunctions(scriptInterface); + JSI_L10n::RegisterScriptFunctions(scriptInterface); + JSI_Lobby::RegisterScriptFunctions(scriptInterface); + JSI_Main::RegisterScriptFunctions(scriptInterface); + JSI_Mod::RegisterScriptFunctions(scriptInterface); + JSI_ModIo::RegisterScriptFunctions(scriptInterface); + JSI_Network::RegisterScriptFunctions(scriptInterface); + JSI_Renderer::RegisterScriptFunctions(scriptInterface); + JSI_SavedGame::RegisterScriptFunctions(scriptInterface); + JSI_Simulation::RegisterScriptFunctions(scriptInterface); + JSI_Sound::RegisterScriptFunctions(scriptInterface); + JSI_UserReport::RegisterScriptFunctions(scriptInterface); + JSI_VFS::RegisterScriptFunctions_GUI(scriptInterface); + JSI_VisualReplay::RegisterScriptFunctions(scriptInterface); +} Index: ps/trunk/source/gui/SettingTypes/CGUIColor.h =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUIColor.h +++ ps/trunk/source/gui/SettingTypes/CGUIColor.h @@ -0,0 +1,61 @@ +/* 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_GUICOLOR +#define INCLUDED_GUICOLOR + +#include "graphics/Color.h" +#include "ps/CStr.h" + +class CGUI; + +/** + * Same as the CColor class, but this one can also parse colors predefined in the GUI page (such as "yellow"). + */ +struct CGUIColor : CColor +{ + // Take advantage of compiler warnings if unintentionally copying this + NONCOPYABLE(CGUIColor); + + // Defines move semantics so that the structs using the class can use it. + MOVABLE(CGUIColor); + + CGUIColor() : CColor() {} + + CGUIColor(float r, float g, float b, float a) : CColor(r, g, b, a) {} + + /** + * Returns this color if it has been set, otherwise the given fallback color. + */ + const CGUIColor& operator||(const CGUIColor& fallback) const + { + if (*this) + return *this; + return fallback; + } + + /** + * Load color depending on current GUI page. + */ + bool ParseString(const CGUI& pGUI, const CStr& value, int defaultAlpha = 255); + + /** + * Ensure that all users check for predefined colors. + */ + bool ParseString(const CStr& value, int defaultAlpha = 255) = delete; +}; +#endif // INCLUDED_GUICOLOR Index: ps/trunk/source/gui/SettingTypes/CGUIColor.cpp =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUIColor.cpp +++ ps/trunk/source/gui/SettingTypes/CGUIColor.cpp @@ -0,0 +1,40 @@ +/* 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 "CGUIColor.h" + +#include "gui/CGUI.h" +#include "ps/CStr.h" + +bool CGUIColor::ParseString(const CGUI& pGUI, const CStr& value, int defaultAlpha) +{ + if (pGUI.HasPreDefinedColor(value)) + { + const CGUIColor& color = pGUI.GetPreDefinedColor(value); + + // Explicit copy assignment + r = color.r; + g = color.g; + b = color.b; + a = color.a; + return true; + } + + return CColor::ParseString(value, defaultAlpha); +} Index: ps/trunk/source/gui/SettingTypes/CGUIList.h =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUIList.h +++ ps/trunk/source/gui/SettingTypes/CGUIList.h @@ -0,0 +1,43 @@ +/* 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_CGUILIST +#define INCLUDED_CGUILIST + +#include "gui/SettingTypes/CGUIString.h" + +#include + +class CGUIList +{ +public: // struct:ish (but for consistency I call it _C_GUIList, and + // for the same reason it is a class, so that confusion doesn't + // appear when forward declaring. + + // Avoid copying the vector. + NONCOPYABLE(CGUIList); + MOVABLE(CGUIList); + CGUIList() = default; + + /** + * List of items (as text), the post-processed result is stored in + * the IGUITextOwner structure of this class. + */ + std::vector m_Items; +}; + +#endif Index: ps/trunk/source/gui/SettingTypes/CGUISeries.h =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUISeries.h +++ ps/trunk/source/gui/SettingTypes/CGUISeries.h @@ -0,0 +1,36 @@ +/* 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_CGUISERIES +#define INCLUDED_CGUISERIES + +#include "maths/Vector2D.h" + +#include + +class CGUISeries +{ +public: + NONCOPYABLE(CGUISeries); + MOVABLE(CGUISeries); + CGUISeries() = default; + + std::vector> m_Series; +}; + +#endif // INCLUDED_CGUISERIES Index: ps/trunk/source/gui/SettingTypes/CGUISize.h =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUISize.h +++ ps/trunk/source/gui/SettingTypes/CGUISize.h @@ -0,0 +1,83 @@ +/* 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_CGUISIZE +#define INCLUDED_CGUISIZE + +#include "ps/CStr.h" +#include "ps/Errors.h" +#include "ps/Shapes.h" +#include "scriptinterface/ScriptInterface.h" + +/** + * This class represents a rectangle relative to a parent rectangle + * The value can be initialized from a string or JS object. + */ +class CGUISize +{ +public: + // COPYABLE, since there are only primitives involved, making move and copy identical, + // and since some temporaries cannot be avoided. + CGUISize(); + CGUISize(const CRect& pixel, const CRect& percent); + + static CGUISize Full(); + + /// Pixel modifiers + CRect pixel; + + /// Percent modifiers + CRect percent; + + /** + * Get client area rectangle when the parent is given + */ + CRect GetSize(const CRect& parent) const; + + /** + * The value can be set from a string looking like: + * + * "0 0 100% 100%" + * "50%-10 50%-10 50%+10 50%+10" + * + * i.e. First percent modifier, then + or - and the pixel modifier. + * Although you can use just the percent or the pixel modifier. Notice + * though that the percent modifier must always be the first when + * both modifiers are inputted. + * + * @return true if success, otherwise size will remain unchanged. + */ + bool FromString(const CStr& Value); + + bool operator==(const CGUISize& other) const + { + return pixel == other.pixel && percent == other.percent; + } + + void ToJSVal(JSContext* cx, JS::MutableHandleValue ret) const; + bool FromJSVal(JSContext* cx, JS::HandleValue v); +}; + + +ERROR_GROUP(GUI); + +ERROR_TYPE(GUI, InvalidSetting); +ERROR_TYPE(GUI, OperationNeedsGUIObject); +ERROR_TYPE(GUI, NameAmbiguity); +ERROR_TYPE(GUI, ObjectNeedsName); + +#endif // INCLUDED_CGUISIZE Index: ps/trunk/source/gui/SettingTypes/CGUISize.cpp =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUISize.cpp +++ ps/trunk/source/gui/SettingTypes/CGUISize.cpp @@ -0,0 +1,232 @@ +/* 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 "CGUISize.h" + +#include "gui/Scripting/JSInterface_GUISize.h" +#include "ps/CLogger.h" + +CGUISize::CGUISize() + : pixel(), percent() +{ +} + +CGUISize::CGUISize(const CRect& pixel, const CRect& percent) + : pixel(pixel), percent(percent) +{ +} + +CGUISize CGUISize::Full() +{ + return CGUISize(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100)); +} + +CRect CGUISize::GetSize(const CRect& parent) const +{ + // If it's a 0 0 100% 100% we need no calculations + if (percent == CRect(0.f, 0.f, 100.f, 100.f) && pixel == CRect()) + return parent; + + CRect client; + + // This should probably be cached and not calculated all the time for every object. + client.left = parent.left + (parent.right-parent.left)*percent.left/100.f + pixel.left; + client.top = parent.top + (parent.bottom-parent.top)*percent.top/100.f + pixel.top; + client.right = parent.left + (parent.right-parent.left)*percent.right/100.f + pixel.right; + client.bottom = parent.top + (parent.bottom-parent.top)*percent.bottom/100.f + pixel.bottom; + + return client; +} + +bool CGUISize::FromString(const CStr& Value) +{ + /* + * GUISizes contain a left, top, right, and bottom + * for example: "50%-150 10%+9 50%+150 10%+25" means + * the left edge is at 50% minus 150 pixels, the top + * edge is at 10% plus 9 pixels, the right edge is at + * 50% plus 150 pixels, and the bottom edge is at 10% + * plus 25 pixels. + * All four coordinates are required and can be + * defined only in pixels, only in percents, or some + * combination of both. + */ + + // Check the input is only numeric + const char* input = Value.c_str(); + CStr buffer = ""; + unsigned int coord = 0; + float pixels[4] = {0, 0, 0, 0}; + float percents[4] = {0, 0, 0, 0}; + for (unsigned int i = 0; i < Value.length(); ++i) + { + switch (input[i]) + { + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + buffer.push_back(input[i]); + break; + case '+': + pixels[coord] += buffer.ToFloat(); + buffer = "+"; + break; + case '-': + pixels[coord] += buffer.ToFloat(); + buffer = "-"; + break; + case '%': + percents[coord] += buffer.ToFloat(); + buffer = ""; + break; + case ' ': + pixels[coord] += buffer.ToFloat(); + buffer = ""; + ++coord; + break; + default: + LOGERROR("CGUISize definition may only include numbers. Your input: '%s'", Value.c_str()); + return false; + } + if (coord > 3) + { + LOGERROR("Too many CGUISize parameters (4 max). Your input: '%s'", Value.c_str()); + return false; + } + } + + if (coord < 3) + { + LOGERROR("Too few CGUISize parameters (4 min). Your input: '%s'", Value.c_str()); + return false; + } + + // Now that we're at the end of the string, flush the remaining buffer. + pixels[coord] += buffer.ToFloat(); + + // Now store the coords in the right place + pixel.left = pixels[0]; + pixel.top = pixels[1]; + pixel.right = pixels[2]; + pixel.bottom = pixels[3]; + percent.left = percents[0]; + percent.top = percents[1]; + percent.right = percents[2]; + percent.bottom = percents[3]; + return true; +} + +void CGUISize::ToJSVal(JSContext* cx, JS::MutableHandleValue ret) const +{ + JSAutoRequest rq(cx); + ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; + ret.setObjectOrNull(pScriptInterface->CreateCustomObject("GUISize")); + + if (!ret.isObject()) + { + JS_ReportError(cx, "CGUISize value is not an Object"); + return; + } + + JS::RootedObject obj(cx, &ret.toObject()); + if (!JS_InstanceOf(cx, obj, &JSI_GUISize::JSI_class, nullptr)) + { + JS_ReportError(cx, "CGUISize value is not a CGUISize class instance"); + return; + } + +#define P(x, y, z)\ + if (!pScriptInterface->SetProperty(ret, #z, x.y)) \ + { \ + JS_ReportError(cx, "Could not SetProperty '%s'", #z); \ + return; \ + } + P(pixel, left, left); + P(pixel, top, top); + P(pixel, right, right); + P(pixel, bottom, bottom); + P(percent, left, rleft); + P(percent, top, rtop); + P(percent, right, rright); + P(percent, bottom, rbottom); +#undef P +} + +bool CGUISize::FromJSVal(JSContext* cx, JS::HandleValue v) +{ + JSAutoRequest rq(cx); + ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; + + if (v.isString()) + { + CStrW str; + if (!ScriptInterface::FromJSVal(cx, v, str)) + { + JS_ReportError(cx, "CGUISize could not read JS string"); + return false; + } + + if (!FromString(str.ToUTF8())) + { + JS_ReportError(cx, "CGUISize could not parse JS string"); + return false; + } + return true; + } + + if (!v.isObject()) + { + JS_ReportError(cx, "CGUISize value is not an String, nor Object"); + return false; + } + + JS::RootedObject obj(cx, &v.toObject()); + if (!JS_InstanceOf(cx, obj, &JSI_GUISize::JSI_class, nullptr)) + { + JS_ReportError(cx, "CGUISize value is not a CGUISize class instance"); + return false; + } + +#define P(x, y, z) \ + if (!pScriptInterface->GetProperty(v, #z, x.y))\ + {\ + JS_ReportError(cx, "CGUISize could not get object property '%s'", #z);\ + return false;\ + } + + P(pixel, left, left); + P(pixel, top, top); + P(pixel, right, right); + P(pixel, bottom, bottom); + P(percent, left, rleft); + P(percent, top, rtop); + P(percent, right, rright); + P(percent, bottom, rbottom); +#undef P + + return true; +} Index: ps/trunk/source/gui/SettingTypes/CGUIString.h =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUIString.h +++ ps/trunk/source/gui/SettingTypes/CGUIString.h @@ -0,0 +1,219 @@ +/* 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_CGUISTRING +#define INCLUDED_CGUISTRING + +#include "gui/CGUIText.h" +#include "ps/CStrIntern.h" + +#include +#include +#include + +class CGUI; + +/** + * String class, substitute for CStr, but that parses + * the tags and builds up a list of all text that will + * be different when outputted. + * + * The difference between CGUIString and CGUIText is that + * CGUIString is a string-class that parses the tags + * when the value is set. The CGUIText is just a container + * which stores the positions and settings of all text-calls + * that will have to be made to the Renderer. + */ +class CGUIString +{ +public: + /** + * A chunk of text that represents one call to the renderer. + * In other words, all text in one chunk, will be drawn + * exactly with the same settings. + */ + struct TextChunk + { + /** + * A tag looks like this "Hello [b]there[/b] little" + */ + struct Tag + { + /** + * Tag Type + */ + enum TagType + { + TAG_B, + TAG_I, + TAG_FONT, + TAG_SIZE, + TAG_COLOR, + TAG_IMGLEFT, + TAG_IMGRIGHT, + TAG_ICON, + TAG_INVALID + }; + + struct TagAttribute + { + std::wstring attrib; + std::wstring value; + }; + + /** + * Set tag from string + * + * @param tagtype TagType by string, like 'img' for [img] + * @return True if m_TagType was set. + */ + bool SetTagType(const CStrW& tagtype); + TagType GetTagType(const CStrW& tagtype) const; + + + /** + * In [b="Hello"][/b] + * m_TagType is TAG_B + */ + TagType m_TagType; + + /** + * In [b="Hello"][/b] + * m_TagValue is 'Hello' + */ + std::wstring m_TagValue; + + /** + * Some tags need an additional attributes + */ + std::vector m_TagAttributes; + }; + + /** + * m_From and m_To is the range of the string + */ + int m_From, m_To; + + /** + * Tags that are present. [a][b] + */ + std::vector m_Tags; + }; + + /** + * All data generated in GenerateTextCall() + */ + struct SFeedback + { + // Avoid copying the vector and list containers. + NONCOPYABLE(SFeedback); + MOVABLE(SFeedback); + SFeedback() = default; + + // Constants + static const int Left = 0; + static const int Right = 1; + + /** + * Reset all member data. + */ + void Reset(); + + /** + * Image stacks, for left and right floating images. + */ + std::array, 2> m_Images; // left and right + + /** + * Text and Sprite Calls. + */ + std::vector m_TextCalls; + + // list for consistent mem addresses so that we can point to elements. + std::list m_SpriteCalls; + + /** + * Width and Height *feedback* + */ + CSize m_Size; + + /** + * If the word inputted was a new line. + */ + bool m_NewLine; + }; + + /** + * Set the value, the string will automatically + * be parsed when set. + */ + void SetValue(const CStrW& str); + + /** + * Get String, with tags + */ + const CStrW& GetOriginalString() const { return m_OriginalString; } + + /** + * Get String, stripped of tags + */ + const CStrW& GetRawString() const { return m_RawString; } + + /** + * Generate Text Call from specified range. The range + * must span only within ONE TextChunk though. Otherwise + * it can't be fit into a single Text Call + * + * Notice it won't make it complete, you will have to add + * X/Y values and such. + * + * @param pGUI Pointer to CGUI object making this call, for e.g. icon retrieval. + * @param Feedback contains all info that is generated. + * @param DefaultFont Default Font + * @param from From character n, + * @param to to character n. + * @param FirstLine Whether this is the first line of text, to calculate its height correctly + * @param pObject Only for Error outputting, optional! If nullptr + * then no Errors will be reported! Useful when you need + * to make several GenerateTextCall in different phases, + * it avoids duplicates. + */ + void GenerateTextCall(const CGUI& pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject = nullptr) const; + + /** + * Words + */ + std::vector m_Words; + +private: + /** + * TextChunks + */ + std::vector m_TextChunks; + + /** + * The full raw string. Stripped of tags. + */ + CStrW m_RawString; + + /** + * The original string value passed to SetValue. + */ + CStrW m_OriginalString; +}; + +#endif // INCLUDED_CGUISTRING Index: ps/trunk/source/gui/SettingTypes/CGUIString.cpp =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUIString.cpp +++ ps/trunk/source/gui/SettingTypes/CGUIString.cpp @@ -0,0 +1,474 @@ +/* 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 "CGUIString.h" + +#include "graphics/FontMetrics.h" +#include "gui/CGUI.h" +#include "lib/utf8.h" +#include "ps/CLogger.h" + +#include +#include + +// List of word delimiter bounds +// The list contains ranges of word delimiters. The odd indexed chars are the start +// of a range, the even are the end of a range. The list must be sorted in INCREASING ORDER +static const int NUM_WORD_DELIMITERS = 4*2; +static const u16 WordDelimiters[NUM_WORD_DELIMITERS] = { + ' ' , ' ', // spaces + '-' , '-', // hyphens + 0x3000, 0x31FF, // ideographic symbols + 0x3400, 0x9FFF +// TODO add unicode blocks of other languages that don't use spaces +}; + +void CGUIString::SFeedback::Reset() +{ + m_Images[Left].clear(); + m_Images[Right].clear(); + m_TextCalls.clear(); + m_SpriteCalls.clear(); + m_Size = CSize(); + m_NewLine = false; +} + +void CGUIString::GenerateTextCall(const CGUI& pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject) const +{ + // Reset width and height, because they will be determined with incrementation + // or comparisons. + Feedback.Reset(); + + // Check out which text chunk this is within. + for (const TextChunk& textChunk : m_TextChunks) + { + // Get the area that is overlapped by both the TextChunk and + // by the from/to inputted. + int _from = std::max(from, textChunk.m_From); + int _to = std::min(to, textChunk.m_To); + + // If from is larger than to, then they are not overlapping + if (_to == _from && textChunk.m_From == textChunk.m_To) + { + // These should never be able to have more than one tag. + ENSURE(textChunk.m_Tags.size() == 1); + + // Icons and images are placed on exactly one position + // in the words-list, and they can be counted twice if placed + // on an edge. But there is always only one logical preference + // that we want. This check filters the unwanted. + + // it's in the end of one word, and the icon + // should really belong to the beginning of the next one + if (_to == to && to >= 1 && to < (int)m_RawString.length()) + { + if (m_RawString[to-1] == ' ' || + m_RawString[to-1] == '-' || + m_RawString[to-1] == '\n') + continue; + } + // This std::string is just a break + if (_from == from && from >= 1) + { + if (m_RawString[from] == '\n' && + m_RawString[from-1] != '\n' && + m_RawString[from-1] != ' ' && + m_RawString[from-1] != '-') + continue; + } + + const TextChunk::Tag& tag = textChunk.m_Tags[0]; + ENSURE(tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT || + tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT || + tag.m_TagType == TextChunk::Tag::TAG_ICON); + + const std::string& path = utf8_from_wstring(tag.m_TagValue); + if (!pGUI.HasIcon(path)) + { + if (pObject) + LOGERROR("Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%s\").", path.c_str()); + continue; + } + + switch (tag.m_TagType) + { + case TextChunk::Tag::TAG_IMGLEFT: + Feedback.m_Images[SFeedback::Left].push_back(path); + break; + case TextChunk::Tag::TAG_IMGRIGHT: + Feedback.m_Images[SFeedback::Right].push_back(path); + break; + case TextChunk::Tag::TAG_ICON: + { + // We'll need to setup a text-call that will point + // to the icon, this is to be able to iterate + // through the text-calls without having to + // complex the structure virtually for nothing more. + CGUIText::STextCall TextCall; + + // Also add it to the sprites being rendered. + CGUIText::SSpriteCall SpriteCall; + + // Get Icon from icon database in pGUI + const SGUIIcon& icon = pGUI.GetIcon(path); + + const CSize& size = icon.m_Size; + + // append width, and make maximum height the height. + Feedback.m_Size.cx += size.cx; + Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); + + // These are also needed later + TextCall.m_Size = size; + SpriteCall.m_Area = size; + + // Handle additional attributes + for (const TextChunk::Tag::TagAttribute& tagAttrib : tag.m_TagAttributes) + { + if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty()) + { + // Displace the sprite + CSize displacement; + // Parse the value + if (!CGUI::ParseString(&pGUI, tagAttrib.value, displacement)) + LOGERROR("Error parsing 'displace' value for tag [ICON]"); + else + SpriteCall.m_Area += displacement; + } + else if (tagAttrib.attrib == L"tooltip") + SpriteCall.m_Tooltip = tagAttrib.value; + else if (tagAttrib.attrib == L"tooltip_style") + SpriteCall.m_TooltipStyle = tagAttrib.value; + } + + SpriteCall.m_Sprite = icon.m_SpriteName; + SpriteCall.m_CellID = icon.m_CellID; + + // Add sprite call + Feedback.m_SpriteCalls.push_back(std::move(SpriteCall)); + + // Finalize text call + TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back(); + + // Add text call + Feedback.m_TextCalls.emplace_back(std::move(TextCall)); + + break; + } + NODEFAULT; + } + } + else if (_to > _from && !Feedback.m_NewLine) + { + CGUIText::STextCall TextCall; + + // Set defaults + TextCall.m_Font = DefaultFont; + TextCall.m_UseCustomColor = false; + + TextCall.m_String = m_RawString.substr(_from, _to-_from); + + // Go through tags and apply changes. + for (const TextChunk::Tag& tag : textChunk.m_Tags) + { + switch (tag.m_TagType) + { + case TextChunk::Tag::TAG_COLOR: + TextCall.m_UseCustomColor = true; + + if (!CGUI::ParseString(&pGUI, tag.m_TagValue, TextCall.m_Color) && pObject) + LOGERROR("Error parsing the value of a [color]-tag in GUI text when reading object \"%s\".", pObject->GetPresentableName().c_str()); + break; + case TextChunk::Tag::TAG_FONT: + // TODO Gee: (2004-08-15) Check if Font exists? + TextCall.m_Font = CStrIntern(utf8_from_wstring(tag.m_TagValue)); + break; + default: + LOGERROR("Encountered unexpected tag applied to text"); + break; + } + } + + // Calculate the size of the font + CSize size; + int cx, cy; + CFontMetrics font (TextCall.m_Font); + font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy); + // For anything other than the first line, the line spacing + // needs to be considered rather than just the height of the text + if (!FirstLine) + cy = font.GetLineSpacing(); + + size.cx = (float)cx; + size.cy = (float)cy; + + // Append width, and make maximum height the height. + Feedback.m_Size.cx += size.cx; + Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); + + // These are also needed later + TextCall.m_Size = size; + + if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n') + Feedback.m_NewLine = true; + + // Add text-chunk + Feedback.m_TextCalls.emplace_back(std::move(TextCall)); + } + } +} + +bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype) +{ + TagType t = GetTagType(tagtype); + if (t == TAG_INVALID) + return false; + + m_TagType = t; + return true; +} + +CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype) const +{ + if (tagtype == L"color") + return TAG_COLOR; + if (tagtype == L"font") + return TAG_FONT; + if (tagtype == L"icon") + return TAG_ICON; + if (tagtype == L"imgleft") + return TAG_IMGLEFT; + if (tagtype == L"imgright") + return TAG_IMGRIGHT; + + return TAG_INVALID; +} + +void CGUIString::SetValue(const CStrW& str) +{ + m_OriginalString = str; + + m_TextChunks.clear(); + m_Words.clear(); + m_RawString.clear(); + + // Current Text Chunk + CGUIString::TextChunk CurrentTextChunk; + CurrentTextChunk.m_From = 0; + + int l = str.length(); + int rawpos = 0; + CStrW tag; + std::vector tags; + bool closing = false; + for (int p = 0; p < l; ++p) + { + TextChunk::Tag tag_; + switch (str[p]) + { + case L'[': + CurrentTextChunk.m_To = rawpos; + // Add the current chunks if it is not empty + if (CurrentTextChunk.m_From != rawpos) + m_TextChunks.push_back(CurrentTextChunk); + CurrentTextChunk.m_From = rawpos; + + closing = false; + if (++p == l) + { + LOGERROR("Partial tag at end of string '%s'", utf8_from_wstring(str)); + break; + } + if (str[p] == L'/') + { + closing = true; + if (tags.empty()) + { + LOGERROR("Encountered closing tag without having any open tags. At %d in '%s'", p, utf8_from_wstring(str)); + break; + } + if (++p == l) + { + LOGERROR("Partial closing tag at end of string '%s'", utf8_from_wstring(str)); + break; + } + } + tag.clear(); + // Parse tag + for (; p < l && str[p] != L']'; ++p) + { + CStrW name, param; + switch (str[p]) + { + case L' ': + if (closing) // We still parse them to make error handling cleaner + LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str)); + + // parse something="something else" + for (++p; p < l && str[p] != L'='; ++p) + name.push_back(str[p]); + + if (p == l) + { + LOGERROR("Parameter without value at pos %d '%s'", p, utf8_from_wstring(str)); + break; + } + FALLTHROUGH; + case L'=': + // parse a quoted parameter + if (closing) // We still parse them to make error handling cleaner + LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str)); + + if (++p == l) + { + LOGERROR("Expected parameter, got end of string '%s'", utf8_from_wstring(str)); + break; + } + if (str[p] != L'"') + { + LOGERROR("Unquoted parameters are not supported (at pos %d '%s')", p, utf8_from_wstring(str)); + break; + } + for (++p; p < l && str[p] != L'"'; ++p) + { + switch (str[p]) + { + case L'\\': + if (++p == l) + { + LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str)); + break; + } + // NOTE: We do not support \n in tag parameters + FALLTHROUGH; + default: + param.push_back(str[p]); + } + } + + if (!name.empty()) + { + TextChunk::Tag::TagAttribute a = {name, param}; + tag_.m_TagAttributes.push_back(a); + } + else + tag_.m_TagValue = param; + break; + default: + tag.push_back(str[p]); + break; + } + } + + if (!tag_.SetTagType(tag)) + { + LOGERROR("Invalid tag '%s' at %d in '%s'", utf8_from_wstring(tag), p, utf8_from_wstring(str)); + break; + } + if (!closing) + { + if (tag_.m_TagType == TextChunk::Tag::TAG_IMGRIGHT + || tag_.m_TagType == TextChunk::Tag::TAG_IMGLEFT + || tag_.m_TagType == TextChunk::Tag::TAG_ICON) + { + TextChunk FreshTextChunk = { rawpos, rawpos }; + FreshTextChunk.m_Tags.push_back(tag_); + m_TextChunks.push_back(FreshTextChunk); + } + else + { + tags.push_back(tag); + CurrentTextChunk.m_Tags.push_back(tag_); + } + } + else + { + if (tag != tags.back()) + { + LOGERROR("Closing tag '%s' does not match last opened tag '%s' at %d in '%s'", utf8_from_wstring(tag), utf8_from_wstring(tags.back()), p, utf8_from_wstring(str)); + break; + } + + tags.pop_back(); + CurrentTextChunk.m_Tags.pop_back(); + } + break; + case L'\\': + if (++p == l) + { + LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str)); + break; + } + if (str[p] == L'n') + { + ++rawpos; + m_RawString.push_back(L'\n'); + break; + } + FALLTHROUGH; + default: + ++rawpos; + m_RawString.push_back(str[p]); + break; + } + } + + // Add the chunk after the last tag + if (CurrentTextChunk.m_From != rawpos) + { + CurrentTextChunk.m_To = rawpos; + m_TextChunks.push_back(CurrentTextChunk); + } + + + // Add a delimiter at start and at end, it helps when + // processing later, because we don't have make exceptions for + // those cases. + m_Words.push_back(0); + + // Add word boundaries in increasing order + for (u32 i = 0; i < m_RawString.length(); ++i) + { + wchar_t c = m_RawString[i]; + if (c == '\n') + { + m_Words.push_back((int)i); + m_Words.push_back((int)i+1); + continue; + } + for (int n = 0; n < NUM_WORD_DELIMITERS; n += 2) + { + if (c <= WordDelimiters[n+1]) + { + if (c >= WordDelimiters[n]) + m_Words.push_back((int)i+1); + // assume the WordDelimiters list is stored in increasing order + break; + } + } + } + + m_Words.push_back((int)m_RawString.length()); + + // Remove duplicates (only if larger than 2) + if (m_Words.size() <= 2) + return; + + m_Words.erase(std::unique(m_Words.begin(), m_Words.end()), m_Words.end()); +} Index: ps/trunk/source/gui/SettingTypes/EAlign.h =================================================================== --- ps/trunk/source/gui/SettingTypes/EAlign.h +++ ps/trunk/source/gui/SettingTypes/EAlign.h @@ -0,0 +1,24 @@ +/* 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_EALIGN +#define INCLUDED_EALIGN + +enum EAlign { EAlign_Left, EAlign_Right, EAlign_Center }; +enum EVAlign { EVAlign_Top, EVAlign_Bottom, EVAlign_Center }; + +#endif // INCLUDED_EALIGN Index: ps/trunk/source/gui/scripting/GuiScriptConversions.cpp =================================================================== --- ps/trunk/source/gui/scripting/GuiScriptConversions.cpp +++ ps/trunk/source/gui/scripting/GuiScriptConversions.cpp @@ -1,369 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ - -#include "precompiled.h" - -#include "gui/CGUIColor.h" -#include "gui/CGUIList.h" -#include "gui/CGUISeries.h" -#include "gui/CGUISize.h" -#include "gui/IGUIObject.h" -#include "lib/external_libraries/libsdl.h" -#include "maths/Vector2D.h" -#include "ps/Hotkey.h" -#include "scriptinterface/ScriptConversions.h" - -#include - -#define SET(obj, name, value) STMT(JS::RootedValue v_(cx); AssignOrToJSVal(cx, &v_, (value)); JS_SetProperty(cx, obj, (name), v_)) - // ignore JS_SetProperty return value, because errors should be impossible - // and we can't do anything useful in the case of errors anyway - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, SDL_Event_ const& val) -{ - JSAutoRequest rq(cx); - const char* typeName; - - switch (val.ev.type) - { - case SDL_WINDOWEVENT: typeName = "windowevent"; break; - case SDL_KEYDOWN: typeName = "keydown"; break; - case SDL_KEYUP: typeName = "keyup"; break; - case SDL_MOUSEMOTION: typeName = "mousemotion"; break; - case SDL_MOUSEBUTTONDOWN: typeName = "mousebuttondown"; break; - case SDL_MOUSEBUTTONUP: typeName = "mousebuttonup"; break; - case SDL_QUIT: typeName = "quit"; break; - case SDL_HOTKEYDOWN: typeName = "hotkeydown"; break; - case SDL_HOTKEYUP: typeName = "hotkeyup"; break; - default: typeName = "(unknown)"; break; - } - - JS::RootedObject obj(cx, JS_NewPlainObject(cx)); - if (!obj) - { - ret.setUndefined(); - return; - } - - SET(obj, "type", typeName); - - switch (val.ev.type) - { - case SDL_KEYDOWN: - case SDL_KEYUP: - { - // SET(obj, "which", (int)val.ev.key.which); // (not in wsdl.h) - // SET(obj, "state", (int)val.ev.key.state); // (not in wsdl.h) - - JS::RootedObject keysym(cx, JS_NewPlainObject(cx)); - if (!keysym) - { - ret.setUndefined(); - return; - } - JS::RootedValue keysymVal(cx, JS::ObjectValue(*keysym)); - JS_SetProperty(cx, obj, "keysym", keysymVal); - - // SET(keysym, "scancode", (int)val.ev.key.keysym.scancode); // (not in wsdl.h) - SET(keysym, "sym", (int)val.ev.key.keysym.sym); - // SET(keysym, "mod", (int)val.ev.key.keysym.mod); // (not in wsdl.h) - { - SET(keysym, "unicode", JS::UndefinedHandleValue); - } - // TODO: scripts have no idea what all the key/mod enum values are; - // we should probably expose them as constants if we expect scripts to use them - - break; - } - case SDL_MOUSEMOTION: - { - // SET(obj, "which", (int)val.ev.motion.which); // (not in wsdl.h) - // SET(obj, "state", (int)val.ev.motion.state); // (not in wsdl.h) - SET(obj, "x", (int)val.ev.motion.x); - SET(obj, "y", (int)val.ev.motion.y); - // SET(obj, "xrel", (int)val.ev.motion.xrel); // (not in wsdl.h) - // SET(obj, "yrel", (int)val.ev.motion.yrel); // (not in wsdl.h) - break; - } - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - { - // SET(obj, "which", (int)val.ev.button.which); // (not in wsdl.h) - SET(obj, "button", (int)val.ev.button.button); - SET(obj, "state", (int)val.ev.button.state); - SET(obj, "x", (int)val.ev.button.x); - SET(obj, "y", (int)val.ev.button.y); - SET(obj, "clicks", (int)val.ev.button.clicks); - break; - } - case SDL_HOTKEYDOWN: - case SDL_HOTKEYUP: - { - SET(obj, "hotkey", static_cast(val.ev.user.data1)); - break; - } - } - - ret.setObject(*obj); -} - -template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, IGUIObject* const& val) -{ - if (val == nullptr) - ret.setNull(); - else - ret.setObject(*val->GetJSObject()); -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIString& val) -{ - ScriptInterface::ToJSVal(cx, ret, val.GetOriginalString()); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIString& out) -{ - std::wstring val; - if (!FromJSVal(cx, v, val)) - return false; - out.SetValue(val); - return true; -} - -JSVAL_VECTOR(CVector2D) -JSVAL_VECTOR(std::vector) -JSVAL_VECTOR(CGUIString) - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIColor& val) -{ - ToJSVal(cx, ret, val); -} - -/** - * The color depends on the predefined color database stored in the current GUI page. - */ -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIColor& out) = delete; - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CSize& val) -{ - CreateObject(cx, ret, "width", val.cx, "height", val.cy); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CSize& out) -{ - if (!v.isObject()) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "CSize value must be an object!"); - return false; - } - - if (!FromJSProperty(cx, v, "width", out.cx)) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "Failed to get CSize.cx property"); - return false; - } - - if (!FromJSProperty(cx, v, "height", out.cy)) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "Failed to get CSize.cy property"); - return false; - } - - return true; -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CPos& val) -{ - CreateObject(cx, ret, "x", val.x, "y", val.y); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CPos& out) -{ - if (!v.isObject()) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "CPos value must be an object!"); - return false; - } - - if (!FromJSProperty(cx, v, "x", out.x)) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "Failed to get CPos.x property"); - return false; - } - - if (!FromJSProperty(cx, v, "y", out.y)) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "Failed to get CPos.y property"); - return false; - } - - return true; -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CRect& val) -{ - CreateObject( - cx, - ret, - "left", val.left, - "right", val.right, - "top", val.top, - "bottom", val.bottom); -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISize& val) -{ - val.ToJSVal(cx, ret); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISize& out) -{ - return out.FromJSVal(cx, v); -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIList& val) -{ - ToJSVal(cx, ret, val.m_Items); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIList& out) -{ - return FromJSVal(cx, v, out.m_Items); -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISeries& val) -{ - ToJSVal(cx, ret, val.m_Series); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISeries& out) -{ - return FromJSVal(cx, v, out.m_Series); -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const EVAlign& val) -{ - std::string word; - switch (val) - { - case EVAlign_Top: - word = "top"; - break; - - case EVAlign_Bottom: - word = "bottom"; - break; - - case EVAlign_Center: - word = "center"; - break; - - default: - word = "error"; - JSAutoRequest rq(cx); - JS_ReportError(cx, "Invalid EVAlign"); - break; - } - ToJSVal(cx, ret, word); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, EVAlign& out) -{ - std::string word; - FromJSVal(cx, v, word); - - if (word == "top") - out = EVAlign_Top; - else if (word == "bottom") - out = EVAlign_Bottom; - else if (word == "center") - out = EVAlign_Center; - else - { - out = EVAlign_Top; - JSAutoRequest rq(cx); - JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')"); - return false; - } - return true; -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const EAlign& val) -{ - std::string word; - switch (val) - { - case EAlign_Left: - word = "left"; - break; - case EAlign_Right: - word = "right"; - break; - case EAlign_Center: - word = "center"; - break; - default: - word = "error"; - JSAutoRequest rq(cx); - JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')"); - break; - } - ToJSVal(cx, ret, word); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, EAlign& out) -{ - std::string word; - FromJSVal(cx, v, word); - - if (word == "left") - out = EAlign_Left; - else if (word == "right") - out = EAlign_Right; - else if (word == "center") - out = EAlign_Center; - else - { - out = EAlign_Left; - JSAutoRequest rq(cx); - JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')"); - return false; - } - return true; -} - -template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISpriteInstance& val) -{ - ToJSVal(cx, ret, val.GetName()); -} - -template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISpriteInstance& out) -{ - std::string name; - if (!FromJSVal(cx, v, name)) - return false; - - out.SetName(name); - return true; -} - -#undef SET Index: ps/trunk/source/gui/scripting/JSInterface_GUIManager.h =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_GUIManager.h +++ ps/trunk/source/gui/scripting/JSInterface_GUIManager.h @@ -1,40 +0,0 @@ -/* 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_JSI_GUIMANAGER -#define INCLUDED_JSI_GUIMANAGER - -#include "scriptinterface/ScriptInterface.h" -#include "simulation2/system/ParamNode.h" - -namespace JSI_GUIManager -{ - void PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData, JS::HandleValue callbackFunction); - void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData); - void PopGuiPage(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue args); - JS::Value GetGUIObjectByName(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); - void SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, JS::HandleValue function); - void UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag); - std::wstring SetCursor(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name); - void ResetCursor(ScriptInterface::CxPrivate* pCxPrivate); - bool TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); - CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); - - void RegisterScriptFunctions(const ScriptInterface& scriptInterface); -} - -#endif // INCLUDED_JSI_GUIMANAGER Index: ps/trunk/source/gui/scripting/JSInterface_GUIManager.cpp =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_GUIManager.cpp +++ ps/trunk/source/gui/scripting/JSInterface_GUIManager.cpp @@ -1,110 +0,0 @@ -/* 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 "JSInterface_GUIManager.h" - -#include "gui/CGUI.h" -#include "gui/GUIManager.h" -#include "gui/IGUIObject.h" -#include "ps/GameSetup/Config.h" -#include "scriptinterface/ScriptInterface.h" - -// Note that the initData argument may only contain clonable data. -// Functions aren't supported for example! -void JSI_GUIManager::PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData, JS::HandleValue callbackFunction) -{ - g_GUI->PushPage(name, pCxPrivate->pScriptInterface->WriteStructuredClone(initData), callbackFunction); -} - -void JSI_GUIManager::SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue initData) -{ - g_GUI->SwitchPage(name, pCxPrivate->pScriptInterface, initData); -} - -void JSI_GUIManager::PopGuiPage(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue args) -{ - if (g_GUI->GetPageCount() < 2) - { - JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); - JSAutoRequest rq(cx); - JS_ReportError(cx, "Can't pop GUI pages when less than two pages are opened!"); - return; - } - - g_GUI->PopPage(pCxPrivate->pScriptInterface->WriteStructuredClone(args)); -} - -JS::Value JSI_GUIManager::GetGUIObjectByName(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) -{ - CGUI* guiPage = static_cast(pCxPrivate->pCBData); - - IGUIObject* guiObj = guiPage->FindObjectByName(name); - if (!guiObj) - return JS::UndefinedValue(); - - return JS::ObjectValue(*guiObj->GetJSObject()); -} - -void JSI_GUIManager::SetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag, JS::HandleValue function) -{ - CGUI* guiPage = static_cast(pCxPrivate->pCBData); - guiPage->SetGlobalHotkey(hotkeyTag, function); -} - -void JSI_GUIManager::UnsetGlobalHotkey(ScriptInterface::CxPrivate* pCxPrivate, const std::string& hotkeyTag) -{ - CGUI* guiPage = static_cast(pCxPrivate->pCBData); - guiPage->UnsetGlobalHotkey(hotkeyTag); -} - -std::wstring JSI_GUIManager::SetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name) -{ - std::wstring old = g_CursorName; - g_CursorName = name; - return old; -} - -void JSI_GUIManager::ResetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) -{ - g_GUI->ResetCursor(); -} - -bool JSI_GUIManager::TemplateExists(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName) -{ - return g_GUI->TemplateExists(templateName); -} - -CParamNode JSI_GUIManager::GetTemplate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName) -{ - return g_GUI->GetTemplate(templateName); -} - -void JSI_GUIManager::RegisterScriptFunctions(const ScriptInterface& scriptInterface) -{ - scriptInterface.RegisterFunction("PushGuiPage"); - scriptInterface.RegisterFunction("SwitchGuiPage"); - scriptInterface.RegisterFunction("SetGlobalHotkey"); - scriptInterface.RegisterFunction("UnsetGlobalHotkey"); - scriptInterface.RegisterFunction("PopGuiPage"); - scriptInterface.RegisterFunction("GetGUIObjectByName"); - scriptInterface.RegisterFunction("SetCursor"); - scriptInterface.RegisterFunction("ResetCursor"); - scriptInterface.RegisterFunction("TemplateExists"); - scriptInterface.RegisterFunction("GetTemplate"); -} Index: ps/trunk/source/gui/scripting/JSInterface_GUISize.h =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_GUISize.h +++ ps/trunk/source/gui/scripting/JSInterface_GUISize.h @@ -1,38 +0,0 @@ -/* 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_JSI_GUISIZE -#define INCLUDED_JSI_GUISIZE - -#include "scriptinterface/ScriptInterface.h" -#include "ps/CStr.h" - -namespace JSI_GUISize -{ - extern JSClass JSI_class; - extern JSPropertySpec JSI_props[]; - extern JSFunctionSpec JSI_methods[]; - - void RegisterScriptClass(ScriptInterface& scriptInterface); - - bool construct(JSContext* cx, uint argc, JS::Value* vp); - bool toString(JSContext* cx, uint argc, JS::Value* vp); - - CStr ToPercentString(double pix, double per); -} - -#endif // INCLUDED_JSI_GUISIZE Index: ps/trunk/source/gui/scripting/JSInterface_GUISize.cpp =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_GUISize.cpp +++ ps/trunk/source/gui/scripting/JSInterface_GUISize.cpp @@ -1,125 +0,0 @@ -/* 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 "JSInterface_GUISize.h" - -#include "ps/CStr.h" -#include "scriptinterface/ScriptInterface.h" - -JSClass JSI_GUISize::JSI_class = { - "GUISize", 0, - nullptr, nullptr, - nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, JSI_GUISize::construct, nullptr -}; - -JSFunctionSpec JSI_GUISize::JSI_methods[] = -{ - JS_FN("toString", JSI_GUISize::toString, 0, 0), - JS_FS_END -}; - -void JSI_GUISize::RegisterScriptClass(ScriptInterface& scriptInterface) -{ - scriptInterface.DefineCustomObjectType(&JSI_GUISize::JSI_class, JSI_GUISize::construct, 0, nullptr, JSI_GUISize::JSI_methods, nullptr, nullptr); -} - -bool JSI_GUISize::construct(JSContext* cx, uint argc, JS::Value* vp) -{ - JSAutoRequest rq(cx); - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - JS::RootedObject obj(cx, pScriptInterface->CreateCustomObject("GUISize")); - - if (args.length() == 8) - { - JS_SetProperty(cx, obj, "left", args[0]); - JS_SetProperty(cx, obj, "top", args[1]); - JS_SetProperty(cx, obj, "right", args[2]); - JS_SetProperty(cx, obj, "bottom", args[3]); - JS_SetProperty(cx, obj, "rleft", args[4]); - JS_SetProperty(cx, obj, "rtop", args[5]); - JS_SetProperty(cx, obj, "rright", args[6]); - JS_SetProperty(cx, obj, "rbottom", args[7]); - } - else if (args.length() == 4) - { - JS::RootedValue zero(cx, JS::NumberValue(0)); - JS_SetProperty(cx, obj, "left", args[0]); - JS_SetProperty(cx, obj, "top", args[1]); - JS_SetProperty(cx, obj, "right", args[2]); - JS_SetProperty(cx, obj, "bottom", args[3]); - JS_SetProperty(cx, obj, "rleft", zero); - JS_SetProperty(cx, obj, "rtop", zero); - JS_SetProperty(cx, obj, "rright", zero); - JS_SetProperty(cx, obj, "rbottom", zero); - } - else - { - JS::RootedValue zero(cx, JS::NumberValue(0)); - JS_SetProperty(cx, obj, "left", zero); - JS_SetProperty(cx, obj, "top", zero); - JS_SetProperty(cx, obj, "right", zero); - JS_SetProperty(cx, obj, "bottom", zero); - JS_SetProperty(cx, obj, "rleft", zero); - JS_SetProperty(cx, obj, "rtop", zero); - JS_SetProperty(cx, obj, "rright", zero); - JS_SetProperty(cx, obj, "rbottom", zero); - } - - args.rval().setObject(*obj); - return true; -} - -// Produces "10", "-10", "50%", "50%-10", "50%+10", etc -CStr JSI_GUISize::ToPercentString(double pix, double per) -{ - if (per == 0) - return CStr::FromDouble(pix); - - return CStr::FromDouble(per)+"%"+(pix == 0.0 ? CStr() : pix > 0.0 ? CStr("+")+CStr::FromDouble(pix) : CStr::FromDouble(pix)); -} - -bool JSI_GUISize::toString(JSContext* cx, uint argc, JS::Value* vp) -{ - // JSAutoRequest not needed for the calls below - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - CStr buffer; - - ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - double val, valr; - -#define SIDE(side) \ - pScriptInterface->GetProperty(args.thisv(), #side, val); \ - pScriptInterface->GetProperty(args.thisv(), "r"#side, valr); \ - buffer += ToPercentString(val, valr); - - SIDE(left); - buffer += " "; - SIDE(top); - buffer += " "; - SIDE(right); - buffer += " "; - SIDE(bottom); -#undef SIDE - - ScriptInterface::ToJSVal(cx, args.rval(), buffer); - return true; -} Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.h =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.h +++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.h @@ -1,39 +0,0 @@ -/* 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_JSI_IGUIOBJECT -#define INCLUDED_JSI_IGUIOBJECT - -#include "scriptinterface/ScriptInterface.h" - -namespace JSI_IGUIObject -{ - extern JSClass JSI_class; - extern JSFunctionSpec JSI_methods[]; - - void RegisterScriptClass(ScriptInterface& scriptInterface); - - bool getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); - bool setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); - bool toString(JSContext* cx, uint argc, JS::Value* vp); - bool focus(JSContext* cx, uint argc, JS::Value* vp); - bool blur(JSContext* cx, uint argc, JS::Value* vp); - bool getComputedSize(JSContext* cx, uint argc, JS::Value* vp); - bool getTextSize(JSContext* cx, uint argc, JS::Value* vp); -} - -#endif // INCLUDED_JSI_IGUIOBJECT Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp +++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp @@ -1,228 +0,0 @@ -/* 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 "JSInterface_IGUIObject.h" - -#include "gui/CGUI.h" -#include "gui/CGUISetting.h" -#include "gui/IGUIObject.h" -#include "scriptinterface/ScriptExtraHeaders.h" -#include "scriptinterface/ScriptInterface.h" - -JSClass JSI_IGUIObject::JSI_class = { - "GUIObject", JSCLASS_HAS_PRIVATE, - nullptr, nullptr, - JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty, - nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr -}; - -JSFunctionSpec JSI_IGUIObject::JSI_methods[] = -{ - JS_FN("toString", JSI_IGUIObject::toString, 0, 0), - JS_FN("focus", JSI_IGUIObject::focus, 0, 0), - JS_FN("blur", JSI_IGUIObject::blur, 0, 0), - JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), - JS_FS_END -}; - -void JSI_IGUIObject::RegisterScriptClass(ScriptInterface& scriptInterface) -{ - scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 0, nullptr, JSI_methods, nullptr, nullptr); -} - -bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) -{ - JSAutoRequest rq(cx); - ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; - - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - JS::RootedValue idval(cx); - if (!JS_IdToValue(cx, id, &idval)) - return false; - - std::string propName; - if (!ScriptInterface::FromJSVal(cx, idval, propName)) - return false; - - // Skip registered functions and inherited properties - // including JSInterfaces of derived classes - if (propName == "constructor" || - propName == "prototype" || - propName == "toString" || - propName == "toJSON" || - propName == "focus" || - propName == "blur" || - propName == "getTextSize" || - propName == "getComputedSize" - ) - return true; - - // Use onWhatever to access event handlers - if (propName.substr(0, 2) == "on") - { - CStr eventName(CStr(propName.substr(2)).LowerCase()); - std::map>::iterator it = e->m_ScriptHandlers.find(eventName); - if (it == e->m_ScriptHandlers.end()) - vp.setNull(); - else - vp.setObject(*it->second.get()); - return true; - } - - if (propName == "parent") - { - IGUIObject* parent = e->GetParent(); - - if (parent) - vp.set(JS::ObjectValue(*parent->GetJSObject())); - else - vp.set(JS::NullValue()); - - return true; - } - else if (propName == "children") - { - ScriptInterface::CreateArray(cx, vp); - - for (size_t i = 0; i < e->m_Children.size(); ++i) - pScriptInterface->SetPropertyInt(vp, i, e->m_Children[i]); - - return true; - } - else if (propName == "name") - { - ScriptInterface::ToJSVal(cx, vp, e->GetName()); - return true; - } - else if (e->SettingExists(propName)) - { - e->m_Settings[propName]->ToJSVal(cx, vp); - return true; - } - - JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); - return false; -} - -bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result) -{ - IGUIObject* e = ScriptInterface::GetPrivate(cx, obj, &JSI_IGUIObject::JSI_class); - if (!e) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - JSAutoRequest rq(cx); - JS::RootedValue idval(cx); - if (!JS_IdToValue(cx, id, &idval)) - return result.fail(JSMSG_NOT_NONNULL_OBJECT); - - std::string propName; - if (!ScriptInterface::FromJSVal(cx, idval, propName)) - return result.fail(JSMSG_UNDEFINED_PROP); - - if (propName == "name") - { - std::string value; - if (!ScriptInterface::FromJSVal(cx, vp, value)) - return result.fail(JSMSG_UNDEFINED_PROP); - e->SetName(value); - return result.succeed(); - } - - JS::RootedObject vpObj(cx); - if (vp.isObject()) - vpObj = &vp.toObject(); - - // Use onWhatever to set event handlers - if (propName.substr(0, 2) == "on") - { - if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject())) - { - JS_ReportError(cx, "on- event-handlers must be functions"); - return result.fail(JSMSG_NOT_FUNCTION); - } - - CStr eventName(CStr(propName.substr(2)).LowerCase()); - e->SetScriptHandler(eventName, vpObj); - - return result.succeed(); - } - - if (e->SettingExists(propName)) - return e->m_Settings[propName]->FromJSVal(cx, vp, true) ? result.succeed() : result.fail(JSMSG_TYPE_ERR_BAD_ARGS); - - JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); - return result.fail(JSMSG_UNDEFINED_PROP); -} - -bool JSI_IGUIObject::toString(JSContext* cx, uint argc, JS::Value* vp) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - ScriptInterface::ToJSVal(cx, args.rval(), "[GUIObject: " + e->GetName() + "]"); - return true; -} - -bool JSI_IGUIObject::focus(JSContext* cx, uint argc, JS::Value* vp) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->GetGUI().SetFocusedObject(e); - args.rval().setUndefined(); - return true; -} - -bool JSI_IGUIObject::blur(JSContext* cx, uint argc, JS::Value* vp) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->GetGUI().SetFocusedObject(nullptr); - args.rval().setUndefined(); - return true; -} - -bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp) -{ - JSAutoRequest rq(cx); - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - IGUIObject* e = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!e) - return false; - - e->UpdateCachedSize(); - ScriptInterface::ToJSVal(cx, args.rval(), e->m_CachedActualSize); - - return true; -} Index: ps/trunk/source/gui/scripting/ScriptFunctions.h =================================================================== --- ps/trunk/source/gui/scripting/ScriptFunctions.h +++ ps/trunk/source/gui/scripting/ScriptFunctions.h @@ -1,25 +0,0 @@ -/* Copyright (C) 2010 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_GUI_SCRIPTFUNCTIONS -#define INCLUDED_GUI_SCRIPTFUNCTIONS - -class ScriptInterface; - -void GuiScriptingInit(ScriptInterface& scriptInterface); - -#endif // INCLUDED_GUI_SCRIPTFUNCTIONS Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp =================================================================== --- ps/trunk/source/gui/scripting/ScriptFunctions.cpp +++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp @@ -1,75 +0,0 @@ -/* 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 "ScriptFunctions.h" - -#include "graphics/scripting/JSInterface_GameView.h" -#include "gui/scripting/JSInterface_GUIManager.h" -#include "gui/scripting/JSInterface_GUISize.h" -#include "gui/scripting/JSInterface_IGUIObject.h" -#include "i18n/scripting/JSInterface_L10n.h" -#include "lobby/scripting/JSInterface_Lobby.h" -#include "network/scripting/JSInterface_Network.h" -#include "ps/scripting/JSInterface_ConfigDB.h" -#include "ps/scripting/JSInterface_Console.h" -#include "ps/scripting/JSInterface_Debug.h" -#include "ps/scripting/JSInterface_Game.h" -#include "ps/scripting/JSInterface_Main.h" -#include "ps/scripting/JSInterface_Mod.h" -#include "ps/scripting/JSInterface_ModIo.h" -#include "ps/scripting/JSInterface_SavedGame.h" -#include "ps/scripting/JSInterface_UserReport.h" -#include "ps/scripting/JSInterface_VFS.h" -#include "ps/scripting/JSInterface_VisualReplay.h" -#include "renderer/scripting/JSInterface_Renderer.h" -#include "scriptinterface/ScriptInterface.h" -#include "simulation2/scripting/JSInterface_Simulation.h" -#include "soundmanager/scripting/JSInterface_Sound.h" - -/* - * This file defines a set of functions that are available to GUI scripts, to allow - * interaction with the rest of the engine. - * Functions are exposed to scripts within the global object 'Engine', so - * scripts should call "Engine.FunctionName(...)" etc. - */ -void GuiScriptingInit(ScriptInterface& scriptInterface) -{ - JSI_GUISize::RegisterScriptClass(scriptInterface); - JSI_IGUIObject::RegisterScriptClass(scriptInterface); - - JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); - JSI_Console::RegisterScriptFunctions(scriptInterface); - JSI_Debug::RegisterScriptFunctions(scriptInterface); - JSI_GUIManager::RegisterScriptFunctions(scriptInterface); - JSI_Game::RegisterScriptFunctions(scriptInterface); - JSI_GameView::RegisterScriptFunctions(scriptInterface); - JSI_L10n::RegisterScriptFunctions(scriptInterface); - JSI_Lobby::RegisterScriptFunctions(scriptInterface); - JSI_Main::RegisterScriptFunctions(scriptInterface); - JSI_Mod::RegisterScriptFunctions(scriptInterface); - JSI_ModIo::RegisterScriptFunctions(scriptInterface); - JSI_Network::RegisterScriptFunctions(scriptInterface); - JSI_Renderer::RegisterScriptFunctions(scriptInterface); - JSI_SavedGame::RegisterScriptFunctions(scriptInterface); - JSI_Simulation::RegisterScriptFunctions(scriptInterface); - JSI_Sound::RegisterScriptFunctions(scriptInterface); - JSI_UserReport::RegisterScriptFunctions(scriptInterface); - JSI_VFS::RegisterScriptFunctions_GUI(scriptInterface); - JSI_VisualReplay::RegisterScriptFunctions(scriptInterface); -} Index: ps/trunk/source/gui/tests/test_ParseString.h =================================================================== --- ps/trunk/source/gui/tests/test_ParseString.h +++ ps/trunk/source/gui/tests/test_ParseString.h @@ -17,7 +17,7 @@ #include "lib/self_test.h" -#include "gui/CGUISize.h" +#include "gui/SettingTypes/CGUISize.h" #include "gui/CGUI.h" #include "ps/CLogger.h" Index: ps/trunk/source/pch/gui/precompiled.h =================================================================== --- ps/trunk/source/pch/gui/precompiled.h +++ ps/trunk/source/pch/gui/precompiled.h @@ -25,7 +25,7 @@ #if HAVE_PCH #include "gui/CGUI.h" -#include "gui/IGUIObject.h" +#include "gui/ObjectBases/IGUIObject.h" #include "ps/CStr.h" #include "scriptinterface/ScriptInterface.h"