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,8 +172,8 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
row.addElement(ed, false, true); row.addElement(ed, false, true);
// Pass input to the actual RatingComponent instead of the spacer. // 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); std::placeholders::_2);
break; break;
} }
case MD_DATE: { case MD_DATE: {
@ -187,8 +187,8 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
row.addElement(ed, false); row.addElement(ed, false);
// Pass input to the actual DateTimeEditComponent instead of the spacer. // 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); std::placeholders::_2);
break; break;
} }
case MD_CONTROLLER: { case MD_CONTROLLER: {

View file

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

View file

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

View file

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

View file

@ -310,8 +310,10 @@ public:
virtual void pauseViewVideos() {} virtual void pauseViewVideos() {}
virtual void muteViewVideos() {} virtual void muteViewVideos() {}
// For Lottie animations. // Used to reset various components like text scrolling, animations etc.
virtual void resetFileAnimation() {}; virtual void resetComponent() {}
// Used by TextComponent.
virtual void setHorizontalScrolling(bool state) {}
// Default implementation just handles <pos> and <size> tags as normalized float pairs. // 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. // 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())} , mRowHeight {std::round(Font::get(FONT_SIZE_MEDIUM)->getHeight())}
, mSelectorBarOffset {0.0f} , mSelectorBarOffset {0.0f}
, mCameraOffset {0.0f} , mCameraOffset {0.0f}
, mLoopRows {false}
, mLoopScroll {false}
, mLoopOffset {0}
, mLoopOffset2 {0}
, mLoopTime {0}
, mScrollIndicatorStatus {SCROLL_NONE} , mScrollIndicatorStatus {SCROLL_NONE}
{ {
// Adjust the padding relative to the aspect ratio and screen resolution to make it look // 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. // Give it to the current row's input handler.
if (mEntries.at(mCursor).data.input_handler) { if (mEntries.at(mCursor).data.inputHandler) {
if (mEntries.at(mCursor).data.input_handler(config, input)) if (mEntries.at(mCursor).data.inputHandler(config, input))
return true; return true;
} }
else { else {
@ -132,12 +127,6 @@ bool ComponentList::input(InputConfig* config, Input input)
void ComponentList::update(int deltaTime) void ComponentList::update(int deltaTime)
{ {
if (!mFocused && mLoopRows) {
mLoopOffset = 0;
mLoopOffset2 = 0;
mLoopTime = 0;
}
// Scroll indicator logic, used by ScrollIndicatorComponent. // Scroll indicator logic, used by ScrollIndicatorComponent.
bool scrollIndicatorChanged {false}; bool scrollIndicatorChanged {false};
@ -167,39 +156,11 @@ void ComponentList::update(int deltaTime)
listUpdate(deltaTime); listUpdate(deltaTime);
if (size()) { if (mFocused && size()) {
float rowWidth {0.0f};
// Update our currently selected row. // Update our currently selected row.
for (auto it = mEntries.at(mCursor).data.elements.cbegin(); for (auto it = mEntries.at(mCursor).data.elements.cbegin();
it != mEntries.at(mCursor).data.elements.cend(); ++it) { it != mEntries.at(mCursor).data.elements.cend(); ++it) {
it->component->update(deltaTime); 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; mSetupCompleted = true;
if (mLoopRows) {
mLoopOffset = 0;
mLoopOffset2 = 0;
mLoopTime = 0;
}
// Update the selector bar position. // Update the selector bar position.
// In the future this might be animated. // In the future this might be animated.
mSelectorBarOffset = 0; mSelectorBarOffset = 0;
@ -299,10 +254,9 @@ void ComponentList::render(const glm::mat4& parentTrans)
mRenderer->pushClipRect(glm::ivec2 {clipRectPosX, clipRectPosY}, mRenderer->pushClipRect(glm::ivec2 {clipRectPosX, clipRectPosY},
glm::ivec2 {clipRectSizeX, clipRectSizeY}); glm::ivec2 {clipRectSizeX, clipRectSizeY});
// Scroll the camera. // Move camera the scroll distance.
trans = glm::translate(trans, glm::vec3 {0.0f, -mCameraOffset, 0.0f}); trans = glm::translate(trans, glm::vec3 {0.0f, -mCameraOffset, 0.0f});
glm::mat4 loopTrans {trans};
const bool darkColorScheme {Settings::getInstance()->getString("MenuColorScheme") != "light"}; const bool darkColorScheme {Settings::getInstance()->getString("MenuColorScheme") != "light"};
// Draw selector bar if we're using the dark color scheme. // 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; std::vector<GuiComponent*> drawAfterCursor;
bool drawAll {false}; bool drawAll {false};
for (size_t i {0}; i < mEntries.size(); ++i) { 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); auto& entry = mEntries.at(i);
drawAll = !mFocused || i != static_cast<unsigned int>(mCursor); drawAll = !mFocused || i != static_cast<unsigned int>(mCursor);
for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); ++it) { for (auto it = entry.data.elements.cbegin(); it != entry.data.elements.cend(); ++it) {
if (drawAll || it->invert_when_selected) { if (drawAll || it->invertWhenSelected) {
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);
}
};
// For the row where the cursor is at, we want to remove any hue from the // 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 // font or image before inverting, as it would otherwise lead to an ugly
// inverted color (e.g. red inverting to a green hue). // 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)}; unsigned char byteBlue {static_cast<unsigned char>(origColor >> 8 & 0xFF)};
// If it's neutral, just proceed with normal rendering. // If it's neutral, just proceed with normal rendering.
if (byteRed == byteGreen && byteGreen == byteBlue) { if (byteRed == byteGreen && byteGreen == byteBlue) {
renderLoopFunc(); it->component->render(trans);
} }
else { else {
if (isTextComponent) if (isTextComponent)
it->component->setColor(mMenuColorPrimary); it->component->setColor(mMenuColorPrimary);
else else
it->component->setColorShift(mMenuColorPrimary); it->component->setColorShift(mMenuColorPrimary);
renderLoopFunc(); it->component->render(trans);
// Revert to the original color after rendering. // Revert to the original color after rendering.
if (isTextComponent) if (isTextComponent)
it->component->setColor(origColor); it->component->setColor(origColor);
@ -383,7 +317,6 @@ void ComponentList::render(const glm::mat4& parentTrans)
} }
} }
// Custom rendering.
mRenderer->setMatrix(trans); mRenderer->setMatrix(trans);
// Draw selector bar if we're using the light color scheme. // Draw selector bar if we're using the light color scheme.
@ -408,14 +341,14 @@ void ComponentList::render(const glm::mat4& parentTrans)
} }
// Draw separators. // Draw separators.
float y {0.0f}; float offsetY {0.0f};
for (unsigned int i {0}; i < mEntries.size(); ++i) { 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); 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); mMenuColorSeparators, mMenuColorSeparators, false, mOpacity, mDimming);
mRenderer->popClipRect(); mRenderer->popClipRect();
} }
@ -427,14 +360,14 @@ void ComponentList::updateElementPosition(const ComponentListRow& row)
yOffset += mRowHeight; yOffset += mRowHeight;
// Assumes updateElementSize has already been called. // 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) { for (unsigned int i {0}; i < row.elements.size(); ++i) {
const auto comp = row.elements.at(i).component; const auto comp = row.elements.at(i).component;
// Center vertically. // Center vertically.
comp->setPosition(x, (mRowHeight - std::floor(comp->getSize().y)) / 2.0f + yOffset); comp->setPosition(offsetX, (mRowHeight - std::floor(comp->getSize().y)) / 2.0f + yOffset);
x += comp->getSize().x; offsetX += comp->getSize().x;
} }
} }
@ -444,13 +377,13 @@ void ComponentList::updateElementSize(const ComponentListRow& row)
std::vector<std::shared_ptr<GuiComponent>> resizeVec; std::vector<std::shared_ptr<GuiComponent>> resizeVec;
for (auto it = row.elements.cbegin(); it != row.elements.cend(); ++it) { for (auto it = row.elements.cbegin(); it != row.elements.cend(); ++it) {
if (it->resize_width) if (it->resizeWidth)
resizeVec.push_back(it->component); resizeVec.push_back(it->component);
else else
width -= it->component->getSize().x; 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(); width = width / resizeVec.size();
for (auto it = resizeVec.cbegin(); it != resizeVec.cend(); ++it) for (auto it = resizeVec.cbegin(); it != resizeVec.cend(); ++it)
(*it)->setSize(width, (*it)->getSize().y); (*it)->setSize(width, (*it)->getSize().y);
@ -487,9 +420,9 @@ std::vector<HelpPrompt> ComponentList::getHelpPrompts()
return prompts; return prompts;
} }
bool ComponentList::moveCursor(int amt) bool ComponentList::moveCursor(int amount)
{ {
bool ret {listInput(amt)}; const bool returnValue {listInput(amount)};
listInput(0); listInput(0);
return ret; return returnValue;
} }

View file

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

View file

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

View file

@ -27,7 +27,7 @@ public:
void setAnimation(const std::string& path); void setAnimation(const std::string& path);
void setPauseAnimation(bool state) { mExternalPause = state; } void setPauseAnimation(bool state) { mExternalPause = state; }
void resetFileAnimation() override; void resetComponent() override;
void onSizeChanged() override; void onSizeChanged() override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, 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(); mAnimationStartTime = std::chrono::system_clock::now();
} }
void LottieAnimComponent::resetFileAnimation() void LottieAnimComponent::resetComponent()
{ {
mExternalPause = false; mExternalPause = false;
mPlayCount = 0; mPlayCount = 0;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -107,7 +107,7 @@ GuiInputConfig::GuiInputConfig(InputConfig* target,
row.addElement(mapping, true); row.addElement(mapping, true);
mMappings.push_back(mapping); 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. // Ignore input not from our target device.
if (config != mTargetConfig) if (config != mTargetConfig)
return false; return false;