Large update to get the new theme engine up and running.

This commit is contained in:
Leon Styhre 2022-01-29 18:41:22 +01:00
parent 0ca2b62edf
commit c35a297d9a
22 changed files with 1436 additions and 489 deletions

View file

@ -642,6 +642,7 @@ int main(int argc, char* argv[])
AudioManager::getInstance();
MameNames::getInstance();
ThemeData::getThemeSets();
loadSystemsReturnCode loadSystemsStatus = loadSystemConfigFile();
if (loadSystemsStatus) {

View file

@ -16,6 +16,7 @@
#include "Window.h"
#include "components/BadgeComponent.h"
#include "components/DateTimeComponent.h"
#include "components/LottieComponent.h"
#include "components/RatingComponent.h"
#include "components/ScrollableContainer.h"
#include "components/TextComponent.h"

View file

@ -45,7 +45,7 @@ void GamelistView::legacyPopulateFields()
// Thumbnails.
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setMetadataField("md_thumbnail");
mImageComponents.back()->setMetadataField("image_md_thumbnail");
mImageComponents.back()->setOrigin(0.5f, 0.5f);
mImageComponents.back()->setVisible(false);
mImageComponents.back()->setMaxSize(mSize.x * (0.25f - 2.0f * padding), mSize.y * 0.10f);
@ -54,7 +54,7 @@ void GamelistView::legacyPopulateFields()
// Marquee.
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setMetadataField("md_marquee");
mImageComponents.back()->setMetadataField("image_md_marquee");
mImageComponents.back()->setOrigin(0.5f, 0.5f);
mImageComponents.back()->setVisible(false);
mImageComponents.back()->setMaxSize(mSize.x * (0.5f - 2.0f * padding), mSize.y * 0.18f);
@ -63,7 +63,7 @@ void GamelistView::legacyPopulateFields()
// Image.
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setMetadataField("md_image");
mImageComponents.back()->setMetadataField("image_md_image");
mImageComponents.back()->setOrigin(0.5f, 0.5f);
mImageComponents.back()->setPosition(mSize.x * 0.25f,
mList.getPosition().y + mSize.y * 0.2125f);
@ -74,9 +74,10 @@ void GamelistView::legacyPopulateFields()
if (mViewStyle == ViewController::VIDEO) {
// Video.
mVideoComponents.push_back(std::make_unique<VideoFFmpegComponent>());
mVideoComponents.back()->setMetadataField("md_video");
mVideoComponents.back()->setMetadataField("video_md_video");
mVideoComponents.back()->setOrigin(0.5f, 0.5f);
mVideoComponents.back()->setPosition(mSize.x * 0.25f, mSize.y * 0.4f);
mVideoComponents.back()->setPosition(mSize.x * 0.25f,
mList.getPosition().y + mSize.y * 0.2125f);
mVideoComponents.back()->setSize(mSize.x * (0.5f - 2.0f * padding), mSize.y * 0.4f);
mVideoComponents.back()->setDefaultZIndex(30.0f);
addChild(mVideoComponents.back().get());
@ -85,84 +86,85 @@ void GamelistView::legacyPopulateFields()
mList.setPosition(mSize.x * (0.50f + padding), mList.getPosition().y);
mList.setSize(mSize.x * (0.50f - padding), mList.getSize().y);
mList.setAlignment(TextListComponent<FileData*>::ALIGN_LEFT);
mList.setCursorChangedCallback([&](const CursorState& /*state*/) { legacyUpdateInfoPanel(); });
mList.setCursorChangedCallback([&](const CursorState& /*state*/) { updateInfoPanel(); });
// Metadata labels + values.
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Rating: ", false);
mTextComponents.back()->setMetadataField("md_lbl_rating");
mTextComponents.back()->setMetadataField("text_md_lbl_rating");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Released: ", false);
mTextComponents.back()->setMetadataField("md_lbl_releasedate");
mTextComponents.back()->setMetadataField("text_md_lbl_releasedate");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Developer: ", false);
mTextComponents.back()->setMetadataField("md_lbl_developer");
mTextComponents.back()->setMetadataField("text_md_lbl_developer");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Publisher: ", false);
mTextComponents.back()->setMetadataField("md_lbl_publisher");
mTextComponents.back()->setMetadataField("text_md_lbl_publisher");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Genre: ", false);
mTextComponents.back()->setMetadataField("md_lbl_genre");
mTextComponents.back()->setMetadataField("text_md_lbl_genre");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Players: ", false);
mTextComponents.back()->setMetadataField("md_lbl_players");
mTextComponents.back()->setMetadataField("text_md_lbl_players");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Last played: ", false);
mTextComponents.back()->setMetadataField("md_lbl_lastplayed");
mTextComponents.back()->setMetadataField("text_md_lbl_lastplayed");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setText("Times played: ", false);
mTextComponents.back()->setMetadataField("md_lbl_playcount");
mTextComponents.back()->setMetadataField("text_md_lbl_playcount");
addChild(mTextComponents.back().get());
mRatingComponents.push_back(std::make_unique<RatingComponent>());
mRatingComponents.back()->setMetadataField("md_rating");
mRatingComponents.back()->setMetadataField("rating_md_rating");
mRatingComponents.back()->setDefaultZIndex(40.0f);
addChild(mRatingComponents.back().get());
mDateTimeComponents.push_back(std::make_unique<DateTimeComponent>());
mDateTimeComponents.back()->setMetadataField("md_releasedate");
mDateTimeComponents.back()->setMetadataField("datetime_md_releasedate");
addChild(mDateTimeComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("md_developer");
mTextComponents.back()->setMetadataField("text_md_developer");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("md_publisher");
mTextComponents.back()->setMetadataField("text_md_publisher");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("md_genre");
mTextComponents.back()->setMetadataField("text_md_genre");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("md_players");
mTextComponents.back()->setMetadataField("text_md_players");
addChild(mTextComponents.back().get());
mDateTimeComponents.push_back(std::make_unique<DateTimeComponent>());
mDateTimeComponents.back()->setMetadataField("md_lastplayed");
mDateTimeComponents.back()->setMetadataField("datetime_md_lastplayed");
mDateTimeComponents.back()->setDisplayRelative(true);
addChild(mDateTimeComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("md_playcount");
mTextComponents.back()->setMetadataField("text_md_playcount");
addChild(mTextComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setMetadataField("md_name");
mTextComponents.back()->setMetadataField("text_md_name");
mTextComponents.back()->setPosition(mSize.x, mSize.y);
mTextComponents.back()->setFont(Font::get(FONT_SIZE_MEDIUM));
mTextComponents.back()->setHorizontalAlignment(ALIGN_CENTER);
@ -172,7 +174,7 @@ void GamelistView::legacyPopulateFields()
// Badges.
mBadgeComponents.push_back(std::make_unique<BadgeComponent>());
mBadgeComponents.back()->setMetadataField("md_badges");
mBadgeComponents.back()->setMetadataField("badges_md_badges");
mBadgeComponents.back()->setOrigin(0.5f, 0.5f);
mBadgeComponents.back()->setPosition(mSize.x * 0.8f, mSize.y * 0.7f);
mBadgeComponents.back()->setSize(mSize.x * 0.15f, mSize.y * 0.2f);
@ -180,22 +182,21 @@ void GamelistView::legacyPopulateFields()
addChild(mBadgeComponents.back().get());
// Scrollable container (game description).
mScrollableContainerComponents.push_back(std::make_unique<ScrollableContainer>());
mScrollableContainerComponents.back()->setMetadataField("md_description");
mScrollableContainerComponents.back()->setSize(
mSize.x * (0.50f - 2.0f * padding),
mSize.y - mScrollableContainerComponents.back()->getPosition().y);
mScrollableContainerComponents.back()->setAutoScroll(true);
mScrollableContainerComponents.back()->setDefaultZIndex(40.0f);
addChild(mScrollableContainerComponents.back().get());
mContainerComponents.push_back(std::make_unique<ScrollableContainer>());
mContainerComponents.back()->setMetadataField("text_md_description");
mContainerComponents.back()->setSize(mSize.x * (0.50f - 2.0f * padding),
mSize.y - mContainerComponents.back()->getPosition().y);
mContainerComponents.back()->setAutoScroll(true);
mContainerComponents.back()->setDefaultZIndex(40.0f);
addChild(mContainerComponents.back().get());
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setFont(Font::get(FONT_SIZE_SMALL));
mTextComponents.back()->setSize(mScrollableContainerComponents.back()->getSize().x, 0.0f);
mScrollableContainerComponents.back()->addChild(mTextComponents.back().get());
mTextComponents.back()->setSize(mContainerComponents.back()->getSize().x, 0.0f);
mContainerComponents.back()->addChild(mTextComponents.back().get());
mGamelistInfoComponents.push_back(std::make_unique<TextComponent>());
mGamelistInfoComponents.back()->setMetadataField("gamelistInfo");
mGamelistInfoComponents.back()->setMetadataField("text_gamelistInfo");
mGamelistInfoComponents.back()->setOrigin(0.5f, 0.5f);
mGamelistInfoComponents.back()->setFont(Font::get(FONT_SIZE_SMALL));
mGamelistInfoComponents.back()->setDefaultZIndex(50.0f);
@ -209,9 +210,9 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr<ThemeData>& theme)
using namespace ThemeFlags;
mTextComponents[LOGOTEXT]->applyTheme(theme, getName(), "logoText", ALL);
mImageComponents[LOGO]->applyTheme(theme, getName(), "logo", ALL);
mImageComponents[BACKGROUND]->applyTheme(theme, getName(), "background", ALL);
mTextComponents[LOGOTEXT]->applyTheme(theme, getName(), "text_logoText", ALL);
mImageComponents[LOGO]->applyTheme(theme, getName(), "image_logo", ALL);
mImageComponents[BACKGROUND]->applyTheme(theme, getName(), "image_background", ALL);
// Remove old theme extras.
for (auto extra : mThemeExtras) {
@ -225,12 +226,13 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr<ThemeData>& theme)
for (auto extra : mThemeExtras)
addChild(extra);
mList.applyTheme(theme, getName(), "gamelist", ALL);
mList.applyTheme(theme, getName(), "textlist_gamelist", ALL);
mImageComponents[LegacyImage::MD_THUMBNAIL]->applyTheme(
theme, getName(), mImageComponents[LegacyImage::MD_THUMBNAIL]->getMetadataField(), ALL);
mImageComponents[LegacyImage::MD_MARQUEE]->applyTheme(
theme, getName(), "md_marquee", POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE);
mImageComponents[LegacyImage::MD_MARQUEE]->applyTheme(theme, getName(), "image_md_marquee",
POSITION | ThemeFlags::SIZE | Z_INDEX |
ROTATION | VISIBLE);
if (mViewStyle == ViewController::DETAILED) {
mImageComponents[LegacyImage::MD_IMAGE]->applyTheme(
@ -276,15 +278,15 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr<ThemeData>& theme)
ALL);
}
for (auto& container : mScrollableContainerComponents) {
for (auto& container : mContainerComponents) {
container->applyTheme(theme, getName(), container->getMetadataField(),
POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE);
}
mTextComponents[LegacyText::MD_DESCRIPTION]->setSize(
mScrollableContainerComponents.front()->getSize().x, 0.0f);
mTextComponents[LegacyText::MD_DESCRIPTION]->setSize(mContainerComponents.front()->getSize().x,
0.0f);
mTextComponents[LegacyText::MD_DESCRIPTION]->applyTheme(
theme, getName(), "md_description",
theme, getName(), "text_md_description",
ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION));
for (auto& gamelistInfo : mGamelistInfoComponents)
@ -299,10 +301,10 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr<ThemeData>& theme)
// Hide some components if we're in Basic mode.
if (mViewStyle == ViewController::BASIC) {
if (mTheme->getElement(getName(), "logoText", "text") == nullptr)
if (mTheme->getElement(getName(), "text_logoText", "text") == nullptr)
mTextComponents[LegacyText::LOGOTEXT]->setVisible(false);
mImageComponents[LegacyImage::MD_IMAGE]->setVisible(false);
for (auto& container : mScrollableContainerComponents)
for (auto& container : mContainerComponents)
container->setVisible(false);
}
@ -339,11 +341,12 @@ void GamelistView::legacyUpdateInfoPanel()
// If we're scrolling, hide the metadata fields if the last game had this options set,
// or if we're in the grouped custom collection view.
if (mList.isScrolling())
if (mList.isScrolling()) {
if ((mLastUpdated && mLastUpdated->metadata.get("hidemetadata") == "true") ||
(mLastUpdated->getSystem()->isCustomCollection() &&
mLastUpdated->getPath() == mLastUpdated->getSystem()->getName()))
hideMetaDataFields = true;
}
if (hideMetaDataFields || mViewStyle == ViewController::BASIC) {
for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_DESCRIPTION; ++i)
@ -484,7 +487,7 @@ void GamelistView::legacyUpdateInfoPanel()
}
mTextComponents[LegacyText::MD_DESCRIPTION]->setText(file->metadata.get("desc"));
for (auto& container : mScrollableContainerComponents)
for (auto& container : mContainerComponents)
container->reset();
for (auto& rating : mRatingComponents)
@ -540,66 +543,6 @@ void GamelistView::legacyUpdateInfoPanel()
}
}
for (auto& text : mTextComponents) {
if (text->getValue() != "")
continue;
std::string metadata = text->getMetadataField();
if (metadata == "")
continue;
if (metadata == "md_controller") {
std::string controller =
BadgeComponent::getDisplayName(file->metadata.get("controller"));
text->setValue(controller == "unknown" ? "" : controller);
continue;
}
if (metadata == "md_name")
text->setValue(file->metadata.get("name"));
else if (metadata == "md_rating")
text->setValue(mRatingComponents.front()->getRatingValue());
else if (metadata == "md_developer")
text->setValue(file->metadata.get("developer"));
else if (metadata == "md_publisher")
text->setValue(file->metadata.get("publisher"));
else if (metadata == "md_genre")
text->setValue(file->metadata.get("genre"));
else if (metadata == "md_players")
text->setValue(file->metadata.get("players"));
else if (metadata == "md_favorite")
text->setValue(file->metadata.get("favorite") == "true" ? "yes" : "no");
else if (metadata == "md_completed")
text->setValue(file->metadata.get("completed") == "true" ? "yes" : "no");
else if (metadata == "md_kidgame")
text->setValue(file->metadata.get("kidgame") == "true" ? "yes" : "no");
else if (metadata == "md_broken")
text->setValue(file->metadata.get("broken") == "true" ? "yes" : "no");
else if (metadata == "md_playcount")
text->setValue(file->metadata.get("playcount"));
else if (metadata == "md_altemulator")
text->setValue(file->metadata.get("altemulator"));
else
text->setValue(metadata);
}
for (auto& date : mDateTimeComponents) {
std::string metadata = date->getMetadataField();
if (metadata == "")
continue;
if (metadata == "md_releasedate") {
date->setValue(file->metadata.get("releasedate"));
}
else if (metadata == "md_lastplayed") {
date->setValue(file->metadata.get("lastplayed"));
date->setDisplayRelative(true);
}
else {
date->setValue("19700101T000000");
}
}
fadingOut = false;
}
@ -756,10 +699,10 @@ void GamelistView::legacyInitMDValues()
mRatingComponents.front()->setPosition(Renderer::getScreenWidth() * 2.0f,
Renderer::getScreenHeight() * 2.0f);
mScrollableContainerComponents.front()->setPosition(Renderer::getScreenWidth() * 2.0f,
mContainerComponents.front()->setPosition(Renderer::getScreenWidth() * 2.0f,
Renderer::getScreenHeight() * 2.0f);
for (auto& container : mScrollableContainerComponents) {
for (auto& container : mContainerComponents) {
container->setPosition(container->getPosition().x, bottom + mSize.y * 0.01f);
container->setSize(container->getSize().x, mSize.y - container->getPosition().y);
}

View file

@ -13,17 +13,14 @@
#include "UIModeController.h"
#include "animations/LambdaAnimation.h"
// #define FADE_IN_START_OPACITY 0.5f
// #define FADE_IN_TIME 325
#define FADE_IN_START_OPACITY 0.5f
#define FADE_IN_TIME 325
GamelistView::GamelistView(FileData* root, bool legacyMode)
GamelistView::GamelistView(FileData* root)
: GamelistBase {root}
, mLegacyMode {legacyMode}
, mLegacyMode {false}
, mViewStyle {ViewController::BASIC}
{
// TEMPORARY
mLegacyMode = true;
mViewStyle = ViewController::getInstance()->getState().viewstyle;
if (mLegacyMode)
@ -71,8 +68,14 @@ void GamelistView::onFileChanged(FileData* file, bool reloadGamelist)
void GamelistView::onShow()
{
// Reset any Lottie animations.
for (auto& animation : mLottieAnimComponents)
animation->resetFileAnimation();
// Reset any Lottie animations.
if (mLegacyMode) {
for (auto extra : mThemeExtras)
extra->resetFileAnimation();
}
mLastUpdated = nullptr;
GuiComponent::onShow();
@ -84,6 +87,8 @@ void GamelistView::onShow()
void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
mLegacyMode = mTheme->isLegacyTheme();
if (mLegacyMode) {
legacyOnThemeChanged(theme);
return;
@ -91,21 +96,94 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
using namespace ThemeFlags;
// Remove old theme extras.
for (auto extra : mThemeExtras) {
removeChild(extra);
delete extra;
if (mTheme->hasView("gamelist")) {
for (auto& element : mTheme->getViewElements("gamelist").elements) {
if (element.second.type == "image") {
mImageComponents.push_back(std::make_unique<ImageComponent>());
mImageComponents.back()->setDefaultZIndex(30.0f);
mImageComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mImageComponents.back()->getMetadataField() != "")
mImageComponents.back()->setScrollHide(true);
addChild(mImageComponents.back().get());
}
else if (element.second.type == "video") {
mVideoComponents.push_back(std::make_unique<VideoFFmpegComponent>());
mVideoComponents.back()->setDefaultZIndex(30.0f);
addChild(mVideoComponents.back().get());
mVideoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mVideoComponents.back()->getMetadataField() != "")
mVideoComponents.back()->setScrollHide(true);
}
else if (element.second.type == "animation") {
mLottieAnimComponents.push_back(std::make_unique<LottieComponent>());
mLottieAnimComponents.back()->setDefaultZIndex(35.0f);
mLottieAnimComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
addChild(mLottieAnimComponents.back().get());
}
else if (element.second.type == "badges") {
mBadgeComponents.push_back(std::make_unique<BadgeComponent>());
mBadgeComponents.back()->setDefaultZIndex(35.0f);
mBadgeComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
mBadgeComponents.back()->setScrollHide(true);
addChild(mBadgeComponents.back().get());
}
else if (element.second.type == "text") {
if (element.second.has("container") && element.second.get<bool>("container")) {
mContainerTextComponents.push_back(std::make_unique<TextComponent>());
mContainerTextComponents.back()->setDefaultZIndex(40.0f);
mContainerComponents.push_back(std::make_unique<ScrollableContainer>());
mContainerComponents.back()->setAutoScroll(true);
mContainerComponents.back()->addChild(mContainerTextComponents.back().get());
mContainerComponents.back()->applyTheme(theme, "gamelist", element.first,
POSITION | ThemeFlags::SIZE | Z_INDEX |
VISIBLE);
mContainerTextComponents.back()->applyTheme(theme, "gamelist", element.first,
ALL ^ POSITION ^ Z_INDEX ^
ThemeFlags::SIZE ^ VISIBLE);
mContainerTextComponents.back()->setSize(
mContainerComponents.back()->getSize().x, 0.0f);
mContainerComponents.back()->setDefaultZIndex(
mContainerTextComponents.back()->getDefaultZIndex());
mContainerComponents.back()->setZIndex(
mContainerTextComponents.back()->getZIndex());
mContainerComponents.back()->setScrollHide(true);
addChild(mContainerComponents.back().get());
}
else {
mTextComponents.push_back(std::make_unique<TextComponent>());
mTextComponents.back()->setDefaultZIndex(40.0f);
mTextComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mTextComponents.back()->getMetadataField() != "")
mTextComponents.back()->setScrollHide(true);
addChild(mTextComponents.back().get());
}
}
else if (element.second.type == "datetime") {
mDateTimeComponents.push_back(std::make_unique<DateTimeComponent>());
mDateTimeComponents.back()->setDefaultZIndex(40.0f);
mDateTimeComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
if (mDateTimeComponents.back()->getMetadataField() != "")
mDateTimeComponents.back()->setScrollHide(true);
addChild(mDateTimeComponents.back().get());
}
else if (element.second.type == "gamelistinfo") {
mGamelistInfoComponents.push_back(std::make_unique<TextComponent>());
mGamelistInfoComponents.back()->setDefaultZIndex(45.0f);
mGamelistInfoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
addChild(mGamelistInfoComponents.back().get());
}
else if (element.second.type == "rating") {
mRatingComponents.push_back(std::make_unique<RatingComponent>());
mRatingComponents.back()->setDefaultZIndex(45.0f);
mRatingComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
mRatingComponents.back()->setScrollHide(true);
addChild(mRatingComponents.back().get());
}
}
}
mThemeExtras.clear();
// Add new theme extras.
mThemeExtras = ThemeData::makeExtras(theme, getName());
for (auto extra : mThemeExtras)
addChild(extra);
mList.applyTheme(theme, getName(), "gamelist", ALL);
// TODO: Implement logic to populate component vectors.
mList.setDefaultZIndex(50.0f);
mList.applyTheme(theme, "gamelist", "textlist_gamelist", ALL);
sortChildren();
}
@ -117,6 +195,23 @@ void GamelistView::update(int deltaTime)
return;
}
if (ViewController::getInstance()->getGameLaunchTriggered()) {
for (auto& image : mImageComponents) {
if (image->isAnimationPlaying(0))
image->finishAnimation(0);
}
}
for (auto& video : mVideoComponents) {
if (!mVideoPlaying)
video->onHide();
else if (mVideoPlaying && !video->isVideoPaused() && !mWindow->isScreensaverActive())
video->onShow();
if (ViewController::getInstance()->getGameLaunchTriggered() && video->isAnimationPlaying(0))
video->finishAnimation(0);
}
updateChildren(deltaTime);
}
@ -140,7 +235,10 @@ void GamelistView::render(const glm::mat4& parentTrans)
HelpStyle GamelistView::getHelpStyle()
{
HelpStyle style;
if (mLegacyMode)
style.applyTheme(mTheme, getName());
else
style.applyTheme(mTheme, "gamelist");
return style;
}
@ -193,6 +291,11 @@ std::vector<HelpPrompt> GamelistView::getHelpPrompts()
void GamelistView::updateInfoPanel()
{
if (mLegacyMode) {
legacyUpdateInfoPanel();
return;
}
FileData* file {(mList.size() == 0 || mList.isScrolling()) ? nullptr : mList.getSelected()};
// If the game data has already been rendered to the info panel, then skip it this time.
@ -219,8 +322,438 @@ void GamelistView::updateInfoPanel()
}
}
if (hideMetaDataFields)
;
// If we're scrolling, hide the metadata fields if the last game had this options set,
// or if we're in the grouped custom collection view.
if (mList.isScrolling()) {
if ((mLastUpdated && mLastUpdated->metadata.get("hidemetadata") == "true") ||
(mLastUpdated->getSystem()->isCustomCollection() &&
mLastUpdated->getPath() == mLastUpdated->getSystem()->getName()))
hideMetaDataFields = true;
}
// TODO: Implement gamelist logic.
if (hideMetaDataFields) {
for (auto& text : mTextComponents) {
if (text->getMetadataField() != "")
text->setVisible(false);
}
for (auto& date : mDateTimeComponents)
date->setVisible(false);
for (auto& badge : mBadgeComponents)
badge->setVisible(false);
for (auto& rating : mRatingComponents)
rating->setVisible(false);
for (auto& cText : mContainerTextComponents) {
if (cText->getMetadataField() != "md_description")
cText->setVisible(false);
}
}
else {
for (auto& text : mTextComponents) {
if (text->getMetadataField() != "")
text->setVisible(true);
}
for (auto& date : mDateTimeComponents)
date->setVisible(true);
for (auto& badge : mBadgeComponents)
badge->setVisible(true);
for (auto& rating : mRatingComponents)
rating->setVisible(true);
for (auto& cText : mContainerTextComponents) {
if (cText->getMetadataField() != "md_description")
cText->setVisible(true);
}
}
bool fadingOut = false;
if (file == nullptr) {
mVideoPlaying = false;
fadingOut = true;
}
else {
// If we're browsing a grouped custom collection, then update the folder metadata
// which will generate a description of three random games and return a pointer to
// the first of these so that we can display its game media.
if (file->getSystem()->isCustomCollection() &&
file->getPath() == file->getSystem()->getName()) {
mRandomGame = CollectionSystemsManager::getInstance()->updateCollectionFolderMetadata(
file->getSystem());
if (mRandomGame) {
for (auto& image : mImageComponents) {
if (image->getMetadataField() == "md_image")
image->setImage(mRandomGame->getImagePath());
else if (image->getMetadataField() == "md_miximage")
image->setImage(mRandomGame->getMiximagePath());
else if (image->getMetadataField() == "md_marquee")
image->setImage(mRandomGame->getMarqueePath(), false, true);
else if (image->getMetadataField() == "md_screenshot")
image->setImage(mRandomGame->getScreenshotPath());
else if (image->getMetadataField() == "md_titlescreen")
image->setImage(mRandomGame->getTitleScreenPath());
else if (image->getMetadataField() == "md_cover")
image->setImage(mRandomGame->getCoverPath());
else if (image->getMetadataField() == "md_backcover")
image->setImage(mRandomGame->getBackCoverPath());
else if (image->getMetadataField() == "md_3dbox")
image->setImage(mRandomGame->get3DBoxPath());
else if (image->getMetadataField() == "md_fanart")
image->setImage(mRandomGame->getFanArtPath());
else if (image->getMetadataField() == "md_thumbnail")
image->setImage(mRandomGame->getThumbnailPath());
}
for (auto& video : mVideoComponents) {
if (video->getMetadataField() == "md_image")
video->setImage(mRandomGame->getImagePath());
else if (video->getMetadataField() == "md_miximage")
video->setImage(mRandomGame->getMiximagePath());
else if (video->getMetadataField() == "md_marquee")
video->setImage(mRandomGame->getMarqueePath(), false, true);
else if (video->getMetadataField() == "md_screenshot")
video->setImage(mRandomGame->getScreenshotPath());
else if (video->getMetadataField() == "md_titlescreen")
video->setImage(mRandomGame->getTitleScreenPath());
else if (video->getMetadataField() == "md_cover")
video->setImage(mRandomGame->getCoverPath());
else if (video->getMetadataField() == "md_backcover")
video->setImage(mRandomGame->getBackCoverPath());
else if (video->getMetadataField() == "md_3dbox")
video->setImage(mRandomGame->get3DBoxPath());
else if (video->getMetadataField() == "md_fanart")
video->setImage(mRandomGame->getFanArtPath());
else if (video->getMetadataField() == "md_thumbnail")
video->setImage(mRandomGame->getThumbnailPath());
// Always stop the video before setting a new video as it will otherwise
// continue to play if it has the same path (i.e. it is the same physical
// video file) as the previously set video. That may happen when entering a
// folder with the same name as the first game file inside, or as in this
// case, when entering a custom collection.
video->onHide();
if (video->hasStaticVideo())
video->setStaticVideo();
else if (!video->setVideo(mRandomGame->getVideoPath()))
video->setDefaultVideo();
}
}
else {
for (auto& image : mImageComponents) {
if (image->getMetadataField() != "")
image->setImage("");
}
for (auto& video : mVideoComponents) {
video->setImage("");
video->setVideo("");
if (video->hasStaticVideo()) {
video->onStopVideo();
video->setStaticVideo();
}
else {
video->setDefaultVideo();
}
}
}
}
else {
for (auto& image : mImageComponents) {
if (image->getMetadataField() == "md_image")
image->setImage(file->getImagePath());
else if (image->getMetadataField() == "md_miximage")
image->setImage(file->getMiximagePath());
else if (image->getMetadataField() == "md_marquee")
image->setImage(file->getMarqueePath(), false, true);
else if (image->getMetadataField() == "md_screenshot")
image->setImage(file->getScreenshotPath());
else if (image->getMetadataField() == "md_titlescreen")
image->setImage(file->getTitleScreenPath());
else if (image->getMetadataField() == "md_cover")
image->setImage(file->getCoverPath());
else if (image->getMetadataField() == "md_backcover")
image->setImage(file->getBackCoverPath());
else if (image->getMetadataField() == "md_3dbox")
image->setImage(file->get3DBoxPath());
else if (image->getMetadataField() == "md_fanart")
image->setImage(file->getFanArtPath());
else if (image->getMetadataField() == "md_thumbnail")
image->setImage(file->getThumbnailPath());
}
for (auto& video : mVideoComponents) {
if (video->getMetadataField() == "md_image")
video->setImage(file->getImagePath());
else if (video->getMetadataField() == "md_miximage")
video->setImage(file->getMiximagePath());
else if (video->getMetadataField() == "md_marquee")
video->setImage(file->getMarqueePath(), false, true);
else if (video->getMetadataField() == "md_screenshot")
video->setImage(file->getScreenshotPath());
else if (video->getMetadataField() == "md_titlescreen")
video->setImage(file->getTitleScreenPath());
else if (video->getMetadataField() == "md_cover")
video->setImage(file->getCoverPath());
else if (video->getMetadataField() == "md_backcover")
video->setImage(file->getBackCoverPath());
else if (video->getMetadataField() == "md_3dbox")
video->setImage(file->get3DBoxPath());
else if (video->getMetadataField() == "md_fanart")
video->setImage(file->getFanArtPath());
else if (video->getMetadataField() == "md_thumbnail")
video->setImage(file->getThumbnailPath());
video->onHide();
if (video->hasStaticVideo())
video->setStaticVideo();
else if (!video->setVideo(file->getVideoPath()))
video->setDefaultVideo();
}
}
mVideoPlaying = true;
// Populate the gamelistInfo field which shows an icon if a folder has been entered
// as well as the game count for the entire system (total and favorites separately).
// If a filter has been applied, then the number of filtered and total games replaces
// the game counter.
for (auto& gamelistInfo : mGamelistInfoComponents) {
std::string gamelistInfoString;
Alignment infoAlign = gamelistInfo->getHorizontalAlignment();
if (mIsFolder && infoAlign == ALIGN_RIGHT)
gamelistInfoString = ViewController::FOLDER_CHAR + " ";
if (mIsFiltered) {
if (mFilteredGameCountAll == mFilteredGameCount)
gamelistInfoString += ViewController::FILTER_CHAR + " " +
std::to_string(mFilteredGameCount) + " / " +
std::to_string(mGameCount);
else
gamelistInfoString +=
ViewController::FILTER_CHAR + " " + std::to_string(mFilteredGameCount) +
" + " + std::to_string(mFilteredGameCountAll - mFilteredGameCount) + " / " +
std::to_string(mGameCount);
}
else {
gamelistInfoString +=
ViewController::CONTROLLER_CHAR + " " + std::to_string(mGameCount);
if (!(file->getSystem()->isCollection() &&
file->getSystem()->getFullName() == "favorites"))
gamelistInfoString += " " + ViewController::FAVORITE_CHAR + " " +
std::to_string(mFavoritesGameCount);
}
if (mIsFolder && infoAlign != ALIGN_RIGHT)
gamelistInfoString += " " + ViewController::FOLDER_CHAR;
gamelistInfo->setValue(gamelistInfoString);
}
// Fade in the game image.
for (auto& image : mImageComponents) {
if (image->getMetadataField() == "md_image") {
auto func = [&image](float t) {
image->setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
};
image->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
}
}
// Fade in the static image.
for (auto& video : mVideoComponents) {
if (video->getMetadataField() == "md_video") {
auto func = [&video](float t) {
video->setOpacity(static_cast<unsigned char>(
glm::mix(static_cast<float>(FADE_IN_START_OPACITY), 1.0f, t) * 255));
};
video->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false);
}
}
for (auto& container : mContainerComponents)
container->reset();
for (auto& rating : mRatingComponents)
rating->setValue(file->metadata.get("rating"));
// Populate the badge slots based on game metadata.
std::vector<BadgeComponent::BadgeInfo> badgeSlots;
for (auto& badgeComponent : mBadgeComponents) {
for (auto& badge : badgeComponent->getBadgeTypes()) {
BadgeComponent::BadgeInfo badgeInfo;
badgeInfo.badgeType = badge;
if (badge == "controller") {
if (file->metadata.get("controller").compare("") != 0) {
badgeInfo.gameController = file->metadata.get("controller");
badgeSlots.push_back(badgeInfo);
}
}
else if (badge == "altemulator") {
if (file->metadata.get(badge).compare("") != 0)
badgeSlots.push_back(badgeInfo);
}
else {
if (file->metadata.get(badge).compare("true") == 0)
badgeSlots.push_back(badgeInfo);
}
}
badgeComponent->setBadges(badgeSlots);
}
for (auto& text : mTextComponents) {
if (text->getMetadataField() == "md_name")
text->setText(file->metadata.get("name"));
}
if (file->getType() == GAME) {
if (!hideMetaDataFields) {
for (auto& date : mDateTimeComponents) {
if (date->getMetadataField() == "md_lastplayed")
date->setValue(file->metadata.get("lastplayed"));
else if (date->getMetadataField() == "md_playcount")
date->setValue(file->metadata.get("playcount"));
}
}
else if (file->getType() == FOLDER) {
if (!hideMetaDataFields) {
for (auto& date : mDateTimeComponents) {
if (date->getMetadataField() == "md_lastplayed") {
date->setValue(file->metadata.get("lastplayed"));
date->setVisible(false);
date->setVisible(false);
}
}
}
}
}
std::string metadata;
auto getMetadataValue = [&file, &metadata]() -> std::string {
if (metadata == "md_name")
return file->metadata.get("name");
else if (metadata == "md_description")
return file->metadata.get("desc");
else if (metadata == "md_developer")
return file->metadata.get("developer");
else if (metadata == "md_publisher")
return file->metadata.get("publisher");
else if (metadata == "md_genre")
return file->metadata.get("genre");
else if (metadata == "md_players")
return file->metadata.get("players");
else if (metadata == "md_favorite")
return file->metadata.get("favorite") == "true" ? "Yes" : "No";
else if (metadata == "md_completed")
return file->metadata.get("completed") == "true" ? "Yes" : "No";
else if (metadata == "md_kidgame")
return file->metadata.get("kidgame") == "true" ? "Yes" : "No";
else if (metadata == "md_broken")
return file->metadata.get("broken") == "true" ? "Yes" : "No";
else if (metadata == "md_playcount")
return file->metadata.get("playcount");
else if (metadata == "md_altemulator")
return file->metadata.get("altemulator");
else
return metadata;
};
for (auto& text : mContainerTextComponents) {
metadata = text->getMetadataField();
if (metadata == "")
continue;
if (metadata == "md_rating") {
text->setValue(mRatingComponents.front()->getRatingValue());
continue;
}
else if (metadata == "md_controller") {
std::string controller =
BadgeComponent::getDisplayName(file->metadata.get("controller"));
text->setValue(controller == "unknown" ? "" : controller);
continue;
}
text->setValue(getMetadataValue());
}
for (auto& text : mTextComponents) {
metadata = text->getMetadataField();
if (metadata == "")
continue;
if (metadata == "md_rating") {
text->setValue(mRatingComponents.front()->getRatingValue());
continue;
}
else if (metadata == "md_controller") {
std::string controller =
BadgeComponent::getDisplayName(file->metadata.get("controller"));
text->setValue(controller == "unknown" ? "" : controller);
continue;
}
text->setValue(getMetadataValue());
}
for (auto& date : mDateTimeComponents) {
std::string metadata = date->getMetadataField();
if (metadata == "")
continue;
if (metadata == "md_releasedate") {
date->setValue(file->metadata.get("releasedate"));
}
else if (metadata == "md_lastplayed") {
date->setValue(file->metadata.get("lastplayed"));
date->setDisplayRelative(true);
}
else {
date->setValue("19700101T000000");
}
}
fadingOut = false;
}
std::vector<GuiComponent*> comps;
for (auto& text : mTextComponents) {
if (text->getScrollHide())
comps.emplace_back(text.get());
}
for (auto& date : mDateTimeComponents) {
if (date->getScrollHide())
comps.emplace_back(date.get());
}
for (auto& image : mImageComponents) {
if (image->getScrollHide())
comps.emplace_back(image.get());
}
for (auto& badge : mBadgeComponents) {
if (badge->getScrollHide())
comps.emplace_back(badge.get());
}
for (auto& rating : mRatingComponents) {
if (rating->getScrollHide())
comps.emplace_back(rating.get());
}
for (auto& container : mContainerComponents) {
if (container->getScrollHide())
comps.emplace_back(container.get());
}
for (auto it = comps.cbegin(); it != comps.cend(); ++it) {
GuiComponent* comp = *it;
// An animation is playing, then animate if reverse != fadingOut.
// An animation is not playing, then animate if opacity != our target opacity.
if ((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255))) {
auto func = [comp](float t) {
comp->setOpacity(static_cast<unsigned char>(glm::mix(0.0f, 1.0f, t) * 255));
};
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
}
}
}

