Added basic GridComponent functionality and integration.

This commit is contained in:
Leon Styhre 2022-11-12 14:08:53 +01:00
parent 12f2142c03
commit 48111ce5e4
11 changed files with 675 additions and 68 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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,10 +163,11 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mCarousel->setItemType(itemType);
}
else {
LOG(LogWarning)
<< "GamelistView::onThemeChanged(): Invalid theme configuration, "
"<itemType> property defined as \""
<< itemType << "\"";
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())

View file

@ -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}));

View file

@ -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;

View file

@ -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},

View file

@ -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};

View file

@ -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) {

View file

@ -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

View file

@ -14,6 +14,7 @@ template <typename T> class PrimaryComponent : public virtual GuiComponent
public:
enum class PrimaryType {
CAROUSEL,
GRID,
TEXTLIST
};

View file

@ -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) {