Removed all horizontal text scrolling code from ComponentList (TextComponent is now used instead for this)

Also some general code cleanup and refactoring
This commit is contained in:
Leon Styhre 2023-08-08 19:18:16 +02:00
parent 8ea2ead679
commit c30d035e3f
16 changed files with 172 additions and 242 deletions

View file

@ -172,7 +172,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
row.addElement(ed, false, true);
// Pass input to the actual RatingComponent instead of the spacer.
row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
row.inputHandler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
std::placeholders::_2);
break;
}
@ -187,7 +187,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
row.addElement(ed, false);
// Pass input to the actual DateTimeEditComponent instead of the spacer.
row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
row.inputHandler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1,
std::placeholders::_2);
break;
}

View file

@ -350,7 +350,6 @@ void GuiScraperSearch::search(ScraperSearchParams& params)
mAutomaticModeGameEntry = 0;
mResultList->clear();
mResultList->setLoopRows(false);
mScraperResults.clear();
mMDRetrieveURLsHandle.reset();
mThumbnailReqMap.clear();
@ -409,9 +408,7 @@ void GuiScraperSearch::stop()
void GuiScraperSearch::onSearchDone(std::vector<ScraperSearchResult>& results)
{
mResultList->clear();
mScraperResults = results;
mResultList->setLoopRows(true);
auto font = Font::get(FONT_SIZE_MEDIUM);
unsigned int color {mMenuColorPrimary};
@ -512,9 +509,10 @@ void GuiScraperSearch::onSearchDone(std::vector<ScraperSearchResult>& results)
gameName.append(" [").append(otherPlatforms).append("]");
row.elements.clear();
row.addElement(
std::make_shared<TextComponent>(Utils::String::toUpper(gameName), font, color),
false);
auto gameEntry =
std::make_shared<TextComponent>(Utils::String::toUpper(gameName), font, color);
gameEntry->setHorizontalScrolling(true);
row.addElement(gameEntry, true);
row.makeAcceptInputHandler([this, i] { returnResult(mScraperResults.at(i)); });
mResultList->addRow(row);
}
@ -602,7 +600,7 @@ void GuiScraperSearch::updateInfoPane()
mResultName->setText(Utils::String::toUpper(res.mdl.get("name")));
mResultDesc->setText(Utils::String::toUpper(res.mdl.get("desc")));
mDescContainer->reset();
mDescContainer->resetComponent();
mResultThumbnail->setImage("");
const std::string& thumb {res.screenshotUrl.empty() ? res.coverUrl : res.screenshotUrl};
@ -670,7 +668,7 @@ bool GuiScraperSearch::input(InputConfig* config, Input input)
if (config->isMappedTo("a", input) && input.value != 0) {
if (mBlockAccept || mScraperResults.empty())
return true;
mResultList->setLoopRows(false);
mResultList->setHorizontalScrolling(false);
}
// Check whether we should allow a refine of the game name.
@ -691,7 +689,7 @@ bool GuiScraperSearch::input(InputConfig* config, Input input)
allowRefine = true;
if (allowRefine) {
mResultList->stopLooping();
mResultList->resetSelectedRow();
openInputScreen(mLastSearch);
}
}

View file

@ -79,10 +79,10 @@ void GamelistView::onFileChanged(FileData* file, bool reloadGamelist)
void GamelistView::onShow()
{
for (auto& animation : mLottieAnimComponents)
animation->resetFileAnimation();
animation->resetComponent();
for (auto& animation : mGIFAnimComponents)
animation->resetFileAnimation();
animation->resetComponent();
for (auto& video : mStaticVideoComponents)
video->stopVideoPlayer();
@ -795,10 +795,11 @@ void GamelistView::updateView(const CursorState& state)
}
for (auto& container : mContainerComponents)
container->reset();
container->resetComponent();
for (auto& scrollableText : mTextComponents)
scrollableText->resetLooping();
// Reset horizontally scrolling text.
for (auto& text : mTextComponents)
text->resetComponent();
for (auto& rating : mRatingComponents)
rating->setValue(file->metadata.get("rating"));

View file