View file

@ -17,7 +17,7 @@
class GamelistView : public GamelistBase
{
public:
GamelistView(FileData* root, bool legacyMode = false);
GamelistView(FileData* root);
~GamelistView();
// Called when a FileData* is added, has its metadata changed, or is removed.
@ -76,9 +76,11 @@ private:
std::vector<std::unique_ptr<DateTimeComponent>> mDateTimeComponents;
std::vector<std::unique_ptr<ImageComponent>> mImageComponents;
std::vector<std::unique_ptr<VideoFFmpegComponent>> mVideoComponents;
std::vector<std::unique_ptr<LottieComponent>> mLottieAnimComponents;
std::vector<std::unique_ptr<BadgeComponent>> mBadgeComponents;
std::vector<std::unique_ptr<RatingComponent>> mRatingComponents;
std::vector<std::unique_ptr<ScrollableContainer>> mScrollableContainerComponents;
std::vector<std::unique_ptr<ScrollableContainer>> mContainerComponents;
std::vector<std::unique_ptr<TextComponent>> mContainerTextComponents;
std::vector<std::unique_ptr<TextComponent>> mGamelistInfoComponents;
enum LegacyText {

View file

@ -76,7 +76,8 @@ void SystemView::populate()
glm::vec3 offsetLogoPlaceholderText = {0.0f, 0.0f, 0.0f};
// Make logo.
const ThemeData::ThemeElement* logoElem = theme->getElement("system", "logo", "image");
const ThemeData::ThemeElement* logoElem =
theme->getElement("system", "image_logo", "image");
if (logoElem) {
auto path = logoElem->get<std::string>("path");
std::string defaultPath =
@ -86,7 +87,8 @@ void SystemView::populate()
ResourceManager::getInstance().fileExists(defaultPath))) {
auto* logo = new ImageComponent(false, false);
logo->setMaxSize(glm::round(mCarousel.logoSize * mCarousel.logoScale));
logo->applyTheme(theme, "system", "logo", ThemeFlags::PATH | ThemeFlags::COLOR);
logo->applyTheme(theme, "system", "image_logo",
ThemeFlags::PATH | ThemeFlags::COLOR);
logo->setRotateByTargetSize(true);
e.data.logo = std::shared_ptr<GuiComponent>(logo);
}
@ -100,7 +102,7 @@ void SystemView::populate()
glm::vec3 center {resolution.x / 2.0f, resolution.y / 2.0f, 1.0f};
// Placeholder Image.
logoElem = theme->getElement("system", "logoPlaceholderImage", "image");
logoElem = theme->getElement("system", "image_logoPlaceholderImage", "image");
if (logoElem) {
auto path = logoElem->get<std::string>("path");
std::string defaultPath =
@ -109,7 +111,8 @@ void SystemView::populate()
(!defaultPath.empty() &&
ResourceManager::getInstance().fileExists(defaultPath))) {
auto* logo = new ImageComponent(false, false);
logo->applyTheme(theme, "system", "logoPlaceholderImage", ThemeFlags::ALL);
logo->applyTheme(theme, "system", "image_logoPlaceholderImage",
ThemeFlags::ALL);
if (!logoElem->has("size"))
logo->setMaxSize(mCarousel.logoSize * mCarousel.logoScale);
offsetLogo = logo->getPosition() - center;
@ -120,7 +123,7 @@ void SystemView::populate()
// Placeholder Text.
const ThemeData::ThemeElement* logoPlaceholderText =
theme->getElement("system", "logoPlaceholderText", "text");
theme->getElement("system", "text_logoPlaceholderText", "text");
if (logoPlaceholderText) {
// Element 'logoPlaceholderText' found in theme: place text
auto* text = new TextComponent(it->getName(), Font::get(FONT_SIZE_LARGE),
@ -134,7 +137,7 @@ void SystemView::populate()
text->setHorizontalAlignment(ALIGN_CENTER);
text->setVerticalAlignment(mCarousel.logoAlignment);
}
text->applyTheme(it->getTheme(), "system", "logoPlaceholderText",
text->applyTheme(it->getTheme(), "system", "text_logoPlaceholderText",
ThemeFlags::ALL);
if (!e.data.logo) {
e.data.logo = std::shared_ptr<GuiComponent>(text);
@ -541,15 +544,16 @@ void SystemView::getViewElements(const std::shared_ptr<ThemeData>& theme)
return;
const ThemeData::ThemeElement* carouselElem =
theme->getElement("system", "systemcarousel", "carousel");
theme->getElement("system", "carousel_systemcarousel", "carousel");
if (carouselElem)
getCarouselFromTheme(carouselElem);
const ThemeData::ThemeElement* sysInfoElem = theme->getElement("system", "systemInfo", "text");
const ThemeData::ThemeElement* sysInfoElem =
theme->getElement("system", "text_systemInfo", "text");
if (sysInfoElem)
mSystemInfo.applyTheme(theme, "system", "systemInfo", ThemeFlags::ALL);
mSystemInfo.applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL);
mViewNeedsReload = false;
}

View file

@ -11,6 +11,8 @@
#include "resources/Font.h"
#define PREFIX "button_"
HelpStyle::HelpStyle()
{
position =
@ -32,7 +34,7 @@ HelpStyle::HelpStyle()
void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view)
{
auto elem = theme->getElement(view, "help", "helpsystem");
auto elem = theme->getElement(view, "helpsystem_help", "helpsystem");
if (!elem)
return;
@ -73,74 +75,76 @@ void HelpStyle::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::s
textStyle = elem->get<std::string>("textStyle");
// Load custom button icons.
// The names may look a bit strange when combined with the PREFIX string "button_" but it's
// because ThemeData adds this prefix to avoid name collisions when using XML attributes.
// General.
if (elem->has("dpad_updown"))
mCustomButtons.dpad_updown = elem->get<std::string>("dpad_updown");
if (elem->has("dpad_leftright"))
mCustomButtons.dpad_leftright = elem->get<std::string>("dpad_leftright");
if (elem->has("dpad_all"))
mCustomButtons.dpad_all = elem->get<std::string>("dpad_all");
if (elem->has("thumbstick_click"))
mCustomButtons.thumbstick_click = elem->get<std::string>("thumbstick_click");
if (elem->has("button_l"))
mCustomButtons.button_l = elem->get<std::string>("button_l");
if (elem->has("button_r"))
mCustomButtons.button_r = elem->get<std::string>("button_r");
if (elem->has("button_lr"))
mCustomButtons.button_lr = elem->get<std::string>("button_lr");
if (elem->has("button_lt"))
mCustomButtons.button_lt = elem->get<std::string>("button_lt");
if (elem->has("button_rt"))
mCustomButtons.button_rt = elem->get<std::string>("button_rt");
if (elem->has(PREFIX "dpad_updown"))
mCustomButtons.dpad_updown = elem->get<std::string>(PREFIX "dpad_updown");
if (elem->has(PREFIX "dpad_leftright"))
mCustomButtons.dpad_leftright = elem->get<std::string>(PREFIX "dpad_leftright");
if (elem->has(PREFIX "dpad_all"))
mCustomButtons.dpad_all = elem->get<std::string>(PREFIX "dpad_all");
if (elem->has(PREFIX "thumbstick_click"))
mCustomButtons.thumbstick_click = elem->get<std::string>(PREFIX "thumbstick_click");
if (elem->has(PREFIX "button_l"))
mCustomButtons.button_l = elem->get<std::string>(PREFIX "button_l");
if (elem->has(PREFIX "button_r"))
mCustomButtons.button_r = elem->get<std::string>(PREFIX "button_r");
if (elem->has(PREFIX "button_lr"))
mCustomButtons.button_lr = elem->get<std::string>(PREFIX "button_lr");
if (elem->has(PREFIX "button_lt"))
mCustomButtons.button_lt = elem->get<std::string>(PREFIX "button_lt");
if (elem->has(PREFIX "button_rt"))
mCustomButtons.button_rt = elem->get<std::string>(PREFIX "button_rt");
// SNES.
if (elem->has("button_a_SNES"))
mCustomButtons.button_a_SNES = elem->get<std::string>("button_a_SNES");
if (elem->has("button_b_SNES"))
mCustomButtons.button_b_SNES = elem->get<std::string>("button_b_SNES");
if (elem->has("button_x_SNES"))
mCustomButtons.button_x_SNES = elem->get<std::string>("button_x_SNES");
if (elem->has("button_y_SNES"))
mCustomButtons.button_y_SNES = elem->get<std::string>("button_y_SNES");
if (elem->has("button_start_SNES"))
mCustomButtons.button_start_SNES = elem->get<std::string>("button_start_SNES");
if (elem->has("button_back_SNES"))
mCustomButtons.button_back_SNES = elem->get<std::string>("button_back_SNES");
if (elem->has(PREFIX "button_a_SNES"))
mCustomButtons.button_a_SNES = elem->get<std::string>(PREFIX "button_a_SNES");
if (elem->has(PREFIX "button_b_SNES"))
mCustomButtons.button_b_SNES = elem->get<std::string>(PREFIX "button_b_SNES");
if (elem->has(PREFIX "button_x_SNES"))
mCustomButtons.button_x_SNES = elem->get<std::string>(PREFIX "button_x_SNES");
if (elem->has(PREFIX "button_y_SNES"))
mCustomButtons.button_y_SNES = elem->get<std::string>(PREFIX "button_y_SNES");
if (elem->has(PREFIX "button_start_SNES"))
mCustomButtons.button_start_SNES = elem->get<std::string>(PREFIX "button_start_SNES");
if (elem->has(PREFIX "button_back_SNES"))
mCustomButtons.button_back_SNES = elem->get<std::string>(PREFIX "button_back_SNES");
// PS.
if (elem->has("button_a_PS"))
mCustomButtons.button_a_PS = elem->get<std::string>("button_a_PS");
if (elem->has("button_b_PS"))
mCustomButtons.button_b_PS = elem->get<std::string>("button_b_PS");
if (elem->has("button_x_PS"))
mCustomButtons.button_x_PS = elem->get<std::string>("button_x_PS");
if (elem->has("button_y_PS"))
mCustomButtons.button_y_PS = elem->get<std::string>("button_y_PS");
if (elem->has("button_start_PS4"))
mCustomButtons.button_start_PS4 = elem->get<std::string>("button_start_PS4");
if (elem->has("button_back_PS4"))
mCustomButtons.button_back_PS4 = elem->get<std::string>("button_back_PS4");
if (elem->has("button_start_PS5"))
mCustomButtons.button_start_PS5 = elem->get<std::string>("button_start_PS5");
if (elem->has("button_back_PS5"))
mCustomButtons.button_back_PS5 = elem->get<std::string>("button_back_PS5");
// PlayStation.
if (elem->has(PREFIX "button_a_PS"))
mCustomButtons.button_a_PS = elem->get<std::string>(PREFIX "button_a_PS");
if (elem->has(PREFIX "button_b_PS"))
mCustomButtons.button_b_PS = elem->get<std::string>(PREFIX "button_b_PS");
if (elem->has(PREFIX "button_x_PS"))
mCustomButtons.button_x_PS = elem->get<std::string>(PREFIX "button_x_PS");
if (elem->has(PREFIX "button_y_PS"))
mCustomButtons.button_y_PS = elem->get<std::string>(PREFIX "button_y_PS");
if (elem->has(PREFIX "button_start_PS4"))
mCustomButtons.button_start_PS4 = elem->get<std::string>(PREFIX "button_start_PS4");
if (elem->has(PREFIX "button_back_PS4"))
mCustomButtons.button_back_PS4 = elem->get<std::string>(PREFIX "button_back_PS4");
if (elem->has(PREFIX "button_start_PS5"))
mCustomButtons.button_start_PS5 = elem->get<std::string>(PREFIX "button_start_PS5");
if (elem->has(PREFIX "button_back_PS5"))
mCustomButtons.button_back_PS5 = elem->get<std::string>(PREFIX "button_back_PS5");
// XBOX.
if (elem->has("button_a_XBOX"))
mCustomButtons.button_a_XBOX = elem->get<std::string>("button_a_XBOX");
if (elem->has("button_b_XBOX"))
mCustomButtons.button_b_XBOX = elem->get<std::string>("button_b_XBOX");
if (elem->has("button_x_XBOX"))
mCustomButtons.button_x_XBOX = elem->get<std::string>("button_x_XBOX");
if (elem->has("button_y_XBOX"))
mCustomButtons.button_y_XBOX = elem->get<std::string>("button_y_XBOX");
if (elem->has("button_start_XBOX"))
mCustomButtons.button_start_XBOX = elem->get<std::string>("button_start_XBOX");
if (elem->has("button_back_XBOX"))
mCustomButtons.button_back_XBOX = elem->get<std::string>("button_back_XBOX");
if (elem->has("button_start_XBOX360"))
mCustomButtons.button_start_XBOX360 = elem->get<std::string>("button_start_XBOX360");
if (elem->has("button_back_XBOX360"))
mCustomButtons.button_back_XBOX360 = elem->get<std::string>("button_back_XBOX360");
if (elem->has(PREFIX "button_a_XBOX"))
mCustomButtons.button_a_XBOX = elem->get<std::string>(PREFIX "button_a_XBOX");
if (elem->has(PREFIX "button_b_XBOX"))
mCustomButtons.button_b_XBOX = elem->get<std::string>(PREFIX "button_b_XBOX");
if (elem->has(PREFIX "button_x_XBOX"))
mCustomButtons.button_x_XBOX = elem->get<std::string>(PREFIX "button_x_XBOX");
if (elem->has(PREFIX "button_y_XBOX"))
mCustomButtons.button_y_XBOX = elem->get<std::string>(PREFIX "button_y_XBOX");
if (elem->has(PREFIX "button_start_XBOX"))
mCustomButtons.button_start_XBOX = elem->get<std::string>(PREFIX "button_start_XBOX");
if (elem->has(PREFIX "button_back_XBOX"))
mCustomButtons.button_back_XBOX = elem->get<std::string>(PREFIX "button_back_XBOX");
if (elem->has(PREFIX "button_start_XBOX360"))
mCustomButtons.button_start_XBOX360 = elem->get<std::string>(PREFIX "button_start_XBOX360");
if (elem->has(PREFIX "button_back_XBOX360"))
mCustomButtons.button_back_XBOX360 = elem->get<std::string>(PREFIX "button_back_XBOX360");
}

View file

@ -133,6 +133,8 @@ void Settings::setDefaults()
mStringMap["GamelistViewStyle"] = {"automatic", "automatic"};
mStringMap["TransitionStyle"] = {"slide", "slide"};
mStringMap["ThemeSet"] = {"rbsimple-DE", "rbsimple-DE"};
mStringMap["ThemeVariant"] = {"", ""};
mStringMap["ThemeAspectRatio"] = {"", ""};
mStringMap["UIMode"] = {"full", "full"};
mStringMap["DefaultSortOrder"] = {"filename, ascending", "filename, ascending"};
mStringMap["MenuOpeningEffect"] = {"scale-up", "scale-up"};

View file

@ -33,18 +33,20 @@ std::shared_ptr<Sound> Sound::getFromTheme(ThemeData* const theme,
const std::string& view,
const std::string& element)
{
std::string elemName {element.substr(6, std::string::npos)};
if (theme == nullptr) {
LOG(LogDebug) << "Sound::getFromTheme(): Using fallback sound file for \"" << element
LOG(LogDebug) << "Sound::getFromTheme(): Using fallback sound file for \"" << elemName
<< "\"";
return get(ResourceManager::getInstance().getResourcePath(":/sounds/" + element + ".wav"));
return get(ResourceManager::getInstance().getResourcePath(":/sounds/" + elemName + ".wav"));
}
LOG(LogDebug) << "Sound::getFromTheme(): Looking for tag <sound name=\"" << element << "\">";
LOG(LogDebug) << "Sound::getFromTheme(): Looking for tag <sound name=\"" << elemName << "\">";
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "sound");
if (!elem || !elem->has("path")) {
LOG(LogDebug) << "Sound::getFromTheme(): Tag not found, using fallback sound file";
return get(ResourceManager::getInstance().getResourcePath(":/sounds/" + element + ".wav"));
return get(ResourceManager::getInstance().getResourcePath(":/sounds/" + elemName + ".wav"));
}
LOG(LogDebug) << "Sound::getFromTheme(): Tag found, ready to load theme sound file";
@ -79,8 +81,7 @@ void Sound::init()
Uint8* data = nullptr;
Uint32 dlen = 0;
if (SDL_LoadWAV(mPath.c_str(), &wave, &data, &dlen) == nullptr) {
LOG(LogError) << "Failed to load theme navigation sound file:";
LOG(LogError) << SDL_GetError();
LOG(LogError) << "Failed to load theme navigation sound file: " << SDL_GetError();
return;
}
@ -90,14 +91,12 @@ void Sound::init()
AudioManager::sAudioFormat.channels, AudioManager::sAudioFormat.freq);
if (conversionStream == nullptr) {
LOG(LogError) << "Failed to create sample conversion stream:";
LOG(LogError) << SDL_GetError();
LOG(LogError) << "Failed to create sample conversion stream: " << SDL_GetError();
return;
}
if (SDL_AudioStreamPut(conversionStream, data, dlen) == -1) {
LOG(LogError) << "Failed to put samples in the conversion stream:";
LOG(LogError) << SDL_GetError();
LOG(LogError) << "Failed to put samples in the conversion stream: " << SDL_GetError();
SDL_FreeAudioStream(conversionStream);
return;
}
@ -106,8 +105,7 @@ void Sound::init()
Uint8* converted = new Uint8[sampleLength];
if (SDL_AudioStreamGet(conversionStream, converted, sampleLength) == -1) {
LOG(LogError) << "Failed to convert sound file '" << mPath << "':";
LOG(LogError) << SDL_GetError();
LOG(LogError) << "Failed to convert sound file '" << mPath << "': " << SDL_GetError();
SDL_FreeAudioStream(conversionStream);
delete[] converted;
return;
@ -209,13 +207,13 @@ void NavigationSounds::loadThemeNavigationSounds(ThemeData* const theme)
"Theme set does not include navigation sound support, using fallback sounds";
}
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowse"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselect"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "select"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "back"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "scroll"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "favorite"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "launch"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_systembrowse"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_quicksysselect"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_select"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_back"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_scroll"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_favorite"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "sound_launch"));
}
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,9 @@
// EmulationStation Desktop Edition
// ThemeData.h
//
// Finds available themes on the file system and loads these,
// including the parsing of individual theme components
// (includes, features, variables, views, elements).
// Finds available themes on the file system and loads and parses these.
// Basic error checking for valid elements and data types is done here,
// with additional validation handled by the individual components.
//
#ifndef ES_CORE_THEME_DATA_H
@ -158,8 +158,31 @@ public:
ThemeData();
class ThemeView
{
public:
std::map<std::string, ThemeElement> elements;
std::vector<std::string> legacyOrderedKeys;
};
struct ThemeVariant {
std::string name;
std::string label;
bool selectable;
bool override;
std::string overrideTrigger;
std::string overrideVariant;
};
struct ThemeCapability {
std::vector<ThemeVariant> variants;
std::vector<std::string> aspectRatios;
bool legacyTheme;
};
struct ThemeSet {
std::string path;
ThemeCapability capabilities;
std::string getName() const { return Utils::FileSystem::getStem(path); }
std::string getThemePath(const std::string& system) const
@ -168,21 +191,22 @@ public:
}
};
// Throws ThemeException.
void loadFile(const std::map<std::string, std::string>& sysDataMap, const std::string& path);
bool hasView(const std::string& view);
ThemeView& getViewElements(std::string view) { return mViews[view]; }
static std::vector<GuiComponent*> makeExtras(const std::shared_ptr<ThemeData>& theme,
const std::string& view);
// If expectedType is an empty string, then do no type checking.
const ThemeElement* getElement(const std::string& view,
const std::string& element,
const std::string& expectedType) const;
static std::map<std::string, ThemeSet> getThemeSets();
static std::map<std::string, ThemeSet>& getThemeSets();
static std::string getThemeFromCurrentSet(const std::string& system);
const bool isLegacyTheme() { return mLegacyTheme; }
enum ElementPropertyType {
NORMALIZED_RECT,
NORMALIZED_PAIR,
@ -196,26 +220,16 @@ public:
std::map<std::string, std::string> mVariables;
private:
class ThemeView
{
public:
std::map<std::string, ThemeElement> elements;
std::vector<std::string> orderedKeys;
};
static std::map<std::string, std::map<std::string, ElementPropertyType>> sElementMap;
static std::vector<std::string> sSupportedFeatures;
static std::vector<std::string> sSupportedViews;
std::deque<std::string> mPaths;
float mVersion;
static const std::shared_ptr<ThemeData> getDefault();
unsigned int getHexColor(const std::string& str);
std::string resolvePlaceholders(const std::string& in);
static ThemeCapability parseThemeCapabilities(const std::string& path);
void parseIncludes(const pugi::xml_node& themeRoot);
void parseFeatures(const pugi::xml_node& themeRoot);
void parseVariants(const pugi::xml_node& themeRoot);
void parseAspectRatios(const pugi::xml_node& themeRoot);
void parseVariables(const pugi::xml_node& root);
void parseViews(const pugi::xml_node& themeRoot);
void parseView(const pugi::xml_node& viewNode, ThemeView& view);
@ -223,7 +237,24 @@ private:
const std::map<std::string, ElementPropertyType>& typeMap,
ThemeElement& element);
static std::map<std::string, std::map<std::string, ElementPropertyType>> sElementMap;
static std::map<std::string, std::map<std::string, std::string>> sPropertyAttributeMap;
static std::vector<std::string> sLegacySupportedFeatures;
static std::vector<std::string> sLegacySupportedViews;
static std::vector<std::string> sSupportedViews;
static std::vector<std::string> sSupportedAspectRatios;
static inline std::map<std::string, ThemeSet> mThemeSets;
std::map<std::string, ThemeData::ThemeSet>::iterator mCurrentThemeSet;
std::map<std::string, ThemeView> mViews;
std::deque<std::string> mPaths;
std::vector<std::string> mVariants;
std::vector<std::string> mAspectRatios;
std::string mSelectedVariant;
std::string mSelectedAspectRatio;
bool mLegacyTheme;
};
#endif // ES_CORE_THEME_DATA_H

