Index: ps/trunk/source/gui/CChart.cpp =================================================================== --- ps/trunk/source/gui/CChart.cpp (revision 23008) +++ ps/trunk/source/gui/CChart.cpp (revision 23009) @@ -1,309 +1,309 @@ /* 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), 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::HandleMessage(SGUIMessage& Message) { // 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, this).GetSize(); + 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/CGUIText.h =================================================================== --- ps/trunk/source/gui/CGUIText.h (revision 23008) +++ ps/trunk/source/gui/CGUIText.h (revision 23009) @@ -1,282 +1,282 @@ /* 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_GUITEXT #define INCLUDED_GUITEXT #include "gui/CGUIColor.h" #include "gui/CGUISprite.h" #include "ps/CStrIntern.h" #include "ps/Shapes.h" #include #include #include class CGUI; class CGUIString; struct SGenerateTextImage; using SGenerateTextImages = std::array, 2>; /** * An CGUIText object is a parsed string, divided into * text-rendering components. Each component, being a * call to the Renderer. For instance, if you by tags * change the color, then the GUI will have to make * individual calls saying it want that color on the * text. * * For instance: * "Hello [b]there[/b] bunny!" * * That without word-wrapping would mean 3 components. * i.e. 3 calls to CRenderer. One drawing "Hello", * one drawing "there" in bold, and one drawing "bunny!". */ class CGUIText { public: /** * A sprite call to the CRenderer */ struct SSpriteCall { // The CGUISpriteInstance makes this uncopyable to avoid invalidating its draw cache NONCOPYABLE(SSpriteCall); MOVABLE(SSpriteCall); SSpriteCall() : m_CellID(0) {} /** * Size and position of sprite */ CRect m_Area; /** * Sprite from global GUI sprite database. */ CGUISpriteInstance m_Sprite; int m_CellID; /** * Tooltip text */ CStrW m_Tooltip; /** * Tooltip style */ CStrW m_TooltipStyle; }; /** * A text call to the CRenderer */ struct STextCall { NONCOPYABLE(STextCall); MOVABLE(STextCall); STextCall() : m_UseCustomColor(false), m_Bold(false), m_Italic(false), m_Underlined(false), m_pSpriteCall(nullptr) {} /** * Position */ CPos m_Pos; /** * Size */ CSize m_Size; /** * The string that is suppose to be rendered. */ CStrW m_String; /** * Use custom color? If true then m_Color is used, * else the color inputted will be used. */ bool m_UseCustomColor; /** * Color setup */ CGUIColor m_Color; /** * Font name */ CStrIntern m_Font; /** * Settings */ bool m_Bold, m_Italic, m_Underlined; /** * *IF* an icon, then this is not nullptr. */ std::list::pointer m_pSpriteCall; }; // The SSpriteCall CGUISpriteInstance makes this uncopyable to avoid invalidating its draw cache. // Also take advantage of exchanging the containers directly with move semantics. NONCOPYABLE(CGUIText); MOVABLE(CGUIText); /** * Generates empty text. */ CGUIText() = default; /** * Generate a CGUIText object from the inputted string. * The function will break down the string and its * tags to calculate exactly which rendering queries * will be sent to the Renderer. Also, horizontal alignment * is taken into acount in this method but NOT vertical alignment. * * @param Text Text to generate CGUIText object from * @param Font Default font, notice both Default color and default font * can be changed by tags. * @param Width Width, 0 if no word-wrapping. * @param BufferZone space between text and edge, and space between text and images. * @param pObject Optional parameter for error output. Used *only* if error parsing fails, * and we need to be able to output which object the error occurred in to aid the user. */ - CGUIText(const CGUI& pGUI, const CGUIString& string, const CStrW& FontW, const float Width, const float BufferZone, const IGUIObject* pObject = nullptr); + CGUIText(const CGUI& pGUI, const CGUIString& string, const CStrW& FontW, const float Width, const float BufferZone, const IGUIObject* pObject); /** * Draw this CGUIText object */ void Draw(CGUI& pGUI, const CGUIColor& DefaultColor, const CPos& pos, const float z, const CRect& clipping) const; const CSize& GetSize() const { return m_Size; } const std::list& GetSpriteCalls() const { return m_SpriteCalls; } const std::vector& GetTextCalls() const { return m_TextCalls; } // Helper functions of the constructor bool ProcessLine( const CGUI& pGUI, const CGUIString& string, const CStrIntern& Font, const IGUIObject* pObject, const SGenerateTextImages& Images, const EAlign align, const float prelim_line_height, const float Width, const float BufferZone, bool& FirstLine, float& x, float& y, int& i, int& from); void SetupSpriteCalls( const CGUI& pGUI, const std::array, 2>& FeedbackImages, const float y, const float Width, const float BufferZone, const int i, const int pos_last_img, SGenerateTextImages& Images); float GetLineOffset( const EAlign align, const float width_range_from, const float width_range_to, const CSize& line_size) const; void ComputeLineRange( const SGenerateTextImages& Images, const float y, const float Width, const float prelim_line_height, float& width_range_from, float& width_range_to) const; void ComputeLineSize( const CGUI& pGUI, const CGUIString& string, const CStrIntern& Font, const bool FirstLine, const float Width, const float width_range_to, const int i, const int temp_from, float& x, CSize& line_size) const; bool AssembleCalls( const CGUI& pGUI, const CGUIString& string, const CStrIntern& Font, const IGUIObject* pObject, const bool FirstLine, const float Width, const float width_range_to, const float dx, const float y, const int temp_from, const int i, float& x, int& from); /** * List of TextCalls, for instance "Hello", "there!" */ std::vector m_TextCalls; /** * List of sprites, or "icons" that should be rendered * along with the text. */ std::list m_SpriteCalls; // list for consistent mem addresses // so that we can point to elements. /** * Width and height of the whole output, used when setting up * scrollbars and such. */ CSize m_Size; }; struct SGenerateTextImage { // The image's starting location in Y float m_YFrom; // The image's end location in Y float m_YTo; // The image width in other words float m_Indentation; void SetupSpriteCall( const bool Left, CGUIText::SSpriteCall& SpriteCall, const float width, const float y, const CSize& Size, const CStr& TextureName, const float BufferZone, const int CellID); }; #endif // INCLUDED_GUITEXT Index: ps/trunk/source/gui/CList.cpp =================================================================== --- ps/trunk/source/gui/CList.cpp (revision 23008) +++ ps/trunk/source/gui/CList.cpp (revision 23009) @@ -1,470 +1,470 @@ /* 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), 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, this); + 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, this); + 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::HandleMessage(SGUIMessage& Message) { IGUIScrollBarOwner::HandleMessage(Message); //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! m_Modified = false; switch (Message.type) { case GUIM_SETTINGS_UPDATED: if (Message.value == "list") SetupText(); // If selected is changed, call "SelectionChange" if (Message.value == "selected") { // TODO: Check range if (m_AutoScroll) UpdateAutoScroll(); // TODO only works if lower-case, shouldn't it be made case sensitive instead? ScriptEvent("selectionchange"); } if (Message.value == "scrollbar") SetupText(); // Update scrollbar if (Message.value == "scrollbar_style") { GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); SetupText(); } break; case GUIM_MOUSE_PRESS_LEFT: { if (!m_Enabled) { PlaySound(m_SoundDisabled); break; } int hovered = GetHoveredItem(); if (hovered == -1) break; SetSetting("selected", hovered, true); UpdateAutoScroll(); PlaySound(m_SoundSelected); if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem) this->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, "mouseleftdoubleclickitem"); else this->SendEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, "mouseleftclickitem"); m_LastItemClickTime = timer_Time(); m_PrevSelectedItem = hovered; break; } case GUIM_MOUSE_LEAVE: { if (m_Hovered == -1) break; SetSetting("hovered", -1, true); ScriptEvent("hoverchange"); break; } case GUIM_MOUSE_OVER: { int hovered = GetHoveredItem(); if (hovered == m_Hovered) break; SetSetting("hovered", hovered, true); ScriptEvent("hoverchange"); break; } case GUIM_LOAD: { GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle); break; } default: break; } IGUITextOwner::HandleMessage(Message); } InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev) { InReaction result = IN_PASS; if (ev->ev.type == SDL_KEYDOWN) { int szChar = ev->ev.key.keysym.sym; switch (szChar) { case SDLK_HOME: SelectFirstElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_END: SelectLastElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_UP: SelectPrevElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_DOWN: SelectNextElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_PAGEUP: GetScrollBar(0).ScrollMinusPlenty(); result = IN_HANDLED; break; case SDLK_PAGEDOWN: GetScrollBar(0).ScrollPlusPlenty(); result = IN_HANDLED; break; default: // Do nothing result = IN_PASS; } } return result; } void CList::Draw() { DrawList(m_Selected, m_Sprite, m_SpriteSelectArea, m_TextColor); } void CList::DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selectarea, const CGUIColor& textcolor) { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner if (m_ScrollBar) IGUIScrollBarOwner::Draw(); { CRect rect = GetListRect(); m_pGUI.DrawSprite(sprite, m_CellID, bz, rect); float scroll = 0.f; if (m_ScrollBar) scroll = GetScrollBar(0).GetPos(); if (selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()) { // Get rectangle of selection: CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); if (rect_sel.top <= rect.bottom && rect_sel.bottom >= rect.top) { if (rect_sel.bottom > rect.bottom) rect_sel.bottom = rect.bottom; if (rect_sel.top < rect.top) rect_sel.top = rect.top; if (m_ScrollBar) { // Remove any overlapping area of the scrollbar. if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && rect_sel.right <= GetScrollBar(0).GetOuterRect().right) rect_sel.right = GetScrollBar(0).GetOuterRect().left; if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && rect_sel.left < GetScrollBar(0).GetOuterRect().right) rect_sel.left = GetScrollBar(0).GetOuterRect().right; } m_pGUI.DrawSprite(sprite_selectarea, m_CellID, bz + 0.05f, rect_sel); } } for (size_t i = 0; i < m_List.m_Items.size(); ++i) { if (m_ItemsYPositions[i+1] - scroll < 0 || m_ItemsYPositions[i] - scroll > rect.GetHeight()) continue; // Clipping area (we'll have to substract the scrollbar) CRect cliparea = GetListRect(); if (m_ScrollBar) { if (cliparea.right > GetScrollBar(0).GetOuterRect().left && cliparea.right <= GetScrollBar(0).GetOuterRect().right) cliparea.right = GetScrollBar(0).GetOuterRect().left; if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && cliparea.left < GetScrollBar(0).GetOuterRect().right) cliparea.left = GetScrollBar(0).GetOuterRect().right; } DrawText(i, textcolor, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz + 0.1f, cliparea); } } } void CList::AddItem(const CStrW& str, const CStrW& data) { CGUIString gui_string; gui_string.SetValue(str); // Do not send a settings-changed message m_List.m_Items.push_back(gui_string); CGUIString data_string; data_string.SetValue(data); m_ListData.m_Items.push_back(data_string); // TODO Temp SetupText(); } bool CList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) { int elmt_item = pFile->GetElementID("item"); if (child.GetNodeName() == elmt_item) { AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); return true; } return false; } void CList::SelectNextElement() { if (m_Selected != static_cast(m_List.m_Items.size()) - 1) { SetSetting("selected", m_Selected + 1, true); PlaySound(m_SoundSelected); } } void CList::SelectPrevElement() { if (m_Selected > 0) { SetSetting("selected", m_Selected - 1, true); PlaySound(m_SoundSelected); } } void CList::SelectFirstElement() { if (m_Selected >= 0) SetSetting("selected", 0, true); } void CList::SelectLastElement() { const int index = static_cast(m_List.m_Items.size()) - 1; if (m_Selected != index) SetSetting("selected", index, true); } void CList::UpdateAutoScroll() { // No scrollbar, no scrolling (at least it's not made to work properly). if (!m_ScrollBar || m_Selected < 0 || static_cast(m_Selected) >= m_ItemsYPositions.size()) return; float scroll = GetScrollBar(0).GetPos(); // Check upper boundary if (m_ItemsYPositions[m_Selected] < scroll) { GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected]); return; // this means, if it wants to align both up and down at the same time // this will have precedence. } // Check lower boundary CRect rect = GetListRect(); if (m_ItemsYPositions[m_Selected+1]-rect.GetHeight() > scroll) GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected+1]-rect.GetHeight()); } int CList::GetHoveredItem() { const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f; const CRect& rect = GetListRect(); CPos mouse = m_pGUI.GetMousePos(); mouse.y += scroll; // Mouse is over scrollbar if (m_ScrollBar && GetScrollBar(0).IsVisible() && mouse.x >= GetScrollBar(0).GetOuterRect().left && mouse.x <= GetScrollBar(0).GetOuterRect().right) return -1; for (size_t i = 0; i < m_List.m_Items.size(); ++i) if (mouse.y >= rect.top + m_ItemsYPositions[i] && mouse.y < rect.top + m_ItemsYPositions[i + 1]) return i; return -1; } Index: ps/trunk/source/gui/COList.cpp =================================================================== --- ps/trunk/source/gui/COList.cpp (revision 23008) +++ ps/trunk/source/gui/COList.cpp (revision 23009) @@ -1,443 +1,443 @@ /* 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, this); + 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, this); + 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, this); + 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/IGUITextOwner.cpp =================================================================== --- ps/trunk/source/gui/IGUITextOwner.cpp (revision 23008) +++ ps/trunk/source/gui/IGUITextOwner.cpp (revision 23009) @@ -1,147 +1,147 @@ /* 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() { } 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, const IGUIObject* pObject) +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, pObject); + m_GeneratedTexts.emplace_back(m_pGUI, Text, Font, Width, BufferZone, this); 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) { if (!m_GeneratedTextsValid) { SetupText(); m_GeneratedTextsValid = true; } 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); } 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")) { 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/IGUITextOwner.h =================================================================== --- ps/trunk/source/gui/IGUITextOwner.h (revision 23008) +++ ps/trunk/source/gui/IGUITextOwner.h (revision 23009) @@ -1,128 +1,128 @@ /* 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 { friend bool JSI_IGUITextOwner::GetTextSize(JSContext* cx, uint argc, JS::Value* vp); public: IGUITextOwner(CGUI& pGUI); 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, const IGUIObject* pObject = nullptr); + 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; /** * 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); /** * Calculate the size of the first generated text. */ CSize CalculateTextSize(); }; #endif // INCLUDED_IGUITEXTOWNER