Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp (revision 22603) +++ ps/trunk/source/gui/CButton.cpp (revision 22604) @@ -1,122 +1,122 @@ /* 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(GUIST_float, "buffer_zone"); - AddSetting(GUIST_CGUIString, "caption"); - AddSetting(GUIST_int, "cell_id"); - AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_CStrW, "sound_disabled"); - AddSetting(GUIST_CStrW, "sound_enter"); - AddSetting(GUIST_CStrW, "sound_leave"); - AddSetting(GUIST_CStrW, "sound_pressed"); - AddSetting(GUIST_CStrW, "sound_released"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_over"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_pressed"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled"); - AddSetting(GUIST_EAlign, "text_align"); - AddSetting(GUIST_EVAlign, "text_valign"); - AddSetting(GUIST_CGUIColor, "textcolor"); - AddSetting(GUIST_CGUIColor, "textcolor_over"); - AddSetting(GUIST_CGUIColor, "textcolor_pressed"); - AddSetting(GUIST_CGUIColor, "textcolor_disabled"); - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + 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"); // Add text AddText(new SGUIText()); } CButton::~CButton() { } void CButton::SetupText() { if (!GetGUI()) return; 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; GUI::GetSetting(this, "caption", caption); float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); *m_GeneratedTexts[0] = GetGUI()->GenerateText(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); GUI::GetSetting(this, strCellId, cell_id); DrawButton(m_CachedActualSize, bz, *sprite, *sprite_over, *sprite_pressed, *sprite_disabled, cell_id); CGUIColor color = ChooseColor(); DrawText(0, color, m_TextPos, bz+0.1f); } Index: ps/trunk/source/gui/CChart.cpp =================================================================== --- ps/trunk/source/gui/CChart.cpp (revision 22603) +++ ps/trunk/source/gui/CChart.cpp (revision 22604) @@ -1,332 +1,332 @@ /* 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 "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(GUIST_CGUIColor, "axis_color"); - AddSetting(GUIST_float, "axis_width"); - AddSetting(GUIST_float, "buffer_zone"); - AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_CStrW, "format_x"); - AddSetting(GUIST_CStrW, "format_y"); - AddSetting(GUIST_CGUIList, "series_color"); - AddSetting(GUIST_CGUISeries, "series"); - AddSetting(GUIST_EAlign, "text_align"); + 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 (!GetGUI()) return; 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); m_Series.clear(); m_Series.resize(pSeries->m_Series.size()); for (size_t i = 0; i < pSeries->m_Series.size(); ++i) { CChartData& data = m_Series[i]; if (i < pSeriesColor->m_Items.size() && !GUI::ParseColor(pSeriesColor->m_Items[i].GetOriginalString(), data.m_Color, 0)) LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(pSeriesColor->m_Items[i].GetOriginalString())); data.m_Points = pSeries->m_Series[i]; } UpdateBounds(); SetupText(); } void CChart::SetupText() { if (!GetGUI()) return; for (SGUIText* t : m_GeneratedTexts) delete t; 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(); } SGUIText* text = new SGUIText(); *text = GetGUI()->GenerateText(gui_str, font, 0, buffer_zone, this); AddText(text); return text->m_Size; } 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 22603) +++ ps/trunk/source/gui/CCheckBox.cpp (revision 22604) @@ -1,150 +1,150 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "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(GUIST_float, "buffer_zone"); - AddSetting(GUIST_CGUIString, "caption"); - AddSetting(GUIST_int, "cell_id"); - AddSetting(GUIST_bool, "checked"); - AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_CStrW, "sound_disabled"); - AddSetting(GUIST_CStrW, "sound_enter"); - AddSetting(GUIST_CStrW, "sound_leave"); - AddSetting(GUIST_CStrW, "sound_pressed"); - AddSetting(GUIST_CStrW, "sound_released"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_over"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_pressed"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled"); - AddSetting(GUIST_CGUISpriteInstance, "sprite2"); - AddSetting(GUIST_CGUISpriteInstance, "sprite2_over"); - AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed"); - AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled"); - AddSetting(GUIST_float, "square_side"); - AddSetting(GUIST_CGUIColor, "textcolor"); - AddSetting(GUIST_CGUIColor, "textcolor_over"); - AddSetting(GUIST_CGUIColor, "textcolor_pressed"); - AddSetting(GUIST_CGUIColor, "textcolor_disabled"); - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + 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(new SGUIText()); } CCheckBox::~CCheckBox() { } void CCheckBox::SetupText() { if (!GetGUI()) return; 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; GUI::GetSetting(this, "caption", caption); float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); *m_GeneratedTexts[0] = GetGUI()->GenerateText(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); } 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); } Index: ps/trunk/source/gui/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp (revision 22603) +++ ps/trunk/source/gui/CDropDown.cpp (revision 22604) @@ -1,565 +1,565 @@ /* 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(GUIST_float, "button_width"); - AddSetting(GUIST_float, "dropdown_size"); - AddSetting(GUIST_float, "dropdown_buffer"); - AddSetting(GUIST_uint, "minimum_visible_items"); -// AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_CStrW, "sound_closed"); - AddSetting(GUIST_CStrW, "sound_disabled"); - AddSetting(GUIST_CStrW, "sound_enter"); - AddSetting(GUIST_CStrW, "sound_leave"); - AddSetting(GUIST_CStrW, "sound_opened"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); // Background that sits around the size - AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_list"); // Background of the drop down list - AddSetting(GUIST_CGUISpriteInstance, "sprite2"); // Button that sits to the right - AddSetting(GUIST_CGUISpriteInstance, "sprite2_over"); - AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed"); - AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled"); - AddSetting(GUIST_EVAlign, "text_valign"); + 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(GUIST_CGUIColor, "textcolor_over"); - //AddSetting(GUIST_CGUIColor, "textcolor_pressed"); - AddSetting(GUIST_CGUIColor, "textcolor_selected"); - AddSetting(GUIST_CGUIColor, "textcolor_disabled"); + //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 = GetMousePos(); if (!GetListRect().PointInside(mouse)) break; bool scrollbar; CGUIList* pList; 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) { 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()) 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 { CPos mouse = 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); // 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) { 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]); 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(!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); 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(GetMousePos()); } else return m_CachedActualSize.PointInside(GetMousePos()); } void CDropDown::Draw() { if (!GetGUI()) return; 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); 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); GetGUI()->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); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_Open) { GUI::GetSettingPointer(this, "sprite2_pressed", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_MouseHovering) { GUI::GetSettingPointer(this, "sprite2_over", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else GetGUI()->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; if (m_Open) { if (m_HideScrollBar) *scrollbar = false; DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); if (m_HideScrollBar) *scrollbar = old; } } // When a dropdown list is opened, it needs to be visible above all the other // controls on the page. The only way I can think of to do this is to increase // its z value when opened, so that it's probably on top. float CDropDown::GetBufferedZ() const { float bz = CList::GetBufferedZ(); if (m_Open) return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value else return bz; } Index: ps/trunk/source/gui/CImage.cpp =================================================================== --- ps/trunk/source/gui/CImage.cpp (revision 22603) +++ ps/trunk/source/gui/CImage.cpp (revision 22604) @@ -1,52 +1,52 @@ /* 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(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_int, "cell_id"); - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + AddSetting("sprite"); + AddSetting("cell_id"); + AddSetting("tooltip"); + AddSetting("tooltip_style"); } CImage::~CImage() { } void CImage::Draw() { if (!GetGUI()) return; 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); } Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 22603) +++ ps/trunk/source/gui/CInput.cpp (revision 22604) @@ -1,2144 +1,2144 @@ /* 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(GUIST_int, "buffer_position"); - AddSetting(GUIST_float, "buffer_zone"); - AddSetting(GUIST_CStrW, "caption"); - AddSetting(GUIST_int, "cell_id"); - AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_CStrW, "mask_char"); - AddSetting(GUIST_bool, "mask"); - AddSetting(GUIST_int, "max_length"); - AddSetting(GUIST_bool, "multiline"); - AddSetting(GUIST_bool, "readonly"); - AddSetting(GUIST_bool, "scrollbar"); - AddSetting(GUIST_CStr, "scrollbar_style"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_selectarea"); - AddSetting(GUIST_CGUIColor, "textcolor"); - AddSetting(GUIST_CGUIColor, "textcolor_selected"); - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + 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); 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); 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); else 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); 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); // 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); 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) { 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) break; if (m_iBufferPos == (int)pCaption->length()) *pCaption = pCaption->Left((long)pCaption->length() - 1); else *pCaption = pCaption->Left(m_iBufferPos - 1) + pCaption->Right((long)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()) break; *pCaption = pCaption->Left(m_iBufferPos) + pCaption->Right((long)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) break; m_WantedX = 0.0f; if (SelectingText()) DeleteCurSelection(); m_iBufferPos_Tail = -1; if (m_iBufferPos == (int)pCaption->length()) *pCaption += cooked; else *pCaption = pCaption->Left(m_iBufferPos) + cooked + pCaption->Right((long)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) { 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_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()) ++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); 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; else *pCaption = pCaption->Left(m_iBufferPos) + text + pCaption->Right((long) 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); 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) { m_iBufferPos_Tail = 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()) { // 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()) { 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()) { 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) { 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()) { // Select chars to the right until we hit whitespace while (++m_iBufferPos < (int)pCaption->length()) { 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()) { 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 (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); if (pCaption->empty()) break; m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); 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])) { // 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]))) { // // 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])) break; m_iBufferPos--; } // now go until we hit white space or punctuation while (m_iBufferPos > 0) { if (iswspace((*pCaption)[m_iBufferPos - 1])) break; 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()) { if (!iswspace((*pCaption)[m_iBufferPos_Tail])) break; } if (m_iBufferPos_Tail == (int)pCaption->length()) break; // now 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])) break; } } else { // single whitespace so select word to the right while (++m_iBufferPos_Tail < (int)pCaption->length()) { if (!iswspace((*pCaption)[m_iBufferPos_Tail])) break; } if (m_iBufferPos_Tail == (int)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()) { 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])) break; 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])) 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(); if (!GetGUI()) return; 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; 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); GUI::GetSetting(this, "cell_id", cell_id); GetGUI()->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); } if (i < (int)it->m_ListOfX.size()) { if (!mask) 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]); 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 = 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); 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); 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/CList.cpp =================================================================== --- ps/trunk/source/gui/CList.cpp (revision 22603) +++ ps/trunk/source/gui/CList.cpp (revision 22604) @@ -1,557 +1,557 @@ /* 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(GUIST_float, "buffer_zone"); - AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_bool, "scrollbar"); - AddSetting(GUIST_CStr, "scrollbar_style"); - AddSetting(GUIST_CStrW, "sound_disabled"); - AddSetting(GUIST_CStrW, "sound_selected"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_selectarea"); - AddSetting(GUIST_int, "cell_id"); - AddSetting(GUIST_EAlign, "text_align"); - AddSetting(GUIST_CGUIColor, "textcolor"); - AddSetting(GUIST_CGUIColor, "textcolor_selected"); - AddSetting(GUIST_int, "selected"); // Index selected. -1 is none. - AddSetting(GUIST_bool, "auto_scroll"); - AddSetting(GUIST_int, "hovered"); - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + 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(GUIST_CGUIList, "list"); - AddSetting(GUIST_CGUIList, "list_data"); // TODO: this should be a list of raw strings, not of CGUIStrings + 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() { if (!GetGUI()) return; m_Modified = true; CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); //ENSURE(m_GeneratedTexts.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. for (SGUIText* const& t : m_GeneratedTexts) delete t; 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) { // Create a new SGUIText. Later on, input it using AddText() SGUIText* text = new SGUIText(); if (!pList->m_Items[i].GetOriginalString().empty()) *text = GetGUI()->GenerateText(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 = GetGUI()->GenerateText(align_string, font, width, buffer_zone, this); } m_ItemsYPositions[i] = buffered_y; buffered_y += text->m_Size.cy; AddText(text); } 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); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); GetGUI()->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); } } CGUIColor color; GUI::GetSetting(this, _textcolor, color); 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); CGUIString data_string; data_string.SetValue(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); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); if (selected != (int)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); 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) GUI::SetSetting(this, "selected", 0); } void CList::SelectLastElement() { int selected; GUI::GetSetting(this, "selected", selected); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); if (selected != (int)pList->m_Items.size()-1) GUI::SetSetting(this, "selected", (int)pList->m_Items.size()-1); } void CList::UpdateAutoScroll() { int selected; bool scrollbar; float scroll; GUI::GetSetting(this, "selected", selected); GUI::GetSetting(this, "scrollbar", 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()) return; 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(); CRect rect = GetListRect(); CPos mouse = 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) 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 22603) +++ ps/trunk/source/gui/COList.cpp (revision 22604) @@ -1,492 +1,492 @@ /* 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(GUIST_CGUISpriteInstance, "sprite_heading"); - AddSetting(GUIST_bool, "sortable"); // The actual sorting is done in JS for more versatility - AddSetting(GUIST_CStr, "selected_column"); - AddSetting(GUIST_int, "selected_column_order"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_asc"); // Show the order of sorting - AddSetting(GUIST_CGUISpriteInstance, "sprite_desc"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_not_sorted"); + 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() { if (!GetGUI()) return; CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); 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. for (SGUIText* const& t : m_GeneratedTexts) delete t; 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; SGUIText* text = new SGUIText(); CGUIString gui_string; gui_string.SetValue(column.m_Heading); *text = GetGUI()->GenerateText(gui_string, font, width, buffer_zone, this); AddText(text); m_HeadingHeight = std::max(m_HeadingHeight, text->m_Size.cy + COLUMN_SHIFT.y); } // Generate texts float buffered_y = 0.f; 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); SGUIText* text = new SGUIText(); if (!pList_c->m_Items[i].GetOriginalString().empty()) *text = GetGUI()->GenerateText(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 = GetGUI()->GenerateText(align_string, font, width, buffer_zone, this); } shift = std::max(shift, text->m_Size.cy); AddText(text); } buffered_y += shift; } 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; CPos mouse = 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(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(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(attr_value.FromUTF8(), width)) LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); else { // Check if it's a relative value, and save as decimal if so. if (attr_value.find("%") != std::string::npos) width = width / 100.f; column.m_Width = width; } } else if (attr_name == "heading") { column.m_Heading = attr_value.FromUTF8(); } } for (XMBElement grandchild : child.GetChildNodes()) { if (grandchild.GetNodeName() != elmt_translatableAttribute) continue; CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id)); // only the heading is translatable for list column if (attributeName.empty() || attributeName != "heading") { LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str()); continue; } CStr value(grandchild.GetText()); if (value.empty()) continue; CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any. if (!context.empty()) { CStr translatedValue(g_L10n.TranslateWithContext(context, value)); column.m_Heading = translatedValue.FromUTF8(); } else { CStr translatedValue(g_L10n.Translate(value)); column.m_Heading = translatedValue.FromUTF8(); } } m_Columns.push_back(column); - AddSetting(GUIST_CGUIList, "list_" + column.m_Id); - AddSetting(GUIST_bool, "hidden_" + column.m_Id); + AddSetting("list_" + column.m_Id); + AddSetting("hidden_" + column.m_Id); GUI::SetSetting(this, "hidden_" + column.m_Id, hidden); SetupText(); return true; } else { 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(); if (!GetGUI()) return; 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); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); GetGUI()->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); } } // Draw line above column header CGUISpriteInstance* sprite_heading = NULL; GUI::GetSettingPointer(this, "sprite_heading", 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); // 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; 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); else GUI::GetSettingPointer(this, "sprite_desc", sprite); } else GUI::GetSettingPointer(this, "sprite_not_sorted", sprite); GetGUI()->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 size_t objectsCount = m_Columns.size(); 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 22603) +++ ps/trunk/source/gui/CProgressBar.cpp (revision 22604) @@ -1,86 +1,86 @@ /* 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(GUIST_CGUISpriteInstance, "sprite_background"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_bar"); - AddSetting(GUIST_float, "caption"); // aka value from 0 to 100 - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + 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() { if (!GetGUI()) return; 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); // 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); } Index: ps/trunk/source/gui/CSlider.cpp =================================================================== --- ps/trunk/source/gui/CSlider.cpp (revision 22603) +++ ps/trunk/source/gui/CSlider.cpp (revision 22604) @@ -1,147 +1,146 @@ /* 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(GUIST_float, "value"); - AddSetting(GUIST_float, "min_value"); - AddSetting(GUIST_float, "max_value"); - AddSetting(GUIST_int, "cell_id"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_CGUISpriteInstance, "sprite_bar"); - AddSetting(GUIST_float, "button_width"); + 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 = 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(GetMousePos().x - m_Mouse.x) * GetSliderRatio(); m_Mouse = GetMousePos(); IncrementallyChangeValue(difference); } break; } default: break; } } void CSlider::Draw() { if (!GetGUI()) return; CGUISpriteInstance* sprite; CGUISpriteInstance* sprite_button; 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()); } 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 22603) +++ ps/trunk/source/gui/CText.cpp (revision 22604) @@ -1,267 +1,267 @@ /* 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(GUIST_float, "buffer_zone"); - AddSetting(GUIST_CGUIString, "caption"); - AddSetting(GUIST_int, "cell_id"); - AddSetting(GUIST_bool, "clip"); - AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_bool, "scrollbar"); - AddSetting(GUIST_CStr, "scrollbar_style"); - AddSetting(GUIST_bool, "scroll_bottom"); - AddSetting(GUIST_bool, "scroll_top"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_EAlign, "text_align"); - AddSetting(GUIST_EVAlign, "text_valign"); - AddSetting(GUIST_CGUIColor, "textcolor"); - AddSetting(GUIST_CGUIColor, "textcolor_disabled"); - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + 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(GUIST_CStrW, "_icon_tooltip"); - AddSetting(GUIST_CStr, "_icon_tooltip_style"); + 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(new SGUIText()); } 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; bool scrollbar; GUI::GetSetting(this, "caption", caption); 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; float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); *m_GeneratedTexts[0] = GetGUI()->GenerateText(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]->m_Size.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(); if (!GetGUI()) return; CGUISpriteInstance* 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); 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 (SGUIText* const& guitext : m_GeneratedTexts) for (const SGUIText::SSpriteCall& spritecall : guitext->m_SpriteCalls) { // Check mouse over sprite if (!spritecall.m_Area.PointInside(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 22603) +++ ps/trunk/source/gui/CTooltip.cpp (revision 22604) @@ -1,176 +1,176 @@ /* 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(GUIST_float, "buffer_zone"); - AddSetting(GUIST_CGUIString, "caption"); - AddSetting(GUIST_CStrW, "font"); - AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_int, "delay"); - AddSetting(GUIST_CGUIColor, "textcolor"); - AddSetting(GUIST_float, "maxwidth"); - AddSetting(GUIST_CPos, "offset"); - AddSetting(GUIST_EVAlign, "anchor"); - AddSetting(GUIST_EAlign, "text_align"); + 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(GUIST_bool, "independent"); + AddSetting("independent"); // If the tooltip is just a reference to another object: - AddSetting(GUIST_CStr, "use_object"); - AddSetting(GUIST_bool, "hide_object"); + AddSetting("use_object"); + AddSetting("hide_object"); // Private settings: // This is set by GUITooltip - AddSetting(GUIST_CPos, "_mousepos"); + 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(new SGUIText()); } CTooltip::~CTooltip() { } void CTooltip::SetupText() { if (!GetGUI()) return; 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; GUI::GetSetting(this, "caption", caption); float max_width = 0.f; GUI::GetSetting(this, "maxwidth", max_width); *m_GeneratedTexts[0] = GetGUI()->GenerateText(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 = GetMousePos(); else GUI::GetSetting(this, "_mousepos", mousepos); GUI::GetSetting(this, "offset", offset); GUI::GetSetting(this, "anchor", anchor); float textwidth = m_GeneratedTexts[0]->m_Size.cx; float textheight = m_GeneratedTexts[0]->m_Size.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() { if (!GetGUI()) return; 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); // 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); CGUIColor color; GUI::GetSetting(this, "textcolor", color); DrawText(0, color, m_CachedActualSize.TopLeft(), z+0.1f); } Index: ps/trunk/source/gui/GUItypes.h =================================================================== --- ps/trunk/source/gui/GUItypes.h (revision 22603) +++ ps/trunk/source/gui/GUItypes.h (revision 22604) @@ -1,44 +1,44 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* This file is used by all bits of GUI code that need to repeat some code for a variety of types (to avoid repeating the list of types in half a dozen places, and to make it much easier to add a new type). Just do #define TYPE(T) your_code_involving_T; #include "GUItypes.h" #undef TYPE to handle every possible type. */ TYPE(bool) -TYPE(int) -TYPE(uint) +TYPE(i32) +TYPE(u32) TYPE(float) TYPE(CGUIColor) TYPE(CClientArea) TYPE(CGUIString) #ifndef GUITYPE_IGNORE_CGUISpriteInstance TYPE(CGUISpriteInstance) #endif TYPE(CStr) TYPE(CStrW) TYPE(EAlign) TYPE(EVAlign) TYPE(CPos) TYPE(CGUIList) TYPE(CGUISeries) Index: ps/trunk/source/gui/GUIutil.cpp =================================================================== --- ps/trunk/source/gui/GUIutil.cpp (revision 22603) +++ ps/trunk/source/gui/GUIutil.cpp (revision 22604) @@ -1,416 +1,430 @@ /* 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 "maths/Matrix3D.h" #include "ps/CLogger.h" #include "ps/GameSetup/Config.h" extern int g_xres, g_yres; +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(Value, settingValue)) + return false; + + GUI::SetSetting(&m_pObject, m_Name, settingValue, SkipMessage); + 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 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 CStrW& Value, int& Output) { Output = Value.ToInt(); return true; } template <> bool __ParseString(const CStrW& Value, u32& Output) { Output = Value.ToUInt(); return true; } template <> bool __ParseString(const CStrW& Value, float& Output) { Output = Value.ToFloat(); return true; } template <> bool __ParseString(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 CStrW& Value, CClientArea& Output) { return Output.SetClientArea(Value.ToUTF8()); } template <> bool GUI::ParseColor(const CStrW& Value, CGUIColor& Output, int DefaultAlpha) { return Output.ParseString(Value.ToUTF8(), DefaultAlpha); } template <> bool __ParseString(const CStrW& Value, CGUIColor& Output) { return Output.ParseString(Value.ToUTF8()); } template <> bool __ParseString(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 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 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 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 CStrW& Value, CGUIString& Output) { Output.SetValue(Value); return true; } template <> bool __ParseString(const CStrW& Value, CStr& Output) { // Do very little. Output = Value.ToUTF8(); return true; } template <> bool __ParseString(const CStrW& Value, CStrW& Output) { Output = Value; return true; } template <> bool __ParseString(const CStrW& Value, CGUISpriteInstance& Output) { Output = CGUISpriteInstance(Value.ToUTF8()); return true; } template <> bool __ParseString(const CStrW& UNUSED(Value), CGUIList& UNUSED(Output)) { return false; } template <> bool __ParseString(const CStrW& UNUSED(Value), CGUISeries& UNUSED(Output)) { return false; } CMatrix3D GetDefaultGuiMatrix() { float xres = g_xres / g_GuiScale; float yres = g_yres / g_GuiScale; CMatrix3D m; m.SetIdentity(); m.Scale(1.0f, -1.f, 1.0f); m.Translate(0.0f, yres, -1000.0f); CMatrix3D proj; proj.SetOrtho(0.f, xres, 0.f, yres, -1.f, 1000.f); m = proj * m; return m; } -#ifndef NDEBUG - #define TYPE(T) \ - template<> void CheckType(const IGUIObject* obj, const CStr& setting) { \ - std::map::const_iterator it = obj->m_Settings.find(setting); \ - if (it == obj->m_Settings.end() || it->second.m_Type != GUIST_##T) \ - { \ - /* Abort now, to avoid corrupting everything by invalidly \ - casting pointers */ \ - DEBUG_DISPLAY_ERROR(L"FATAL ERROR: Inconsistent types in GUI"); \ - } \ - } - #include "GUItypes.h" - #undef TYPE -#endif - - 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); + 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.m_pSetting == NULL) + if (it->second == nullptr) return PSRETURN_GUI_InvalidSetting; -#ifndef NDEBUG - CheckType(pObject, Setting); -#endif - // Get value - Value = (T*)(it->second.m_pSetting); + Value = &(static_cast* >(it->second)->m_pSetting); return PSRETURN_OK; } 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); + 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; + 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; } -#ifndef NDEBUG - CheckType(pObject, Setting); -#endif - 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: #define TYPE(T) \ template PSRETURN GUI::GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, T*& Value); \ template PSRETURN GUI::GetSetting(const IGUIObject* pObject, const CStr& Setting, T& Value); \ template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, T& Value, const bool& SkipMessage); \ - template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage); + template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage); \ + template class CGUISetting; \ + #define GUITYPE_IGNORE_CGUISpriteInstance #include "GUItypes.h" #undef GUITYPE_IGNORE_CGUISpriteInstance #undef TYPE // Don't instantiate GetSetting - this will cause linker errors if // you attempt to retrieve a sprite using GetSetting, since that copies the sprite // and will mess up the caching performed by DrawSprite. You have to use GetSettingPointer // instead. (This is mainly useful to stop me accidentally using the wrong function.) template PSRETURN GUI::GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, CGUISpriteInstance*& Value); template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, CGUISpriteInstance& Value, const bool& SkipMessage); +template class CGUISetting; Index: ps/trunk/source/gui/GUIutil.h =================================================================== --- ps/trunk/source/gui/GUIutil.h (revision 22603) +++ ps/trunk/source/gui/GUIutil.h (revision 22604) @@ -1,304 +1,361 @@ /* 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; class CMatrix3D; +template class GUI; + +class IGUISetting +{ +public: + virtual ~IGUISetting() {}; + + /** + * 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: + 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 CStrW& Value, T& tOutput); // Model-view-projection matrix with (0,0) in top-left of screen CMatrix3D GetDefaultGuiMatrix(); struct SGUIMessage; -#ifndef NDEBUG -// Used to ensure type-safety, sort of -template void CheckType(const IGUIObject* obj, const CStr& setting); -#endif - - /** * 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: // 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 * * @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); /** * This will return the value of the first sprite if it's not null, * if it is null, it will return the value of the second sprite, if * that one is null, then null it is. * * @param prim Primary sprite that should be used * @param sec Secondary sprite if Primary should fail * @return Resulting string */ static const CGUISpriteInstance& FallBackSprite(const CGUISpriteInstance& prim, const CGUISpriteInstance& sec) { return (prim.IsEmpty() ? sec : prim); } /** * Same principle as FallBackSprite * * @param prim Primary color that should be used * @param sec Secondary color if Primary should fail * @return Resulting color * @see FallBackSprite */ static CGUIColor FallBackColor(const CGUIColor& prim, const CGUIColor& sec) { // CGUIColor() == null. return ((prim!=CGUIColor())?(prim):(sec)); } /** * 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 CStrW& Value, T& tOutput) { return __ParseString(Value, tOutput); } static bool ParseColor(const CStrW& Value, CGUIColor& tOutput, int DefaultAlpha); 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 22603) +++ ps/trunk/source/gui/IGUIObject.cpp (revision 22604) @@ -1,577 +1,492 @@ /* 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" -template -void SGUISetting::Init(IGUIObject& pObject, const CStr& Name) -{ - m_pSetting = new T(); - - m_FromJSVal = [Name, &pObject](JSContext* cx, JS::HandleValue v) { - T value; - if (!ScriptInterface::FromJSVal(cx, v, value)) - return false; - - GUI::SetSetting(&pObject, Name, value); - return true; - }; - - m_ToJSVal = [Name, this](JSContext* cx, JS::MutableHandleValue v) { - ScriptInterface::ToJSVal(cx, v, *static_cast(m_pSetting)); - }; -} - IGUIObject::IGUIObject(CGUI* pGUI) : m_pGUI(pGUI), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime() { - AddSetting(GUIST_bool, "enabled"); - AddSetting(GUIST_bool, "hidden"); - AddSetting(GUIST_CClientArea, "size"); - AddSetting(GUIST_CStr, "style"); - AddSetting(GUIST_CStr, "hotkey"); - AddSetting(GUIST_float, "z"); - AddSetting(GUIST_bool, "absolute"); - AddSetting(GUIST_bool, "ghost"); - AddSetting(GUIST_float, "aspectratio"); - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + 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) - switch (p.second.m_Type) - { - // delete() needs to know the type of the variable - never delete a void* -#define TYPE(t) case GUIST_##t: delete (t*)p.second.m_pSetting; break; -#include "GUItypes.h" -#undef TYPE - default: - debug_warn(L"Invalid setting type"); - } + 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); // If this (not the child) object is already attached // to a CGUI, it pGUI pointer will be non-null. // This will mean we'll have to check if we're using // names already used. if (pChild->GetGUI()) { 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? } -void IGUIObject::AddSetting(const EGUISettingType& Type, const CStr& Name) +template +void IGUIObject::AddSetting(const CStr& Name) { - // Is name already taken? - if (m_Settings.count(Name) >= 1) + // This can happen due to inheritance + if (SettingExists(Name)) return; - // Construct, and set type - m_Settings[Name].m_Type = Type; - - switch (Type) - { -#define TYPE(type) \ - case GUIST_##type: \ - m_Settings[Name].Init(*this, Name);\ - break; - - // Construct the setting. - #include "GUItypes.h" - -#undef TYPE - - default: - debug_warn(L"IGUIObject::AddSetting failed, type not recognized!"); - break; - } + m_Settings[Name] = new CGUISetting(*this, Name); } - bool IGUIObject::MouseOver() { if (!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); return m_CachedActualSize.PointInside(GetMousePos()); } bool IGUIObject::MouseOverIcon() { return false; } CPos IGUIObject::GetMousePos() const { if (GetGUI()) return GetGUI()->m_MousePos; return CPos(); } 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 { - // Because GetOffsets will direct dynamically defined - // classes with polymorphism to respective ms_SettingsInfo - // we need to make no further updates on this function - // in derived classes. - //return (GetSettingsInfo().count(Setting) >= 1); - return (m_Settings.count(Setting) >= 1); + 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; - SGUISetting set = m_Settings[Setting]; - -#define TYPE(type) \ - else if (set.m_Type == GUIST_##type) \ - { \ - type _Value; \ - if (!GUI::ParseString(Value, _Value)) \ - return PSRETURN_GUI_UnableToParse; \ - GUI::SetSetting(this, Setting, _Value, SkipMessage); \ - } - - if (0) - ; -#include "GUItypes.h" -#undef TYPE - else - { - // Why does it always fail? - //return PS_FAIL; - return LogInvalidSettings(Setting); - } - return PSRETURN_OK; -} - - - -PSRETURN IGUIObject::GetSettingType(const CStr& Setting, EGUISettingType& Type) const -{ - if (!SettingExists(Setting)) - return LogInvalidSettings(Setting); - - if (m_Settings.find(Setting) == m_Settings.end()) - return LogInvalidSettings(Setting); - - Type = m_Settings.find(Setting)->second.m_Type; + 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::GetSetting(this, "size", ca); // 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); else 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& GUIinstance, const CStr& StyleName) { // Fetch style if (GUIinstance.m_Styles.count(StyleName) == 1) { LoadStyle(GUIinstance.m_Styles[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) { if(!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); 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.setCompileAndGo(true); 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); m_pGUI->GetScriptInterface()->CreateObject( &mouse, "x", m_pGUI->m_MousePos.x, "y", m_pGUI->m_MousePos.y, "buttons", m_pGUI->m_MouseButtons); 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() { GetGUI()->m_FocusedObject = this; } bool IGUIObject::IsFocused() const { return GetGUI()->m_FocusedObject == this; } bool IGUIObject::IsRootObject() const { return GetGUI() != 0 && m_pParent == GetGUI()->m_BaseObject; } 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"); } -PSRETURN IGUIObject::LogInvalidSettings(const CStr8& Setting) const -{ - LOGWARNING("IGUIObject: setting %s was not found on an object", Setting.c_str()); - return PSRETURN_GUI_InvalidSetting; -} +// Instantiate templated functions: +#define TYPE(T) template void IGUIObject::AddSetting(const CStr& Name); +#include "GUItypes.h" +#undef TYPE Index: ps/trunk/source/gui/IGUIObject.h =================================================================== --- ps/trunk/source/gui/IGUIObject.h (revision 22603) +++ ps/trunk/source/gui/IGUIObject.h (revision 22604) @@ -1,553 +1,489 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * The base class of an object. * All objects are derived from this class. * It's an abstract data type, so it can't be used per se. * Also contains a Dummy object which is used for completely blank objects. */ #ifndef INCLUDED_IGUIOBJECT #define INCLUDED_IGUIOBJECT #include "GUIbase.h" #include "GUItext.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "lib/input.h" // just for IN_PASS #include "ps/XML/Xeromyces.h" #include #include #include -struct SGUISetting; struct SGUIStyle; class CGUI; class JSObject; +class IGUISetting; ERROR_TYPE(GUI, UnableToParse); /** - * Setting Type - * @see SGUISetting - * - * For use of later macros, all names should be GUIST_ followed - * by the code name (case sensitive!). - */ -#define TYPE(T) GUIST_##T, -enum EGUISettingType -{ - #include "GUItypes.h" -}; -#undef TYPE - -/** - * A GUI Setting is anything that can be inputted from XML as - * \-attributes (with exceptions). For instance: - * \ - * - * "style" will be a SGUISetting. - */ -struct SGUISetting -{ - SGUISetting() : m_pSetting(NULL) {} - - /** - * Stores the instance of the setting type holding the setting data. Can be set from XML and JS. - */ - void *m_pSetting; - - EGUISettingType m_Type; - - template - void Init(IGUIObject& pObject, const CStr& Name); - - /** - * Parses the given JS::Value using ScriptInterface::FromJSVal and assigns it to the setting data. - */ - std::function m_FromJSVal; - - /** - * Converts the setting data to a JS::Value using ScriptInterface::ToJSVal. - */ - std::function m_ToJSVal; -}; - -/** * GUI object such as a button or an input-box. * Abstract data type ! */ class IGUIObject { friend class CGUI; friend class IGUIScrollBar; friend class GUITooltip; // Allow getProperty to access things like GetParent() friend bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); friend bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool UNUSED(strict), JS::MutableHandleValue vp); friend bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint argc, JS::Value* vp); public: IGUIObject(CGUI* pGUI); virtual ~IGUIObject(); /** * Checks if mouse is hovering this object. * The mouse position is cached in CGUI. * * This function checks if the mouse is hovering the * rectangle that the base setting "size" makes. * Although it is virtual, so one could derive * an object from CButton, which changes only this * to checking the circle that "size" makes. * * @return true if mouse is hovering */ virtual bool MouseOver(); /** * Test if mouse position is over an icon */ virtual bool MouseOverIcon(); //-------------------------------------------------------- /** @name Leaf Functions */ //-------------------------------------------------------- //@{ /// Get object name, name is unique const CStr& GetName() const { return m_Name; } /// Get object name void SetName(const CStr& Name) { m_Name = Name; } // Get Presentable name. // Will change all internally set names to something like "" CStr GetPresentableName() const; /** * Adds object and its children to the map, it's name being the * first part, and the second being itself. * * @param ObjectMap Adds this to the map_pObjects. * * @throws PSERROR_GUI_ObjectNeedsName Name is missing * @throws PSERROR_GUI_NameAmbiguity Name is already taken */ void AddToPointersMap(map_pObjects& ObjectMap); /** * Notice nothing will be returned or thrown if the child hasn't * been inputted into the GUI yet. This is because that's were * all is checked. Now we're just linking two objects, but * it's when we're inputting them into the GUI we'll check * validity! Notice also when adding it to the GUI this function * will inevitably have been called by CGUI::AddObject which * will catch the throw and return the error code. * i.e. The user will never put in the situation wherein a throw * must be caught, the GUI's internal error handling will be * completely transparent to the interfacially sequential model. * * @param pChild Child to add * * @throws PSERROR_GUI from CGUI::UpdateObjects(). */ void AddChild(IGUIObject* pChild); //@} //-------------------------------------------------------- /** @name Iterate * Used to iterate over all children of this object. */ //-------------------------------------------------------- //@{ vector_pObjects::iterator begin() { return m_Children.begin(); } vector_pObjects::iterator end() { return m_Children.end(); } //@} //-------------------------------------------------------- /** @name Settings Management */ //-------------------------------------------------------- //@{ /** - * Checks if settings exists, only available for derived - * classes that has this set up, that's why the base - * class just returns false + * Returns whether there is a setting with the given name registered. * * @param Setting setting name * @return True if settings exist. */ bool SettingExists(const CStr& Setting) const; /** * All sizes are relative to resolution, and the calculation * is not wanted in real time, therefore it is cached, update * the cached size with this function. */ virtual void UpdateCachedSize(); /** * Reset internal state of this object. */ virtual void ResetStates(); /** * Set a setting by string, regardless of what type it is. * * example a CRect(10,10,20,20) would be "10 10 20 20" * * @param Setting Setting by name * @param Value Value to set to * @param SkipMessage Does not send a GUIM_SETTINGS_UPDATED if true * * @return PSRETURN (PSRETURN_OK if successful) */ PSRETURN SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage = false); /** - * Retrieves the type of a named setting. - * - * @param Setting Setting by name - * @param Type Stores an EGUISettingType - * @return PSRETURN (PSRETURN_OK if successful) - */ - PSRETURN GetSettingType(const CStr& Setting, EGUISettingType& Type) const; - - /** * Set the script handler for a particular object-specific action * * @param Action Name of action * @param Code Javascript code to execute when the action occurs * @param pGUI GUI instance to associate the script with */ void RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI); /** * Creates the JS Object representing this page upon first use. * Can be overridden by derived classes to extend it. */ virtual void CreateJSObject(); /** * Retrieves the JSObject representing this GUI object. */ JSObject* GetJSObject(); //@} protected: //-------------------------------------------------------- /** @name Called by CGUI and friends * * Methods that the CGUI will call using * its friendship, these should not * be called by user. * These functions' security are a lot * what constitutes the GUI's */ //-------------------------------------------------------- //@{ /** * Add a setting to m_Settings * * @param Type Setting type * @param Name Setting reference name */ - void AddSetting(const EGUISettingType& Type, const CStr& Name); + template void AddSetting(const CStr& Name); /** * Calls Destroy on all children, and deallocates all memory. * MEGA TODO Should it destroy it's children? */ virtual void Destroy(); public: /** * This function is called with different messages * for instance when the mouse enters the object. * * @param Message GUI Message */ virtual void HandleMessage(SGUIMessage& UNUSED(Message)) {} protected: /** * Draws the object. * * @throws PSERROR if any. But this will mostlikely be * very rare since if an object is drawn unsuccessfully * it'll probably only output in the Error log, and not * disrupt the whole GUI drawing. */ virtual void Draw() = 0; /** * Some objects need to handle the SDL_Event_ manually. * For instance the input box. * * Only the object with focus will have this function called. * * Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then * the key won't be passed on and processed by other handlers. * This is used for keys that the GUI uses. */ virtual InReaction ManuallyHandleEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; } /** * Loads a style. * * @param GUIinstance Reference to the GUI * @param StyleName Style by name */ void LoadStyle(CGUI& GUIinstance, const CStr& StyleName); /** * Loads a style. * * @param Style The style object. */ void LoadStyle(const SGUIStyle& Style); /** * Returns not the Z value, but the actual buffered Z value, i.e. if it's * defined relative, then it will check its parent's Z value and add * the relativity. * * @return Actual Z value on the screen. */ virtual float GetBufferedZ() const; /** * Set parent of this object */ void SetParent(IGUIObject* pParent) { m_pParent = pParent; } public: CGUI* GetGUI() { return m_pGUI; } const CGUI* GetGUI() const { return m_pGUI; } /** * Take focus! */ void SetFocus(); /** * Workaround to avoid a dynamic_cast which can be 80 times slower than this. */ virtual void* GetTextOwner() { return nullptr; } protected: /** * Check if object is focused. */ bool IsFocused() const; /** * NOTE! This will not just return m_pParent, when that is * need use it! There is one exception to it, when the parent is * the top-node (the object that isn't a real object), this * will return NULL, so that the top-node's children are * seemingly parentless. * * @return Pointer to parent */ IGUIObject* GetParent() const; /** * Get Mouse from CGUI. */ CPos GetMousePos() const; /** * Handle additional children to the \-tag. In IGUIObject, this function does * nothing. In CList and CDropDown, it handles the \, used to build the data. * * Returning false means the object doesn't recognize the child. Should be reported. * Notice 'false' is default, because an object not using this function, should not * have any additional children (and this function should never be called). */ virtual bool HandleAdditionalChildren(const XMBElement& UNUSED(child), CXeromyces* UNUSED(pFile)) { return false; } /** * Cached size, real size m_Size is actually dependent on resolution * and can have different *real* outcomes, this is the real outcome * cached to avoid slow calculations in real time. */ CRect m_CachedActualSize; /** * Send event to this GUI object (HandleMessage and ScriptEvent) * * @param type Type of GUI message to be handled * @param EventName String representation of event name * @return IN_HANDLED if event was handled, or IN_PASS if skipped */ InReaction SendEvent(EGUIMessageType type, const CStr& EventName); /** * Execute the script for a particular action. * Does nothing if no script has been registered for that action. * The mouse coordinates will be passed as the first argument. * * @param Action Name of action */ void ScriptEvent(const CStr& Action); /** * Execute the script for a particular action. * Does nothing if no script has been registered for that action. * * @param Action Name of action * @param paramData JS::HandleValueArray arguments to pass to the event. */ void ScriptEvent(const CStr& Action, JS::HandleValueArray paramData); void SetScriptHandler(const CStr& Action, JS::HandleObject Function); /** * Inputes the object that is currently hovered, this function * updates this object accordingly (i.e. if it's the object * being inputted one thing happens, and not, another). * * @param pMouseOver Object that is currently hovered, * can OF COURSE be NULL too! */ void UpdateMouseOver(IGUIObject* const& pMouseOver); //@} private: //-------------------------------------------------------- /** @name Internal functions */ //-------------------------------------------------------- //@{ /** * Inputs a reference pointer, checks if the new inputted object * if hovered, if so, then check if this's Z value is greater * than the inputted object... If so then the object is closer * and we'll replace the pointer with this. * Also Notice input can be NULL, which means the Z value demand * is out. NOTICE you can't input NULL as const so you'll have * to set an object to NULL. * * @param pObject Object pointer, can be either the old one, or * the new one. */ void ChooseMouseOverAndClosest(IGUIObject*& pObject); // Is the object a Root object, in philosophy, this means it // has got no parent, and technically, it's got the m_BaseObject // as parent. bool IsRootObject() const; - /** - * Logs an invalid setting search and returns the correct return result - * - * @return the error result - */ - PSRETURN LogInvalidSettings(const CStr8& Setting) const; - static void Trace(JSTracer* trc, void* data) { reinterpret_cast(data)->TraceMember(trc); } void TraceMember(JSTracer* trc); // Variables protected: // Name of object CStr m_Name; // Constructed on the heap, will be destroyed along with the the object // TODO Gee: really the above? vector_pObjects m_Children; // Pointer to parent IGUIObject *m_pParent; //This represents the last click time for each mouse button double m_LastClickTime[6]; /** * This is an array of true or false, each element is associated with * a string representing a setting. Number of elements is equal to * number of settings. * * A true means the setting has been manually set in the file when * read. This is important to know because I don't want to force * the user to include its \-XML-files first, so somehow * the GUI needs to know which settings were set, and which is meant * to. */ // More variables // Is mouse hovering the object? used with the function MouseOver() bool m_MouseHovering; /** * Settings pool, all an object's settings are located here * If a derived object has got more settings that the base * settings, it's because they have a new version of the * function SetupSettings(). * * @see SetupSettings() */ public: - std::map m_Settings; + std::map m_Settings; protected: // An object can't function stand alone CGUI* const m_pGUI; // Internal storage for registered script handlers. std::map > m_ScriptHandlers; // Cached JSObject representing this GUI object JS::PersistentRootedObject m_JSObject; }; /** * Dummy object used primarily for the root object * or objects of type 'empty' */ class CGUIDummyObject : public IGUIObject { GUI_OBJECT(CGUIDummyObject) public: CGUIDummyObject(CGUI* pGUI) : IGUIObject(pGUI) {} virtual void Draw() {} // Empty can never be hovered. It is only a category. virtual bool MouseOver() { return false; } }; #endif // INCLUDED_IGUIOBJECT Index: ps/trunk/source/gui/MiniMap.cpp =================================================================== --- ps/trunk/source/gui/MiniMap.cpp (revision 22603) +++ ps/trunk/source/gui/MiniMap.cpp (revision 22604) @@ -1,709 +1,709 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include #include "MiniMap.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MiniPatch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "lib/bits.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/timer.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" #include "simulation2/system/ParamNode.h" extern bool g_GameRestarted; // Set max drawn entities to UINT16_MAX for now, which is more than enough // TODO: we should be cleverer about drawing them to reduce clutter const u16 MAX_ENTITIES_DRAWN = 65535; static unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = unsigned(float(color & 0xff) * x); unsigned int g = unsigned(float((color>>8) & 0xff) * x); unsigned int b = unsigned(float((color>>16) & 0xff) * x); return (0xff000000 | b | g<<8 | r<<16); } CMiniMap::CMiniMap(CGUI* pGUI) : IGUIObject(pGUI), m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f), m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0) { - AddSetting(GUIST_CStrW, "tooltip"); - AddSetting(GUIST_CStr, "tooltip_style"); + AddSetting("tooltip"); + AddSetting("tooltip_style"); m_Clicking = false; m_MouseHovering = false; // Register Relax NG validator CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); // Get the maximum height for unit passage in water. CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); else m_ShallowPassageHeight = 0.0f; m_AttributePos.type = GL_FLOAT; m_AttributePos.elems = 2; m_VertexArray.AddAttribute(&m_AttributePos); m_AttributeColor.type = GL_UNSIGNED_BYTE; m_AttributeColor.elems = 4; m_VertexArray.AddAttribute(&m_AttributeColor); m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_VertexArray.Layout(); m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) *index++ = i; m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) { (*attrColor)[0] = 0; (*attrColor)[1] = 0; (*attrColor)[2] = 0; (*attrColor)[3] = 0; ++attrColor; (*attrPos)[0] = -10000.0f; (*attrPos)[1] = -10000.0f; ++attrPos; } m_VertexArray.Upload(); double blinkDuration = 1.0; // Tests won't have config initialised if (CConfigDB::IsInitialised()) { CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); } m_HalfBlinkDuration = blinkDuration/2; } CMiniMap::~CMiniMap() { Destroy(); } void CMiniMap::HandleMessage(SGUIMessage& Message) { switch (Message.type) { case GUIM_MOUSE_PRESS_LEFT: if (m_MouseHovering) { SetCameraPos(); m_Clicking = true; } break; case GUIM_MOUSE_RELEASE_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPos(); m_Clicking = false; break; case GUIM_MOUSE_DBLCLICK_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPos(); m_Clicking = false; break; case GUIM_MOUSE_ENTER: m_MouseHovering = true; break; case GUIM_MOUSE_LEAVE: m_Clicking = false; m_MouseHovering = false; break; case GUIM_MOUSE_RELEASE_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); break; case GUIM_MOUSE_DBLCLICK_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); break; case GUIM_MOUSE_MOTION: if (m_MouseHovering && m_Clicking) SetCameraPos(); break; case GUIM_MOUSE_WHEEL_DOWN: case GUIM_MOUSE_WHEEL_UP: Message.Skip(); break; default: break; } } bool CMiniMap::MouseOver() { // Get the mouse position. CPos mousePos = GetMousePos(); // Get the position of the center of the minimap. CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0); // Take the magnitude of the difference of the mouse position and minimap center. double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2)); // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap. if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0) return true; else return false; } void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) { // Determine X and Z according to proportion of mouse position and minimap CPos mousePos = GetMousePos(); float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth(); float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight(); float angle = GetAngle(); // Scale world coordinates for shrunken square map x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5); z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5); } void CMiniMap::SetCameraPos() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); CVector3D target; GetMouseWorldCoordinates(target.X, target.Z); target.Y = terrain->GetExactGroundLevel(target.X, target.Z); g_Game->GetView()->MoveCameraTarget(target); } float CMiniMap::GetAngle() { CVector3D cameraIn = m_Camera->m_Orientation.GetIn(); return -atan2(cameraIn.X, cameraIn.Z); } void CMiniMap::FireWorldClickEvent(int UNUSED(button), int UNUSED(clicks)) { JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); float x, z; GetMouseWorldCoordinates(x, z); JS::RootedValue coords(cx); g_GUI->GetActiveGUI()->GetScriptInterface()->CreateObject(&coords, "x", x, "z", z); JS::AutoValueVector paramData(cx); paramData.append(coords); ScriptEvent("worldclick", paramData); } // This sets up and draws the rectangle on the minimap // which represents the view of the camera in the world. void CMiniMap::DrawViewRect(CMatrix3D transform) { // Compute the camera frustum intersected with a fixed-height plane. // Use the water height as a fixed base height, which should be the lowest we can go float h = g_Renderer.GetWaterManager()->m_WaterHeight; const float width = m_CachedActualSize.GetWidth(); const float height = m_CachedActualSize.GetHeight(); const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize); CVector3D hitPt[4]; hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h); hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h); hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h); hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h); float ViewRect[4][2]; for (int i = 0; i < 4; ++i) { // convert to minimap space ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize); ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize); } float viewVerts[] = { ViewRect[0][0], -ViewRect[0][1], ViewRect[1][0], -ViewRect[1][1], ViewRect[2][0], -ViewRect[2][1], ViewRect[3][0], -ViewRect[3][1] }; // Enable Scissoring to restrict the rectangle to only the minimap. glScissor( m_CachedActualSize.left * g_GuiScale, g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale, width * g_GuiScale, height * g_GuiScale); glEnable(GL_SCISSOR_TEST); glLineWidth(2.0f); CShaderDefines lineDefines; lineDefines.Add(str_MINIMAP_LINE, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); shader->Uniform(str_transform, transform); shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f); shader->VertexPointer(2, GL_FLOAT, 0, viewVerts); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_LINE_LOOP, 0, 4); tech->EndPass(); glLineWidth(1.0f); glDisable(GL_SCISSOR_TEST); } struct MinimapUnitVertex { u8 r, g, b, a; float x, y; }; // Adds a vertex to the passed VertexArray static void inline addVertex(const MinimapUnitVertex& v, VertexArrayIterator& attrColor, VertexArrayIterator& attrPos) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.x; (*attrPos)[1] = v.y; ++attrPos; } void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) { // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m) // Scale square maps to fit in circular minimap area const float s = sin(angle) * m_MapScale; const float c = cos(angle) * m_MapScale; const float m = coordMax / 2.f; float quadTex[] = { m*(-c + s + 1.f), m*(-c + -s + 1.f), m*(c + s + 1.f), m*(-c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(-c + -s + 1.f), m*(c + -s + 1.f), m*(-c + s + 1.f), m*(-c + -s + 1.f) }; float quadVerts[] = { x, y, z, x2, y, z, x2, y2, z, x2, y2, z, x, y2, z, x, y, z }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(3, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_TRIANGLES, 0, 6); } // TODO: render the minimap in a framebuffer and just draw the frambuffer texture // most of the time, updating the framebuffer twice a frame. // Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling // (those operations cause a gpu sync, which slows down the way gpu works) void CMiniMap::Draw() { PROFILE3("render minimap"); // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if (!(GetGUI() && g_Game && g_Game->IsGameStarted())) return; CSimulation2* sim = g_Game->GetSimulation2(); CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); ENSURE(cmpRangeManager); // Set our globals in case they hadn't been set before m_Camera = g_Game->GetView()->GetCamera(); m_Terrain = g_Game->GetWorld()->GetTerrain(); m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); m_MapSize = m_Terrain->GetVerticesPerSide(); m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); if (!m_TerrainTexture || g_GameRestarted) CreateTextures(); // only update 2x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow) // TODO: Update all but camera at same speed as simulation static double last_time; const double cur_time = timer_Time(); const bool doUpdate = cur_time - last_time > 0.5; if (doUpdate) { last_time = cur_time; if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight) RebuildTerrainTexture(); } const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float z = GetBufferedZ(); const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize; const float angle = GetAngle(); const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f); // Disable depth updates to prevent apparent z-fighting-related issues // with some drivers causing units to get drawn behind the texture. glDepthMask(0); CShaderProgramPtr shader; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines); tech->BeginPass(); shader = tech->GetShader(); // Draw the main textured quad shader->BindTexture(str_baseTex, m_TerrainTexture); const CMatrix3D baseTransform = GetDefaultGuiMatrix(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, baseTextureTransform); DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z); // Draw territory boundaries glEnable(GL_BLEND); CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *territoryTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); // Draw the LOS quad in black, using alpha values from the LOS texture CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); CShaderDefines losDefines; losDefines.Add(str_MINIMAP_LOS, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines); tech->BeginPass(); shader = tech->GetShader(); shader->BindTexture(str_baseTex, losTexture.GetTexture()); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *losTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); glDisable(GL_BLEND); PROFILE_START("minimap units"); CShaderDefines pointDefines; pointDefines.Add(str_MINIMAP_POINT, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines); tech->BeginPass(); shader = tech->GetShader(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_pointSize, 3.f); CMatrix3D unitMatrix; unitMatrix.SetIdentity(); // Center the minimap on the origin of the axis of rotation. unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f); // Rotate the map. unitMatrix.RotateZ(angle); // Scale square maps to fit. unitMatrix.Scale(unitScale, unitScale, 1.f); // Move the minimap back to it's starting position. unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f); // Move the minimap to it's final location. unitMatrix.Translate(x, y, z); // Apply the gui matrix. unitMatrix *= GetDefaultGuiMatrix(); // Load the transform into the shader. shader->Uniform(str_transform, unitMatrix); const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap); if (doUpdate) { VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); m_EntitiesDrawn = 0; MinimapUnitVertex v; std::vector pingingVertices; pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); if (cur_time > m_NextBlinkTime) { m_BlinkState = !m_BlinkState; m_NextBlinkTime = cur_time + m_HalfBlinkDuration; } entity_pos_t posX, posZ; for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer()); if (vis != ICmpRangeManager::VIS_HIDDEN) { v.a = 255; v.x = posX.ToFloat() * sx; v.y = -posZ.ToFloat() * sy; // Check minimap pinging to indicate something if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration)) { v.r = 255; // ping color is white v.g = 255; v.b = 255; pingingVertices.push_back(v); } else { addVertex(v, attrColor, attrPos); ++m_EntitiesDrawn; } } } } // Add the pinged vertices at the end, so they are drawn on top for (size_t v = 0; v < pingingVertices.size(); ++v) { addVertex(pingingVertices[v], attrColor, attrPos); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); m_VertexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { #if !CONFIG2_GLES if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif u8* indexBase = m_IndexArray.Bind(); u8* base = m_VertexArray.Bind(); const GLsizei stride = (GLsizei)m_VertexArray.GetStride(); shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset); shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase); g_Renderer.GetStats().m_DrawCalls++; CVertexBuffer::Unbind(); #if !CONFIG2_GLES if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif } tech->EndPass(); DrawViewRect(unitMatrix); PROFILE_END("minimap units"); // Reset depth mask glDepthMask(1); } void CMiniMap::CreateTextures() { Destroy(); // Create terrain texture glGenTextures(1, &m_TerrainTexture); g_Renderer.BindTexture(0, m_TerrainTexture); // Initialise texture with solid black, for the areas we don't // overwrite with glTexSubImage2D later u32* texData = new u32[m_TextureSize * m_TextureSize]; for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i) texData[i] = 0xFF000000; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); delete[] texData; m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Rebuild and upload both of them RebuildTerrainTexture(); } void CMiniMap::RebuildTerrainTexture() { u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; m_TerrainDirty = false; for (u32 j = 0; j < h; ++j) { u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; for (u32 i = 0; i < w; ++i) { float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j) + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j) + m_Terrain->GetVertexGroundLevel((int)i, (int)j+1) + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) { // shallow water *dataPtr++ = 0xffc09870; } else if (avgHeight < m_WaterHeight) { // Set water as constant color for consistency on different maps *dataPtr++ = 0xffa07850; } else { int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0xFFFFFFFF; CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j); if (mp) { CTerrainTextureEntry* tex = mp->GetTextureEntry(); if (tex) { // If the texture can't be loaded yet, set the dirty flags // so we'll try regenerating the terrain texture again soon if(!tex->GetTexture()->TryLoad()) m_TerrainDirty = true; color = tex->GetBaseColor(); } } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture g_Renderer.BindTexture(0, m_TerrainTexture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData); } void CMiniMap::Destroy() { if (m_TerrainTexture) { glDeleteTextures(1, &m_TerrainTexture); m_TerrainTexture = 0; } SAFE_ARRAY_DELETE(m_TerrainData); } Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 22603) +++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 22604) @@ -1,263 +1,263 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "JSInterface_IGUIObject.h" #include "gui/CGUI.h" #include "gui/CGUIColor.h" #include "gui/CList.h" #include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "gui/IGUIScrollBar.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptExtraHeaders.h" #include "scriptinterface/ScriptInterface.h" JSClass JSI_IGUIObject::JSI_class = { "GUIObject", JSCLASS_HAS_PRIVATE, nullptr, nullptr, JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; JSFunctionSpec JSI_IGUIObject::JSI_methods[] = { JS_FN("toString", JSI_IGUIObject::toString, 0, 0), JS_FN("focus", JSI_IGUIObject::focus, 0, 0), JS_FN("blur", JSI_IGUIObject::blur, 0, 0), JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0), JS_FS_END }; bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) { JSAutoRequest rq(cx); ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; JS::RootedValue idval(cx); if (!JS_IdToValue(cx, id, &idval)) return false; std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) return false; // Skip registered functions and inherited properties // including JSInterfaces of derived classes if (propName == "constructor" || propName == "prototype" || propName == "toString" || propName == "toJSON" || propName == "focus" || propName == "blur" || propName == "getTextSize" || propName == "getComputedSize" ) return true; // Use onWhatever to access event handlers if (propName.substr(0, 2) == "on") { CStr eventName(CStr(propName.substr(2)).LowerCase()); std::map>::iterator it = e->m_ScriptHandlers.find(eventName); if (it == e->m_ScriptHandlers.end()) vp.setNull(); else vp.setObject(*it->second.get()); return true; } if (propName == "parent") { IGUIObject* parent = e->GetParent(); if (parent) vp.set(JS::ObjectValue(*parent->GetJSObject())); else vp.set(JS::NullValue()); return true; } else if (propName == "children") { pScriptInterface->CreateArray(vp); for (size_t i = 0; i < e->m_Children.size(); ++i) pScriptInterface->SetPropertyInt(vp, i, e->m_Children[i]); return true; } else if (propName == "name") { ScriptInterface::ToJSVal(cx, vp, e->GetName()); return true; } else if (e->SettingExists(propName)) { - e->m_Settings[propName].m_ToJSVal(cx, vp); + e->m_Settings[propName]->ToJSVal(cx, vp); return true; } JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); return false; } bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool UNUSED(strict), JS::MutableHandleValue vp) { IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; JSAutoRequest rq(cx); JS::RootedValue idval(cx); if (!JS_IdToValue(cx, id, &idval)) return false; std::string propName; if (!ScriptInterface::FromJSVal(cx, idval, propName)) return false; if (propName == "name") { std::string value; if (!ScriptInterface::FromJSVal(cx, vp, value)) return false; e->SetName(value); return true; } JS::RootedObject vpObj(cx); if (vp.isObject()) vpObj = &vp.toObject(); // Use onWhatever to set event handlers if (propName.substr(0, 2) == "on") { if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(cx, &vp.toObject())) { JS_ReportError(cx, "on- event-handlers must be functions"); return false; } CStr eventName(CStr(propName.substr(2)).LowerCase()); e->SetScriptHandler(eventName, vpObj); return true; } if (e->SettingExists(propName)) - return e->m_Settings[propName].m_FromJSVal(cx, vp); + return e->m_Settings[propName]->FromJSVal(cx, vp); JS_ReportError(cx, "Property '%s' does not exist!", propName.c_str()); return false; } void JSI_IGUIObject::init(ScriptInterface& scriptInterface) { scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 1, nullptr, JSI_methods, nullptr, nullptr); } bool JSI_IGUIObject::toString(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; ScriptInterface::ToJSVal(cx, rec.rval(), "[GUIObject: " + e->GetName() + "]"); return true; } bool JSI_IGUIObject::focus(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; e->GetGUI()->SetFocusedObject(e); rec.rval().setUndefined(); return true; } bool JSI_IGUIObject::blur(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; e->GetGUI()->SetFocusedObject(NULL); rec.rval().setUndefined(); return true; } bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint UNUSED(argc), JS::Value* vp) { JSAutoRequest rq(cx); JS::CallReceiver rec = JS::CallReceiverFromVp(vp); JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL); if (!e) return false; e->UpdateCachedSize(); CRect size = e->m_CachedActualSize; JS::RootedValue objVal(cx); try { ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->CreateObject( &objVal, "left", size.left, "right", size.right, "top", size.top, "bottom", size.bottom); } catch (PSERROR_Scripting_ConversionFailed&) { debug_warn(L"Error creating size object!"); return false; } rec.rval().set(objVal); return true; }