Index: ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.cpp =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.cpp (revision 23019) +++ ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.cpp (nonexistent) @@ -1,55 +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_IGUITextOwner.h" - -#include "gui/IGUITextOwner.h" -#include "scriptinterface/ScriptInterface.h" - -JSFunctionSpec JSI_IGUITextOwner::JSI_methods[] = -{ - JS_FN("getTextSize", JSI_IGUITextOwner::GetTextSize, 0, 0), - JS_FS_END -}; - -void JSI_IGUITextOwner::RegisterScriptFunctions(JSContext* cx, JS::HandleObject obj) -{ - JS_DefineFunctions(cx, obj, JSI_methods); -} - -bool JSI_IGUITextOwner::GetTextSize(JSContext* cx, uint argc, JS::Value* vp) -{ - // No JSAutoRequest needed for these calls - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - IGUIObject* obj = ScriptInterface::GetPrivate(cx, args, &JSI_IGUIObject::JSI_class); - if (!obj) - return false; - - // Avoid dynamic_cast for performance reasons - IGUITextOwner* objText = static_cast(obj->GetTextOwner()); - if (!objText) - { - JSAutoRequest rq(cx); - JS_ReportError(cx, "This IGUIObject is not an IGUITextOwner!"); - return false; - } - - ScriptInterface::ToJSVal(cx, args.rval(), objText->CalculateTextSize()); - return true; -} Property changes on: ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.cpp ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.h =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.h (revision 23019) +++ ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.h (nonexistent) @@ -1,32 +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_IGUITEXTOWNER -#define INCLUDED_JSI_IGUITEXTOWNER - -#include "scriptinterface/ScriptInterface.h" - -namespace JSI_IGUITextOwner -{ - extern JSFunctionSpec JSI_methods[]; - - void RegisterScriptFunctions(JSContext* cx, JS::HandleObject obj); - - bool GetTextSize(JSContext* cx, uint argc, JS::Value* vp); -} - -#endif // INCLUDED_JSI_IGUITEXTOWNER Property changes on: ps/trunk/source/gui/scripting/JSInterface_IGUITextOwner.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp (revision 23019) +++ ps/trunk/source/gui/CButton.cpp (revision 23020) @@ -1,107 +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/CGUIColor.h" #include "gui/CGUIText.h" CButton::CButton(CGUI& pGUI) : IGUIObject(pGUI), - IGUIButtonBehavior(pGUI), - IGUITextOwner(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) { - // Important + 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/CButton.h =================================================================== --- ps/trunk/source/gui/CButton.h (revision 23019) +++ ps/trunk/source/gui/CButton.h (revision 23020) @@ -1,84 +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/IGUIButtonBehavior.h" #include "gui/IGUIObject.h" #include "gui/IGUITextOwner.h" #include "gui/CGUISprite.h" #include "gui/CGUIString.h" -class CButton : public IGUIButtonBehavior, public IGUITextOwner +class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior { GUI_OBJECT(CButton) public: CButton(CGUI& pGUI); virtual ~CButton(); /** * @see IGUIObject#ResetStates() */ - virtual void ResetStates() { IGUIButtonBehavior::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/CChart.cpp =================================================================== --- ps/trunk/source/gui/CChart.cpp (revision 23019) +++ ps/trunk/source/gui/CChart.cpp (revision 23020) @@ -1,309 +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/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(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/CChart.h =================================================================== --- ps/trunk/source/gui/CChart.h (revision 23019) +++ ps/trunk/source/gui/CChart.h (revision 23020) @@ -1,106 +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/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 IGUITextOwner +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/CCheckBox.cpp =================================================================== --- ps/trunk/source/gui/CCheckBox.cpp (revision 23019) +++ ps/trunk/source/gui/CCheckBox.cpp (revision 23020) @@ -1,82 +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(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) { - // Important + IGUIObject::HandleMessage(Message); IGUIButtonBehavior::HandleMessage(Message); switch (Message.type) { case GUIM_PRESSED: { - // Switch to opposite. 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/CCheckBox.h =================================================================== --- ps/trunk/source/gui/CCheckBox.h (revision 23019) +++ ps/trunk/source/gui/CCheckBox.h (revision 23020) @@ -1,61 +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/IGUIButtonBehavior.h" -class CCheckBox : public IGUIButtonBehavior +class CCheckBox : public IGUIObject, public IGUIButtonBehavior { GUI_OBJECT(CCheckBox) public: CCheckBox(CGUI& pGUI); virtual ~CCheckBox(); /** * @see IGUIObject#ResetStates() */ - virtual void ResetStates() { IGUIButtonBehavior::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/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp (revision 23019) +++ ps/trunk/source/gui/CDropDown.cpp (revision 23020) @@ -1,506 +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/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), - IGUIObject(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) { - // Important + // 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/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 23019) +++ ps/trunk/source/gui/CInput.cpp (revision 23020) @@ -1,2045 +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 "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(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/CInput.h =================================================================== --- ps/trunk/source/gui/CInput.h (revision 23019) +++ ps/trunk/source/gui/CInput.h (revision 23020) @@ -1,212 +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/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 IGUIScrollBarOwner +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() { IGUIScrollBarOwner::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/CList.cpp =================================================================== --- ps/trunk/source/gui/CList.cpp (revision 23019) +++ ps/trunk/source/gui/CList.cpp (revision 23020) @@ -1,470 +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/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(pGUI), - IGUIScrollBarOwner(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/CList.h =================================================================== --- ps/trunk/source/gui/CList.h (revision 23019) +++ ps/trunk/source/gui/CList.h (revision 23020) @@ -1,145 +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/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 IGUIScrollBarOwner, public IGUITextOwner +class CList : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner { GUI_OBJECT(CList) public: CList(CGUI& pGUI); virtual ~CList(); /** * @see IGUIObject#ResetStates() */ - virtual void ResetStates() { IGUIScrollBarOwner::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/COList.cpp =================================================================== --- ps/trunk/source/gui/COList.cpp (revision 23019) +++ ps/trunk/source/gui/COList.cpp (revision 23020) @@ -1,443 +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/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), - IGUIObject(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.cpp =================================================================== --- ps/trunk/source/gui/CProgressBar.cpp (revision 23019) +++ ps/trunk/source/gui/CProgressBar.cpp (revision 23020) @@ -1,74 +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) { - // Important 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.cpp =================================================================== --- ps/trunk/source/gui/CRadioButton.cpp (revision 23019) +++ ps/trunk/source/gui/CRadioButton.cpp (revision 23020) @@ -1,49 +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), IGUIObject(pGUI) + : CCheckBox(pGUI) { } void CRadioButton::HandleMessage(SGUIMessage& Message) { - // Important 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.cpp =================================================================== --- ps/trunk/source/gui/CSlider.cpp (revision 23019) +++ ps/trunk/source/gui/CSlider.cpp (revision 23020) @@ -1,137 +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/CText.cpp =================================================================== --- ps/trunk/source/gui/CText.cpp (revision 23019) +++ ps/trunk/source/gui/CText.cpp (revision 23020) @@ -1,239 +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(pGUI), - IGUITextOwner(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/CText.h =================================================================== --- ps/trunk/source/gui/CText.h (revision 23019) +++ ps/trunk/source/gui/CText.h (revision 23020) @@ -1,88 +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/CGUIString.h" #include "gui/IGUIScrollBarOwner.h" #include "gui/IGUITextOwner.h" /** * Text field that just displays static text. */ -class CText : public IGUIScrollBarOwner, public IGUITextOwner +class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner { GUI_OBJECT(CText) public: CText(CGUI& pGUI); virtual ~CText(); /** * @see IGUIObject#ResetStates() */ - virtual void ResetStates() { IGUIScrollBarOwner::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/CTooltip.cpp =================================================================== --- ps/trunk/source/gui/CTooltip.cpp (revision 23019) +++ ps/trunk/source/gui/CTooltip.cpp (revision 23020) @@ -1,156 +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/CGUIString.h" #include "gui/CGUIText.h" #include CTooltip::CTooltip(CGUI& pGUI) : IGUIObject(pGUI), - IGUITextOwner(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; CClientArea 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/CTooltip.h =================================================================== --- ps/trunk/source/gui/CTooltip.h (revision 23019) +++ ps/trunk/source/gui/CTooltip.h (revision 23020) @@ -1,63 +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/IGUITextOwner.h" #include "gui/CGUISprite.h" #include "gui/CGUIString.h" /** * Dynamic tooltips. Similar to CText. */ -class CTooltip : public IGUITextOwner +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/GUITooltip.cpp =================================================================== --- ps/trunk/source/gui/GUITooltip.cpp (revision 23019) +++ ps/trunk/source/gui/GUITooltip.cpp (revision 23020) @@ -1,344 +1,344 @@ /* 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 "GUITooltip.h" #include "gui/CGUI.h" #include "gui/IGUIObject.h" #include "lib/timer.h" #include "ps/CLogger.h" /* Tooltips: When holding the mouse stationary over an object for some amount of time, the tooltip is displayed. If the mouse moves off that object, the tooltip disappears. If the mouse re-enters an object within a short time, the new tooltip is displayed immediately. (This lets you run the mouse across a series of buttons, without waiting ages for the text to pop up every time.) See Visual Studio's toolbar buttons for an example. Implemented as a state machine: (where "*" lines are checked constantly, and "<" lines are handled on entry to that state) IN MOTION * If the mouse stops, check whether it should have a tooltip and move to 'STATIONARY, NO TOOLTIP' or 'STATIONARY, TOOLIP' * If the mouse enters an object with a tooltip delay of 0, switch to 'SHOWING' STATIONARY, NO TOOLTIP * If the mouse moves, switch to 'IN MOTION' STATIONARY, TOOLTIP < Set target time = now + tooltip time * If the mouse moves, switch to 'IN MOTION' * If now > target time, switch to 'SHOWING' SHOWING < Start displaying the tooltip * If the mouse leaves the object, check whether the new object has a tooltip and switch to 'SHOWING' or 'COOLING' COOLING (since I can't think of a better name) < Stop displaying the tooltip < Set target time = now + cooldown time * If the mouse has moved and is over a tooltipped object, switch to 'SHOWING' * If now > target time, switch to 'STATIONARY, NO TOOLTIP' */ enum { ST_IN_MOTION, ST_STATIONARY_NO_TOOLTIP, ST_STATIONARY_TOOLTIP, ST_SHOWING, ST_COOLING }; GUITooltip::GUITooltip() : m_State(ST_IN_MOTION), m_PreviousObject(nullptr), m_PreviousTooltipName() { } const double CooldownTime = 0.25; // TODO: Don't hard-code this value bool GUITooltip::GetTooltip(IGUIObject* obj, CStr& style) { if (obj && obj->SettingExists("_icon_tooltip_style") && obj->MouseOverIcon()) { style = obj->GetSetting("_icon_tooltip_style"); if (!obj->GetSetting("_icon_tooltip").empty()) { if (style.empty()) style = "default"; m_IsIconTooltip = true; return true; } } if (obj && obj->SettingExists("tooltip_style")) { style = obj->GetSetting("tooltip_style"); if (!obj->GetSetting("tooltip").empty()) { if (style.empty()) style = "default"; m_IsIconTooltip = false; return true; } } return false; } void GUITooltip::ShowTooltip(IGUIObject* obj, const CPos& pos, const CStr& style, CGUI& pGUI) { ENSURE(obj); if (style.empty()) return; - // Must be a CTooltip*, but we avoid dynamic_cast + // Must be a CTooltip* IGUIObject* tooltipobj = pGUI.FindObjectByName("__tooltip_" + style); if (!tooltipobj || !tooltipobj->SettingExists("use_object")) { LOGERROR("Cannot find tooltip named '%s'", style.c_str()); return; } IGUIObject* usedobj; // object actually used to display the tooltip in const CStr& usedObjectName = tooltipobj->GetSetting("use_object"); if (usedObjectName.empty()) { usedobj = tooltipobj; if (usedobj->SettingExists("_mousepos")) { usedobj->SetSetting("_mousepos", pos, true); } else { LOGERROR("Object '%s' used by tooltip '%s' isn't a tooltip object!", usedObjectName.c_str(), style.c_str()); return; } } else { usedobj = pGUI.FindObjectByName(usedObjectName); if (!usedobj) { LOGERROR("Cannot find object named '%s' used by tooltip '%s'", usedObjectName.c_str(), style.c_str()); return; } } if (usedobj->SettingExists("caption")) { const CStrW& text = obj->GetSetting(m_IsIconTooltip ? "_icon_tooltip" : "tooltip"); usedobj->SetSettingFromString("caption", text, true); } else { LOGERROR("Object '%s' used by tooltip '%s' must have a caption setting!", usedobj->GetPresentableName().c_str(), style.c_str()); return; } // Every IGUIObject has a "hidden" setting usedobj->SetSetting("hidden", false, true); } void GUITooltip::HideTooltip(const CStr& style, CGUI& pGUI) { if (style.empty()) return; - // Must be a CTooltip*, but we avoid dynamic_cast + // Must be a CTooltip* IGUIObject* tooltipobj = pGUI.FindObjectByName("__tooltip_" + style); if (!tooltipobj || !tooltipobj->SettingExists("use_object") || !tooltipobj->SettingExists("hide_object")) { LOGERROR("Cannot find tooltip named '%s' or it is not a tooltip", style.c_str()); return; } const CStr& usedObjectName = tooltipobj->GetSetting("use_object"); if (!usedObjectName.empty()) { IGUIObject* usedobj = pGUI.FindObjectByName(usedObjectName); if (usedobj && usedobj->SettingExists("caption")) { usedobj->SetSettingFromString("caption", L"", true); } else { LOGERROR("Object named '%s' used by tooltip '%s' does not exist or does not have a caption setting!", usedObjectName.c_str(), style.c_str()); return; } if (tooltipobj->GetSetting("hide_object")) // Every IGUIObject has a "hidden" setting usedobj->SetSetting("hidden", true, true); } else tooltipobj->SetSetting("hidden", true, true); } static i32 GetTooltipDelay(const CStr& style, CGUI& pGUI) { - // Must be a CTooltip*, but we avoid dynamic_cast + // Must be a CTooltip* IGUIObject* tooltipobj = pGUI.FindObjectByName("__tooltip_" + style); if (!tooltipobj) { LOGERROR("Cannot find tooltip object named '%s'", style.c_str()); return 500; } return tooltipobj->GetSetting("delay"); } void GUITooltip::Update(IGUIObject* Nearest, const CPos& MousePos, CGUI& GUI) { // Called once per frame, so efficiency isn't vital double now = timer_Time(); CStr style; int nextstate = -1; switch (m_State) { case ST_IN_MOTION: if (MousePos == m_PreviousMousePos) { if (GetTooltip(Nearest, style)) nextstate = ST_STATIONARY_TOOLTIP; else nextstate = ST_STATIONARY_NO_TOOLTIP; } else { // Check for movement onto a zero-delayed tooltip if (GetTooltip(Nearest, style) && GetTooltipDelay(style, GUI)==0) { // Reset any previous tooltips completely //m_Time = now + (double)GetTooltipDelay(style, GUI) / 1000.; HideTooltip(m_PreviousTooltipName, GUI); nextstate = ST_SHOWING; } } break; case ST_STATIONARY_NO_TOOLTIP: if (MousePos != m_PreviousMousePos) nextstate = ST_IN_MOTION; break; case ST_STATIONARY_TOOLTIP: if (MousePos != m_PreviousMousePos) nextstate = ST_IN_MOTION; else if (now >= m_Time) { // Make sure the tooltip still exists if (GetTooltip(Nearest, style)) nextstate = ST_SHOWING; else { // Failed to retrieve style - the object has probably been // altered, so just restart the process nextstate = ST_IN_MOTION; } } break; case ST_SHOWING: // Handle special case of icon tooltips if (Nearest == m_PreviousObject && (!m_IsIconTooltip || Nearest->MouseOverIcon())) { // Still showing the same object's tooltip, but the text might have changed if (GetTooltip(Nearest, style)) ShowTooltip(Nearest, MousePos, style, GUI); } else { // Mouse moved onto a new object if (GetTooltip(Nearest, style)) { CStr style_old; // If we're displaying a tooltip with no delay, then we want to // reset so that other object that should have delay can't // "ride this tail", it have to wait. // Notice that this doesn't apply to when you go from one delay=0 // to another delay=0 if (GetTooltip(m_PreviousObject, style_old) && GetTooltipDelay(style_old, GUI) == 0 && GetTooltipDelay(style, GUI) != 0) { HideTooltip(m_PreviousTooltipName, GUI); nextstate = ST_IN_MOTION; } else { // Hide old scrollbar HideTooltip(m_PreviousTooltipName, GUI); nextstate = ST_SHOWING; } } else nextstate = ST_COOLING; } break; case ST_COOLING: if (GetTooltip(Nearest, style)) nextstate = ST_SHOWING; else if (now >= m_Time) nextstate = ST_IN_MOTION; break; } // Handle state-entry code: if (nextstate != -1) { switch (nextstate) { case ST_STATIONARY_TOOLTIP: m_Time = now + (double)GetTooltipDelay(style, GUI) / 1000.; break; case ST_SHOWING: ShowTooltip(Nearest, MousePos, style, GUI); m_PreviousTooltipName = style; break; case ST_COOLING: HideTooltip(m_PreviousTooltipName, GUI); m_Time = now + CooldownTime; break; } m_State = nextstate; } m_PreviousMousePos = MousePos; m_PreviousObject = Nearest; } Index: ps/trunk/source/gui/IGUIButtonBehavior.cpp =================================================================== --- ps/trunk/source/gui/IGUIButtonBehavior.cpp (revision 23019) +++ ps/trunk/source/gui/IGUIButtonBehavior.cpp (revision 23020) @@ -1,143 +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/CGUI.h" #include "gui/CGUISprite.h" -IGUIButtonBehavior::IGUIButtonBehavior(CGUI& pGUI) - : IGUIObject(pGUI), +IGUIButtonBehavior::IGUIButtonBehavior(IGUIObject& pObject) + : m_pObject(pObject), m_Pressed(), m_PressedRight(), m_SoundDisabled(), m_SoundEnter(), m_SoundLeave(), m_SoundPressed(), m_SoundReleased() { - RegisterSetting("sound_disabled", m_SoundDisabled); - RegisterSetting("sound_enter", m_SoundEnter); - RegisterSetting("sound_leave", m_SoundLeave); - RegisterSetting("sound_pressed", m_SoundPressed); - RegisterSetting("sound_released", 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_Enabled) - PlaySound(m_SoundEnter); + if (m_pObject.IsEnabled()) + m_pObject.PlaySound(m_SoundEnter); break; case GUIM_MOUSE_LEAVE: - if (m_Enabled) - PlaySound(m_SoundLeave); + if (m_pObject.IsEnabled()) + m_pObject.PlaySound(m_SoundLeave); break; case GUIM_MOUSE_DBLCLICK_LEFT: - if (!m_Enabled) + if (!m_pObject.IsEnabled()) break; // Since GUIM_MOUSE_PRESS_LEFT also gets called twice in a // doubleclick event, we let it handle playing sounds. - SendEvent(GUIM_DOUBLE_PRESSED, "doublepress"); + m_pObject.SendEvent(GUIM_DOUBLE_PRESSED, "doublepress"); break; case GUIM_MOUSE_PRESS_LEFT: - if (!m_Enabled) + if (!m_pObject.IsEnabled()) { - PlaySound(m_SoundDisabled); + m_pObject.PlaySound(m_SoundDisabled); break; } - PlaySound(m_SoundPressed); - SendEvent(GUIM_PRESSED, "press"); + m_pObject.PlaySound(m_SoundPressed); + m_pObject.SendEvent(GUIM_PRESSED, "press"); m_Pressed = true; break; case GUIM_MOUSE_DBLCLICK_RIGHT: - if (!m_Enabled) + if (!m_pObject.IsEnabled()) break; // Since GUIM_MOUSE_PRESS_RIGHT also gets called twice in a // doubleclick event, we let it handle playing sounds. - SendEvent(GUIM_DOUBLE_PRESSED_MOUSE_RIGHT, "doublepressright"); + m_pObject.SendEvent(GUIM_DOUBLE_PRESSED_MOUSE_RIGHT, "doublepressright"); break; case GUIM_MOUSE_PRESS_RIGHT: - if (!m_Enabled) + if (!m_pObject.IsEnabled()) { - PlaySound(m_SoundDisabled); + m_pObject.PlaySound(m_SoundDisabled); break; } // Button was right-clicked - PlaySound(m_SoundPressed); - SendEvent(GUIM_PRESSED_MOUSE_RIGHT, "pressright"); + m_pObject.PlaySound(m_SoundPressed); + m_pObject.SendEvent(GUIM_PRESSED_MOUSE_RIGHT, "pressright"); m_PressedRight = true; break; case GUIM_MOUSE_RELEASE_RIGHT: - if (!m_Enabled) + if (!m_pObject.IsEnabled()) break; if (m_PressedRight) { m_PressedRight = false; - PlaySound(m_SoundReleased); + m_pObject.PlaySound(m_SoundReleased); } break; case GUIM_MOUSE_RELEASE_LEFT: - if (!m_Enabled) + if (!m_pObject.IsEnabled()) break; if (m_Pressed) { m_Pressed = false; - PlaySound(m_SoundReleased); + 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_Enabled) + if (!m_pObject.IsEnabled()) return sprite_disabled || sprite; - if (!m_MouseHovering) + if (!m_pObject.IsMouseOver()) return sprite; if (m_Pressed) return sprite_pressed || sprite; return sprite_over || sprite; } Index: ps/trunk/source/gui/IGUIButtonBehavior.h =================================================================== --- ps/trunk/source/gui/IGUIButtonBehavior.h (revision 23019) +++ ps/trunk/source/gui/IGUIButtonBehavior.h (revision 23020) @@ -1,90 +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/IGUIObject.h" class CGUISpriteInstance; /** * Appends button behaviours to the IGUIObject. * Can be used with multiple inheritance alongside * IGUISettingsObject and such. */ -class IGUIButtonBehavior : virtual public IGUIObject +class IGUIButtonBehavior { + NONCOPYABLE(IGUIButtonBehavior); + public: - IGUIButtonBehavior(CGUI& pGUI); + 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() - { - // Notify the gui that we aren't hovered anymore - UpdateMouseOver(nullptr); - m_Pressed = false; - m_PressedRight = false; - } + 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/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/IGUIObject.cpp (revision 23019) +++ ps/trunk/source/gui/IGUIObject.cpp (revision 23020) @@ -1,543 +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.GetClientArea(m_pParent->m_CachedActualSize); else m_CachedActualSize = m_Size.GetClientArea(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/IGUIObject.h =================================================================== --- ps/trunk/source/gui/IGUIObject.h (revision 23019) +++ ps/trunk/source/gui/IGUIObject.h (revision 23020) @@ -1,522 +1,526 @@ /* 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/GUIbase.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "lib/input.h" // just for IN_PASS #include "ps/XML/Xeromyces.h" #include #include #include class IGUISetting; /** * 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); /** - * Creates the JS Object representing this page upon first use. - * Can be overridden by derived classes to extend it. + * Inheriting classes may append JS functions to the JS object representing this class. */ - virtual void CreateJSObject(); + 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 */ //-------------------------------------------------------- //@{ - /** - * 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); - 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(); - /** - * Workaround to avoid a dynamic_cast which can be 80 times slower than this. - */ - virtual void* GetTextOwner() { return nullptr; } - 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; /** - * 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); - - /** * 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); - /** - * Retrieves the configured sound filename from the given setting name and plays that once. - */ - void PlaySound(const CStrW& soundPath) const; - //@} 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 vector_pObjects 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; CClientArea 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/IGUIScrollBarOwner.cpp =================================================================== --- ps/trunk/source/gui/IGUIScrollBarOwner.cpp (revision 23019) +++ ps/trunk/source/gui/IGUIScrollBarOwner.cpp (revision 23020) @@ -1,70 +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(CGUI& pGUI) - : IGUIObject(pGUI) +IGUIScrollBarOwner::IGUIScrollBarOwner(IGUIObject& pObject) + : m_pObject(pObject) { } IGUIScrollBarOwner::~IGUIScrollBarOwner() { for (IGUIScrollBar* const& sb : m_ScrollBars) delete sb; } void IGUIScrollBarOwner::ResetStates() { - IGUIObject::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_pGUI.GetScrollBarStyle(style); + 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.cpp =================================================================== --- ps/trunk/source/gui/IGUITextOwner.cpp (revision 23019) +++ ps/trunk/source/gui/IGUITextOwner.cpp (revision 23020) @@ -1,147 +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/CGUIString.h" -#include "gui/scripting/JSInterface_IGUITextOwner.h" #include -IGUITextOwner::IGUITextOwner(CGUI& pGUI) - : IGUIObject(pGUI), m_GeneratedTextsValid(false) +IGUITextOwner::IGUITextOwner(IGUIObject& pObject) + : m_pObject(pObject), + m_GeneratedTextsValid() { } IGUITextOwner::~IGUITextOwner() { } -void IGUITextOwner::CreateJSObject() -{ - IGUIObject::CreateJSObject(); - - JSI_IGUITextOwner::RegisterScriptFunctions( - m_pGUI.GetScriptInterface()->GetContext(), m_JSObject); -} - 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_pGUI, Text, Font, Width, BufferZone, this); + 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() { - // If an ancestor's size changed, this will let us intercept the change and // update our text positions - - IGUIObject::UpdateCachedSize(); m_GeneratedTextsValid = false; } -void IGUITextOwner::DrawText(size_t index, const CGUIColor& color, const CPos& pos, float z, const CRect& clipping) +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_pGUI, color, pos, z, clipping); + 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 (GetSetting("text_valign")) + 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; } } -CSize IGUITextOwner::CalculateTextSize() -{ - if (!m_GeneratedTextsValid) - { - SetupText(); - m_GeneratedTextsValid = true; - } - - if (m_GeneratedTexts.empty()) - return CSize(); - - // GUI Object types that use multiple texts may override this function. - return m_GeneratedTexts[0].GetSize(); -} - bool IGUITextOwner::MouseOverIcon() { return false; } Index: ps/trunk/source/gui/MiniMap.cpp =================================================================== --- ps/trunk/source/gui/MiniMap.cpp (revision 23019) +++ ps/trunk/source/gui/MiniMap.cpp (revision 23020) @@ -1,710 +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 "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/IGUIScrollBarOwner.h =================================================================== --- ps/trunk/source/gui/IGUIScrollBarOwner.h (revision 23019) +++ ps/trunk/source/gui/IGUIScrollBarOwner.h (revision 23020) @@ -1,86 +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/IGUIObject.h" #include struct SGUIScrollBarStyle; class IGUIScrollBar; /** * Base-class this if you want an object to contain * one, or several, scroll-bars. */ -class IGUIScrollBarOwner : virtual public IGUIObject +class IGUIScrollBarOwner { + NONCOPYABLE(IGUIScrollBarOwner); + friend class IGUIScrollBar; public: - IGUIScrollBarOwner(CGUI& pGUI); + 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/IGUITextOwner.h =================================================================== --- ps/trunk/source/gui/IGUITextOwner.h (revision 23019) +++ ps/trunk/source/gui/IGUITextOwner.h (revision 23020) @@ -1,128 +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/IGUIObject.h" -#include "gui/scripting/JSInterface_IGUITextOwner.h" #include struct CGUIColor; class CGUIText; class CGUIString; /** * Framework for handling Output text. */ -class IGUITextOwner : virtual public IGUIObject +class IGUITextOwner { - friend bool JSI_IGUITextOwner::GetTextSize(JSContext* cx, uint argc, JS::Value* vp); + NONCOPYABLE(IGUITextOwner); public: - IGUITextOwner(CGUI& pGUI); + 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); /** - * Subscribe the custom JS methods. - */ - void CreateJSObject(); - - /** * @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(); - /** - * Workaround to avoid a dynamic_cast which can be 80 times slower than this. - */ - virtual void* GetTextOwner() { return this; } - 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: /** - * Calculate the size of the first generated text. + * Reference to the IGUIObject. + * Private, because we don't want to inherit it in multiple classes. */ - CSize CalculateTextSize(); + IGUIObject& m_pObject; }; #endif // INCLUDED_IGUITEXTOWNER