@ -69,17 +69,18 @@ void SystemView::goToSystem(SystemData* system, bool animate)
selector->setNeedsRefresh();
}
// Reset horizontally scrolling text.
for (auto& text : mSystemElements[mPrimary->getCursor()].textComponents)
text->resetLooping();
text->resetComponent();
for (auto& video : mSystemElements[mPrimary->getCursor()].videoComponents)
video->setStaticVideo();
for (auto& anim : mSystemElements[mPrimary->getCursor()].lottieAnimComponents)
anim->resetFileAnimation();
anim->resetComponent();
for (auto& anim : mSystemElements[mPrimary->getCursor()].GIFAnimComponents)
anim->resetFileAnimation();
anim->resetComponent();
updateGameSelectors();
updateGameCount();
@ -223,8 +224,9 @@ std::vector<HelpPrompt> SystemView::getHelpPrompts()
void SystemView::onCursorChanged(const CursorState& state)
{
// Reset horizontally scrolling text.
for (auto& text : mSystemElements[mPrimary->getCursor()].textComponents)
text->resetLooping();
text->resetComponent();
const int cursor {mPrimary->getCursor()};
const int scrollVelocity {mPrimary->getScrollingVelocity()};
@ -279,7 +281,7 @@ void SystemView::onCursorChanged(const CursorState& state)
}
for (auto& container : mSystemElements[mPrimary->getCursor()].containerComponents)
container->reset();
container->resetComponent();
// This is needed to avoid erratic camera movements during extreme navigation input when using
// slide transitions. This should very rarely occur during normal application usage.
@ -312,10 +314,10 @@ void SystemView::onCursorChanged(const CursorState& state)
video->setStaticVideo();
for (auto& anim : mSystemElements[mPrimary->getCursor()].lottieAnimComponents)
anim->resetFileAnimation();
anim->resetComponent();
for (auto& anim : mSystemElements[mPrimary->getCursor()].GIFAnimComponents)
anim->resetFileAnimation();
anim->resetComponent();
updateGameSelectors();
startViewVideos();

View file

@ -310,8 +310,10 @@ public:
virtual void pauseViewVideos() {}
virtual void muteViewVideos() {}
// For Lottie animations.
virtual void resetFileAnimation() {};
// Used to reset various components like text scrolling, animations etc.
virtual void resetComponent() {}
// Used by TextComponent.
virtual void setHorizontalScrolling(bool state) {}
// Default implementation just handles <pos> and <size> tags as normalized float pairs.
// You probably want to keep this behavior for any derived classes as well as add your own.

View file

