2020-09-17 20:00:07 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-06-28 16:39:18 +00:00
|
|
|
//
|
2020-09-17 20:00:07 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-28 16:39:18 +00:00
|
|
|
// ScrollableContainer.cpp
|
|
|
|
//
|
2021-09-30 18:11:56 +00:00
|
|
|
// Component containing scrollable information, used for the game
|
|
|
|
// description text in the scraper and gamelist views.
|
2020-06-28 16:39:18 +00:00
|
|
|
//
|
|
|
|
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "components/ScrollableContainer.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
#include "Window.h"
|
2021-01-02 20:17:23 +00:00
|
|
|
#include "animations/LambdaAnimation.h"
|
2019-08-08 20:16:11 +00:00
|
|
|
#include "renderers/Renderer.h"
|
2021-01-17 21:02:22 +00:00
|
|
|
#include "resources/Font.h"
|
2013-07-03 01:01:58 +00:00
|
|
|
|
2022-01-19 17:01:54 +00:00
|
|
|
ScrollableContainer::ScrollableContainer()
|
2022-03-14 18:51:48 +00:00
|
|
|
: mRenderer {Renderer::getInstance()}
|
|
|
|
, mScrollPos {0.0f, 0.0f}
|
2022-01-16 11:09:55 +00:00
|
|
|
, mScrollDir {0.0f, 0.0f}
|
2022-09-16 16:49:39 +00:00
|
|
|
, mAdjustedHeight {0.0f}
|
2022-01-16 11:09:55 +00:00
|
|
|
, mClipSpacing {0.0f}
|
|
|
|
, mAutoScrollDelay {0}
|
|
|
|
, mAutoScrollSpeed {0}
|
|
|
|
, mAutoScrollAccumulator {0}
|
|
|
|
, mAutoScrollResetAccumulator {0}
|
|
|
|
, mAdjustedAutoScrollSpeed {0}
|
2022-09-16 16:49:39 +00:00
|
|
|
, mVerticalSnap {true}
|
2022-01-16 11:09:55 +00:00
|
|
|
, mUpdatedSize {false}
|
2013-07-03 01:01:58 +00:00
|
|
|
{
|
2021-01-02 20:17:23 +00:00
|
|
|
// Set the modifier to get equivalent scrolling speed regardless of screen resolution.
|
2023-02-07 17:51:04 +00:00
|
|
|
mResolutionModifier = mRenderer->getScreenResolutionModifier();
|
2021-01-05 11:52:21 +00:00
|
|
|
|
|
|
|
mAutoScrollResetDelayConstant = AUTO_SCROLL_RESET_DELAY;
|
|
|
|
mAutoScrollDelayConstant = AUTO_SCROLL_DELAY;
|
|
|
|
mAutoScrollSpeedConstant = AUTO_SCROLL_SPEED;
|
2013-07-03 01:01:58 +00:00
|
|
|
}
|
|
|
|
|
2014-05-19 01:15:15 +00:00
|
|
|
void ScrollableContainer::setAutoScroll(bool autoScroll)
|
2013-07-03 01:01:58 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
if (autoScroll) {
|
2022-01-16 11:09:55 +00:00
|
|
|
mScrollDir = glm::vec2 {0.0f, 1.0f};
|
2021-01-18 23:11:02 +00:00
|
|
|
mAutoScrollDelay = static_cast<int>(mAutoScrollDelayConstant);
|
2023-08-08 17:18:16 +00:00
|
|
|
resetComponent();
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
else {
|
2022-01-16 11:09:55 +00:00
|
|
|
mScrollDir = glm::vec2 {};
|
2020-06-28 16:39:18 +00:00
|
|
|
mAutoScrollDelay = 0;
|
|
|
|
mAutoScrollSpeed = 0;
|
|
|
|
mAutoScrollAccumulator = 0;
|
|
|
|
}
|
2013-07-03 01:01:58 +00:00
|
|
|
}
|
|
|
|
|
2021-01-17 09:17:41 +00:00
|
|
|
void ScrollableContainer::setScrollParameters(float autoScrollDelayConstant,
|
2021-07-07 18:31:46 +00:00
|
|
|
float autoScrollResetDelayConstant,
|
2021-09-30 18:11:56 +00:00
|
|
|
float autoScrollSpeedConstant)
|
2013-07-03 01:01:58 +00:00
|
|
|
{
|
2021-09-30 18:11:56 +00:00
|
|
|
mAutoScrollResetDelayConstant = glm::clamp(autoScrollResetDelayConstant, 1000.0f, 10000.0f);
|
|
|
|
mAutoScrollDelayConstant = glm::clamp(autoScrollDelayConstant, 1000.0f, 10000.0f);
|
|
|
|
mAutoScrollSpeedConstant = AUTO_SCROLL_SPEED / glm::clamp(autoScrollSpeedConstant, 0.1f, 10.0f);
|
2021-01-17 21:02:22 +00:00
|
|
|
}
|
|
|
|
|
2023-08-08 17:18:16 +00:00
|
|
|
void ScrollableContainer::resetComponent()
|
2013-07-03 01:01:58 +00:00
|
|
|
{
|
2022-09-27 19:56:15 +00:00
|
|
|
mScrollPos = glm::vec2 {0.0f, 0.0f};
|
2021-01-05 11:52:21 +00:00
|
|
|
mAutoScrollResetAccumulator = 0;
|
|
|
|
mAutoScrollAccumulator = -mAutoScrollDelay + mAutoScrollSpeed;
|
|
|
|
mAtEnd = false;
|
2022-10-24 22:39:40 +00:00
|
|
|
// This is needed to resize to the designated area when the background image gets invalidated.
|
2022-09-27 19:56:15 +00:00
|
|
|
if (!mChildren.empty()) {
|
2022-10-24 22:39:40 +00:00
|
|
|
float combinedHeight {0.0f};
|
|
|
|
const float cacheGlyphHeight {
|
|
|
|
static_cast<float>(mChildren.front()->getTextCacheGlyphHeight())};
|
|
|
|
if (cacheGlyphHeight > 0.0f)
|
|
|
|
combinedHeight = cacheGlyphHeight * mChildren.front()->getLineSpacing();
|
|
|
|
else
|
|
|
|
return;
|
2022-09-27 19:56:15 +00:00
|
|
|
if (mChildren.front()->getSize().y > mSize.y) {
|
|
|
|
if (mVerticalSnap) {
|
|
|
|
float numLines {std::floor(mSize.y / combinedHeight)};
|
2023-01-16 16:20:54 +00:00
|
|
|
if (numLines == 0)
|
|
|
|
numLines = 1;
|
2022-09-27 19:56:15 +00:00
|
|
|
mAdjustedHeight = std::round(numLines * combinedHeight);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mAdjustedHeight = mSize.y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-07-03 01:01:58 +00:00
|
|
|
}
|
|
|
|
|
2022-02-10 19:02:56 +00:00
|
|
|
void ScrollableContainer::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|
|
|
const std::string& view,
|
|
|
|
const std::string& element,
|
|
|
|
unsigned int properties)
|
|
|
|
{
|
|
|
|
using namespace ThemeFlags;
|
|
|
|
GuiComponent::applyTheme(theme, view, element, properties);
|
|
|
|
|
|
|
|
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "text")};
|
2023-02-25 13:40:55 +00:00
|
|
|
if (!elem)
|
2022-02-10 19:02:56 +00:00
|
|
|
return;
|
|
|
|
|
2022-09-16 16:49:39 +00:00
|
|
|
if (elem->has("containerVerticalSnap"))
|
|
|
|
mVerticalSnap = elem->get<bool>("containerVerticalSnap");
|
|
|
|
|
2022-02-10 19:02:56 +00:00
|
|
|
if (elem->has("containerScrollSpeed")) {
|
|
|
|
mAutoScrollSpeedConstant =
|
|
|
|
AUTO_SCROLL_SPEED / glm::clamp(elem->get<float>("containerScrollSpeed"), 0.1f, 10.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (elem->has("containerStartDelay")) {
|
|
|
|
mAutoScrollDelayConstant =
|
|
|
|
glm::clamp(elem->get<float>("containerStartDelay"), 0.0f, 10.0f) * 1000.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (elem->has("containerResetDelay")) {
|
|
|
|
mAutoScrollResetDelayConstant =
|
|
|
|
glm::clamp(elem->get<float>("containerResetDelay"), 0.0f, 20.0f) * 1000.0f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-03 01:01:58 +00:00
|
|
|
void ScrollableContainer::update(int deltaTime)
|
|
|
|
{
|
2022-09-15 21:31:51 +00:00
|
|
|
if (!isVisible() || mSize == glm::vec2 {0.0f, 0.0f})
|
2022-01-29 10:06:58 +00:00
|
|
|
return;
|
|
|
|
|
2022-10-16 11:51:22 +00:00
|
|
|
const glm::vec2 contentSize {glm::round(mChildren.front()->getSize())};
|
2022-01-16 11:09:55 +00:00
|
|
|
float rowModifier {1.0f};
|
2021-09-30 18:11:56 +00:00
|
|
|
|
2022-10-24 22:39:40 +00:00
|
|
|
const float lineSpacing {mChildren.front()->getLineSpacing()};
|
|
|
|
float combinedHeight {0.0f};
|
|
|
|
const float cacheGlyphHeight {static_cast<float>(mChildren.front()->getTextCacheGlyphHeight())};
|
|
|
|
if (cacheGlyphHeight > 0.0f)
|
|
|
|
combinedHeight = cacheGlyphHeight * lineSpacing;
|
|
|
|
else
|
|
|
|
return;
|
2021-09-30 18:11:56 +00:00
|
|
|
|
2021-10-15 18:31:51 +00:00
|
|
|
// Calculate the spacing which will be used to clip the container.
|
|
|
|
if (lineSpacing > 1.2f && mClipSpacing == 0.0f) {
|
2022-09-27 19:56:15 +00:00
|
|
|
const float minimumSpacing {mChildren.front()->getFont()->getHeight(1.2f)};
|
|
|
|
const float currentSpacing {mChildren.front()->getFont()->getHeight(lineSpacing)};
|
2021-10-15 18:31:51 +00:00
|
|
|
mClipSpacing = std::round((currentSpacing - minimumSpacing) / 2.0f);
|
|
|
|
}
|
2021-10-14 19:59:09 +00:00
|
|
|
|
2021-09-30 18:11:56 +00:00
|
|
|
// Resize container to font height boundary to avoid rendering a fraction of the last line.
|
2022-09-16 16:49:39 +00:00
|
|
|
if (!mUpdatedSize) {
|
|
|
|
if (mVerticalSnap) {
|
|
|
|
float numLines {std::floor(mSize.y / combinedHeight)};
|
2023-01-16 16:20:54 +00:00
|
|
|
if (numLines == 0)
|
|
|
|
numLines = 1;
|
2022-09-16 16:49:39 +00:00
|
|
|
mAdjustedHeight = std::round(numLines * combinedHeight);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mAdjustedHeight = mSize.y;
|
|
|
|
}
|
2021-09-30 18:11:56 +00:00
|
|
|
mUpdatedSize = true;
|
|
|
|
}
|
2022-09-16 16:49:39 +00:00
|
|
|
|
|
|
|
// Don't scroll if the media viewer or screensaver is active or if text scrolling is disabled;
|
|
|
|
if (mWindow->isMediaViewerActive() || mWindow->isScreensaverActive() ||
|
|
|
|
!mWindow->getAllowTextScrolling()) {
|
2022-09-24 20:53:52 +00:00
|
|
|
if (mScrollPos != glm::vec2 {0.0f, 0.0f} && !mWindow->isLaunchScreenDisplayed())
|
2023-08-08 17:18:16 +00:00
|
|
|
resetComponent();
|
2022-09-16 16:49:39 +00:00
|
|
|
return;
|
2021-09-30 18:11:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!mAdjustedAutoScrollSpeed) {
|
2022-01-16 11:09:55 +00:00
|
|
|
float fontSize {static_cast<float>(mChildren.front()->getFont()->getSize())};
|
|
|
|
float width {contentSize.x / (fontSize * 1.3f)};
|
2021-01-18 23:11:02 +00:00
|
|
|
|
2021-09-30 18:11:56 +00:00
|
|
|
// Keep speed adjustments within reason.
|
2022-01-16 11:09:55 +00:00
|
|
|
float speedModifier {glm::clamp(width, 10.0f, 40.0f)};
|
2021-01-02 20:17:23 +00:00
|
|
|
|
2021-09-30 18:11:56 +00:00
|
|
|
speedModifier *= mAutoScrollSpeedConstant;
|
|
|
|
speedModifier /= mResolutionModifier;
|
2021-09-30 18:18:15 +00:00
|
|
|
mAdjustedAutoScrollSpeed = static_cast<int>(speedModifier);
|
2021-09-30 18:11:56 +00:00
|
|
|
}
|
2020-10-17 10:16:58 +00:00
|
|
|
|
2022-09-24 20:53:52 +00:00
|
|
|
// If there are less than 8 lines of text, accelerate the scrolling further.
|
|
|
|
const float lines {mAdjustedHeight / combinedHeight};
|
|
|
|
if (lines < 8.0f)
|
|
|
|
rowModifier = lines / 8.0f;
|
|
|
|
|
2021-09-30 18:11:56 +00:00
|
|
|
if (mAdjustedAutoScrollSpeed < 0)
|
|
|
|
mAdjustedAutoScrollSpeed = 1;
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2021-09-30 18:11:56 +00:00
|
|
|
if (mAdjustedAutoScrollSpeed != 0) {
|
2021-01-18 23:11:02 +00:00
|
|
|
mAutoScrollAccumulator += deltaTime;
|
2021-09-30 18:11:56 +00:00
|
|
|
while (mAutoScrollAccumulator >=
|
|
|
|
static_cast<int>(rowModifier * static_cast<float>(mAdjustedAutoScrollSpeed))) {
|
2022-10-24 22:39:40 +00:00
|
|
|
if (!mAtEnd && contentSize.y > mAdjustedHeight)
|
2021-03-14 08:49:26 +00:00
|
|
|
mScrollPos += mScrollDir;
|
2021-09-30 18:11:56 +00:00
|
|
|
mAutoScrollAccumulator -=
|
|
|
|
static_cast<int>(rowModifier * static_cast<float>(mAdjustedAutoScrollSpeed));
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clip scrolling within bounds.
|
2021-08-16 16:25:01 +00:00
|
|
|
if (mScrollPos.x < 0.0f)
|
|
|
|
mScrollPos.x = 0.0f;
|
|
|
|
if (mScrollPos.y < 0.0f)
|
|
|
|
mScrollPos.y = 0.0f;
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2022-10-16 13:00:38 +00:00
|
|
|
if (mScrollPos.x + std::round(mSize.x) > contentSize.x) {
|
|
|
|
mScrollPos.x = contentSize.x - std::round(mSize.x);
|
2020-06-28 16:39:18 +00:00
|
|
|
mAtEnd = true;
|
|
|
|
}
|
|
|
|
|
2022-10-24 22:39:40 +00:00
|
|
|
if (contentSize.y < mAdjustedHeight)
|
2021-08-16 16:25:01 +00:00
|
|
|
mScrollPos.y = 0.0f;
|
2022-10-24 22:39:40 +00:00
|
|
|
else if (mScrollPos.y + mAdjustedHeight > contentSize.y)
|
2020-06-28 16:39:18 +00:00
|
|
|
mAtEnd = true;
|
|
|
|
|
|
|
|
if (mAtEnd) {
|
|
|
|
mAutoScrollResetAccumulator += deltaTime;
|
2021-01-17 21:02:22 +00:00
|
|
|
if (mAutoScrollResetAccumulator >= static_cast<int>(mAutoScrollResetDelayConstant)) {
|
2021-01-02 20:17:23 +00:00
|
|
|
// Fade in the text as it resets to the start position.
|
2022-01-31 21:53:21 +00:00
|
|
|
float maxOpacity {static_cast<float>(mChildren.front()->getColor() & 0x000000FF) /
|
|
|
|
255.0f};
|
|
|
|
auto func = [this, maxOpacity](float t) {
|
|
|
|
unsigned int color {mChildren.front()->getColor()};
|
2022-02-11 21:10:25 +00:00
|
|
|
float opacity {glm::mix(0.0f, maxOpacity, t)};
|
|
|
|
color = (color & 0xFFFFFF00) + static_cast<unsigned char>(opacity * 255.0f);
|
2022-01-31 21:53:21 +00:00
|
|
|
this->mChildren.front()->setColor(color);
|
2022-01-16 11:09:55 +00:00
|
|
|
mScrollPos = glm::vec2 {};
|
2021-01-02 20:17:23 +00:00
|
|
|
mAutoScrollResetAccumulator = 0;
|
|
|
|
mAutoScrollAccumulator = -mAutoScrollDelay + mAutoScrollSpeed;
|
|
|
|
mAtEnd = false;
|
|
|
|
};
|
|
|
|
this->setAnimation(new LambdaAnimation(func, 300), 0, nullptr, false);
|
|
|
|
}
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GuiComponent::update(deltaTime);
|
2013-07-03 01:01:58 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 17:30:31 +00:00
|
|
|
void ScrollableContainer::render(const glm::mat4& parentTrans)
|
2021-01-05 11:52:21 +00:00
|
|
|
{
|
2022-02-12 16:38:55 +00:00
|
|
|
if (!isVisible() || mThemeOpacity == 0.0f || mChildren.front()->getValue() == "")
|
2021-01-05 11:52:21 +00:00
|
|
|
return;
|
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::mat4 trans {parentTrans * getTransform()};
|
2021-01-05 11:52:21 +00:00
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::ivec2 clipPos {static_cast<int>(trans[3].x), static_cast<int>(trans[3].y)};
|
2023-03-26 17:29:35 +00:00
|
|
|
glm::ivec2 clipDim {mSize.x, mAdjustedHeight};
|
2021-01-05 11:52:21 +00:00
|
|
|
|
2021-10-14 19:59:09 +00:00
|
|
|
// By effectively clipping the upper and lower boundaries of the container we mostly avoid
|
|
|
|
// scrolling outside the vertical starting and ending positions.
|
2021-10-15 18:31:51 +00:00
|
|
|
clipPos.y += static_cast<int>(mClipSpacing);
|
|
|
|
clipDim.y -= static_cast<int>(mClipSpacing);
|
2021-10-14 19:59:09 +00:00
|
|
|
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->pushClipRect(clipPos, clipDim);
|
2021-01-05 11:52:21 +00:00
|
|
|
|
2022-10-24 22:39:40 +00:00
|
|
|
trans = glm::translate(trans, -glm::vec3 {mScrollPos.x, mScrollPos.y, 0.0f});
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->setMatrix(trans);
|
2021-01-05 11:52:21 +00:00
|
|
|
|
2022-08-21 14:51:21 +00:00
|
|
|
if (Settings::getInstance()->getBool("DebugText"))
|
2022-09-16 16:49:39 +00:00
|
|
|
mRenderer->drawRect(0.0f, mScrollPos.y, mSize.x, mAdjustedHeight, 0x0000FF33, 0x0000FF33);
|
2022-08-21 14:51:21 +00:00
|
|
|
|
2021-01-05 11:52:21 +00:00
|
|
|
GuiComponent::renderChildren(trans);
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->popClipRect();
|
2021-01-05 11:52:21 +00:00
|
|
|
}
|