Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp (revision 22692) +++ ps/trunk/source/gui/CButton.cpp (revision 22693) @@ -1,119 +1,109 @@ /* 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"; - CGUIString* caption = nullptr; - GUI::GetSettingPointer(this, "caption", caption); + 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); + 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(); - CGUISpriteInstance* sprite; - CGUISpriteInstance* sprite_over; - CGUISpriteInstance* sprite_pressed; - CGUISpriteInstance* sprite_disabled; 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"); - GUI::GetSettingPointer(this, strSprite, sprite); - GUI::GetSettingPointer(this, strSpriteOver, sprite_over); - GUI::GetSettingPointer(this, strSpritePressed, sprite_pressed); - GUI::GetSettingPointer(this, strSpriteDisabled, sprite_disabled); + 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); + DrawButton(m_CachedActualSize, bz, sprite, sprite_over, sprite_pressed, sprite_disabled, cell_id); CGUIColor color = ChooseColor(); DrawText(0, color, m_TextPos, bz+0.1f); } Index: ps/trunk/source/gui/CChart.cpp =================================================================== --- ps/trunk/source/gui/CChart.cpp (revision 22692) +++ ps/trunk/source/gui/CChart.cpp (revision 22693) @@ -1,324 +1,321 @@ /* 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); } 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() { - CGUISeries* pSeries; - GUI::GetSettingPointer(this, "series", pSeries); - - CGUIList* pSeriesColor; - GUI::GetSettingPointer(this, "series_color", pSeriesColor); + 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) + 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())); + 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]; + 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/CCheckBox.cpp =================================================================== --- ps/trunk/source/gui/CCheckBox.cpp (revision 22692) +++ ps/trunk/source/gui/CCheckBox.cpp (revision 22693) @@ -1,147 +1,131 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CCheckBox.h" #include "gui/CGUIColor.h" #include "graphics/FontMetrics.h" #include "ps/CLogger.h" #include "ps/CStrIntern.h" /** * TODO: Since there is no call to DrawText, the checkbox won't render any text. * Thus the font, caption, textcolor and other settings have no effect. */ CCheckBox::CCheckBox(CGUI* pGUI) : IGUIObject(pGUI), IGUITextOwner(pGUI), IGUIButtonBehavior(pGUI) { AddSetting("buffer_zone"); AddSetting("caption"); AddSetting("cell_id"); AddSetting("checked"); 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("sprite2"); AddSetting("sprite2_over"); AddSetting("sprite2_pressed"); AddSetting("sprite2_disabled"); AddSetting("square_side"); AddSetting("textcolor"); AddSetting("textcolor_over"); AddSetting("textcolor_pressed"); AddSetting("textcolor_disabled"); AddSetting("tooltip"); AddSetting("tooltip_style"); AddText(); } CCheckBox::~CCheckBox() { } void CCheckBox::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"; float square_side; GUI::GetSetting(this, "square_side", square_side); - CGUIString* caption = nullptr; - GUI::GetSettingPointer(this, "caption", caption); + 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() - square_side, 0.f, this); + m_GeneratedTexts[0] = CGUIText(m_pGUI, caption, font, m_CachedActualSize.GetWidth() - square_side, 0.f, this); } void CCheckBox::HandleMessage(SGUIMessage& Message) { // Important IGUIButtonBehavior::HandleMessage(Message); IGUITextOwner::HandleMessage(Message); switch (Message.type) { case GUIM_PRESSED: { bool checked; // Switch to opposite. GUI::GetSetting(this, "checked", checked); checked = !checked; GUI::SetSetting(this, "checked", checked); break; } default: break; } } void CCheckBox::Draw() { - float bz = GetBufferedZ(); - bool checked; - int cell_id; - CGUISpriteInstance* sprite; - CGUISpriteInstance* sprite_over; - CGUISpriteInstance* sprite_pressed; - CGUISpriteInstance* sprite_disabled; - - GUI::GetSetting(this, "checked", checked); - GUI::GetSetting(this, "cell_id", cell_id); - - if (checked) - { - GUI::GetSettingPointer(this, "sprite2", sprite); - GUI::GetSettingPointer(this, "sprite2_over", sprite_over); - GUI::GetSettingPointer(this, "sprite2_pressed", sprite_pressed); - GUI::GetSettingPointer(this, "sprite2_disabled", sprite_disabled); - } + if (GUI::GetSetting(this, "checked")) + DrawButton( + m_CachedActualSize, + GetBufferedZ(), + GUI::GetSetting(this, "sprite2"), + GUI::GetSetting(this, "sprite2_over"), + GUI::GetSetting(this, "sprite2_pressed"), + GUI::GetSetting(this, "sprite2_disabled"), + GUI::GetSetting(this, "cell_id")); else - { - GUI::GetSettingPointer(this, "sprite", sprite); - GUI::GetSettingPointer(this, "sprite_over", sprite_over); - GUI::GetSettingPointer(this, "sprite_pressed", sprite_pressed); - GUI::GetSettingPointer(this, "sprite_disabled", sprite_disabled); - } - - DrawButton(m_CachedActualSize, - bz, - *sprite, - *sprite_over, - *sprite_pressed, - *sprite_disabled, - cell_id); + DrawButton( + m_CachedActualSize, + GetBufferedZ(), + GUI::GetSetting(this, "sprite"), + GUI::GetSetting(this, "sprite_over"), + GUI::GetSetting(this, "sprite_pressed"), + GUI::GetSetting(this, "sprite_disabled"), + GUI::GetSetting(this, "cell_id")); } Index: ps/trunk/source/gui/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp (revision 22692) +++ ps/trunk/source/gui/CDropDown.cpp (revision 22693) @@ -1,559 +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; - CGUIList* pList; + const CGUIList& pList = GUI::GetSetting(this, "list"); GUI::GetSetting(this, "scrollbar", scrollbar); - GUI::GetSettingPointer(this, "list", pList); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); CRect rect = GetListRect(); mouse.y += scroll; int set = -1; - for (int i = 0; i < (int)pList->m_Items.size(); ++i) + 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) { - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); - if (pList->m_Items.empty()) + 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(); - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); + 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 < (int)pList->m_Items.size(); ++i) + 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((int)(pList->m_Items[i].GetRawString().LowerCase()[j]) - (int)m_InputBuffer[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); - CGUISpriteInstance* sprite; - CGUISpriteInstance* sprite2; - CGUISpriteInstance* sprite2_second; int cell_id, selected = 0; CGUIColor color; bool enabled; GUI::GetSetting(this, "enabled", enabled); - GUI::GetSettingPointer(this, "sprite2", sprite2); + 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); - GUI::GetSettingPointer(this, enabled ? "sprite" : "sprite_disabled", sprite); - m_pGUI->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); + 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) { - GUI::GetSettingPointer(this, "sprite2_disabled", sprite2_second); - m_pGUI->DrawSprite(*sprite2_second || *sprite2, cell_id, bz + 0.05f, rect); + CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_disabled"); + m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); } else if (m_Open) { - GUI::GetSettingPointer(this, "sprite2_pressed", sprite2_second); - m_pGUI->DrawSprite(*sprite2_second || *sprite2, cell_id, bz + 0.05f, rect); + CGUISpriteInstance& sprite2_second = GUI::GetSetting(this, "sprite2_pressed"); + m_pGUI->DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); } else if (m_MouseHovering) { - GUI::GetSettingPointer(this, "sprite2_over", sprite2_second); - m_pGUI->DrawSprite(*sprite2_second || *sprite2, cell_id, bz + 0.05f, rect); + 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); + 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); CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); DrawText(selected, color, pos, bz+0.1f, cliparea); } - bool* scrollbar = NULL; - bool old; - GUI::GetSettingPointer(this, "scrollbar", scrollbar); - - old = *scrollbar; + // 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; + scrollbar = false; DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); if (m_HideScrollBar) - *scrollbar = old; + 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/CImage.cpp =================================================================== --- ps/trunk/source/gui/CImage.cpp (revision 22692) +++ ps/trunk/source/gui/CImage.cpp (revision 22693) @@ -1,49 +1,48 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CImage.h" #include "GUI.h" #include "lib/ogl.h" CImage::CImage(CGUI* pGUI) : IGUIObject(pGUI) { AddSetting("sprite"); AddSetting("cell_id"); AddSetting("tooltip"); AddSetting("tooltip_style"); } CImage::~CImage() { } void CImage::Draw() { float bz = GetBufferedZ(); - CGUISpriteInstance* sprite; int cell_id; - GUI::GetSettingPointer(this, "sprite", sprite); GUI::GetSetting(this, "cell_id", cell_id); - GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); + CGUISpriteInstance& sprite = GUI::GetSetting(this, "sprite"); + m_pGUI->DrawSprite(sprite, cell_id, bz, m_CachedActualSize); } Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 22692) +++ ps/trunk/source/gui/CInput.cpp (revision 22693) @@ -1,2141 +1,2130 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CInput.h" #include "gui/CGUIScrollBarVertical.h" #include "gui/GUI.h" #include "graphics/FontMetrics.h" #include "graphics/ShaderManager.h" #include "graphics/TextRenderer.h" #include "lib/ogl.h" #include "lib/sysdep/clipboard.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/GameSetup/Config.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "renderer/Renderer.h" #include extern int g_yres; CInput::CInput(CGUI* pGUI) : IGUIObject(pGUI), IGUIScrollBarOwner(pGUI), m_iBufferPos(-1), m_iBufferPos_Tail(-1), m_SelectingText(false), m_HorizontalScroll(0.f), m_PrevTime(0.0), m_CursorVisState(true), m_CursorBlinkRate(0.5), m_ComposingText(false), m_iComposedLength(0), m_iComposedPos(0), m_iInsertPos(0), m_Readonly(false) { AddSetting("buffer_position"); AddSetting("buffer_zone"); AddSetting("caption"); AddSetting("cell_id"); AddSetting("font"); AddSetting("mask_char"); AddSetting("mask"); AddSetting("max_length"); AddSetting("multiline"); AddSetting("readonly"); AddSetting("scrollbar"); AddSetting("scrollbar_style"); AddSetting("sprite"); AddSetting("sprite_selectarea"); AddSetting("textcolor"); AddSetting("textcolor_selected"); AddSetting("tooltip"); AddSetting("tooltip_style"); CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate); CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); bar->SetRightAligned(true); AddScrollBar(bar); } CInput::~CInput() { } void CInput::UpdateBufferPositionSetting() { GUI::SetSetting(this, "buffer_position", m_iBufferPos, true); } void CInput::ClearComposedText() { - CStrW* pCaption = nullptr; - GUI::GetSettingPointer(this, "caption", pCaption); - - pCaption->erase(m_iInsertPos, m_iComposedLength); + CStrW& pCaption = GUI::GetSetting(this, "caption"); + pCaption.erase(m_iInsertPos, m_iComposedLength); m_iBufferPos = m_iInsertPos; UpdateBufferPositionSetting(); m_iComposedLength = 0; m_iComposedPos = 0; } InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) { ENSURE(m_iBufferPos != -1); switch (ev->ev.type) { case SDL_HOTKEYDOWN: { if (m_ComposingText) return IN_HANDLED; return ManuallyHandleHotkeyEvent(ev); } // SDL2 has a new method of text input that better supports Unicode and CJK // see https://wiki.libsdl.org/Tutorials/TextInput case SDL_TEXTINPUT: { if (m_Readonly) return IN_PASS; // Text has been committed, either single key presses or through an IME - CStrW* pCaption = nullptr; - GUI::GetSettingPointer(this, "caption", pCaption); + CStrW& pCaption = GUI::GetSetting(this, "caption"); std::wstring text = wstring_from_utf8(ev->ev.text.text); m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); if (m_ComposingText) { ClearComposedText(); m_ComposingText = false; } - if (m_iBufferPos == (int)pCaption->length()) - pCaption->append(text); + if (m_iBufferPos == static_cast(pCaption.length())) + pCaption.append(text); else - pCaption->insert(m_iBufferPos, text); + pCaption.insert(m_iBufferPos, text); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); m_iBufferPos += text.length(); UpdateBufferPositionSetting(); m_iBufferPos_Tail = -1; UpdateAutoScroll(); SendEvent(GUIM_TEXTEDIT, "textedit"); return IN_HANDLED; } case SDL_TEXTEDITING: { if (m_Readonly) return IN_PASS; // Text is being composed with an IME // TODO: indicate this by e.g. underlining the uncommitted text - CStrW* pCaption = nullptr; - GUI::GetSettingPointer(this, "caption", pCaption); + CStrW& pCaption = GUI::GetSetting(this, "caption"); const char* rawText = ev->ev.edit.text; int rawLength = strlen(rawText); std::wstring wtext = wstring_from_utf8(rawText); debug_printf("SDL_TEXTEDITING: text=%s, start=%d, length=%d\n", rawText, ev->ev.edit.start, ev->ev.edit.length); m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); // Remember cursor position when text composition begins if (!m_ComposingText) m_iInsertPos = m_iBufferPos; else { // Composed text is replaced each time ClearComposedText(); } m_ComposingText = ev->ev.edit.start != 0 || rawLength != 0; if (m_ComposingText) { - pCaption->insert(m_iInsertPos, wtext); + pCaption.insert(m_iInsertPos, wtext); // The text buffer is limited to SDL_TEXTEDITINGEVENT_TEXT_SIZE bytes, yet start // increases without limit, so don't let it advance beyond the composed text length m_iComposedLength = wtext.length(); m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength; m_iBufferPos = m_iInsertPos + m_iComposedPos; // TODO: composed text selection - what does ev.edit.length do? m_iBufferPos_Tail = -1; } UpdateBufferPositionSetting(); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); UpdateAutoScroll(); SendEvent(GUIM_TEXTEDIT, "textedit"); return IN_HANDLED; } case SDL_KEYDOWN: { if (m_ComposingText) return IN_HANDLED; // Since the GUI framework doesn't handle to set settings // in Unicode (CStrW), we'll simply retrieve the actual // pointer and edit that. - CStrW* pCaption = nullptr; - GUI::GetSettingPointer(this, "caption", pCaption); + CStrW& pCaption = GUI::GetSetting(this, "caption"); SDL_Keycode keyCode = ev->ev.key.keysym.sym; ManuallyImmutableHandleKeyDownEvent(keyCode, pCaption); ManuallyMutableHandleKeyDownEvent(keyCode, pCaption); UpdateBufferPositionSetting(); return IN_HANDLED; } default: { return IN_PASS; } } } -void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW* pCaption) +void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW& pCaption) { if (m_Readonly) return; wchar_t cooked = 0; switch (keyCode) { case SDLK_TAB: { SendEvent(GUIM_TAB, "tab"); // Don't send a textedit event, because it should only // be sent if the GUI control changes the text break; } case SDLK_BACKSPACE: { m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); else { m_iBufferPos_Tail = -1; - if (pCaption->empty() || m_iBufferPos == 0) + if (pCaption.empty() || m_iBufferPos == 0) break; - if (m_iBufferPos == (int)pCaption->length()) - *pCaption = pCaption->Left((long)pCaption->length() - 1); + if (m_iBufferPos == static_cast(pCaption.length())) + pCaption = pCaption.Left(static_cast(pCaption.length()) - 1); else - *pCaption = pCaption->Left(m_iBufferPos - 1) + - pCaption->Right((long)pCaption->length() - m_iBufferPos); + pCaption = + pCaption.Left(m_iBufferPos - 1) + + pCaption.Right(static_cast(pCaption.length()) - m_iBufferPos); --m_iBufferPos; UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); } UpdateAutoScroll(); SendEvent(GUIM_TEXTEDIT, "textedit"); break; } case SDLK_DELETE: { m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); else { - if (pCaption->empty() || m_iBufferPos == (int)pCaption->length()) + if (pCaption.empty() || m_iBufferPos == static_cast(pCaption.length())) break; - *pCaption = pCaption->Left(m_iBufferPos) + - pCaption->Right((long)pCaption->length() - (m_iBufferPos + 1)); + pCaption = + pCaption.Left(m_iBufferPos) + + pCaption.Right(static_cast(pCaption.length()) - (m_iBufferPos + 1)); UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); } UpdateAutoScroll(); SendEvent(GUIM_TEXTEDIT, "textedit"); break; } case SDLK_KP_ENTER: case SDLK_RETURN: { // 'Return' should do a Press event for single liners (e.g. submitting forms) // otherwise a '\n' character will be added. bool multiline; GUI::GetSetting(this, "multiline", multiline); if (!multiline) { SendEvent(GUIM_PRESSED, "press"); break; } cooked = '\n'; // Change to '\n' and do default: FALLTHROUGH; } default: // Insert a character { // In SDL2, we no longer get Unicode wchars via SDL_Keysym // we use text input events instead and they provide UTF-8 chars if (cooked == 0) return; // check max length int max_length; GUI::GetSetting(this, "max_length", max_length); - if (max_length != 0 && (int)pCaption->length() >= max_length) + if (max_length != 0 && static_cast(pCaption.length()) >= max_length) break; m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); m_iBufferPos_Tail = -1; - if (m_iBufferPos == (int)pCaption->length()) - *pCaption += cooked; + if (m_iBufferPos == static_cast(pCaption.length())) + pCaption += cooked; else - *pCaption = pCaption->Left(m_iBufferPos) + cooked + - pCaption->Right((long)pCaption->length() - m_iBufferPos); + pCaption = + pCaption.Left(m_iBufferPos) + cooked + + pCaption.Right(static_cast(pCaption.length()) - m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1); ++m_iBufferPos; UpdateAutoScroll(); SendEvent(GUIM_TEXTEDIT, "textedit"); break; } } } -void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW* pCaption) +void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW& pCaption) { bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; switch (keyCode) { case SDLK_HOME: { // If there's not a selection, we should create one now if (!shiftKeyPressed) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } m_iBufferPos = 0; m_WantedX = 0.0f; UpdateAutoScroll(); break; } case SDLK_END: { // If there's not a selection, we should create one now if (!shiftKeyPressed) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } - m_iBufferPos = (long)pCaption->length(); + m_iBufferPos = static_cast(pCaption.length()); m_WantedX = 0.0f; UpdateAutoScroll(); break; } /** * Conventions for Left/Right when text is selected: * * References: * * Visual Studio * Visual Studio has the 'newer' approach, used by newer versions of * things, and in newer applications. A left press will always place * the pointer on the left edge of the selection, and then of course * remove the selection. Right will do the exact same thing. * If you have the pointer on the right edge and press right, it will * in other words just remove the selection. * * Windows (eg. Notepad) * A left press always takes the pointer a step to the left and * removes the selection as if it were never there in the first place. * Right of course does the same thing but to the right. * * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN * Messenger. */ case SDLK_LEFT: { m_WantedX = 0.f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) m_iBufferPos_Tail = -1; else if (!SelectingText()) m_iBufferPos_Tail = m_iBufferPos; if (m_iBufferPos > 0) --m_iBufferPos; } else { if (m_iBufferPos_Tail < m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); break; } case SDLK_RIGHT: { m_WantedX = 0.0f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) m_iBufferPos_Tail = -1; else if (!SelectingText()) m_iBufferPos_Tail = m_iBufferPos; - if (m_iBufferPos < (int)pCaption->length()) + if (m_iBufferPos < static_cast(pCaption.length())) ++m_iBufferPos; } else { if (m_iBufferPos_Tail > m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); break; } /** * Conventions for Up/Down when text is selected: * * References: * * Visual Studio * Visual Studio has a very strange approach, down takes you below the * selection to the next row, and up to the one prior to the whole * selection. The weird part is that it is always aligned as the * 'pointer'. I decided this is to much work for something that is * a bit arbitrary * * Windows (eg. Notepad) * Just like with left/right, the selection is destroyed and it moves * just as if there never were a selection. * * I chose the Notepad convention even though I use the VS convention with * left/right. */ case SDLK_UP: { if (!shiftKeyPressed) m_iBufferPos_Tail = -1; else if (!SelectingText()) m_iBufferPos_Tail = m_iBufferPos; std::list::iterator current = m_CharacterPositions.begin(); while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) break; ++current; } float pos_x; if (m_iBufferPos - current->m_ListStart == 0) pos_x = 0.f; else pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; if (m_WantedX > pos_x) pos_x = m_WantedX; // Now change row: if (current != m_CharacterPositions.begin()) { --current; // Find X-position: m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } // else we can't move up UpdateAutoScroll(); break; } case SDLK_DOWN: { if (!shiftKeyPressed) m_iBufferPos_Tail = -1; else if (!SelectingText()) m_iBufferPos_Tail = m_iBufferPos; std::list::iterator current = m_CharacterPositions.begin(); while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) break; ++current; } float pos_x; if (m_iBufferPos - current->m_ListStart == 0) pos_x = 0.f; else pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; if (m_WantedX > pos_x) pos_x = m_WantedX; // Now change row: // Add first, so we can check if it's .end() ++current; if (current != m_CharacterPositions.end()) { // Find X-position: m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } // else we can't move up UpdateAutoScroll(); break; } case SDLK_PAGEUP: { GetScrollBar(0).ScrollMinusPlenty(); UpdateAutoScroll(); break; } case SDLK_PAGEDOWN: { GetScrollBar(0).ScrollPlusPlenty(); UpdateAutoScroll(); break; } default: { break; } } } InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev) { - CStrW* pCaption = nullptr; - GUI::GetSettingPointer(this, "caption", pCaption); + CStrW& pCaption = GUI::GetSetting(this, "caption"); bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "paste") { if (m_Readonly) return IN_PASS; m_WantedX = 0.0f; wchar_t* text = sys_clipboard_get(); if (text) { if (SelectingText()) DeleteCurSelection(); - if (m_iBufferPos == (int)pCaption->length()) - *pCaption += text; + if (m_iBufferPos == static_cast(pCaption.length())) + pCaption += text; else - *pCaption = pCaption->Left(m_iBufferPos) + text + - pCaption->Right((long) pCaption->length()-m_iBufferPos); + pCaption = + pCaption.Left(m_iBufferPos) + text + + pCaption.Right(static_cast(pCaption.length()) - m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); m_iBufferPos += (int)wcslen(text); UpdateAutoScroll(); UpdateBufferPositionSetting(); sys_clipboard_free(text); SendEvent(GUIM_TEXTEDIT, "textedit"); } return IN_HANDLED; } else if (hotkey == "copy" || hotkey == "cut") { if (m_Readonly && hotkey == "cut") return IN_PASS; m_WantedX = 0.0f; if (SelectingText()) { int virtualFrom; int virtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { virtualFrom = m_iBufferPos; virtualTo = m_iBufferPos_Tail; } else { virtualFrom = m_iBufferPos_Tail; virtualTo = m_iBufferPos; } - CStrW text = (pCaption->Left(virtualTo)).Right(virtualTo - virtualFrom); + CStrW text = (pCaption.Left(virtualTo)).Right(virtualTo - virtualFrom); sys_clipboard_set(&text[0]); if (hotkey == "cut") { DeleteCurSelection(); UpdateAutoScroll(); SendEvent(GUIM_TEXTEDIT, "textedit"); } } return IN_HANDLED; } else if (hotkey == "text.delete.left") { if (m_Readonly) return IN_PASS; m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); - if (!pCaption->empty() && m_iBufferPos != 0) + if (!pCaption.empty() && m_iBufferPos != 0) { m_iBufferPos_Tail = m_iBufferPos; - CStrW searchString = pCaption->Left(m_iBufferPos); + CStrW searchString = pCaption.Left(m_iBufferPos); // If we are starting in whitespace, adjust position until we get a non whitespace while (m_iBufferPos > 0) { if (!iswspace(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } // If we end up on a punctuation char we just delete it (treat punct like a word) if (iswpunct(searchString[m_iBufferPos - 1])) m_iBufferPos--; else { // Now we are on a non white space character, adjust position to char after next whitespace char is found while (m_iBufferPos > 0) { if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } } UpdateBufferPositionSetting(); DeleteCurSelection(); SendEvent(GUIM_TEXTEDIT, "textedit"); } UpdateAutoScroll(); return IN_HANDLED; } else if (hotkey == "text.delete.right") { if (m_Readonly) return IN_PASS; m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); - if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) + if (!pCaption.empty() && m_iBufferPos < static_cast(pCaption.length())) { // Delete the word to the right of the cursor m_iBufferPos_Tail = m_iBufferPos; // Delete chars to the right unit we hit whitespace - while (++m_iBufferPos < (int)pCaption->length()) + while (++m_iBufferPos < static_cast(pCaption.length())) { - if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) + if (iswspace(pCaption[m_iBufferPos]) || iswpunct(pCaption[m_iBufferPos])) break; } // Eliminate any whitespace behind the word we just deleted - while (m_iBufferPos < (int)pCaption->length()) + while (m_iBufferPos < static_cast(pCaption.length())) { - if (!iswspace((*pCaption)[m_iBufferPos])) + if (!iswspace(pCaption[m_iBufferPos])) break; ++m_iBufferPos; } UpdateBufferPositionSetting(); DeleteCurSelection(); } UpdateAutoScroll(); SendEvent(GUIM_TEXTEDIT, "textedit"); return IN_HANDLED; } else if (hotkey == "text.move.left") { m_WantedX = 0.0f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) m_iBufferPos_Tail = -1; else if (!SelectingText()) m_iBufferPos_Tail = m_iBufferPos; - if (!pCaption->empty() && m_iBufferPos != 0) + if (!pCaption.empty() && m_iBufferPos != 0) { - CStrW searchString = pCaption->Left(m_iBufferPos); + CStrW searchString = pCaption.Left(m_iBufferPos); // If we are starting in whitespace, adjust position until we get a non whitespace while (m_iBufferPos > 0) { if (!iswspace(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } // If we end up on a puctuation char we just select it (treat punct like a word) if (iswpunct(searchString[m_iBufferPos - 1])) m_iBufferPos--; else { // Now we are on a non white space character, adjust position to char after next whitespace char is found while (m_iBufferPos > 0) { if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } } } } else { if (m_iBufferPos_Tail < m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateBufferPositionSetting(); UpdateAutoScroll(); return IN_HANDLED; } else if (hotkey == "text.move.right") { m_WantedX = 0.0f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) m_iBufferPos_Tail = -1; else if (!SelectingText()) m_iBufferPos_Tail = m_iBufferPos; - if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) + if (!pCaption.empty() && m_iBufferPos < static_cast(pCaption.length())) { // Select chars to the right until we hit whitespace - while (++m_iBufferPos < (int)pCaption->length()) + while (++m_iBufferPos < static_cast(pCaption.length())) { - if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) + if (iswspace(pCaption[m_iBufferPos]) || iswpunct(pCaption[m_iBufferPos])) break; } // Also select any whitespace following the word we just selected - while (m_iBufferPos < (int)pCaption->length()) + while (m_iBufferPos < static_cast(pCaption.length())) { - if (!iswspace((*pCaption)[m_iBufferPos])) + if (!iswspace(pCaption[m_iBufferPos])) break; ++m_iBufferPos; } } } else { if (m_iBufferPos_Tail > m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateBufferPositionSetting(); UpdateAutoScroll(); return IN_HANDLED; } return IN_PASS; } void CInput::HandleMessage(SGUIMessage& Message) { IGUIScrollBarOwner::HandleMessage(Message); switch (Message.type) { case GUIM_SETTINGS_UPDATED: { bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); // Update scroll-bar // TODO Gee: (2004-09-01) Is this really updated each time it should? if (scrollbar && (Message.value == CStr("size") || Message.value == CStr("z") || Message.value == CStr("absolute"))) { GetScrollBar(0).SetX(m_CachedActualSize.right); GetScrollBar(0).SetY(m_CachedActualSize.top); GetScrollBar(0).SetZ(GetBufferedZ()); GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); } // Update scrollbar if (Message.value == CStr("scrollbar_style")) { CStr scrollbar_style; GUI::GetSetting(this, Message.value, scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); } if (Message.value == CStr("buffer_position")) { GUI::GetSetting(this, Message.value, m_iBufferPos); m_iBufferPos_Tail = -1; // position change resets selection } if (Message.value == CStr("size") || Message.value == CStr("z") || Message.value == CStr("font") || Message.value == CStr("absolute") || Message.value == CStr("caption") || Message.value == CStr("scrollbar") || Message.value == CStr("scrollbar_style")) { UpdateText(); } if (Message.value == CStr("multiline")) { bool multiline; GUI::GetSetting(this, "multiline", multiline); if (!multiline) GetScrollBar(0).SetLength(0.f); else GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); UpdateText(); } UpdateAutoScroll(); if (Message.value == CStr("readonly")) GUI::GetSetting(this, "readonly", m_Readonly); break; } case GUIM_MOUSE_PRESS_LEFT: { bool scrollbar, multiline; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "multiline", multiline); // Check if we're selecting the scrollbar if (GetScrollBar(0).GetStyle() && multiline) { if (m_pGUI->GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width) break; } if (m_ComposingText) break; // Okay, this section is about pressing the mouse and // choosing where the point should be placed. For // instance, if we press between a and b, the point // should of course be placed accordingly. Other // special cases are handled like the input box norms. if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]) m_iBufferPos = GetMouseHoveringTextPosition(); else m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); m_SelectingText = true; UpdateAutoScroll(); // If we immediately release the button it will just be seen as a click // for the user though. break; } case GUIM_MOUSE_DBLCLICK_LEFT: { if (m_ComposingText) break; - CStrW* pCaption = nullptr; - GUI::GetSettingPointer(this, "caption", pCaption); + const CStrW& pCaption = GUI::GetSetting(this, "caption"); - if (pCaption->empty()) + if (pCaption.empty()) break; m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); - if (m_iBufferPos >= (int)pCaption->length()) - m_iBufferPos = m_iBufferPos_Tail = pCaption->length() - 1; + if (m_iBufferPos >= (int)pCaption.length()) + m_iBufferPos = m_iBufferPos_Tail = pCaption.length() - 1; // See if we are clicking over whitespace - if (iswspace((*pCaption)[m_iBufferPos])) + if (iswspace(pCaption[m_iBufferPos])) { // see if we are in a section of whitespace greater than one character - if ((m_iBufferPos + 1 < (int) pCaption->length() && iswspace((*pCaption)[m_iBufferPos + 1])) || - (m_iBufferPos - 1 > 0 && iswspace((*pCaption)[m_iBufferPos - 1]))) + if ((m_iBufferPos + 1 < (int) pCaption.length() && iswspace(pCaption[m_iBufferPos + 1])) || + (m_iBufferPos - 1 > 0 && iswspace(pCaption[m_iBufferPos - 1]))) { // // We are clicking in an area with more than one whitespace character // so we select both the word to the left and then the word to the right // // [1] First the left // skip the whitespace while (m_iBufferPos > 0) { - if (!iswspace((*pCaption)[m_iBufferPos - 1])) + if (!iswspace(pCaption[m_iBufferPos - 1])) break; m_iBufferPos--; } // now go until we hit white space or punctuation while (m_iBufferPos > 0) { - if (iswspace((*pCaption)[m_iBufferPos - 1])) + if (iswspace(pCaption[m_iBufferPos - 1])) break; m_iBufferPos--; - if (iswpunct((*pCaption)[m_iBufferPos])) + if (iswpunct(pCaption[m_iBufferPos])) break; } // [2] Then the right // go right until we are not in whitespace - while (++m_iBufferPos_Tail < (int)pCaption->length()) + while (++m_iBufferPos_Tail < static_cast(pCaption.length())) { - if (!iswspace((*pCaption)[m_iBufferPos_Tail])) + if (!iswspace(pCaption[m_iBufferPos_Tail])) break; } - if (m_iBufferPos_Tail == (int)pCaption->length()) + if (m_iBufferPos_Tail == static_cast(pCaption.length())) break; // now go to the right until we hit whitespace or punctuation - while (++m_iBufferPos_Tail < (int)pCaption->length()) + while (++m_iBufferPos_Tail < static_cast(pCaption.length())) { - if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + if (iswspace(pCaption[m_iBufferPos_Tail]) || iswpunct(pCaption[m_iBufferPos_Tail])) break; } } else { // single whitespace so select word to the right - while (++m_iBufferPos_Tail < (int)pCaption->length()) + while (++m_iBufferPos_Tail < static_cast(pCaption.length())) { - if (!iswspace((*pCaption)[m_iBufferPos_Tail])) + if (!iswspace(pCaption[m_iBufferPos_Tail])) break; } - if (m_iBufferPos_Tail == (int)pCaption->length()) + if (m_iBufferPos_Tail == static_cast(pCaption.length())) break; // Don't include the leading whitespace m_iBufferPos = m_iBufferPos_Tail; // now go to the right until we hit whitespace or punctuation - while (++m_iBufferPos_Tail < (int)pCaption->length()) + while (++m_iBufferPos_Tail < static_cast(pCaption.length())) { - if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + if (iswspace(pCaption[m_iBufferPos_Tail]) || iswpunct(pCaption[m_iBufferPos_Tail])) break; } } } else { // clicked on non-whitespace so select current word // go until we hit white space or punctuation while (m_iBufferPos > 0) { - if (iswspace((*pCaption)[m_iBufferPos - 1])) + if (iswspace(pCaption[m_iBufferPos - 1])) break; m_iBufferPos--; - if (iswpunct((*pCaption)[m_iBufferPos])) + if (iswpunct(pCaption[m_iBufferPos])) break; } // go to the right until we hit whitespace or punctuation - while (++m_iBufferPos_Tail < (int)pCaption->length()) - if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + while (++m_iBufferPos_Tail < static_cast(pCaption.length())) + if (iswspace(pCaption[m_iBufferPos_Tail]) || iswpunct(pCaption[m_iBufferPos_Tail])) break; } UpdateAutoScroll(); break; } case GUIM_MOUSE_RELEASE_LEFT: { if (m_SelectingText) m_SelectingText = false; break; } case GUIM_MOUSE_MOTION: { // If we just pressed down and started to move before releasing // this is one way of selecting larger portions of text. if (m_SelectingText) { // Actually, first we need to re-check that the mouse button is // really pressed (it can be released while outside the control. if (!g_mouse_buttons[SDL_BUTTON_LEFT]) m_SelectingText = false; else m_iBufferPos = GetMouseHoveringTextPosition(); UpdateAutoScroll(); } break; } case GUIM_LOAD: { GetScrollBar(0).SetX(m_CachedActualSize.right); GetScrollBar(0).SetY(m_CachedActualSize.top); GetScrollBar(0).SetZ(GetBufferedZ()); GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); CStr scrollbar_style; GUI::GetSetting(this, "scrollbar_style", scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); UpdateText(); UpdateAutoScroll(); GUI::GetSetting(this, "readonly", m_Readonly); break; } case GUIM_GOT_FOCUS: { m_iBufferPos = 0; m_PrevTime = 0.0; m_CursorVisState = false; // Tell the IME where to draw the candidate list SDL_Rect rect; rect.h = m_CachedActualSize.GetSize().cy; rect.w = m_CachedActualSize.GetSize().cx; rect.x = m_CachedActualSize.TopLeft().x; rect.y = m_CachedActualSize.TopLeft().y; SDL_SetTextInputRect(&rect); SDL_StartTextInput(); break; } case GUIM_LOST_FOCUS: { if (m_ComposingText) { // Simulate a final text editing event to clear the composition SDL_Event_ evt; evt.ev.type = SDL_TEXTEDITING; evt.ev.edit.length = 0; evt.ev.edit.start = 0; evt.ev.edit.text[0] = 0; ManuallyHandleEvent(&evt); } SDL_StopTextInput(); m_iBufferPos = -1; m_iBufferPos_Tail = -1; break; } default: { break; } } UpdateBufferPositionSetting(); } void CInput::UpdateCachedSize() { // If an ancestor's size changed, this will let us intercept the change and // update our scrollbar positions IGUIObject::UpdateCachedSize(); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) { GetScrollBar(0).SetX(m_CachedActualSize.right); GetScrollBar(0).SetY(m_CachedActualSize.top); GetScrollBar(0).SetZ(GetBufferedZ()); GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); } } void CInput::Draw() { float bz = GetBufferedZ(); if (m_CursorBlinkRate > 0.0) { // check if the cursor visibility state needs to be changed double currTime = timer_Time(); if (currTime - m_PrevTime >= m_CursorBlinkRate) { m_CursorVisState = !m_CursorVisState; m_PrevTime = currTime; } } else // should always be visible m_CursorVisState = true; // First call draw on ScrollBarOwner bool scrollbar; float buffer_zone; bool multiline; bool mask; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); GUI::GetSetting(this, "mask", mask); if (scrollbar && multiline) IGUIScrollBarOwner::Draw(); CStrW font_name_w; CGUIColor color, color_selected; GUI::GetSetting(this, "font", font_name_w); GUI::GetSetting(this, "textcolor", color); GUI::GetSetting(this, "textcolor_selected", color_selected); CStrIntern font_name(font_name_w.ToUTF8()); - // Get pointer of caption, it might be very large, and we don't - // want to copy it continuously. - CStrW* pCaption = NULL; + const CStrW& pCaption = GUI::GetSetting(this, "caption"); wchar_t mask_char = L'*'; if (mask) { CStrW maskStr; GUI::GetSetting(this, "mask_char", maskStr); if (maskStr.length() > 0) mask_char = maskStr[0]; } - else - GUI::GetSettingPointer(this, "caption", pCaption); - CGUISpriteInstance* sprite = NULL; - CGUISpriteInstance* sprite_selectarea = NULL; - int cell_id; - - GUI::GetSettingPointer(this, "sprite", sprite); - GUI::GetSettingPointer(this, "sprite_selectarea", sprite_selectarea); + CGUISpriteInstance& sprite = GUI::GetSetting(this, "sprite"); + CGUISpriteInstance& sprite_selectarea = GUI::GetSetting(this, "sprite_selectarea"); + int cell_id; GUI::GetSetting(this, "cell_id", cell_id); - GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); + m_pGUI->DrawSprite(sprite, cell_id, bz, m_CachedActualSize); float scroll = 0.f; if (scrollbar && multiline) scroll = GetScrollBar(0).GetPos(); CFontMetrics font(font_name); // We'll have to setup clipping manually, since we're doing the rendering manually. CRect cliparea(m_CachedActualSize); // First we'll figure out the clipping area, which is the cached actual size // substracted by an optional scrollbar if (scrollbar) { scroll = GetScrollBar(0).GetPos(); // substract scrollbar from cliparea if (cliparea.right > GetScrollBar(0).GetOuterRect().left && cliparea.right <= GetScrollBar(0).GetOuterRect().right) cliparea.right = GetScrollBar(0).GetOuterRect().left; if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && cliparea.left < GetScrollBar(0).GetOuterRect().right) cliparea.left = GetScrollBar(0).GetOuterRect().right; } if (cliparea != CRect()) { glEnable(GL_SCISSOR_TEST); glScissor( cliparea.left * g_GuiScale, g_yres - cliparea.bottom * g_GuiScale, cliparea.GetWidth() * g_GuiScale, cliparea.GetHeight() * g_GuiScale); } // These are useful later. int VirtualFrom, VirtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { VirtualFrom = m_iBufferPos; VirtualTo = m_iBufferPos_Tail; } else { VirtualFrom = m_iBufferPos_Tail; VirtualTo = m_iBufferPos; } // Get the height of this font. float h = (float)font.GetHeight(); float ls = (float)font.GetLineSpacing(); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); CTextRenderer textRenderer(tech->GetShader()); textRenderer.Font(font_name); // Set the Z to somewhat more, so we can draw a selected area between the // the control and the text. textRenderer.Translate( (float)(int)(m_CachedActualSize.left) + buffer_zone, (float)(int)(m_CachedActualSize.top+h) + buffer_zone, bz+0.1f); // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE // (sort of like a | which is aligned to the left of most characters) float buffered_y = -scroll+buffer_zone; // When selecting larger areas, we need to draw a rectangle box // around it, and this is to keep track of where the box // started, because we need to follow the iteration until we // reach the end, before we can actually draw it. bool drawing_box = false; float box_x = 0.f; float x_pointer = 0.f; // If we have a selecting box (i.e. when you have selected letters, not just when // the pointer is between two letters) we need to process all letters once // before we do it the second time and render all the text. We can't do it // in the same loop because text will have been drawn, so it will disappear when // drawn behind the text that has already been drawn. Confusing, well it's necessary // (I think). if (SelectingText()) { // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos, // just like you can select from right to left, as you can // left to right. Is there a difference? Yes, the pointer // be placed accordingly, so that if you select shift and // expand this selection, it will expand on appropriate side. // Anyway, since the drawing procedure needs "To" to be // greater than from, we need virtual values that might switch // place. int VirtualFrom, VirtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { VirtualFrom = m_iBufferPos; VirtualTo = m_iBufferPos_Tail; } else { VirtualFrom = m_iBufferPos_Tail; VirtualTo = m_iBufferPos; } bool done = false; for (std::list::const_iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, buffered_y += ls, x_pointer = 0.f) { if (multiline && buffered_y > m_CachedActualSize.GetHeight()) break; // We might as well use 'i' here to iterate, because we need it // (often compared against ints, so don't make it size_t) for (int i = 0; i < (int)it->m_ListOfX.size()+2; ++i) { if (it->m_ListStart + i == VirtualFrom) { // we won't actually draw it now, because we don't // know the width of each glyph to that position. // we need to go along with the iteration, and // make a mark where the box started: drawing_box = true; // will turn false when finally rendered. // Get current x position box_x = x_pointer; } const bool at_end = (i == (int)it->m_ListOfX.size()+1); if (drawing_box && (it->m_ListStart + i == VirtualTo || at_end)) { // Depending on if it's just a row change, or if it's // the end of the select box, do slightly different things. if (at_end) { if (it->m_ListStart + i != VirtualFrom) // and actually add a white space! yes, this is done in any common input x_pointer += (float)font.GetCharacterWidth(L' '); } else { drawing_box = false; done = true; } CRect rect; // Set 'rect' depending on if it's a multiline control, or a one-line control if (multiline) { rect = CRect( m_CachedActualSize.left + box_x + buffer_zone, m_CachedActualSize.top + buffered_y + (h - ls) / 2, m_CachedActualSize.left + x_pointer + buffer_zone, m_CachedActualSize.top + buffered_y + (h + ls) / 2); if (rect.bottom < m_CachedActualSize.top) continue; if (rect.top < m_CachedActualSize.top) rect.top = m_CachedActualSize.top; if (rect.bottom > m_CachedActualSize.bottom) rect.bottom = m_CachedActualSize.bottom; } else // if one-line { rect = CRect( m_CachedActualSize.left + box_x + buffer_zone - m_HorizontalScroll, m_CachedActualSize.top + buffered_y + (h - ls) / 2, m_CachedActualSize.left + x_pointer + buffer_zone - m_HorizontalScroll, m_CachedActualSize.top + buffered_y + (h + ls) / 2); if (rect.left < m_CachedActualSize.left) rect.left = m_CachedActualSize.left; if (rect.right > m_CachedActualSize.right) rect.right = m_CachedActualSize.right; } - if (sprite_selectarea) - GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect); + m_pGUI->DrawSprite(sprite_selectarea, cell_id, bz+0.05f, rect); } if (i < (int)it->m_ListOfX.size()) { if (!mask) - x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]); + x_pointer += (float)font.GetCharacterWidth(pCaption[it->m_ListStart + i]); else x_pointer += (float)font.GetCharacterWidth(mask_char); } } if (done) break; // If we're about to draw a box, and all of a sudden changes // line, we need to draw that line's box, and then reset // the box drawing to the beginning of the new line. if (drawing_box) box_x = 0.f; } } // Reset some from previous run buffered_y = -scroll; // Setup initial color (then it might change and change back, when drawing selected area) textRenderer.Color(color); tech->BeginPass(); bool using_selected_color = false; for (std::list::const_iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, buffered_y += ls) { if (buffered_y + buffer_zone >= -ls || !multiline) { if (multiline && buffered_y + buffer_zone > m_CachedActualSize.GetHeight()) break; CMatrix3D savedTransform = textRenderer.GetTransform(); // Text must always be drawn in integer values. So we have to convert scroll if (multiline) textRenderer.Translate(0.f, -(float)(int)scroll, 0.f); else textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f, 0.f); // We might as well use 'i' here, because we need it // (often compared against ints, so don't make it size_t) for (int i = 0; i < (int)it->m_ListOfX.size()+1; ++i) { if (!multiline && i < (int)it->m_ListOfX.size()) { if (it->m_ListOfX[i] - m_HorizontalScroll < -buffer_zone) { // We still need to translate the OpenGL matrix if (i == 0) textRenderer.Translate(it->m_ListOfX[i], 0.f, 0.f); else textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f); continue; } } // End of selected area, change back color if (SelectingText() && it->m_ListStart + i == VirtualTo) { using_selected_color = false; textRenderer.Color(color); } // selecting only one, then we need only to draw a cursor. if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos && m_CursorVisState) textRenderer.Put(0.0f, 0.0f, L"_"); // Drawing selected area if (SelectingText() && it->m_ListStart + i >= VirtualFrom && it->m_ListStart + i < VirtualTo && !using_selected_color) { using_selected_color = true; textRenderer.Color(color_selected); } if (i != (int)it->m_ListOfX.size()) { if (!mask) - textRenderer.PrintfAdvance(L"%lc", (*pCaption)[it->m_ListStart + i]); + textRenderer.PrintfAdvance(L"%lc", pCaption[it->m_ListStart + i]); else textRenderer.PrintfAdvance(L"%lc", mask_char); } // check it's now outside a one-liner, then we'll break if (!multiline && i < (int)it->m_ListOfX.size() && it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth() - buffer_zone) break; } if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos) { textRenderer.Color(color); if (m_CursorVisState) textRenderer.PutAdvance(L"_"); if (using_selected_color) textRenderer.Color(color_selected); } textRenderer.SetTransform(savedTransform); } textRenderer.Translate(0.f, ls, 0.f); } textRenderer.Render(); if (cliparea != CRect()) glDisable(GL_SCISSOR_TEST); tech->EndPass(); } void CInput::UpdateText(int from, int to_before, int to_after) { CStrW caption; CStrW font_name_w; float buffer_zone; bool multiline; bool mask; GUI::GetSetting(this, "font", font_name_w); GUI::GetSetting(this, "caption", caption); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); GUI::GetSetting(this, "mask", mask); CStrIntern font_name(font_name_w.ToUTF8()); wchar_t mask_char = L'*'; if (mask) { CStrW maskStr; GUI::GetSetting(this, "mask_char", maskStr); if (maskStr.length() > 0) mask_char = maskStr[0]; } // Ensure positions are valid after caption changes m_iBufferPos = std::min(m_iBufferPos, (int)caption.size()); m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, (int)caption.size()); UpdateBufferPositionSetting(); if (font_name.empty()) { // Destroy everything stored, there's no font, so there can be no data. m_CharacterPositions.clear(); return; } SRow row; row.m_ListStart = 0; int to = 0; // make sure it's initialized if (to_before == -1) to = (int)caption.length(); CFontMetrics font(font_name); std::list::iterator current_line; // Used to ... TODO int check_point_row_start = -1; int check_point_row_end = -1; // Reset if (from == 0 && to_before == -1) { m_CharacterPositions.clear(); current_line = m_CharacterPositions.begin(); } else { ENSURE(to_before != -1); std::list::iterator destroy_row_from; std::list::iterator destroy_row_to; // Used to check if the above has been set to anything, // previously a comparison like: // destroy_row_from == std::list::iterator() // ... was used, but it didn't work with GCC. bool destroy_row_from_used = false; bool destroy_row_to_used = false; // Iterate, and remove everything between 'from' and 'to_before' // actually remove the entire lines they are on, it'll all have // to be redone. And when going along, we'll delete a row at a time // when continuing to see how much more after 'to' we need to remake. int i = 0; for (std::list::iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, ++i) { if (!destroy_row_from_used && it->m_ListStart > from) { // Destroy the previous line, and all to 'to_before' destroy_row_from = it; --destroy_row_from; destroy_row_from_used = true; // For the rare case that we might remove characters to a word // so that it suddenly fits on the previous row, // we need to by standards re-do the whole previous line too // (if one exists) if (destroy_row_from != m_CharacterPositions.begin()) --destroy_row_from; } if (!destroy_row_to_used && it->m_ListStart > to_before) { destroy_row_to = it; destroy_row_to_used = true; // If it isn't the last row, we'll add another row to delete, // just so we can see if the last restorted line is // identical to what it was before. If it isn't, then we'll // have to continue. // 'check_point_row_start' is where we store how the that // line looked. if (destroy_row_to != m_CharacterPositions.end()) { check_point_row_start = destroy_row_to->m_ListStart; check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); if (destroy_row_to->m_ListOfX.empty()) ++check_point_row_end; } ++destroy_row_to; break; } } if (!destroy_row_from_used) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; // As usual, let's destroy another row back if (destroy_row_from != m_CharacterPositions.begin()) --destroy_row_from; current_line = destroy_row_from; } if (!destroy_row_to_used) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = -1; } // set 'from' to the row we'll destroy from // and 'to' to the row we'll destroy to from = destroy_row_from->m_ListStart; if (destroy_row_to != m_CharacterPositions.end()) to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to. else to = (int)caption.length(); // Setup the first row row.m_ListStart = destroy_row_from->m_ListStart; std::list::iterator temp_it = destroy_row_to; --temp_it; current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to); // If there has been a change in number of characters // we need to change all m_ListStart that comes after // the interval we just destroyed. We'll change all // values with the delta change of the string length. int delta = to_after - to_before; if (delta != 0) { for (std::list::iterator it = current_line; it != m_CharacterPositions.end(); ++it) it->m_ListStart += delta; // Update our check point too! check_point_row_start += delta; check_point_row_end += delta; if (to != (int)caption.length()) to += delta; } } int last_word_started = from; float x_pos = 0.f; //if (to_before != -1) // return; for (int i = from; i < to; ++i) { if (caption[i] == L'\n' && multiline) { if (i == to-1 && to != (int)caption.length()) break; // it will be added outside current_line = m_CharacterPositions.insert(current_line, row); ++current_line; // Setup the next row: row.m_ListOfX.clear(); row.m_ListStart = i+1; x_pos = 0.f; } else { if (caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix. caption[i] == L'-'*/) last_word_started = i+1; if (!mask) x_pos += (float)font.GetCharacterWidth(caption[i]); else x_pos += (float)font.GetCharacterWidth(mask_char); if (x_pos >= GetTextAreaWidth() && multiline) { // The following decides whether it will word-wrap a word, // or if it's only one word on the line, where it has to // break the word apart. if (last_word_started == row.m_ListStart) { last_word_started = i; row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started)); //row.m_ListOfX.push_back(x_pos); //continue; } else { // regular word-wrap row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1)); } // Now, create a new line: // notice: when we enter a newline, you can stand with the cursor // both before and after that character, being on different // rows. With automatic word-wrapping, that is not possible. Which // is intuitively correct. current_line = m_CharacterPositions.insert(current_line, row); ++current_line; // Setup the next row: row.m_ListOfX.clear(); row.m_ListStart = last_word_started; i = last_word_started-1; x_pos = 0.f; } else // Get width of this character: row.m_ListOfX.push_back(x_pos); } // Check if it's the last iteration, and we're not revising the whole string // because in that case, more word-wrapping might be needed. // also check if the current line isn't the end if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end()) { // check all rows and see if any existing if (row.m_ListStart != check_point_row_start) { std::list::iterator destroy_row_from; std::list::iterator destroy_row_to; // Are used to check if the above has been set to anything, // previously a comparison like: // destroy_row_from == std::list::iterator() // was used, but it didn't work with GCC. bool destroy_row_from_used = false; bool destroy_row_to_used = false; // Iterate, and remove everything between 'from' and 'to_before' // actually remove the entire lines they are on, it'll all have // to be redone. And when going along, we'll delete a row at a time // when continuing to see how much more after 'to' we need to remake. int i = 0; for (std::list::iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, ++i) { if (!destroy_row_from_used && it->m_ListStart > check_point_row_start) { // Destroy the previous line, and all to 'to_before' //if (i >= 2) // destroy_row_from = it-2; //else // destroy_row_from = it-1; destroy_row_from = it; destroy_row_from_used = true; //--destroy_row_from; } if (!destroy_row_to_used && it->m_ListStart > check_point_row_end) { destroy_row_to = it; destroy_row_to_used = true; // If it isn't the last row, we'll add another row to delete, // just so we can see if the last restorted line is // identical to what it was before. If it isn't, then we'll // have to continue. // 'check_point_row_start' is where we store how the that // line looked. if (destroy_row_to != m_CharacterPositions.end()) { check_point_row_start = destroy_row_to->m_ListStart; check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); if (destroy_row_to->m_ListOfX.empty()) ++check_point_row_end; } else check_point_row_start = check_point_row_end = -1; ++destroy_row_to; break; } } if (!destroy_row_from_used) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; current_line = destroy_row_from; } if (!destroy_row_to_used) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = check_point_row_end = -1; } if (destroy_row_to != m_CharacterPositions.end()) to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to. else to = (int)caption.length(); // Set current line, new rows will be added before current_line, so // we'll choose the destroy_row_to, because it won't be deleted // in the coming erase. current_line = destroy_row_to; m_CharacterPositions.erase(destroy_row_from, destroy_row_to); } // else, the for loop will end naturally. } } // This is kind of special, when we renew a some lines, then the last // one will sometimes end with a space (' '), that really should // be omitted when word-wrapping. So we'll check if the last row // we'll add has got the same value as the next row. if (current_line != m_CharacterPositions.end()) { if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart) row.m_ListOfX.resize(row.m_ListOfX.size()-1); } // add the final row (even if empty) m_CharacterPositions.insert(current_line, row); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) { GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + buffer_zone*2.f); GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); } } int CInput::GetMouseHoveringTextPosition() const { if (m_CharacterPositions.empty()) return 0; // Return position int retPosition; float buffer_zone; bool multiline; GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); std::list::const_iterator current = m_CharacterPositions.begin(); CPos mouse = m_pGUI->GetMousePos(); if (multiline) { CStrW font_name_w; bool scrollbar; GUI::GetSetting(this, "font", font_name_w); GUI::GetSetting(this, "scrollbar", scrollbar); CStrIntern font_name(font_name_w.ToUTF8()); float scroll = 0.f; if (scrollbar) scroll = GetScrollBarPos(0); // Now get the height of the font. // TODO: Get the real font CFontMetrics font(font_name); float spacing = (float)font.GetLineSpacing(); // Change mouse position relative to text. mouse -= m_CachedActualSize.TopLeft(); mouse.x -= buffer_zone; mouse.y += scroll - buffer_zone; int row = (int)((mouse.y) / spacing); if (row < 0) row = 0; if (row > (int)m_CharacterPositions.size()-1) row = (int)m_CharacterPositions.size()-1; // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. for (int i = 0; i < row; ++i) ++current; } else { // current is already set to begin, // but we'll change the mouse.x to fit our horizontal scrolling mouse -= m_CachedActualSize.TopLeft(); mouse.x -= buffer_zone - m_HorizontalScroll; // mouse.y is moot } retPosition = current->m_ListStart; // Okay, now loop through the glyphs to find the appropriate X position float dummy; retPosition += GetXTextPosition(current, mouse.x, dummy); return retPosition; } // Does not process horizontal scrolling, 'x' must be modified before inputted. int CInput::GetXTextPosition(const std::list::const_iterator& current, const float& x, float& wanted) const { int ret = 0; float previous = 0.f; int i = 0; for (std::vector::const_iterator it = current->m_ListOfX.begin(); it != current->m_ListOfX.end(); ++it, ++i) { if (*it >= x) { if (x - previous >= *it - x) ret += i+1; else ret += i; break; } previous = *it; } // If a position wasn't found, we will assume the last // character of that line. if (i == (int)current->m_ListOfX.size()) { ret += i; wanted = x; } else wanted = 0.f; return ret; } void CInput::DeleteCurSelection() { - CStrW* pCaption = nullptr; - GUI::GetSettingPointer(this, "caption", pCaption); + CStrW& pCaption = GUI::GetSetting(this, "caption"); int virtualFrom; int virtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { virtualFrom = m_iBufferPos; virtualTo = m_iBufferPos_Tail; } else { virtualFrom = m_iBufferPos_Tail; virtualTo = m_iBufferPos; } - *pCaption = pCaption->Left(virtualFrom) + - pCaption->Right((long)pCaption->length() - virtualTo); + pCaption = + pCaption.Left(virtualFrom) + + pCaption.Right(static_cast(pCaption.length()) - virtualTo); UpdateText(virtualFrom, virtualTo, virtualFrom); // Remove selection m_iBufferPos_Tail = -1; m_iBufferPos = virtualFrom; UpdateBufferPositionSetting(); } bool CInput::SelectingText() const { return m_iBufferPos_Tail != -1 && m_iBufferPos_Tail != m_iBufferPos; } float CInput::GetTextAreaWidth() { bool scrollbar; float buffer_zone; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "buffer_zone", buffer_zone); if (scrollbar && GetScrollBar(0).GetStyle()) return m_CachedActualSize.GetWidth() - buffer_zone*2.f - GetScrollBar(0).GetStyle()->m_Width; else return m_CachedActualSize.GetWidth() - buffer_zone*2.f; } void CInput::UpdateAutoScroll() { float buffer_zone; bool multiline; GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); // Autoscrolling up and down if (multiline) { CStrW font_name_w; bool scrollbar; GUI::GetSetting(this, "font", font_name_w); GUI::GetSetting(this, "scrollbar", scrollbar); CStrIntern font_name(font_name_w.ToUTF8()); float scroll = 0.f; if (!scrollbar) return; scroll = GetScrollBar(0).GetPos(); // Now get the height of the font. // TODO: Get the real font CFontMetrics font(font_name); float spacing = (float)font.GetLineSpacing(); //float height = font.GetHeight(); // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. std::list::iterator current = m_CharacterPositions.begin(); int row = 0; while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) break; ++current; ++row; } // If scrolling down if (-scroll + (float)(row+1) * spacing + buffer_zone*2.f > m_CachedActualSize.GetHeight()) { // Scroll so the selected row is shown completely, also with buffer_zone length to the edge. GetScrollBar(0).SetPos((float)(row+1) * spacing - m_CachedActualSize.GetHeight() + buffer_zone*2.f); } // If scrolling up else if (-scroll + (float)row * spacing < 0.f) { // Scroll so the selected row is shown completely, also with buffer_zone length to the edge. GetScrollBar(0).SetPos((float)row * spacing); } } else // autoscrolling left and right { // Get X position of position: if (m_CharacterPositions.empty()) return; float x_position = 0.f; float x_total = 0.f; if (!m_CharacterPositions.begin()->m_ListOfX.empty()) { // Get position of m_iBufferPos if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos && m_iBufferPos > 0) x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1]; // Get complete length: x_total = m_CharacterPositions.begin()->m_ListOfX[m_CharacterPositions.begin()->m_ListOfX.size()-1]; } // Check if outside to the right if (x_position - m_HorizontalScroll + buffer_zone*2.f > m_CachedActualSize.GetWidth()) m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + buffer_zone*2.f; // Check if outside to the left if (x_position - m_HorizontalScroll < 0.f) m_HorizontalScroll = x_position; // Check if the text doesn't even fill up to the right edge even though scrolling is done. if (m_HorizontalScroll != 0.f && x_total - m_HorizontalScroll + buffer_zone*2.f < m_CachedActualSize.GetWidth()) m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + buffer_zone*2.f; // Now this is the fail-safe, if x_total isn't even the length of the control, // remove all scrolling if (x_total + buffer_zone*2.f < m_CachedActualSize.GetWidth()) m_HorizontalScroll = 0.f; } } Index: ps/trunk/source/gui/CInput.h =================================================================== --- ps/trunk/source/gui/CInput.h (revision 22692) +++ ps/trunk/source/gui/CInput.h (revision 22693) @@ -1,193 +1,193 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_CINPUT #define INCLUDED_CINPUT #include "GUI.h" #include "lib/external_libraries/libsdl.h" /** * Text field where you can input and edit the text. * * It doesn't use IGUITextOwner, because we don't need * any other features than word-wrapping, and we need to be * able to rapidly change the string. * * @see IGUIObject */ class CInput : public IGUIScrollBarOwner { GUI_OBJECT(CInput) protected: // forwards struct SRow; public: CInput(CGUI* pGUI); virtual ~CInput(); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates() { IGUIScrollBarOwner::ResetStates(); } // Check where the mouse is hovering, and get the appropriate text position. // return is the text-position index. int GetMouseHoveringTextPosition() const; // Same as above, but only on one row in X, and a given value, not the mouse's. // wanted is filled with x if the row didn't extend as far as the mouse pos. int GetXTextPosition(const std::list::const_iterator& c, const float& x, float& wanted) const; protected: /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Handle events manually to catch keyboard inputting. */ virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); /** * Handle events manually to catch keys which change the text. */ - virtual void ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW* pCaption); + virtual void ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW& pCaption); /** * Handle events manually to catch keys which don't change the text. */ - virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW* pCaption); + virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode, CStrW& pCaption); /** * Handle hotkey events (called by ManuallyHandleEvent) */ virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev); /** * @see IGUIObject#UpdateCachedSize() */ virtual void UpdateCachedSize(); /** * Draws the Text */ virtual void Draw(); /** * Calculate m_CharacterPosition * the main task for this function is to perfom word-wrapping * You input from which character it has been changed, because * if we add a character to the very last end, we don't want * process everything all over again! Also notice you can * specify a 'to' also, it will only be used though if a '\n' * appears, because then the word-wrapping won't change after * that. */ void UpdateText(int from = 0, int to_before = -1, int to_after = -1); /** * Delete the current selection. Also places the pointer at the * crack between the two segments kept. */ void DeleteCurSelection(); /** * Is text selected? It can be denote two ways, m_iBufferPos_Tail * being -1 or the same as m_iBufferPos. This makes for clearer * code. */ bool SelectingText() const; /// Get area of where text can be drawn. float GetTextAreaWidth(); /// Called every time the auto-scrolling should be checked. void UpdateAutoScroll(); /// Clear composed IME input when supported (SDL2 only). void ClearComposedText(); /// Updates the buffer (cursor) position exposed to JS. void UpdateBufferPositionSetting(); protected: /// Cursor position int m_iBufferPos; /// Cursor position we started to select from. (-1 if not selecting) /// (NB: Can be larger than m_iBufferPos if selecting from back to front.) int m_iBufferPos_Tail; /// If we're composing text with an IME bool m_ComposingText; /// The length and position of the current IME composition int m_iComposedLength, m_iComposedPos; /// The position to insert committed text int m_iInsertPos; // the outer vector is lines, and the inner is X positions // in a row. So that we can determine where characters are // placed. It's important because we need to know where the // pointer should be placed when the input control is pressed. struct SRow { int m_ListStart; /// Where does the Row starts std::vector m_ListOfX; /// List of X values for each character. }; /** * List of rows to ease changing its size, so iterators stay valid. * For one-liners only one row is used. */ std::list m_CharacterPositions; // *** Things for a multi-lined input control *** // /** * When you change row with up/down, and the row you jump to does * not have anything at that X position, then it will keep the * m_WantedX position in mind when switching to the next row. * It will keep on being used until it reach a row which meets the * requirements. * 0.0f means not in use. */ float m_WantedX; /** * If we are in the process of selecting a larger selection of text * using the mouse click (hold) and drag, this is true. */ bool m_SelectingText; // *** Things for one-line input control *** // float m_HorizontalScroll; /// Used to store the previous time for flashing the cursor. double m_PrevTime; /// Cursor blink rate in seconds, if greater than 0.0. double m_CursorBlinkRate; /// If the cursor should be drawn or not. bool m_CursorVisState; /// If enabled, it is only allowed to select and copy text. bool m_Readonly; }; #endif // INCLUDED_CINPUT Index: ps/trunk/source/gui/CList.cpp =================================================================== --- ps/trunk/source/gui/CList.cpp (revision 22692) +++ ps/trunk/source/gui/CList.cpp (revision 22693) @@ -1,549 +1,527 @@ /* 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/CGUIColor.h" #include "gui/CGUIScrollBarVertical.h" #include "lib/external_libraries/libsdl.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "soundmanager/ISoundManager.h" CList::CList(CGUI* pGUI) : IGUIObject(pGUI), IGUITextOwner(pGUI), IGUIScrollBarOwner(pGUI), m_Modified(false), m_PrevSelectedItem(-1), m_LastItemClickTime(0) { // Add sprite_disabled! TODO AddSetting("buffer_zone"); AddSetting("font"); AddSetting("scrollbar"); AddSetting("scrollbar_style"); AddSetting("sound_disabled"); AddSetting("sound_selected"); AddSetting("sprite"); AddSetting("sprite_selectarea"); AddSetting( "cell_id"); AddSetting("text_align"); AddSetting("textcolor"); AddSetting("textcolor_selected"); AddSetting( "selected"); // Index selected. -1 is none. AddSetting("auto_scroll"); AddSetting( "hovered"); AddSetting("tooltip"); AddSetting("tooltip_style"); // Each list item has both a name (in 'list') and an associated data string (in 'list_data') AddSetting("list"); AddSetting("list_data"); GUI::SetSetting(this, "scrollbar", false); GUI::SetSetting(this, "selected", -1); GUI::SetSetting(this, "hovered", -1); GUI::SetSetting(this, "auto_scroll", false); // Add scroll-bar CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); bar->SetRightAligned(true); AddScrollBar(bar); } CList::~CList() { } void CList::SetupText() { m_Modified = true; - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); + const CGUIList& pList = GUI::GetSetting(this, "list"); //ENSURE(m_GeneratedTexts.size()>=1); - m_ItemsYPositions.resize(pList->m_Items.size()+1); + m_ItemsYPositions.resize(pList.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(); CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style. font = L"default"; bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); float width = GetListRect().GetWidth(); // remove scrollbar if applicable if (scrollbar && GetScrollBar(0).GetStyle()) width -= GetScrollBar(0).GetStyle()->m_Width; float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); // Generate texts float buffered_y = 0.f; - for (size_t i = 0; i < pList->m_Items.size(); ++i) + for (size_t i = 0; i < pList.m_Items.size(); ++i) { CGUIText* text; - if (!pList->m_Items[i].GetOriginalString().empty()) - text = &AddText(pList->m_Items[i], font, width, buffer_zone, this); + if (!pList.m_Items[i].GetOriginalString().empty()) + text = &AddText(pList.m_Items[i], font, width, buffer_zone, this); else { // Minimum height of a space character of the current font size CGUIString align_string; align_string.SetValue(L" "); text = &AddText(align_string, font, width, buffer_zone, this); } m_ItemsYPositions[i] = buffered_y; buffered_y += text->GetSize().cy; } - m_ItemsYPositions[pList->m_Items.size()] = buffered_y; + m_ItemsYPositions[pList.m_Items.size()] = buffered_y; // Setup scrollbar if (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 bool auto_scroll; GUI::GetSetting(this, "auto_scroll", auto_scroll); if (auto_scroll) 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") { CStr scrollbar_style; GUI::GetSetting(this, Message.value, scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); SetupText(); } break; 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; } int hovered = GetHoveredItem(); if (hovered == -1) break; GUI::SetSetting(this, "selected", hovered); UpdateAutoScroll(); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); 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: { int hoveredSetting = -1; GUI::GetSetting(this, "hovered", hoveredSetting); if (hoveredSetting == -1) break; GUI::SetSetting(this, "hovered", -1); ScriptEvent("hoverchange"); break; } case GUIM_MOUSE_OVER: { int hoveredSetting = -1; GUI::GetSetting(this, "hovered", hoveredSetting); int hovered = GetHoveredItem(); if (hovered == hoveredSetting) break; GUI::SetSetting(this, "hovered", hovered); ScriptEvent("hoverchange"); break; } case GUIM_LOAD: { CStr scrollbar_style; GUI::GetSetting(this, "scrollbar_style", scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); 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() { int selected; GUI::GetSetting(this, "selected", selected); DrawList(selected, "sprite", "sprite_selectarea", "textcolor"); } void CList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) IGUIScrollBarOwner::Draw(); if (GetGUI()) { CRect rect = GetListRect(); - CGUISpriteInstance* sprite = NULL; - CGUISpriteInstance* sprite_selectarea = NULL; - int cell_id; - GUI::GetSettingPointer(this, _sprite, sprite); - GUI::GetSettingPointer(this, _sprite_selected, sprite_selectarea); - GUI::GetSetting(this, "cell_id", cell_id); + CGUISpriteInstance& sprite = GUI::GetSetting(this, _sprite); + CGUISpriteInstance& sprite_selectarea = GUI::GetSetting(this, _sprite_selected); - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); - - GetGUI()->DrawSprite(*sprite, cell_id, bz, rect); + const int cell_id = GUI::GetSetting(this, "cell_id"); + m_pGUI->DrawSprite(sprite, cell_id, bz, rect); float scroll = 0.f; if (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 (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; } - GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel); + m_pGUI->DrawSprite(sprite_selectarea, cell_id, bz+0.05f, rect_sel); } } CGUIColor color; GUI::GetSetting(this, _textcolor, color); - for (size_t i = 0; i < pList->m_Items.size(); ++i) + const CGUIList& pList = GUI::GetSetting(this, "list"); + + for (size_t i = 0; i < pList.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 (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, color, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz+0.1f, cliparea); } } } void CList::AddItem(const CStrW& str, const CStrW& data) { - CGUIList* pList; - CGUIList* pListData; - GUI::GetSettingPointer(this, "list", pList); - GUI::GetSettingPointer(this, "list_data", pListData); - CGUIString gui_string; gui_string.SetValue(str); - pList->m_Items.push_back(gui_string); + + // Do not send a settings-changed message + CGUIList& pList = GUI::GetSetting(this, "list"); + pList.m_Items.push_back(gui_string); CGUIString data_string; data_string.SetValue(data); - pListData->m_Items.push_back(data_string); + CGUIList& pListData = GUI::GetSetting(this, "list_data"); + pListData.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() { - int selected; - GUI::GetSetting(this, "selected", selected); + int selected = GUI::GetSetting(this, "selected"); - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); + const CGUIList& pList = GUI::GetSetting(this, "list"); - if (selected != (int)pList->m_Items.size()-1) + if (selected != static_cast(pList.m_Items.size()) - 1) { ++selected; GUI::SetSetting(this, "selected", selected); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } } void CList::SelectPrevElement() { - int selected; - GUI::GetSetting(this, "selected", selected); + int selected = GUI::GetSetting(this, "selected"); if (selected > 0) { --selected; GUI::SetSetting(this, "selected", selected); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } } void CList::SelectFirstElement() { - int selected; - GUI::GetSetting(this, "selected", selected); - - if (selected >= 0) + if (GUI::GetSetting(this, "selected") >= 0) GUI::SetSetting(this, "selected", 0); } void CList::SelectLastElement() { - int selected; - GUI::GetSetting(this, "selected", selected); - - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); + const CGUIList& pList = GUI::GetSetting(this, "list"); + const int index = static_cast(pList.m_Items.size()) - 1; - if (selected != (int)pList->m_Items.size()-1) - GUI::SetSetting(this, "selected", (int)pList->m_Items.size()-1); + if (GUI::GetSetting(this, "selected") != index) + GUI::SetSetting(this, "selected", index); } void CList::UpdateAutoScroll() { - int selected; - bool scrollbar; - float scroll; - GUI::GetSetting(this, "selected", selected); - GUI::GetSetting(this, "scrollbar", scrollbar); + const int selected = GUI::GetSetting(this, "selected"); + const bool scrollbar = GUI::GetSetting(this, "scrollbar"); // No scrollbar, no scrolling (at least it's not made to work properly). - if (!scrollbar || selected < 0 || (std::size_t) selected >= m_ItemsYPositions.size()) + if (!scrollbar || selected < 0 || static_cast(selected) >= m_ItemsYPositions.size()) return; - scroll = GetScrollBar(0).GetPos(); + float scroll = GetScrollBar(0).GetPos(); // Check upper boundary if (m_ItemsYPositions[selected] < scroll) { GetScrollBar(0).SetPos(m_ItemsYPositions[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[selected+1]-rect.GetHeight() > scroll) GetScrollBar(0).SetPos(m_ItemsYPositions[selected+1]-rect.GetHeight()); } int CList::GetHoveredItem() { - bool scrollbar; - CGUIList* pList; - GUI::GetSetting(this, "scrollbar", scrollbar); - GUI::GetSettingPointer(this, "list", pList); - float scroll = 0.f; - if (scrollbar) - scroll = GetScrollBar(0).GetPos(); + const bool scrollbar = GUI::GetSetting(this, "scrollbar"); + const float scroll = scrollbar ? GetScrollBar(0).GetPos() : 0.f; const CRect& rect = GetListRect(); CPos mouse = m_pGUI->GetMousePos(); mouse.y += scroll; // Mouse is over scrollbar if (scrollbar && GetScrollBar(0).IsVisible() && mouse.x >= GetScrollBar(0).GetOuterRect().left && mouse.x <= GetScrollBar(0).GetOuterRect().right) return -1; - for (size_t i = 0; i < pList->m_Items.size(); ++i) + const CGUIList& pList = GUI::GetSetting(this, "list"); + for (size_t i = 0; i < pList.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 22692) +++ ps/trunk/source/gui/COList.cpp (revision 22693) @@ -1,480 +1,474 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "COList.h" #include "gui/CGUIColor.h" #include "i18n/L10n.h" #include "ps/CLogger.h" #include "soundmanager/ISoundManager.h" const float SORT_SPRITE_DIM = 16.0f; const CPos COLUMN_SHIFT = CPos(0, 4); COList::COList(CGUI* pGUI) : CList(pGUI), IGUIObject(pGUI) { AddSetting("sprite_heading"); AddSetting("sortable"); // The actual sorting is done in JS for more versatility AddSetting("selected_column"); AddSetting("selected_column_order"); AddSetting("sprite_asc"); // Show the order of sorting AddSetting("sprite_desc"); AddSetting("sprite_not_sorted"); } void COList::SetupText() { - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); - - m_ItemsYPositions.resize(pList->m_Items.size() + 1); + const CGUIList& pList = GUI::GetSetting(this, "list"); + m_ItemsYPositions.resize(pList.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(); CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style. font = L"default"; bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); m_TotalAvailableColumnWidth = GetListRect().GetWidth(); // remove scrollbar if applicable if (scrollbar && GetScrollBar(0).GetStyle()) m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width; float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); 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, font, width, buffer_zone, this); 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 < pList->m_Items.size(); ++i) + for (size_t i = 0; i < pList.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; - CGUIList* pList_c; - GUI::GetSettingPointer(this, "list_" + column.m_Id, pList_c); + CGUIList& pList_c = GUI::GetSetting(this, "list_" + column.m_Id); CGUIText* text; - if (!pList_c->m_Items[i].GetOriginalString().empty()) - text = &AddText(pList_c->m_Items[i], font, width, buffer_zone, this); + if (!pList_c.m_Items[i].GetOriginalString().empty()) + text = &AddText(pList_c.m_Items[i], font, width, buffer_zone, this); else { // Minimum height of a space character of the current font size CGUIString align_string; align_string.SetValue(L" "); text = &AddText(align_string, font, width, buffer_zone, this); } shift = std::max(shift, text->GetSize().cy); } buffered_y += shift; } - m_ItemsYPositions[pList->m_Items.size()] = buffered_y; + m_ItemsYPositions[pList.m_Items.size()] = buffered_y; if (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: { bool sortable; GUI::GetSetting(this, "sortable", sortable); if (!sortable) return; const CPos& mouse = m_pGUI->GetMousePos(); if (!m_CachedActualSize.PointInside(mouse)) return; CStr selectedColumn; GUI::GetSetting(this, "selected_column", selectedColumn); int selectedColumnOrder; GUI::GetSetting(this, "selected_column_order", selectedColumnOrder); float xpos = 0; for (const COListColumn& column : m_Columns) { bool hidden = false; GUI::GetSetting(this, "hidden_" + column.m_Id, hidden); if (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 != selectedColumn) { selectedColumnOrder = 1; selectedColumn = column.m_Id; } else selectedColumnOrder = -selectedColumnOrder; GUI::SetSetting(this, "selected_column", column.m_Id); GUI::SetSetting(this, "selected_column_order", selectedColumnOrder); ScriptEvent("selectioncolumnchange"); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); 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; bool hidden = false; for (XMBAttribute attr : child.GetAttributes()) { CStr attr_name(pFile->GetAttributeString(attr.Name)); CStr attr_value(attr.Value); if (attr_name == "color") { CGUIColor color; if (!GUI::ParseString(m_pGUI, attr_value.FromUTF8(), color)) LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); else column.m_TextColor = color; } else if (attr_name == "id") { column.m_Id = attr_value; } else if (attr_name == "hidden") { if (!GUI::ParseString(m_pGUI, attr_value.FromUTF8(), hidden)) LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); } else if (attr_name == "width") { float width; if (!GUI::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(); } } AddSetting("list_" + column.m_Id); AddSetting("hidden_" + column.m_Id); GUI::SetSetting(this, "hidden_" + column.m_Id, hidden); m_Columns.emplace_back(std::move(column)); SetupText(); return true; } return false; } void COList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) { float bz = GetBufferedZ(); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) IGUIScrollBarOwner::Draw(); CRect rect = GetListRect(); - CGUISpriteInstance* sprite = NULL; - CGUISpriteInstance* sprite_selectarea = NULL; + CGUISpriteInstance& sprite = GUI::GetSetting(this, _sprite); + CGUISpriteInstance& sprite_selectarea = GUI::GetSetting(this, _sprite_selected); + int cell_id; - GUI::GetSettingPointer(this, _sprite, sprite); - GUI::GetSettingPointer(this, _sprite_selected, sprite_selectarea); GUI::GetSetting(this, "cell_id", cell_id); - CGUIList* pList; - GUI::GetSettingPointer(this, "list", pList); - - GetGUI()->DrawSprite(*sprite, cell_id, bz, rect); + m_pGUI->DrawSprite(sprite, cell_id, bz, rect); float scroll = 0.f; if (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 (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 - GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel); + m_pGUI->DrawSprite(sprite_selectarea, cell_id, bz+0.05f, rect_sel); } } // Draw line above column header - CGUISpriteInstance* sprite_heading = NULL; - GUI::GetSettingPointer(this, "sprite_heading", sprite_heading); + CGUISpriteInstance& sprite_heading = GUI::GetSetting(this, "sprite_heading"); CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, m_CachedActualSize.top + m_HeadingHeight); - GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head); + m_pGUI->DrawSprite(sprite_heading, cell_id, bz, rect_head); // Draw column headers bool sortable; GUI::GetSetting(this, "sortable", sortable); CStr selectedColumn; GUI::GetSetting(this, "selected_column", selectedColumn); int selectedColumnOrder; GUI::GetSetting(this, "selected_column_order", selectedColumnOrder); CGUIColor color; GUI::GetSetting(this, _textcolor, color); float xpos = 0; for (size_t col = 0; col < m_Columns.size(); ++col) { bool hidden = false; GUI::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden); if (hidden) continue; // Check if it's a decimal value, and if so, assume relative positioning. float width = m_Columns[col].m_Width; if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0) width *= m_TotalAvailableColumnWidth; CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); // Draw sort arrows in colum header if (sortable) { - CGUISpriteInstance* sprite; + CStr spriteName; if (selectedColumn == m_Columns[col].m_Id) { if (selectedColumnOrder == 0) LOGERROR("selected_column_order must not be 0"); if (selectedColumnOrder != -1) - GUI::GetSettingPointer(this, "sprite_asc", sprite); + spriteName = "sprite_asc"; else - GUI::GetSettingPointer(this, "sprite_desc", sprite); + spriteName = "sprite_desc"; } else - GUI::GetSettingPointer(this, "sprite_not_sorted", sprite); + spriteName = "sprite_not_sorted"; - GetGUI()->DrawSprite(*sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - SORT_SPRITE_DIM, 0), leftTopCorner + CPos(width, SORT_SPRITE_DIM))); + CGUISpriteInstance& sprite = GUI::GetSetting(this, spriteName); + m_pGUI->DrawSprite(sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - SORT_SPRITE_DIM, 0), leftTopCorner + CPos(width, SORT_SPRITE_DIM))); } // Draw column header text DrawText(col, color, leftTopCorner + COLUMN_SHIFT, bz + 0.1f, rect_head); xpos += width; } // Draw list items for each column + const CGUIList& pList = GUI::GetSetting(this, "list"); const size_t objectsCount = m_Columns.size(); - for (size_t i = 0; i < pList->m_Items.size(); ++i) + for (size_t i = 0; i < pList.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 (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; for (size_t col = 0; col < objectsCount; ++col) { bool hidden = false; GUI::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden); if (hidden) continue; // Determine text position and width const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]); float width = m_Columns[col].m_Width; // Check if it's a decimal value, and if so, assume relative positioning. if (m_Columns[col].m_Width < 1 && m_Columns[col].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, m_Columns[col].m_TextColor, textPos, bz + 0.1f, cliparea2); xpos += width; } } } Index: ps/trunk/source/gui/CProgressBar.cpp =================================================================== --- ps/trunk/source/gui/CProgressBar.cpp (revision 22692) +++ ps/trunk/source/gui/CProgressBar.cpp (revision 22693) @@ -1,83 +1,82 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GUI.h" #include "CProgressBar.h" #include "lib/ogl.h" CProgressBar::CProgressBar(CGUI* pGUI) : IGUIObject(pGUI) { AddSetting("sprite_background"); AddSetting("sprite_bar"); AddSetting("caption"); // aka value from 0 to 100 AddSetting("tooltip"); AddSetting("tooltip_style"); } CProgressBar::~CProgressBar() { } void CProgressBar::HandleMessage(SGUIMessage& Message) { // Important IGUIObject::HandleMessage(Message); switch (Message.type) { case GUIM_SETTINGS_UPDATED: // Update scroll-bar // TODO Gee: (2004-09-01) Is this really updated each time it should? if (Message.value == CStr("caption")) { float value; GUI::GetSetting(this, "caption", value); if (value > 100.f) GUI::SetSetting(this, "caption", 100.f); else if (value < 0.f) GUI::SetSetting(this, "caption", 0.f); } break; default: break; } } void CProgressBar::Draw() { + CGUISpriteInstance& sprite_bar = GUI::GetSetting(this, "sprite_bar"); + CGUISpriteInstance& sprite_background = GUI::GetSetting(this, "sprite_background"); + float bz = GetBufferedZ(); - CGUISpriteInstance* sprite_background; - CGUISpriteInstance* sprite_bar; int cell_id = 0; float value = 0; - GUI::GetSettingPointer(this, "sprite_background", sprite_background); - GUI::GetSettingPointer(this, "sprite_bar", sprite_bar); GUI::GetSetting(this, "caption", value); - GetGUI()->DrawSprite(*sprite_background, cell_id, bz, m_CachedActualSize); + m_pGUI->DrawSprite(sprite_background, cell_id, bz, m_CachedActualSize); // Get size of bar (notice it is drawn slightly closer, to appear above the background) CRect bar_size(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.left+m_CachedActualSize.GetWidth()*(value/100.f), m_CachedActualSize.bottom); - GetGUI()->DrawSprite(*sprite_bar, cell_id, bz+0.01f, bar_size); + m_pGUI->DrawSprite(sprite_bar, cell_id, bz+0.01f, bar_size); } Index: ps/trunk/source/gui/CSlider.cpp =================================================================== --- ps/trunk/source/gui/CSlider.cpp (revision 22692) +++ ps/trunk/source/gui/CSlider.cpp (revision 22693) @@ -1,143 +1,142 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CSlider.h" #include "GUI.h" #include "lib/ogl.h" #include "ps/CLogger.h" CSlider::CSlider(CGUI* pGUI) : IGUIObject(pGUI), m_IsPressed(false), m_ButtonSide(0) { AddSetting("value"); AddSetting("min_value"); AddSetting("max_value"); AddSetting("cell_id"); AddSetting("sprite"); AddSetting("sprite_bar"); AddSetting("button_width"); GUI::GetSetting(this, "value", m_Value); GUI::GetSetting(this, "min_value", m_MinValue); GUI::GetSetting(this, "max_value", m_MaxValue); GUI::GetSetting(this, "button_width", m_ButtonSide); m_Value = Clamp(m_Value, m_MinValue, m_MaxValue); } CSlider::~CSlider() { } float CSlider::GetSliderRatio() const { return (m_MaxValue - m_MinValue) / (m_CachedActualSize.GetWidth() - m_ButtonSide); } void CSlider::IncrementallyChangeValue(const float difference) { m_Value = Clamp(m_Value + difference, m_MinValue, m_MaxValue); UpdateValue(); } void CSlider::HandleMessage(SGUIMessage& Message) { switch (Message.type) { case GUIM_SETTINGS_UPDATED: { GUI::GetSetting(this, "value", m_Value); GUI::GetSetting(this, "min_value", m_MinValue); GUI::GetSetting(this, "max_value", m_MaxValue); GUI::GetSetting(this, "button_width", m_ButtonSide); m_Value = Clamp(m_Value, m_MinValue, m_MaxValue); break; } case GUIM_MOUSE_WHEEL_DOWN: { if (m_IsPressed) break; IncrementallyChangeValue(-0.01f); break; } case GUIM_MOUSE_WHEEL_UP: { if (m_IsPressed) break; IncrementallyChangeValue(0.01f); break; } case GUIM_MOUSE_PRESS_LEFT: { m_Mouse = m_pGUI->GetMousePos(); m_IsPressed = true; IncrementallyChangeValue((m_Mouse.x - GetButtonRect().CenterPoint().x) * GetSliderRatio()); break; } case GUIM_MOUSE_RELEASE_LEFT: { m_IsPressed = false; break; } case GUIM_MOUSE_MOTION: { if (!MouseOver()) m_IsPressed = false; if (m_IsPressed) { float difference = float(m_pGUI->GetMousePos().x - m_Mouse.x) * GetSliderRatio(); m_Mouse = m_pGUI->GetMousePos(); IncrementallyChangeValue(difference); } break; } default: break; } } void CSlider::Draw() { - CGUISpriteInstance* sprite; - CGUISpriteInstance* sprite_button; + CGUISpriteInstance& sprite = GUI::GetSetting(this, "sprite_bar"); + CGUISpriteInstance& sprite_button = GUI::GetSetting(this, "sprite"); + int cell_id; - GUI::GetSettingPointer(this, "sprite_bar", sprite); - GUI::GetSettingPointer(this, "sprite", sprite_button); GUI::GetSetting(this, "cell_id", cell_id); CRect slider_line(m_CachedActualSize); slider_line.left += m_ButtonSide / 2.0f; slider_line.right -= m_ButtonSide / 2.0f; float bz = GetBufferedZ(); - GetGUI()->DrawSprite(*sprite, cell_id, bz, slider_line); - GetGUI()->DrawSprite(*sprite_button, cell_id, bz, GetButtonRect()); + m_pGUI->DrawSprite(sprite, cell_id, bz, slider_line); + m_pGUI->DrawSprite(sprite_button, cell_id, bz, GetButtonRect()); } void CSlider::UpdateValue() { GUI::SetSetting(this, "value", m_Value); ScriptEvent("valuechange"); } CRect CSlider::GetButtonRect() { float ratio = m_MaxValue > m_MinValue ? (m_Value - m_MinValue) / (m_MaxValue - m_MinValue) : 0.0f; float x = m_CachedActualSize.left + ratio * (m_CachedActualSize.GetWidth() - m_ButtonSide); float y = m_CachedActualSize.top + (m_CachedActualSize.GetHeight() - m_ButtonSide) / 2.0; return CRect(x, y, x + m_ButtonSide, y + m_ButtonSide); } Index: ps/trunk/source/gui/CText.cpp =================================================================== --- ps/trunk/source/gui/CText.cpp (revision 22692) +++ ps/trunk/source/gui/CText.cpp (revision 22693) @@ -1,265 +1,262 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CText.h" #include "gui/CGUIScrollBarVertical.h" #include "gui/GUI.h" #include "lib/ogl.h" CText::CText(CGUI* pGUI) : IGUIObject(pGUI), IGUIScrollBarOwner(pGUI), IGUITextOwner(pGUI) { AddSetting("buffer_zone"); AddSetting("caption"); AddSetting("cell_id"); AddSetting("clip"); AddSetting("font"); AddSetting("scrollbar"); AddSetting("scrollbar_style"); AddSetting("scroll_bottom"); AddSetting("scroll_top"); AddSetting("sprite"); AddSetting("text_align"); AddSetting("text_valign"); AddSetting("textcolor"); AddSetting("textcolor_disabled"); AddSetting("tooltip"); AddSetting("tooltip_style"); // Private settings AddSetting("_icon_tooltip"); AddSetting("_icon_tooltip_style"); //GUI::SetSetting(this, "ghost", true); GUI::SetSetting(this, "scrollbar", false); GUI::SetSetting(this, "clip", true); // Add scroll-bar CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); bar->SetRightAligned(true); AddScrollBar(bar); // Add text AddText(); } CText::~CText() { } void CText::SetupText() { if (m_GeneratedTexts.empty()) return; CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style. font = L"default"; - CGUIString* caption = nullptr; - GUI::GetSettingPointer(this, "caption", caption); - bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); float width = m_CachedActualSize.GetWidth(); // remove scrollbar if applicable if (scrollbar && GetScrollBar(0).GetStyle()) width -= GetScrollBar(0).GetStyle()->m_Width; + const CGUIString& caption = GUI::GetSetting(this, "caption"); + const float buffer_zone = GUI::GetSetting(this, "buffer_zone"); - float buffer_zone = 0.f; - GUI::GetSetting(this, "buffer_zone", buffer_zone); - m_GeneratedTexts[0] = CGUIText(m_pGUI, *caption, font, width, buffer_zone, this); + m_GeneratedTexts[0] = CGUIText(m_pGUI, caption, font, width, buffer_zone, this); if (!scrollbar) CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]); // Setup scrollbar if (scrollbar) { bool scroll_top = false, scroll_bottom = false; GUI::GetSetting(this, "scroll_bottom", scroll_bottom); GUI::GetSetting(this, "scroll_top", scroll_top); // If we are currently scrolled to the bottom of the text, // then add more lines of text, update the scrollbar so we // stick to the bottom. // (Use 1.5px delta so this triggers the first time caption is set) bool bottom = false; if (scroll_bottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f) bottom = true; GetScrollBar(0).SetScrollRange(m_GeneratedTexts[0].GetSize().cy); GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); GetScrollBar(0).SetX(m_CachedActualSize.right); GetScrollBar(0).SetY(m_CachedActualSize.top); GetScrollBar(0).SetZ(GetBufferedZ()); GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); if (bottom) GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos()); if (scroll_top) GetScrollBar(0).SetPos(0.0f); } } void CText::HandleMessage(SGUIMessage& Message) { IGUIScrollBarOwner::HandleMessage(Message); //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! switch (Message.type) { case GUIM_SETTINGS_UPDATED: if (Message.value == "scrollbar") SetupText(); // Update scrollbar if (Message.value == "scrollbar_style") { CStr scrollbar_style; GUI::GetSetting(this, Message.value, scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); SetupText(); } break; case GUIM_MOUSE_WHEEL_DOWN: { GetScrollBar(0).ScrollPlus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); break; } case GUIM_MOUSE_WHEEL_UP: { GetScrollBar(0).ScrollMinus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); break; } case GUIM_LOAD: { GetScrollBar(0).SetX(m_CachedActualSize.right); GetScrollBar(0).SetY(m_CachedActualSize.top); GetScrollBar(0).SetZ(GetBufferedZ()); GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top); CStr scrollbar_style; GUI::GetSetting(this, "scrollbar_style", scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); break; } default: break; } IGUITextOwner::HandleMessage(Message); } void CText::Draw() { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) // Draw scrollbar IGUIScrollBarOwner::Draw(); - CGUISpriteInstance* sprite; + CGUISpriteInstance& sprite = GUI::GetSetting(this, "sprite"); + int cell_id; bool clip; - GUI::GetSettingPointer(this, "sprite", sprite); GUI::GetSetting(this, "cell_id", cell_id); GUI::GetSetting(this, "clip", clip); - GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); + m_pGUI->DrawSprite(sprite, cell_id, bz, m_CachedActualSize); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); // Clipping area (we'll have to subtract the scrollbar) CRect cliparea; if (clip) { cliparea = m_CachedActualSize; if (scrollbar) { // subtract scrollbar from cliparea if (cliparea.right > GetScrollBar(0).GetOuterRect().left && cliparea.right <= GetScrollBar(0).GetOuterRect().right) cliparea.right = GetScrollBar(0).GetOuterRect().left; if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && cliparea.left < GetScrollBar(0).GetOuterRect().right) cliparea.left = GetScrollBar(0).GetOuterRect().right; } } bool enabled; GUI::GetSetting(this, "enabled", enabled); CGUIColor color; GUI::GetSetting(this, enabled ? "textcolor" : "textcolor_disabled", color); if (scrollbar) DrawText(0, color, m_CachedActualSize.TopLeft() - CPos(0.f, scroll), bz+0.1f, cliparea); else DrawText(0, color, m_TextPos, bz+0.1f, cliparea); } bool CText::MouseOverIcon() { for (const CGUIText& guitext : m_GeneratedTexts) for (const CGUIText::SSpriteCall& spritecall : guitext.GetSpriteCalls()) { // Check mouse over sprite if (!spritecall.m_Area.PointInside(m_pGUI->GetMousePos() - m_CachedActualSize.TopLeft())) continue; // If tooltip exists, set the property if (!spritecall.m_Tooltip.empty()) { SetSetting("_icon_tooltip_style", spritecall.m_TooltipStyle); SetSetting("_icon_tooltip", spritecall.m_Tooltip); } return true; } return false; } Index: ps/trunk/source/gui/CTooltip.cpp =================================================================== --- ps/trunk/source/gui/CTooltip.cpp (revision 22692) +++ ps/trunk/source/gui/CTooltip.cpp (revision 22693) @@ -1,169 +1,167 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CTooltip.h" #include "CGUI.h" #include CTooltip::CTooltip(CGUI* pGUI) : IGUIObject(pGUI), IGUITextOwner(pGUI) { // If the tooltip is an object by itself: AddSetting("buffer_zone"); AddSetting("caption"); AddSetting("font"); AddSetting("sprite"); AddSetting("delay"); AddSetting("textcolor"); AddSetting("maxwidth"); AddSetting("offset"); AddSetting("anchor"); AddSetting("text_align"); // This is used for tooltips that are hidden/revealed manually by scripts, rather than through the standard tooltip display mechanism AddSetting("independent"); // If the tooltip is just a reference to another object: AddSetting("use_object"); AddSetting("hide_object"); // Private settings: // This is set by GUITooltip AddSetting("_mousepos"); // Defaults GUI::SetSetting(this, "delay", 500); GUI::SetSetting(this, "anchor", EVAlign_Bottom); GUI::SetSetting(this, "text_align", EAlign_Left); // Set up a blank piece of text, to be replaced with a more // interesting message later AddText(); } CTooltip::~CTooltip() { } void CTooltip::SetupText() { ENSURE(m_GeneratedTexts.size() == 1); 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); - CGUIString* caption = nullptr; - GUI::GetSettingPointer(this, "caption", caption); + const CGUIString& caption = GUI::GetSetting(this, "caption"); float max_width = 0.f; GUI::GetSetting(this, "maxwidth", max_width); - m_GeneratedTexts[0] = CGUIText(m_pGUI, *caption, font, max_width, buffer_zone, this); + m_GeneratedTexts[0] = CGUIText(m_pGUI, caption, font, max_width, buffer_zone, this); // Position the tooltip relative to the mouse: CPos mousepos, offset; EVAlign anchor; bool independent; GUI::GetSetting(this, "independent", independent); if (independent) mousepos = m_pGUI->GetMousePos(); else GUI::GetSetting(this, "_mousepos", mousepos); GUI::GetSetting(this, "offset", offset); GUI::GetSetting(this, "anchor", anchor); float textwidth = m_GeneratedTexts[0].GetSize().cx; float textheight = m_GeneratedTexts[0].GetSize().cy; CClientArea size; size.pixel.left = mousepos.x + offset.x; size.pixel.right = size.pixel.left + textwidth; switch (anchor) { case EVAlign_Top: size.pixel.top = mousepos.y + offset.y; size.pixel.bottom = size.pixel.top + textheight; break; case EVAlign_Bottom: size.pixel.bottom = mousepos.y + offset.y; size.pixel.top = size.pixel.bottom - textheight; break; case EVAlign_Center: size.pixel.top = mousepos.y + offset.y - textheight/2.f; size.pixel.bottom = size.pixel.top + textwidth; break; default: debug_warn(L"Invalid EVAlign!"); } // Reposition the tooltip if it's falling off the screen: extern int g_xres, g_yres; extern float g_GuiScale; float screenw = g_xres / g_GuiScale; float screenh = g_yres / g_GuiScale; if (size.pixel.top < 0.f) size.pixel.bottom -= size.pixel.top, size.pixel.top = 0.f; else if (size.pixel.bottom > screenh) size.pixel.top -= (size.pixel.bottom-screenh), size.pixel.bottom = screenh; if (size.pixel.left < 0.f) size.pixel.right -= size.pixel.left, size.pixel.left = 0.f; else if (size.pixel.right > screenw) size.pixel.left -= (size.pixel.right-screenw), size.pixel.right = screenw; GUI::SetSetting(this, "size", size); } void CTooltip::HandleMessage(SGUIMessage& Message) { IGUITextOwner::HandleMessage(Message); } void CTooltip::Draw() { float z = 900.f; // TODO: Find a nicer way of putting the tooltip on top of everything else - CGUISpriteInstance* sprite; - GUI::GetSettingPointer(this, "sprite", sprite); + CGUISpriteInstance& sprite = GUI::GetSetting(this, "sprite"); // Normally IGUITextOwner will handle this updating but since SetupText can modify the position // we need to call it now *before* we do the rest of the drawing if (!m_GeneratedTextsValid) { SetupText(); m_GeneratedTextsValid = true; } - GetGUI()->DrawSprite(*sprite, 0, z, m_CachedActualSize); + m_pGUI->DrawSprite(sprite, 0, z, m_CachedActualSize); CGUIColor color; GUI::GetSetting(this, "textcolor", color); DrawText(0, color, m_CachedActualSize.TopLeft(), z+0.1f); } Index: ps/trunk/source/gui/GUIutil.cpp =================================================================== --- ps/trunk/source/gui/GUIutil.cpp (revision 22692) +++ ps/trunk/source/gui/GUIutil.cpp (revision 22693) @@ -1,424 +1,438 @@ /* 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 "GUIutil.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "ps/CLogger.h" template CGUISetting::CGUISetting(IGUIObject& pObject, const CStr& Name) : m_pSetting(T()), m_Name(Name), m_pObject(pObject) { } template bool CGUISetting::FromString(const CStrW& Value, const bool& SkipMessage) { T settingValue; if (!GUI::ParseString(m_pObject.GetGUI(), Value, settingValue)) return false; GUI::SetSetting(&m_pObject, m_Name, settingValue, SkipMessage); return true; }; template<> bool CGUISetting::FromJSVal(JSContext* cx, JS::HandleValue Value) { CGUIColor settingValue; if (Value.isString()) { CStr name; if (!ScriptInterface::FromJSVal(cx, Value, name)) return false; if (!settingValue.ParseString(m_pObject.GetGUI(), name)) { JS_ReportError(cx, "Invalid color '%s'", name.c_str()); return false; } } else if (!ScriptInterface::FromJSVal(cx, Value, settingValue)) return false; GUI::SetSetting(&m_pObject, m_Name, settingValue); return true; }; template bool CGUISetting::FromJSVal(JSContext* cx, JS::HandleValue Value) { T settingValue; if (!ScriptInterface::FromJSVal(cx, Value, settingValue)) return false; GUI::SetSetting(&m_pObject, m_Name, settingValue); return true; }; template void CGUISetting::ToJSVal(JSContext* cx, JS::MutableHandleValue Value) { ScriptInterface::ToJSVal(cx, Value, m_pSetting); }; template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, bool& Output) { if (Value == L"true") Output = true; else if (Value == L"false") Output = false; else return false; return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, int& Output) { Output = Value.ToInt(); return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, u32& Output) { Output = Value.ToUInt(); return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, float& Output) { Output = Value.ToFloat(); return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CRect& Output) { const unsigned int NUM_COORDS = 4; float coords[NUM_COORDS]; std::wstringstream stream; stream.str(Value); // Parse each coordinate for (unsigned int i = 0; i < NUM_COORDS; ++i) { if (stream.eof()) { LOGWARNING("Too few CRect parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str()); return false; } stream >> coords[i]; if ((stream.rdstate() & std::wstringstream::failbit) != 0) { LOGWARNING("Unable to parse CRect parameters. Your input: '%s'", Value.ToUTF8().c_str()); return false; } } if (!stream.eof()) { LOGWARNING("Too many CRect parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str()); return false; } // Finally the rectangle values Output = CRect(coords[0], coords[1], coords[2], coords[3]); return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CClientArea& Output) { return Output.SetClientArea(Value.ToUTF8()); } template <> bool __ParseString(const CGUI* pGUI, const CStrW& Value, CGUIColor& Output) { return Output.ParseString(pGUI, Value.ToUTF8()); } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CSize& Output) { const unsigned int NUM_COORDS = 2; float coords[NUM_COORDS]; std::wstringstream stream; stream.str(Value); // Parse each coordinate for (unsigned int i = 0; i < NUM_COORDS; ++i) { if (stream.eof()) { LOGWARNING("Too few CSize parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str()); return false; } stream >> coords[i]; if ((stream.rdstate() & std::wstringstream::failbit) != 0) { LOGWARNING("Unable to parse CSize parameters. Your input: '%s'", Value.ToUTF8().c_str()); return false; } } Output.cx = coords[0]; Output.cy = coords[1]; if (!stream.eof()) { LOGWARNING("Too many CSize parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str()); return false; } return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CPos& Output) { const unsigned int NUM_COORDS = 2; float coords[NUM_COORDS]; std::wstringstream stream; stream.str(Value); // Parse each coordinate for (unsigned int i = 0; i < NUM_COORDS; ++i) { if (stream.eof()) { LOGWARNING("Too few CPos parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str()); return false; } stream >> coords[i]; if ((stream.rdstate() & std::wstringstream::failbit) != 0) { LOGWARNING("Unable to parse CPos parameters. Your input: '%s'", Value.ToUTF8().c_str()); return false; } } Output.x = coords[0]; Output.y = coords[1]; if (!stream.eof()) { LOGWARNING("Too many CPos parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str()); return false; } return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, EAlign& Output) { if (Value == L"left") Output = EAlign_Left; else if (Value == L"center") Output = EAlign_Center; else if (Value == L"right") Output = EAlign_Right; else return false; return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, EVAlign& Output) { if (Value == L"top") Output = EVAlign_Top; else if (Value == L"center") Output = EVAlign_Center; else if (Value == L"bottom") Output = EVAlign_Bottom; else return false; return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CGUIString& Output) { Output.SetValue(Value); return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CStr& Output) { Output = Value.ToUTF8(); return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CStrW& Output) { Output = Value; return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& Value, CGUISpriteInstance& Output) { Output = CGUISpriteInstance(Value.ToUTF8()); return true; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& UNUSED(Value), CGUIList& UNUSED(Output)) { return false; } template <> bool __ParseString(const CGUI* UNUSED(pGUI), const CStrW& UNUSED(Value), CGUISeries& UNUSED(Output)) { return false; } template PSRETURN GUI::GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, T*& Value) { ENSURE(pObject != NULL); std::map::const_iterator it = pObject->m_Settings.find(Setting); if (it == pObject->m_Settings.end()) { LOGWARNING("setting %s was not found on object %s", Setting.c_str(), pObject->GetPresentableName().c_str()); return PSRETURN_GUI_InvalidSetting; } if (it->second == nullptr) return PSRETURN_GUI_InvalidSetting; // Get value Value = &(static_cast* >(it->second)->m_pSetting); return PSRETURN_OK; } template +bool GUI::HasSetting(const IGUIObject* pObject, const CStr& Setting) +{ + return pObject->m_Settings.count(Setting) != 0; +} + +template +T& GUI::GetSetting(const IGUIObject* pObject, const CStr& Setting) +{ + return static_cast* >(pObject->m_Settings.at(Setting))->m_pSetting; +} + +template PSRETURN GUI::GetSetting(const IGUIObject* pObject, const CStr& Setting, T& Value) { T* v = NULL; PSRETURN ret = GetSettingPointer(pObject, Setting, v); if (ret == PSRETURN_OK) Value = *v; return ret; } template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, T& Value, const bool& SkipMessage) { return SetSettingWrap(pObject, Setting, Value, SkipMessage, [&pObject, &Setting, &Value]() { static_cast* >(pObject->m_Settings[Setting])->m_pSetting = std::move(Value); }); } template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage) { return SetSettingWrap(pObject, Setting, Value, SkipMessage, [&pObject, &Setting, &Value]() { static_cast* >(pObject->m_Settings[Setting])->m_pSetting = Value; }); } // Helper function for SetSetting template bool IsBoolTrue(const T&) { return false; } template <> bool IsBoolTrue(const bool& v) { return v; } template PSRETURN GUI::SetSettingWrap(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage, const std::function& valueSet) { ENSURE(pObject != NULL); if (!pObject->SettingExists(Setting)) { LOGWARNING("setting %s was not found on object %s", Setting.c_str(), pObject->GetPresentableName().c_str()); return PSRETURN_GUI_InvalidSetting; } valueSet(); // Some settings needs special attention at change // If setting was "size", we need to re-cache itself and all children if (Setting == "size") { RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); } else if (Setting == "hidden") { // Hiding an object requires us to reset it and all children if (IsBoolTrue(Value)) RecurseObject(0, pObject, &IGUIObject::ResetStates); } if (!SkipMessage) { SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting); pObject->HandleMessage(msg); } return PSRETURN_OK; } // Instantiate templated functions: // These functions avoid copies by working with a pointer and move semantics. #define TYPE(T) \ + template bool GUI::HasSetting(const IGUIObject* pObject, const CStr& Setting); \ + template T& GUI::GetSetting(const IGUIObject* pObject, const CStr& Setting); \ template PSRETURN GUI::GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, T*& Value); \ template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, T& Value, const bool& SkipMessage); \ template class CGUISetting; \ #include "GUItypes.h" #undef TYPE // Copying functions - discouraged except for primitives. #define TYPE(T) \ template PSRETURN GUI::GetSetting(const IGUIObject* pObject, const CStr& Setting, T& Value); \ template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage); \ #define GUITYPE_IGNORE_NONCOPYABLE #include "GUItypes.h" #undef GUITYPE_IGNORE_NONCOPYABLE #undef TYPE Index: ps/trunk/source/gui/GUIutil.h =================================================================== --- ps/trunk/source/gui/GUIutil.h (revision 22692) +++ ps/trunk/source/gui/GUIutil.h (revision 22693) @@ -1,333 +1,344 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* GUI util --Overview-- Contains help class GUI<>, which gives us templated parameter to all functions within GUI. --More info-- Check GUI.h */ #ifndef INCLUDED_GUIUTIL #define INCLUDED_GUIUTIL #include "gui/CGUI.h" #include "gui/CGUISprite.h" #include "gui/GUIbase.h" #include "gui/IGUIObject.h" #include class CClientArea; class CGUIString; template class GUI; class IGUISetting { public: NONCOPYABLE(IGUISetting); IGUISetting() = default; virtual ~IGUISetting() = default; /** * Parses the given string and assigns to the setting value. Used for parsing XML attributes. */ virtual bool FromString(const CStrW& Value, const bool& SkipMessage) = 0; /** * Parses the given JS::Value using ScriptInterface::FromJSVal and assigns it to the setting data. */ virtual bool FromJSVal(JSContext* cx, JS::HandleValue Value) = 0; /** * Converts the setting data to a JS::Value using ScriptInterface::ToJSVal. */ virtual void ToJSVal(JSContext* cx, JS::MutableHandleValue Value) = 0; }; template class CGUISetting : public IGUISetting { friend class GUI; public: NONCOPYABLE(CGUISetting); CGUISetting(IGUIObject& pObject, const CStr& Name); /** * Parses the given string and assigns to the setting value. Used for parsing XML attributes. */ bool FromString(const CStrW& Value, const bool& SkipMessage) override; /** * Parses the given JS::Value using ScriptInterface::FromJSVal and assigns it to the setting data. */ bool FromJSVal(JSContext* cx, JS::HandleValue Value) override; /** * Converts the setting data to a JS::Value using ScriptInterface::ToJSVal. */ void ToJSVal(JSContext* cx, JS::MutableHandleValue Value) override; private: /** * The object that stores this setting. */ IGUIObject& m_pObject; /** * Property name identifying the setting. */ const CStr m_Name; /** * Holds the value of the setting.. */ T m_pSetting; }; template bool __ParseString(const CGUI* pGUI, const CStrW& Value, T& tOutput); struct SGUIMessage; /** * Includes static functions that needs one template * argument. * * int is only to please functions that doesn't even use T * and are only within this class because it's convenient */ template class GUI { // Private functions further ahead friend class CGUI; friend class IGUIObject; public: NONCOPYABLE(GUI); + /** + * Determines whether a setting with the given name is registered. + * This function may be used as a safeguard for GetSetting. + */ + static bool HasSetting(const IGUIObject* pObject, const CStr& Setting); + + /** + * Get a mutable reference to the setting. + * If no such setting exists, an exception of type std::out_of_range is thrown. + * + * If the value is modified, there is no GUIM_SETTINGS_UPDATED message sent. + * SetSetting should be used to modify the value if there is a use for the message. + */ + static T& GetSetting(const IGUIObject* pObject, const CStr& Setting); + // Like GetSetting (below), but doesn't make a copy of the value // (so it can be modified later) static PSRETURN GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, T*& Value); /** - * Retrieves a setting by name from object pointer + * Copy-assigns the current setting value to the given reference. * * @param pObject Object pointer * @param Setting Setting by name * @param Value Stores value here, note type T! */ static PSRETURN GetSetting(const IGUIObject* pObject, const CStr& Setting, T& Value); /** * Sets a value by name using a real datatype as input. - * - * This is the official way of setting a setting, no other - * way should only cautiously be used! - * * This variant will use the move-assignment. * * @param pObject Object pointer * @param Setting Setting by name * @param Value Sets value to this, note type T! * @param SkipMessage Does not send a GUIM_SETTINGS_UPDATED if true */ static PSRETURN SetSetting(IGUIObject* pObject, const CStr& Setting, T& Value, const bool& SkipMessage = false); /** * This variant will copy the value. */ static PSRETURN SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage = false); /** * Sets a value by setting and object name using a real * datatype as input. * * This is just a wrapper for __ParseString() which really * works the magic. * * @param Value The value in string form, like "0 0 100% 100%" * @param tOutput Parsed value of type T * @return True at success. * * @see __ParseString() */ static bool ParseString(const CGUI* pGUI, const CStrW& Value, T& tOutput) { return __ParseString(pGUI, Value, tOutput); } private: /** * Changes the value of the setting by calling the valueSet functon that performs either a copy or move assignment. * Updates some internal data depending on the setting changed. */ static PSRETURN SetSettingWrap(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage, const std::function& valueSet); // templated typedef of function pointer typedef void (IGUIObject::*void_Object_pFunction_argT)(const T& arg); typedef void (IGUIObject::*void_Object_pFunction_argRefT)(T& arg); typedef void (IGUIObject::*void_Object_pFunction)(); typedef void (IGUIObject::*void_Object_pFunction_argTJS)(const T& arg, JS::HandleValueArray paramData); /** * If you want to call a IGUIObject-function * on not just an object, but also on ALL of their children * you want to use this recursion system. * It recurses an object calling a function on itself * and all children (and so forth). * * Restrictions:\n * You can also set restrictions, so that if the recursion * reaches an objects with certain setup, it just doesn't * call the function on the object, nor it's children for * that matter. i.e. it cuts that object off from the * recursion tree. What setups that can cause restrictions * are hardcoded and specific. Check out the defines * GUIRR_* for all different setups. * * Error reports are either logged or thrown out of RecurseObject. * Always use it with try/catch! * * @param RR Recurse Restrictions, set to 0 if no restrictions * @param pObject Top object, this is where the iteration starts * @param pFunc Function to recurse * @param Argument Argument for pFunc of type T * @throws PSERROR Depends on what pFunc might throw. PSERROR is standard. * Itself doesn't throw anything. */ static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction_argT pFunc, const T& Argument) { // TODO Gee: Don't run this for the base object. if (CheckIfRestricted(RR, pObject)) return; (pObject->*pFunc)(Argument); // Iterate children for (IGUIObject* const& obj : *pObject) RecurseObject(RR, obj, pFunc, Argument); } /** * Argument is reference. * * @see RecurseObject() */ static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction_argRefT pFunc, T& Argument) { if (CheckIfRestricted(RR, pObject)) return; (pObject->*pFunc)(Argument); // Iterate children for (IGUIObject* const& obj : *pObject) RecurseObject(RR, obj, pFunc, Argument); } static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction_argTJS pFunc, const T& Argument, JS::HandleValueArray paramData) { if (CheckIfRestricted(RR, pObject)) return; (pObject->*pFunc)(Argument, paramData); // Iterate children for (IGUIObject* const& obj : *pObject) RecurseObject(RR, obj, pFunc, Argument, paramData); } /** * With no argument. * * @see RecurseObject() */ static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction pFunc) { if (CheckIfRestricted(RR, pObject)) return; (pObject->*pFunc)(); // Iterate children for (IGUIObject* const& obj : *pObject) RecurseObject(RR, obj, pFunc); } /** * Checks restrictions for the iteration, for instance if * you tell the recursor to avoid all hidden objects, it * will, and this function checks a certain object's * restriction values. * * @param RR What kind of restriction, for instance hidden or disabled * @param pObject Object * @return true if restricted */ static bool CheckIfRestricted(int RR, IGUIObject* pObject) { // Statically initialise some strings, so we don't have to do // lots of allocation every time this function is called static const CStr strHidden("hidden"); static const CStr strEnabled("enabled"); static const CStr strGhost("ghost"); if (RR & GUIRR_HIDDEN) { bool hidden = true; GUI::GetSetting(pObject, strHidden, hidden); if (hidden) return true; } if (RR & GUIRR_DISABLED) { bool enabled = false; GUI::GetSetting(pObject, strEnabled, enabled); if (!enabled) return true; } if (RR & GUIRR_GHOST) { bool ghost = true; GUI::GetSetting(pObject, strGhost, ghost); if (ghost) return true; } // false means not restricted return false; } }; #endif // INCLUDED_GUIUTIL Index: ps/trunk/source/gui/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/IGUIObject.cpp (revision 22692) +++ ps/trunk/source/gui/IGUIObject.cpp (revision 22693) @@ -1,470 +1,469 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GUI.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "ps/GameSetup/Config.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "scriptinterface/ScriptInterface.h" IGUIObject::IGUIObject(CGUI* pGUI) : m_pGUI(pGUI), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime() { AddSetting("enabled"); AddSetting("hidden"); AddSetting("size"); AddSetting("style"); AddSetting("hotkey"); AddSetting("z"); AddSetting("absolute"); AddSetting("ghost"); AddSetting("aspectratio"); AddSetting("tooltip"); AddSetting("tooltip_style"); // Setup important defaults GUI::SetSetting(this, "hidden", false); GUI::SetSetting(this, "ghost", false); GUI::SetSetting(this, "enabled", true); GUI::SetSetting(this, "absolute", true); } IGUIObject::~IGUIObject() { for (const std::pair& p : m_Settings) delete p.second; if (!m_ScriptHandlers.empty()) JS_RemoveExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this); } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- void IGUIObject::AddChild(IGUIObject* pChild) { // ENSURE(pChild); pChild->SetParent(this); m_Children.push_back(pChild); { try { // Atomic function, if it fails it won't // have changed anything //UpdateObjects(); pChild->GetGUI()->UpdateObjects(); } catch (PSERROR_GUI&) { // If anything went wrong, reverse what we did and throw // an exception telling it never added a child m_Children.erase(m_Children.end()-1); throw; } } // else do nothing } void IGUIObject::AddToPointersMap(map_pObjects& ObjectMap) { // Just don't do anything about the top node if (m_pParent == NULL) return; // Now actually add this one // notice we won't add it if it's doesn't have any parent // (i.e. being the base object) if (m_Name.empty()) { throw PSERROR_GUI_ObjectNeedsName(); } if (ObjectMap.count(m_Name) > 0) { throw PSERROR_GUI_NameAmbiguity(m_Name.c_str()); } else { ObjectMap[m_Name] = this; } } void IGUIObject::Destroy() { // Is there anything besides the children to destroy? } template void IGUIObject::AddSetting(const CStr& Name) { // This can happen due to inheritance if (SettingExists(Name)) return; m_Settings[Name] = new CGUISetting(*this, Name); } bool IGUIObject::MouseOver() { return m_CachedActualSize.PointInside(m_pGUI->GetMousePos()); } bool IGUIObject::MouseOverIcon() { return false; } void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver) { if (pMouseOver == this) { if (!m_MouseHovering) SendEvent(GUIM_MOUSE_ENTER, "mouseenter"); m_MouseHovering = true; SendEvent(GUIM_MOUSE_OVER, "mousemove"); } else { if (m_MouseHovering) { m_MouseHovering = false; SendEvent(GUIM_MOUSE_LEAVE, "mouseleave"); } } } bool IGUIObject::SettingExists(const CStr& Setting) const { return m_Settings.count(Setting) == 1; } PSRETURN IGUIObject::SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage) { if (!SettingExists(Setting)) return PSRETURN_GUI_InvalidSetting; if (!m_Settings[Setting]->FromString(Value, SkipMessage)) return PSRETURN_GUI_UnableToParse; return PSRETURN_OK; } void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject) { if (!MouseOver()) return; // Check if we've got competition at all if (pObject == NULL) { pObject = this; return; } // Or if it's closer if (GetBufferedZ() >= pObject->GetBufferedZ()) { pObject = this; return; } } IGUIObject* IGUIObject::GetParent() const { // Important, we're not using GetParent() for these // checks, that could screw it up if (m_pParent) { if (m_pParent->m_pParent == NULL) return NULL; } return m_pParent; } void IGUIObject::ResetStates() { // Notify the gui that we aren't hovered anymore UpdateMouseOver(nullptr); } void IGUIObject::UpdateCachedSize() { bool absolute; GUI::GetSetting(this, "absolute", absolute); float aspectratio = 0.f; GUI::GetSetting(this, "aspectratio", aspectratio); - CClientArea* ca; - GUI::GetSettingPointer(this, "size", ca); + const CClientArea& ca = GUI::GetSetting(this, "size"); // If absolute="false" and the object has got a parent, // use its cached size instead of the screen. Notice // it must have just been cached for it to work. if (absolute == false && m_pParent && !IsRootObject()) - m_CachedActualSize = ca->GetClientArea(m_pParent->m_CachedActualSize); + m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize); else - m_CachedActualSize = ca->GetClientArea(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale)); + m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale)); // In a few cases, GUI objects have to resize to fill the screen // but maintain a constant aspect ratio. // Adjust the size to be the max possible, centered in the original size: if (aspectratio) { if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight()*aspectratio) { float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight()*aspectratio; m_CachedActualSize.left += delta/2.f; m_CachedActualSize.right -= delta/2.f; } else { float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth()/aspectratio; m_CachedActualSize.bottom -= delta/2.f; m_CachedActualSize.top += delta/2.f; } } } void IGUIObject::LoadStyle(CGUI& pGUI, const CStr& StyleName) { if (pGUI.HasStyle(StyleName)) LoadStyle(pGUI.GetStyle(StyleName)); else debug_warn(L"IGUIObject::LoadStyle failed"); } void IGUIObject::LoadStyle(const SGUIStyle& Style) { // Iterate settings, it won't be able to set them all probably, but that doesn't matter for (const std::pair& p : Style.m_SettingsDefaults) { // Try set setting in object SetSetting(p.first, p.second); // It doesn't matter if it fail, it's not suppose to be able to set every setting. // since it's generic. // The beauty with styles is that it can contain more settings // than exists for the objects using it. So if the SetSetting // fails, don't care. } } float IGUIObject::GetBufferedZ() const { bool absolute; GUI::GetSetting(this, "absolute", absolute); float Z; GUI::GetSetting(this, "z", Z); if (absolute) return Z; else { if (GetParent()) return GetParent()->GetBufferedZ() + Z; else { // In philosophy, a parentless object shouldn't be able to have a relative sizing, // but we'll accept it so that absolute can be used as default without a complaint. // Also, you could consider those objects children to the screen resolution. return Z; } } } void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI) { JSContext* cx = pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedValue globalVal(cx, pGUI->GetGlobalObject()); JS::RootedObject globalObj(cx, &globalVal.toObject()); const int paramCount = 1; const char* paramNames[paramCount] = { "mouse" }; // Location to report errors from CStr CodeName = GetName()+" "+Action; // Generate a unique name static int x = 0; char buf[64]; sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str()); JS::CompileOptions options(cx); options.setFileAndLine(CodeName.c_str(), 0); options.setIsRunOnce(false); JS::RootedFunction func(cx); JS::AutoObjectVector emptyScopeChain(cx); if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func)) { LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str()); return; } JS::RootedObject funcObj(cx, JS_GetFunctionObject(func)); SetScriptHandler(Action, funcObj); } void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function) { ENSURE(m_pGUI && "A GUI must be associated with the GUIObject before adding ScriptHandlers!"); if (m_ScriptHandlers.empty()) JS_AddExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this); m_ScriptHandlers[Action] = JS::Heap(Function); } InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName) { PROFILE2_EVENT("gui event"); PROFILE2_ATTR("type: %s", EventName.c_str()); PROFILE2_ATTR("object: %s", m_Name.c_str()); SGUIMessage msg(type); HandleMessage(msg); ScriptEvent(EventName); return (msg.skipped ? IN_PASS : IN_HANDLED); } void IGUIObject::ScriptEvent(const CStr& Action) { std::map >::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); // Set up the 'mouse' parameter JS::RootedValue mouse(cx); const CPos& mousePos = m_pGUI->GetMousePos(); m_pGUI->GetScriptInterface()->CreateObject( &mouse, "x", mousePos.x, "y", mousePos.y, "buttons", m_pGUI->GetMouseButtons()); JS::AutoValueVector paramData(cx); paramData.append(mouse); JS::RootedObject obj(cx, GetJSObject()); JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); JS::RootedValue result(cx); bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result); if (!ok) { // We have no way to propagate the script exception, so just ignore it // and hope the caller checks JS_IsExceptionPending } } void IGUIObject::ScriptEvent(const CStr& Action, JS::HandleValueArray paramData) { std::map >::iterator it = m_ScriptHandlers.find(Action); if (it == m_ScriptHandlers.end()) return; JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedObject obj(cx, GetJSObject()); JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second)); JS::RootedValue result(cx); if (!JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result)) JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str()); } void IGUIObject::CreateJSObject() { JSContext* cx = m_pGUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); m_JSObject.init(cx, m_pGUI->GetScriptInterface()->CreateCustomObject("GUIObject")); JS_SetPrivate(m_JSObject.get(), this); } JSObject* IGUIObject::GetJSObject() { // Cache the object when somebody first asks for it, because otherwise // we end up doing far too much object allocation. if (!m_JSObject.initialized()) CreateJSObject(); return m_JSObject.get(); } CStr IGUIObject::GetPresentableName() const { // __internal(), must be at least 13 letters to be able to be // an internal name if (m_Name.length() <= 12) return m_Name; if (m_Name.substr(0, 10) == "__internal") return CStr("[unnamed object]"); else return m_Name; } void IGUIObject::SetFocus() { m_pGUI->SetFocusedObject(this); } bool IGUIObject::IsFocused() const { return m_pGUI->GetFocusedObject() == this; } bool IGUIObject::IsRootObject() const { return m_pParent == m_pGUI->GetBaseObject(); } void IGUIObject::TraceMember(JSTracer* trc) { // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced! for (std::pair>& handler : m_ScriptHandlers) JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers"); } // Instantiate templated functions: #define TYPE(T) template void IGUIObject::AddSetting(const CStr& Name); #include "GUItypes.h" #undef TYPE