From 10a02dbd8fe24d2fe7743ae4a426c47fd69c4c2c Mon Sep 17 00:00:00 2001 From: Bruno Van de Velde Date: Sat, 22 Dec 2018 18:38:18 +0100 Subject: [PATCH] Labels can now have a scrollbar (disabled by default for backwards compatibility) --- changelog.txt | 5 +- include/TGUI/Config.hpp | 5 + include/TGUI/RendererDefines.hpp | 2 +- include/TGUI/Renderers/LabelRenderer.hpp | 28 ++ .../Renderers/ScrollablePanelRenderer.hpp | 4 +- include/TGUI/Widgets/Label.hpp | 49 ++- src/TGUI/Renderers/LabelRenderer.cpp | 3 + src/TGUI/Widgets/Label.cpp | 286 ++++++++++++++---- tests/Clipping.cpp | 2 +- tests/Widgets/Label.cpp | 34 +++ tests/Widgets/ScrollablePanel.cpp | 22 ++ .../expected/Label_Complex_WithScrollbar.png | Bin 0 -> 30264 bytes themes/BabyBlue.txt | 1 + themes/Black.txt | 1 + themes/TransparentGrey.txt | 1 + 15 files changed, 381 insertions(+), 62 deletions(-) create mode 100644 tests/expected/Label_Complex_WithScrollbar.png diff --git a/changelog.txt b/changelog.txt index ced6fbe6..cf9fc2c7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,9 @@ TGUI 0.8.3 (TBD) ----------------- +- Label can now have a vertical scrollbar +- Default scrollbar width wasn't always taken from texture size in widgets containing scrollbars +- Scrollbar wasn't drawn correctly when Maximum equaled ViewportSize with AutoHide disabled - Default icons in TreeView didn't change color when item was selected - Rounded icon position in TreeView to avoid bad icon alignment @@ -11,7 +14,7 @@ TGUI 0.8.2 (16 December 2018) - TreeView widget added - Text styles of lines in ChatBox can now be changed - Clipping was broken when using multiple windows -- ScrollbablePanel didn't fully scroll to right with both scrollbars visible +- ScrollbablePanel didn't fully scroll to the right with both scrollbars visible TGUI 0.8.1 (15 October 2018) diff --git a/include/TGUI/Config.hpp b/include/TGUI/Config.hpp index 090b6c6f..fa6f51c4 100644 --- a/include/TGUI/Config.hpp +++ b/include/TGUI/Config.hpp @@ -98,4 +98,9 @@ #define TGUI_PRINT_WARNING(msg) #endif +#ifdef TGUI_NEXT + #define TGUI_REMOVE_DEPRECATED_CODE + #define TGUI_USE_CPP17 +#endif + #endif // TGUI_CONFIG_HPP diff --git a/include/TGUI/RendererDefines.hpp b/include/TGUI/RendererDefines.hpp index 868a12dd..cdbbc186 100644 --- a/include/TGUI/RendererDefines.hpp +++ b/include/TGUI/RendererDefines.hpp @@ -144,7 +144,7 @@ return it->second.getRenderer(); \ else \ { \ - auto renderer = Theme::getDefault()->getRendererNoThrow(RENDERER); \ + const auto& renderer = Theme::getDefault()->getRendererNoThrow(RENDERER); \ m_data->propertyValuePairs[toLower(#NAME)] = {renderer ? renderer : RendererData::create()}; \ return renderer; \ } \ diff --git a/include/TGUI/Renderers/LabelRenderer.hpp b/include/TGUI/Renderers/LabelRenderer.hpp index ed49d4fa..d4f0eda7 100644 --- a/include/TGUI/Renderers/LabelRenderer.hpp +++ b/include/TGUI/Renderers/LabelRenderer.hpp @@ -159,6 +159,34 @@ namespace tgui TextStyle getTextStyle() const; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Sets the renderer data of the scrollbars + /// @param scrollbarRendererData Data about how the scrollbars should look + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setScrollbar(std::shared_ptr scrollbarRendererData); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer data of the scrollbars + /// @return Data about how the scrollbars looks + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::shared_ptr getScrollbar() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Sets the wanted width of the scrollbar + /// @param scrollbarWidth Requested scrollbar width or 0 to use the default width (texture size if using textures) + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setScrollbarWidth(float scrollbarWidth); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the wanted width of the scrollbar + /// @return Requested scrollbar width or 0 if no width was set (texture width or default value will be used) + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + float getScrollbarWidth() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }; diff --git a/include/TGUI/Renderers/ScrollablePanelRenderer.hpp b/include/TGUI/Renderers/ScrollablePanelRenderer.hpp index f56f4f5d..6c8357ff 100644 --- a/include/TGUI/Renderers/ScrollablePanelRenderer.hpp +++ b/include/TGUI/Renderers/ScrollablePanelRenderer.hpp @@ -59,14 +59,14 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// @brief Sets the wanted width scrollbar + /// @brief Sets the wanted width of the scrollbar /// @param scrollbarWidth Requested scrollbar width or 0 to use the default width (texture size if using textures) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void setScrollbarWidth(float scrollbarWidth); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// @brief Returns the wanted width scrollbar + /// @brief Returns the wanted width of the scrollbar /// @return Requested scrollbar width or 0 if no width was set (texture width or default value will be used) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// float getScrollbarWidth() const; diff --git a/include/TGUI/Widgets/Label.hpp b/include/TGUI/Widgets/Label.hpp index 90bec22d..2e67f559 100644 --- a/include/TGUI/Widgets/Label.hpp +++ b/include/TGUI/Widgets/Label.hpp @@ -29,6 +29,8 @@ #include #include +#include +#include #include ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -46,6 +48,15 @@ namespace tgui typedef std::shared_ptr ConstPtr; ///< Shared constant widget pointer + /// @brief Defines when the scrollbar shows up + enum class ScrollbarPolicy + { + Automatic, ///< Show the scrollbar only when needed (default) + Always, ///< Always show the scrollbar (except for auto-sized labels), even when the text fits inside the label + Never ///< Never show the scrollbar, even if the text does not fit inside the label + }; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief The horizontal text alignment ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -211,6 +222,20 @@ namespace tgui VerticalAlignment getVerticalAlignment() const; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes when the vertical scrollbar should be displayed + /// @param policy The policy for displaying the vertical scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setScrollbarPolicy(ScrollbarPolicy policy); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns when the vertical scrollbar should be displayed + /// @return The policy for displaying the vertical scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ScrollbarPolicy getScrollbarPolicy() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Changes whether the label is auto-sized or not /// @@ -296,11 +321,24 @@ namespace tgui bool canGainFocus() const override; - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @internal - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void leftMousePressed(Vector2f pos) override; + + /// @internal void leftMouseReleased(Vector2f pos) override; + /// @internal + void mouseMoved(Vector2f pos) override; + + /// @internal + bool mouseWheelScrolled(float delta, Vector2f pos) override; + + /// @internal + void mouseNoLongerOnWidget() override; + + /// @internal + void mouseNoLongerDown() override; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Draw the widget to a render target @@ -394,6 +432,13 @@ namespace tgui // Will be set to true after the first click, but gets reset to false when the second click does not occur soon after bool m_possibleDoubleClick = false; + CopiedSharedPtr m_scrollbar; +#ifdef TGUI_NEXT + ScrollbarPolicy m_scrollbarPolicy = ScrollbarPolicy::Automatic; +#else + ScrollbarPolicy m_scrollbarPolicy = ScrollbarPolicy::Never; +#endif + // Cached renderer properties Borders m_bordersCached; Padding m_paddingCached; diff --git a/src/TGUI/Renderers/LabelRenderer.cpp b/src/TGUI/Renderers/LabelRenderer.cpp index 666ae5e8..5013abe1 100644 --- a/src/TGUI/Renderers/LabelRenderer.cpp +++ b/src/TGUI/Renderers/LabelRenderer.cpp @@ -38,6 +38,9 @@ namespace tgui TGUI_RENDERER_PROPERTY_COLOR(LabelRenderer, BorderColor, Color::Black) TGUI_RENDERER_PROPERTY_TEXT_STYLE(LabelRenderer, TextStyle, sf::Text::Regular) + + TGUI_RENDERER_PROPERTY_RENDERER(LabelRenderer, Scrollbar, "scrollbar") + TGUI_RENDERER_PROPERTY_NUMBER(LabelRenderer, ScrollbarWidth, 0) } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/TGUI/Widgets/Label.cpp b/src/TGUI/Widgets/Label.cpp index d20b5c48..6c23769c 100644 --- a/src/TGUI/Widgets/Label.cpp +++ b/src/TGUI/Widgets/Label.cpp @@ -28,10 +28,6 @@ #include -#ifdef TGUI_USE_CPP17 - #include -#endif - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace tgui @@ -41,10 +37,12 @@ namespace tgui Label::Label() { m_type = "Label"; + m_draggableWidget = true; m_renderer = aurora::makeCopied(); setRenderer(Theme::getDefault()->getRendererNoThrow(m_type)); + m_scrollbar->setVisible(false); setTextSize(getGlobalTextSize()); } @@ -177,13 +175,31 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void Label::setScrollbarPolicy(ScrollbarPolicy policy) + { + m_scrollbarPolicy = policy; + + // The policy only has an effect when not auto-sizing + if (!m_autoSize) + rearrangeText(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Label::ScrollbarPolicy Label::getScrollbarPolicy() const + { + return m_scrollbarPolicy; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void Label::setAutoSize(bool autoSize) { - if (m_autoSize != autoSize) - { - m_autoSize = autoSize; - rearrangeText(); - } + if (m_autoSize == autoSize) + return; + + m_autoSize = autoSize; + rearrangeText(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -197,11 +213,11 @@ namespace tgui void Label::setMaximumTextWidth(float maximumWidth) { - if (m_maximumTextWidth != maximumWidth) - { - m_maximumTextWidth = maximumWidth; - rearrangeText(); - } + if (m_maximumTextWidth == maximumWidth) + return; + + m_maximumTextWidth = maximumWidth; + rearrangeText(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -246,26 +262,93 @@ namespace tgui ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void Label::leftMousePressed(Vector2f pos) + { + if (m_scrollbar->isShown() && m_scrollbar->mouseOnWidget(pos - getPosition())) + { + m_mouseDown = true; + m_scrollbar->leftMousePressed(pos - getPosition()); + } + else + ClickableWidget::leftMousePressed(pos); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void Label::leftMouseReleased(Vector2f pos) { - const bool mouseDown = m_mouseDown; - - ClickableWidget::leftMouseReleased(pos); - - if (mouseDown) + if (!m_scrollbar->isShown() || !m_scrollbar->isMouseDown()) { - // Check if you double-clicked - if (m_possibleDoubleClick) + const bool mouseDown = m_mouseDown; + ClickableWidget::leftMouseReleased(pos); + + if (mouseDown) { + // Check if you double-clicked + if (m_possibleDoubleClick) + { + m_possibleDoubleClick = false; + onDoubleClick.emit(this, m_string); + } + else // This is the first click + { + m_animationTimeElapsed = {}; + m_possibleDoubleClick = true; + } + } + else // Mouse didn't go down on the label, so this isn't considered a click m_possibleDoubleClick = false; - onDoubleClick.emit(this, m_string); - } - else // This is the first click - { - m_animationTimeElapsed = {}; - m_possibleDoubleClick = true; - } } + else + m_mouseDown = false; + + if (m_scrollbar->isShown()) + m_scrollbar->leftMouseReleased(pos - getPosition()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void Label::mouseMoved(Vector2f pos) + { + if (m_scrollbar->isShown() && ((m_scrollbar->isMouseDown() && m_scrollbar->isMouseDownOnThumb()) || m_scrollbar->mouseOnWidget(pos - getPosition()))) + m_scrollbar->mouseMoved(pos - getPosition()); + else + { + ClickableWidget::mouseMoved(pos); + + if (m_scrollbar->isShown()) + m_scrollbar->mouseNoLongerOnWidget(); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool Label::mouseWheelScrolled(float delta, Vector2f pos) + { + if (!m_autoSize && m_scrollbar->isShown()) + { + m_scrollbar->mouseWheelScrolled(delta, pos - getPosition()); + return true; + } + + return false; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void Label::mouseNoLongerOnWidget() + { + ClickableWidget::mouseNoLongerOnWidget(); + m_scrollbar->mouseNoLongerOnWidget(); + m_possibleDoubleClick = false; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void Label::mouseNoLongerDown() + { + ClickableWidget::mouseNoLongerDown(); + m_scrollbar->mouseNoLongerDown(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -313,6 +396,23 @@ namespace tgui { m_backgroundColorCached = getSharedRenderer()->getBackgroundColor(); } + else if (property == "scrollbar") + { + m_scrollbar->setRenderer(getSharedRenderer()->getScrollbar()); + + // If no scrollbar width was set then we may need to use the one from the texture + if (!getSharedRenderer()->getScrollbarWidth()) + { + m_scrollbar->setSize({m_scrollbar->getDefaultWidth(), m_scrollbar->getSize().y}); + rearrangeText(); + } + } + else if (property == "scrollbarwidth") + { + const float width = getSharedRenderer()->getScrollbarWidth() ? getSharedRenderer()->getScrollbarWidth() : m_scrollbar->getDefaultWidth(); + m_scrollbar->setSize({width, m_scrollbar->getSize().y}); + rearrangeText(); + } else if (property == "font") { Widget::rendererChanged(property); @@ -354,6 +454,14 @@ namespace tgui if (m_ignoringMouseEvents) node->propertyValuePairs["IgnoreMouseEvents"] = std::make_unique(Serializer::serialize(m_ignoringMouseEvents)); + if (m_scrollbarPolicy != ScrollbarPolicy::Automatic) + { + if (m_scrollbarPolicy == ScrollbarPolicy::Always) + node->propertyValuePairs["ScrollbarPolicy"] = std::make_unique("Always"); + else if (m_scrollbarPolicy == ScrollbarPolicy::Never) + node->propertyValuePairs["ScrollbarPolicy"] = std::make_unique("Never"); + } + node->propertyValuePairs["TextSize"] = std::make_unique(to_string(m_textSize)); return node; } @@ -386,6 +494,19 @@ namespace tgui throw Exception{"Failed to parse VerticalAlignment property, found unknown value."}; } + if (node->propertyValuePairs["scrollbarpolicy"]) + { + std::string policy = toLower(trim(node->propertyValuePairs["scrollbarpolicy"]->value)); + if (policy == "automatic") + setScrollbarPolicy(ScrollbarPolicy::Automatic); + else if (policy == "always") + setScrollbarPolicy(ScrollbarPolicy::Always); + else if (policy == "never") + setScrollbarPolicy(ScrollbarPolicy::Never); + else + throw Exception{"Failed to parse ScrollbarPolicy property, found unknown value."}; + } + if (node->propertyValuePairs["text"]) setText(Deserializer::deserialize(ObjectConverter::Type::String, node->propertyValuePairs["text"]->value).getString()); if (node->propertyValuePairs["textsize"]) @@ -423,6 +544,27 @@ namespace tgui const float textOffset = Text::getExtraHorizontalPadding(m_fontCached, m_textSize, m_textStyleCached); + // Show or hide the scrollbar + if (m_autoSize) + m_scrollbar->setVisible(false); + else + { + if (m_scrollbarPolicy == ScrollbarPolicy::Always) + { + m_scrollbar->setVisible(true); + m_scrollbar->setAutoHide(false); + } + else if (m_scrollbarPolicy == ScrollbarPolicy::Never) + { + m_scrollbar->setVisible(false); + } + else // ScrollbarPolicy::Automatic + { + m_scrollbar->setVisible(true); + m_scrollbar->setAutoHide(true); + } + } + // Find the maximum width of one line float maxWidth; if (m_autoSize) @@ -430,12 +572,52 @@ namespace tgui else { maxWidth = getSize().x - m_bordersCached.getLeft() - m_bordersCached.getRight() - m_paddingCached.getLeft() - m_paddingCached.getRight() - 2*textOffset; + + // If the scrollbar is always visible then we take it into account, otherwise we assume there is no scrollbar + if (m_scrollbarPolicy == ScrollbarPolicy::Always) + maxWidth -= m_scrollbar->getSize().x; + if (maxWidth <= 0) return; } // Fit the text in the available space - const sf::String string = Text::wordWrap(maxWidth, m_string, m_fontCached, m_textSize, m_textStyleCached & sf::Text::Bold); + sf::String string = Text::wordWrap(maxWidth, m_string, m_fontCached, m_textSize, m_textStyleCached & sf::Text::Bold); + + const Outline outline = {m_paddingCached.getLeft() + m_bordersCached.getLeft(), + m_paddingCached.getTop() + m_bordersCached.getTop(), + m_paddingCached.getRight() + m_bordersCached.getRight(), + m_paddingCached.getBottom() + m_bordersCached.getBottom()}; + + const auto lineCount = std::count(string.begin(), string.end(), '\n') + 1; + float requiredTextHeight = lineCount * m_fontCached.getLineSpacing(m_textSize) + + Text::calculateExtraVerticalSpace(m_fontCached, m_textSize, m_textStyleCached) + + Text::getExtraVerticalPadding(m_textSize); + + // Check if a scrollbar should be added + if (!m_autoSize) + { + // If the text doesn't fit in the label then we need to run the word-wrap again, but this time taking the scrollbar into account + if ((m_scrollbarPolicy == ScrollbarPolicy::Automatic) && (requiredTextHeight > getSize().y - outline.getTop() - outline.getBottom())) + { + maxWidth -= m_scrollbar->getSize().x; + if (maxWidth <= 0) + return; + + string = Text::wordWrap(maxWidth, m_string, m_fontCached, m_textSize, m_textStyleCached & sf::Text::Bold); + + const auto newLineCount = std::count(string.begin(), string.end(), '\n') + 1; + requiredTextHeight = newLineCount * m_fontCached.getLineSpacing(m_textSize) + + Text::calculateExtraVerticalSpace(m_fontCached, m_textSize, m_textStyleCached) + + Text::getExtraVerticalPadding(m_textSize); + } + + m_scrollbar->setSize(m_scrollbar->getSize().x, static_cast(getSize().y - m_bordersCached.getTop() - m_bordersCached.getBottom())); + m_scrollbar->setViewportSize(static_cast(getSize().y - outline.getTop() - outline.getBottom())); + m_scrollbar->setMaximum(static_cast(requiredTextHeight)); + m_scrollbar->setPosition({getSize().x - m_bordersCached.getRight() - m_scrollbar->getSize().x, m_bordersCached.getTop()}); + m_scrollbar->setScrollAmount(m_textSize); + } // Split the string in multiple lines float width = 0; @@ -463,19 +645,10 @@ namespace tgui searchPosStart = newLinePos + 1; } - const Outline outline = {m_paddingCached.getLeft() + m_bordersCached.getLeft(), - m_paddingCached.getTop() + m_bordersCached.getTop(), - m_paddingCached.getRight() + m_bordersCached.getRight(), - m_paddingCached.getBottom() + m_bordersCached.getBottom()}; - // Update the size of the label if (m_autoSize) { - Widget::setSize({std::max(width, maxWidth) + outline.getLeft() + outline.getRight() + 2*textOffset, - (std::max(m_lines.size(), 1) * m_fontCached.getLineSpacing(m_textSize)) - + Text::calculateExtraVerticalSpace(m_fontCached, m_textSize, m_textStyleCached) - + Text::getExtraVerticalPadding(m_textSize) + outline.getTop() + outline.getBottom()}); - + Widget::setSize({std::max(width, maxWidth) + outline.getLeft() + outline.getRight() + 2*textOffset, requiredTextHeight + outline.getTop() + outline.getBottom()}); m_bordersCached.updateParentSize(getSize()); m_paddingCached.updateParentSize(getSize()); } @@ -535,6 +708,7 @@ namespace tgui void Label::draw(sf::RenderTarget& target, sf::RenderStates states) const { states.transform.translate(std::round(getPosition().x), std::round(getPosition().y)); + const sf::RenderStates statesForScrollbar = states; Vector2f innerSize = {getSize().x - m_bordersCached.getLeft() - m_bordersCached.getRight(), getSize().y - m_bordersCached.getTop() - m_bordersCached.getBottom()}; @@ -550,27 +724,29 @@ namespace tgui if (m_backgroundColorCached.isSet() && (m_backgroundColorCached != Color::Transparent)) drawRectangleShape(target, states, innerSize, m_backgroundColorCached); - // Apply clipping when needed - #ifdef TGUI_USE_CPP17 - std::optional clipping; - #else - std::unique_ptr clipping; - #endif - if (!m_autoSize) + // Draw the scrollbar + if (m_scrollbar->isVisible()) + m_scrollbar->draw(target, statesForScrollbar); + + // Draw the text + if (m_autoSize) + { + for (const auto& line : m_lines) + line.draw(target, states); + } + else { innerSize.x -= m_paddingCached.getLeft() + m_paddingCached.getRight(); innerSize.y -= m_paddingCached.getTop() + m_paddingCached.getBottom(); - #ifdef TGUI_USE_CPP17 - clipping.emplace(target, states, Vector2f{m_paddingCached.getLeft(), m_paddingCached.getTop()}, innerSize); - #else - clipping = std::make_unique(target, states, Vector2f{m_paddingCached.getLeft(), m_paddingCached.getTop()}, innerSize); - #endif - } + const Clipping clipping{target, states, Vector2f{m_paddingCached.getLeft(), m_paddingCached.getTop()}, innerSize}; - // Draw the text - for (const auto& line : m_lines) - line.draw(target, states); + if (m_scrollbar->isShown()) + states.transform.translate({0, -static_cast(m_scrollbar->getValue())}); + + for (const auto& line : m_lines) + line.draw(target, states); + } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tests/Clipping.cpp b/tests/Clipping.cpp index 84faa374..3baa4575 100644 --- a/tests/Clipping.cpp +++ b/tests/Clipping.cpp @@ -102,7 +102,7 @@ TEST_CASE("[Clipping]") auto group = tgui::Group::create(); panel->add(group); - auto layout = tgui::HorizontalLayout::create({150, 25}); + auto layout = tgui::HorizontalLayout::create({150, 30}); layout->setPosition({-30, -15}); group->add(layout); diff --git a/tests/Widgets/Label.cpp b/tests/Widgets/Label.cpp index 48c72e52..b2c36466 100644 --- a/tests/Widgets/Label.cpp +++ b/tests/Widgets/Label.cpp @@ -128,6 +128,17 @@ TEST_CASE("[Label]") REQUIRE(label->getMaximumTextWidth() == 500); } + SECTION("ScrollbarPolicy") + { + REQUIRE(label->getScrollbarPolicy() == tgui::Label::ScrollbarPolicy::Never); + label->setScrollbarPolicy(tgui::Label::ScrollbarPolicy::Always); + REQUIRE(label->getScrollbarPolicy() == tgui::Label::ScrollbarPolicy::Always); + label->setScrollbarPolicy(tgui::Label::ScrollbarPolicy::Automatic); + REQUIRE(label->getScrollbarPolicy() == tgui::Label::ScrollbarPolicy::Automatic); + label->setScrollbarPolicy(tgui::Label::ScrollbarPolicy::Never); + REQUIRE(label->getScrollbarPolicy() == tgui::Label::ScrollbarPolicy::Never); + } + SECTION("IgnoreMouseEvents") { REQUIRE(!label->isIgnoringMouseEvents()); @@ -176,6 +187,10 @@ TEST_CASE("[Label]") { auto renderer = label->getRenderer(); + tgui::ScrollbarRenderer scrollbarRenderer; + scrollbarRenderer.setTrackColor(sf::Color::Red); + scrollbarRenderer.setThumbColor(sf::Color::Blue); + SECTION("set serialized property") { REQUIRE_NOTHROW(renderer->setProperty("TextColor", "rgb(100, 50, 150)")); @@ -184,6 +199,8 @@ TEST_CASE("[Label]") REQUIRE_NOTHROW(renderer->setProperty("Borders", "(1, 2, 3, 4)")); REQUIRE_NOTHROW(renderer->setProperty("Padding", "(5, 6, 7, 8)")); REQUIRE_NOTHROW(renderer->setProperty("TextStyle", "Bold | Italic")); + REQUIRE_NOTHROW(renderer->setProperty("Scrollbar", "{ TrackColor = Red; ThumbColor = Blue; }")); + REQUIRE_NOTHROW(renderer->setProperty("ScrollbarWidth", "15")); } SECTION("set object property") @@ -194,6 +211,8 @@ TEST_CASE("[Label]") REQUIRE_NOTHROW(renderer->setProperty("Borders", tgui::Borders{1, 2, 3, 4})); REQUIRE_NOTHROW(renderer->setProperty("Padding", tgui::Borders{5, 6, 7, 8})); REQUIRE_NOTHROW(renderer->setProperty("TextStyle", tgui::TextStyle{sf::Text::Bold | sf::Text::Italic})); + REQUIRE_NOTHROW(renderer->setProperty("Scrollbar", scrollbarRenderer.getData())); + REQUIRE_NOTHROW(renderer->setProperty("ScrollbarWidth", 15)); } SECTION("functions") @@ -204,6 +223,8 @@ TEST_CASE("[Label]") renderer->setBorders({1, 2, 3, 4}); renderer->setPadding({5, 6, 7, 8}); renderer->setTextStyle(sf::Text::Bold | sf::Text::Italic); + renderer->setScrollbar(scrollbarRenderer.getData()); + renderer->setScrollbarWidth(15); } REQUIRE(renderer->getProperty("TextColor").getColor() == sf::Color(100, 50, 150)); @@ -212,6 +233,7 @@ TEST_CASE("[Label]") REQUIRE(renderer->getProperty("Borders").getOutline() == tgui::Borders(1, 2, 3, 4)); REQUIRE(renderer->getProperty("Padding").getOutline() == tgui::Padding(5, 6, 7, 8)); REQUIRE(renderer->getProperty("TextStyle").getTextStyle() == (sf::Text::Bold | sf::Text::Italic)); + REQUIRE(renderer->getProperty("ScrollbarWidth").getNumber() == 15); REQUIRE(renderer->getTextColor() == sf::Color(100, 50, 150)); REQUIRE(renderer->getBackgroundColor() == sf::Color(150, 100, 50)); @@ -219,6 +241,10 @@ TEST_CASE("[Label]") REQUIRE(renderer->getBorders() == tgui::Borders(1, 2, 3, 4)); REQUIRE(renderer->getPadding() == tgui::Padding(5, 6, 7, 8)); REQUIRE(renderer->getTextStyle() == (sf::Text::Bold | sf::Text::Italic)); + + REQUIRE(renderer->getScrollbar()->propertyValuePairs.size() == 2); + REQUIRE(renderer->getScrollbar()->propertyValuePairs["trackcolor"].getColor() == sf::Color::Red); + REQUIRE(renderer->getScrollbar()->propertyValuePairs["thumbcolor"].getColor() == sf::Color::Blue); } SECTION("Saving and loading from file") @@ -227,6 +253,7 @@ TEST_CASE("[Label]") label->setTextSize(25); label->setHorizontalAlignment(tgui::Label::HorizontalAlignment::Center); label->setVerticalAlignment(tgui::Label::VerticalAlignment::Bottom); + label->setScrollbarPolicy(tgui::Label::ScrollbarPolicy::Never); label->setMaximumTextWidth(300); label->ignoreMouseEvents(true); @@ -250,6 +277,7 @@ TEST_CASE("[Label]") SECTION("Complex") { TEST_DRAW_INIT(420, 215, label) + label->setScrollbarPolicy(tgui::Label::ScrollbarPolicy::Automatic); label->setText("Bacon ipsum dolor amet alcatra jerky turkey ball tip jowl beef. Shank landjaeger frankfurter, doner burgdoggen strip steak chicken pancetta jowl. Pork loin leberkas meatloaf ham shoulder cow hamburger pancetta. Rump turducken ribeye salami pork chop sirloin. Leberkas alcatra filet mignon jerky pork belly."); label->setTextSize(18); label->setSize(400, 205); @@ -259,6 +287,12 @@ TEST_CASE("[Label]") label->getRenderer()->setPadding({4, 3, 2, 1}); label->getRenderer()->setOpacity(0.7f); TEST_DRAW("Label_Complex.png") + + SECTION("With scrollbar") + { + label->setText(label->getText() + "\n" + label->getText()); + TEST_DRAW("Label_Complex_WithScrollbar.png") + } } } } diff --git a/tests/Widgets/ScrollablePanel.cpp b/tests/Widgets/ScrollablePanel.cpp index bcfe537d..ca82e08c 100644 --- a/tests/Widgets/ScrollablePanel.cpp +++ b/tests/Widgets/ScrollablePanel.cpp @@ -78,6 +78,28 @@ TEST_CASE("[ScrollablePanel]") } } + SECTION("VerticalScrollbarPolicy") + { + REQUIRE(panel->getVerticalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Automatic); + panel->setVerticalScrollbarPolicy(tgui::ScrollablePanel::ScrollbarPolicy::Always); + REQUIRE(panel->getVerticalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Always); + panel->setVerticalScrollbarPolicy(tgui::ScrollablePanel::ScrollbarPolicy::Automatic); + REQUIRE(panel->getVerticalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Automatic); + panel->setVerticalScrollbarPolicy(tgui::ScrollablePanel::ScrollbarPolicy::Never); + REQUIRE(panel->getVerticalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Never); + } + + SECTION("HorizontalScrollbarPolicy") + { + REQUIRE(panel->getHorizontalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Automatic); + panel->setHorizontalScrollbarPolicy(tgui::ScrollablePanel::ScrollbarPolicy::Always); + REQUIRE(panel->getHorizontalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Always); + panel->setHorizontalScrollbarPolicy(tgui::ScrollablePanel::ScrollbarPolicy::Automatic); + REQUIRE(panel->getHorizontalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Automatic); + panel->setHorizontalScrollbarPolicy(tgui::ScrollablePanel::ScrollbarPolicy::Never); + REQUIRE(panel->getHorizontalScrollbarPolicy() == tgui::ScrollablePanel::ScrollbarPolicy::Never); + } + SECTION("Events / Signals") { unsigned int mousePressedCount = 0; diff --git a/tests/expected/Label_Complex_WithScrollbar.png b/tests/expected/Label_Complex_WithScrollbar.png new file mode 100644 index 0000000000000000000000000000000000000000..4349b45d34e921811e75a7987d83362e66d01166 GIT binary patch literal 30264 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 literal 0 HcmV?d00001 diff --git a/themes/BabyBlue.txt b/themes/BabyBlue.txt index b1fa5c5a..9326d1ca 100644 --- a/themes/BabyBlue.txt +++ b/themes/BabyBlue.txt @@ -70,6 +70,7 @@ EditBox { Label { TextColor = rgb(100, 100, 100); + Scrollbar = &Scrollbar; } ListBox { diff --git a/themes/Black.txt b/themes/Black.txt index 3ed7c094..dd4071f7 100644 --- a/themes/Black.txt +++ b/themes/Black.txt @@ -74,6 +74,7 @@ EditBox { Label { TextColor = rgb(190, 190, 190); + Scrollbar = &Scrollbar; } ListBox { diff --git a/themes/TransparentGrey.txt b/themes/TransparentGrey.txt index 4225b120..09fc1388 100644 --- a/themes/TransparentGrey.txt +++ b/themes/TransparentGrey.txt @@ -67,6 +67,7 @@ EditBox { Label { TextColor = rgba(255, 255, 255, 215); + Scrollbar = &Scrollbar; } ListBox {