View file

@ -290,8 +290,10 @@ void BadgeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
for (auto slot : slots) {
if (std::find(mBadgeTypes.cbegin(), mBadgeTypes.cend(), slot) != mBadgeTypes.end()) {
if (properties & PATH && elem->has(slot))
mBadgeIcons[slot] = elem->get<std::string>(slot);
// The "badge_" string is required as ThemeData adds this as a prefix to avoid
// name collisions when using XML attributes.
if (properties & PATH && elem->has("badge_" + slot))
mBadgeIcons[slot] = elem->get<std::string>("badge_" + slot);
FlexboxComponent::FlexboxItem item;
item.label = slot;
@ -309,8 +311,9 @@ void BadgeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
}
for (auto& gameController : sGameControllers) {
if (properties & PATH && elem->has(gameController.shortName))
gameController.fileName = elem->get<std::string>(gameController.shortName);
if (properties & PATH && elem->has("controller_" + gameController.shortName))
gameController.fileName =
elem->get<std::string>("controller_" + gameController.shortName);
}
GuiComponent::applyTheme(theme, view, element, properties);

View file

@ -51,7 +51,7 @@ public:
unsigned int properties) override;
private:
inline static std::vector<GameControllers> sGameControllers;
static inline std::vector<GameControllers> sGameControllers;
std::vector<FlexboxComponent::FlexboxItem> mFlexboxItems;
FlexboxComponent mFlexboxComponent;