@ -22,11 +22,6 @@ ComponentList::ComponentList()
, mRowHeight {std::round(Font::get(FONT_SIZE_MEDIUM)->getHeight())}
, mSelectorBarOffset {0.0f}
, mCameraOffset {0.0f}
, mLoopRows {false}
, mLoopScroll {false}
, mLoopOffset {0}
, mLoopOffset2 {0}
, mLoopTime {0}
, mScrollIndicatorStatus {SCROLL_NONE}
{
// Adjust the padding relative to the aspect ratio and screen resolution to make it look
@ -85,8 +80,8 @@ bool ComponentList::input(InputConfig* config, Input input)
}
// Give it to the current row's input handler.
if (mEntries.at(mCursor).data.input_handler) {
if (mEntries.at(mCursor).data.input_handler(config, input))
if (mEntries.at(mCursor).data.inputHandler) {
if (mEntries.at(mCursor).data.inputHandler(config, input))
return true;
}
else {
@ -132,12 +127,6 @@ bool ComponentList::input(InputConfig* config, Input input)
void ComponentList::update(int deltaTime)
{
if (!mFocused && mLoopRows) {
mLoopOffset = 0;
mLoopOffset2 = 0;
mLoopTime = 0;
}
// Scroll indicator logic, used by ScrollIndicatorComponent.
bool scrollIndicatorChanged {false};
@ -167,39 +156,11 @@ void ComponentList::update(int deltaTime)
listUpdate(deltaTime);
if (size()) {
float rowWidth {0.0f};
if (mFocused && size()) {
// Update our currently selected row.
for (auto it = mEntries.at(mCursor).data.elements.cbegin();
it != mEntries.at(mCursor).data.elements.cend(); ++it) {
it->component->update(deltaTime);
rowWidth += it->component->getSize().x;
}
if (mLoopRows && rowWidth + mHorizontalPadding / 2.0f > mSize.x) {
// Loop the text.
const float speed {
Font::get(FONT_SIZE_MEDIUM)->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x * 0.247f};
const float delay {1500.0f};
const float scrollLength {rowWidth};
const float returnLength {speed * 1.5f};
const float scrollTime {(scrollLength * 1000.0f) / speed};
const float returnTime {(returnLength * 1000.0f) / speed};
const int maxTime {static_cast<int>(delay + scrollTime + returnTime)};
mLoopTime += deltaTime;
while (mLoopTime > maxTime)
mLoopTime -= maxTime;
mLoopOffset = static_cast<int>(Utils::Math::loop(delay, scrollTime + returnTime,
static_cast<float>(mLoopTime),
scrollLength + returnLength));
if (mLoopOffset > (scrollLength - (mSize.x - returnLength)))
mLoopOffset2 = static_cast<int>(mLoopOffset - (scrollLength + returnLength));
else if (mLoopOffset2 < 0)
mLoopOffset2 = 0;
}
}
}
@ -208,12 +169,6 @@ void ComponentList::onCursorChanged(const CursorState& state)
{
mSetupCompleted = true;
if (mLoopRows) {
mLoopOffset = 0;
mLoopOffset2 = 0;
mLoopTime = 0;
}
// Update the selector bar position.
// In the future this might be animated.
mSelectorBarOffset = 0;
@ -299,10 +254,9 @@ void ComponentList::render(const glm::mat4& parentTrans)
mRenderer->pushClipRect(glm::ivec2 {clipRectPosX, clipRectPosY},
glm::ivec2 {clipRectSizeX, clipRectSizeY});
// Scroll the camera.
// Move camera the scroll distance.
trans = glm::translate(trans, glm::vec3 {0.0f, -mCameraOffset, 0.0f});
glm::mat4 loopTrans {trans};
const bool darkColorScheme {Settings::getInstance()->getString("MenuColorScheme") != "light"};
// Draw selector bar if we're using the dark color scheme.
@ -316,30 +270,10 @@ void ComponentList::render(const glm::mat4& parentTrans)
std::vector<GuiComponent*> drawAfterCursor;
bool drawAll {false};
for (size_t i {0}; i < mEntries.size(); ++i) {
if (mLoopRows && mFocused && mLoopOffset > 0) {
loopTrans =
glm::translate(trans, glm::vec3 {static_cast<float>(-mLoopOffset), 0.0f, 0.0f});
}
auto& entry = mEntries.at(i);
drawAll = !mFocused || i != static_cast<unsigned int>(mCursor);
for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); ++it) {
if (drawAll || it->invert_when_selected) {
auto renderLoopFunc = [&]() {
// Needed to avoid flickering when returning to the start position.
if (mLoopOffset == 0 && mLoopOffset2 == 0)
mLoopScroll = false;
it->component->render(loopTrans);
// Render row again if text is moved far enough for it to repeat.
if (mLoopOffset2 < 0 || mLoopScroll) {
mLoopScroll = true;
loopTrans = glm::translate(
trans, glm::vec3 {static_cast<float>(-mLoopOffset2), 0.0f, 0.0f});
it->component->render(loopTrans);
}
};
if (drawAll || it->invertWhenSelected) {
// For the row where the cursor is at, we want to remove any hue from the
// font or image before inverting, as it would otherwise lead to an ugly
// inverted color (e.g. red inverting to a green hue).
@ -358,14 +292,14 @@ void ComponentList::render(const glm::mat4& parentTrans)
unsigned char byteBlue {static_cast<unsigned char>(origColor >> 8 & 0xFF)};
// If it's neutral, just proceed with normal rendering.
if (byteRed == byteGreen && byteGreen == byteBlue) {
renderLoopFunc();
it->component->render(trans);
}
else {
if (isTextComponent)
it->component->setColor(mMenuColorPrimary);
else
it->component->setColorShift(mMenuColorPrimary);
renderLoopFunc();
it->component->render(trans);
// Revert to the original color after rendering.
if (isTextComponent)
it->component->setColor(origColor);
@ -383,7 +317,6 @@ void ComponentList::render(const glm::mat4& parentTrans)
}
}
// Custom rendering.
mRenderer->setMatrix(trans);
// Draw selector bar if we're using the light color scheme.
@ -408,14 +341,14 @@ void ComponentList::render(const glm::mat4& parentTrans)
}
// Draw separators.
float y {0.0f};
float offsetY {0.0f};
for (unsigned int i {0}; i < mEntries.size(); ++i) {
mRenderer->drawRect(0.0f, y, mSize.x, 1.0f * mRenderer->getScreenResolutionModifier(),
mRenderer->drawRect(0.0f, offsetY, mSize.x, 1.0f * mRenderer->getScreenResolutionModifier(),
mMenuColorSeparators, mMenuColorSeparators, false, mOpacity, mDimming);
y += mRowHeight;
offsetY += mRowHeight;
}
mRenderer->drawRect(0.0f, y, mSize.x, 1.0f * mRenderer->getScreenResolutionModifier(),
mRenderer->drawRect(0.0f, offsetY, mSize.x, 1.0f * mRenderer->getScreenResolutionModifier(),
mMenuColorSeparators, mMenuColorSeparators, false, mOpacity, mDimming);
mRenderer->popClipRect();
}
@ -427,14 +360,14 @@ void ComponentList::updateElementPosition(const ComponentListRow& row)
yOffset += mRowHeight;
// Assumes updateElementSize has already been called.
float x {mHorizontalPadding / 2.0f};
float offsetX {mHorizontalPadding / 2.0f};
for (unsigned int i {0}; i < row.elements.size(); ++i) {
const auto comp = row.elements.at(i).component;
// Center vertically.
comp->setPosition(x, (mRowHeight - std::floor(comp->getSize().y)) / 2.0f + yOffset);
x += comp->getSize().x;
comp->setPosition(offsetX, (mRowHeight - std::floor(comp->getSize().y)) / 2.0f + yOffset);
offsetX += comp->getSize().x;
}
}
@ -444,13 +377,13 @@ void ComponentList::updateElementSize(const ComponentListRow& row)
std::vector<std::shared_ptr<GuiComponent>> resizeVec;
for (auto it = row.elements.cbegin(); it != row.elements.cend(); ++it) {
if (it->resize_width)
if (it->resizeWidth)
resizeVec.push_back(it->component);
else
width -= it->component->getSize().x;
}
// Redistribute the "unused" width equally among the components with resize_width set to true.
// Redistribute the "unused" width equally among the components if resizeWidth is set to true.
width = width / resizeVec.size();
for (auto it = resizeVec.cbegin(); it != resizeVec.cend(); ++it)
(*it)->setSize(width, (*it)->getSize().y);
@ -487,9 +420,9 @@ std::vector<HelpPrompt> ComponentList::getHelpPrompts()
return prompts;
}
bool ComponentList::moveCursor(int amt)
bool ComponentList::moveCursor(int amount)
{
bool ret {listInput(amt)};
const bool returnValue {listInput(amount)};
listInput(0);
return ret;
return returnValue;
}

View file

@ -12,41 +12,41 @@
#include "IList.h"
struct ComponentListElement {
ComponentListElement(const std::shared_ptr<GuiComponent>& cmp = nullptr,
bool resize_w = true,
bool inv = true)
: component(cmp)
, resize_width(resize_w)
, invert_when_selected(inv)
ComponentListElement(const std::shared_ptr<GuiComponent>& componentArg = nullptr,
bool resizeWidthArg = true,
bool invertWhenSelectedArg = true)
: component {componentArg}
, resizeWidth {resizeWidthArg}
, invertWhenSelected {invertWhenSelectedArg}
{
}
std::shared_ptr<GuiComponent> component;
bool resize_width;
bool invert_when_selected;
bool resizeWidth;
bool invertWhenSelected;
};
struct ComponentListRow {
std::vector<ComponentListElement> elements;
// The input handler is called when the user enters any input while this row is
// highlighted (including up/down).
// highlighted (including up/down navigation).
// Return false to let the list try to use it or true if the input has been consumed.
// If no input handler is supplied (input_handler == nullptr), the default behavior is
// to forward the input to the rightmost element in the currently selected row.
std::function<bool(InputConfig*, Input)> input_handler;
// If no input handler is supplied (inputHandler == nullptr), then the default behavior
// is to forward the input to the rightmost element in the currently selected row.
std::function<bool(InputConfig*, Input)> inputHandler;
void addElement(const std::shared_ptr<GuiComponent>& component,
bool resize_width,
bool invert_when_selected = true)
bool resizeWidth,
bool invertWhenSelected = true)
{
elements.push_back(ComponentListElement(component, resize_width, invert_when_selected));
elements.push_back(ComponentListElement(component, resizeWidth, invertWhenSelected));
}
// Utility function for making an input handler for "when the users presses A on this, do func".
// Utility function for making an input handler for an input event.
void makeAcceptInputHandler(const std::function<void()>& func)
{
input_handler = [func](InputConfig* config, Input input) -> bool {
inputHandler = [func](InputConfig* config, Input input) -> bool {
if (config->isMappedTo("a", input) && input.value != 0) {
func();
return true;
@ -78,26 +78,33 @@ public:
void onSizeChanged() override;
void onFocusGained() override { mFocused = true; }
void onFocusLost() override { mFocused = false; }
void onFocusLost() override
{
mFocused = false;
resetSelectedRow();
}
bool moveCursor(int amt);
bool moveCursor(int amount);
int getCursorId() const { return mCursor; }
const float getRowHeight() const { return mRowHeight; }
void setRowHeight(float height) { mRowHeight = height; }
const float getTotalRowHeight() const { return mRowHeight * mEntries.size(); }
// Horizontal looping for row content that doesn't fit on-screen.
void setLoopRows(bool state)
void resetSelectedRow()
{
stopLooping();
mLoopRows = state;
if (mEntries.size() > static_cast<size_t>(mCursor)) {
for (auto& comp : mEntries.at(mCursor).data.elements)
comp.component->resetComponent();
}
void stopLooping()
}
void setHorizontalScrolling(bool state)
{
mLoopOffset = 0;
mLoopOffset2 = 0;
mLoopTime = 0;
for (auto& entry : mEntries) {
for (auto& element : entry.data.elements)
element.component->setHorizontalScrolling(state);
}
}
void resetScrollIndicatorStatus()
@ -140,12 +147,6 @@ private:
float mSelectorBarOffset;
float mCameraOffset;
bool mLoopRows;
bool mLoopScroll;
int mLoopOffset;
int mLoopOffset2;
int mLoopTime;
std::function<void(CursorState state)> mCursorChangedCallback;
std::function<void(ScrollIndicator state, bool singleRowScroll)>
mScrollIndicatorChangedCallback;

View file

@ -263,7 +263,7 @@ void GIFAnimComponent::setAnimation(const std::string& path)
mAnimationStartTime = std::chrono::system_clock::now();
}
void GIFAnimComponent::resetFileAnimation()
void GIFAnimComponent::resetComponent()
{
mExternalPause = false;
mPlayCount = 0;

View file

@ -27,7 +27,7 @@ public:
void setAnimation(const std::string& path);
void setPauseAnimation(bool state) { mExternalPause = state; }
void resetFileAnimation() override;
void resetComponent() override;
void onSizeChanged() override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,

View file

@ -229,7 +229,7 @@ void LottieAnimComponent::setAnimation(const std::string& path)
mAnimationStartTime = std::chrono::system_clock::now();
}
void LottieAnimComponent::resetFileAnimation()
void LottieAnimComponent::resetComponent()
{
mExternalPause = false;
mPlayCount = 0;

View file

@ -39,7 +39,7 @@ public:
}
void setPauseAnimation(bool state) { mExternalPause = state; }
void resetFileAnimation() override;
void resetComponent() override;
void onSizeChanged() override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,

View file

@ -41,7 +41,7 @@ void ScrollableContainer::setAutoScroll(bool autoScroll)
if (autoScroll) {
mScrollDir = glm::vec2 {0.0f, 1.0f};
mAutoScrollDelay = static_cast<int>(mAutoScrollDelayConstant);
reset();
resetComponent();
}
else {
mScrollDir = glm::vec2 {};
@ -60,7 +60,7 @@ void ScrollableContainer::setScrollParameters(float autoScrollDelayConstant,
mAutoScrollSpeedConstant = AUTO_SCROLL_SPEED / glm::clamp(autoScrollSpeedConstant, 0.1f, 10.0f);
}
void ScrollableContainer::reset()
void ScrollableContainer::resetComponent()
{
mScrollPos = glm::vec2 {0.0f, 0.0f};
mAutoScrollResetAccumulator = 0;
@ -161,7 +161,7 @@ void ScrollableContainer::update(int deltaTime)
if (mWindow->isMediaViewerActive() || mWindow->isScreensaverActive() ||
!mWindow->getAllowTextScrolling()) {
if (mScrollPos != glm::vec2 {0.0f, 0.0f} && !mWindow->isLaunchScreenDisplayed())
reset();
resetComponent();
return;
}

View file

@ -34,7 +34,7 @@ public:
void setScrollParameters(float autoScrollDelayConstant,
float autoScrollResetDelayConstant,
float autoScrollSpeedConstant) override;
void reset();
void resetComponent() override;
void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,

View file

@ -33,14 +33,13 @@ TextComponent::TextComponent()
, mNoTopMargin {false}
, mSelectable {false}
, mVerticalAutoSizing {false}
, mLoopHorizontal {false}
, mLoopScroll {false}
, mLoopSpeed {0.0f}
, mLoopSpeedMultiplier {1.0f}
, mLoopDelay {1500.0f}
, mLoopOffset1 {0}
, mLoopOffset2 {0}
, mLoopTime {0}
, mHorizontalScrolling {false}
, mScrollSpeed {0.0f}
, mScrollSpeedMultiplier {1.0f}
, mScrollDelay {1500.0f}
, mScrollOffset1 {0.0f}
, mScrollOffset2 {0.0f}
, mScrollTime {0.0f}
{
}
@ -71,14 +70,13 @@ TextComponent::TextComponent(const std::string& text,
, mNoTopMargin {false}
, mSelectable {false}
, mVerticalAutoSizing {false}
, mLoopHorizontal {false}
, mLoopScroll {false}
, mLoopSpeed {0.0f}
, mLoopSpeedMultiplier {1.0f}
, mLoopDelay {1500.0f}
, mLoopOffset1 {0}
, mLoopOffset2 {0}
, mLoopTime {0}
, mHorizontalScrolling {false}
, mScrollSpeed {0.0f}
, mScrollSpeedMultiplier {1.0f}
, mScrollDelay {1500.0f}
, mScrollOffset1 {0.0f}
, mScrollOffset2 {0.0f}
, mScrollTime {0.0f}
{
setFont(font);
setColor(color);
@ -200,7 +198,7 @@ void TextComponent::render(const glm::mat4& parentTrans)
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0x0000FF33, 0x0000FF33);
}
if (mLoopHorizontal && mTextCache != nullptr) {
if (mHorizontalScrolling && mTextCache != nullptr) {
// Clip everything to be inside our bounds.
glm::vec3 dim {mSize.x, mSize.y, 0.0f};
dim.x = (trans[0].x * dim.x + trans[3].x) - trans[3].x;
@ -218,13 +216,12 @@ void TextComponent::render(const glm::mat4& parentTrans)
if (mTextCache->metrics.size.x < mSize.x) {
if (mHorizontalAlignment == Alignment::ALIGN_CENTER)
offsetX = static_cast<float>((mSize.x - mTextCache->metrics.size.x) / 2.0f);
offsetX = (mSize.x - mTextCache->metrics.size.x) / 2.0f;
else if (mHorizontalAlignment == Alignment::ALIGN_RIGHT)
offsetX = mSize.x - mTextCache->metrics.size.x;
}
trans = glm::translate(trans,
glm::vec3 {offsetX - static_cast<float>(mLoopOffset1), 0.0f, 0.0f});
trans = glm::translate(trans, glm::vec3 {offsetX - mScrollOffset1, 0.0f, 0.0f});
}
auto renderFunc = [this](glm::mat4 trans) {
@ -271,17 +268,18 @@ void TextComponent::render(const glm::mat4& parentTrans)
break;
}
case ALIGN_CENTER: {
mRenderer->drawRect(
mLoopHorizontal ? 0.0f : (mSize.x - mTextCache->metrics.size.x) / 2.0f,
0.0f, mTextCache->metrics.size.x, mTextCache->metrics.size.y,
0x00000033, 0x00000033);
mRenderer->drawRect(mHorizontalScrolling ?
0.0f :
(mSize.x - mTextCache->metrics.size.x) / 2.0f,
0.0f, mTextCache->metrics.size.x,
mTextCache->metrics.size.y, 0x00000033, 0x00000033);
break;
}
case ALIGN_RIGHT: {
mRenderer->drawRect(mLoopHorizontal ? 0.0f :
mSize.x - mTextCache->metrics.size.x,
0.0f, mTextCache->metrics.size.x,
mTextCache->metrics.size.y, 0x00000033, 0x00000033);
mRenderer->drawRect(
mHorizontalScrolling ? 0.0f : mSize.x - mTextCache->metrics.size.x,
0.0f, mTextCache->metrics.size.x, mTextCache->metrics.size.y,
0x00000033, 0x00000033);
break;
}
default: {
@ -295,21 +293,15 @@ void TextComponent::render(const glm::mat4& parentTrans)
renderFunc(trans);
if (mLoopHorizontal && mTextCache != nullptr && mTextCache->metrics.size.x > mSize.x) {
// Needed to avoid flickering when returning to the start position.
if (mLoopOffset1 == 0 && mLoopOffset2 == 0)
mLoopScroll = false;
// Render again if text has moved far enough for it to repeat.
if (mLoopOffset2 < 0 || (mLoopDelay != 0.0f && mLoopScroll)) {
mLoopScroll = true;
if (mHorizontalScrolling && mTextCache != nullptr && mTextCache->metrics.size.x > mSize.x) {
if (mScrollOffset2 < 0.0f) {
trans = glm::translate(parentTrans * getTransform(),
glm::vec3 {static_cast<float>(-mLoopOffset2), 0.0f, 0.0f});
mRenderer->setMatrix(trans);
glm::vec3 {-mScrollOffset2, 0.0f, 0.0f});
renderFunc(trans);
}
}
if (mLoopHorizontal && mTextCache != nullptr)
if (mHorizontalScrolling && mTextCache != nullptr)
mRenderer->popClipRect();
}
@ -320,7 +312,7 @@ void TextComponent::setValue(const std::string& value)
mThemeMetadata == "genre" || mThemeMetadata == "players")) {
setText(mDefaultValue);
}
else if (mLoopHorizontal) {
else if (mHorizontalScrolling) {
setText(Utils::String::replace(value, "\n", " "));
}
else {
@ -328,53 +320,52 @@ void TextComponent::setValue(const std::string& value)
}
}
void TextComponent::setHorizontalLooping(bool state)
void TextComponent::setHorizontalScrolling(bool state)
{
resetLooping();
mLoopHorizontal = state;
resetComponent();
mHorizontalScrolling = state;
if (mLoopHorizontal)
mLoopSpeed =
mFont->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x * 0.247f * mLoopSpeedMultiplier;
if (mHorizontalScrolling)
mScrollSpeed =
mFont->sizeText("ABCDEFGHIJKLMNOPQRSTUVWXYZ").x * 0.247f * mScrollSpeedMultiplier;
}
void TextComponent::update(int deltaTime)
{
if (mLoopHorizontal && mTextCache != nullptr) {
if (mHorizontalScrolling && mTextCache != nullptr) {
// Don't scroll if the media viewer or screensaver is active or if text scrolling
// is disabled;
if (mWindow->isMediaViewerActive() || mWindow->isScreensaverActive() ||
!mWindow->getAllowTextScrolling()) {
if (mLoopTime != 0 && !mWindow->isLaunchScreenDisplayed())
resetLooping();
if (mScrollTime != 0 && !mWindow->isLaunchScreenDisplayed())
resetComponent();
return;
}
assert(mLoopSpeed != 0.0f);
assert(mScrollSpeed != 0.0f);
mLoopOffset1 = 0;
mLoopOffset2 = 0;
mScrollOffset1 = 0.0f;
mScrollOffset2 = 0.0f;
if (mTextCache->metrics.size.x > mSize.x) {
// Loop the text.
const float scrollLength {mTextCache->metrics.size.x};
const float returnLength {mLoopSpeed * 1.5f / mLoopSpeedMultiplier};
const float scrollTime {(scrollLength * 1000.0f) / mLoopSpeed};
const float returnTime {(returnLength * 1000.0f) / mLoopSpeed};
const int maxTime {static_cast<int>(mLoopDelay + scrollTime + returnTime)};
const float returnLength {mScrollSpeed * 1.5f / mScrollSpeedMultiplier};
const float scrollTime {(scrollLength * 1000.0f) / mScrollSpeed};
const float returnTime {(returnLength * 1000.0f) / mScrollSpeed};
const float maxTime {mScrollDelay + scrollTime + returnTime};
mLoopTime += deltaTime;
while (mLoopTime > maxTime)
mLoopTime -= maxTime;
mScrollTime += deltaTime;
mLoopOffset1 = static_cast<int>(Utils::Math::loop(mLoopDelay, scrollTime + returnTime,
static_cast<float>(mLoopTime),
scrollLength + returnLength));
while (mScrollTime > maxTime)
mScrollTime -= maxTime;
if (mLoopOffset1 > (scrollLength - (mSize.x - returnLength)))
mLoopOffset2 = static_cast<int>(mLoopOffset1 - (scrollLength + returnLength));
else if (mLoopOffset2 < 0)
mLoopOffset2 = 0;
mScrollOffset1 = Utils::Math::loop(mScrollDelay, scrollTime + returnTime, mScrollTime,
scrollLength + returnLength);
if (mScrollOffset1 > (scrollLength - (mSize.x - returnLength)))
mScrollOffset2 = mScrollOffset1 - (scrollLength + returnLength);
else if (mScrollOffset2 < 0)
mScrollOffset2 = 0;
}
}
}
@ -418,7 +409,7 @@ void TextComponent::onTextChanged()
const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight};
if (mLoopHorizontal) {
if (mHorizontalScrolling) {
mTextCache = std::shared_ptr<TextCache>(font->buildTextCache(text, 0.0f, 0.0f, mColor));
}
else if (isMultiline && !isScrollable) {
@ -564,14 +555,14 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& containerType {elem->get<std::string>("containerType")};
if (containerType == "horizontal") {
if (elem->has("containerScrollSpeed")) {
mLoopSpeedMultiplier =
mScrollSpeedMultiplier =
glm::clamp(elem->get<float>("containerScrollSpeed"), 0.1f, 10.0f);
}
if (elem->has("containerStartDelay")) {
mLoopDelay =
mScrollDelay =
glm::clamp(elem->get<float>("containerStartDelay"), 0.0f, 10.0f) * 1000.0f;
}
mLoopHorizontal = true;
mHorizontalScrolling = true;
}
else if (containerType != "vertical") {
LOG(LogError) << "TextComponent: Invalid theme configuration, property "
@ -692,7 +683,7 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setFont(Font::getFromTheme(elem, properties, mFont, maxHeight, false));
// We need to do this after setting the font as the loop speed is calculated from its size.
if (mLoopHorizontal)
setHorizontalLooping(true);
// We need to do this after setting the font as the scroll speed is calculated from its size.
if (mHorizontalScrolling)
setHorizontalScrolling(true);
}

View file

@ -48,6 +48,7 @@ public:
void setRenderBackground(bool render) { mRenderBackground = render; }
void render(const glm::mat4& parentTrans) override;
void onFocusLost() override { resetComponent(); }
std::string getValue() const override { return mText; }
void setValue(const std::string& value) override;
@ -86,14 +87,16 @@ public:
return (mTextCache == nullptr ? 0 : mTextCache->metrics.maxGlyphHeight);
}
// Horizontal looping for single-line content that is too long to fit.
void setHorizontalLooping(bool state);
// Horizontal scrolling for single-line content that is too long to fit.
void setHorizontalScrolling(bool state) override;
void setHorizontalScrollingSpeedMultiplier(float speed) { mScrollSpeedMultiplier = speed; }
void setHorizontalScrollingDelay(float delay) { mScrollDelay = delay; }
void resetLooping()
void resetComponent()
{
mLoopOffset1 = 0;
mLoopOffset2 = 0;
mLoopTime = 0;
mScrollOffset1 = 0;
mScrollOffset2 = 0;
mScrollTime = 0;
}
void update(int deltaTime) override;
@ -144,14 +147,13 @@ private:
bool mSelectable;
bool mVerticalAutoSizing;
bool mLoopHorizontal;
bool mLoopScroll;
float mLoopSpeed;
float mLoopSpeedMultiplier;
float mLoopDelay;
int mLoopOffset1;
int mLoopOffset2;
int mLoopTime;
bool mHorizontalScrolling;
float mScrollSpeed;
float mScrollSpeedMultiplier;
float mScrollDelay;
float mScrollOffset1;
float mScrollOffset2;
float mScrollTime;
};
#endif // ES_CORE_COMPONENTS_TEXT_COMPONENT_H

View file

@ -107,7 +107,7 @@ GuiInputConfig::GuiInputConfig(InputConfig* target,
row.addElement(mapping, true);
mMappings.push_back(mapping);
row.input_handler = [this, i, mapping](InputConfig* config, Input input) -> bool {
row.inputHandler = [this, i, mapping](InputConfig* config, Input input) -> bool {
// Ignore input not from our target device.
if (config != mTargetConfig)
return false;