mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-01-30 03:55:40 +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();
|
auto theme = mRoot->getSystem()->getTheme();
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string carouselItemType;
|
std::string defaultItem;
|
||||||
std::string carouselDefaultItem;
|
|
||||||
|
|
||||||
if (mCarousel != nullptr) {
|
if (mCarousel != nullptr) {
|
||||||
carouselItemType = mCarousel->getItemType();
|
defaultItem = mCarousel->getDefaultItem();
|
||||||
carouselDefaultItem = mCarousel->getDefaultItem();
|
if (!ResourceManager::getInstance().fileExists(defaultItem))
|
||||||
if (!ResourceManager::getInstance().fileExists(carouselDefaultItem))
|
defaultItem = "";
|
||||||
carouselDefaultItem = "";
|
|
||||||
}
|
}
|
||||||
|
else if (mGrid != nullptr) {
|
||||||
|
defaultItem = mGrid->getDefaultItem();
|
||||||
|
if (!ResourceManager::getInstance().fileExists(defaultItem))
|
||||||
|
defaultItem = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (files.size() > 0) {
|
if (files.size() > 0) {
|
||||||
for (auto it = files.cbegin(); it != files.cend(); ++it) {
|
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) {
|
if (mCarousel != nullptr) {
|
||||||
assert(carouselItemType != "");
|
|
||||||
|
|
||||||
CarouselComponent<FileData*>::Entry carouselEntry;
|
CarouselComponent<FileData*>::Entry carouselEntry;
|
||||||
carouselEntry.name = (*it)->getName();
|
carouselEntry.name = (*it)->getName();
|
||||||
carouselEntry.object = *it;
|
carouselEntry.object = *it;
|
||||||
|
@ -606,13 +608,29 @@ void GamelistBase::populateList(const std::vector<FileData*>& files, FileData* f
|
||||||
else if (letterCase == LetterCase::CAPITALIZED)
|
else if (letterCase == LetterCase::CAPITALIZED)
|
||||||
carouselEntry.name = Utils::String::toCapitalized(carouselEntry.name);
|
carouselEntry.name = Utils::String::toCapitalized(carouselEntry.name);
|
||||||
|
|
||||||
if (carouselDefaultItem != "")
|
if (defaultItem != "")
|
||||||
carouselEntry.data.defaultItemPath = carouselDefaultItem;
|
carouselEntry.data.defaultItemPath = defaultItem;
|
||||||
|
|
||||||
mCarousel->addEntry(carouselEntry, theme);
|
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;
|
TextListComponent<FileData*>::Entry textListEntry;
|
||||||
std::string indicators {mTextList->getIndicators()};
|
std::string indicators {mTextList->getIndicators()};
|
||||||
std::string collectionIndicators {mTextList->getCollectionIndicators()};
|
std::string collectionIndicators {mTextList->getCollectionIndicators()};
|
||||||
|
@ -717,13 +735,20 @@ void GamelistBase::addPlaceholder(FileData* firstEntry)
|
||||||
textListEntry.data.entryType = TextListEntryType::SECONDARY;
|
textListEntry.data.entryType = TextListEntryType::SECONDARY;
|
||||||
mTextList->addEntry(textListEntry);
|
mTextList->addEntry(textListEntry);
|
||||||
}
|
}
|
||||||
if (mCarousel != nullptr) {
|
else if (mCarousel != nullptr) {
|
||||||
CarouselComponent<FileData*>::Entry carouselEntry;
|
CarouselComponent<FileData*>::Entry carouselEntry;
|
||||||
carouselEntry.name = placeholder->getName();
|
carouselEntry.name = placeholder->getName();
|
||||||
letterCaseFunc(carouselEntry.name);
|
letterCaseFunc(carouselEntry.name);
|
||||||
carouselEntry.object = placeholder;
|
carouselEntry.object = placeholder;
|
||||||
mCarousel->addEntry(carouselEntry, mRoot->getSystem()->getTheme());
|
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)
|
void GamelistBase::generateFirstLetterIndex(const std::vector<FileData*>& files)
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "components/TextComponent.h"
|
#include "components/TextComponent.h"
|
||||||
#include "components/VideoFFmpegComponent.h"
|
#include "components/VideoFFmpegComponent.h"
|
||||||
#include "components/primary/CarouselComponent.h"
|
#include "components/primary/CarouselComponent.h"
|
||||||
|
#include "components/primary/GridComponent.h"
|
||||||
#include "components/primary/TextListComponent.h"
|
#include "components/primary/TextListComponent.h"
|
||||||
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
@ -90,6 +91,7 @@ protected:
|
||||||
|
|
||||||
FileData* mRoot;
|
FileData* mRoot;
|
||||||
std::unique_ptr<CarouselComponent<FileData*>> mCarousel;
|
std::unique_ptr<CarouselComponent<FileData*>> mCarousel;
|
||||||
|
std::unique_ptr<GridComponent<FileData*>> mGrid;
|
||||||
std::unique_ptr<TextListComponent<FileData*>> mTextList;
|
std::unique_ptr<TextListComponent<FileData*>> mTextList;
|
||||||
PrimaryComponent<FileData*>* mPrimary;
|
PrimaryComponent<FileData*>* mPrimary;
|
||||||
|
|
||||||
|
|
|
@ -114,15 +114,24 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||||
|
|
||||||
if (mTheme->hasView("gamelist")) {
|
if (mTheme->hasView("gamelist")) {
|
||||||
for (auto& element : mTheme->getViewElements("gamelist").elements) {
|
for (auto& element : mTheme->getViewElements("gamelist").elements) {
|
||||||
if (element.second.type == "textlist" || element.second.type == "carousel") {
|
if (element.second.type == "carousel" || element.second.type == "grid" ||
|
||||||
if (element.second.type == "carousel" && mTextList != nullptr) {
|
element.second.type == "textlist") {
|
||||||
|
if (element.second.type == "carousel" &&
|
||||||
|
(mGrid != nullptr || mTextList != nullptr)) {
|
||||||
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
|
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
|
||||||
<< "defined, skipping <carousel> configuration entry";
|
<< "defined, skipping carousel configuration entry";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (element.second.type == "textlist" && mCarousel != nullptr) {
|
if (element.second.type == "grid" &&
|
||||||
|
(mCarousel != nullptr || mTextList != nullptr)) {
|
||||||
LOG(LogWarning) << "SystemView::populate(): Multiple primary components "
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,9 +163,10 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||||
mCarousel->setItemType(itemType);
|
mCarousel->setItemType(itemType);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LOG(LogWarning)
|
LOG(LogWarning) << "GamelistView::onThemeChanged(): Invalid theme "
|
||||||
<< "GamelistView::onThemeChanged(): Invalid theme configuration, "
|
"configuration, carousel property \"itemType\" "
|
||||||
"<itemType> property defined as \""
|
"for element \""
|
||||||
|
<< element.first.substr(9) << "\" defined as \""
|
||||||
<< itemType << "\"";
|
<< itemType << "\"";
|
||||||
mCarousel->setItemType("marquee");
|
mCarousel->setItemType("marquee");
|
||||||
}
|
}
|
||||||
|
@ -174,6 +184,40 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||||
mPrimary->applyTheme(theme, "gamelist", element.first, ALL);
|
mPrimary->applyTheme(theme, "gamelist", element.first, ALL);
|
||||||
addChild(mPrimary);
|
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 (element.second.type == "image") {
|
||||||
// If this is the startup system, then forceload the images to avoid texture pop-in.
|
// If this is the startup system, then forceload the images to avoid texture pop-in.
|
||||||
if (isStartupSystem)
|
if (isStartupSystem)
|
||||||
|
@ -331,6 +375,9 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
|
||||||
mCarousel->getType() == CarouselComponent<FileData*>::CarouselType::HORIZONTAL_WHEEL)
|
mCarousel->getType() == CarouselComponent<FileData*>::CarouselType::HORIZONTAL_WHEEL)
|
||||||
mLeftRightAvailable = false;
|
mLeftRightAvailable = false;
|
||||||
}
|
}
|
||||||
|
else if (mGrid != nullptr) {
|
||||||
|
mLeftRightAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& video : mStaticVideoComponents) {
|
for (auto& video : mStaticVideoComponents) {
|
||||||
if (video->hasStaticVideo())
|
if (video->hasStaticVideo())
|
||||||
|
|
|
@ -145,9 +145,6 @@ bool SystemView::input(InputConfig* config, Input input)
|
||||||
|
|
||||||
void SystemView::update(int deltaTime)
|
void SystemView::update(int deltaTime)
|
||||||
{
|
{
|
||||||
if (!mPrimary->isAnimationPlaying(0))
|
|
||||||
mMaxFade = false;
|
|
||||||
|
|
||||||
mPrimary->update(deltaTime);
|
mPrimary->update(deltaTime);
|
||||||
|
|
||||||
for (auto& video : mSystemElements[mPrimary->getCursor()].videoComponents) {
|
for (auto& video : mSystemElements[mPrimary->getCursor()].videoComponents) {
|
||||||
|
@ -295,19 +292,24 @@ void SystemView::onCursorChanged(const CursorState& state)
|
||||||
|
|
||||||
Animation* anim;
|
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") {
|
if (transitionStyle == "fade") {
|
||||||
float startFade {mFadeOpacity};
|
float startFade {mFadeOpacity};
|
||||||
anim = new LambdaAnimation(
|
anim = new LambdaAnimation(
|
||||||
[this, startFade, startPos, endPos, posMax](float t) {
|
[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)
|
if (t < 0.3f)
|
||||||
mFadeOpacity =
|
mFadeOpacity =
|
||||||
glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f));
|
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)
|
if (t > 0.5f)
|
||||||
mCamOffset = endPos;
|
mCamOffset = endPos;
|
||||||
|
|
||||||
if (t >= 0.7f && t != 1.0f)
|
if (mNavigated && t >= 0.7f && t != 1.0f)
|
||||||
mMaxFade = true;
|
mMaxFade = true;
|
||||||
|
|
||||||
// Update the game count when the entire animation has been completed.
|
// Update the game count when the entire animation has been completed.
|
||||||
|
@ -328,15 +330,16 @@ void SystemView::onCursorChanged(const CursorState& state)
|
||||||
updateGameCount();
|
updateGameCount();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
500);
|
static_cast<int>(animTime * 1.3f));
|
||||||
}
|
}
|
||||||
else if (transitionStyle == "slide") {
|
else if (transitionStyle == "slide") {
|
||||||
mUpdatedGameCount = false;
|
mUpdatedGameCount = false;
|
||||||
anim = new LambdaAnimation(
|
anim = new LambdaAnimation(
|
||||||
[this, startPos, endPos, posMax](float t) {
|
[this, startPos, endPos, posMax](float t) {
|
||||||
t -= 1;
|
// Non-linear interpolation.
|
||||||
float f {glm::mix(startPos, endPos, t * t * t + 1.0f)};
|
t = 1.0f - (1.0f - t) * (1.0f - t);
|
||||||
if (f < 0.0f)
|
float f {(endPos * t) + (startPos * (1.0f - t))};
|
||||||
|
if (f < 0)
|
||||||
f += posMax;
|
f += posMax;
|
||||||
if (f >= posMax)
|
if (f >= posMax)
|
||||||
f -= posMax;
|
f -= posMax;
|
||||||
|
@ -362,23 +365,13 @@ void SystemView::onCursorChanged(const CursorState& state)
|
||||||
updateGameCount();
|
updateGameCount();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
500);
|
static_cast<int>(animTime));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Instant.
|
// Instant.
|
||||||
updateGameCount();
|
updateGameCount();
|
||||||
anim = new LambdaAnimation(
|
anim = new LambdaAnimation(
|
||||||
[this, startPos, endPos, posMax](float t) {
|
[this, startPos, endPos, posMax](float t) { mCamOffset = endPos; }, animTime);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setAnimation(anim, 0, nullptr, false, 0);
|
setAnimation(anim, 0, nullptr, false, 0);
|
||||||
|
@ -458,17 +451,27 @@ void SystemView::populate()
|
||||||
ThemeFlags::ALL);
|
ThemeFlags::ALL);
|
||||||
elements.gameSelectors.back()->setNeedsRefresh();
|
elements.gameSelectors.back()->setNeedsRefresh();
|
||||||
}
|
}
|
||||||
if (element.second.type == "textlist" || element.second.type == "carousel") {
|
if (element.second.type == "carousel" || element.second.type == "grid" ||
|
||||||
if (element.second.type == "carousel" && mTextList != nullptr) {
|
element.second.type == "textlist") {
|
||||||
|
if (element.second.type == "carousel" &&
|
||||||
|
(mGrid != nullptr || mTextList != nullptr)) {
|
||||||
LOG(LogWarning)
|
LOG(LogWarning)
|
||||||
<< "SystemView::populate(): Multiple primary components "
|
<< "SystemView::populate(): Multiple primary components "
|
||||||
<< "defined, skipping <carousel> configuration entry";
|
<< "defined, skipping carousel configuration entry";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (element.second.type == "textlist" && mCarousel != nullptr) {
|
if (element.second.type == "grid" &&
|
||||||
|
(mCarousel != nullptr || mTextList != nullptr)) {
|
||||||
LOG(LogWarning)
|
LOG(LogWarning)
|
||||||
<< "SystemView::populate(): Multiple primary components "
|
<< "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;
|
continue;
|
||||||
}
|
}
|
||||||
if (element.second.type == "carousel" && mCarousel == nullptr) {
|
if (element.second.type == "carousel" && mCarousel == nullptr) {
|
||||||
|
@ -476,6 +479,11 @@ void SystemView::populate()
|
||||||
mPrimary = mCarousel.get();
|
mPrimary = mCarousel.get();
|
||||||
mPrimaryType = PrimaryType::CAROUSEL;
|
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) {
|
else if (element.second.type == "textlist" && mTextList == nullptr) {
|
||||||
mTextList = std::make_unique<TextListComponent<SystemData*>>();
|
mTextList = std::make_unique<TextListComponent<SystemData*>>();
|
||||||
mPrimary = mTextList.get();
|
mPrimary = mTextList.get();
|
||||||
|
@ -497,7 +505,7 @@ void SystemView::populate()
|
||||||
anim->setPauseAnimation(true);
|
anim->setPauseAnimation(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (mCarousel != nullptr) {
|
if (mCarousel != nullptr || mGrid != nullptr) {
|
||||||
if (element.second.has("staticItem"))
|
if (element.second.has("staticItem"))
|
||||||
itemPath = element.second.get<std::string>("staticItem");
|
itemPath = element.second.get<std::string>("staticItem");
|
||||||
if (element.second.has("defaultItem"))
|
if (element.second.has("defaultItem"))
|
||||||
|
@ -675,6 +683,15 @@ void SystemView::populate()
|
||||||
entry.data.defaultItemPath = defaultItemPath;
|
entry.data.defaultItemPath = defaultItemPath;
|
||||||
mCarousel->addEntry(entry, theme);
|
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) {
|
else if (mTextList != nullptr) {
|
||||||
TextListComponent<SystemData*>::Entry entry;
|
TextListComponent<SystemData*>::Entry entry;
|
||||||
entry.name = it->getFullName();
|
entry.name = it->getFullName();
|
||||||
|
@ -1268,6 +1285,10 @@ void SystemView::renderElements(const glm::mat4& parentTrans, bool abovePrimary)
|
||||||
elementTrans,
|
elementTrans,
|
||||||
glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
|
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) {
|
else if (mTextList != nullptr) {
|
||||||
elementTrans = glm::translate(
|
elementTrans = glm::translate(
|
||||||
elementTrans, glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
|
elementTrans, glm::round(glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}));
|
||||||
|
|
|
@ -126,6 +126,7 @@ private:
|
||||||
|
|
||||||
Renderer* mRenderer;
|
Renderer* mRenderer;
|
||||||
std::unique_ptr<CarouselComponent<SystemData*>> mCarousel;
|
std::unique_ptr<CarouselComponent<SystemData*>> mCarousel;
|
||||||
|
std::unique_ptr<GridComponent<SystemData*>> mGrid;
|
||||||
std::unique_ptr<TextListComponent<SystemData*>> mTextList;
|
std::unique_ptr<TextListComponent<SystemData*>> mTextList;
|
||||||
std::unique_ptr<TextComponent> mLegacySystemInfo;
|
std::unique_ptr<TextComponent> mLegacySystemInfo;
|
||||||
std::vector<SystemViewElements> mSystemElements;
|
std::vector<SystemViewElements> mSystemElements;
|
||||||
|
|
|
@ -322,6 +322,40 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
|
||||||
{"fadeAbovePrimary", BOOLEAN},
|
{"fadeAbovePrimary", BOOLEAN},
|
||||||
{"zIndex", FLOAT},
|
{"zIndex", FLOAT},
|
||||||
{"legacyZIndexMode", STRING}}}, // For backward compatibility with legacy themes.
|
{"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",
|
{"textlist",
|
||||||
{{"pos", NORMALIZED_PAIR},
|
{{"pos", NORMALIZED_PAIR},
|
||||||
{"size", NORMALIZED_PAIR},
|
{"size", NORMALIZED_PAIR},
|
||||||
|
|
|
@ -73,6 +73,7 @@ protected:
|
||||||
const ScrollTierList& mTierList;
|
const ScrollTierList& mTierList;
|
||||||
const ListLoopType mLoopType;
|
const ListLoopType mLoopType;
|
||||||
int mCursor;
|
int mCursor;
|
||||||
|
int mLastCursor;
|
||||||
int mScrollTier;
|
int mScrollTier;
|
||||||
int mScrollVelocity;
|
int mScrollVelocity;
|
||||||
int mScrollTierAccumulator;
|
int mScrollTierAccumulator;
|
||||||
|
@ -88,6 +89,7 @@ public:
|
||||||
, mTierList {tierList}
|
, mTierList {tierList}
|
||||||
, mLoopType {loopType}
|
, mLoopType {loopType}
|
||||||
, mCursor {0}
|
, mCursor {0}
|
||||||
|
, mLastCursor {0}
|
||||||
, mScrollTier {0}
|
, mScrollTier {0}
|
||||||
, mScrollVelocity {0}
|
, mScrollVelocity {0}
|
||||||
, mScrollTierAccumulator {0}
|
, mScrollTierAccumulator {0}
|
||||||
|
@ -213,6 +215,7 @@ protected:
|
||||||
|
|
||||||
bool listFirstRow()
|
bool listFirstRow()
|
||||||
{
|
{
|
||||||
|
mLastCursor = mCursor;
|
||||||
mCursor = 0;
|
mCursor = 0;
|
||||||
onCursorChanged(CursorState::CURSOR_STOPPED);
|
onCursorChanged(CursorState::CURSOR_STOPPED);
|
||||||
onScroll();
|
onScroll();
|
||||||
|
@ -221,6 +224,7 @@ protected:
|
||||||
|
|
||||||
bool listLastRow()
|
bool listLastRow()
|
||||||
{
|
{
|
||||||
|
mLastCursor = mCursor;
|
||||||
mCursor = static_cast<int>(mEntries.size()) - 1;
|
mCursor = static_cast<int>(mEntries.size()) - 1;
|
||||||
onCursorChanged(CursorState::CURSOR_STOPPED);
|
onCursorChanged(CursorState::CURSOR_STOPPED);
|
||||||
onScroll();
|
onScroll();
|
||||||
|
@ -323,6 +327,8 @@ protected:
|
||||||
if (mScrollVelocity == 0 || size() < 2)
|
if (mScrollVelocity == 0 || size() < 2)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
mLastCursor = mCursor;
|
||||||
|
|
||||||
int cursor {mCursor + amt};
|
int cursor {mCursor + amt};
|
||||||
int absAmt {amt < 0 ? -amt : amt};
|
int absAmt {amt < 0 ? -amt : amt};
|
||||||
|
|
||||||
|
|
|
@ -1327,7 +1327,7 @@ template <typename T> void CarouselComponent<T>::onCursorChanged(const CursorSta
|
||||||
mPositiveDirection = false;
|
mPositiveDirection = false;
|
||||||
|
|
||||||
mEntryCamTarget = endPos;
|
mEntryCamTarget = endPos;
|
||||||
float animTime {380};
|
float animTime {380.0f};
|
||||||
float timeDiff {1.0f};
|
float timeDiff {1.0f};
|
||||||
|
|
||||||
// If startPos is inbetween two positions then reduce the time slightly as the distance will
|
// 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;
|
timeDiff = startPos - endPos;
|
||||||
|
|
||||||
if (timeDiff != 1.0f)
|
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(
|
Animation* anim {new LambdaAnimation(
|
||||||
[this, startPos, endPos, posMax](float t) {
|
[this, startPos, endPos, posMax](float t) {
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
// Grid, usable in both the system and gamelist views.
|
// Grid, usable in both the system and gamelist views.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef ES_CORE_COMPONENTS_GRID_COMPONENT_H
|
#ifndef ES_CORE_COMPONENTS_PRIMARY_GRID_COMPONENT_H
|
||||||
#define ES_CORE_COMPONENTS_GRID_COMPONENT_H
|
#define ES_CORE_COMPONENTS_PRIMARY_GRID_COMPONENT_H
|
||||||
|
|
||||||
#include "components/IList.h"
|
#include "components/IList.h"
|
||||||
#include "components/primary/PrimaryComponent.h"
|
#include "components/primary/PrimaryComponent.h"
|
||||||
|
@ -26,10 +26,19 @@ class GridComponent : public PrimaryComponent<T>, protected IList<GridEntry, T>
|
||||||
protected:
|
protected:
|
||||||
using List::mCursor;
|
using List::mCursor;
|
||||||
using List::mEntries;
|
using List::mEntries;
|
||||||
|
using List::mLastCursor;
|
||||||
|
using List::mScrollVelocity;
|
||||||
|
using List::mSize;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using Entry = typename IList<GridEntry, T>::Entry;
|
||||||
|
|
||||||
GridComponent();
|
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
|
void setCancelTransitionsCallback(const std::function<void()>& func) override
|
||||||
{
|
{
|
||||||
mCancelTransitionsCallback = func;
|
mCancelTransitionsCallback = func;
|
||||||
|
@ -42,29 +51,489 @@ public:
|
||||||
const size_t getNumEntries() override { return mEntries.size(); }
|
const size_t getNumEntries() override { return mEntries.size(); }
|
||||||
const bool getFadeAbovePrimary() const override { return mFadeAbovePrimary; }
|
const bool getFadeAbovePrimary() const override { return mFadeAbovePrimary; }
|
||||||
const LetterCase getLetterCase() const override { return mLetterCase; }
|
const LetterCase getLetterCase() const override { return mLetterCase; }
|
||||||
virtual const LetterCase getLetterCaseCollections() const = 0;
|
const LetterCase getLetterCaseCollections() const override { return mLetterCaseCollections; }
|
||||||
virtual const LetterCase getLetterCaseGroupedCollections() const = 0;
|
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:
|
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;
|
Renderer* mRenderer;
|
||||||
std::function<void()> mCancelTransitionsCallback;
|
std::function<void()> mCancelTransitionsCallback;
|
||||||
std::function<void(CursorState state)> mCursorChangedCallback;
|
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 mLetterCase;
|
||||||
LetterCase mLetterCaseCollections;
|
LetterCase mLetterCaseCollections;
|
||||||
LetterCase mLetterCaseGroupedCollections;
|
LetterCase mLetterCaseGroupedCollections;
|
||||||
|
float mLineSpacing;
|
||||||
|
bool mFadeAbovePrimary;
|
||||||
|
int mPreviousScrollVelocity;
|
||||||
|
bool mPositiveDirection;
|
||||||
|
bool mGamelistView;
|
||||||
|
bool mLayoutValid;
|
||||||
|
bool mRowJump;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
GridComponent<T>::GridComponent()
|
GridComponent<T>::GridComponent()
|
||||||
: IList<GridEntry, T> {}
|
: IList<GridEntry, T> {LIST_SCROLL_STYLE_SLOW, ListLoopType::LIST_PAUSE_AT_END}
|
||||||
, mRenderer {Renderer::getInstance()}
|
, 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}
|
, mLetterCase {LetterCase::NONE}
|
||||||
, mLetterCaseCollections {LetterCase::NONE}
|
, mLetterCaseCollections {LetterCase::NONE}
|
||||||
, mLetterCaseGroupedCollections {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:
|
public:
|
||||||
enum class PrimaryType {
|
enum class PrimaryType {
|
||||||
CAROUSEL,
|
CAROUSEL,
|
||||||
|
GRID,
|
||||||
TEXTLIST
|
TEXTLIST
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -705,7 +705,7 @@ template <typename T> void TextListComponent<T>::onCursorChanged(const CursorSta
|
||||||
float posMax {static_cast<float>(mEntries.size())};
|
float posMax {static_cast<float>(mEntries.size())};
|
||||||
float endPos {static_cast<float>(mCursor)};
|
float endPos {static_cast<float>(mCursor)};
|
||||||
|
|
||||||
float animTime {380};
|
float animTime {380.0f};
|
||||||
float timeDiff {1.0f};
|
float timeDiff {1.0f};
|
||||||
|
|
||||||
// If startPos is inbetween two positions then reduce the time slightly as the distance will
|
// 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)
|
if (timeDiff != 1.0f)
|
||||||
animTime =
|
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(
|
Animation* anim {new LambdaAnimation(
|
||||||
[this, startPos, endPos, posMax](float t) {
|
[this, startPos, endPos, posMax](float t) {
|
||||||
|
|
Loading…
Reference in a new issue