mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-01-18 07:05:39 +00:00
Added basic GridComponent functionality and integration.
This commit is contained in:
parent
12f2142c03
commit
48111ce5e4
|
@ -568,15 +568,19 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
|
|||
|
||||
auto theme = mRoot->getSystem()->getTheme();
|
||||
std::string name;
|
||||
std::string carouselItemType;
|
||||
std::string carouselDefaultItem;
|
||||
std::string defaultItem;
|
||||
|
||||
if (mCarousel != nullptr) {
|
||||
carouselItemType = mCarousel->getItemType();
|
||||
carouselDefaultItem = mCarousel->getDefaultItem();
|
||||
if (!ResourceManager::getInstance().fileExists(carouselDefaultItem))
|
||||
carouselDefaultItem = "";
|
||||
defaultItem = mCarousel->getDefaultItem();
|
||||
if (!ResourceManager::getInstance().fileExists(defaultItem))
|
||||
defaultItem = "";
|
||||
}
|
||||
else if (mGrid != nullptr) {
|
||||
defaultItem = mGrid->getDefaultItem();
|
||||
if (!ResourceManager::getInstance().fileExists(defaultItem))
|
||||
defaultItem = "";
|
||||
}
|
||||
|
||||
if (files.size() > 0) {
|
||||
for (auto it = files.cbegin(); it != files.cend(); ++it) {
|
||||
|
||||
|
@ -593,8 +597,6 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
|
|||
}
|
||||
|
||||
if (mCarousel != nullptr) {
|
||||
assert(carouselItemType != "");
|
||||
|
||||
CarouselComponent<FileData*>::Entry carouselEntry;
|
||||
carouselEntry.name = (*it)->getName();
|
||||
carouselEntry.object = *it;
|
||||
|
@ -606,13 +608,29 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
|
|||
else if (letterCase == LetterCase::CAPITALIZED)
|
||||
carouselEntry.name = Utils::String::toCapitalized(carouselEntry.name);
|
||||
|
||||
if (carouselDefaultItem != "")
|
||||
carouselEntry.data.defaultItemPath = carouselDefaultItem;
|
||||
if (defaultItem != "")
|
||||
carouselEntry.data.defaultItemPath = defaultItem;
|
||||
|
||||
mCarousel->addEntry(carouselEntry, theme);
|
||||
}
|
||||
else if (mGrid != nullptr) {
|
||||
GridComponent<FileData*>::Entry gridEntry;
|
||||
gridEntry.name = (*it)->getName();
|
||||
gridEntry.object = *it;
|
||||
|
||||
if (mTextList != nullptr) {
|
||||
if (letterCase == LetterCase::UPPERCASE)
|
||||
gridEntry.name = Utils::String::toUpper(gridEntry.name);
|
||||
else if (letterCase == LetterCase::LOWERCASE)
|
||||
gridEntry.name = Utils::String::toLower(gridEntry.name);
|
||||
else if (letterCase == LetterCase::CAPITALIZED)
|
||||
gridEntry.name = Utils::String::toCapitalized(gridEntry.name);
|
||||
|
||||
if (defaultItem != "")
|
||||
gridEntry.data.defaultItemPath = defaultItem;
|
||||
|
||||
mGrid->addEntry(gridEntry, theme);
|
||||
}
|
||||
else if (mTextList != nullptr) {
|
||||
TextListComponent<FileData*>::Entry textListEntry;
|
||||
std::string indicators {mTextList->getIndicators()};
|
||||
std::string collectionIndicators {mTextList->getCollectionIndicators()};
|
||||
|
@ -717,13 +735,20 @@ void GamelistBase::addPlaceholder(FileData* firstEntry)
|
|||
textListEntry.data.entryType = TextListEntryType::SECONDARY;
|
||||
mTextList->addEntry(textListEntry);
|
||||
}
|
||||
if (mCarousel != nullptr) {
|
||||
else if (mCarousel != nullptr) {
|
||||
CarouselComponent<FileData*>::Entry carouselEntry;
|
||||
carouselEntry.name = placeholder->getName();
|
||||
letterCaseFunc(carouselEntry.name);
|
||||
carouselEntry.object = placeholder;
|
||||
mCarousel->addEntry(carouselEntry, mRoot->getSystem()->getTheme());
|
||||
}
|
||||
else if (mGrid != nullptr) {
|
||||
GridComponent<FileData*>::Entry gridEntry;
|
||||
gridEntry.name = placeholder->getName();
|
||||
letterCaseFunc(gridEntry.name);
|
||||
gridEntry.object = placeholder;
|
||||
mGrid->addEntry(gridEntry, mRoot->getSystem()->getTheme());
|
||||
}
|
||||
}
|
||||
|
||||
void GamelistBase::generateFirstLetterIndex(const std::vector<FileData*>& files)
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "components/TextComponent.h"
|
||||
#include "components/VideoFFmpegComponent.h"
|
||||
#include "components/primary/CarouselComponent.h"
|
||||
#include "components/primary/GridComponent.h"
|
||||
#include "components/primary/TextListComponent.h"
|
||||
|
||||
#include <stack>
|
||||
|
@ -90,6 +91,7 @@ protected:
|
|||
|
||||
FileData* mRoot;
|
||||
std::unique_ptr<CarouselComponent<FileData*>> mCarousel;
|
||||
std::unique_ptr<GridComponent<FileData*>> mGrid;
|
||||
std::unique_ptr<TextListComponent<FileData*>> mTextList;
|
||||
PrimaryComponent<FileData*>* mPrimary;
|
||||
|
||||
|
|
|
@ -114,15 +114,24 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
|||
|
||||
if (mTheme->hasView("gamelist")) {
|
||||
for (auto& element : mTheme->getViewElements("gamelist").elements) {
|
||||
if (element.second.type == "textlist" || element.second.type == "carousel") {
|
||||
if (element.second.type == "carousel" && mTextList != nullptr) {
|
||||
if (element.second.type == "carousel" || element.second.type == "grid" ||
|
||||
element.second.type == "textlist") {
|
||||
if (element.second.type == "carousel" &&
|
||||
(mGrid != nullptr || mTextList != nullptr)) {
|
||||
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
|
||||
<< "defined, skipping <carousel> configuration entry";
|
||||
<< "defined, skipping carousel configuration entry";
|
||||
continue;
|
||||
}
|
||||
if (element.second.type == "textlist" && mCarousel != nullptr) {
|
||||
if (element.second.type == "grid" &&
|
||||
(mCarousel != nullptr || mTextList != nullptr)) {
|
||||
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
|
||||
<< "defined, skipping <textlist> configuration entry";
|
||||
<< "defined, skipping grid configuration entry";
|
||||
continue;
|
||||
}
|
||||
if (element.second.type == "textlist" &&
|
||||
(mCarousel != nullptr || mGrid != nullptr)) {
|
||||
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
|
||||
<< "defined, skipping textlist configuration entry";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -154,9 +163,10 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
|||
mCarousel->setItemType(itemType);
|
||||
}
|
||||
else {
|
||||
LOG(LogWarning)
|
||||
<< "GamelistView::onThemeChanged(): Invalid theme configuration, "
|
||||
"<itemType> property defined as \""
|
||||
LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme "
|
||||
"configuration, carousel property \"itemType\" "
|
||||
"for element \""
|
||||
<< element.first.substr(9) << "\" defined as \""
|
||||
<< itemType << "\"";
|
||||
mCarousel->setItemType("marquee");
|
||||
}
|
||||
|
@ -174,6 +184,40 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
|||
mPrimary->applyTheme(theme, "gamelist", element.first, ALL);
|
||||
addChild(mPrimary);
|
||||
}
|
||||
if (element.second.type == "grid") {
|
||||
if (mGrid == nullptr) {
|
||||
mGrid = std::make_unique<GridComponent<FileData*>>();
|
||||
if (element.second.has("itemType")) {
|
||||
const std::string itemType {element.second.get<std::string>("itemType")};
|
||||
if (itemType == "marquee" || itemType == "cover" ||
|
||||
itemType == "backcover" || itemType == "3dbox" ||
|
||||
itemType == "physicalmedia" || itemType == "screenshot" ||
|
||||
itemType == "titlescreen" || itemType == "miximage" ||
|
||||
itemType == "fanart" || itemType == "none") {
|
||||
mGrid->setItemType(itemType);
|
||||
}
|
||||
else {
|
||||
LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme "
|
||||
"configuration, grid property \"itemType\" "
|
||||
"for element \""
|
||||
<< element.first.substr(5) << "\" defined as \""
|
||||
<< itemType << "\"";
|
||||
mGrid->setItemType("marquee");
|
||||
}
|
||||
}
|
||||
else {
|
||||
mGrid->setItemType("marquee");
|
||||
}
|
||||
if (element.second.has("defaultItem"))
|
||||
mGrid->setDefaultItem(element.second.get<std::string>("defaultItem"));
|
||||
mPrimary = mGrid.get();
|
||||
}
|
||||
mPrimary->setCursorChangedCallback(
|
||||
[&](const CursorState& state) { updateView(state); });
|
||||
mPrimary->setDefaultZIndex(50.0f);
|
||||
mPrimary->applyTheme(theme, "gamelist", element.first, ALL);
|
||||
addChild(mPrimary);
|
||||
}
|
||||
if (element.second.type == "image") {
|
||||
// If this is the startup system, then forceload the images to avoid texture pop-in.
|
||||
if (isStartupSystem)
|
||||
|
@ -331,6 +375,9 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
|||
mCarousel->getType() == CarouselComponent<FileData*>::CarouselType::HORIZONTAL_WHEEL)
|
||||
mLeftRightAvailable = false;
|
||||
}
|
||||
else if (mGrid != nullptr) {
|
||||
mLeftRightAvailable = false;
|
||||
}
|
||||
|
||||
for (auto& video : mStaticVideoComponents) {
|
||||
if (video->hasStaticVideo())
|
||||
|
|
|
@ -145,9 +145,6 @@ bool SystemView::input(InputConfig* config, Input input)
|
|||
|
||||
void SystemView::update(int deltaTime)
|
||||
{
|
||||
if (!mPrimary->isAnimationPlaying(0))
|
||||
mMaxFade = false;
|
||||
|
||||
mPrimary->update(deltaTime);
|
||||
|
||||
for (auto& video : mSystemElements[mPrimary->getCursor()].videoComponents) {
|
||||
|
@ -295,19 +292,24 @@ void SystemView::onCursorChanged(const CursorState& state)
|
|||
|
||||
Animation* anim;
|
||||
|
||||
float animTime {380.0f};
|
||||
float timeDiff {1.0f};
|
||||
|
||||
// If startPos is inbetween two positions then reduce the time slightly as the distance will
|
||||
// be shorter meaning the animation would play for too long if not compensated for.
|
||||
if (scrollVelocity == 1)
|
||||
timeDiff = endPos - startPos;
|
||||
else if (scrollVelocity == -1)
|
||||
timeDiff = startPos - endPos;
|
||||
|
||||
if (timeDiff != 1.0f)
|
||||
animTime =
|
||||
glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), 200.0f, animTime);
|
||||
|
||||
if (transitionStyle == "fade") {
|
||||
float startFade {mFadeOpacity};
|
||||
anim = new LambdaAnimation(
|
||||
[this, startFade, startPos, endPos, posMax](float t) {
|
||||
t -= 1;
|
||||
float f {glm::mix(startPos, endPos, t * t * t + 1.0f)};
|
||||
if (f < 0.0f)
|
||||
f += posMax;
|
||||
if (f >= posMax)
|
||||
f -= posMax;
|
||||
|
||||
t += 1;
|
||||
|
||||
if (t < 0.3f)
|
||||
mFadeOpacity =
|
||||
glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f));
|
||||
|
@ -319,7 +321,7 @@ void SystemView::onCursorChanged(const CursorState& state)
|
|||
if (t > 0.5f)
|
||||
mCamOffset = endPos;
|
||||
|
||||
if (t >= 0.7f && t != 1.0f)
|
||||
if (mNavigated && t >= 0.7f && t != 1.0f)
|
||||
mMaxFade = true;
|
||||
|
||||
// Update the game count when the entire animation has been completed.
|
||||
|
@ -328,15 +330,16 @@ void SystemView::onCursorChanged(const CursorState& state)
|
|||
updateGameCount();
|
||||
}
|
||||
},
|
||||
500);
|
||||
static_cast<int>(animTime * 1.3f));
|
||||
}
|
||||
else if (transitionStyle == "slide") {
|
||||
mUpdatedGameCount = false;
|
||||
anim = new LambdaAnimation(
|
||||
[this, startPos, endPos, posMax](float t) {
|
||||
t -= 1;
|
||||
float f {glm::mix(startPos, endPos, t * t * t + 1.0f)};
|
||||
if (f < 0.0f)
|
||||
// Non-linear interpolation.
|
||||
t = 1.0f - (1.0f - t) * (1.0f - t);
|
||||
float f {(endPos * t) + (startPos * (1.0f - t))};
|
||||
if (f < 0)
|
||||
f += posMax;
|
||||
if (f >= posMax)
|
||||
f -= posMax;
|
||||
|
@ -362,23 +365,13 @@ void SystemView::onCursorChanged(const CursorState& state)
|
|||
updateGameCount();
|
||||
}
|
||||
},
|
||||
500);
|
||||
static_cast<int>(animTime));
|
||||
}
|
||||
else {
|
||||
// Instant.
|
||||
updateGameCount();
|
||||
anim = new LambdaAnimation(
|
||||
[this, startPos, endPos, posMax](float t) {
|
||||
t -= 1;
|
||||
float f {glm::mix(startPos, endPos, t * t * t + 1.0f)};
|
||||
if (f < 0.0f)
|
||||
f += posMax;
|
||||
if (f >= posMax)
|
||||
f -= posMax;
|
||||
|
||||
mCamOffset = endPos;
|
||||
},
|
||||
500);
|
||||
[this, startPos, endPos, posMax](float t) { mCamOffset = endPos; }, animTime);
|
||||
}
|
||||
|
||||
setAnimation(anim, 0, nullptr, false, 0);
|
||||
|
@ -458,17 +451,27 @@ void SystemView::populate()
|
|||
ThemeFlags::ALL);
|
||||
elements.gameSelectors.back()->setNeedsRefresh();
|
||||
}
|
||||
if (element.second.type == "textlist" || element.second.type == "carousel") {
|
||||
if (element.second.type == "carousel" && mTextList != nullptr) {
|
||||
if (element.second.type == "carousel" || element.second.type == "grid" ||
|
||||
element.second.type == "textlist") {
|
||||
if (element.second.type == "carousel" &&
|
||||
(mGrid != nullptr || mTextList != nullptr)) {
|
||||
LOG(LogWarning)
|
||||
<< "SystemView::populate(): Multiple primary components "
|
||||
<< "defined, skipping <carousel> configuration entry";
|
||||
<< "defined, skipping carousel configuration entry";
|
||||
continue;
|
||||
}
|
||||
if (element.second.type == "textlist" && mCarousel != nullptr) {
|
||||
if (element.second.type == "grid" &&
|
||||
(mCarousel != nullptr || mTextList != nullptr)) {
|
||||
LOG(LogWarning)
|
||||
<< "SystemView::populate(): Multiple primary components "
|
||||
<< "defined, skipping <textlist> configuration entry";
|
||||
<< "defined, skipping grid configuration entry";
|
||||
continue;
|
||||
}
|
||||
if (element.second.type == "textlist" &&
|
||||
(mCarousel != nullptr || mGrid != nullptr)) {
|
||||
LOG(LogWarning)
|
||||
<< "SystemView::populate(): Multiple primary components "
|
||||
<< "defined, skipping textlist configuration entry";
|
||||
continue;
|
||||
}
|
||||
if (element.second.type == "carousel" && mCarousel == nullptr) {
|
||||
|
@ -476,6 +479,11 @@ void SystemView::populate()
|
|||
mPrimary = mCarousel.get();
|
||||
mPrimaryType = PrimaryType::CAROUSEL;
|
||||
}
|
||||
else if (element.second.type == "grid" && mGrid == nullptr) {
|
||||
mGrid = std::make_unique<GridComponent<SystemData*>>();
|
||||
mPrimary = mGrid.get();
|
||||
mPrimaryType = PrimaryType::GRID;
|
||||
}
|
||||
else if (element.second.type == "textlist" && mTextList == nullptr) {
|
||||
mTextList = std::make_unique<TextListComponent<SystemData*>>();
|
||||
mPrimary = mTextList.get();
|
||||
|
@ -497,7 +505,7 @@ void SystemView::populate()
|
|||
anim->setPauseAnimation(true);
|
||||
}
|
||||
});
|
||||
if (mCarousel != nullptr) {
|
||||
if (mCarousel != nullptr || mGrid != nullptr) {
|
||||
if (element.second.has("staticItem"))
|
||||
itemPath = element.second.get<std::string>("staticItem");
|
||||
if (element.second.has("defaultItem"))
|
||||
|
@ -675,6 +683,15 @@ void SystemView::populate()
|
|||
entry.data.defaultItemPath = defaultItemPath;
|
||||
mCarousel->addEntry(entry, theme);
|
||||
}
|
||||
else if (mGrid != nullptr) {
|
||||
GridComponent<SystemData*>::Entry entry;
|
||||
entry.name = it->getFullName();
|
||||
letterCaseFunc(entry.name);
|
||||
entry.object = it;
|
||||
entry.data.itemPath = itemPath;
|
||||
entry.data.defaultItemPath = defaultItemPath;
|
||||
mGrid->addEntry(entry, theme);
|
||||
}
|
||||
else if (mTextList != nullptr) {
|
||||
TextListComponent<SystemData*>::Entry entry;
|
||||
entry.name = it->getFullName();
|
||||
|
@ -1268,6 +1285,10 @@ void SystemView::renderElements(const glm::mat4& parentTrans, bool abovePrimary)
|
|||
elementTrans,
|
||||
glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
|
||||
}
|
||||
else if (mGrid != nullptr) {
|
||||
elementTrans = glm::translate(
|
||||
elementTrans, glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
|
||||
}
|
||||
else if (mTextList != nullptr) {
|
||||
elementTrans = glm::translate(
|
||||
elementTrans, glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
|
||||
|
|
|
@ -126,6 +126,7 @@ private:
|
|||
|
||||
Renderer* mRenderer;
|
||||
std::unique_ptr<CarouselComponent<SystemData*>> mCarousel;
|
||||
std::unique_ptr<GridComponent<SystemData*>> mGrid;
|
||||
std::unique_ptr<TextListComponent<SystemData*>> mTextList;
|
||||
std::unique_ptr<TextComponent> mLegacySystemInfo;
|
||||
std::vector<SystemViewElements> mSystemElements;
|
||||
|
|
|
@ -322,6 +322,40 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
|
|||
{"fadeAbovePrimary", BOOLEAN},
|
||||
{"zIndex", FLOAT},
|
||||
{"legacyZIndexMode", STRING}}}, // For backward compatibility with legacy themes.
|
||||
{"grid",
|
||||
{{"pos", NORMALIZED_PAIR},
|
||||
{"size", NORMALIZED_PAIR},
|
||||
{"origin", NORMALIZED_PAIR},
|
||||
{"columns", UNSIGNED_INTEGER},
|
||||
{"staticItem", PATH},
|
||||
{"itemType", STRING},
|
||||
{"defaultItem", PATH},
|
||||
{"itemSize", NORMALIZED_PAIR},
|
||||
{"itemScale", FLOAT},
|
||||
{"itemSpacing", NORMALIZED_PAIR},
|
||||
{"itemTransitions", STRING},
|
||||
{"itemHorizontalAlignment", STRING},
|
||||
{"itemVerticalAlignment", STRING},
|
||||
{"horizontalMargins", NORMALIZED_PAIR},
|
||||
{"verticalMargins", NORMALIZED_PAIR},
|
||||
{"horizontalOffset", FLOAT},
|
||||
{"verticalOffset", FLOAT},
|
||||
{"unfocusedItemOpacity", FLOAT},
|
||||
{"edgeScaleInwards", BOOLEAN},
|
||||
{"color", COLOR},
|
||||
{"colorEnd", COLOR},
|
||||
{"gradientType", STRING},
|
||||
{"text", STRING},
|
||||
{"textColor", COLOR},
|
||||
{"textBackgroundColor", COLOR},
|
||||
{"fontPath", PATH},
|
||||
{"fontSize", FLOAT},
|
||||
{"letterCase", STRING},
|
||||
{"letterCaseCollections", STRING},
|
||||
{"letterCaseGroupedCollections", STRING},
|
||||
{"lineSpacing", FLOAT},
|
||||
{"fadeAbovePrimary", BOOLEAN},
|
||||
{"zIndex", FLOAT}}},
|
||||
{"textlist",
|
||||
{{"pos", NORMALIZED_PAIR},
|
||||
{"size", NORMALIZED_PAIR},
|
||||
|
|
|
@ -73,6 +73,7 @@ protected:
|
|||
const ScrollTierList& mTierList;
|
||||
const ListLoopType mLoopType;
|
||||
int mCursor;
|
||||
int mLastCursor;
|
||||
int mScrollTier;
|
||||
int mScrollVelocity;
|
||||
int mScrollTierAccumulator;
|
||||
|
@ -88,6 +89,7 @@ public:
|
|||
, mTierList {tierList}
|
||||
, mLoopType {loopType}
|
||||
, mCursor {0}
|
||||
, mLastCursor {0}
|
||||
, mScrollTier {0}
|
||||
, mScrollVelocity {0}
|
||||
, mScrollTierAccumulator {0}
|
||||
|
@ -213,6 +215,7 @@ protected:
|
|||
|
||||
bool listFirstRow()
|
||||
{
|
||||
mLastCursor = mCursor;
|
||||
mCursor = 0;
|
||||
onCursorChanged(CursorState::CURSOR_STOPPED);
|
||||
onScroll();
|
||||
|
@ -221,6 +224,7 @@ protected:
|
|||
|
||||
bool listLastRow()
|
||||
{
|
||||
mLastCursor = mCursor;
|
||||
mCursor = static_cast<int>(mEntries.size()) - 1;
|
||||
onCursorChanged(CursorState::CURSOR_STOPPED);
|
||||
onScroll();
|
||||
|
@ -323,6 +327,8 @@ protected:
|
|||
if (mScrollVelocity == 0 || size() < 2)
|
||||
return;
|
||||
|
||||
mLastCursor = mCursor;
|
||||
|
||||
int cursor {mCursor + amt};
|
||||
int absAmt {amt < 0 ? -amt : amt};
|
||||
|
||||
|
|
|
@ -1327,7 +1327,7 @@ template <typename T> void CarouselComponent<T>::onCursorChanged(const CursorSta
|
|||
mPositiveDirection = false;
|
||||
|
||||
mEntryCamTarget = endPos;
|
||||
float animTime {380};
|
||||
float animTime {380.0f};
|
||||
float timeDiff {1.0f};
|
||||
|
||||
// If startPos is inbetween two positions then reduce the time slightly as the distance will
|
||||
|
@ -1338,7 +1338,8 @@ template <typename T> void CarouselComponent<T>::onCursorChanged(const CursorSta
|
|||
timeDiff = startPos - endPos;
|
||||
|
||||
if (timeDiff != 1.0f)
|
||||
animTime = glm::clamp(std::fabs(glm::mix(0.0f, 380.0f, timeDiff * 1.5f)), 200.0f, 380.0f);
|
||||
animTime =
|
||||
glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), 200.0f, animTime);
|
||||
|
||||
Animation* anim {new LambdaAnimation(
|
||||
[this, startPos, endPos, posMax](float t) {
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
// Grid, usable in both the system and gamelist views.
|
||||
//
|
||||
|
||||
#ifndef ES_CORE_COMPONENTS_GRID_COMPONENT_H
|
||||
#define ES_CORE_COMPONENTS_GRID_COMPONENT_H
|
||||
#ifndef ES_CORE_COMPONENTS_PRIMARY_GRID_COMPONENT_H
|
||||
#define ES_CORE_COMPONENTS_PRIMARY_GRID_COMPONENT_H
|
||||
|
||||
#include "components/IList.h"
|
||||
#include "components/primary/PrimaryComponent.h"
|
||||
|
@ -26,10 +26,19 @@ class GridComponent : public PrimaryComponent<T>, protected IList<GridEntry, T>
|
|||
protected:
|
||||
using List::mCursor;
|
||||
using List::mEntries;
|
||||
using List::mLastCursor;
|
||||
using List::mScrollVelocity;
|
||||
using List::mSize;
|
||||
|
||||
public:
|
||||
using Entry = typename IList<GridEntry, T>::Entry;
|
||||
|
||||
GridComponent();
|
||||
|
||||
void addEntry(Entry& entry, const std::shared_ptr<ThemeData>& theme);
|
||||
void updateEntry(Entry& entry, const std::shared_ptr<ThemeData>& theme);
|
||||
void onDemandTextureLoad() override;
|
||||
|
||||
void setCancelTransitionsCallback(const std::function<void()>& func) override
|
||||
{
|
||||
mCancelTransitionsCallback = func;
|
||||
|
@ -42,29 +51,489 @@ public:
|
|||
const size_t getNumEntries() override { return mEntries.size(); }
|
||||
const bool getFadeAbovePrimary() const override { return mFadeAbovePrimary; }
|
||||
const LetterCase getLetterCase() const override { return mLetterCase; }
|
||||
virtual const LetterCase getLetterCaseCollections() const = 0;
|
||||
virtual const LetterCase getLetterCaseGroupedCollections() const = 0;
|
||||
const LetterCase getLetterCaseCollections() const override { return mLetterCaseCollections; }
|
||||
const LetterCase getLetterCaseGroupedCollections() const override
|
||||
{
|
||||
return mLetterCaseGroupedCollections;
|
||||
}
|
||||
const std::string& getItemType() { return mItemType; }
|
||||
void setItemType(std::string itemType) { mItemType = itemType; }
|
||||
const std::string& getDefaultItem() { return mDefaultItem; }
|
||||
void setDefaultItem(std::string defaultItem) { mDefaultItem = defaultItem; }
|
||||
bool input(InputConfig* config, Input input) override;
|
||||
void update(int deltaTime) override;
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
void applyTheme(const std::shared_ptr<ThemeData>& theme,
|
||||
const std::string& view,
|
||||
const std::string& element,
|
||||
unsigned int properties) override;
|
||||
|
||||
private:
|
||||
void calculateLayout();
|
||||
|
||||
void onCursorChanged(const CursorState& state) override;
|
||||
bool isScrolling() const override { return List::isScrolling(); }
|
||||
void stopScrolling() override { List::stopScrolling(); }
|
||||
const int getScrollingVelocity() override { return List::getScrollingVelocity(); }
|
||||
void clear() override { List::clear(); }
|
||||
const T& getSelected() const override { return List::getSelected(); }
|
||||
const T& getNext() const override { return List::getNext(); }
|
||||
const T& getPrevious() const override { return List::getPrevious(); }
|
||||
const T& getFirst() const override { return List::getFirst(); }
|
||||
const T& getLast() const override { return List::getLast(); }
|
||||
bool setCursor(const T& obj) override { return List::setCursor(obj); }
|
||||
bool remove(const T& obj) override { return List::remove(obj); }
|
||||
int size() const override { return List::size(); }
|
||||
|
||||
Renderer* mRenderer;
|
||||
std::function<void()> mCancelTransitionsCallback;
|
||||
std::function<void(CursorState state)> mCursorChangedCallback;
|
||||
|
||||
bool mFadeAbovePrimary;
|
||||
std::string mItemType;
|
||||
std::string mDefaultItem;
|
||||
float mEntryOffset;
|
||||
float mEntryCamTarget;
|
||||
float mTransitionFactor;
|
||||
std::shared_ptr<Font> mFont;
|
||||
|
||||
unsigned int mColumns;
|
||||
glm::vec2 mItemSize;
|
||||
float mItemScale;
|
||||
glm::vec2 mItemSpacing;
|
||||
bool mInstantItemTransitions;
|
||||
float mUnfocusedItemOpacity;
|
||||
unsigned int mTextColor;
|
||||
unsigned int mTextBackgroundColor;
|
||||
LetterCase mLetterCase;
|
||||
LetterCase mLetterCaseCollections;
|
||||
LetterCase mLetterCaseGroupedCollections;
|
||||
float mLineSpacing;
|
||||
bool mFadeAbovePrimary;
|
||||
int mPreviousScrollVelocity;
|
||||
bool mPositiveDirection;
|
||||
bool mGamelistView;
|
||||
bool mLayoutValid;
|
||||
bool mRowJump;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
GridComponent<T>::GridComponent()
|
||||
: IList<GridEntry, T> {}
|
||||
: IList<GridEntry, T> {LIST_SCROLL_STYLE_SLOW, ListLoopType::LIST_PAUSE_AT_END}
|
||||
, mRenderer {Renderer::getInstance()}
|
||||
, mFadeAbovePrimary {false}
|
||||
, mEntryOffset {0.0f}
|
||||
, mEntryCamTarget {0.0f}
|
||||
, mTransitionFactor {1.0f}
|
||||
, mFont {Font::get(FONT_SIZE_LARGE)}
|
||||
, mColumns {5}
|
||||
, mItemSize {glm::vec2 {Renderer::getScreenWidth() * 0.15f,
|
||||
Renderer::getScreenHeight() * 0.25f}}
|
||||
, mItemScale {1.2f}
|
||||
, mItemSpacing {glm::vec2 {Renderer::getScreenWidth() * 0.02f,
|
||||
Renderer::getScreenHeight() * 0.02f}}
|
||||
, mInstantItemTransitions {false}
|
||||
, mUnfocusedItemOpacity {1.0f}
|
||||
, mTextColor {0x000000FF}
|
||||
, mTextBackgroundColor {0xFFFFFF00}
|
||||
, mLetterCase {LetterCase::NONE}
|
||||
, mLetterCaseCollections {LetterCase::NONE}
|
||||
, mLetterCaseGroupedCollections {LetterCase::NONE}
|
||||
, mLineSpacing {1.5f}
|
||||
, mFadeAbovePrimary {false}
|
||||
, mPreviousScrollVelocity {0}
|
||||
, mPositiveDirection {false}
|
||||
, mGamelistView {std::is_same_v<T, FileData*> ? true : false}
|
||||
, mLayoutValid {false}
|
||||
, mRowJump {false}
|
||||
{
|
||||
}
|
||||
|
||||
#endif // ES_CORE_COMPONENTS_GRID_COMPONENT_H
|
||||
template <typename T>
|
||||
void GridComponent<T>::addEntry(Entry& entry, const std::shared_ptr<ThemeData>& theme)
|
||||
{
|
||||
bool dynamic {true};
|
||||
|
||||
if (!mGamelistView)
|
||||
dynamic = false;
|
||||
|
||||
if (entry.data.itemPath != "" &&
|
||||
ResourceManager::getInstance().fileExists(entry.data.itemPath)) {
|
||||
auto item = std::make_shared<ImageComponent>(false, dynamic);
|
||||
item->setLinearInterpolation(true);
|
||||
item->setMipmapping(true);
|
||||
item->setMaxSize(mItemSize);
|
||||
item->setImage(entry.data.itemPath);
|
||||
item->applyTheme(theme, "system", "", ThemeFlags::ALL);
|
||||
item->setOrigin(0.5f, 0.5f);
|
||||
item->setRotateByTargetSize(true);
|
||||
entry.data.item = item;
|
||||
}
|
||||
else if (entry.data.defaultItemPath != "" &&
|
||||
ResourceManager::getInstance().fileExists(entry.data.defaultItemPath)) {
|
||||
auto defaultItem = std::make_shared<ImageComponent>(false, dynamic);
|
||||
defaultItem->setLinearInterpolation(true);
|
||||
defaultItem->setMipmapping(true);
|
||||
defaultItem->setMaxSize(mItemSize);
|
||||
defaultItem->setImage(entry.data.defaultItemPath);
|
||||
defaultItem->applyTheme(theme, "system", "", ThemeFlags::ALL);
|
||||
defaultItem->setOrigin(0.5f, 0.5f);
|
||||
defaultItem->setRotateByTargetSize(true);
|
||||
entry.data.item = defaultItem;
|
||||
}
|
||||
|
||||
if (!entry.data.item) {
|
||||
// If no item image is present, add item text as fallback.
|
||||
auto text = std::make_shared<TextComponent>(
|
||||
entry.name, mFont, 0x000000FF, Alignment::ALIGN_CENTER, Alignment::ALIGN_CENTER,
|
||||
glm::vec3 {0.0f, 0.0f, 0.0f}, mItemSize, 0x00000000);
|
||||
text->setOrigin(0.5f, 0.5f);
|
||||
text->setLineSpacing(mLineSpacing);
|
||||
if (!mGamelistView)
|
||||
text->setValue(entry.name);
|
||||
text->setColor(mTextColor);
|
||||
text->setBackgroundColor(mTextBackgroundColor);
|
||||
text->setRenderBackground(true);
|
||||
|
||||
entry.data.item = text;
|
||||
}
|
||||
|
||||
List::add(entry);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void GridComponent<T>::updateEntry(Entry& entry, const std::shared_ptr<ThemeData>& theme)
|
||||
{
|
||||
if (entry.data.itemPath != "") {
|
||||
auto item = std::make_shared<ImageComponent>(false, true);
|
||||
item->setLinearInterpolation(true);
|
||||
item->setMipmapping(true);
|
||||
item->setMaxSize(mItemSize);
|
||||
item->setImage(entry.data.itemPath);
|
||||
item->applyTheme(theme, "system", "", ThemeFlags::ALL);
|
||||
item->setOrigin(0.5f, 0.5f);
|
||||
item->setRotateByTargetSize(true);
|
||||
entry.data.item = item;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void GridComponent<T>::onDemandTextureLoad()
|
||||
{
|
||||
if constexpr (std::is_same_v<T, FileData*>) {
|
||||
const int numEntries {static_cast<int>(mEntries.size())};
|
||||
|
||||
// TODO: Currently loads every item every time.
|
||||
for (int i {0}; i < size(); ++i) {
|
||||
int cursor {i};
|
||||
|
||||
while (cursor < 0)
|
||||
cursor += numEntries;
|
||||
while (cursor >= numEntries)
|
||||
cursor -= numEntries;
|
||||
|
||||
auto& entry = mEntries.at(cursor);
|
||||
|
||||
if (entry.data.itemPath == "") {
|
||||
FileData* game {entry.object};
|
||||
|
||||
if (mItemType == "" || mItemType == "marquee")
|
||||
entry.data.itemPath = game->getMarqueePath();
|
||||
else if (mItemType == "cover")
|
||||
entry.data.itemPath = game->getCoverPath();
|
||||
else if (mItemType == "backcover")
|
||||
entry.data.itemPath = game->getBackCoverPath();
|
||||
else if (mItemType == "3dbox")
|
||||
entry.data.itemPath = game->get3DBoxPath();
|
||||
else if (mItemType == "physicalmedia")
|
||||
entry.data.itemPath = game->getPhysicalMediaPath();
|
||||
else if (mItemType == "screenshot")
|
||||
entry.data.itemPath = game->getScreenshotPath();
|
||||
else if (mItemType == "titlescreen")
|
||||
entry.data.itemPath = game->getTitleScreenPath();
|
||||
else if (mItemType == "miximage")
|
||||
entry.data.itemPath = game->getMiximagePath();
|
||||
else if (mItemType == "fanart")
|
||||
entry.data.itemPath = game->getFanArtPath();
|
||||
else if (mItemType == "none") // Display the game name as text.
|
||||
return;
|
||||
|
||||
auto theme = game->getSystem()->getTheme();
|
||||
updateEntry(entry, theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> bool GridComponent<T>::input(InputConfig* config, Input input)
|
||||
{
|
||||
if (size() > 0) {
|
||||
if (input.value != 0) {
|
||||
mRowJump = false;
|
||||
|
||||
if (config->isMappedLike("left", input)) {
|
||||
if (mCancelTransitionsCallback)
|
||||
mCancelTransitionsCallback();
|
||||
List::listInput(-1);
|
||||
return true;
|
||||
}
|
||||
if (config->isMappedLike("right", input)) {
|
||||
if (mCancelTransitionsCallback)
|
||||
mCancelTransitionsCallback();
|
||||
List::listInput(1);
|
||||
return true;
|
||||
}
|
||||
if (config->isMappedLike("up", input)) {
|
||||
if (mCancelTransitionsCallback)
|
||||
mCancelTransitionsCallback();
|
||||
mRowJump = true;
|
||||
List::listInput(-mColumns);
|
||||
return true;
|
||||
}
|
||||
if (config->isMappedLike("down", input)) {
|
||||
if (mCancelTransitionsCallback)
|
||||
mCancelTransitionsCallback();
|
||||
mRowJump = true;
|
||||
List::listInput(mColumns);
|
||||
return true;
|
||||
}
|
||||
if (config->isMappedLike("lefttrigger", input)) {
|
||||
if (getCursor() == 0)
|
||||
return true;
|
||||
if (mCancelTransitionsCallback)
|
||||
mCancelTransitionsCallback();
|
||||
return this->listFirstRow();
|
||||
}
|
||||
if (config->isMappedLike("righttrigger", input)) {
|
||||
if (getCursor() == static_cast<int>(mEntries.size()) - 1)
|
||||
return true;
|
||||
if (mCancelTransitionsCallback)
|
||||
mCancelTransitionsCallback();
|
||||
return this->listLastRow();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (config->isMappedLike("left", input) || config->isMappedLike("right", input) ||
|
||||
config->isMappedLike("up", input) || config->isMappedLike("down", input) ||
|
||||
config->isMappedLike("lefttrigger", input) ||
|
||||
config->isMappedLike("righttrigger", input)) {
|
||||
if constexpr (std::is_same_v<T, SystemData*>) {
|
||||
if (isScrolling())
|
||||
onCursorChanged(CursorState::CURSOR_STOPPED);
|
||||
List::listInput(0);
|
||||
}
|
||||
else {
|
||||
if (isScrolling())
|
||||
onCursorChanged(CursorState::CURSOR_STOPPED);
|
||||
List::listInput(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GuiComponent::input(config, input);
|
||||
}
|
||||
|
||||
template <typename T> void GridComponent<T>::update(int deltaTime)
|
||||
{
|
||||
if (!mLayoutValid)
|
||||
calculateLayout();
|
||||
|
||||
List::listUpdate(deltaTime);
|
||||
GuiComponent::update(deltaTime);
|
||||
}
|
||||
|
||||
template <typename T> void GridComponent<T>::render(const glm::mat4& parentTrans)
|
||||
{
|
||||
int numEntries {static_cast<int>(mEntries.size())};
|
||||
if (numEntries == 0)
|
||||
return;
|
||||
|
||||
glm::mat4 trans {parentTrans * List::getTransform()};
|
||||
mRenderer->setMatrix(trans);
|
||||
|
||||
// In image debug mode, draw a green rectangle covering the entire grid area.
|
||||
if (Settings::getInstance()->getBool("DebugImage"))
|
||||
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0x00FF0033, 0x00FF0033);
|
||||
|
||||
for (size_t i {0}; i < mEntries.size(); ++i) {
|
||||
float opacity {mUnfocusedItemOpacity};
|
||||
float scale {1.0f};
|
||||
|
||||
if (i == static_cast<size_t>(mCursor)) {
|
||||
scale = glm::mix(1.0f, mItemScale, mTransitionFactor);
|
||||
opacity = 1.0f - glm::mix(mUnfocusedItemOpacity, 0.0f, mTransitionFactor);
|
||||
}
|
||||
else if (i == static_cast<size_t>(mLastCursor)) {
|
||||
scale = glm::mix(mItemScale, 1.0f, mTransitionFactor);
|
||||
opacity = glm::mix(1.0f, mUnfocusedItemOpacity, mTransitionFactor);
|
||||
}
|
||||
|
||||
mEntries.at(i).data.item->setScale(scale);
|
||||
mEntries.at(i).data.item->setOpacity(opacity);
|
||||
mEntries.at(i).data.item->render(trans);
|
||||
mEntries.at(i).data.item->setScale(1.0f);
|
||||
mEntries.at(i).data.item->setOpacity(1.0f);
|
||||
}
|
||||
|
||||
GuiComponent::renderChildren(trans);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void GridComponent<T>::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
||||
const std::string& view,
|
||||
const std::string& element,
|
||||
unsigned int properties)
|
||||
{
|
||||
GuiComponent::applyTheme(theme, view, element, properties);
|
||||
using namespace ThemeFlags;
|
||||
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "grid")};
|
||||
|
||||
if (!elem)
|
||||
return;
|
||||
|
||||
if (elem->has("columns"))
|
||||
mColumns = glm::clamp(elem->get<unsigned int>("columns"), 0u, 100u);
|
||||
|
||||
if (elem->has("itemSize")) {
|
||||
const glm::vec2 itemSize {glm::clamp(elem->get<glm::vec2>("itemSize"), 0.05f, 1.0f)};
|
||||
mItemSize = itemSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight());
|
||||
}
|
||||
|
||||
if (elem->has("itemScale"))
|
||||
mItemScale = glm::clamp(elem->get<float>("itemScale"), 0.5f, 2.0f);
|
||||
|
||||
if (elem->has("itemTransitions")) {
|
||||
const std::string& itemTransitions {elem->get<std::string>("itemTransitions")};
|
||||
if (itemTransitions == "scale") {
|
||||
mInstantItemTransitions = false;
|
||||
}
|
||||
else if (itemTransitions == "instant") {
|
||||
mInstantItemTransitions = true;
|
||||
}
|
||||
else {
|
||||
mInstantItemTransitions = false;
|
||||
LOG(LogWarning) << "GridComponent: Invalid theme configuration, property "
|
||||
"\"itemTransitions\" for element \""
|
||||
<< element.substr(5) << "\" defined as \"" << itemTransitions << "\"";
|
||||
}
|
||||
}
|
||||
|
||||
if (elem->has("itemSpacing")) {
|
||||
const glm::vec2 itemSpacing {glm::clamp(elem->get<glm::vec2>("itemSpacing"), 0.0f, 0.1f)};
|
||||
mItemSpacing =
|
||||
itemSpacing * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight());
|
||||
}
|
||||
|
||||
if (elem->has("unfocusedItemOpacity"))
|
||||
mUnfocusedItemOpacity = glm::clamp(elem->get<float>("unfocusedItemOpacity"), 0.1f, 1.0f);
|
||||
}
|
||||
|
||||
template <typename T> void GridComponent<T>::onCursorChanged(const CursorState& state)
|
||||
{
|
||||
float startPos {mEntryOffset};
|
||||
float posMax {static_cast<float>(mEntries.size())};
|
||||
float target {static_cast<float>(mCursor)};
|
||||
|
||||
// Find the shortest path to the target.
|
||||
float endPos {target}; // Directly.
|
||||
|
||||
if (mPreviousScrollVelocity > 0 && mScrollVelocity == 0 && mEntryOffset > posMax - 1.0f)
|
||||
startPos = 0.0f;
|
||||
|
||||
float dist {std::fabs(endPos - startPos)};
|
||||
|
||||
if (std::fabs(target + posMax - startPos - mScrollVelocity) < dist)
|
||||
endPos = target + posMax; // Loop around the end (0 -> max).
|
||||
if (std::fabs(target - posMax - startPos - mScrollVelocity) < dist)
|
||||
endPos = target - posMax; // Loop around the start (max - 1 -> -1).
|
||||
|
||||
// Make sure there are no reverse jumps between items.
|
||||
bool changedDirection {false};
|
||||
if (mPreviousScrollVelocity != 0 && mPreviousScrollVelocity != mScrollVelocity)
|
||||
changedDirection = true;
|
||||
|
||||
if (!changedDirection && mScrollVelocity > 0 && endPos < startPos)
|
||||
endPos = endPos + posMax;
|
||||
|
||||
if (!changedDirection && mScrollVelocity < 0 && endPos > startPos)
|
||||
endPos = endPos - posMax;
|
||||
|
||||
if (mScrollVelocity != 0)
|
||||
mPreviousScrollVelocity = mScrollVelocity;
|
||||
|
||||
// Needed to make sure that overlapping items are renderered correctly.
|
||||
if (startPos > endPos)
|
||||
mPositiveDirection = true;
|
||||
else
|
||||
mPositiveDirection = false;
|
||||
|
||||
mEntryCamTarget = endPos;
|
||||
float animTime {250.0f};
|
||||
float timeDiff {1.0f};
|
||||
|
||||
// If startPos is inbetween two positions then reduce the time slightly as the distance will
|
||||
// be shorter meaning the animation would play for too long if not compensated for.
|
||||
if (mScrollVelocity == 1)
|
||||
timeDiff = endPos - startPos;
|
||||
else if (mScrollVelocity == -1)
|
||||
timeDiff = startPos - endPos;
|
||||
|
||||
if (timeDiff != 1.0f)
|
||||
animTime =
|
||||
glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), 180.0f, animTime);
|
||||
|
||||
Animation* anim {new LambdaAnimation(
|
||||
[this, startPos, endPos, posMax](float t) {
|
||||
// Non-linear interpolation.
|
||||
t = 1.0f - (1.0f - t) * (1.0f - t);
|
||||
float f {(endPos * t) + (startPos * (1.0f - t))};
|
||||
if (f < 0)
|
||||
f += posMax;
|
||||
if (f >= posMax)
|
||||
f -= posMax;
|
||||
|
||||
mEntryOffset = f;
|
||||
|
||||
if (mInstantItemTransitions) {
|
||||
mTransitionFactor = 1.0f;
|
||||
}
|
||||
else {
|
||||
// Linear interpolation.
|
||||
mTransitionFactor = t;
|
||||
// Non-linear interpolation doesn't seem to be a good match for this component.
|
||||
// mTransitionFactor = {(1.0f * t) + (0.0f * (1.0f - t))};
|
||||
}
|
||||
},
|
||||
static_cast<int>(animTime))};
|
||||
|
||||
GuiComponent::setAnimation(anim, 0, nullptr, false, 0);
|
||||
|
||||
if (mCursorChangedCallback)
|
||||
mCursorChangedCallback(state);
|
||||
}
|
||||
|
||||
template <typename T> void GridComponent<T>::calculateLayout()
|
||||
{
|
||||
assert(!mEntries.empty());
|
||||
|
||||
unsigned int columnCount {0};
|
||||
unsigned int rowCount {0};
|
||||
|
||||
for (auto& entry : mEntries) {
|
||||
entry.data.item->setPosition(glm::vec3 {
|
||||
(mItemSize.x * columnCount) + (mItemSize.x * 0.5f) + mItemSpacing.x * columnCount,
|
||||
(mItemSize.y * rowCount) + (mItemSize.y * 0.5f) + mItemSpacing.y * rowCount, 0.0f});
|
||||
if (columnCount == mColumns - 1) {
|
||||
++rowCount;
|
||||
columnCount = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
++columnCount;
|
||||
}
|
||||
|
||||
mLayoutValid = true;
|
||||
}
|
||||
|
||||
#endif // ES_CORE_COMPONENTS_PRIMARY_GRID_COMPONENT_H
|
||||
|
|
|
@ -14,6 +14,7 @@ template <typename T> class PrimaryComponent : public virtual GuiComponent
|
|||
public:
|
||||
enum class PrimaryType {
|
||||
CAROUSEL,
|
||||
GRID,
|
||||
TEXTLIST
|
||||
};
|
||||
|
||||
|
|
|
@ -705,7 +705,7 @@ template <typename T> void TextListComponent<T>::onCursorChanged(const CursorSta
|
|||
float posMax {static_cast<float>(mEntries.size())};
|
||||
float endPos {static_cast<float>(mCursor)};
|
||||
|
||||
float animTime {380};
|
||||
float animTime {380.0f};
|
||||
float timeDiff {1.0f};
|
||||
|
||||
// If startPos is inbetween two positions then reduce the time slightly as the distance will
|
||||
|
@ -717,7 +717,7 @@ template <typename T> void TextListComponent<T>::onCursorChanged(const CursorSta
|
|||
|
||||
if (timeDiff != 1.0f)
|
||||
animTime =
|
||||
glm::clamp(std::fabs(glm::mix(0.0f, 380.0f, timeDiff * 1.5f)), 200.0f, 380.0f);
|
||||
glm::clamp(std::fabs(glm::mix(0.0f, animTime, timeDiff * 1.5f)), 200.0f, animTime);
|
||||
|
||||
Animation* anim {new LambdaAnimation(
|
||||
[this, startPos, endPos, posMax](float t) {
|
||||
|
|
Loading…
Reference in a new issue