View file

@ -111,7 +111,6 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
unsigned int properties)
{
GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "datetime");

View file

@ -475,7 +475,6 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
unsigned int properties)
{
using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element,
(properties ^ ThemeFlags::SIZE) |
((properties & (ThemeFlags::SIZE | POSITION)) ? ORIGIN : 0));
@ -505,6 +504,9 @@ void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setImage(elem->get<std::string>("path"), tile);
}
if (properties & METADATA && elem->has("metadata"))
setMetadataField(elem->get<std::string>("metadata"));
if (properties & COLOR) {
if (elem->has("color"))
setColorShift(elem->get<unsigned int>("color"));

View file

@ -214,7 +214,6 @@ void LottieComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
unsigned int properties)
{
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "animation")};
if (elem->has("size")) {

View file

@ -50,8 +50,8 @@ private:
std::vector<uint8_t> mPictureRGBA;
std::unordered_map<size_t, std::vector<uint8_t>> mFrameCache;
// Set a 1024 MiB total Lottie animation cache as default.
inline static size_t mMaxTotalFrameCache = 1024 * 1024 * 1024;
inline static size_t mTotalFrameCache;
static inline size_t mMaxTotalFrameCache = 1024 * 1024 * 1024;
static inline size_t mTotalFrameCache;
bool mCacheFrames;
size_t mMaxCacheSize;
size_t mCacheSize;

View file

@ -211,7 +211,6 @@ void RatingComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
unsigned int properties)
{
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "rating");
if (!elem)
return;

