From 3efbfa13c5a9e65799020451ce08e6c3d308d885 Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sun, 1 Sep 2024 11:01:44 +0200 Subject: [PATCH] Fixed incorrect rendering of outlines in RichTextLabel (there was a gap between each piece + even if the gap didn't exist it would render outlines of the second piece on top of the first piece) --- changelog.md | 2 +- .../Backend/Renderer/BackendRenderTarget.hpp | 26 ++++++++++++ include/TGUI/Backend/Renderer/BackendText.hpp | 4 +- include/TGUI/Widgets/Label.hpp | 2 +- src/Backend/Renderer/BackendRenderTarget.cpp | 40 ++++++++++++++++++ src/Backend/Renderer/BackendText.cpp | 8 ++-- src/Widgets/Label.cpp | 26 +++++++++++- src/Widgets/RichTextLabel.cpp | 2 +- tests/Widgets/RichTextLabel.cpp | 9 ++++ tests/expected/RichTextLabel_Outline.png | Bin 0 -> 6463 bytes 10 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 tests/expected/RichTextLabel_Outline.png diff --git a/changelog.md b/changelog.md index 47b901a4a..be1157228 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,7 @@ TGUI 1.6 (TBD) -------------- -Fixed crash on exit when tool tip was visible +- Fixed crash on exit when tool tip was visible TGUI 1.5 (25 August 2024) diff --git a/include/TGUI/Backend/Renderer/BackendRenderTarget.hpp b/include/TGUI/Backend/Renderer/BackendRenderTarget.hpp index 820e077b1..3ce2f11db 100644 --- a/include/TGUI/Backend/Renderer/BackendRenderTarget.hpp +++ b/include/TGUI/Backend/Renderer/BackendRenderTarget.hpp @@ -148,9 +148,35 @@ TGUI_MODULE_EXPORT namespace tgui /// /// @param states Render states to use for drawing /// @param text Text to draw + /// + /// This function does the same as calling both drawTextOutline and drawTextWithoutOutline. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// virtual void drawText(const RenderStates& states, const Text& text); + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Draws only the outline of some text + /// + /// @param states Render states to use for drawing + /// @param text Text to draw + /// + /// This function should always be called prior to drawTextWithoutOutline. It exists to allow rendering the outline of + /// multiple texts before rendering all these texts, which is done by calling drawTextOutline for each text before + /// calling drawTextWithoutOutline on all the same text objects. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + virtual void drawTextOutline(const RenderStates& states, const Text& text); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Draws some text, but without rendering its outline + /// + /// @param states Render states to use for drawing + /// @param text Text to draw + /// + /// This function should always be called after drawTextOutline. It exists to allow rendering the outline of + /// multiple texts before rendering all these texts, which is done by calling drawTextOutline for each text before + /// calling drawTextWithoutOutline on all the same text objects. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + virtual void drawTextWithoutOutline(const RenderStates& states, const Text& text); + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Draws a single triangles (using the color that is specified in the vertices) /// diff --git a/include/TGUI/Backend/Renderer/BackendText.hpp b/include/TGUI/Backend/Renderer/BackendText.hpp index 222b4f0e8..7bbca024a 100644 --- a/include/TGUI/Backend/Renderer/BackendText.hpp +++ b/include/TGUI/Backend/Renderer/BackendText.hpp @@ -158,9 +158,11 @@ TGUI_MODULE_EXPORT namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Returns the information that is needed to render this text + /// @param includeOutline Should the returned data include the text outline? + /// @param includeText Should the returned data include the text itself (i.e. everything except the outline)? /// @return Data that contains the textures and vertices used by this text ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - TGUI_NODISCARD TextVertexData getVertexData(); + TGUI_NODISCARD TextVertexData getVertexData(bool includeOutline = true, bool includeText = true); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected: diff --git a/include/TGUI/Widgets/Label.hpp b/include/TGUI/Widgets/Label.hpp index c8eba22fc..204efa79a 100644 --- a/include/TGUI/Widgets/Label.hpp +++ b/include/TGUI/Widgets/Label.hpp @@ -398,7 +398,7 @@ TGUI_MODULE_EXPORT namespace tgui Color m_textColorCached; Color m_borderColorCached; Color m_backgroundColorCached; - Color m_textOutlineColorCached; + Color m_textOutlineColorCached = Color::Black; float m_textOutlineThicknessCached = 0; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/Backend/Renderer/BackendRenderTarget.cpp b/src/Backend/Renderer/BackendRenderTarget.cpp index 6d1188ed9..35f6f01d9 100644 --- a/src/Backend/Renderer/BackendRenderTarget.cpp +++ b/src/Backend/Renderer/BackendRenderTarget.cpp @@ -374,6 +374,46 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendRenderTarget::drawTextOutline(const RenderStates& states, const Text& text) + { + RenderStates transformedStates = states; + transformedStates.transform.translate(text.getPosition()); + + // Round the text to the nearest pixel to try to avoid blurry text + transformedStates.transform.roundPosition(m_pixelsPerPoint); + + auto vertexData = text.getBackendText()->getVertexData(true, false); + + for (const auto& data : vertexData) + { + const std::shared_ptr& texture = data.first; + const std::shared_ptr>& vertices = data.second; + drawVertexArray(transformedStates, vertices->data(), vertices->size(), nullptr, 0, texture); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void BackendRenderTarget::drawTextWithoutOutline(const RenderStates& states, const Text& text) + { + RenderStates transformedStates = states; + transformedStates.transform.translate(text.getPosition()); + + // Round the text to the nearest pixel to try to avoid blurry text + transformedStates.transform.roundPosition(m_pixelsPerPoint); + + auto vertexData = text.getBackendText()->getVertexData(false, true); + + for (const auto& data : vertexData) + { + const std::shared_ptr& texture = data.first; + const std::shared_ptr>& vertices = data.second; + drawVertexArray(transformedStates, vertices->data(), vertices->size(), nullptr, 0, texture); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BackendRenderTarget::drawTriangle(const RenderStates& states, const Vertex& point1, const Vertex& point2, const Vertex& point3) { const std::array vertices = {{ point1, point2, point3 }}; diff --git a/src/Backend/Renderer/BackendText.cpp b/src/Backend/Renderer/BackendText.cpp index 1c1e66436..7609f2a22 100644 --- a/src/Backend/Renderer/BackendText.cpp +++ b/src/Backend/Renderer/BackendText.cpp @@ -233,7 +233,7 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - BackendText::TextVertexData BackendText::getVertexData() + BackendText::TextVertexData BackendText::getVertexData(bool includeOutline, bool includeText) { BackendText::TextVertexData data; @@ -257,10 +257,10 @@ namespace tgui texture = m_font->getTexture(m_characterSize, m_lastFontTextureVersion); } - if (m_outlineVertices && !m_outlineVertices->empty()) + if (includeOutline && m_outlineVertices && !m_outlineVertices->empty()) data.emplace_back(texture, m_outlineVertices); - if (m_vertices && !m_vertices->empty()) + if (includeText && m_vertices && !m_vertices->empty()) data.emplace_back(texture, m_vertices); return data; @@ -416,7 +416,7 @@ namespace tgui const float fontHeight = m_font->getFontHeight(m_characterSize); const float height = std::max(fontHeight, lineSpacing) + (nrLines - 1) * lineSpacing; - m_size = {maxX + 2 * m_outlineThickness, height + 2 * m_outlineThickness}; + m_size = {maxX + m_outlineThickness, height + 2 * m_outlineThickness}; // Normalize the texture coordinates const Vector2u textureSize = m_font->getTextureSize(m_characterSize); diff --git a/src/Widgets/Label.cpp b/src/Widgets/Label.cpp index 5e498b4dc..63c0a72e2 100644 --- a/src/Widgets/Label.cpp +++ b/src/Widgets/Label.cpp @@ -847,10 +847,21 @@ namespace tgui // Draw the text if (m_autoSize) { + // We need to draw all the outlines prior to start rendering the text itself, + // because otherwise the outline of one piece could render on top of a previously drawn piece. + if (m_textOutlineThicknessCached) + { + for (const auto& line : m_lines) + { + for (const auto& text : line) + target.drawTextOutline(states, text); + } + } + for (const auto& line : m_lines) { for (const auto& text : line) - target.drawText(states, text); + target.drawTextWithoutOutline(states, text); } } else @@ -863,10 +874,21 @@ namespace tgui if (m_scrollbar->isVisible()) states.transform.translate({0, -static_cast(m_scrollbar->getValue())}); + // We need to draw all the outlines prior to start rendering the text itself, + // because otherwise the outline of one piece could render on top of a previously drawn piece. + if (m_textOutlineThicknessCached) + { + for (const auto& line : m_lines) + { + for (const auto& text : line) + target.drawTextOutline(states, text); + } + } + for (const auto& line : m_lines) { for (const auto& text : line) - target.drawText(states, text); + target.drawTextWithoutOutline(states, text); } target.removeClippingLayer(); diff --git a/src/Widgets/RichTextLabel.cpp b/src/Widgets/RichTextLabel.cpp index 382aebc4c..f33e1ada8 100644 --- a/src/Widgets/RichTextLabel.cpp +++ b/src/Widgets/RichTextLabel.cpp @@ -214,7 +214,7 @@ namespace tgui textPiece.setPosition({pos.x + lineWidth, pos.y}); maxLineHeight = std::max(maxLineHeight, textPiece.getSize().y); - lineWidth += textPiece.getSize().x; + lineWidth += textPiece.getSize().x - (2 * m_textOutlineThicknessCached); // Take kerning into account if (j > 0 && !textPiecesLine[j-1].text.empty() && !textPiecesLine[j].text.empty()) diff --git a/tests/Widgets/RichTextLabel.cpp b/tests/Widgets/RichTextLabel.cpp index 1651be620..3c299e81d 100644 --- a/tests/Widgets/RichTextLabel.cpp +++ b/tests/Widgets/RichTextLabel.cpp @@ -329,5 +329,14 @@ TEST_CASE("[RichTextLabel]") U"can be included when there are too many lines!\n\n\n\n\n\n\n\n"); TEST_DRAW("RichTextLabel.png") + + SECTION("Outline") + { + // Outline of one piece should not be rendered on top of another piece, and there should be no additional gap between pieces + label->getRenderer()->setTextOutlineColor(tgui::Color::Green); + label->getRenderer()->setTextOutlineThickness(4); + label->setText("HelloWorld"); + TEST_DRAW("RichTextLabel_Outline.png") + } } } diff --git a/tests/expected/RichTextLabel_Outline.png b/tests/expected/RichTextLabel_Outline.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4730c2e77d2d58ab0469bb3294abfb8648f9f4 GIT binary patch literal 6463 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