Index: ps/trunk/source/graphics/Color.cpp =================================================================== --- ps/trunk/source/graphics/Color.cpp (revision 22693) +++ ps/trunk/source/graphics/Color.cpp (revision 22694) @@ -1,147 +1,150 @@ /* 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 "graphics/Color.h" #include "graphics/SColor.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/CStr.h" #if HAVE_SSE # include # include "lib/sysdep/arch/x86_x64/x86_x64.h" #endif static SColor4ub fallback_ConvertRGBColorTo4ub(const RGBColor& src) { SColor4ub result; result.R = clamp(static_cast(src.X * 255), 0, 255); result.G = clamp(static_cast(src.Y * 255), 0, 255); result.B = clamp(static_cast(src.Z * 255), 0, 255); result.A = 255; return result; } // on IA32, this is replaced by an SSE assembly version in ia32.cpp SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src) = fallback_ConvertRGBColorTo4ub; // Assembler-optimized function for color conversion #if HAVE_SSE static SColor4ub sse_ConvertRGBColorTo4ub(const RGBColor& src) { const __m128 zero = _mm_setzero_ps(); const __m128 _255 = _mm_set_ss(255.0f); __m128 r = _mm_load_ss(&src.X); __m128 g = _mm_load_ss(&src.Y); __m128 b = _mm_load_ss(&src.Z); // C = min(255, 255*max(C, 0)) ( == clamp(255*C, 0, 255) ) r = _mm_max_ss(r, zero); g = _mm_max_ss(g, zero); b = _mm_max_ss(b, zero); r = _mm_mul_ss(r, _255); g = _mm_mul_ss(g, _255); b = _mm_mul_ss(b, _255); r = _mm_min_ss(r, _255); g = _mm_min_ss(g, _255); b = _mm_min_ss(b, _255); // convert to integer and combine channels using bit logic int ri = _mm_cvtss_si32(r); int gi = _mm_cvtss_si32(g); int bi = _mm_cvtss_si32(b); return SColor4ub(ri, gi, bi, 0xFF); } #endif void ColorActivateFastImpl() { if(0) { } #if HAVE_SSE else if (x86_x64::Cap(x86_x64::CAP_SSE)) { ConvertRGBColorTo4ub = sse_ConvertRGBColorTo4ub; } #endif else { debug_printf("No SSE available. Slow fallback routines will be used.\n"); } } +/** + * Important: This function does not modify the value if parsing fails. + */ bool CColor::ParseString(const CStr8& value, int defaultAlpha) { const size_t NUM_VALS = 4; int values[NUM_VALS] = { 0, 0, 0, defaultAlpha }; std::stringstream stream; stream.str(value); // Parse each value size_t i; for (i = 0; i < NUM_VALS; ++i) { if (stream.eof()) break; stream >> values[i]; if ((stream.rdstate() & std::stringstream::failbit) != 0) { LOGWARNING("Unable to parse CColor parameters. Your input: '%s'", value.c_str()); return false; } if (values[i] < 0 || values[i] > 255) { LOGWARNING("Invalid value (<0 or >255) when parsing CColor parameters. Your input: '%s'", value.c_str()); return false; } } if (i < 3) { LOGWARNING("Not enough parameters when parsing as CColor. Your input: '%s'", value.c_str()); return false; } if (!stream.eof()) { LOGWARNING("Too many parameters when parsing as CColor. Your input: '%s'", value.c_str()); return false; } r = values[0] / 255.f; g = values[1] / 255.f; b = values[2] / 255.f; a = values[3] / 255.f; return true; } bool CColor::operator==(const CColor& color) const { return r == color.r && g == color.g && b == color.b && a == color.a; } Index: ps/trunk/source/graphics/Color.h =================================================================== --- ps/trunk/source/graphics/Color.h (revision 22693) +++ ps/trunk/source/graphics/Color.h (revision 22694) @@ -1,84 +1,85 @@ /* 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_COLOR #define INCLUDED_COLOR #include "graphics/SColor.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" // Simple defines for 3 and 4 component floating point colors - just map to // corresponding vector types. typedef CVector3D RGBColor; typedef CVector4D RGBAColor; // Convert float RGB(A) colors to unsigned byte. // Exposed as function pointer because it is set at init-time to // one of several implementations depending on CPU caps. extern SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src); // call once ia32_Init has run; detects CPU caps and activates the best // possible codepath. extern void ColorActivateFastImpl(); class CStr8; struct CColor { CColor() : r(-1.f), g(-1.f), b(-1.f), a(1.f) {} CColor(float cr, float cg, float cb, float ca) : r(cr), g(cg), b(cb), a(ca) {} /** * Returns whether this has been set to a valid color. */ operator bool() const { return r >= 0 && g >= 0 && b >= 0 && a >= 0; } /** * Try to parse @p Value as a color. Returns true on success, false otherwise. + * Leaves the color unchanged if it failed. * @param value Should be "r g b" or "r g b a" where each value is an integer in [0,255]. * @param defaultAlpha The alpha value that is used if the format of @p Value is "r g b". */ bool ParseString(const CStr8& value, int defaultAlpha = 255); bool operator==(const CColor& color) const; bool operator!=(const CColor& color) const { return !(*this == color); } // For passing to glColor[34]fv: const float* FloatArray() const { return &r; } // For passing to CRenderer: SColor4ub AsSColor4ub() const { return SColor4ub( static_cast(r * 255.f), static_cast(g * 255.f), static_cast(b * 255.f), static_cast(a * 255.f) ); } float r, g, b, a; }; #endif // INCLUDED_COLOR Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp (revision 22693) +++ ps/trunk/source/gui/CButton.cpp (revision 22694) @@ -1,109 +1,108 @@ /* 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/CGUIColor.h" #include "lib/ogl.h" CButton::CButton(CGUI* pGUI) : IGUIObject(pGUI), IGUIButtonBehavior(pGUI), IGUITextOwner(pGUI) { AddSetting("buffer_zone"); AddSetting("caption"); AddSetting("cell_id"); AddSetting("font"); AddSetting("sound_disabled"); AddSetting("sound_enter"); AddSetting("sound_leave"); AddSetting("sound_pressed"); AddSetting("sound_released"); AddSetting("sprite"); AddSetting("sprite_over"); AddSetting("sprite_pressed"); AddSetting("sprite_disabled"); AddSetting("text_align"); AddSetting("text_valign"); AddSetting("textcolor"); AddSetting("textcolor_over"); AddSetting("textcolor_pressed"); AddSetting("textcolor_disabled"); AddSetting("tooltip"); AddSetting("tooltip_style"); AddText(); } CButton::~CButton() { } void CButton::SetupText() { ENSURE(m_GeneratedTexts.size() == 1); CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Default should not be hard-coded, but be in styles! font = L"default"; const CGUIString& caption = GUI::GetSetting(this, "caption"); float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); m_GeneratedTexts[0] = CGUIText(m_pGUI, caption, font, m_CachedActualSize.GetWidth(), buffer_zone, this); CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]); } void CButton::HandleMessage(SGUIMessage& Message) { // Important IGUIButtonBehavior::HandleMessage(Message); IGUITextOwner::HandleMessage(Message); } void CButton::Draw() { float bz = GetBufferedZ(); int cell_id; // Statically initialise some strings, so we don't have to do // lots of allocation every time this function is called static const CStr strSprite("sprite"); static const CStr strSpriteOver("sprite_over"); static const CStr strSpritePressed("sprite_pressed"); static const CStr strSpriteDisabled("sprite_disabled"); static const CStr strCellId("cell_id"); CGUISpriteInstance& sprite = GUI::GetSetting(this, strSprite); CGUISpriteInstance& sprite_over = GUI::GetSetting(this, strSpriteOver); CGUISpriteInstance& sprite_pressed = GUI::GetSetting(this, strSpritePressed); CGUISpriteInstance& sprite_disabled = GUI::GetSetting(this, strSpriteDisabled); GUI::GetSetting(this, strCellId, cell_id); DrawButton(m_CachedActualSize, bz, sprite, sprite_over, sprite_pressed, sprite_disabled, cell_id); - CGUIColor color = ChooseColor(); - DrawText(0, color, m_TextPos, bz+0.1f); + DrawText(0, ChooseColor(), m_TextPos, bz + 0.1f); } Index: ps/trunk/source/gui/CChart.cpp =================================================================== --- ps/trunk/source/gui/CChart.cpp (revision 22693) +++ ps/trunk/source/gui/CChart.cpp (revision 22694) @@ -1,321 +1,319 @@ /* 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 "gui/CGUIColor.h" #include "gui/CGUIString.h" #include "gui/GUIMatrix.h" #include "graphics/ShaderManager.h" #include "i18n/L10n.h" #include "lib/ogl.h" #include "ps/CLogger.h" #include "renderer/Renderer.h" #include "third_party/cppformat/format.h" #include CChart::CChart(CGUI* pGUI) : IGUIObject(pGUI), IGUITextOwner(pGUI) { AddSetting("axis_color"); AddSetting("axis_width"); AddSetting("buffer_zone"); AddSetting("font"); AddSetting("format_x"); AddSetting("format_y"); AddSetting("series_color"); AddSetting("series"); AddSetting("text_align"); GUI::GetSetting(this, "axis_width", m_AxisWidth); GUI::GetSetting(this, "format_x", m_FormatX); GUI::GetSetting(this, "format_y", m_FormatY); } CChart::~CChart() { } void CChart::HandleMessage(SGUIMessage& Message) { // TODO: implement zoom switch (Message.type) { case GUIM_SETTINGS_UPDATED: { GUI::GetSetting(this, "axis_width", m_AxisWidth); GUI::GetSetting(this, "format_x", m_FormatX); GUI::GetSetting(this, "format_y", m_FormatY); UpdateSeries(); break; } } } void CChart::DrawLine(const CShaderProgramPtr& shader, const 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 - CGUIColor axis_color(0.5f, 0.5f, 0.5f, 1.f); - GUI::GetSetting(this, "axis_color", axis_color); - DrawTriangleStrip(shader, axis_color, vertices); + DrawTriangleStrip(shader, GUI::GetSetting(this, "axis_color"), 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() { const CGUISeries& pSeries = GUI::GetSetting(this, "series"); const CGUIList& pSeriesColor = GUI::GetSetting(this, "series_color"); m_Series.clear(); m_Series.resize(pSeries.m_Series.size()); for (size_t i = 0; i < pSeries.m_Series.size(); ++i) { CChartData& data = m_Series[i]; if (i < pSeriesColor.m_Items.size() && !data.m_Color.ParseString(m_pGUI, pSeriesColor.m_Items[i].GetOriginalString().ToUTF8(), 0)) LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(pSeriesColor.m_Items[i].GetOriginalString())); data.m_Points = pSeries.m_Series[i]; } UpdateBounds(); SetupText(); } void CChart::SetupText() { m_GeneratedTexts.clear(); m_TextPositions.clear(); if (m_Series.empty()) return; CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) font = L"default"; float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); // Add Y-axis GUI::GetSetting(this, "format_y", m_FormatY); const float height = GetChartRect().GetHeight(); // TODO: split values depend on the format; if (m_EqualY) { // We don't need to generate many items for equal values AddFormattedValue(m_FormatY, m_RightTop.Y, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().TopLeft()); } else for (int i = 0; i < 3; ++i) { AddFormattedValue(m_FormatY, m_RightTop.Y - (m_RightTop.Y - m_LeftBottom.Y) / 3.f * i, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().TopLeft() + CPos(0.f, height / 3.f * i)); } // Add X-axis GUI::GetSetting(this, "format_x", m_FormatX); const float width = GetChartRect().GetWidth(); if (m_EqualX) { CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size); } else for (int i = 0; i < 3; ++i) { CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X - (m_RightTop.X - m_LeftBottom.X) / 3 * i, font, buffer_zone); m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size - CPos(width / 3 * i, 0.f)); } } CSize CChart::AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone) { // TODO: we need to catch cases with equal formatted values. CGUIString gui_str; if (format == L"DECIMAL2") { wchar_t buffer[64]; swprintf(buffer, 64, L"%.2f", value); gui_str.SetValue(buffer); } else if (format == L"INTEGER") { wchar_t buffer[64]; swprintf(buffer, 64, L"%d", 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(); } 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/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp (revision 22693) +++ ps/trunk/source/gui/CDropDown.cpp (revision 22694) @@ -1,552 +1,552 @@ /* 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/CGUIColor.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "soundmanager/ISoundManager.h" CDropDown::CDropDown(CGUI* pGUI) : CList(pGUI), IGUIObject(pGUI), m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1) { AddSetting("button_width"); AddSetting("dropdown_size"); AddSetting("dropdown_buffer"); AddSetting("minimum_visible_items"); // AddSetting("sound_closed"); AddSetting("sound_disabled"); AddSetting("sound_enter"); AddSetting("sound_leave"); AddSetting("sound_opened"); AddSetting("sprite"); // Background that sits around the size AddSetting("sprite_disabled"); AddSetting("sprite_list"); // Background of the drop down list AddSetting("sprite2"); // Button that sits to the right AddSetting("sprite2_over"); AddSetting("sprite2_pressed"); AddSetting("sprite2_disabled"); AddSetting("text_valign"); // Add these in CList! And implement TODO //AddSetting("textcolor_over"); //AddSetting("textcolor_pressed"); AddSetting("textcolor_selected"); AddSetting("textcolor_disabled"); // Scrollbar is forced to be true. GUI::SetSetting(this, "scrollbar", true); } CDropDown::~CDropDown() { } void CDropDown::SetupText() { SetupListRect(); CList::SetupText(); } void CDropDown::UpdateCachedSize() { CList::UpdateCachedSize(); SetupText(); } void CDropDown::HandleMessage(SGUIMessage& Message) { // Important 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; bool scrollbar; const CGUIList& pList = GUI::GetSetting(this, "list"); GUI::GetSetting(this, "scrollbar", scrollbar); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); CRect rect = GetListRect(); mouse.y += scroll; int set = -1; for (int i = 0; i < static_cast(pList.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: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_enter", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } case GUIM_MOUSE_LEAVE: { GUI::GetSetting(this, "selected", m_ElementHighlight); bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_leave", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); 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: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_disabled", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } if (!m_Open) { const CGUIList& pList = GUI::GetSetting(this, "list"); if (pList.m_Items.empty()) return; m_Open = true; GetScrollBar(0).SetZ(GetBufferedZ()); GUI::GetSetting(this, "selected", m_ElementHighlight); // Start at the position of the selected item, if possible. GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_opened", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); 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()); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); 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: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) break; ++m_ElementHighlight; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_MOUSE_WHEEL_UP: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight - 1 < 0) break; m_ElementHighlight--; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_LOST_FOCUS: { if (m_Open) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } 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() GUI::SetSetting(this, "selected", m_ElementHighlight); 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(); const CGUIList& pList = GUI::GetSetting(this, "list"); // 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(pList.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(pList.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) { GUI::SetSetting(this, "selected", closest); 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) GUI::GetSetting(this, "selected", m_ElementHighlight); return result; } void CDropDown::SetupListRect() { extern int g_yres; extern float g_GuiScale; float size, buffer, yres; yres = g_yres / g_GuiScale; u32 minimumVisibleItems; GUI::GetSetting(this, "dropdown_size", size); GUI::GetSetting(this, "dropdown_buffer", buffer); GUI::GetSetting(this, "minimum_visible_items", minimumVisibleItems); if (m_ItemsYPositions.empty()) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); m_HideScrollBar = false; } // Too many items so use a scrollbar else if (m_ItemsYPositions.back() > size) { // Place items below if at least some items can be placed below if (m_CachedActualSize.bottom + buffer + size <= yres) m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || m_CachedActualSize.top < yres - m_CachedActualSize.bottom) m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, 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 - buffer - size), m_CachedActualSize.right, m_CachedActualSize.top - buffer); m_HideScrollBar = false; } else { // Enough space below, no scrollbar needed if (m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back() <= yres) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + 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() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || m_CachedActualSize.top < yres - m_CachedActualSize.bottom) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, 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 - buffer - m_ItemsYPositions.back()), m_CachedActualSize.right, m_CachedActualSize.top - buffer); m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + buffer; } } } CRect CDropDown::GetListRect() const { return m_CachedListRect; } bool CDropDown::MouseOver() { 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() { float bz = GetBufferedZ(); float dropdown_size, button_width; GUI::GetSetting(this, "dropdown_size", dropdown_size); GUI::GetSetting(this, "button_width", button_width); int cell_id, selected = 0; - CGUIColor color; bool enabled; GUI::GetSetting(this, "enabled", enabled); CGUISpriteInstance& sprite = GUI::GetSetting(this, enabled ? "sprite" : "sprite_disabled"); CGUISpriteInstance& sprite2 = GUI::GetSetting(this, "sprite2"); GUI::GetSetting(this, "cell_id", cell_id); GUI::GetSetting(this, "selected", selected); - GUI::GetSetting(this, enabled ? "textcolor_selected" : "textcolor_disabled", color); m_pGUI->DrawSprite(sprite, cell_id, bz, m_CachedActualSize); if (button_width > 0.f) { CRect rect(m_CachedActualSize.right-button_width, m_CachedActualSize.top, m_CachedActualSize.right, m_CachedActualSize.bottom); if (!enabled) { CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_disabled"); m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); } else if (m_Open) { CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_pressed"); m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); } else if (m_MouseHovering) { CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_over"); m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); } else m_pGUI->DrawSprite(sprite2, cell_id, bz + 0.05f, rect); } if (selected != -1) // TODO: Maybe check validity completely? { CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right-button_width, m_CachedActualSize.bottom); + const CGUIColor& color = GUI::GetSetting(this, enabled ? "textcolor_selected" : "textcolor_disabled"); + CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); DrawText(selected, color, pos, bz+0.1f, cliparea); } // Disable scrollbar during drawing without sending a setting-changed message bool& scrollbar = GUI::GetSetting(this, "scrollbar"); bool old = scrollbar; if (m_Open) { if (m_HideScrollBar) scrollbar = false; DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); if (m_HideScrollBar) scrollbar = old; } } // When a dropdown list is opened, it needs to be visible above all the other // controls on the page. The only way I can think of to do this is to increase // its z value when opened, so that it's probably on top. float CDropDown::GetBufferedZ() const { float bz = CList::GetBufferedZ(); if (m_Open) return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value else return bz; } Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 22693) +++ ps/trunk/source/gui/CGUI.cpp (revision 22694) @@ -1,1373 +1,1356 @@ /* 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 #include #include "GUI.h" // Types - when including them into the engine. #include "CButton.h" #include "CChart.h" #include "CCheckBox.h" #include "CDropDown.h" #include "CImage.h" #include "CInput.h" #include "CList.h" #include "COList.h" #include "CProgressBar.h" #include "CRadioButton.h" #include "CSlider.h" #include "CText.h" #include "CTooltip.h" #include "MiniMap.h" #include "graphics/FontMetrics.h" #include "graphics/ShaderManager.h" #include "i18n/L10n.h" #include "lib/bits.h" #include "lib/input.h" #include "lib/sysdep/sysdep.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "scripting/ScriptFunctions.h" #include "scriptinterface/ScriptInterface.h" extern int g_yres; const double SELECT_DBLCLICK_RATE = 0.5; const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYUP) { const char* hotkey = static_cast(ev->ev.user.data1); std::map >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) for (IGUIObject* const& obj : it->second) { // Update hotkey status before sending the event, // else the status will be outdated when processing the GUI event. HotkeyInputHandler(ev); ret = IN_HANDLED; if (ev->ev.type == SDL_HOTKEYDOWN) obj->SendEvent(GUIM_PRESSED, "press"); else obj->SendEvent(GUIM_RELEASED, "release"); } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CPos((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale); SGUIMessage msg(GUIM_MOUSE_MOTION); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, msg); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= Bit(ev->ev.button.button); break; default: break; } } // Update m_MousePos (for delayed mouse button events) CPos oldMousePos = m_MousePos; if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale); } // Only one object can be hovered IGUIObject* pNearest = NULL; // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress! try { PROFILE("mouse events"); // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly NULL pNearest = FindObjectUnderMouse(); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: // Focus the clicked object (or focus none if nothing clicked on) SetFocusedObject(pNearest); if (pNearest) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress"); break; case SDL_BUTTON_RIGHT: if (pNearest) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress"); break; default: break; } } else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest) { if (ev->ev.wheel.y < 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown"); else if (ev->ev.wheel.y > 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup"); } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease"); } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease"); } break; } // Reset all states on all visible objects GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ResetStates); // Since the hover state will have been reset, we reload it. GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); } } catch (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::HandleEvent error"); // TODO Gee: Handle } // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~Bit(ev->ev.button.button); break; default: break; } } // Restore m_MousePos (for delayed mouse button events) if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) m_MousePos = oldMousePos; // Handle keys for input boxes if (GetFocusedObject()) { if ((ev->ev.type == SDL_KEYDOWN && ev->ev.key.keysym.sym != SDLK_ESCAPE && !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] && !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) || ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { CStr action = "tick"; GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, action); m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, this); } void CGUI::SendEventToAll(const CStr& EventName) { // janwas 2006-03-03: spoke with Ykkrosh about EventName case. // when registering, case is converted to lower - this avoids surprise // if someone were to get the case wrong and then not notice their // handler is never called. however, until now, the other end // (sending events here) wasn't converting to lower case, // leading to a similar problem. // now fixed; case is irrelevant since all are converted to lower. GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase()); } void CGUI::SendEventToAll(const CStr& EventName, JS::HandleValueArray paramData) { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase(), paramData); } CGUI::CGUI(const shared_ptr& runtime) : m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0) { m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", runtime)); m_ScriptInterface->SetCallbackData(this); GuiScriptingInit(*m_ScriptInterface); m_ScriptInterface->LoadGlobalScripts(); m_BaseObject = new CGUIDummyObject(this); } CGUI::~CGUI() { Destroy(); if (m_BaseObject) delete m_BaseObject; } IGUIObject* CGUI::ConstructObject(const CStr& str) { if (m_ObjectTypes.count(str) > 0) return (*m_ObjectTypes[str])(this); // Error reporting will be handled with the nullptr return. return nullptr; } void CGUI::Initialize() { // Add base types! // You can also add types outside the GUI to extend the flexibility of the GUI. // Pyrogenesis though will have all the object types inserted from here. AddObjectType("empty", &CGUIDummyObject::ConstructObject); AddObjectType("button", &CButton::ConstructObject); AddObjectType("image", &CImage::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); AddObjectType("radiobutton", &CRadioButton::ConstructObject); AddObjectType("progressbar", &CProgressBar::ConstructObject); AddObjectType("minimap", &CMiniMap::ConstructObject); AddObjectType("input", &CInput::ConstructObject); AddObjectType("list", &CList::ConstructObject); AddObjectType("olist", &COList::ConstructObject); AddObjectType("dropdown", &CDropDown::ConstructObject); AddObjectType("tooltip", &CTooltip::ConstructObject); AddObjectType("chart", &CChart::ConstructObject); AddObjectType("slider", &CSlider::ConstructObject); } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); try { // Recurse IGUIObject::Draw() with restriction: hidden // meaning all hidden objects won't call Draw (nor will it recurse its children) GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw); } catch (PSERROR_GUI& e) { LOGERROR("GUI draw error: %s", e.what()); } } void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping)) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (!Sprite) return; // TODO: Clipping? Sprite.Draw(this, Rect, CellID, m_Sprites, Z); } void CGUI::Destroy() { // We can use the map to delete all // now we don't want to cancel all if one Destroy fails for (const std::pair& p : m_pAllObjects) { try { p.second->Destroy(); } catch (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::Destroy error"); // TODO Gee: Handle } delete p.second; } m_pAllObjects.clear(); for (const std::pair& p : m_Sprites) delete p.second; m_Sprites.clear(); m_Icons.clear(); } void CGUI::UpdateResolution() { // Update ALL cached GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize); } void CGUI::AddObject(IGUIObject* pObject) { try { m_BaseObject->AddChild(pObject); // Cache tree GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); SGUIMessage msg(GUIM_LOAD); GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, msg); } catch (PSERROR_GUI&) { throw; } } void CGUI::UpdateObjects() { // We'll fill a temporary map until we know everything succeeded map_pObjects AllObjects; try { // Fill freshly GUI::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects); } catch (PSERROR_GUI&) { throw; } // Else actually update the real one m_pAllObjects.swap(AllObjects); } bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.count(Name) != 0; } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return NULL; else return it->second; } IGUIObject* CGUI::FindObjectUnderMouse() const { IGUIObject* pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } void CGUI::SetFocusedObject(IGUIObject* pObject) { if (pObject == m_FocusedObject) return; if (m_FocusedObject) { SGUIMessage msg(GUIM_LOST_FOCUS); m_FocusedObject->HandleMessage(msg); } m_FocusedObject = pObject; if (m_FocusedObject) { SGUIMessage msg(GUIM_GOT_FOCUS); m_FocusedObject->HandleMessage(msg); } } const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const { std::map::const_iterator it = m_ScrollBarStyles.find(style); if (it == m_ScrollBarStyles.end()) return nullptr; return &it->second; } -bool CGUI::GetPreDefinedColor(const CStr& name, CGUIColor& Output) const -{ - std::map::const_iterator cit = m_PreDefinedColors.find(name); - if (cit == m_PreDefinedColors.end()) - return false; - - Output = cit->second; - return true; -} - /** * @callgraph */ void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set& Paths) { Paths.insert(Filename); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK) return; XMBElement node = XeroFile.GetRoot(); CStr root_name(XeroFile.GetElementString(node.GetNodeName())); try { if (root_name == "objects") { Xeromyces_ReadRootObjects(node, &XeroFile, Paths); // Re-cache all values so these gets cached too. //UpdateResolution(); } else if (root_name == "sprites") Xeromyces_ReadRootSprites(node, &XeroFile); else if (root_name == "styles") Xeromyces_ReadRootStyles(node, &XeroFile); else if (root_name == "setup") Xeromyces_ReadRootSetup(node, &XeroFile); else debug_warn(L"CGUI::LoadXmlFile error"); } catch (PSERROR_GUI& e) { LOGERROR("Errors loading GUI file %s (%u)", Filename.string8(), e.getCode()); return; } } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, boost::unordered_set& Paths) { int el_script = pFile->GetElementID("script"); std::vector > subst; // Iterate main children // they should all be or