View file

@ -291,11 +291,15 @@ void TextComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& element,
unsigned int properties)
{
using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags;
std::string elementType {"text"};
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "text");
if (element.substr(0, 13) == "gamelistinfo_")
elementType = "gamelistinfo";
const ThemeData::ThemeElement* elem = theme->getElement(view, element, elementType);
if (!elem)
return;

View file

@ -75,13 +75,13 @@ bool VideoComponent::setVideo(std::string path)
return false;
}
void VideoComponent::setImage(std::string path)
void VideoComponent::setImage(const std::string& path, bool tile, bool linearMagnify)
{
// Check that the image has changed.
if (path == mStaticImagePath)
return;
mStaticImage.setImage(path);
mStaticImage.setImage(path, tile, linearMagnify);
mStaticImagePath = path;
}
@ -212,7 +212,6 @@ void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
unsigned int properties)
{
using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element,
(properties ^ ThemeFlags::SIZE) |
((properties & (ThemeFlags::SIZE | POSITION)) ? ORIGIN : 0));
@ -245,14 +244,24 @@ void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
if (elem->has("default"))
mConfig.defaultVideoPath = elem->get<std::string>("default");
if (elem->has("path"))
mConfig.staticVideoPath = elem->get<std::string>("path");
if ((properties & ThemeFlags::DELAY) && elem->has("delay"))
mConfig.startDelay = static_cast<unsigned>((elem->get<float>("delay") * 1000.0f));
if (elem->has("showSnapshotNoVideo"))
if (!theme->isLegacyTheme())
mConfig.showSnapshotNoVideo = true;
else if (elem->has("showSnapshotNoVideo"))
mConfig.showSnapshotNoVideo = elem->get<bool>("showSnapshotNoVideo");
if (elem->has("showSnapshotDelay"))
if (!theme->isLegacyTheme() && mConfig.startDelay != 0)
mConfig.showSnapshotDelay = true;
else if (elem->has("showSnapshotDelay"))
mConfig.showSnapshotDelay = elem->get<bool>("showSnapshotDelay");
if (properties & METADATA && elem->has("imageMetadata"))
setMetadataField(elem->get<std::string>("imageMetadata"));
}
std::vector<HelpPrompt> VideoComponent::getHelpPrompts()

View file

@ -26,6 +26,7 @@ class VideoComponent : public GuiComponent
bool showSnapshotNoVideo;
bool showSnapshotDelay;
std::string defaultVideoPath;
std::string staticVideoPath;
};
public:
@ -36,8 +37,10 @@ public:
bool setVideo(std::string path);
// Configures the component to show the default video.
void setDefaultVideo() { setVideo(mConfig.defaultVideoPath); }
// Configures the component to show the static video.
void setStaticVideo() { setVideo(mConfig.staticVideoPath); }
// Loads a static image that is displayed if the video cannot be played.
void setImage(std::string path);
void setImage(const std::string& path, bool tile = false, bool linearMagnify = false);
// Sets whether we're in media viewer mode.
void setMediaViewerMode(bool isMediaViewer) { mMediaViewerMode = isMediaViewer; }
// Sets whether we're in screensaver mode.
@ -45,6 +48,8 @@ public:
// Set the opacity for the embedded static image.
void setOpacity(unsigned char opacity) override { mOpacity = opacity; }
bool hasStaticVideo() { return !mConfig.staticVideoPath.empty(); }
void onShow() override;
void onHide() override;
void onStopVideo() override;

View file

@ -215,6 +215,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
Renderer::drawTriangleStrips(&vertices[0], 4, trans);
}
else {
if (mVisible)
VideoComponent::renderSnapshot(parentTrans);
}
}