From afe249c5fc3bdc2e57ac8afac9ccea57cb4d22bb Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 4 Feb 2022 21:42:08 +0100 Subject: [PATCH 01/82] Added CarouselComponent skeleton. --- es-core/CMakeLists.txt | 2 ++ es-core/src/components/CarouselComponent.cpp | 14 +++++++++++++ es-core/src/components/CarouselComponent.h | 22 ++++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 es-core/src/components/CarouselComponent.cpp create mode 100644 es-core/src/components/CarouselComponent.h diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index d028db4dd..ddbde8dc3 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -36,6 +36,7 @@ set(CORE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgeComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/CarouselComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h @@ -114,6 +115,7 @@ set(CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BadgeComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/BusyComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/CarouselComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp new file mode 100644 index 000000000..8ca97d03f --- /dev/null +++ b/es-core/src/components/CarouselComponent.cpp @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// CarouselComponent.cpp +// +// Carousel. +// + +#include "components/CarouselComponent.h" + +CarouselComponent::CarouselComponent() +{ + // +} diff --git a/es-core/src/components/CarouselComponent.h b/es-core/src/components/CarouselComponent.h new file mode 100644 index 000000000..e17f2022c --- /dev/null +++ b/es-core/src/components/CarouselComponent.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// CarouselComponent.h +// +// Carousel. +// + +#include "GuiComponent.h" + +#ifndef ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H +#define ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H + +class CarouselComponent : public GuiComponent +{ +public: + CarouselComponent(); + +private: +}; + +#endif // ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H From d564a234c1daf93b831aeca0585a56e3e9ce864a Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 6 Feb 2022 13:58:50 +0100 Subject: [PATCH 02/82] Implemented a better legacy theme detection in GamelistView. --- es-app/src/views/GamelistView.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index 453c086f7..c16d632b9 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -87,7 +87,12 @@ void GamelistView::onShow() void GamelistView::onThemeChanged(const std::shared_ptr& theme) { - mLegacyMode = mTheme->isLegacyTheme(); + auto themeSets = ThemeData::getThemeSets(); + std::map::const_iterator selectedSet { + themeSets.find(Settings::getInstance()->getString("ThemeSet"))}; + + assert(selectedSet != themeSets.cend()); + mLegacyMode = selectedSet->second.capabilities.legacyTheme; if (mLegacyMode) { legacyOnThemeChanged(theme); From b5d49e9b43178eb3d7676df5746ac86d8b77a885 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 6 Feb 2022 14:01:40 +0100 Subject: [PATCH 03/82] Migrated the carousel code from SystemView to CarouselComponent. --- es-app/src/views/SystemView.cpp | 779 +++++-------------- es-app/src/views/SystemView.h | 104 +-- es-app/src/views/ViewController.cpp | 22 +- es-core/src/components/CarouselComponent.cpp | 448 ++++++++++- es-core/src/components/CarouselComponent.h | 79 +- 5 files changed, 756 insertions(+), 676 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 82003464d..17d60a702 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -11,7 +11,6 @@ #include "Log.h" #include "Settings.h" #include "Sound.h" -#include "SystemData.h" #include "UIModeController.h" #include "Window.h" #include "animations/LambdaAnimation.h" @@ -31,211 +30,120 @@ namespace } // namespace SystemView::SystemView() - : IList {LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP} - , mSystemInfo {"SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER} - , mPreviousScrollVelocity {0} + : mCamOffset {0.0f} + , mFadeOpacity {0.0f} , mUpdatedGameCount {false} , mViewNeedsReload {true} , mLegacyMode {false} { - mCamOffset = 0; - mExtrasCamOffset = 0; - mExtrasFadeOpacity = 0.0f; + mCarousel = std::make_unique(); + mSystemInfo = std::make_unique("SYSTEM INFO", Font::get(FONT_SIZE_SMALL), + 0x33333300, ALIGN_CENTER); setSize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); + populate(); + + mCarousel->setCursorChangedCallback([&](const CursorState& state) { onCursorChanged(state); }); + mCarousel->setCancelTransitionsCallback( + [&] { ViewController::getInstance()->cancelViewTransitions(); }); } SystemView::~SystemView() { - // TEMPORARY - // if (mLegacyMode) { - // Delete any existing extras. - for (auto entry : mEntries) { - for (auto extra : entry.data.backgroundExtras) - delete extra; - entry.data.backgroundExtras.clear(); + if (mLegacyMode) { + // Delete any existing extras. + for (auto& entry : mElements) { + for (auto extra : entry.legacyExtras) + delete extra; + entry.legacyExtras.clear(); + } } - // } } void SystemView::populate() { - mEntries.clear(); + auto themeSets = ThemeData::getThemeSets(); + std::map::const_iterator selectedSet = + themeSets.find(Settings::getInstance()->getString("ThemeSet")); + + assert(selectedSet != themeSets.cend()); + mLegacyMode = selectedSet->second.capabilities.legacyTheme; for (auto it : SystemData::sSystemVector) { - const std::shared_ptr& theme = it->getTheme(); - - mLegacyMode = theme->isLegacyTheme(); + const std::shared_ptr& theme {it->getTheme()}; if (mViewNeedsReload) getViewElements(theme); - Entry e; - e.name = it->getName(); - e.object = it; - - // Component offset. Used for positioning placeholders. - glm::vec3 offsetLogo {0.0f, 0.0f, 0.0f}; - glm::vec3 offsetLogoPlaceholderText {0.0f, 0.0f, 0.0f}; - - // Make logo. - const ThemeData::ThemeElement* logoElem { - theme->getElement("system", "image_logo", "image")}; - if (logoElem) { - std::string path; - if (logoElem->has("path")) - path = logoElem->get("path"); - std::string defaultPath { - logoElem->has("default") ? logoElem->get("default") : ""}; - if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) || - (!defaultPath.empty() && ResourceManager::getInstance().fileExists(defaultPath))) { - auto* logo = new ImageComponent(false, false); - logo->setMaxSize(glm::round(mCarousel.logoSize * mCarousel.logoScale)); - logo->applyTheme(theme, "system", "image_logo", - ThemeFlags::PATH | ThemeFlags::COLOR); - logo->setRotateByTargetSize(true); - e.data.logo = std::shared_ptr(logo); - } - } - - // No logo available? Make placeholder. - if (!e.data.logo) { - - glm::vec2 resolution {static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}; - glm::vec3 center {resolution.x / 2.0f, resolution.y / 2.0f, 1.0f}; - - // Placeholder Image. - logoElem = theme->getElement("system", "image_logoPlaceholderImage", "image"); - if (logoElem) { - auto path = logoElem->get("path"); - std::string defaultPath = - logoElem->has("default") ? logoElem->get("default") : ""; - if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) || - (!defaultPath.empty() && - ResourceManager::getInstance().fileExists(defaultPath))) { - auto* logo = new ImageComponent(false, false); - logo->applyTheme(theme, "system", "image_logoPlaceholderImage", - ThemeFlags::ALL); - if (!logoElem->has("size")) - logo->setMaxSize(mCarousel.logoSize * mCarousel.logoScale); - offsetLogo = logo->getPosition() - center; - logo->setRotateByTargetSize(true); - e.data.logo = std::shared_ptr(logo); - } - } - - // Placeholder Text. - const ThemeData::ThemeElement* logoPlaceholderText = - 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), - 0x000000FF, ALIGN_CENTER); - text->setSize(mCarousel.logoSize * mCarousel.logoScale); - if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) { - text->setHorizontalAlignment(mCarousel.logoAlignment); - text->setVerticalAlignment(ALIGN_CENTER); - } - else { - text->setHorizontalAlignment(ALIGN_CENTER); - text->setVerticalAlignment(mCarousel.logoAlignment); - } - text->applyTheme(it->getTheme(), "system", "text_logoPlaceholderText", - ThemeFlags::ALL); - if (!e.data.logo) { - e.data.logo = std::shared_ptr(text); - offsetLogo = text->getPosition() - center; - // text->setVisible(true); - } - else { - e.data.logoPlaceholderText = std::shared_ptr(text); - offsetLogoPlaceholderText = text->getPosition() - center; - } - } - else { - // Fallback to legacy centered placeholder text. - auto* text = new TextComponent(it->getName(), Font::get(FONT_SIZE_LARGE), - 0x000000FF, ALIGN_CENTER); - text->setSize(mCarousel.logoSize * mCarousel.logoScale); - text->applyTheme(it->getTheme(), "system", "logoText", - ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | - ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING | - ThemeFlags::TEXT); - e.data.logo = std::shared_ptr(text); - - if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) { - text->setHorizontalAlignment(mCarousel.logoAlignment); - text->setVerticalAlignment(ALIGN_CENTER); - } - else { - text->setHorizontalAlignment(ALIGN_CENTER); - text->setVerticalAlignment(mCarousel.logoAlignment); - } - } - } - - if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) { - if (mCarousel.logoAlignment == ALIGN_LEFT) - e.data.logo->setOrigin(0, 0.5); - else if (mCarousel.logoAlignment == ALIGN_RIGHT) - e.data.logo->setOrigin(1.0, 0.5); - else - e.data.logo->setOrigin(0.5, 0.5); - } - else { - if (mCarousel.logoAlignment == ALIGN_TOP) - e.data.logo->setOrigin(0.5, 0); - else if (mCarousel.logoAlignment == ALIGN_BOTTOM) - e.data.logo->setOrigin(0.5, 1); - else - e.data.logo->setOrigin(0.5, 0.5); - } - - glm::vec2 denormalized {mCarousel.logoSize * e.data.logo->getOrigin()}; - glm::vec3 v = {denormalized.x, denormalized.y, 0.0f}; - e.data.logo->setPosition(v + offsetLogo); - if (e.data.logoPlaceholderText) - e.data.logoPlaceholderText->setPosition(v + offsetLogoPlaceholderText); - if (mLegacyMode) { - // Make background extras. - e.data.backgroundExtras = ThemeData::makeExtras(it->getTheme(), "system"); + SystemViewElements elements; + elements.name = it->getName(); + elements.legacyExtras = ThemeData::makeExtras(theme, "system"); // Sort the extras by z-index. std::stable_sort( - e.data.backgroundExtras.begin(), e.data.backgroundExtras.end(), + elements.legacyExtras.begin(), elements.legacyExtras.end(), [](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); }); + + mElements.emplace_back(std::move(elements)); } - else { + if (!mLegacyMode) { + SystemViewElements elements; if (theme->hasView("system")) { + elements.name = it->getName(); for (auto& element : theme->getViewElements("system").elements) { if (element.second.type == "image") { - e.data.imageComponents.push_back(std::make_shared()); - e.data.imageComponents.back()->setDefaultZIndex(30.0f); - e.data.imageComponents.back()->applyTheme(theme, "system", element.first, - ThemeFlags::ALL); - if (e.data.imageComponents.back()->getMetadataField() != "") - e.data.imageComponents.back()->setScrollHide(true); + elements.imageComponents.emplace_back(std::make_unique()); + elements.imageComponents.back()->setDefaultZIndex(30.0f); + elements.imageComponents.back()->applyTheme(theme, "system", element.first, + ThemeFlags::ALL); + if (elements.imageComponents.back()->getMetadataField() != "") + elements.imageComponents.back()->setScrollHide(true); + elements.children.emplace_back(elements.imageComponents.back().get()); } else if (element.second.type == "text") { - e.data.textComponents.push_back(std::make_unique()); - e.data.textComponents.back()->setDefaultZIndex(40.0f); - e.data.textComponents.back()->applyTheme(theme, "system", element.first, - ThemeFlags::ALL); - if (e.data.textComponents.back()->getMetadataField() != "") - e.data.textComponents.back()->setScrollHide(true); + elements.textComponents.push_back(std::make_unique()); + elements.textComponents.back()->setDefaultZIndex(40.0f); + elements.textComponents.back()->applyTheme(theme, "system", element.first, + ThemeFlags::ALL); + if (elements.textComponents.back()->getMetadataField() != "") + elements.textComponents.back()->setScrollHide(true); + elements.children.emplace_back(elements.textComponents.back().get()); } } } + + elements.children.emplace_back(mCarousel.get()); + elements.children.emplace_back(mSystemInfo.get()); + + std::stable_sort( + elements.children.begin(), elements.children.end(), + [](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); }); + + std::stable_sort(elements.imageComponents.begin(), elements.imageComponents.end(), + [](const std::unique_ptr& a, + const std::unique_ptr& b) { + return b->getZIndex() > a->getZIndex(); + }); + std::stable_sort(elements.textComponents.begin(), elements.textComponents.end(), + [](const std::unique_ptr& a, + const std::unique_ptr& b) { + return b->getZIndex() > a->getZIndex(); + }); + mElements.emplace_back(std::move(elements)); } - this->add(e); + CarouselComponent::Entry entry; + entry.name = it->getName(); + entry.object = it; + + mCarousel->addEntry(theme, entry); } - if (mEntries.empty()) { + + if (mCarousel->getNumEntries() == 0) { // Something is wrong, there is not a single system to show, check if UI mode is not full. if (!UIModeController::getInstance()->isUIModeFull()) { Settings::getInstance()->setString("UIMode", "full"); @@ -249,32 +157,35 @@ void SystemView::populate() void SystemView::updateGameCount() { - std::pair gameCount = getSelected()->getDisplayedGameCount(); + std::pair gameCount = + mCarousel->getSelected()->getDisplayedGameCount(); std::stringstream ss; - if (!getSelected()->isGameSystem()) + if (!mCarousel->getSelected()->isGameSystem()) ss << "CONFIGURATION"; - else if (getSelected()->isCollection() && (getSelected()->getName() == "favorites")) + else if (mCarousel->getSelected()->isCollection() && + (mCarousel->getSelected()->getName() == "favorites")) ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S"); // The "recent" gamelist has probably been trimmed after sorting, so we'll cap it at // its maximum limit of 50 games. - else if (getSelected()->isCollection() && (getSelected()->getName() == "recent")) + else if (mCarousel->getSelected()->isCollection() && + (mCarousel->getSelected()->getName() == "recent")) ss << (gameCount.first > 50 ? 50 : gameCount.first) << " GAME" << (gameCount.first == 1 ? " " : "S"); else ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S ") << "(" << gameCount.second << " FAVORITE" << (gameCount.second == 1 ? ")" : "S)"); - mSystemInfo.setText(ss.str()); + mSystemInfo->setText(ss.str()); } void SystemView::goToSystem(SystemData* system, bool animate) { - setCursor(system); + mCarousel->setCursor(system); updateGameCount(); if (!animate) - finishAnimation(0); + finishSystemAnimation(0); } bool SystemView::input(InputConfig* config, Input input) @@ -287,39 +198,9 @@ bool SystemView::input(InputConfig* config, Input input) return true; } - switch (mCarousel.type) { - case VERTICAL: - case VERTICAL_WHEEL: - if (config->isMappedLike("up", input)) { - ViewController::getInstance()->cancelViewTransitions(); - listInput(-1); - return true; - } - if (config->isMappedLike("down", input)) { - ViewController::getInstance()->cancelViewTransitions(); - listInput(1); - return true; - } - break; - case HORIZONTAL: - case HORIZONTAL_WHEEL: - default: - if (config->isMappedLike("left", input)) { - ViewController::getInstance()->cancelViewTransitions(); - listInput(-1); - return true; - } - if (config->isMappedLike("right", input)) { - ViewController::getInstance()->cancelViewTransitions(); - listInput(1); - return true; - } - break; - } - if (config->isMappedTo("a", input)) { - stopScrolling(); - ViewController::getInstance()->goToGamelist(getSelected()); + mCarousel->stopScrolling(); + ViewController::getInstance()->goToGamelist(mCarousel->getSelected()); NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); return true; } @@ -328,7 +209,7 @@ bool SystemView::input(InputConfig* config, Input input) config->isMappedTo("rightthumbstickclick", input))) { // Get a random system and jump to it. NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND); - setCursor(SystemData::getRandomSystem(getSelected())); + mCarousel->setCursor(SystemData::getRandomSystem(mCarousel->getSelected())); return true; } @@ -343,18 +224,13 @@ bool SystemView::input(InputConfig* config, Input input) return true; } } - else { - if (config->isMappedLike("left", input) || config->isMappedLike("right", input) || - config->isMappedLike("up", input) || config->isMappedLike("down", input)) - listInput(0); - } - return GuiComponent::input(config, input); + return mCarousel->input(config, input); } void SystemView::update(int deltaTime) { - listUpdate(deltaTime); + mCarousel->update(deltaTime); GuiComponent::update(deltaTime); } @@ -363,52 +239,29 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) // Update help style. updateHelpPrompts(); - float startPos = mCamOffset; - float posMax = static_cast(mEntries.size()); - float target = static_cast(mCursor); + int scrollVelocity {mCarousel->getScrollingVelocity()}; + + float startPos {mCamOffset}; + float posMax {static_cast(mCarousel->getNumEntries())}; + float target {static_cast(mCarousel->getCursor())}; // Find the shortest path to the target. - float endPos = target; // Directly. - float dist = fabs(endPos - startPos); + float endPos {target}; // Directly. + float dist {fabs(endPos - startPos)}; - if (fabs(target + posMax - startPos - mScrollVelocity) < dist) + if (fabs(target + posMax - startPos - scrollVelocity) < dist) endPos = target + posMax; // Loop around the end (0 -> max). - if (fabs(target - posMax - startPos - mScrollVelocity) < dist) + if (fabs(target - posMax - startPos - scrollVelocity) < dist) endPos = target - posMax; // Loop around the start (max - 1 -> -1). - // This logic is only needed when there are two game systems, to prevent ugly jumps back - // an forth when selecting the same direction rapidly several times in a row. - if (posMax == 2) { - if (mPreviousScrollVelocity == 0) - mPreviousScrollVelocity = mScrollVelocity; - else if (mScrollVelocity < 0 && startPos < endPos) - mPreviousScrollVelocity = -1; - else if (mScrollVelocity > 0 && startPos > endPos) - mPreviousScrollVelocity = 1; - } - - std::string transition_style = Settings::getInstance()->getString("TransitionStyle"); - - // To prevent ugly jumps with two systems when quickly repeating the same direction. - if (mPreviousScrollVelocity != 0 && posMax == 2 && mScrollVelocity == mPreviousScrollVelocity) { - if (fabs(endPos - startPos) < 0.5 || fabs(endPos - startPos) > 1.5) { - (mScrollVelocity < 0) ? endPos -= 1 : endPos += 1; - (mCursor == 0) ? mCursor = 1 : mCursor = 0; - updateGameCount(); - return; - } - } - - // No need to animate transition, we're not going anywhere (probably mEntries.size() == 1). - if (endPos == mCamOffset && endPos == mExtrasCamOffset) - return; + std::string transition_style {Settings::getInstance()->getString("TransitionStyle")}; Animation* anim; if (transition_style == "fade") { - float startExtrasFade = mExtrasFadeOpacity; + float startFade {mFadeOpacity}; anim = new LambdaAnimation( - [this, startExtrasFade, startPos, endPos, posMax](float t) { + [this, startFade, startPos, endPos, posMax](float t) { t -= 1; float f = glm::mix(startPos, endPos, t * t * t + 1); if (f < 0) @@ -416,23 +269,21 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) if (f >= posMax) f -= posMax; - this->mCamOffset = f; - t += 1; if (t < 0.3f) - this->mExtrasFadeOpacity = - glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startExtrasFade, 0.0f, 1.0f)); + this->mFadeOpacity = + glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f)); else if (t < 0.7f) - this->mExtrasFadeOpacity = 1.0f; + this->mFadeOpacity = 1.0f; else - this->mExtrasFadeOpacity = + this->mFadeOpacity = glm::mix(1.0f, 0.0f, glm::clamp((t - 0.6f) / 0.3f, 0.0f, 1.0f)); if (t > 0.5f) - this->mExtrasCamOffset = endPos; + this->mCamOffset = endPos; // Update the game count when the entire animation has been completed. - if (mExtrasFadeOpacity == 1.0f) + if (mFadeOpacity == 1.0f) updateGameCount(); }, 500); @@ -449,10 +300,9 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) f -= posMax; this->mCamOffset = f; - this->mExtrasCamOffset = f; // Hack to make the game count being updated in the middle of the animation. - bool update = false; + bool update {false}; if (endPos == -1.0f && fabs(fabs(posMax) - fabs(mCamOffset)) > 0.5f && !mUpdatedGameCount) { update = true; @@ -484,8 +334,7 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) if (f >= posMax) f -= posMax; - this->mCamOffset = f; - this->mExtrasCamOffset = endPos; + this->mCamOffset = endPos; }, 500); } @@ -495,46 +344,80 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) void SystemView::render(const glm::mat4& parentTrans) { - if (size() == 0) + if (mCarousel->getNumEntries() == 0) return; // Nothing to render. glm::mat4 trans {getTransform() * parentTrans}; - if (mCarousel.legacyZIndexMode) { - // Render all extras. - renderExtras(trans, INT16_MIN, INT16_MAX); + // Adding texture loading buffers depending on scrolling speed and status. + int bufferIndex {mCarousel->getScrollingVelocity() + 1}; - // Fade the screen if we're using fade transitions and we're currently transitioning. - // This basically renders a black rectangle on top of the currently visible extras - // (and beneath the carousel and help prompts). - if (mExtrasFadeOpacity) - renderFade(trans); + Renderer::pushClipRect(glm::ivec2 {}, + glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - // Always render the carousel on top so that it's not faded. - renderCarousel(trans); + for (int i = static_cast(mCamOffset) + logoBuffersLeft[bufferIndex]; + i <= static_cast(mCamOffset) + logoBuffersRight[bufferIndex]; ++i) { + int index {i}; + while (index < 0) + index += static_cast(mCarousel->getNumEntries()); + while (index >= static_cast(mCarousel->getNumEntries())) + index -= static_cast(mCarousel->getNumEntries()); + + if (mCarousel->isAnimationPlaying(0) || index == mCarousel->getCursor()) { + glm::mat4 elementTrans {trans}; + if (mCarousel->getType() == CarouselComponent::HORIZONTAL || + mCarousel->getType() == CarouselComponent::HORIZONTAL_WHEEL) + elementTrans = glm::translate(elementTrans, + glm::vec3 {(i - mCamOffset) * mSize.x, 0.0f, 0.0f}); + else + elementTrans = glm::translate(elementTrans, + glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}); + + Renderer::pushClipRect( + glm::ivec2 {static_cast(elementTrans[3].x), + static_cast(elementTrans[3].y)}, + glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); + + if (mLegacyMode && mElements.size() > static_cast(index)) { + for (auto element : mElements[index].legacyExtras) + element->render(elementTrans); + } + else if (mElements.size() > static_cast(index)) { + for (auto child : mElements[index].children) { + if (child == mCarousel.get()) { + // Render black above anything lower than the zIndex of the carousel + // if fade transitions are in use and we're transitioning. + if (mFadeOpacity) + renderFade(trans); + child->render(trans); + } + else { + child->render(elementTrans); + } + } + } + + if (mLegacyMode) + mSystemInfo->render(elementTrans); + + Renderer::popClipRect(); + } } - else { - // Render the extras that are below the carousel. - renderExtras(trans, INT16_MIN, mCarousel.zIndex); - // Fade the screen if we're using fade transitions and we're currently transitioning. - // This basically renders a black rectangle on top of the currently visible extras - // (and beneath the carousel and help prompts). - if (mExtrasFadeOpacity) + if (mLegacyMode) { + if (mFadeOpacity) renderFade(trans); - - // Render the carousel. - renderCarousel(trans); - - // Render the rest of the extras. - renderExtras(trans, mCarousel.zIndex, INT16_MAX); + mCarousel->render(trans); } + + Renderer::popClipRect(); } std::vector SystemView::getHelpPrompts() { std::vector prompts; - if (mCarousel.type == VERTICAL || mCarousel.type == VERTICAL_WHEEL) + if (mCarousel->getType() == CarouselComponent::VERTICAL || + mCarousel->getType() == CarouselComponent::VERTICAL_WHEEL) prompts.push_back(HelpPrompt("up/down", "choose")); else prompts.push_back(HelpPrompt("left/right", "choose")); @@ -554,7 +437,7 @@ std::vector SystemView::getHelpPrompts() HelpStyle SystemView::getHelpStyle() { HelpStyle style; - style.applyTheme(mEntries.at(mCursor).object->getTheme(), "system"); + style.applyTheme(mCarousel->getEntry(mCarousel->getCursor()).object->getTheme(), "system"); return style; } @@ -569,323 +452,33 @@ void SystemView::getViewElements(const std::shared_ptr& theme) { LOG(LogDebug) << "SystemView::getViewElements()"; - getDefaultElements(); + if (theme->hasView("system")) + mViewNeedsReload = false; + else + mViewNeedsReload = true; - if (!theme->hasView("system")) - return; + mCarousel->applyTheme(theme, "system", "carousel_systemcarousel", ThemeFlags::ALL); - const ThemeData::ThemeElement* carouselElem = - theme->getElement("system", "carousel_systemcarousel", "carousel"); + // System info bar. + mSystemInfo->setSize(mSize.x, mSystemInfo->getFont()->getLetterHeight() * 2.2f); + mSystemInfo->setPosition(0.0f, mCarousel->getPosition().y + mCarousel->getSize().y); + mSystemInfo->setBackgroundColor(0xDDDDDDD8); + mSystemInfo->setRenderBackground(true); + mSystemInfo->setFont(Font::get(static_cast(0.035f * mSize.y), Font::getDefaultPath())); + mSystemInfo->setColor(0x000000FF); + mSystemInfo->setZIndex(49.0f); + mSystemInfo->setDefaultZIndex(49.0f); - if (carouselElem) - getCarouselFromTheme(carouselElem); - - const ThemeData::ThemeElement* sysInfoElem = - theme->getElement("system", "text_systemInfo", "text"); + const ThemeData::ThemeElement* sysInfoElem { + theme->getElement("system", "text_systemInfo", "text")}; if (sysInfoElem) - mSystemInfo.applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL); - - mViewNeedsReload = false; -} - -void SystemView::renderCarousel(const glm::mat4& trans) -{ - // Background box behind logos. - glm::mat4 carouselTrans {trans}; - carouselTrans = - glm::translate(carouselTrans, glm::vec3 {mCarousel.pos.x, mCarousel.pos.y, 0.0f}); - carouselTrans = glm::translate(carouselTrans, - glm::vec3 {mCarousel.origin.x * mCarousel.size.x * -1.0f, - mCarousel.origin.y * mCarousel.size.y * -1.0f, 0.0f}); - - glm::vec2 clipPos {carouselTrans[3].x, carouselTrans[3].y}; - Renderer::pushClipRect(glm::ivec2 {static_cast(std::round(clipPos.x)), - static_cast(std::round(clipPos.y))}, - glm::ivec2 {static_cast(std::round(mCarousel.size.x)), - static_cast(std::round(mCarousel.size.y))}); - - Renderer::setMatrix(carouselTrans); - Renderer::drawRect(0.0f, 0.0f, mCarousel.size.x, mCarousel.size.y, mCarousel.color, - mCarousel.colorEnd, mCarousel.colorGradientHorizontal); - - // Draw logos. - // Note: logoSpacing will also include the size of the logo itself. - glm::vec2 logoSpacing {}; - float xOff = 0.0f; - float yOff = 0.0f; - - switch (mCarousel.type) { - case VERTICAL_WHEEL: { - yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f - (mCamOffset * logoSpacing.y); - if (mCarousel.logoAlignment == ALIGN_LEFT) - xOff = mCarousel.logoSize.x / 10.0f; - else if (mCarousel.logoAlignment == ALIGN_RIGHT) - xOff = mCarousel.size.x - (mCarousel.logoSize.x * 1.1f); - else - xOff = (mCarousel.size.x - mCarousel.logoSize.x) / 2.0f; - break; - } - case VERTICAL: { - logoSpacing.y = ((mCarousel.size.y - (mCarousel.logoSize.y * mCarousel.maxLogoCount)) / - (mCarousel.maxLogoCount)) + - mCarousel.logoSize.y; - yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f - (mCamOffset * logoSpacing.y); - if (mCarousel.logoAlignment == ALIGN_LEFT) - xOff = mCarousel.logoSize.x / 10.0f; - else if (mCarousel.logoAlignment == ALIGN_RIGHT) - xOff = mCarousel.size.x - (mCarousel.logoSize.x * 1.1f); - else - xOff = (mCarousel.size.x - mCarousel.logoSize.x) / 2.0f; - break; - } - case HORIZONTAL_WHEEL: { - xOff = std::round((mCarousel.size.x - mCarousel.logoSize.x) / 2.0f - - (mCamOffset * logoSpacing.y)); - if (mCarousel.logoAlignment == ALIGN_TOP) - yOff = mCarousel.logoSize.y / 10.0f; - else if (mCarousel.logoAlignment == ALIGN_BOTTOM) - yOff = mCarousel.size.y - (mCarousel.logoSize.y * 1.1f); - else - yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f; - break; - } - case HORIZONTAL: { - } - default: { - logoSpacing.x = ((mCarousel.size.x - (mCarousel.logoSize.x * mCarousel.maxLogoCount)) / - (mCarousel.maxLogoCount)) + - mCarousel.logoSize.x; - xOff = std::round((mCarousel.size.x - mCarousel.logoSize.x) / 2.0f - - (mCamOffset * logoSpacing.x)); - if (mCarousel.logoAlignment == ALIGN_TOP) - yOff = mCarousel.logoSize.y / 10.0f; - else if (mCarousel.logoAlignment == ALIGN_BOTTOM) - yOff = mCarousel.size.y - (mCarousel.logoSize.y * 1.1f); - else - yOff = (mCarousel.size.y - mCarousel.logoSize.y) / 2.0f; - break; - } - } - - int center = static_cast(mCamOffset); - int logoCount = std::min(mCarousel.maxLogoCount, static_cast(mEntries.size())); - - // Adding texture loading buffers depending on scrolling speed and status. - int bufferIndex = getScrollingVelocity() + 1; - int bufferLeft = logoBuffersLeft[bufferIndex]; - int bufferRight = logoBuffersRight[bufferIndex]; - if (logoCount == 1) { - bufferLeft = 0; - bufferRight = 0; - } - - for (int i = center - logoCount / 2 + bufferLeft; // Line break. - i <= center + logoCount / 2 + bufferRight; ++i) { - int index = i; - - while (index < 0) - index += static_cast(mEntries.size()); - while (index >= static_cast(mEntries.size())) - index -= static_cast(mEntries.size()); - - glm::mat4 logoTrans {carouselTrans}; - logoTrans = glm::translate( - logoTrans, glm::vec3 {i * logoSpacing.x + xOff, i * logoSpacing.y + yOff, 0.0f}); - - float distance = i - mCamOffset; - - float scale = 1.0f + ((mCarousel.logoScale - 1.0f) * (1.0f - fabs(distance))); - scale = std::min(mCarousel.logoScale, std::max(1.0f, scale)); - scale /= mCarousel.logoScale; - - int opacity = - static_cast(std::round(0x80 + ((0xFF - 0x80) * (1.0f - fabs(distance))))); - opacity = std::max(static_cast(0x80), opacity); - - const std::shared_ptr& comp = mEntries.at(index).data.logo; - if (mCarousel.type == VERTICAL_WHEEL || mCarousel.type == HORIZONTAL_WHEEL) { - comp->setRotationDegrees(mCarousel.logoRotation * distance); - comp->setRotationOrigin(mCarousel.logoRotationOrigin); - } - - // When running at lower resolutions, prevent the scale-down to go all the way to the - // minimum value. This avoids potential single-pixel alignment issues when the logo - // can't be vertically placed exactly in the middle of the carousel. Although the - // problem theoretically exists at all resolutions, it's not visble at around 1080p - // and above. - if (std::min(mSize.x, mSize.y) < 1080.0f) - scale = glm::clamp(scale, 1.0f / mCarousel.logoScale + 0.01f, 1.0f); - - comp->setScale(scale); - comp->setOpacity(static_cast(opacity)); - comp->render(logoTrans); - - if (mEntries.at(index).data.logoPlaceholderText) { - const std::shared_ptr& comp = mEntries.at(index).data.logoPlaceholderText; - if (mCarousel.type == VERTICAL_WHEEL || mCarousel.type == HORIZONTAL_WHEEL) { - comp->setRotationDegrees(mCarousel.logoRotation * distance); - comp->setRotationOrigin(mCarousel.logoRotationOrigin); - } - comp->setScale(scale); - comp->setOpacity(static_cast(opacity)); - comp->render(logoTrans); - } - } - Renderer::popClipRect(); -} - -void SystemView::renderExtras(const glm::mat4& trans, float lower, float upper) -{ - int extrasCenter = static_cast(mExtrasCamOffset); - - // Adding texture loading buffers depending on scrolling speed and status. - int bufferIndex {getScrollingVelocity() + 1}; - - Renderer::pushClipRect(glm::ivec2 {}, - glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - - for (int i = extrasCenter + logoBuffersLeft[bufferIndex]; - i <= extrasCenter + logoBuffersRight[bufferIndex]; ++i) { - int index = i; - while (index < 0) - index += static_cast(mEntries.size()); - while (index >= static_cast(mEntries.size())) - index -= static_cast(mEntries.size()); - - // Only render selected system when not showing. - if (mShowing || index == mCursor) { - glm::mat4 extrasTrans {trans}; - if (mCarousel.type == HORIZONTAL || mCarousel.type == HORIZONTAL_WHEEL) - extrasTrans = glm::translate( - extrasTrans, glm::vec3 {(i - mExtrasCamOffset) * mSize.x, 0.0f, 0.0f}); - else - extrasTrans = glm::translate( - extrasTrans, glm::vec3 {0.0f, (i - mExtrasCamOffset) * mSize.y, 0.0f}); - - Renderer::pushClipRect( - glm::ivec2 {static_cast(extrasTrans[3].x), static_cast(extrasTrans[3].y)}, - glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - SystemViewData data = mEntries.at(index).data; - - // Quick hack to get the new theme engine to work without using extras. - for (auto& image : data.imageComponents) { - image->render(extrasTrans); - } - for (auto& text : data.textComponents) { - text->render(extrasTrans); - } - - for (unsigned int j = 0; j < data.backgroundExtras.size(); ++j) { - GuiComponent* extra = data.backgroundExtras[j]; - if (extra->getZIndex() >= lower && extra->getZIndex() < upper) - extra->render(extrasTrans); - } - mSystemInfo.render(extrasTrans); - Renderer::popClipRect(); - } - } - Renderer::popClipRect(); + mSystemInfo->applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL); } void SystemView::renderFade(const glm::mat4& trans) { - unsigned int fadeColor = 0x00000000 | static_cast(mExtrasFadeOpacity * 255.0f); + unsigned int fadeColor {0x00000000 | static_cast(mFadeOpacity * 255.0f)}; Renderer::setMatrix(trans); Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, fadeColor, fadeColor); } - -void SystemView::getDefaultElements(void) -{ - // Carousel. - mCarousel.type = HORIZONTAL; - mCarousel.logoAlignment = ALIGN_CENTER; - mCarousel.size.x = mSize.x; - mCarousel.size.y = floorf(0.2325f * mSize.y); - mCarousel.pos.x = 0.0f; - mCarousel.pos.y = floorf(0.5f * (mSize.y - mCarousel.size.y)); - mCarousel.origin.x = 0.0f; - mCarousel.origin.y = 0.0f; - mCarousel.color = 0xFFFFFFD8; - mCarousel.colorEnd = 0xFFFFFFD8; - mCarousel.colorGradientHorizontal = true; - mCarousel.logoScale = 1.2f; - mCarousel.logoRotation = 7.5f; - mCarousel.logoRotationOrigin.x = -5.0f; - mCarousel.logoRotationOrigin.y = 0.5f; - mCarousel.logoSize.x = 0.25f * mSize.x; - mCarousel.logoSize.y = 0.155f * mSize.y; - mCarousel.maxLogoCount = 3; - mCarousel.zIndex = 40.0f; - mCarousel.legacyZIndexMode = true; - - // System info bar. - mSystemInfo.setSize(mSize.x, mSystemInfo.getFont()->getLetterHeight() * 2.2f); - mSystemInfo.setPosition(0.0f, mCarousel.pos.y + mCarousel.size.y); - mSystemInfo.setBackgroundColor(0xDDDDDDD8); - mSystemInfo.setRenderBackground(true); - mSystemInfo.setFont(Font::get(static_cast(0.035f * mSize.y), Font::getDefaultPath())); - mSystemInfo.setColor(0x000000FF); - mSystemInfo.setZIndex(50.0f); - mSystemInfo.setDefaultZIndex(50.0f); -} - -void SystemView::getCarouselFromTheme(const ThemeData::ThemeElement* elem) -{ - if (elem->has("type")) { - if (!(elem->get("type").compare("vertical"))) - mCarousel.type = VERTICAL; - else if (!(elem->get("type").compare("vertical_wheel"))) - mCarousel.type = VERTICAL_WHEEL; - else if (!(elem->get("type").compare("horizontal_wheel"))) - mCarousel.type = HORIZONTAL_WHEEL; - else - mCarousel.type = HORIZONTAL; - } - if (elem->has("size")) - mCarousel.size = elem->get("size") * mSize; - if (elem->has("pos")) - mCarousel.pos = elem->get("pos") * mSize; - if (elem->has("origin")) - mCarousel.origin = elem->get("origin"); - if (elem->has("color")) { - mCarousel.color = elem->get("color"); - mCarousel.colorEnd = mCarousel.color; - } - if (elem->has("colorEnd")) - mCarousel.colorEnd = elem->get("colorEnd"); - if (elem->has("gradientType")) - mCarousel.colorGradientHorizontal = - !(elem->get("gradientType").compare("horizontal")); - if (elem->has("logoScale")) - mCarousel.logoScale = elem->get("logoScale"); - if (elem->has("logoSize")) - mCarousel.logoSize = elem->get("logoSize") * mSize; - if (elem->has("maxLogoCount")) - mCarousel.maxLogoCount = static_cast(std::round(elem->get("maxLogoCount"))); - if (elem->has("zIndex")) - mCarousel.zIndex = elem->get("zIndex"); - if (elem->has("logoRotation")) - mCarousel.logoRotation = elem->get("logoRotation"); - if (elem->has("logoRotationOrigin")) - mCarousel.logoRotationOrigin = elem->get("logoRotationOrigin"); - if (elem->has("logoAlignment")) { - if (!(elem->get("logoAlignment").compare("left"))) - mCarousel.logoAlignment = ALIGN_LEFT; - else if (!(elem->get("logoAlignment").compare("right"))) - mCarousel.logoAlignment = ALIGN_RIGHT; - else if (!(elem->get("logoAlignment").compare("top"))) - mCarousel.logoAlignment = ALIGN_TOP; - else if (!(elem->get("logoAlignment").compare("bottom"))) - mCarousel.logoAlignment = ALIGN_BOTTOM; - else - mCarousel.logoAlignment = ALIGN_CENTER; - } - if (elem->has("legacyZIndexMode")) { - mCarousel.legacyZIndexMode = - elem->get("legacyZIndexMode").compare("true") == 0 ? true : false; - } - else { - mCarousel.legacyZIndexMode = true; - } -} \ No newline at end of file diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index 232c7d4cc..84f7e861a 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -11,11 +11,10 @@ #include "GuiComponent.h" #include "Sound.h" -#include "components/BadgeComponent.h" +#include "SystemData.h" +#include "components/CarouselComponent.h" #include "components/DateTimeComponent.h" -#include "components/IList.h" #include "components/LottieComponent.h" -#include "components/RatingComponent.h" #include "components/ScrollableContainer.h" #include "components/TextComponent.h" #include "components/TextListComponent.h" @@ -26,105 +25,72 @@ class SystemData; -enum CarouselType : unsigned int { - HORIZONTAL = 0, - VERTICAL = 1, - VERTICAL_WHEEL = 2, - HORIZONTAL_WHEEL = 3 +struct SystemViewElements { + std::string name; + std::vector legacyExtras; + std::vector children; + + std::vector> textComponents; + std::vector> imageComponents; + std::vector> videoComponents; + std::vector> lottieAnimComponents; + std::vector> containerComponents; + std::vector> containerTextComponents; }; -struct SystemViewData { - std::shared_ptr logo; - std::shared_ptr logoPlaceholderText; - std::vector backgroundExtras; - - std::vector> textComponents; - std::vector> dateTimeComponents; - std::vector> imageComponents; - std::vector> videoComponents; - std::vector> lottieAnimComponents; - std::vector> badgeComponents; - std::vector> ratingComponents; - std::vector> containerComponents; - std::vector> containerTextComponents; - std::vector> gamelistInfoComponents; -}; - -struct SystemViewCarousel { - CarouselType type; - glm::vec2 pos; - glm::vec2 size; - glm::vec2 origin; - float logoScale; - float logoRotation; - glm::vec2 logoRotationOrigin; - Alignment logoAlignment; - unsigned int color; - unsigned int colorEnd; - bool colorGradientHorizontal; - int maxLogoCount; // Number of logos shown on the carousel. - glm::vec2 logoSize; - float zIndex; - bool legacyZIndexMode; -}; - -class SystemView : public IList +class SystemView : public GuiComponent { public: SystemView(); ~SystemView(); - void onShow() override { mShowing = true; } - void onHide() override { mShowing = false; } - void goToSystem(SystemData* system, bool animate); bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; void render(const glm::mat4& parentTrans) override; + bool isScrolling() { return mCarousel->isScrolling(); } + void stopScrolling() { mCarousel->stopScrolling(); } + bool isSystemAnimationPlaying(unsigned char slot) + { + return mCarousel->isAnimationPlaying(slot); + } + void finishSystemAnimation(unsigned char slot) + { + finishAnimation(slot); + mCarousel->finishAnimation(slot); + } + + CarouselComponent::CarouselType getCarouselType() { return mCarousel->getType(); } + SystemData* getFirstSystem() { return mCarousel->getFirst(); } + void onThemeChanged(const std::shared_ptr& theme); std::vector getHelpPrompts() override; HelpStyle getHelpStyle() override; - CarouselType getCarouselType() { return mCarousel.type; } - protected: - void onCursorChanged(const CursorState& state) override; - void onScroll() override - { - NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND); - } + void onCursorChanged(const CursorState& state); private: void populate(); void updateGameCount(); // Get the ThemeElements that make up the SystemView. void getViewElements(const std::shared_ptr& theme); - // Populate the system carousel with the legacy values. - void getDefaultElements(void); - void getCarouselFromTheme(const ThemeData::ThemeElement* elem); - // Render system carousel. - void renderCarousel(const glm::mat4& parentTrans); - // Draw background extras. - void renderExtras(const glm::mat4& parentTrans, float lower, float upper); void renderFade(const glm::mat4& trans); - SystemViewCarousel mCarousel; - TextComponent mSystemInfo; + std::unique_ptr mCarousel; + std::unique_ptr mSystemInfo; + + std::vector mElements; - // Unit is list index. float mCamOffset; - float mExtrasCamOffset; - float mExtrasFadeOpacity; + float mFadeOpacity; - int mPreviousScrollVelocity; bool mUpdatedGameCount; bool mViewNeedsReload; - bool mShowing; - bool mLegacyMode; }; diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 882dea7f2..f9beda556 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -233,7 +233,7 @@ void ViewController::goToStart(bool playTransition) Settings::getInstance()->setString("StartupSystem", ""); } // Get the first system entry. - goToSystemView(getSystemListView()->getFirst(), false); + goToSystemView(getSystemListView()->getFirstSystem(), false); } void ViewController::ReloadAndGoToStart() @@ -293,8 +293,8 @@ void ViewController::stopScrolling() mSystemListView->stopScrolling(); mCurrentView->stopListScrolling(); - if (mSystemListView->isAnimationPlaying(0)) - mSystemListView->finishAnimation(0); + if (mSystemListView->isSystemAnimationPlaying(0)) + mSystemListView->finishSystemAnimation(0); } int ViewController::getSystemId(SystemData* system) @@ -351,16 +351,16 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition) if (applicationStartup) { mCamera = glm::translate(mCamera, -mCurrentView->getPosition()); if (Settings::getInstance()->getString("TransitionStyle") == "slide") { - if (getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL || - getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL_WHEEL) + if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL || + getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL) mCamera[3].y += static_cast(Renderer::getScreenHeight()); else mCamera[3].x -= static_cast(Renderer::getScreenWidth()); updateHelpPrompts(); } else if (Settings::getInstance()->getString("TransitionStyle") == "fade") { - if (getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL || - getSystemListView()->getCarouselType() == CarouselType::HORIZONTAL_WHEEL) + if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL || + getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL) mCamera[3].y += static_cast(Renderer::getScreenHeight()); else mCamera[3].x += static_cast(Renderer::getScreenWidth()); @@ -463,8 +463,8 @@ void ViewController::goToGamelist(SystemData* system) // Stop any scrolling, animations and camera movements. if (mState.viewing == SYSTEM_SELECT) { mSystemListView->stopScrolling(); - if (mSystemListView->isAnimationPlaying(0)) - mSystemListView->finishAnimation(0); + if (mSystemListView->isSystemAnimationPlaying(0)) + mSystemListView->finishSystemAnimation(0); } if (slideTransitions) @@ -825,8 +825,8 @@ bool ViewController::input(InputConfig* config, Input input) mSystemListView->stopScrolling(); // Finish the animation too, so that it doesn't continue // to play when we've closed the menu. - if (mSystemListView->isAnimationPlaying(0)) - mSystemListView->finishAnimation(0); + if (mSystemListView->isSystemAnimationPlaying(0)) + mSystemListView->finishSystemAnimation(0); // Stop the gamelist scrolling as well as it would otherwise // also continue to run after closing the menu. mCurrentView->stopListScrolling(); diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index 8ca97d03f..1c9babd4b 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -8,7 +8,451 @@ #include "components/CarouselComponent.h" -CarouselComponent::CarouselComponent() +#include "Log.h" +#include "animations/LambdaAnimation.h" + +namespace +{ + // Buffer values for scrolling velocity (left, stopped, right). + const int logoBuffersLeft[] = {-5, -2, -1}; + const int logoBuffersRight[] = {1, 2, 5}; + +} // namespace + +CarouselComponent::CarouselComponent() + : IList {LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP} + , mCamOffset {0.0f} + , mPreviousScrollVelocity {0} + , mType {HORIZONTAL} + , mLogoAlignment {ALIGN_CENTER} + , mMaxLogoCount {3} + , mLogoSize {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f} + , mLogoScale {1.2f} + , mLogoRotation {7.5f} + , mLogoRotationOrigin {-5.0f, 0.5f} + , mCarouselColor {0} + , mCarouselColorEnd {0} + , mColorGradientHorizontal {true} { - // +} + +void CarouselComponent::addEntry(const std::shared_ptr& theme, Entry& entry) +{ + // Make logo. + const ThemeData::ThemeElement* logoElem {theme->getElement("system", "image_logo", "image")}; + + if (logoElem) { + std::string path; + if (logoElem->has("path")) + path = logoElem->get("path"); + std::string defaultPath {logoElem->has("default") ? logoElem->get("default") : + ""}; + if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) || + (!defaultPath.empty() && ResourceManager::getInstance().fileExists(defaultPath))) { + auto logo = std::make_shared(false, false); + logo->setMaxSize(glm::round(mLogoSize * mLogoScale)); + logo->applyTheme(theme, "system", "image_logo", ThemeFlags::PATH | ThemeFlags::COLOR); + logo->setRotateByTargetSize(true); + entry.data.logo = logo; + } + } + + if (!entry.data.logo) { + glm::vec2 resolution {static_cast(Renderer::getScreenWidth()), + static_cast(Renderer::getScreenHeight())}; + glm::vec3 center {resolution.x / 2.0f, resolution.y / 2.0f, 1.0f}; + + // Placeholder Image. + logoElem = theme->getElement("system", "image_logoPlaceholderImage", "image"); + if (logoElem) { + auto path = logoElem->get("path"); + std::string defaultPath { + logoElem->has("default") ? logoElem->get("default") : ""}; + if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) || + (!defaultPath.empty() && ResourceManager::getInstance().fileExists(defaultPath))) { + auto logo = std::make_shared(false, false); + logo->applyTheme(theme, "system", "image_logoPlaceholderImage", ThemeFlags::ALL); + if (!logoElem->has("size")) + logo->setMaxSize(mLogoSize * mLogoScale); + logo->setRotateByTargetSize(true); + entry.data.logo = logo; + } + } + + // Placeholder Text. + const ThemeData::ThemeElement* logoPlaceholderText = + theme->getElement("system", "text_logoPlaceholderText", "text"); + if (logoPlaceholderText) { + // Element 'logoPlaceholderText' found in theme configuration. + auto text = std::make_shared(entry.name, Font::get(FONT_SIZE_LARGE), + 0x000000FF, ALIGN_CENTER); + text->setSize(mLogoSize * mLogoScale); + if (mType == VERTICAL || mType == VERTICAL_WHEEL) { + text->setHorizontalAlignment(mLogoAlignment); + text->setVerticalAlignment(ALIGN_CENTER); + } + else { + text->setHorizontalAlignment(ALIGN_CENTER); + text->setVerticalAlignment(mLogoAlignment); + } + text->applyTheme(theme, "system", "text_logoPlaceholderText", + ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | + ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING | + ThemeFlags::TEXT); + if (!entry.data.logo) + entry.data.logo = text; + } + else { + // Fallback to legacy centered placeholder text. + auto text = std::make_shared(entry.name, Font::get(FONT_SIZE_LARGE), + 0x000000FF, ALIGN_CENTER); + text->setSize(mLogoSize * mLogoScale); + text->applyTheme(theme, "system", "text_logoText", + ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | + ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING | + ThemeFlags::TEXT); + entry.data.logo = text; + + if (mType == VERTICAL || mType == VERTICAL_WHEEL) { + text->setHorizontalAlignment(mLogoAlignment); + text->setVerticalAlignment(ALIGN_CENTER); + } + else { + text->setHorizontalAlignment(ALIGN_CENTER); + text->setVerticalAlignment(mLogoAlignment); + } + } + } + + if (mType == VERTICAL || mType == VERTICAL_WHEEL) { + if (mLogoAlignment == ALIGN_LEFT) + entry.data.logo->setOrigin(0, 0.5); + else if (mLogoAlignment == ALIGN_RIGHT) + entry.data.logo->setOrigin(1.0, 0.5); + else + entry.data.logo->setOrigin(0.5, 0.5); + } + else { + if (mLogoAlignment == ALIGN_TOP) + entry.data.logo->setOrigin(0.5, 0); + else if (mLogoAlignment == ALIGN_BOTTOM) + entry.data.logo->setOrigin(0.5, 1); + else + entry.data.logo->setOrigin(0.5, 0.5); + } + + glm::vec2 denormalized {mLogoSize * entry.data.logo->getOrigin()}; + entry.data.logo->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f}); + + add(entry); +} + +void CarouselComponent::update(int deltaTime) +{ + listUpdate(deltaTime); + GuiComponent::update(deltaTime); +} + +bool CarouselComponent::input(InputConfig* config, Input input) +{ + if (input.value != 0) { + switch (mType) { + case VERTICAL: + case VERTICAL_WHEEL: + if (config->isMappedLike("up", input)) { + if (mCancelTransitionsCallback) + mCancelTransitionsCallback(); + listInput(-1); + return true; + } + if (config->isMappedLike("down", input)) { + if (mCancelTransitionsCallback) + mCancelTransitionsCallback(); + listInput(1); + return true; + } + break; + case HORIZONTAL: + case HORIZONTAL_WHEEL: + default: + if (config->isMappedLike("left", input)) { + if (mCancelTransitionsCallback) + mCancelTransitionsCallback(); + listInput(-1); + return true; + } + if (config->isMappedLike("right", input)) { + if (mCancelTransitionsCallback) + mCancelTransitionsCallback(); + listInput(1); + return true; + } + break; + } + } + else { + if (config->isMappedLike("left", input) || config->isMappedLike("right", input) || + config->isMappedLike("up", input) || config->isMappedLike("down", input)) { + listInput(0); + } + } + + return GuiComponent::input(config, input); +} + +void CarouselComponent::render(const glm::mat4& parentTrans) +{ + // Background box behind logos. + glm::mat4 carouselTrans {parentTrans}; + carouselTrans = glm::translate(carouselTrans, glm::vec3 {mPosition.x, mPosition.y, 0.0f}); + carouselTrans = glm::translate( + carouselTrans, glm::vec3 {mOrigin.x * mSize.x * -1.0f, mOrigin.y * mSize.y * -1.0f, 0.0f}); + + glm::vec2 clipPos {carouselTrans[3].x, carouselTrans[3].y}; + Renderer::pushClipRect( + glm::ivec2 {static_cast(std::round(clipPos.x)), + static_cast(std::round(clipPos.y))}, + glm::ivec2 {static_cast(std::round(mSize.x)), static_cast(std::round(mSize.y))}); + + Renderer::setMatrix(carouselTrans); + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, mCarouselColor, mCarouselColorEnd, + mColorGradientHorizontal); + + // Draw logos. + // logoSpacing will also include the size of the logo itself. + glm::vec2 logoSpacing {}; + float xOff {0.0f}; + float yOff {0.0f}; + + switch (mType) { + case VERTICAL_WHEEL: { + yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y); + if (mLogoAlignment == ALIGN_LEFT) + xOff = mLogoSize.x / 10.0f; + else if (mLogoAlignment == ALIGN_RIGHT) + xOff = mSize.x - (mLogoSize.x * 1.1f); + else + xOff = (mSize.x - mLogoSize.x) / 2.0f; + break; + } + case VERTICAL: { + logoSpacing.y = + ((mSize.y - (mLogoSize.y * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.y; + yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y); + if (mLogoAlignment == ALIGN_LEFT) + xOff = mLogoSize.x / 10.0f; + else if (mLogoAlignment == ALIGN_RIGHT) + xOff = mSize.x - (mLogoSize.x * 1.1f); + else + xOff = (mSize.x - mLogoSize.x) / 2.0f; + break; + } + case HORIZONTAL_WHEEL: { + xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.y)); + if (mLogoAlignment == ALIGN_TOP) + yOff = mLogoSize.y / 10.0f; + else if (mLogoAlignment == ALIGN_BOTTOM) + yOff = mSize.y - (mLogoSize.y * 1.1f); + else + yOff = (mSize.y - mLogoSize.y) / 2.0f; + break; + } + case HORIZONTAL: { + } + default: { + logoSpacing.x = + ((mSize.x - (mLogoSize.x * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.x; + xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.x)); + if (mLogoAlignment == ALIGN_TOP) + yOff = mLogoSize.y / 10.0f; + else if (mLogoAlignment == ALIGN_BOTTOM) + yOff = mSize.y - (mLogoSize.y * 1.1f); + else + yOff = (mSize.y - mLogoSize.y) / 2.0f; + break; + } + } + + int center {static_cast(mCamOffset)}; + int logoCount {std::min(mMaxLogoCount, static_cast(mEntries.size()))}; + + // Adding texture loading buffers depending on scrolling speed and status. + int bufferIndex {getScrollingVelocity() + 1}; + int bufferLeft {logoBuffersLeft[bufferIndex]}; + int bufferRight {logoBuffersRight[bufferIndex]}; + if (logoCount == 1) { + bufferLeft = 0; + bufferRight = 0; + } + + for (int i = center - logoCount / 2 + bufferLeft; // Line break. + i <= center + logoCount / 2 + bufferRight; ++i) { + int index {i}; + + while (index < 0) + index += static_cast(mEntries.size()); + while (index >= static_cast(mEntries.size())) + index -= static_cast(mEntries.size()); + + glm::mat4 logoTrans {carouselTrans}; + logoTrans = glm::translate( + logoTrans, glm::vec3 {i * logoSpacing.x + xOff, i * logoSpacing.y + yOff, 0.0f}); + + float distance = i - mCamOffset; + + float scale {1.0f + ((mLogoScale - 1.0f) * (1.0f - fabs(distance)))}; + scale = std::min(mLogoScale, std::max(1.0f, scale)); + scale /= mLogoScale; + + int opacity { + static_cast(std::round(0x80 + ((0xFF - 0x80) * (1.0f - fabs(distance)))))}; + opacity = std::max(static_cast(0x80), opacity); + + const std::shared_ptr& comp = mEntries.at(index).data.logo; + + if (comp == nullptr) + continue; + + if (mType == VERTICAL_WHEEL || mType == HORIZONTAL_WHEEL) { + comp->setRotationDegrees(mLogoRotation * distance); + comp->setRotationOrigin(mLogoRotationOrigin); + } + + // When running at lower resolutions, prevent the scale-down to go all the way to the + // minimum value. This avoids potential single-pixel alignment issues when the logo + // can't be vertically placed exactly in the middle of the carousel. Although the + // problem theoretically exists at all resolutions, it's not visble at around 1080p + // and above. + if (std::min(Renderer::getScreenWidth(), Renderer::getScreenHeight()) < 1080.0f) + scale = glm::clamp(scale, 1.0f / mLogoScale + 0.01f, 1.0f); + + comp->setScale(scale); + comp->setOpacity(static_cast(opacity)); + comp->render(logoTrans); + } + + Renderer::popClipRect(); +} + +void CarouselComponent::applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) +{ + using namespace ThemeFlags; + const ThemeData::ThemeElement* elem {theme->getElement(view, element, "carousel")}; + + mSize.x = Renderer::getScreenWidth(); + mSize.y = Renderer::getScreenHeight() * 0.2325f; + mPosition.x = 0.0f; + mPosition.y = floorf(0.5f * (Renderer::getScreenHeight() - mSize.y)); + mCarouselColor = 0xFFFFFFD8; + mCarouselColorEnd = 0xFFFFFFD8; + mDefaultZIndex = 50.0f; + + if (!elem) + return; + + if (elem->has("type")) { + if (!(elem->get("type").compare("vertical"))) + mType = VERTICAL; + else if (!(elem->get("type").compare("vertical_wheel"))) + mType = VERTICAL_WHEEL; + else if (!(elem->get("type").compare("horizontal_wheel"))) + mType = HORIZONTAL_WHEEL; + else + mType = HORIZONTAL; + } + + if (elem->has("color")) { + mCarouselColor = elem->get("color"); + mCarouselColorEnd = mCarouselColor; + } + if (elem->has("colorEnd")) + mCarouselColorEnd = elem->get("colorEnd"); + if (elem->has("gradientType")) + mColorGradientHorizontal = !(elem->get("gradientType").compare("horizontal")); + + if (elem->has("logoScale")) + mLogoScale = elem->get("logoScale"); + if (elem->has("logoSize")) + mLogoSize = elem->get("logoSize") * + glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight()); + if (elem->has("maxLogoCount")) + mMaxLogoCount = static_cast(std::round(elem->get("maxLogoCount"))); + + if (elem->has("logoRotation")) + mLogoRotation = elem->get("logoRotation"); + if (elem->has("logoRotationOrigin")) + mLogoRotationOrigin = elem->get("logoRotationOrigin"); + if (elem->has("logoAlignment")) { + if (!(elem->get("logoAlignment").compare("left"))) + mLogoAlignment = ALIGN_LEFT; + else if (!(elem->get("logoAlignment").compare("right"))) + mLogoAlignment = ALIGN_RIGHT; + else if (!(elem->get("logoAlignment").compare("top"))) + mLogoAlignment = ALIGN_TOP; + else if (!(elem->get("logoAlignment").compare("bottom"))) + mLogoAlignment = ALIGN_BOTTOM; + else + mLogoAlignment = ALIGN_CENTER; + } + + GuiComponent::applyTheme(theme, view, element, ALL); +} + +void CarouselComponent::onCursorChanged(const CursorState& state) +{ + float startPos {mCamOffset}; + float posMax {static_cast(mEntries.size())}; + float target {static_cast(mCursor)}; + + // Find the shortest path to the target. + float endPos {target}; // Directly. + float dist {fabs(endPos - startPos)}; + + if (fabs(target + posMax - startPos - mScrollVelocity) < dist) + endPos = target + posMax; // Loop around the end (0 -> max). + if (fabs(target - posMax - startPos - mScrollVelocity) < dist) + endPos = target - posMax; // Loop around the start (max - 1 -> -1). + + // This logic is only needed when there are two game systems, to prevent ugly jumps back + // an forth when selecting the same direction rapidly several times in a row. + if (posMax == 2) { + if (mPreviousScrollVelocity == 0) + mPreviousScrollVelocity = mScrollVelocity; + else if (mScrollVelocity < 0 && startPos < endPos) + mPreviousScrollVelocity = -1; + else if (mScrollVelocity > 0 && startPos > endPos) + mPreviousScrollVelocity = 1; + } + if (mPreviousScrollVelocity != 0 && posMax == 2 && mScrollVelocity == mPreviousScrollVelocity) { + if (fabs(endPos - startPos) < 0.5 || fabs(endPos - startPos) > 1.5) { + (mScrollVelocity < 0) ? endPos -= 1 : endPos += 1; + (mCursor == 0) ? mCursor = 1 : mCursor = 0; + return; + } + } + + // No need to animate transition, we're not going anywhere (probably mEntries.size() == 1). + if (endPos == mCamOffset) + return; + + Animation* anim = new LambdaAnimation( + [this, startPos, endPos, posMax](float t) { + t -= 1; + float f = glm::mix(startPos, endPos, t * t * t + 1); + if (f < 0) + f += posMax; + if (f >= posMax) + f -= posMax; + + this->mCamOffset = f; + }, + 500); + + setAnimation(anim, 0, nullptr, false, 0); + + if (mCursorChangedCallback) + mCursorChangedCallback(state); } diff --git a/es-core/src/components/CarouselComponent.h b/es-core/src/components/CarouselComponent.h index e17f2022c..d8c421678 100644 --- a/es-core/src/components/CarouselComponent.h +++ b/es-core/src/components/CarouselComponent.h @@ -7,16 +7,93 @@ // #include "GuiComponent.h" +#include "Sound.h" +#include "components/IList.h" +#include "components/ImageComponent.h" +#include "components/TextComponent.h" +#include "resources/Font.h" + +class SystemData; #ifndef ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H #define ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H -class CarouselComponent : public GuiComponent +struct CarouselElement { + std::shared_ptr logo; +}; + +class CarouselComponent : public IList { public: CarouselComponent(); + void addElement(const std::shared_ptr& component, + const std::string& name, + SystemData* object) + { + Entry listEntry; + listEntry.name = name; + listEntry.object = object; + listEntry.data.logo = component; + add(listEntry); + } + + void addEntry(const std::shared_ptr& theme, Entry& entry); + Entry& getEntry(int index) { return mEntries.at(index); } + + enum CarouselType { + HORIZONTAL, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). + VERTICAL, + VERTICAL_WHEEL, + HORIZONTAL_WHEEL + }; + + int getCursor() { return mCursor; } + const CarouselType getType() { return mType; } + size_t getNumEntries() { return mEntries.size(); } + + void setCursorChangedCallback(const std::function& func) + { + mCursorChangedCallback = func; + } + void setCancelTransitionsCallback(const std::function& func) + { + mCancelTransitionsCallback = func; + } + + 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& theme, + const std::string& view, + const std::string& element, + unsigned int properties) override; + +protected: + void onCursorChanged(const CursorState& state) override; + void onScroll() override + { + NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND); + } + private: + std::function mCursorChangedCallback; + std::function mCancelTransitionsCallback; + + float mCamOffset; + int mPreviousScrollVelocity; + + CarouselType mType; + Alignment mLogoAlignment; + int mMaxLogoCount; + glm::vec2 mLogoSize; + float mLogoScale; + float mLogoRotation; + glm::vec2 mLogoRotationOrigin; + unsigned int mCarouselColor; + unsigned int mCarouselColorEnd; + bool mColorGradientHorizontal; }; #endif // ES_CORE_COMPONENTS_CAROUSEL_COMPONENT_H From 6d01e85885a99c2f9b68b9ab9f7da319b30bd808 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 6 Feb 2022 14:55:48 +0100 Subject: [PATCH 04/82] Reshuffled some functions in SystemView. --- es-app/src/views/SystemView.cpp | 538 ++++++++++++++++---------------- 1 file changed, 269 insertions(+), 269 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 17d60a702..abcec09a2 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -62,6 +62,275 @@ SystemView::~SystemView() } } +void SystemView::goToSystem(SystemData* system, bool animate) +{ + mCarousel->setCursor(system); + updateGameCount(); + + if (!animate) + finishSystemAnimation(0); +} + +bool SystemView::input(InputConfig* config, Input input) +{ + if (input.value != 0) { + if (config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r && + SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) { + LOG(LogDebug) << "SystemView::input(): Reloading all"; + ViewController::getInstance()->reloadAll(); + return true; + } + + if (config->isMappedTo("a", input)) { + mCarousel->stopScrolling(); + ViewController::getInstance()->goToGamelist(mCarousel->getSelected()); + NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); + return true; + } + if (Settings::getInstance()->getBool("RandomAddButton") && + (config->isMappedTo("leftthumbstickclick", input) || + config->isMappedTo("rightthumbstickclick", input))) { + // Get a random system and jump to it. + NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND); + mCarousel->setCursor(SystemData::getRandomSystem(mCarousel->getSelected())); + return true; + } + + if (!UIModeController::getInstance()->isUIModeKid() && config->isMappedTo("back", input) && + Settings::getInstance()->getBool("ScreensaverControls")) { + if (!mWindow->isScreensaverActive()) { + ViewController::getInstance()->stopScrolling(); + ViewController::getInstance()->cancelViewTransitions(); + mWindow->startScreensaver(); + mWindow->renderScreensaver(); + } + return true; + } + } + + return mCarousel->input(config, input); +} + +void SystemView::update(int deltaTime) +{ + mCarousel->update(deltaTime); + GuiComponent::update(deltaTime); +} + +void SystemView::render(const glm::mat4& parentTrans) +{ + if (mCarousel->getNumEntries() == 0) + return; // Nothing to render. + + glm::mat4 trans {getTransform() * parentTrans}; + + // Adding texture loading buffers depending on scrolling speed and status. + int bufferIndex {mCarousel->getScrollingVelocity() + 1}; + + Renderer::pushClipRect(glm::ivec2 {}, + glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); + + for (int i = static_cast(mCamOffset) + logoBuffersLeft[bufferIndex]; + i <= static_cast(mCamOffset) + logoBuffersRight[bufferIndex]; ++i) { + int index {i}; + while (index < 0) + index += static_cast(mCarousel->getNumEntries()); + while (index >= static_cast(mCarousel->getNumEntries())) + index -= static_cast(mCarousel->getNumEntries()); + + if (mCarousel->isAnimationPlaying(0) || index == mCarousel->getCursor()) { + glm::mat4 elementTrans {trans}; + if (mCarousel->getType() == CarouselComponent::HORIZONTAL || + mCarousel->getType() == CarouselComponent::HORIZONTAL_WHEEL) + elementTrans = glm::translate(elementTrans, + glm::vec3 {(i - mCamOffset) * mSize.x, 0.0f, 0.0f}); + else + elementTrans = glm::translate(elementTrans, + glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}); + + Renderer::pushClipRect( + glm::ivec2 {static_cast(elementTrans[3].x), + static_cast(elementTrans[3].y)}, + glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); + + if (mLegacyMode && mElements.size() > static_cast(index)) { + for (auto element : mElements[index].legacyExtras) + element->render(elementTrans); + } + else if (mElements.size() > static_cast(index)) { + for (auto child : mElements[index].children) { + if (child == mCarousel.get()) { + // Render black above anything lower than the zIndex of the carousel + // if fade transitions are in use and we're transitioning. + if (mFadeOpacity) + renderFade(trans); + child->render(trans); + } + else { + child->render(elementTrans); + } + } + } + + if (mLegacyMode) + mSystemInfo->render(elementTrans); + + Renderer::popClipRect(); + } + } + + if (mLegacyMode) { + if (mFadeOpacity) + renderFade(trans); + mCarousel->render(trans); + } + + Renderer::popClipRect(); +} + +void SystemView::onThemeChanged(const std::shared_ptr& /*theme*/) +{ + LOG(LogDebug) << "SystemView::onThemeChanged()"; + mViewNeedsReload = true; + populate(); +} + +std::vector SystemView::getHelpPrompts() +{ + std::vector prompts; + if (mCarousel->getType() == CarouselComponent::VERTICAL || + mCarousel->getType() == CarouselComponent::VERTICAL_WHEEL) + prompts.push_back(HelpPrompt("up/down", "choose")); + else + prompts.push_back(HelpPrompt("left/right", "choose")); + + prompts.push_back(HelpPrompt("a", "select")); + + if (Settings::getInstance()->getBool("RandomAddButton")) + prompts.push_back(HelpPrompt("thumbstickclick", "random")); + + if (!UIModeController::getInstance()->isUIModeKid() && + Settings::getInstance()->getBool("ScreensaverControls")) + prompts.push_back(HelpPrompt("back", "screensaver")); + + return prompts; +} + +HelpStyle SystemView::getHelpStyle() +{ + HelpStyle style; + style.applyTheme(mCarousel->getEntry(mCarousel->getCursor()).object->getTheme(), "system"); + return style; +} + +void SystemView::onCursorChanged(const CursorState& /*state*/) +{ + // Update help style. + updateHelpPrompts(); + + int scrollVelocity {mCarousel->getScrollingVelocity()}; + + float startPos {mCamOffset}; + float posMax {static_cast(mCarousel->getNumEntries())}; + float target {static_cast(mCarousel->getCursor())}; + + // Find the shortest path to the target. + float endPos {target}; // Directly. + float dist {fabs(endPos - startPos)}; + + if (fabs(target + posMax - startPos - scrollVelocity) < dist) + endPos = target + posMax; // Loop around the end (0 -> max). + if (fabs(target - posMax - startPos - scrollVelocity) < dist) + endPos = target - posMax; // Loop around the start (max - 1 -> -1). + + std::string transition_style {Settings::getInstance()->getString("TransitionStyle")}; + + Animation* anim; + + if (transition_style == "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); + if (f < 0) + f += posMax; + if (f >= posMax) + f -= posMax; + + t += 1; + if (t < 0.3f) + this->mFadeOpacity = + glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f)); + else if (t < 0.7f) + this->mFadeOpacity = 1.0f; + else + this->mFadeOpacity = + glm::mix(1.0f, 0.0f, glm::clamp((t - 0.6f) / 0.3f, 0.0f, 1.0f)); + + if (t > 0.5f) + this->mCamOffset = endPos; + + // Update the game count when the entire animation has been completed. + if (mFadeOpacity == 1.0f) + updateGameCount(); + }, + 500); + } + else if (transition_style == "slide") { + mUpdatedGameCount = false; + anim = new LambdaAnimation( + [this, startPos, endPos, posMax](float t) { + t -= 1; + float f = glm::mix(startPos, endPos, t * t * t + 1); + if (f < 0) + f += posMax; + if (f >= posMax) + f -= posMax; + + this->mCamOffset = f; + + // Hack to make the game count being updated in the middle of the animation. + bool update {false}; + if (endPos == -1.0f && fabs(fabs(posMax) - fabs(mCamOffset)) > 0.5f && + !mUpdatedGameCount) { + update = true; + } + else if (endPos > posMax && fabs(endPos - posMax - fabs(mCamOffset)) < 0.5f && + !mUpdatedGameCount) { + update = true; + } + else if (fabs(fabs(endPos) - fabs(mCamOffset)) < 0.5f && !mUpdatedGameCount) { + update = true; + } + + if (update) { + mUpdatedGameCount = true; + updateGameCount(); + } + }, + 500); + } + else { + // Instant. + updateGameCount(); + anim = new LambdaAnimation( + [this, startPos, endPos, posMax](float t) { + t -= 1; + float f = glm::mix(startPos, endPos, t * t * t + 1); + if (f < 0) + f += posMax; + if (f >= posMax) + f -= posMax; + + this->mCamOffset = endPos; + }, + 500); + } + + setAnimation(anim, 0, nullptr, false, 0); +} + void SystemView::populate() { auto themeSets = ThemeData::getThemeSets(); @@ -179,275 +448,6 @@ void SystemView::updateGameCount() mSystemInfo->setText(ss.str()); } -void SystemView::goToSystem(SystemData* system, bool animate) -{ - mCarousel->setCursor(system); - updateGameCount(); - - if (!animate) - finishSystemAnimation(0); -} - -bool SystemView::input(InputConfig* config, Input input) -{ - if (input.value != 0) { - if (config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r && - SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) { - LOG(LogDebug) << "SystemView::input(): Reloading all"; - ViewController::getInstance()->reloadAll(); - return true; - } - - if (config->isMappedTo("a", input)) { - mCarousel->stopScrolling(); - ViewController::getInstance()->goToGamelist(mCarousel->getSelected()); - NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); - return true; - } - if (Settings::getInstance()->getBool("RandomAddButton") && - (config->isMappedTo("leftthumbstickclick", input) || - config->isMappedTo("rightthumbstickclick", input))) { - // Get a random system and jump to it. - NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND); - mCarousel->setCursor(SystemData::getRandomSystem(mCarousel->getSelected())); - return true; - } - - if (!UIModeController::getInstance()->isUIModeKid() && config->isMappedTo("back", input) && - Settings::getInstance()->getBool("ScreensaverControls")) { - if (!mWindow->isScreensaverActive()) { - ViewController::getInstance()->stopScrolling(); - ViewController::getInstance()->cancelViewTransitions(); - mWindow->startScreensaver(); - mWindow->renderScreensaver(); - } - return true; - } - } - - return mCarousel->input(config, input); -} - -void SystemView::update(int deltaTime) -{ - mCarousel->update(deltaTime); - GuiComponent::update(deltaTime); -} - -void SystemView::onCursorChanged(const CursorState& /*state*/) -{ - // Update help style. - updateHelpPrompts(); - - int scrollVelocity {mCarousel->getScrollingVelocity()}; - - float startPos {mCamOffset}; - float posMax {static_cast(mCarousel->getNumEntries())}; - float target {static_cast(mCarousel->getCursor())}; - - // Find the shortest path to the target. - float endPos {target}; // Directly. - float dist {fabs(endPos - startPos)}; - - if (fabs(target + posMax - startPos - scrollVelocity) < dist) - endPos = target + posMax; // Loop around the end (0 -> max). - if (fabs(target - posMax - startPos - scrollVelocity) < dist) - endPos = target - posMax; // Loop around the start (max - 1 -> -1). - - std::string transition_style {Settings::getInstance()->getString("TransitionStyle")}; - - Animation* anim; - - if (transition_style == "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); - if (f < 0) - f += posMax; - if (f >= posMax) - f -= posMax; - - t += 1; - if (t < 0.3f) - this->mFadeOpacity = - glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f)); - else if (t < 0.7f) - this->mFadeOpacity = 1.0f; - else - this->mFadeOpacity = - glm::mix(1.0f, 0.0f, glm::clamp((t - 0.6f) / 0.3f, 0.0f, 1.0f)); - - if (t > 0.5f) - this->mCamOffset = endPos; - - // Update the game count when the entire animation has been completed. - if (mFadeOpacity == 1.0f) - updateGameCount(); - }, - 500); - } - else if (transition_style == "slide") { - mUpdatedGameCount = false; - anim = new LambdaAnimation( - [this, startPos, endPos, posMax](float t) { - t -= 1; - float f = glm::mix(startPos, endPos, t * t * t + 1); - if (f < 0) - f += posMax; - if (f >= posMax) - f -= posMax; - - this->mCamOffset = f; - - // Hack to make the game count being updated in the middle of the animation. - bool update {false}; - if (endPos == -1.0f && fabs(fabs(posMax) - fabs(mCamOffset)) > 0.5f && - !mUpdatedGameCount) { - update = true; - } - else if (endPos > posMax && fabs(endPos - posMax - fabs(mCamOffset)) < 0.5f && - !mUpdatedGameCount) { - update = true; - } - else if (fabs(fabs(endPos) - fabs(mCamOffset)) < 0.5f && !mUpdatedGameCount) { - update = true; - } - - if (update) { - mUpdatedGameCount = true; - updateGameCount(); - } - }, - 500); - } - else { - // Instant. - updateGameCount(); - anim = new LambdaAnimation( - [this, startPos, endPos, posMax](float t) { - t -= 1; - float f = glm::mix(startPos, endPos, t * t * t + 1); - if (f < 0) - f += posMax; - if (f >= posMax) - f -= posMax; - - this->mCamOffset = endPos; - }, - 500); - } - - setAnimation(anim, 0, nullptr, false, 0); -} - -void SystemView::render(const glm::mat4& parentTrans) -{ - if (mCarousel->getNumEntries() == 0) - return; // Nothing to render. - - glm::mat4 trans {getTransform() * parentTrans}; - - // Adding texture loading buffers depending on scrolling speed and status. - int bufferIndex {mCarousel->getScrollingVelocity() + 1}; - - Renderer::pushClipRect(glm::ivec2 {}, - glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - - for (int i = static_cast(mCamOffset) + logoBuffersLeft[bufferIndex]; - i <= static_cast(mCamOffset) + logoBuffersRight[bufferIndex]; ++i) { - int index {i}; - while (index < 0) - index += static_cast(mCarousel->getNumEntries()); - while (index >= static_cast(mCarousel->getNumEntries())) - index -= static_cast(mCarousel->getNumEntries()); - - if (mCarousel->isAnimationPlaying(0) || index == mCarousel->getCursor()) { - glm::mat4 elementTrans {trans}; - if (mCarousel->getType() == CarouselComponent::HORIZONTAL || - mCarousel->getType() == CarouselComponent::HORIZONTAL_WHEEL) - elementTrans = glm::translate(elementTrans, - glm::vec3 {(i - mCamOffset) * mSize.x, 0.0f, 0.0f}); - else - elementTrans = glm::translate(elementTrans, - glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}); - - Renderer::pushClipRect( - glm::ivec2 {static_cast(elementTrans[3].x), - static_cast(elementTrans[3].y)}, - glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - - if (mLegacyMode && mElements.size() > static_cast(index)) { - for (auto element : mElements[index].legacyExtras) - element->render(elementTrans); - } - else if (mElements.size() > static_cast(index)) { - for (auto child : mElements[index].children) { - if (child == mCarousel.get()) { - // Render black above anything lower than the zIndex of the carousel - // if fade transitions are in use and we're transitioning. - if (mFadeOpacity) - renderFade(trans); - child->render(trans); - } - else { - child->render(elementTrans); - } - } - } - - if (mLegacyMode) - mSystemInfo->render(elementTrans); - - Renderer::popClipRect(); - } - } - - if (mLegacyMode) { - if (mFadeOpacity) - renderFade(trans); - mCarousel->render(trans); - } - - Renderer::popClipRect(); -} - -std::vector SystemView::getHelpPrompts() -{ - std::vector prompts; - if (mCarousel->getType() == CarouselComponent::VERTICAL || - mCarousel->getType() == CarouselComponent::VERTICAL_WHEEL) - prompts.push_back(HelpPrompt("up/down", "choose")); - else - prompts.push_back(HelpPrompt("left/right", "choose")); - - prompts.push_back(HelpPrompt("a", "select")); - - if (Settings::getInstance()->getBool("RandomAddButton")) - prompts.push_back(HelpPrompt("thumbstickclick", "random")); - - if (!UIModeController::getInstance()->isUIModeKid() && - Settings::getInstance()->getBool("ScreensaverControls")) - prompts.push_back(HelpPrompt("back", "screensaver")); - - return prompts; -} - -HelpStyle SystemView::getHelpStyle() -{ - HelpStyle style; - style.applyTheme(mCarousel->getEntry(mCarousel->getCursor()).object->getTheme(), "system"); - return style; -} - -void SystemView::onThemeChanged(const std::shared_ptr& /*theme*/) -{ - LOG(LogDebug) << "SystemView::onThemeChanged()"; - mViewNeedsReload = true; - populate(); -} - void SystemView::getViewElements(const std::shared_ptr& theme) { LOG(LogDebug) << "SystemView::getViewElements()"; From 3ceecdc793e9a58ec8e0297092f40b85614f8aec Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 6 Feb 2022 20:13:53 +0100 Subject: [PATCH 05/82] Added metadata property support for the SystemView text fields. --- es-app/src/views/SystemView.cpp | 161 ++++++++++++++++++++++---------- es-app/src/views/SystemView.h | 6 +- 2 files changed, 117 insertions(+), 50 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index abcec09a2..eb98d11d2 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -36,25 +36,22 @@ SystemView::SystemView() , mViewNeedsReload {true} , mLegacyMode {false} { - mCarousel = std::make_unique(); - mSystemInfo = std::make_unique("SYSTEM INFO", Font::get(FONT_SIZE_SMALL), - 0x33333300, ALIGN_CENTER); - setSize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); - populate(); - + mCarousel = std::make_unique(); mCarousel->setCursorChangedCallback([&](const CursorState& state) { onCursorChanged(state); }); mCarousel->setCancelTransitionsCallback( [&] { ViewController::getInstance()->cancelViewTransitions(); }); + + populate(); } SystemView::~SystemView() { if (mLegacyMode) { // Delete any existing extras. - for (auto& entry : mElements) { + for (auto& entry : mSystemElements) { for (auto extra : entry.legacyExtras) delete extra; entry.legacyExtras.clear(); @@ -153,12 +150,12 @@ void SystemView::render(const glm::mat4& parentTrans) static_cast(elementTrans[3].y)}, glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - if (mLegacyMode && mElements.size() > static_cast(index)) { - for (auto element : mElements[index].legacyExtras) + if (mLegacyMode && mSystemElements.size() > static_cast(index)) { + for (auto element : mSystemElements[index].legacyExtras) element->render(elementTrans); } - else if (mElements.size() > static_cast(index)) { - for (auto child : mElements[index].children) { + else if (mSystemElements.size() > static_cast(index)) { + for (auto child : mSystemElements[index].children) { if (child == mCarousel.get()) { // Render black above anything lower than the zIndex of the carousel // if fade transitions are in use and we're transitioning. @@ -173,7 +170,7 @@ void SystemView::render(const glm::mat4& parentTrans) } if (mLegacyMode) - mSystemInfo->render(elementTrans); + mLegacySystemInfo->render(elementTrans); Renderer::popClipRect(); } @@ -340,6 +337,11 @@ void SystemView::populate() assert(selectedSet != themeSets.cend()); mLegacyMode = selectedSet->second.capabilities.legacyTheme; + if (mLegacyMode) { + mLegacySystemInfo = std::make_unique( + "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER); + } + for (auto it : SystemData::sSystemVector) { const std::shared_ptr& theme {it->getTheme()}; @@ -356,13 +358,14 @@ void SystemView::populate() elements.legacyExtras.begin(), elements.legacyExtras.end(), [](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); }); - mElements.emplace_back(std::move(elements)); + mSystemElements.emplace_back(std::move(elements)); } if (!mLegacyMode) { SystemViewElements elements; if (theme->hasView("system")) { elements.name = it->getName(); + elements.fullName = it->getFullName(); for (auto& element : theme->getViewElements("system").elements) { if (element.second.type == "image") { elements.imageComponents.emplace_back(std::make_unique()); @@ -374,19 +377,31 @@ void SystemView::populate() elements.children.emplace_back(elements.imageComponents.back().get()); } else if (element.second.type == "text") { - elements.textComponents.push_back(std::make_unique()); - elements.textComponents.back()->setDefaultZIndex(40.0f); - elements.textComponents.back()->applyTheme(theme, "system", element.first, - ThemeFlags::ALL); - if (elements.textComponents.back()->getMetadataField() != "") - elements.textComponents.back()->setScrollHide(true); - elements.children.emplace_back(elements.textComponents.back().get()); + if (element.second.has("metadata") && + element.second.get("metadata").substr(0, 12) == + "sy_gamecount") { + if (element.second.has("metadata")) { + elements.gameCountComponents.emplace_back( + std::make_unique()); + elements.gameCountComponents.back()->setDefaultZIndex(40.0f); + elements.gameCountComponents.back()->applyTheme( + theme, "system", element.first, ThemeFlags::ALL); + elements.children.emplace_back( + elements.gameCountComponents.back().get()); + } + } + else { + elements.textComponents.emplace_back(std::make_unique()); + elements.textComponents.back()->setDefaultZIndex(40.0f); + elements.textComponents.back()->applyTheme( + theme, "system", element.first, ThemeFlags::ALL); + elements.children.emplace_back(elements.textComponents.back().get()); + } } } } elements.children.emplace_back(mCarousel.get()); - elements.children.emplace_back(mSystemInfo.get()); std::stable_sort( elements.children.begin(), elements.children.end(), @@ -402,7 +417,7 @@ void SystemView::populate() const std::unique_ptr& b) { return b->getZIndex() > a->getZIndex(); }); - mElements.emplace_back(std::move(elements)); + mSystemElements.emplace_back(std::move(elements)); } CarouselComponent::Entry entry; @@ -412,6 +427,19 @@ void SystemView::populate() mCarousel->addEntry(theme, entry); } + for (auto& elements : mSystemElements) { + for (auto& text : elements.textComponents) { + if (text->getMetadataField() != "") { + if (text->getMetadataField() == "sy_name") + text->setValue(elements.name); + else if (text->getMetadataField() == "sy_fullname") + text->setValue(elements.fullName); + else + text->setValue(text->getMetadataField()); + } + } + } + if (mCarousel->getNumEntries() == 0) { // Something is wrong, there is not a single system to show, check if UI mode is not full. if (!UIModeController::getInstance()->isUIModeFull()) { @@ -429,23 +457,56 @@ void SystemView::updateGameCount() std::pair gameCount = mCarousel->getSelected()->getDisplayedGameCount(); std::stringstream ss; + std::stringstream ssGames; + std::stringstream ssFavorites; + bool games {false}; + bool favorites {false}; - if (!mCarousel->getSelected()->isGameSystem()) - ss << "CONFIGURATION"; + if (!mCarousel->getSelected()->isGameSystem()) { + ss << "Configuration"; + } else if (mCarousel->getSelected()->isCollection() && - (mCarousel->getSelected()->getName() == "favorites")) - ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S"); - // The "recent" gamelist has probably been trimmed after sorting, so we'll cap it at - // its maximum limit of 50 games. + (mCarousel->getSelected()->getName() == "favorites")) { + ss << gameCount.first << " Game" << (gameCount.first == 1 ? " " : "s"); + } else if (mCarousel->getSelected()->isCollection() && - (mCarousel->getSelected()->getName() == "recent")) - ss << (gameCount.first > 50 ? 50 : gameCount.first) << " GAME" - << (gameCount.first == 1 ? " " : "S"); - else - ss << gameCount.first << " GAME" << (gameCount.first == 1 ? " " : "S ") << "(" - << gameCount.second << " FAVORITE" << (gameCount.second == 1 ? ")" : "S)"); + (mCarousel->getSelected()->getName() == "recent")) { + // The "recent" gamelist has probably been trimmed after sorting, so we'll cap it at + // its maximum limit of 50 games. + ss << (gameCount.first > 50 ? 50 : gameCount.first) << " Game" + << (gameCount.first == 1 ? " " : "s"); + } + else { + ss << gameCount.first << " Game" << (gameCount.first == 1 ? " " : "s ") << "(" + << gameCount.second << " Favorite" << (gameCount.second == 1 ? ")" : "s)"); + ssGames << gameCount.first << " Game" << (gameCount.first == 1 ? " " : "s "); + ssFavorites << gameCount.second << " Favorite" << (gameCount.second == 1 ? "" : "s"); + games = true; + favorites = true; + } - mSystemInfo->setText(ss.str()); + if (mLegacyMode) { + mLegacySystemInfo->setText(ss.str()); + } + else { + for (auto& gameCount : mSystemElements[mCarousel->getCursor()].gameCountComponents) { + if (gameCount->getMetadataField() == "sy_gamecount") { + gameCount->setValue(ss.str()); + } + else if (gameCount->getMetadataField() == "sy_gamecount_games") { + if (games) + gameCount->setValue(ssGames.str()); + else + gameCount->setValue(ss.str()); + } + else if (gameCount->getMetadataField() == "sy_gamecount_favorites") { + gameCount->setValue(ssFavorites.str()); + } + else { + gameCount->setValue(gameCount->getMetadataField()); + } + } + } } void SystemView::getViewElements(const std::shared_ptr& theme) @@ -459,21 +520,25 @@ void SystemView::getViewElements(const std::shared_ptr& theme) mCarousel->applyTheme(theme, "system", "carousel_systemcarousel", ThemeFlags::ALL); - // System info bar. - mSystemInfo->setSize(mSize.x, mSystemInfo->getFont()->getLetterHeight() * 2.2f); - mSystemInfo->setPosition(0.0f, mCarousel->getPosition().y + mCarousel->getSize().y); - mSystemInfo->setBackgroundColor(0xDDDDDDD8); - mSystemInfo->setRenderBackground(true); - mSystemInfo->setFont(Font::get(static_cast(0.035f * mSize.y), Font::getDefaultPath())); - mSystemInfo->setColor(0x000000FF); - mSystemInfo->setZIndex(49.0f); - mSystemInfo->setDefaultZIndex(49.0f); + if (mLegacyMode) { + // System info bar. + mLegacySystemInfo->setSize(mSize.x, mLegacySystemInfo->getFont()->getLetterHeight() * 2.2f); + mLegacySystemInfo->setPosition(0.0f, mCarousel->getPosition().y + mCarousel->getSize().y); + mLegacySystemInfo->setBackgroundColor(0xDDDDDDD8); + mLegacySystemInfo->setRenderBackground(true); + mLegacySystemInfo->setFont( + Font::get(static_cast(0.035f * mSize.y), Font::getDefaultPath())); + mLegacySystemInfo->setColor(0x000000FF); + mLegacySystemInfo->setUppercase(true); + mLegacySystemInfo->setZIndex(49.0f); + mLegacySystemInfo->setDefaultZIndex(49.0f); - const ThemeData::ThemeElement* sysInfoElem { - theme->getElement("system", "text_systemInfo", "text")}; + const ThemeData::ThemeElement* sysInfoElem { + theme->getElement("system", "text_systemInfo", "text")}; - if (sysInfoElem) - mSystemInfo->applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL); + if (sysInfoElem) + mLegacySystemInfo->applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL); + } } void SystemView::renderFade(const glm::mat4& trans) diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index 84f7e861a..4ae658701 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -27,9 +27,11 @@ class SystemData; struct SystemViewElements { std::string name; + std::string fullName; std::vector legacyExtras; std::vector children; + std::vector> gameCountComponents; std::vector> textComponents; std::vector> imageComponents; std::vector> videoComponents; @@ -82,9 +84,9 @@ private: void renderFade(const glm::mat4& trans); std::unique_ptr mCarousel; - std::unique_ptr mSystemInfo; + std::unique_ptr mLegacySystemInfo; - std::vector mElements; + std::vector mSystemElements; float mCamOffset; float mFadeOpacity; From 0bd38ba26c295df0447f7432bea6fd9ca2c3229d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 6 Feb 2022 20:36:06 +0100 Subject: [PATCH 06/82] Updated SystemView for non-legacy carousel theming. --- es-app/src/views/SystemView.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index eb98d11d2..cfea938a9 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -367,7 +367,10 @@ void SystemView::populate() elements.name = it->getName(); elements.fullName = it->getFullName(); for (auto& element : theme->getViewElements("system").elements) { - if (element.second.type == "image") { + if (element.second.type == "carousel") { + mCarousel->applyTheme(theme, "system", element.first, ThemeFlags::ALL); + } + else if (element.second.type == "image") { elements.imageComponents.emplace_back(std::make_unique()); elements.imageComponents.back()->setDefaultZIndex(30.0f); elements.imageComponents.back()->applyTheme(theme, "system", element.first, @@ -518,10 +521,9 @@ void SystemView::getViewElements(const std::shared_ptr& theme) else mViewNeedsReload = true; - mCarousel->applyTheme(theme, "system", "carousel_systemcarousel", ThemeFlags::ALL); - if (mLegacyMode) { - // System info bar. + mCarousel->applyTheme(theme, "system", "carousel_systemcarousel", ThemeFlags::ALL); + mLegacySystemInfo->setSize(mSize.x, mLegacySystemInfo->getFont()->getLetterHeight() * 2.2f); mLegacySystemInfo->setPosition(0.0f, mCarousel->getPosition().y + mCarousel->getSize().y); mLegacySystemInfo->setBackgroundColor(0xDDDDDDD8); From c2de9c8d1d38e7869097b06884adc658a932c2a4 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 6 Feb 2022 20:44:11 +0100 Subject: [PATCH 07/82] (rbsimple-DE) Updated for the new game counter logic. --- themes/rbsimple-DE/colors_dark.xml | 4 ++-- themes/rbsimple-DE/colors_light.xml | 2 +- themes/rbsimple-DE/theme.xml | 12 +++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/themes/rbsimple-DE/colors_dark.xml b/themes/rbsimple-DE/colors_dark.xml index ba45d4d96..b3ed1b73f 100644 --- a/themes/rbsimple-DE/colors_dark.xml +++ b/themes/rbsimple-DE/colors_dark.xml @@ -1,9 +1,9 @@ - + 747474D8 - + C6C6C6 555555D8 diff --git a/themes/rbsimple-DE/colors_light.xml b/themes/rbsimple-DE/colors_light.xml index 958c73fde..c08b1a587 100644 --- a/themes/rbsimple-DE/colors_light.xml +++ b/themes/rbsimple-DE/colors_light.xml @@ -1,6 +1,6 @@ - + 202020 DDDDDDD8 diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 59bb62572..1655ea21d 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -18,15 +18,21 @@ based on: 'recalbox-multi' by the Recalbox community - + 1.23 0.25 0.125 - + ./core/fonts/Exo2-RegularCondensed.otf 0.035 + 0.5 0.6437 + 1 0.056 + 0.5 0.5 + true + sy_gamecount + center + 50 - 0.5 0.5 0.5 0.5 From 69b9b587d6bf2735bd1c1647ac6b5bb5b6e9889f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 7 Feb 2022 21:05:56 +0100 Subject: [PATCH 08/82] Added support for text entries to CarouselComponent. Also fixed some issues with the carousel wheels and removed an unused function in ThemeData. --- es-app/src/views/SystemView.cpp | 14 +- es-core/src/ThemeData.cpp | 34 +-- es-core/src/ThemeData.h | 1 - es-core/src/components/CarouselComponent.cpp | 255 ++++++++++--------- es-core/src/components/CarouselComponent.h | 21 +- 5 files changed, 166 insertions(+), 159 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index cfea938a9..117afd0c2 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -331,8 +331,8 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) void SystemView::populate() { auto themeSets = ThemeData::getThemeSets(); - std::map::const_iterator selectedSet = - themeSets.find(Settings::getInstance()->getString("ThemeSet")); + std::map::const_iterator selectedSet { + themeSets.find(Settings::getInstance()->getString("ThemeSet"))}; assert(selectedSet != themeSets.cend()); mLegacyMode = selectedSet->second.capabilities.legacyTheme; @@ -344,6 +344,8 @@ void SystemView::populate() for (auto it : SystemData::sSystemVector) { const std::shared_ptr& theme {it->getTheme()}; + std::string logoPath; + std::string defaultLogoPath; if (mViewNeedsReload) getViewElements(theme); @@ -369,6 +371,10 @@ void SystemView::populate() for (auto& element : theme->getViewElements("system").elements) { if (element.second.type == "carousel") { mCarousel->applyTheme(theme, "system", element.first, ThemeFlags::ALL); + if (element.second.has("logo")) + logoPath = element.second.get("logo"); + if (element.second.has("defaultLogo")) + defaultLogoPath = element.second.get("defaultLogo"); } else if (element.second.type == "image") { elements.imageComponents.emplace_back(std::make_unique()); @@ -426,8 +432,10 @@ void SystemView::populate() CarouselComponent::Entry entry; entry.name = it->getName(); entry.object = it; + entry.data.logoPath = logoPath; + entry.data.defaultLogoPath = defaultLogoPath; - mCarousel->addEntry(theme, entry); + mCarousel->addEntry(theme, entry, mLegacyMode); } for (auto& elements : mSystemElements) { diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index cde942e77..0925aa0a5 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -203,12 +203,21 @@ std::map> {"color", COLOR}, {"colorEnd", COLOR}, {"gradientType", STRING}, + {"logo", PATH}, + {"defaultLogo", PATH}, + {"logoSize", NORMALIZED_PAIR}, {"logoScale", FLOAT}, {"logoRotation", FLOAT}, {"logoRotationOrigin", NORMALIZED_PAIR}, - {"logoSize", NORMALIZED_PAIR}, {"logoAlignment", STRING}, {"maxLogoCount", FLOAT}, + {"text", STRING}, + {"textColor", COLOR}, + {"textBackgroundColor", COLOR}, + {"textStyle", STRING}, + {"fontPath", PATH}, + {"fontSize", FLOAT}, + {"lineSpacing", FLOAT}, {"zIndex", FLOAT}, {"legacyZIndexMode", STRING}}}, {"textlist", @@ -545,29 +554,6 @@ const std::string ThemeData::getAspectRatioLabel(const std::string& aspectRatio) return "invalid ratio"; } -const std::shared_ptr ThemeData::getDefault() -{ - static std::shared_ptr theme = nullptr; - if (theme == nullptr) { - theme = std::shared_ptr(new ThemeData()); - - const std::string path {Utils::FileSystem::getHomePath() + - "/.emulationstation/es_theme_default.xml"}; - if (Utils::FileSystem::exists(path)) { - try { - std::map emptyMap; - theme->loadFile(emptyMap, path); - } - catch (ThemeException& e) { - LOG(LogError) << e.what(); - theme = std::shared_ptr(new ThemeData()); // Reset to empty. - } - } - } - - return theme; -} - unsigned int ThemeData::getHexColor(const std::string& str) { ThemeException error; diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index b31a124cb..a256d8a6d 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -222,7 +222,6 @@ public: std::map mVariables; private: - static const std::shared_ptr getDefault(); unsigned int getHexColor(const std::string& str); std::string resolvePlaceholders(const std::string& in); diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index 1c9babd4b..185cbbcae 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -24,122 +24,113 @@ CarouselComponent::CarouselComponent() , mCamOffset {0.0f} , mPreviousScrollVelocity {0} , mType {HORIZONTAL} + , mFont {Font::get(FONT_SIZE_LARGE)} + , mTextColor {0x000000FF} + , mTextBackgroundColor {0xFFFFFF00} + , mLineSpacing {1.5f} , mLogoAlignment {ALIGN_CENTER} , mMaxLogoCount {3} , mLogoSize {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f} , mLogoScale {1.2f} , mLogoRotation {7.5f} - , mLogoRotationOrigin {-5.0f, 0.5f} + , mLogoRotationOrigin {-3.0f, 0.5f} , mCarouselColor {0} , mCarouselColorEnd {0} , mColorGradientHorizontal {true} { } -void CarouselComponent::addEntry(const std::shared_ptr& theme, Entry& entry) +void CarouselComponent::addEntry(const std::shared_ptr& theme, + Entry& entry, + bool legacyMode) { // Make logo. - const ThemeData::ThemeElement* logoElem {theme->getElement("system", "image_logo", "image")}; + if (legacyMode) { + const ThemeData::ThemeElement* logoElem { + theme->getElement("system", "image_logo", "image")}; - if (logoElem) { - std::string path; - if (logoElem->has("path")) - path = logoElem->get("path"); - std::string defaultPath {logoElem->has("default") ? logoElem->get("default") : - ""}; - if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) || - (!defaultPath.empty() && ResourceManager::getInstance().fileExists(defaultPath))) { - auto logo = std::make_shared(false, false); - logo->setMaxSize(glm::round(mLogoSize * mLogoScale)); - logo->applyTheme(theme, "system", "image_logo", ThemeFlags::PATH | ThemeFlags::COLOR); - logo->setRotateByTargetSize(true); - entry.data.logo = logo; - } - } - - if (!entry.data.logo) { - glm::vec2 resolution {static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}; - glm::vec3 center {resolution.x / 2.0f, resolution.y / 2.0f, 1.0f}; - - // Placeholder Image. - logoElem = theme->getElement("system", "image_logoPlaceholderImage", "image"); if (logoElem) { - auto path = logoElem->get("path"); + std::string path; + if (logoElem->has("path")) + path = logoElem->get("path"); std::string defaultPath { logoElem->has("default") ? logoElem->get("default") : ""}; if ((!path.empty() && ResourceManager::getInstance().fileExists(path)) || (!defaultPath.empty() && ResourceManager::getInstance().fileExists(defaultPath))) { auto logo = std::make_shared(false, false); - logo->applyTheme(theme, "system", "image_logoPlaceholderImage", ThemeFlags::ALL); - if (!logoElem->has("size")) - logo->setMaxSize(mLogoSize * mLogoScale); + logo->setMaxSize(glm::round(mLogoSize * mLogoScale)); + logo->applyTheme(theme, "system", "image_logo", + ThemeFlags::PATH | ThemeFlags::COLOR); logo->setRotateByTargetSize(true); entry.data.logo = logo; } } - - // Placeholder Text. - const ThemeData::ThemeElement* logoPlaceholderText = - theme->getElement("system", "text_logoPlaceholderText", "text"); - if (logoPlaceholderText) { - // Element 'logoPlaceholderText' found in theme configuration. - auto text = std::make_shared(entry.name, Font::get(FONT_SIZE_LARGE), - 0x000000FF, ALIGN_CENTER); - text->setSize(mLogoSize * mLogoScale); - if (mType == VERTICAL || mType == VERTICAL_WHEEL) { - text->setHorizontalAlignment(mLogoAlignment); - text->setVerticalAlignment(ALIGN_CENTER); - } - else { - text->setHorizontalAlignment(ALIGN_CENTER); - text->setVerticalAlignment(mLogoAlignment); - } - text->applyTheme(theme, "system", "text_logoPlaceholderText", - ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | - ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING | - ThemeFlags::TEXT); - if (!entry.data.logo) - entry.data.logo = text; + } + else { + if (entry.data.logoPath != "" && + ResourceManager::getInstance().fileExists(entry.data.logoPath)) { + auto logo = std::make_shared(false, false); + logo->setImage(entry.data.logoPath, false, false); + logo->setMaxSize(glm::round(mLogoSize * mLogoScale)); + logo->applyTheme(theme, "system", "", ThemeFlags::ALL); + logo->setRotateByTargetSize(true); + entry.data.logo = logo; } - else { - // Fallback to legacy centered placeholder text. - auto text = std::make_shared(entry.name, Font::get(FONT_SIZE_LARGE), - 0x000000FF, ALIGN_CENTER); - text->setSize(mLogoSize * mLogoScale); + else if (entry.data.defaultLogoPath != "" && + ResourceManager::getInstance().fileExists(entry.data.defaultLogoPath)) { + auto defaultLogo = std::make_shared(false, false); + defaultLogo->setImage(entry.data.defaultLogoPath, false, false); + defaultLogo->setMaxSize(glm::round(mLogoSize * mLogoScale)); + defaultLogo->applyTheme(theme, "system", "", ThemeFlags::ALL); + defaultLogo->setRotateByTargetSize(true); + entry.data.logo = defaultLogo; + } + } + + if (!entry.data.logo) { + // If no logo image is present, add logo text as fallback. + auto text = std::make_shared(entry.name, mFont, 0x000000FF, ALIGN_CENTER); + text->setSize(mLogoSize * mLogoScale); + if (legacyMode) { text->applyTheme(theme, "system", "text_logoText", ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING | ThemeFlags::TEXT); - entry.data.logo = text; + } + if (!legacyMode) { + text->setLineSpacing(mLineSpacing); + if (mText != "") + text->setValue(mText); + text->setColor(mTextColor); + text->setBackgroundColor(mTextBackgroundColor); + text->setRenderBackground(true); + } + entry.data.logo = text; - if (mType == VERTICAL || mType == VERTICAL_WHEEL) { - text->setHorizontalAlignment(mLogoAlignment); - text->setVerticalAlignment(ALIGN_CENTER); - } - else { - text->setHorizontalAlignment(ALIGN_CENTER); - text->setVerticalAlignment(mLogoAlignment); - } + if (mLogoAlignment == ALIGN_LEFT || mLogoAlignment == ALIGN_RIGHT) { + text->setHorizontalAlignment(mLogoAlignment); + text->setVerticalAlignment(ALIGN_CENTER); + } + else if (mLogoAlignment == ALIGN_TOP || mLogoAlignment == ALIGN_BOTTOM) { + text->setVerticalAlignment(mLogoAlignment); + text->setHorizontalAlignment(ALIGN_CENTER); + } + else { + text->setHorizontalAlignment(ALIGN_CENTER); + text->setVerticalAlignment(ALIGN_CENTER); } } - if (mType == VERTICAL || mType == VERTICAL_WHEEL) { - if (mLogoAlignment == ALIGN_LEFT) - entry.data.logo->setOrigin(0, 0.5); - else if (mLogoAlignment == ALIGN_RIGHT) - entry.data.logo->setOrigin(1.0, 0.5); - else - entry.data.logo->setOrigin(0.5, 0.5); - } - else { - if (mLogoAlignment == ALIGN_TOP) - entry.data.logo->setOrigin(0.5, 0); - else if (mLogoAlignment == ALIGN_BOTTOM) - entry.data.logo->setOrigin(0.5, 1); - else - entry.data.logo->setOrigin(0.5, 0.5); - } + if (mLogoAlignment == ALIGN_LEFT) + entry.data.logo->setOrigin(0, 0.5); + else if (mLogoAlignment == ALIGN_RIGHT) + entry.data.logo->setOrigin(1.0, 0.5); + else if (mLogoAlignment == ALIGN_TOP) + entry.data.logo->setOrigin(0.5, 0); + else if (mLogoAlignment == ALIGN_BOTTOM) + entry.data.logo->setOrigin(0.5, 1); + else + entry.data.logo->setOrigin(0.5, 0.5); glm::vec2 denormalized {mLogoSize * entry.data.logo->getOrigin()}; entry.data.logo->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f}); @@ -225,17 +216,12 @@ void CarouselComponent::render(const glm::mat4& parentTrans) float yOff {0.0f}; switch (mType) { - case VERTICAL_WHEEL: { - yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y); - if (mLogoAlignment == ALIGN_LEFT) - xOff = mLogoSize.x / 10.0f; - else if (mLogoAlignment == ALIGN_RIGHT) - xOff = mSize.x - (mLogoSize.x * 1.1f); - else - xOff = (mSize.x - mLogoSize.x) / 2.0f; + case HORIZONTAL_WHEEL: + case VERTICAL_WHEEL: + xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.y)); + yOff = (mSize.y - mLogoSize.y) / 2.0f; break; - } - case VERTICAL: { + case VERTICAL: logoSpacing.y = ((mSize.y - (mLogoSize.y * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.y; yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y); @@ -246,20 +232,8 @@ void CarouselComponent::render(const glm::mat4& parentTrans) else xOff = (mSize.x - mLogoSize.x) / 2.0f; break; - } - case HORIZONTAL_WHEEL: { - xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.y)); - if (mLogoAlignment == ALIGN_TOP) - yOff = mLogoSize.y / 10.0f; - else if (mLogoAlignment == ALIGN_BOTTOM) - yOff = mSize.y - (mLogoSize.y * 1.1f); - else - yOff = (mSize.y - mLogoSize.y) / 2.0f; - break; - } - case HORIZONTAL: { - } - default: { + case HORIZONTAL: + default: logoSpacing.x = ((mSize.x - (mLogoSize.x * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.x; xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.x)); @@ -270,7 +244,6 @@ void CarouselComponent::render(const glm::mat4& parentTrans) else yOff = (mSize.y - mLogoSize.y) / 2.0f; break; - } } int center {static_cast(mCamOffset)}; @@ -374,28 +347,74 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, mColorGradientHorizontal = !(elem->get("gradientType").compare("horizontal")); if (elem->has("logoScale")) - mLogoScale = elem->get("logoScale"); - if (elem->has("logoSize")) - mLogoSize = elem->get("logoSize") * - glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight()); + mLogoScale = glm::clamp(elem->get("logoScale"), 0.5f, 3.0f); + if (elem->has("logoSize")) { + // Keep size within a 0.05 and 1.0 multiple of the screen size. + glm::vec2 logoSize {elem->get("logoSize")}; + if (std::max(logoSize.x, logoSize.y) > 1.0f) { + logoSize /= std::max(logoSize.x, logoSize.y); + } + else if (std::min(logoSize.x, logoSize.y) < 0.005f) { + float ratio {std::min(logoSize.x, logoSize.y) / 0.005f}; + logoSize /= ratio; + // Just an extra precaution if a crazy ratio was used. + logoSize.x = glm::clamp(logoSize.x, 0.005f, 1.0f); + logoSize.y = glm::clamp(logoSize.y, 0.005f, 1.0f); + } + mLogoSize = logoSize * glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight()); + } if (elem->has("maxLogoCount")) - mMaxLogoCount = static_cast(std::round(elem->get("maxLogoCount"))); + mMaxLogoCount = + glm::clamp(static_cast(std::round(elem->get("maxLogoCount"))), 2, 30); if (elem->has("logoRotation")) mLogoRotation = elem->get("logoRotation"); if (elem->has("logoRotationOrigin")) mLogoRotationOrigin = elem->get("logoRotationOrigin"); if (elem->has("logoAlignment")) { - if (!(elem->get("logoAlignment").compare("left"))) + if (!(elem->get("logoAlignment").compare("left")) && mType != HORIZONTAL) { mLogoAlignment = ALIGN_LEFT; - else if (!(elem->get("logoAlignment").compare("right"))) + } + else if (!(elem->get("logoAlignment").compare("right")) && + mType != HORIZONTAL) { mLogoAlignment = ALIGN_RIGHT; - else if (!(elem->get("logoAlignment").compare("top"))) + } + else if (!(elem->get("logoAlignment").compare("top")) && mType != VERTICAL) { mLogoAlignment = ALIGN_TOP; - else if (!(elem->get("logoAlignment").compare("bottom"))) + } + else if (!(elem->get("logoAlignment").compare("bottom")) && + mType != VERTICAL) { mLogoAlignment = ALIGN_BOTTOM; - else + } + else { mLogoAlignment = ALIGN_CENTER; + } + } + + mFont = Font::getFromTheme(elem, properties, mFont); + + if (elem->has("textColor")) + mTextColor = elem->get("textColor"); + if (elem->has("textBackgroundColor")) + mTextBackgroundColor = elem->get("textBackgroundColor"); + + if (elem->has("lineSpacing")) + mLineSpacing = glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f); + + std::string textStyle; + + if (elem->has("textStyle")) + textStyle = elem->get("textStyle"); + + if (elem->has("text")) { + if (textStyle == "uppercase") + mText = Utils::String::toUpper(elem->get("text")); + else if (textStyle == "lowercase") + mText = Utils::String::toLower(elem->get("text")); + else if (textStyle == "camelcase") + mText = Utils::String::toCamelCase(elem->get("text")); + else + mText = elem->get("text"); } GuiComponent::applyTheme(theme, view, element, ALL); diff --git a/es-core/src/components/CarouselComponent.h b/es-core/src/components/CarouselComponent.h index d8c421678..04390f7b8 100644 --- a/es-core/src/components/CarouselComponent.h +++ b/es-core/src/components/CarouselComponent.h @@ -20,25 +20,15 @@ class SystemData; struct CarouselElement { std::shared_ptr logo; + std::string logoPath; + std::string defaultLogoPath; }; class CarouselComponent : public IList { public: CarouselComponent(); - - void addElement(const std::shared_ptr& component, - const std::string& name, - SystemData* object) - { - Entry listEntry; - listEntry.name = name; - listEntry.object = object; - listEntry.data.logo = component; - add(listEntry); - } - - void addEntry(const std::shared_ptr& theme, Entry& entry); + void addEntry(const std::shared_ptr& theme, Entry& entry, bool legacyMode); Entry& getEntry(int index) { return mEntries.at(index); } enum CarouselType { @@ -85,6 +75,11 @@ private: int mPreviousScrollVelocity; CarouselType mType; + std::shared_ptr mFont; + unsigned int mTextColor; + unsigned int mTextBackgroundColor; + std::string mText; + float mLineSpacing; Alignment mLogoAlignment; int mMaxLogoCount; glm::vec2 mLogoSize; From 71a647e0cbf83c9cc12f4bd67ecbd15aa49d070a Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 7 Feb 2022 21:18:04 +0100 Subject: [PATCH 09/82] (rbsimple-DE) Updated to use the new carousel logic. Also added default band colors for unthemed systems. --- themes/rbsimple-DE/colors_dark.xml | 18 ++++++++++++++++++ themes/rbsimple-DE/colors_light.xml | 19 +++++++++++++++++++ themes/rbsimple-DE/theme.xml | 18 ++++++++---------- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/themes/rbsimple-DE/colors_dark.xml b/themes/rbsimple-DE/colors_dark.xml index b3ed1b73f..30c27be2a 100644 --- a/themes/rbsimple-DE/colors_dark.xml +++ b/themes/rbsimple-DE/colors_dark.xml @@ -2,6 +2,7 @@ 747474D8 + F0F0F0 C6C6C6 @@ -28,6 +29,23 @@ + + + + F6DD08 + + + 800000 + + + FF0000 + + + 303030 + + + + 404040 diff --git a/themes/rbsimple-DE/colors_light.xml b/themes/rbsimple-DE/colors_light.xml index c08b1a587..23daab5b0 100644 --- a/themes/rbsimple-DE/colors_light.xml +++ b/themes/rbsimple-DE/colors_light.xml @@ -1,5 +1,8 @@ + + 262626 + 202020 DDDDDDD8 @@ -22,6 +25,22 @@ + + + + F6DD08 + + + 800000 + + + FF0000 + + + 303030 + + + 262626 diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 1655ea21d..fcaeb121a 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -19,8 +19,15 @@ based on: 'recalbox-multi' by the Recalbox community + ./${system.theme}/images/logo.svg 1.23 0.25 0.125 + ./core/fonts/Exo2-RegularCondensed.otf + 0.070 + uppercase + 1.2 + ${system.fullName} + 3 ./core/fonts/Exo2-RegularCondensed.otf @@ -29,19 +36,10 @@ based on: 'recalbox-multi' by the Recalbox community 1 0.056 0.5 0.5 true - sy_gamecount center + sy_gamecount 50 - - 0.5 0.5 - 0.5 0.5 - ./core/fonts/Exo2-RegularCondensed.otf - 0.065 - 000000FF - ${system.fullName} - false - From d1cbbad8eecd78d2b4d71b8516265c07bbcedac7 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 7 Feb 2022 21:23:36 +0100 Subject: [PATCH 10/82] Documentation update. --- CHANGELOG.md | 2 + THEMES-DEV.md | 109 ++++++++++++++++++++++++++++++++++------------- THEMES-LEGACY.md | 15 +------ THEMES.md | 13 +----- 4 files changed, 84 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bab55f4cb..b1a68b695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ * Added the ability to set a manual sortname specifically for custom collections using the metadata editor * When scraping in semi-automatic mode, horizontal scrolling of long game names are no longer reset when automatically selecting the result * Reduced CPU usage significantly when a menu is open by not rendering the bottom of the stack +* Reduced CPU usage by only rendering the currently visible system in SystemView * Added an OpenGL ES 2.0 renderer (borrowed from the RetroPie fork of EmulationStation) * Added logging of the display refresh rate on startup * Improved the theme loading error logging to make it consistent and easier to understand @@ -41,6 +42,7 @@ * Large refactoring to improve thread safety and improve singleton pattern usage * Moved all Platform functions to the utility namespace instead of using the global namespace * Implemented proper XML attribute support in ThemeData that eliminated the risk of name collisions +* Migrated the carousel code from SystemView to a separate new CarouselComponent * Changed all occurances of "GameList" to "Gamelist" throughout the codebase * Removed a huge amount of unnecessary Window* function parameters throughout the codebase * Refactored the six gamelist classes into two new classes; GamelistBase and GamelistView diff --git a/THEMES-DEV.md b/THEMES-DEV.md index d77fc22ea..bcb83fcec 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -946,7 +946,13 @@ It's strongly recommended to use the same image dimensions for all badges as var - A string literal to display. * `metadata` - type: STRING - This translates to the metadata values that are available for the game. If an invalid metadata field is defined, it will be printed as a string literal. - - Possible values: + - Possible values for the system view: + - `sy_name` - Short system name as defined in es_systems.xml. + - `sy_fullname` - Full system name as defined in es_systems.xml. + - `sy_gamecount` - Number of games available for the system. Number of favorites are printed inside brackets if applicable. + - `sy_gamecount_games` - Number of games available for the system. Does not print the favorites count. + - `sy_gamecount_favorites` - Number of favorite games for the system, may be blank if favorites are not applicable. + - Possible values for the gamelist view: - `md_name` - Game name. - `md_description` - Game description. Should be combined with the `container` property in most cases. - `md_rating` - The numerical representation of the game rating, for example `3` or `4.5`. @@ -962,9 +968,9 @@ It's strongly recommended to use the same image dimensions for all badges as var - `md_controller` - The controller for the game. Will be blank if none has been selected. - `md_altemulator` - The alternative emulator for the game. Will be blank if none has been selected. * `container` - type: BOOLEAN - - Whether the text should be placed inside a scrollable container. + - Whether the text should be placed inside a scrollable container. Only available for the gamelist view. * `fontPath` - type: PATH - - Path to a truetype font (.ttf). + - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). * `alignment` - type: STRING @@ -1005,7 +1011,7 @@ It's strongly recommended to use the same image dimensions for all badges as var - `md_releasedate` - The release date of the game. - `md_lastplayed` - The time the game was last played. This will be displayed as a value relative to the current date and time. * `fontPath` - type: PATH - - Path to a truetype font (.ttf). + - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). * `alignment` - type: STRING @@ -1054,7 +1060,7 @@ Displays the game count (all games as well as favorites), any applied filters, a - Default is `0.5 0.5` * `backgroundColor` - type: COLOR * `fontPath` - type: PATH - - Path to a truetype font (.ttf). + - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). * `color` - type: COLOR @@ -1095,7 +1101,7 @@ Displays the game count (all games as well as favorites), any applied filters, a * `type` - type: STRING - Sets the scoll direction of the carousel. - - Accepted values are `horizontal`, `vertical`, `horizontal_wheel` or `vertical_wheel`. + - Valid values are `horizontal`, `vertical`, `horizontal_wheel` or `vertical_wheel`. - Default is `horizontal` * `size` - type: NORMALIZED_PAIR - Default is `1 0.2325` @@ -1104,12 +1110,25 @@ Displays the game count (all games as well as favorites), any applied filters, a * `origin` - type: NORMALIZED_PAIR - Where on the carousel `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the carousel exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. * `color` - type: COLOR - - Controls the color of the carousel background. + - Color of the carousel background panel. Setting a value of `00000000` makes the background panel transparent. - Default is `FFFFFFD8` +* `colorEnd` - type: COLOR + - Setting this to something other than what is defined for `color` creates a color gradient on the background panel. + - Default is `0xFFFFFFD8` +* `gradientType` - type: STRING + - The direction to apply the color gradient if both `color` and `colorEnd` have been defined. + - Possible values are `horizontal` or `vertical` + - Default is `horizontal` +* `logo` - type: PATH + - Path to the logo image file. Most common extensions are supported (including .jpg, .png, and unanimated .gif). +* `defaultLogo` - type: PATH + - Path to the default logo file which will be displayed if the image defined via the `logo` property is not found. Most common extensions are supported (including .jpg, .png, and unanimated .gif). * `logoSize` - type: NORMALIZED_PAIR + - Minimum value is `0.05` and maximum value is `1` - Default is `0.25 0.155` * `logoScale` - type: FLOAT. - Selected logo is increased in size by this scale + - Minimum value is `0.5` and maximum value is `3` - Default is `1.2` * `logoRotation` - type: FLOAT - Angle in degrees that the logos should be rotated. Value should be positive. @@ -1118,21 +1137,39 @@ Displays the game count (all games as well as favorites), any applied filters, a * `logoRotationOrigin` - type: NORMALIZED_PAIR - Point around which the logos will be rotated. - This property only applies when `type` is "horizontal_wheel" or "vertical_wheel". - - Default is `-5 0.5` + - Default is `-3 0.5` * `logoAlignment` - type: STRING - - Sets the alignment of the logos relative to the carousel. - - Accepted values are `top`, `bottom` or `center` when `type` is "horizontal" or "horizontal_wheel". - - Accepted values are `left`, `right` or `center` when `type` is "vertical" or "vertical_wheel". + - Sets `logo` and `text` alignment relative to the carousel. + - Valid values are `top`, `bottom` or `center` when `type` is "horizontal" + - Valid values are `left`, `right` or `center` when `type` is "vertical" + - All values are valid when `type` is "horizontal_wheel" or "vertical_wheel". - Default is `center` * `maxLogoCount` - type: FLOAT - Sets the number of logos to display in the carousel. + - Minimum value is `2` and maximum value is `30` - Default is `3` +* `text` - type: STRING + - A string literal to display if there is no `logo` or `defaultLogo` properties defined and if no images were found. +* `textColor` - type: COLOR + - Default is `000000FF` +* `textBackgroundColor` - type: COLOR + - Default is `FFFFFF00` +* `textStyle` - type: STRING + - The style of the text. + - Valid values are `original`, `uppercase`, `lowercase` or `camelcase` + - Default is `original` (original formatting is retained) +* `fontPath` - type: PATH + - Path to a TrueType font (.ttf) used as fallback if there is no `logo` image defined or found, and if `defaultLogo` has not been defined. +* `fontSize` - type: FLOAT + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). + - Default is `0.085` +* `lineSpacing` - type: FLOAT + - Controls the space between lines (as a multiple of font height). + - Minimum value is `0.5` and maximum value is `3` + - Default is `1.5` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. - Default is `50` -* `legacyZIndexMode` - type: BOOLEAN - - If true, the carousel will ignore zIndex and always render on top of other elements. - - Default is `true` #### textlist @@ -1177,24 +1214,36 @@ This is a list containing rows of text which can be navigated using the keyboard The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu. -* `pos` - type: NORMALIZED_PAIR. Default is "0.012 0.9515" -* `origin` - type: NORMALIZED_PAIR. +* `pos` - type: NORMALIZED_PAIR + - Default is `0.012 0.9515` +* `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. -* `textColor` - type: COLOR. Default is 777777FF. -* `textColorDimmed` - type: COLOR. Default is the same value as textColor. Must be placed under the 'system' view. -* `iconColor` - type: COLOR. Default is 777777FF. -* `iconColorDimmed` - type: COLOR. Default is the same value as iconColor. Must be placed under the 'system' view. -* `fontPath` - type: PATH. -* `fontSize` - type: FLOAT. -* `entrySpacing` - type: FLOAT. Default is 16.0. +* `textColor` - type: COLOR + - Default is `777777FF` +* `textColorDimmed` - type: COLOR + - Must be placed under the 'system' view. + - Default is the same value as textColor. +* `iconColor` - type: COLOR + - Default is `777777FF` +* `iconColorDimmed` - type: COLOR + - Must be placed under the 'system' view. + - Default is the same value as iconColor. +* `fontPath` - type: PATH +* `fontSize` - type: FLOAT +* `entrySpacing` - type: FLOAT - Spacing in pixels between the help system elements. -* `iconTextSpacing` - type: FLOAT. Default is 8.0. - - Spacing in pixels within a help system element between it's icon and text. -* `textStyle` - type: STRING. Default is `uppercase`. - - The style of the text. Options: `uppercase`, `lowercase`, `camelcase`. -* `customButtonIcon` - type: PATH. - - A button icon override. Specify the button type in the attribute `button`. The available buttons are: + - Default is `16.0` +* `iconTextSpacing` - type: FLOAT + - Spacing in pixels within a help system element between its icon and text. + - Default is `8.0` +* `textStyle` - type: STRING + - The style of the text. + - Valid values are `uppercase`, `lowercase` or `camelcase` + - Default is `uppercase` +* `customButtonIcon` - type: PATH + - A button icon override. Specify the button type in the attribute `button`. + - The available buttons are: \ `dpad_updown`, `dpad_leftright`, `dpad_all`, @@ -1225,7 +1274,7 @@ The help system is a special element that displays a context-sensitive list of a `button_back_XBOX`, `button_start_XBOX`, `button_back_XBOX360`, - `button_start_XBOX360`. + `button_start_XBOX360` #### imagegrid diff --git a/THEMES-LEGACY.md b/THEMES-LEGACY.md index d6f52950d..0e7d51bee 100644 --- a/THEMES-LEGACY.md +++ b/THEMES-LEGACY.md @@ -364,8 +364,6 @@ Below are the default zIndex values per element type: * System Logo/Text - 50 * `image name="logo"` * `text name="logoText"` - * `image name="logoPlaceholderImage"` - * `text name="logoPlaceholderText"` * Gamelist information - 50 * `text name="gamelistInfo"` * Badges - 50 @@ -418,15 +416,8 @@ or to specify only a portion of the value of a theme property: - The system logo carousel * `image name="logo"` - PATH | COLOR - A logo image, to be displayed in the system logo carousel. -* `image name="logoPlaceholderImage"` - ALL - - A logo image, to be displayed system name in the system logo carousel when no logo is available. Set the position - to `0.5 0.5` to center the image. -* `text name="logoPlaceholderText"` - ALL - - Logo text, to be displayed system name in the system logo carousel when no logo is available. The logo text is - displayed on top of `logoPlaceholderImage`. Set the position to `0.5 0.5` to center the text. * `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT - - **Deprecated:** A logo text, to be displayed system name in the system logo carousel when no logo is available. - Ignored when `logoPlaceholderImage` or `logoPlaceholderText` are set. + - A logo text, to be displayed system name in the system logo carousel when no logo is available. * `text name="systemInfo"` - ALL - Displays details of the system currently selected in the carousel. * You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed @@ -1023,9 +1014,7 @@ It's strongly recommended to use the same image dimensions for all badges as var - Sets the number of logos to display in the carousel. - Default is 3 * `zIndex` - type: FLOAT. - - z-index value for component. Components will be rendered in order of z-index value from low to high. -* `legacyZIndexMode` - type: BOOLEAN - - If true, the carousel will ignore zIndex and always render on top of other components. Default is `true`. + - z-index value for component. Components will be rendered in order of z-index value from low to high with the carousel above all other components. The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu. diff --git a/THEMES.md b/THEMES.md index 2fb7ead79..ba904a2f2 100644 --- a/THEMES.md +++ b/THEMES.md @@ -362,8 +362,6 @@ Below are the default zIndex values per element type: * System Logo/Text - 50 * `image name="logo"` * `text name="logoText"` - * `image name="logoPlaceholderImage"` - * `text name="logoPlaceholderText"` * Gamelist information - 50 * `text name="gamelistInfo"` * Badges - 50 @@ -416,15 +414,8 @@ or to specify only a portion of the value of a theme property: - The system logo carousel * `image name="logo"` - PATH | COLOR - A logo image, to be displayed in the system logo carousel. -* `image name="logoPlaceholderImage"` - ALL - - A logo image, to be displayed system name in the system logo carousel when no logo is available. Set the position - to `0.5 0.5` to center the image. -* `text name="logoPlaceholderText"` - ALL - - Logo text, to be displayed system name in the system logo carousel when no logo is available. The logo text is - displayed on top of `logoPlaceholderImage`. Set the position to `0.5 0.5` to center the text. * `text name="logoText"` - FONT_PATH | COLOR | FORCE_UPPERCASE | LINE_SPACING | TEXT - - **Deprecated:** A logo text, to be displayed system name in the system logo carousel when no logo is available. - Ignored when `logoPlaceholderImage` or `logoPlaceholderText` are set. + - A logo text, to be displayed system name in the system logo carousel when no logo is available. * `text name="systemInfo"` - ALL - Displays details of the system currently selected in the carousel. * You can use extra elements (elements with `extra="true"`) to add your own backgrounds, etc. They will be displayed @@ -996,8 +987,6 @@ It's strongly recommended to use the same image dimensions for all badges as var - Default is 3 * `zIndex` - type: FLOAT. - z-index value for component. Components will be rendered in order of z-index value from low to high. -* `legacyZIndexMode` - type: BOOLEAN - - If true, the carousel will ignore zIndex and always render on top of other components. Default is `true`. The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu. From 03f8e020c3176378ec517b84c56e6938b6dd0e3f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 00:05:06 +0100 Subject: [PATCH 11/82] Fixed a reverse scrolling issue in CarouselComponent. --- es-app/src/views/SystemView.cpp | 15 ++++++++++ es-app/src/views/SystemView.h | 1 + es-core/src/components/CarouselComponent.cpp | 30 +++++++++----------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 117afd0c2..c4c1cb09f 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -32,6 +32,7 @@ namespace SystemView::SystemView() : mCamOffset {0.0f} , mFadeOpacity {0.0f} + , mPreviousScrollVelocity {0} , mUpdatedGameCount {false} , mViewNeedsReload {true} , mLegacyMode {false} @@ -240,6 +241,20 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) if (fabs(target - posMax - startPos - scrollVelocity) < dist) endPos = target - posMax; // Loop around the start (max - 1 -> -1). + // Make sure transitions do not animate in reverse. + bool changedDirection {false}; + if (mPreviousScrollVelocity != 0 && mPreviousScrollVelocity != scrollVelocity) + changedDirection = true; + + if (!changedDirection && scrollVelocity > 0 && endPos < startPos) + endPos = endPos + posMax; + + if (!changedDirection && scrollVelocity < 0 && endPos > startPos) + endPos = endPos - posMax; + + if (scrollVelocity != 0) + mPreviousScrollVelocity = scrollVelocity; + std::string transition_style {Settings::getInstance()->getString("TransitionStyle")}; Animation* anim; diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index 4ae658701..a4e6cdeab 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -90,6 +90,7 @@ private: float mCamOffset; float mFadeOpacity; + int mPreviousScrollVelocity; bool mUpdatedGameCount; bool mViewNeedsReload; diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index 185cbbcae..1c2fd2821 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -435,23 +435,19 @@ void CarouselComponent::onCursorChanged(const CursorState& state) if (fabs(target - posMax - startPos - mScrollVelocity) < dist) endPos = target - posMax; // Loop around the start (max - 1 -> -1). - // This logic is only needed when there are two game systems, to prevent ugly jumps back - // an forth when selecting the same direction rapidly several times in a row. - if (posMax == 2) { - if (mPreviousScrollVelocity == 0) - mPreviousScrollVelocity = mScrollVelocity; - else if (mScrollVelocity < 0 && startPos < endPos) - mPreviousScrollVelocity = -1; - else if (mScrollVelocity > 0 && startPos > endPos) - mPreviousScrollVelocity = 1; - } - if (mPreviousScrollVelocity != 0 && posMax == 2 && mScrollVelocity == mPreviousScrollVelocity) { - if (fabs(endPos - startPos) < 0.5 || fabs(endPos - startPos) > 1.5) { - (mScrollVelocity < 0) ? endPos -= 1 : endPos += 1; - (mCursor == 0) ? mCursor = 1 : mCursor = 0; - return; - } - } + // Make sure there are no reverse jumps between logos. + 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; // No need to animate transition, we're not going anywhere (probably mEntries.size() == 1). if (endPos == mCamOffset) From 027af497d5637daeab0e6257a5aecebe65c86630 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 18:16:15 +0100 Subject: [PATCH 12/82] Fixed an issue where VideoComponent static images were not fading out correctly in the gamelist view. --- es-app/src/views/GamelistView.cpp | 17 ++++++++++++++--- es-core/src/components/VideoComponent.h | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index c16d632b9..c70db67c4 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -205,10 +205,17 @@ void GamelistView::update(int deltaTime) } for (auto& video : mVideoComponents) { - if (!mVideoPlaying) - video->onHide(); - else if (mVideoPlaying && !video->isVideoPaused() && !mWindow->isScreensaverActive()) + if (!mVideoPlaying) { + if (!video->getScrollHide()) + video->onHide(); + else if (!video->hasStaticImage()) + video->onHide(); + else if (video->getOpacity() == 0) + video->onHide(); + } + else if (mVideoPlaying && !video->isVideoPaused() && !mWindow->isScreensaverActive()) { video->onShow(); + } if (ViewController::getInstance()->getGameLaunchTriggered() && video->isAnimationPlaying(0)) video->finishAnimation(0); @@ -733,6 +740,10 @@ void GamelistView::updateInfoPanel() if (image->getScrollHide()) comps.emplace_back(image.get()); } + for (auto& video : mVideoComponents) { + if (video->getScrollHide()) + comps.emplace_back(video.get()); + } for (auto& badge : mBadgeComponents) { if (badge->getScrollHide()) comps.emplace_back(badge.get()); diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index a12ab2eb3..f18d5522b 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -49,6 +49,7 @@ public: void setOpacity(unsigned char opacity) override { mOpacity = opacity; } bool hasStaticVideo() { return !mConfig.staticVideoPath.empty(); } + bool hasStaticImage() { return mStaticImage.getTextureSize() != glm::ivec2 {0, 0}; } void onShow() override; void onHide() override; From 98e265413ef824322820d19ab5f1ce6b1f7bede1 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 18:19:01 +0100 Subject: [PATCH 13/82] Removed some unnecessary legacy gamelist code. --- es-app/src/views/GamelistLegacy.h | 57 +++++++------------------------ 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h index 6c5287965..313bd2bf7 100644 --- a/es-app/src/views/GamelistLegacy.h +++ b/es-app/src/views/GamelistLegacy.h @@ -267,15 +267,9 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr& theme) theme, getName(), mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->getMetadataField(), ALL); - if (mLegacyMode) { - for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_NAME; ++i) - mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getMetadataField(), - ALL ^ ThemeFlags::TEXT); - } - else { - for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_NAME; ++i) - mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getMetadataField(), - ALL); + for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_NAME; ++i) { + mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getMetadataField(), + ALL ^ ThemeFlags::TEXT); } for (auto& container : mContainerComponents) { @@ -550,41 +544,16 @@ void GamelistView::legacyUpdateInfoPanel() std::vector comps; - if (!mLegacyMode) { - 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()); - } - } - - if (mLegacyMode) { - for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::END; ++i) - comps.emplace_back(mTextComponents[i].get()); - comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE].get()); - comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED].get()); - comps.emplace_back(mTextComponents[LegacyText::MD_NAME].get()); - comps.emplace_back(mImageComponents[LegacyImage::MD_THUMBNAIL].get()); - comps.emplace_back(mImageComponents[LegacyImage::MD_MARQUEE].get()); - comps.emplace_back(mImageComponents[LegacyImage::MD_IMAGE].get()); - comps.push_back(mBadgeComponents.front().get()); - comps.push_back(mRatingComponents.front().get()); - } + for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::END; ++i) + comps.emplace_back(mTextComponents[i].get()); + comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE].get()); + comps.emplace_back(mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED].get()); + comps.emplace_back(mTextComponents[LegacyText::MD_NAME].get()); + comps.emplace_back(mImageComponents[LegacyImage::MD_THUMBNAIL].get()); + comps.emplace_back(mImageComponents[LegacyImage::MD_MARQUEE].get()); + comps.emplace_back(mImageComponents[LegacyImage::MD_IMAGE].get()); + comps.push_back(mBadgeComponents.front().get()); + comps.push_back(mRatingComponents.front().get()); for (auto it = comps.cbegin(); it != comps.cend(); ++it) { GuiComponent* comp = *it; From b9e917816984aa471446b2753d58f0a98529a852 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 18:22:06 +0100 Subject: [PATCH 14/82] Fixed multiple issues in CarouselComponent. --- es-app/src/views/SystemView.cpp | 205 +++++++++---------- es-app/src/views/SystemView.h | 7 +- es-core/src/components/CarouselComponent.cpp | 30 ++- 3 files changed, 115 insertions(+), 127 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index c4c1cb09f..39a244cf4 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -24,8 +24,8 @@ namespace { // Buffer values for scrolling velocity (left, stopped, right). - const int logoBuffersLeft[] = {-5, -2, -1}; - const int logoBuffersRight[] = {1, 2, 5}; + const int logoBuffersLeft[] {-5, -2, -1}; + const int logoBuffersRight[] {1, 2, 5}; } // namespace @@ -120,70 +120,21 @@ void SystemView::render(const glm::mat4& parentTrans) if (mCarousel->getNumEntries() == 0) return; // Nothing to render. + renderElements(parentTrans, false); glm::mat4 trans {getTransform() * parentTrans}; - // Adding texture loading buffers depending on scrolling speed and status. - int bufferIndex {mCarousel->getScrollingVelocity() + 1}; - - Renderer::pushClipRect(glm::ivec2 {}, - glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - - for (int i = static_cast(mCamOffset) + logoBuffersLeft[bufferIndex]; - i <= static_cast(mCamOffset) + logoBuffersRight[bufferIndex]; ++i) { - int index {i}; - while (index < 0) - index += static_cast(mCarousel->getNumEntries()); - while (index >= static_cast(mCarousel->getNumEntries())) - index -= static_cast(mCarousel->getNumEntries()); - - if (mCarousel->isAnimationPlaying(0) || index == mCarousel->getCursor()) { - glm::mat4 elementTrans {trans}; - if (mCarousel->getType() == CarouselComponent::HORIZONTAL || - mCarousel->getType() == CarouselComponent::HORIZONTAL_WHEEL) - elementTrans = glm::translate(elementTrans, - glm::vec3 {(i - mCamOffset) * mSize.x, 0.0f, 0.0f}); - else - elementTrans = glm::translate(elementTrans, - glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}); - - Renderer::pushClipRect( - glm::ivec2 {static_cast(elementTrans[3].x), - static_cast(elementTrans[3].y)}, - glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); - - if (mLegacyMode && mSystemElements.size() > static_cast(index)) { - for (auto element : mSystemElements[index].legacyExtras) - element->render(elementTrans); - } - else if (mSystemElements.size() > static_cast(index)) { - for (auto child : mSystemElements[index].children) { - if (child == mCarousel.get()) { - // Render black above anything lower than the zIndex of the carousel - // if fade transitions are in use and we're transitioning. - if (mFadeOpacity) - renderFade(trans); - child->render(trans); - } - else { - child->render(elementTrans); - } - } - } - - if (mLegacyMode) - mLegacySystemInfo->render(elementTrans); - - Renderer::popClipRect(); - } + // During fade transitions draw a black rectangle above all elements placed below the carousel. + if (mFadeOpacity != 0.0f) { + unsigned int fadeColor {0x00000000 | static_cast(mFadeOpacity * 255.0f)}; + Renderer::setMatrix(trans); + Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, fadeColor, fadeColor); } - if (mLegacyMode) { - if (mFadeOpacity) - renderFade(trans); - mCarousel->render(trans); - } + mCarousel->render(trans); - Renderer::popClipRect(); + // For legacy themes the carousel is always rendered on top of all other elements. + if (!mLegacyMode) + renderElements(parentTrans, true); } void SystemView::onThemeChanged(const std::shared_ptr& /*theme*/) @@ -264,24 +215,24 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) anim = new LambdaAnimation( [this, startFade, startPos, endPos, posMax](float t) { t -= 1; - float f = glm::mix(startPos, endPos, t * t * t + 1); - if (f < 0) + 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) - this->mFadeOpacity = + mFadeOpacity = glm::mix(0.0f, 1.0f, glm::clamp(t / 0.2f + startFade, 0.0f, 1.0f)); else if (t < 0.7f) - this->mFadeOpacity = 1.0f; + mFadeOpacity = 1.0f; else - this->mFadeOpacity = - glm::mix(1.0f, 0.0f, glm::clamp((t - 0.6f) / 0.3f, 0.0f, 1.0f)); + mFadeOpacity = glm::mix(1.0f, 0.0f, glm::clamp((t - 0.6f) / 0.3f, 0.0f, 1.0f)); if (t > 0.5f) - this->mCamOffset = endPos; + mCamOffset = endPos; // Update the game count when the entire animation has been completed. if (mFadeOpacity == 1.0f) @@ -294,13 +245,13 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) anim = new LambdaAnimation( [this, startPos, endPos, posMax](float t) { t -= 1; - float f = glm::mix(startPos, endPos, t * t * t + 1); - if (f < 0) + float f {glm::mix(startPos, endPos, t * t * t + 1.0f)}; + if (f < 0.0f) f += posMax; if (f >= posMax) f -= posMax; - this->mCamOffset = f; + mCamOffset = f; // Hack to make the game count being updated in the middle of the animation. bool update {false}; @@ -329,13 +280,13 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) anim = new LambdaAnimation( [this, startPos, endPos, posMax](float t) { t -= 1; - float f = glm::mix(startPos, endPos, t * t * t + 1); - if (f < 0) + float f {glm::mix(startPos, endPos, t * t * t + 1.0f)}; + if (f < 0.0f) f += posMax; if (f >= posMax) f -= posMax; - this->mCamOffset = endPos; + mCamOffset = endPos; }, 500); } @@ -345,6 +296,8 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) void SystemView::populate() { + LOG(LogDebug) << "SystemView::populate(): Populating carousel"; + auto themeSets = ThemeData::getThemeSets(); std::map::const_iterator selectedSet { themeSets.find(Settings::getInstance()->getString("ThemeSet"))}; @@ -362,8 +315,8 @@ void SystemView::populate() std::string logoPath; std::string defaultLogoPath; - if (mViewNeedsReload) - getViewElements(theme); + if (mLegacyMode && mViewNeedsReload) + legacyApplyTheme(theme); if (mLegacyMode) { SystemViewElements elements; @@ -424,8 +377,10 @@ void SystemView::populate() } } } - - elements.children.emplace_back(mCarousel.get()); + else { + // Apply default carousel configuration. + mCarousel->applyTheme(theme, "system", "", ThemeFlags::ALL); + } std::stable_sort( elements.children.begin(), elements.children.end(), @@ -535,40 +490,82 @@ void SystemView::updateGameCount() } } -void SystemView::getViewElements(const std::shared_ptr& theme) +void SystemView::legacyApplyTheme(const std::shared_ptr& theme) { - LOG(LogDebug) << "SystemView::getViewElements()"; - if (theme->hasView("system")) mViewNeedsReload = false; else mViewNeedsReload = true; - if (mLegacyMode) { - mCarousel->applyTheme(theme, "system", "carousel_systemcarousel", ThemeFlags::ALL); + mCarousel->applyTheme(theme, "system", "carousel_systemcarousel", ThemeFlags::ALL); - mLegacySystemInfo->setSize(mSize.x, mLegacySystemInfo->getFont()->getLetterHeight() * 2.2f); - mLegacySystemInfo->setPosition(0.0f, mCarousel->getPosition().y + mCarousel->getSize().y); - mLegacySystemInfo->setBackgroundColor(0xDDDDDDD8); - mLegacySystemInfo->setRenderBackground(true); - mLegacySystemInfo->setFont( - Font::get(static_cast(0.035f * mSize.y), Font::getDefaultPath())); - mLegacySystemInfo->setColor(0x000000FF); - mLegacySystemInfo->setUppercase(true); - mLegacySystemInfo->setZIndex(49.0f); - mLegacySystemInfo->setDefaultZIndex(49.0f); + mLegacySystemInfo->setSize(mSize.x, mLegacySystemInfo->getFont()->getLetterHeight() * 2.2f); + mLegacySystemInfo->setPosition(0.0f, mCarousel->getPosition().y + mCarousel->getSize().y); + mLegacySystemInfo->setBackgroundColor(0xDDDDDDD8); + mLegacySystemInfo->setRenderBackground(true); + mLegacySystemInfo->setFont( + Font::get(static_cast(0.035f * mSize.y), Font::getDefaultPath())); + mLegacySystemInfo->setColor(0x000000FF); + mLegacySystemInfo->setUppercase(true); + mLegacySystemInfo->setZIndex(49.0f); + mLegacySystemInfo->setDefaultZIndex(49.0f); - const ThemeData::ThemeElement* sysInfoElem { - theme->getElement("system", "text_systemInfo", "text")}; + const ThemeData::ThemeElement* sysInfoElem { + theme->getElement("system", "text_systemInfo", "text")}; - if (sysInfoElem) - mLegacySystemInfo->applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL); + if (sysInfoElem) + mLegacySystemInfo->applyTheme(theme, "system", "text_systemInfo", ThemeFlags::ALL); +} + +void SystemView::renderElements(const glm::mat4& parentTrans, bool abovePrimary) +{ + glm::mat4 trans {getTransform() * parentTrans}; + + // Adding texture loading buffers depending on scrolling speed and status. + int bufferIndex {mCarousel->getScrollingVelocity() + 1}; + + const float primaryZIndex {mCarousel->getZIndex()}; + + for (int i = static_cast(mCamOffset) + logoBuffersLeft[bufferIndex]; + i <= static_cast(mCamOffset) + logoBuffersRight[bufferIndex]; ++i) { + int index {i}; + while (index < 0) + index += static_cast(mCarousel->getNumEntries()); + while (index >= static_cast(mCarousel->getNumEntries())) + index -= static_cast(mCarousel->getNumEntries()); + + if (mCarousel->isAnimationPlaying(0) || index == mCarousel->getCursor()) { + glm::mat4 elementTrans {trans}; + if (mCarousel->getType() == CarouselComponent::HORIZONTAL || + mCarousel->getType() == CarouselComponent::HORIZONTAL_WHEEL) + elementTrans = glm::translate(elementTrans, + glm::vec3 {(i - mCamOffset) * mSize.x, 0.0f, 0.0f}); + else + elementTrans = glm::translate(elementTrans, + glm::vec3 {0.0f, (i - mCamOffset) * mSize.y, 0.0f}); + + Renderer::pushClipRect( + glm::ivec2 {static_cast(glm::round(elementTrans[3].x)), + static_cast(glm::round(elementTrans[3].y))}, + glm::ivec2 {static_cast(mSize.x), static_cast(mSize.y)}); + + if (mLegacyMode && mSystemElements.size() > static_cast(index)) { + for (auto element : mSystemElements[index].legacyExtras) + element->render(elementTrans); + } + else if (!mLegacyMode && mSystemElements.size() > static_cast(index)) { + for (auto child : mSystemElements[index].children) { + if (abovePrimary && child->getZIndex() > primaryZIndex && mFadeOpacity == 0.0f) + child->render(elementTrans); + else if (!abovePrimary && child->getZIndex() <= primaryZIndex) + child->render(elementTrans); + } + } + + if (mLegacyMode) + mLegacySystemInfo->render(elementTrans); + + Renderer::popClipRect(); + } } } - -void SystemView::renderFade(const glm::mat4& trans) -{ - unsigned int fadeColor {0x00000000 | static_cast(mFadeOpacity * 255.0f)}; - Renderer::setMatrix(trans); - Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, fadeColor, fadeColor); -} diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index a4e6cdeab..631119100 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -78,14 +78,11 @@ protected: private: void populate(); void updateGameCount(); - // Get the ThemeElements that make up the SystemView. - void getViewElements(const std::shared_ptr& theme); - - void renderFade(const glm::mat4& trans); + void legacyApplyTheme(const std::shared_ptr& theme); + void renderElements(const glm::mat4& parentTrans, bool abovePrimary); std::unique_ptr mCarousel; std::unique_ptr mLegacySystemInfo; - std::vector mSystemElements; float mCamOffset; diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index 1c2fd2821..efadc9296 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -14,8 +14,8 @@ namespace { // Buffer values for scrolling velocity (left, stopped, right). - const int logoBuffersLeft[] = {-5, -2, -1}; - const int logoBuffersRight[] = {1, 2, 5}; + const int logoBuffersLeft[] {-5, -2, -1}; + const int logoBuffersRight[] {1, 2, 5}; } // namespace @@ -138,12 +138,6 @@ void CarouselComponent::addEntry(const std::shared_ptr& theme, add(entry); } -void CarouselComponent::update(int deltaTime) -{ - listUpdate(deltaTime); - GuiComponent::update(deltaTime); -} - bool CarouselComponent::input(InputConfig* config, Input input) { if (input.value != 0) { @@ -191,21 +185,23 @@ bool CarouselComponent::input(InputConfig* config, Input input) return GuiComponent::input(config, input); } +void CarouselComponent::update(int deltaTime) +{ + listUpdate(deltaTime); + GuiComponent::update(deltaTime); +} + void CarouselComponent::render(const glm::mat4& parentTrans) { - // Background box behind logos. glm::mat4 carouselTrans {parentTrans}; carouselTrans = glm::translate(carouselTrans, glm::vec3 {mPosition.x, mPosition.y, 0.0f}); carouselTrans = glm::translate( carouselTrans, glm::vec3 {mOrigin.x * mSize.x * -1.0f, mOrigin.y * mSize.y * -1.0f, 0.0f}); glm::vec2 clipPos {carouselTrans[3].x, carouselTrans[3].y}; - Renderer::pushClipRect( - glm::ivec2 {static_cast(std::round(clipPos.x)), - static_cast(std::round(clipPos.y))}, - glm::ivec2 {static_cast(std::round(mSize.x)), static_cast(std::round(mSize.y))}); - Renderer::setMatrix(carouselTrans); + + // Background box behind logos. Renderer::drawRect(0.0f, 0.0f, mSize.x, mSize.y, mCarouselColor, mCarouselColorEnd, mColorGradientHorizontal); @@ -303,8 +299,6 @@ void CarouselComponent::render(const glm::mat4& parentTrans) comp->setOpacity(static_cast(opacity)); comp->render(logoTrans); } - - Renderer::popClipRect(); } void CarouselComponent::applyTheme(const std::shared_ptr& theme, @@ -456,13 +450,13 @@ void CarouselComponent::onCursorChanged(const CursorState& state) Animation* anim = new LambdaAnimation( [this, startPos, endPos, posMax](float t) { t -= 1; - float f = glm::mix(startPos, endPos, t * t * t + 1); + float f {glm::mix(startPos, endPos, t * t * t + 1)}; if (f < 0) f += posMax; if (f >= posMax) f -= posMax; - this->mCamOffset = f; + mCamOffset = f; }, 500); From 9856a3da1bf5ffcd068fdd8e2b5b6a398ab04e35 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 18:45:03 +0100 Subject: [PATCH 15/82] ThemeData now only prints debug messages for missing files set using variables. --- es-core/src/ThemeData.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 0925aa0a5..dc5d96978 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -1136,10 +1136,22 @@ void ThemeData::parseElement(const pugi::xml_node& root, if (!ResourceManager::getInstance().fileExists(path)) { std::stringstream ss; - LOG(LogWarning) - << error.message << ": Couldn't find file \"" << node.text().get() << "\" " - << ((node.text().get() != path) ? "which resolves to \"" + path + "\"" : - ""); + // For explicits paths, print a warning if the file couldn't be found, but + // only print a debug message if it was set using a variable. + if (str == node.text().as_string()) { + LOG(LogWarning) + << error.message << ": Couldn't find file \"" << node.text().get() + << "\" " + << ((node.text().get() != path) ? "which resolves to \"" + path + "\"" : + ""); + } + else { + LOG(LogDebug) + << error.message << ": Couldn't find file \"" << node.text().get() + << "\" " + << ((node.text().get() != path) ? "which resolves to \"" + path + "\"" : + ""); + } } element.properties[nodeName] = path; break; From 6db671de3dbe84cec1e5609b0b73ecff20731b60 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 20:44:22 +0100 Subject: [PATCH 16/82] Added support to TextComponent for lowercase and capitalized text conversions. Also changed camelCase to capitalized and textStyle to letterCase in various places. --- es-core/src/HelpStyle.cpp | 6 +- es-core/src/HelpStyle.h | 2 +- es-core/src/ThemeData.cpp | 14 ++-- es-core/src/components/CarouselComponent.cpp | 14 ++-- es-core/src/components/HelpComponent.cpp | 6 +- es-core/src/components/TextComponent.cpp | 74 ++++++++++++++++++-- es-core/src/components/TextComponent.h | 4 ++ es-core/src/utils/StringUtil.cpp | 11 +-- es-core/src/utils/StringUtil.h | 2 +- 9 files changed, 102 insertions(+), 31 deletions(-) diff --git a/es-core/src/HelpStyle.cpp b/es-core/src/HelpStyle.cpp index b5d9f732e..13b4ba2e2 100644 --- a/es-core/src/HelpStyle.cpp +++ b/es-core/src/HelpStyle.cpp @@ -24,7 +24,7 @@ HelpStyle::HelpStyle() iconColorDimmed = 0x777777FF; entrySpacing = 16.0f; iconTextSpacing = 8.0f; - textStyle = "uppercase"; + letterCase = "uppercase"; if (FONT_SIZE_SMALL != 0) font = Font::get(FONT_SIZE_SMALL); @@ -71,8 +71,8 @@ void HelpStyle::applyTheme(const std::shared_ptr& theme, const std::s if (elem->has("iconTextSpacing")) iconTextSpacing = elem->get("iconTextSpacing"); - if (elem->has("textStyle")) - textStyle = elem->get("textStyle"); + if (elem->has("letterCase")) + letterCase = elem->get("letterCase"); // Load custom button icons. // The names may look a bit strange when combined with the PREFIX string "button_" but it's diff --git a/es-core/src/HelpStyle.h b/es-core/src/HelpStyle.h index 0eee99dbb..77c60de4a 100644 --- a/es-core/src/HelpStyle.h +++ b/es-core/src/HelpStyle.h @@ -28,7 +28,7 @@ struct HelpStyle { std::shared_ptr font; float entrySpacing; float iconTextSpacing; - std::string textStyle; + std::string letterCase; struct CustomButtonIcons { diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index dc5d96978..dbf443afb 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -150,7 +150,8 @@ std::map> {"alignment", STRING}, {"color", COLOR}, {"backgroundColor", COLOR}, - {"forceUppercase", BOOLEAN}, + {"letterCase", STRING}, + {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes. {"lineSpacing", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, @@ -166,7 +167,8 @@ std::map> {"alignment", STRING}, {"color", COLOR}, {"backgroundColor", COLOR}, - {"forceUppercase", BOOLEAN}, + {"letterCase", STRING}, + {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes. {"lineSpacing", FLOAT}, {"format", STRING}, {"displayRelative", BOOLEAN}, @@ -214,7 +216,7 @@ std::map> {"text", STRING}, {"textColor", COLOR}, {"textBackgroundColor", COLOR}, - {"textStyle", STRING}, + {"letterCase", STRING}, {"fontPath", PATH}, {"fontSize", FLOAT}, {"lineSpacing", FLOAT}, @@ -240,7 +242,8 @@ std::map> {"scrollSound", PATH}, // For backward compatibility with legacy themes. {"alignment", STRING}, {"horizontalMargin", FLOAT}, - {"forceUppercase", BOOLEAN}, + {"letterCase", STRING}, + {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes. {"lineSpacing", FLOAT}, {"zIndex", FLOAT}}}, {"helpsystem", @@ -254,7 +257,8 @@ std::map> {"fontSize", FLOAT}, {"entrySpacing", FLOAT}, {"iconTextSpacing", FLOAT}, - {"textStyle", STRING}, + {"letterCase", STRING}, + {"textStyle", STRING}, // For backward compatibility with legacy themes. {"customButtonIcon", PATH}}}, {"sound", {{"path", PATH}}}, diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index efadc9296..7cbdcb6ba 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -395,18 +395,18 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("lineSpacing")) mLineSpacing = glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f); - std::string textStyle; + std::string letterCase; - if (elem->has("textStyle")) - textStyle = elem->get("textStyle"); + if (elem->has("letterCase")) + letterCase = elem->get("letterCase"); if (elem->has("text")) { - if (textStyle == "uppercase") + if (letterCase == "uppercase") mText = Utils::String::toUpper(elem->get("text")); - else if (textStyle == "lowercase") + else if (letterCase == "lowercase") mText = Utils::String::toLower(elem->get("text")); - else if (textStyle == "camelcase") - mText = Utils::String::toCamelCase(elem->get("text")); + else if (letterCase == "capitalize") + mText = Utils::String::toCapitalized(elem->get("text")); else mText = elem->get("text"); } diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index 04e532052..023f707cf 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -224,10 +224,10 @@ void HelpComponent::updateGrid() // Apply text style and color from the theme to the label and add it to the label list. std::string lblInput = it->second; - if (mStyle.textStyle == "lowercase") + if (mStyle.letterCase == "lowercase") lblInput = Utils::String::toLower(lblInput); - else if (mStyle.textStyle == "camelcase") - lblInput = Utils::String::toCamelCase(lblInput); + else if (mStyle.letterCase == "capitalize") + lblInput = Utils::String::toCapitalized(lblInput); else lblInput = Utils::String::toUpper(lblInput); auto lbl = std::make_shared( diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index ffd221f18..fe5afaef4 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -20,6 +20,8 @@ TextComponent::TextComponent() , mBgColorOpacity {0x00000000} , mRenderBackground {false} , mUppercase {false} + , mLowercase {false} + , mCapitalized {false} , mAutoCalcExtent {1, 1} , mHorizontalAlignment {ALIGN_LEFT} , mVerticalAlignment {ALIGN_CENTER} @@ -43,6 +45,8 @@ TextComponent::TextComponent(const std::string& text, , mBgColorOpacity {0x00000000} , mRenderBackground {false} , mUppercase {false} + , mLowercase {false} + , mCapitalized {false} , mAutoCalcExtent {1, 1} , mHorizontalAlignment {align} , mVerticalAlignment {ALIGN_CENTER} @@ -120,6 +124,30 @@ void TextComponent::setText(const std::string& text, bool update) void TextComponent::setUppercase(bool uppercase) { mUppercase = uppercase; + if (uppercase) { + mLowercase = false; + mCapitalized = false; + } + onTextChanged(); +} + +void TextComponent::setLowercase(bool lowercase) +{ + mLowercase = lowercase; + if (lowercase) { + mUppercase = false; + mCapitalized = false; + } + onTextChanged(); +} + +void TextComponent::setCapitalized(bool capitalized) +{ + mCapitalized = capitalized; + if (capitalized) { + mUppercase = false; + mLowercase = false; + } onTextChanged(); } @@ -198,14 +226,37 @@ void TextComponent::render(const glm::mat4& parentTrans) void TextComponent::calculateExtent() { if (mAutoCalcExtent.x) { - mSize = mFont->sizeText(mUppercase ? Utils::String::toUpper(mText) : mText, mLineSpacing); + if (mUppercase) + mSize = mFont->sizeText(Utils::String::toUpper(mText), mLineSpacing); + else if (mLowercase) + mSize = mFont->sizeText(Utils::String::toLower(mText), mLineSpacing); + else if (mCapitalized) + mSize = mFont->sizeText(Utils::String::toCapitalized(mText), mLineSpacing); + else + mSize = mFont->sizeText(mText, mLineSpacing); // Original case. } else { - if (mAutoCalcExtent.y) - mSize.y = mFont - ->sizeWrappedText(mUppercase ? Utils::String::toUpper(mText) : mText, - getSize().x, mLineSpacing) - .y; + if (mAutoCalcExtent.y) { + if (mUppercase) { + mSize.y = + mFont->sizeWrappedText(Utils::String::toUpper(mText), getSize().x, mLineSpacing) + .y; + } + else if (mLowercase) { + mSize.y = + mFont->sizeWrappedText(Utils::String::toLower(mText), getSize().x, mLineSpacing) + .y; + } + else if (mCapitalized) { + mSize.y = mFont + ->sizeWrappedText(Utils::String::toCapitalized(mText), getSize().x, + mLineSpacing) + .y; + } + else { + mSize.y = mFont->sizeWrappedText(mText, getSize().x, mLineSpacing).y; + } + } } } @@ -218,7 +269,16 @@ void TextComponent::onTextChanged() return; } - std::string text = mUppercase ? Utils::String::toUpper(mText) : mText; + std::string text; + + if (mUppercase) + text = Utils::String::toUpper(mText); + else if (mLowercase) + text = Utils::String::toLower(mText); + else if (mCapitalized) + text = Utils::String::toCapitalized(mText); + else + text = mText; // Original case. std::shared_ptr f = mFont; const bool isMultiline = (mSize.y == 0.0f || mSize.y > f->getHeight() * 1.2f); diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index 05854a524..a133765fe 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -35,6 +35,8 @@ public: void setFont(const std::shared_ptr& font); void setUppercase(bool uppercase); + void setLowercase(bool lowercase); + void setCapitalized(bool capitalize); void onSizeChanged() override; void setText(const std::string& text, bool update = true); void setHiddenText(const std::string& text) { mHiddenText = text; } @@ -90,6 +92,8 @@ private: bool mRenderBackground; bool mUppercase; + bool mLowercase; + bool mCapitalized; glm::ivec2 mAutoCalcExtent; std::shared_ptr mTextCache; Alignment mHorizontalAlignment; diff --git a/es-core/src/utils/StringUtil.cpp b/es-core/src/utils/StringUtil.cpp index 59566a8eb..2e40e86f6 100644 --- a/es-core/src/utils/StringUtil.cpp +++ b/es-core/src/utils/StringUtil.cpp @@ -538,10 +538,13 @@ namespace Utils return stringUpper; } - std::string toCamelCase(const std::string& stringArg) + std::string toCapitalized(const std::string& stringArg) { - std::string line = stringArg; - bool active = true; + if (stringArg == "") + return stringArg; + + std::string line {stringArg}; + bool active {true}; for (int i = 0; line[i] != '\0'; ++i) { if (std::isalpha(line[i])) { @@ -552,7 +555,7 @@ namespace Utils else line[i] = Utils::String::toLower(std::string(1, line[i]))[0]; } - else if (line[i] == ' ') + else if (line[i] == ' ' || line[i] == '\n' || line[i] == '\r' || line[i] == '\t') active = true; } diff --git a/es-core/src/utils/StringUtil.h b/es-core/src/utils/StringUtil.h index ae64019b9..8fbd6af0f 100644 --- a/es-core/src/utils/StringUtil.h +++ b/es-core/src/utils/StringUtil.h @@ -27,7 +27,7 @@ namespace Utils size_t moveCursor(const std::string& stringArg, const size_t cursor, const int amount); std::string toLower(const std::string& stringArg); std::string toUpper(const std::string& stringArg); - std::string toCamelCase(const std::string& stringArg); + std::string toCapitalized(const std::string& stringArg); std::string trim(const std::string& stringArg); std::string replace(const std::string& stringArg, const std::string& replace, From 09bc9770f216af3794d8700a99d33f95c65cfba5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 22:06:34 +0100 Subject: [PATCH 17/82] Changed the theme property forceUppercase to the more versatile letterCase property. --- es-core/src/ThemeData.cpp | 9 ++- es-core/src/ThemeData.h | 13 ++-- es-core/src/components/CarouselComponent.cpp | 22 ++++-- es-core/src/components/DateTimeComponent.cpp | 19 ++++++ es-core/src/components/TextComponent.cpp | 39 ++++++++--- es-core/src/components/TextComponent.h | 4 +- es-core/src/components/TextListComponent.h | 72 +++++++++++++++++++- 7 files changed, 149 insertions(+), 29 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index dbf443afb..07819e888 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -1066,10 +1066,15 @@ void ThemeData::parseElement(const pugi::xml_node& root, std::string nodeName = node.name(); - if (!mLegacyTheme && element.type == "video") { - if (nodeName == "showSnapshotNoVideo" || nodeName == "showSnapshotDelay") + if (!mLegacyTheme) { + if (nodeName == "showSnapshotNoVideo" || nodeName == "showSnapshotDelay") { throw error << ": Legacy <" << nodeName << "> property found for non-legacy theme set"; + } + else if (nodeName == "forceUppercase") { + throw error << ": Legacy <" << nodeName + << "> property found for non-legacy theme set"; + } } // If an attribute exists, then replace nodeName with its name. diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index a256d8a6d..15288ffa4 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -50,12 +50,13 @@ namespace ThemeFlags ALIGNMENT = 0x00000080, TEXT = 0x00000100, METADATA = 0x00000200, - FORCE_UPPERCASE = 0x00000400, - LINE_SPACING = 0x00000800, - DELAY = 0x00001000, - Z_INDEX = 0x00002000, - ROTATION = 0x00004000, - VISIBLE = 0x00008000, + LETTER_CASE = 0x00000400, + FORCE_UPPERCASE = 0x00000800, // For backward compatibility with legacy themes. + LINE_SPACING = 0x00001000, + DELAY = 0x00002000, + Z_INDEX = 0x00004000, + ROTATION = 0x00008000, + VISIBLE = 0x00010000, ALL = 0xFFFFFFFF }; // clang-format on diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index 7cbdcb6ba..1cd128c53 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -94,8 +94,8 @@ void CarouselComponent::addEntry(const std::shared_ptr& theme, if (legacyMode) { text->applyTheme(theme, "system", "text_logoText", ThemeFlags::FONT_PATH | ThemeFlags::FONT_SIZE | ThemeFlags::COLOR | - ThemeFlags::FORCE_UPPERCASE | ThemeFlags::LINE_SPACING | - ThemeFlags::TEXT); + ThemeFlags::LETTER_CASE | ThemeFlags::FORCE_UPPERCASE | + ThemeFlags::LINE_SPACING | ThemeFlags::TEXT); } if (!legacyMode) { text->setLineSpacing(mLineSpacing); @@ -401,14 +401,24 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, letterCase = elem->get("letterCase"); if (elem->has("text")) { - if (letterCase == "uppercase") + if (letterCase == "uppercase") { mText = Utils::String::toUpper(elem->get("text")); - else if (letterCase == "lowercase") + } + else if (letterCase == "lowercase") { mText = Utils::String::toLower(elem->get("text")); - else if (letterCase == "capitalize") + } + else if (letterCase == "capitalize") { mText = Utils::String::toCapitalized(elem->get("text")); - else + } + else if (letterCase == "none") { mText = elem->get("text"); + } + else { + LOG(LogWarning) + << "CarouselComponent: Invalid theme configuration, property set to \"" + << letterCase << "\""; + mText = elem->get("text"); + } } GuiComponent::applyTheme(theme, view, element, ALL); diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index 3ab63c81b..59efb5440 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -147,6 +147,25 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, if (properties & METADATA && elem->has("metadata")) setMetadataField(elem->get("metadata")); + if (properties & LETTER_CASE && elem->has("letterCase")) { + std::string letterCase {elem->get("letterCase")}; + if (letterCase == "uppercase") { + setUppercase(true); + } + else if (letterCase == "lowercase") { + setLowercase(true); + } + else if (letterCase == "capitalize") { + setCapitalize(true); + } + else if (letterCase != "none") { + LOG(LogWarning) + << "DateTimeComponent: Invalid theme configuration, property set to \"" + << letterCase << "\""; + } + } + + // Legacy themes only. if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index fe5afaef4..81603a8ec 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -21,7 +21,7 @@ TextComponent::TextComponent() , mRenderBackground {false} , mUppercase {false} , mLowercase {false} - , mCapitalized {false} + , mCapitalize {false} , mAutoCalcExtent {1, 1} , mHorizontalAlignment {ALIGN_LEFT} , mVerticalAlignment {ALIGN_CENTER} @@ -46,7 +46,7 @@ TextComponent::TextComponent(const std::string& text, , mRenderBackground {false} , mUppercase {false} , mLowercase {false} - , mCapitalized {false} + , mCapitalize {false} , mAutoCalcExtent {1, 1} , mHorizontalAlignment {align} , mVerticalAlignment {ALIGN_CENTER} @@ -126,7 +126,7 @@ void TextComponent::setUppercase(bool uppercase) mUppercase = uppercase; if (uppercase) { mLowercase = false; - mCapitalized = false; + mCapitalize = false; } onTextChanged(); } @@ -136,15 +136,15 @@ void TextComponent::setLowercase(bool lowercase) mLowercase = lowercase; if (lowercase) { mUppercase = false; - mCapitalized = false; + mCapitalize = false; } onTextChanged(); } -void TextComponent::setCapitalized(bool capitalized) +void TextComponent::setCapitalize(bool capitalize) { - mCapitalized = capitalized; - if (capitalized) { + mCapitalize = capitalize; + if (capitalize) { mUppercase = false; mLowercase = false; } @@ -230,7 +230,7 @@ void TextComponent::calculateExtent() mSize = mFont->sizeText(Utils::String::toUpper(mText), mLineSpacing); else if (mLowercase) mSize = mFont->sizeText(Utils::String::toLower(mText), mLineSpacing); - else if (mCapitalized) + else if (mCapitalize) mSize = mFont->sizeText(Utils::String::toCapitalized(mText), mLineSpacing); else mSize = mFont->sizeText(mText, mLineSpacing); // Original case. @@ -247,7 +247,7 @@ void TextComponent::calculateExtent() mFont->sizeWrappedText(Utils::String::toLower(mText), getSize().x, mLineSpacing) .y; } - else if (mCapitalized) { + else if (mCapitalize) { mSize.y = mFont ->sizeWrappedText(Utils::String::toCapitalized(mText), getSize().x, mLineSpacing) @@ -275,7 +275,7 @@ void TextComponent::onTextChanged() text = Utils::String::toUpper(mText); else if (mLowercase) text = Utils::String::toLower(mText); - else if (mCapitalized) + else if (mCapitalize) text = Utils::String::toCapitalized(mText); else text = mText; // Original case. @@ -397,6 +397,25 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, if (properties & METADATA && elem->has("metadata")) setMetadataField(elem->get("metadata")); + if (properties & LETTER_CASE && elem->has("letterCase")) { + std::string letterCase {elem->get("letterCase")}; + if (letterCase == "uppercase") { + setUppercase(true); + } + else if (letterCase == "lowercase") { + setLowercase(true); + } + else if (letterCase == "capitalize") { + setCapitalize(true); + } + else if (letterCase != "none") { + LOG(LogWarning) + << "TextComponent: Invalid theme configuration, property set to \"" + << letterCase << "\""; + } + } + + // Legacy themes only. if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index a133765fe..bd6dc1b89 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -36,7 +36,7 @@ public: void setFont(const std::shared_ptr& font); void setUppercase(bool uppercase); void setLowercase(bool lowercase); - void setCapitalized(bool capitalize); + void setCapitalize(bool capitalize); void onSizeChanged() override; void setText(const std::string& text, bool update = true); void setHiddenText(const std::string& text) { mHiddenText = text; } @@ -93,7 +93,7 @@ private: bool mUppercase; bool mLowercase; - bool mCapitalized; + bool mCapitalize; glm::ivec2 mAutoCalcExtent; std::shared_ptr mTextCache; Alignment mHorizontalAlignment; diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 1ac6bb6c3..0f5c66028 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -78,6 +78,38 @@ public: void setUppercase(bool uppercase) { mUppercase = uppercase; + + if (uppercase) { + mLowercase = false; + mCapitalize = false; + } + + for (auto it = mEntries.begin(); it != mEntries.end(); ++it) + it->data.textCache.reset(); + } + + void setLowercase(bool lowercase) + { + mLowercase = lowercase; + + if (lowercase) { + mUppercase = false; + mCapitalize = false; + } + + for (auto it = mEntries.begin(); it != mEntries.end(); ++it) + it->data.textCache.reset(); + } + + void setCapitalize(bool capitalize) + { + mCapitalize = capitalize; + + if (capitalize) { + mUppercase = false; + mLowercase = false; + } + for (auto it = mEntries.begin(); it != mEntries.end(); ++it) it->data.textCache.reset(); } @@ -115,6 +147,8 @@ private: std::shared_ptr mFont; bool mUppercase; + bool mLowercase; + bool mCapitalize; float mLineSpacing; float mSelectorHeight; float mSelectorOffsetY; @@ -140,6 +174,8 @@ template TextListComponent::TextListComponent() mFont = Font::get(FONT_SIZE_MEDIUM); mUppercase = false; + mLowercase = false; + mCapitalize = false; mLineSpacing = 1.5f; mSelectorHeight = mFont->getSize() * 1.5f; mSelectorOffsetY = 0; @@ -229,9 +265,20 @@ template void TextListComponent::render(const glm::mat4& parentT else color = mColors[entry.data.colorId]; - if (!entry.data.textCache) - entry.data.textCache = std::unique_ptr(font->buildTextCache( - mUppercase ? Utils::String::toUpper(entry.name) : entry.name, 0, 0, 0x000000FF)); + if (!entry.data.textCache) { + if (mUppercase) + entry.data.textCache = std::unique_ptr( + font->buildTextCache(Utils::String::toUpper(entry.name), 0, 0, 0x000000FF)); + else if (mLowercase) + entry.data.textCache = std::unique_ptr( + font->buildTextCache(Utils::String::toLower(entry.name), 0, 0, 0x000000FF)); + else if (mCapitalize) + entry.data.textCache = std::unique_ptr(font->buildTextCache( + Utils::String::toCapitalized(entry.name), 0, 0, 0x000000FF)); + else + entry.data.textCache = + std::unique_ptr(font->buildTextCache(entry.name, 0, 0, 0x000000FF)); + } // If a game is marked as hidden, lower the text opacity a lot. // If a game is marked to not be counted, lower the opacity a moderate amount. @@ -462,6 +509,25 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, } } + if (properties & LETTER_CASE && elem->has("letterCase")) { + std::string letterCase {elem->get("letterCase")}; + if (letterCase == "uppercase") { + setUppercase(true); + } + else if (letterCase == "lowercase") { + setLowercase(true); + } + else if (letterCase == "capitalize") { + setCapitalize(true); + } + else if (letterCase != "none") { + LOG(LogWarning) + << "TextListComponent: Invalid theme configuration, property set to \"" + << letterCase << "\""; + } + } + + // Legacy themes only. if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); From 0bd31eca9fb97855f5e2878b29081f5b2a5f064e Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 22:09:49 +0100 Subject: [PATCH 18/82] (rbsimple-DE) Updates to use the new letterCase property. --- themes/rbsimple-DE/theme.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index fcaeb121a..3f0b721ce 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -24,7 +24,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.25 0.125 ./core/fonts/Exo2-RegularCondensed.otf 0.070 - uppercase + uppercase 1.2 ${system.fullName} 3 @@ -35,7 +35,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.5 0.6437 1 0.056 0.5 0.5 - true + uppercase center sy_gamecount 50 @@ -134,7 +134,7 @@ based on: 'recalbox-multi' by the Recalbox community - true + uppercase 4:3 not implemented ./core/fonts/Exo2-SemiBoldCondensed.otf 0.03 @@ -162,25 +162,25 @@ based on: 'recalbox-multi' by the Recalbox community - true + uppercase ./core/fonts/Exo2-BoldCondensed.otf 0.02 0.144 0.02 - true + uppercase ./core/fonts/Exo2-RegularCondensed.otf 0.02 true - true md_description + uppercase ./core/fonts/Exo2-SemiBoldCondensed.otf 0.024 - true + uppercase ./core/fonts/Exo2-RegularCondensed.otf 0.02 0.144 0.02 @@ -188,7 +188,7 @@ based on: 'recalbox-multi' by the Recalbox community ./core/fonts/Exo2-BoldCondensed.otf 0.025 - true + uppercase From 0f3fddc7dcc170f78665b72c4f944be5302ac2df Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 22:17:31 +0100 Subject: [PATCH 19/82] Documentation update. --- CHANGELOG.md | 5 ++++- THEMES-DEV.md | 31 +++++++++++++++++-------------- THEMES-LEGACY.md | 2 -- THEMES.md | 2 -- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a68b695..a28dc42eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * Added support for defining what type of image metadata to use for all image elements (and also for the video component static image) * Added a legacy (backward compatibility) mode for still supporting older RetroPie EmulationStation themes * Added theme support for Lottie animations (vector graphics) +* Replaced the forceUppercase theme property with a more versatile letterCase property (forceUppercase is retained for legacy theme compatibility) * Made it possible to set any text element as a scrollable container using either metadata values or literal strings * Made it possible to use almost all game metadata field when theming text elements * Added scraper support for displaying the returned platform if it does not match the game platform, or if multiple platforms are defined for the system @@ -28,11 +29,12 @@ * Added the ability to set a manual sortname specifically for custom collections using the metadata editor * When scraping in semi-automatic mode, horizontal scrolling of long game names are no longer reset when automatically selecting the result * Reduced CPU usage significantly when a menu is open by not rendering the bottom of the stack -* Reduced CPU usage by only rendering the currently visible system in SystemView +* Reduced CPU usage significantly by only rendering the necessary systems in SystemView * Added an OpenGL ES 2.0 renderer (borrowed from the RetroPie fork of EmulationStation) * Added logging of the display refresh rate on startup * Improved the theme loading error logging to make it consistent and easier to understand * Added a log warning for unthemed systems during theme set loading +* Changed the warning log level for missing theme files to debug level if the paths are set using variables * Added a color model conversion shader for converting from BGRA to RGBA * Added renderer support for supplying a separate format than internalFormat when creating textures (although not really supported by the OpenGL standard) * Added the rlottie library as a Git subtree @@ -63,6 +65,7 @@ * Changing some values using the metadata editor could lead to an incorrect sort order if the changes were done from within a grouped custom collection * Changing the setting "Group unthemed custom collections" could lead to incorrect custom collections sorting under some circumstances * When multi-scraping in semi-automatic mode and a long game name was scrolling, the start position was not reset when scraping the next game +* The VideoComponent static images were not fading out smoothly on gamelist fast-scrolling (only fixed for non-legacy themes) * Clearing a game in the metadata editor would sometimes not remove all media files (if there were both a .jpg and a .png for a certain file type) * The ScummVM platform entry was missing for TheGamesDB which resulted in very inaccurate scraper searches * During multi-scraping the busy indicator was not displayed after a result was acquired but before the thumbnail was completely downloaded diff --git a/THEMES-DEV.md b/THEMES-DEV.md index bcb83fcec..57f51f769 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -78,7 +78,9 @@ The following are the most important changes compared to the legacy theme struct * The concept of _features_ is gone * The `` tag is gone as tracking theme versions doesn't make much sense after all * The `video` element properties `showSnapshotNoVideo` and `showSnapshotDelay` have been removed +* The `forceUppercase` property has been replaced with the more versatile `letterCase` property * Correct theme structure is enforced more strictly than before, and deviations will generate error log messages and make the theme loading fail +* Many additional elements and properties have been added, refer to the [Reference](THEMES-DEV.md#reference) section for more information Attempting to use any of the legacy logic in the new theme structure will make the theme loading fail, for example adding the _extra="true"_ attribute to any element. @@ -977,8 +979,9 @@ It's strongly recommended to use the same image dimensions for all badges as var - Valid values are `left`, `center`, or `right`. Controls alignment on the X axis and `center` will also align vertically. * `color` - type: COLOR * `backgroundColor` - type: COLOR -* `forceUppercase` - type: BOOLEAN - - Draw text in uppercase. +* `letterCase` - type: STRING + - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` + - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). - Default is `1.5` @@ -1018,8 +1021,9 @@ It's strongly recommended to use the same image dimensions for all badges as var - Valid values are `left`, `center`, or `right`. Controls alignment on the X axis and `center` will also align vertically. * `color` - type: COLOR * `backgroundColor` - type: COLOR -* `forceUppercase` - type: BOOLEAN - - Draw text in uppercase. +* `letterCase` - type: STRING + - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` + - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). - Default is `1.5` @@ -1100,7 +1104,7 @@ Displays the game count (all games as well as favorites), any applied filters, a #### carousel * `type` - type: STRING - - Sets the scoll direction of the carousel. + - Sets the carousel type and scroll direction. - Valid values are `horizontal`, `vertical`, `horizontal_wheel` or `vertical_wheel`. - Default is `horizontal` * `size` - type: NORMALIZED_PAIR @@ -1154,10 +1158,9 @@ Displays the game count (all games as well as favorites), any applied filters, a - Default is `000000FF` * `textBackgroundColor` - type: COLOR - Default is `FFFFFF00` -* `textStyle` - type: STRING - - The style of the text. - - Valid values are `original`, `uppercase`, `lowercase` or `camelcase` - - Default is `original` (original formatting is retained) +* `letterCase` - type: STRING + - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` + - Default is `none` (original letter case is retained) * `fontPath` - type: PATH - Path to a TrueType font (.ttf) used as fallback if there is no `logo` image defined or found, and if `defaultLogo` has not been defined. * `fontSize` - type: FLOAT @@ -1201,8 +1204,9 @@ This is a list containing rows of text which can be navigated using the keyboard - Valid values are `left`, `center`, or `right`. Controls alignment on the X axis. * `horizontalMargin` - type: FLOAT - Horizontal offset for text from the alignment point. If `alignment` is "left", offsets the text to the right. If `alignment` is "right", offsets text to the left. No effect if `alignment` is "center". Given as a percentage of the element's parent's width (same unit as `size`'s X value). -* `forceUppercase` - type: BOOLEAN - - Draw text in uppercase. +* `letterCase` - type: STRING + - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` + - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). - Default is `1.5` @@ -1237,9 +1241,8 @@ The help system is a special element that displays a context-sensitive list of a * `iconTextSpacing` - type: FLOAT - Spacing in pixels within a help system element between its icon and text. - Default is `8.0` -* `textStyle` - type: STRING - - The style of the text. - - Valid values are `uppercase`, `lowercase` or `camelcase` +* `letterCase` - type: STRING + - Valid values are `uppercase`, `lowercase` or `capitalize` - Default is `uppercase` * `customButtonIcon` - type: PATH - A button icon override. Specify the button type in the attribute `button`. diff --git a/THEMES-LEGACY.md b/THEMES-LEGACY.md index 0e7d51bee..01f5a5323 100644 --- a/THEMES-LEGACY.md +++ b/THEMES-LEGACY.md @@ -900,8 +900,6 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren - Spacing in pixels between the help system components. * `iconTextSpacing` - type: FLOAT. Default is 8.0. - Spacing in pixels within a help system component between it's icon and text. -* `textStyle` - type: STRING. Default is `uppercase`. - - The style of the text. Options: `uppercase`, `lowercase`, `camelcase`. * `customButtonIcon` - type: PATH. - A button icon override. Specify the button type in the attribute `button`. The available buttons are: `dpad_updown`, diff --git a/THEMES.md b/THEMES.md index ba904a2f2..aa715f0df 100644 --- a/THEMES.md +++ b/THEMES.md @@ -872,8 +872,6 @@ ES-DE borrows the concept of "nine patches" from Android (or "9-Slices"). Curren - Spacing in pixels between the help system components. * `iconTextSpacing` - type: FLOAT. Default is 8.0. - Spacing in pixels within a help system component between it's icon and text. -* `textStyle` - type: STRING. Default is `uppercase`. - - The style of the text. Options: `uppercase`, `lowercase`, `camelcase`. * `customButtonIcon` - type: PATH. - A button icon override. Specify the button type in the attribute `button`. The available buttons are: `dpad_updown`, From 59f9791efe558ea1dc1f08c5fb73fdca7f169060 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 23:00:15 +0100 Subject: [PATCH 20/82] Added new theme system variables for differentiating between collections and non-collection systems. --- es-app/src/SystemData.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index d49160fe5..70a931ccf 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -1231,6 +1231,18 @@ void SystemData::loadTheme() sysData.insert(std::pair("system.name", getName())); sysData.insert(std::pair("system.theme", getThemeFolder())); sysData.insert(std::pair("system.fullName", getFullName())); + if (isCollection()) { + sysData.insert( + std::pair("system.fullName.collections", getFullName())); + sysData.insert( + std::pair("system.theme.collections", getThemeFolder())); + } + else { + sysData.insert(std::pair("system.fullName.noCollections", + getFullName())); + sysData.insert(std::pair("system.theme.noCollections", + getThemeFolder())); + } mTheme->loadFile(sysData, path); } From bdbc0580d2231a9c7215eaa5e9478108079dc120 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 23:10:45 +0100 Subject: [PATCH 21/82] Added two more theme system variables. --- es-app/src/SystemData.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 70a931ccf..84359029c 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -1232,12 +1232,16 @@ void SystemData::loadTheme() sysData.insert(std::pair("system.theme", getThemeFolder())); sysData.insert(std::pair("system.fullName", getFullName())); if (isCollection()) { + sysData.insert( + std::pair("system.name.collections", getName())); sysData.insert( std::pair("system.fullName.collections", getFullName())); sysData.insert( std::pair("system.theme.collections", getThemeFolder())); } else { + sysData.insert( + std::pair("system.name.noCollections", getName())); sysData.insert(std::pair("system.fullName.noCollections", getFullName())); sysData.insert(std::pair("system.theme.noCollections", From 70eb937f85d813de3e91a22adb8d6d1a8f04f33c Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 23:12:12 +0100 Subject: [PATCH 22/82] Fixed a potential crash for legacy themes with broken configuration. --- es-core/src/ThemeData.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 07819e888..cfc0164c3 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -1182,9 +1182,11 @@ void ThemeData::parseElement(const pugi::xml_node& root, case BOOLEAN: { bool boolVal = false; // Only look at the first character. - if (str.front() == '1' || str.front() == 't' || str.front() == 'T' || - str.front() == 'y' || str.front() == 'Y') - boolVal = true; + if (str.size() > 0) { + if (str.front() == '1' || str.front() == 't' || str.front() == 'T' || + str.front() == 'y' || str.front() == 'Y') + boolVal = true; + } element.properties[node.name()] = boolVal; break; From 788c9a3f58b9b9d8b93d62188371bd76c3871dc1 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 9 Feb 2022 23:14:36 +0100 Subject: [PATCH 23/82] Small documentation update. --- CHANGELOG.md | 1 + THEMES-DEV.md | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a28dc42eb..70f836ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ * Improved the theme loading error logging to make it consistent and easier to understand * Added a log warning for unthemed systems during theme set loading * Changed the warning log level for missing theme files to debug level if the paths are set using variables +* Added new theme system variables for differentiating between collections and non-collection systems * Added a color model conversion shader for converting from BGRA to RGBA * Added renderer support for supplying a separate format than internalFormat when creating textures (although not really supported by the OpenGL standard) * Added the rlottie library as a Git subtree diff --git a/THEMES-DEV.md b/THEMES-DEV.md index 57f51f769..95b3b1d32 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -686,10 +686,16 @@ Theme variables can be used to simplify theme construction. There are 2 types o #### System variables -System variables are system specific and are derived from the values in es_systems.xml. +System variables are system specific and are derived from the values in es_systems.xml (except for collections). * `system.name` +* `system.name.collections` +* `system.name.noCollections` * `system.fullName` +* `system.fullName.collections` +* `system.fullName.noCollections` * `system.theme` +* `system.theme.collections` +* `system.theme.noCollections` #### Theme defined variables Variables can also be defined in the theme. From 28a3beb9cee8c0e6bad110e11f74d0e582793dee Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 10 Feb 2022 20:02:56 +0100 Subject: [PATCH 24/82] ScrollableContainer parameters are now themeable. --- es-app/src/views/GamelistView.cpp | 4 +-- es-core/src/ThemeData.cpp | 3 ++ .../src/components/ScrollableContainer.cpp | 28 +++++++++++++++++++ es-core/src/components/ScrollableContainer.h | 5 ++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index c70db67c4..247dbdb51 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -135,7 +135,6 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) else if (element.second.type == "text") { if (element.second.has("container") && element.second.get("container")) { mContainerComponents.push_back(std::make_unique()); - mContainerComponents.back()->setAutoScroll(true); mContainerComponents.back()->setDefaultZIndex(40.0f); addChild(mContainerComponents.back().get()); mContainerTextComponents.push_back(std::make_unique()); @@ -144,11 +143,12 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) mContainerComponents.back()->applyTheme(theme, "gamelist", element.first, POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE); + mContainerComponents.back()->setAutoScroll(true); mContainerTextComponents.back()->setSize( mContainerComponents.back()->getSize().x, 0.0f); mContainerTextComponents.back()->applyTheme( theme, "gamelist", element.first, - (ALL ^ POSITION ^ Z_INDEX ^ ThemeFlags::SIZE ^ VISIBLE ^ ROTATION) | COLOR); + ALL ^ POSITION ^ Z_INDEX ^ ThemeFlags::SIZE ^ VISIBLE ^ ROTATION); mContainerComponents.back()->setScrollHide(true); } else { diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index cfc0164c3..99f1f54f2 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -145,6 +145,9 @@ std::map> {"text", STRING}, {"metadata", STRING}, {"container", BOOLEAN}, + {"containerScrollSpeed", FLOAT}, + {"containerStartDelay", FLOAT}, + {"containerResetDelay", FLOAT}, {"fontPath", PATH}, {"fontSize", FLOAT}, {"alignment", STRING}, diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index d299ce680..a937db9d2 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -77,6 +77,34 @@ void ScrollableContainer::reset() } } +void ScrollableContainer::applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) +{ + using namespace ThemeFlags; + GuiComponent::applyTheme(theme, view, element, properties); + + const ThemeData::ThemeElement* elem {theme->getElement(view, element, "text")}; + if (!elem || !elem->has("container")) + return; + + if (elem->has("containerScrollSpeed")) { + mAutoScrollSpeedConstant = + AUTO_SCROLL_SPEED / glm::clamp(elem->get("containerScrollSpeed"), 0.1f, 10.0f); + } + + if (elem->has("containerStartDelay")) { + mAutoScrollDelayConstant = + glm::clamp(elem->get("containerStartDelay"), 0.0f, 10.0f) * 1000.0f; + } + + if (elem->has("containerResetDelay")) { + mAutoScrollResetDelayConstant = + glm::clamp(elem->get("containerResetDelay"), 0.0f, 20.0f) * 1000.0f; + } +} + void ScrollableContainer::update(int deltaTime) { if (mSize == glm::vec2 {0.0f, 0.0f}) diff --git a/es-core/src/components/ScrollableContainer.h b/es-core/src/components/ScrollableContainer.h index 500f7fd3c..4201810d5 100644 --- a/es-core/src/components/ScrollableContainer.h +++ b/es-core/src/components/ScrollableContainer.h @@ -33,6 +33,11 @@ public: float autoScrollSpeedConstant) override; void reset(); + void applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) override; + void update(int deltaTime) override; void render(const glm::mat4& parentTrans) override; From e6d6f3252f83c864b92376ae89a83b1ad1c38f1f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Thu, 10 Feb 2022 21:56:02 +0100 Subject: [PATCH 25/82] Improved the StringUtil::toCapitalized function. --- es-core/src/utils/StringUtil.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/es-core/src/utils/StringUtil.cpp b/es-core/src/utils/StringUtil.cpp index 2e40e86f6..5eb54e49f 100644 --- a/es-core/src/utils/StringUtil.cpp +++ b/es-core/src/utils/StringUtil.cpp @@ -546,17 +546,19 @@ namespace Utils std::string line {stringArg}; bool active {true}; - for (int i = 0; line[i] != '\0'; ++i) { - if (std::isalpha(line[i])) { + for (auto& chr : line) { + if (std::isalnum(chr)) { if (active) { - line[i] = Utils::String::toUpper(std::string(1, line[i]))[0]; + chr = std::toupper(chr); active = false; } - else - line[i] = Utils::String::toLower(std::string(1, line[i]))[0]; + else { + chr = std::tolower(chr); + } } - else if (line[i] == ' ' || line[i] == '\n' || line[i] == '\r' || line[i] == '\t') + else if (chr == ' ' || chr == '-' || chr == '\n' || chr == '\r' || chr == '\t') { active = true; + } } return line; From 4eb763d8166aeb0c099e5c093df4d9c8e5fc944c Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 00:19:08 +0100 Subject: [PATCH 26/82] Replaced the ambiguous alignment properties with specific horizontal and vertical properties. --- es-core/src/ThemeData.cpp | 43 ++++-- es-core/src/ThemeData.h | 11 +- es-core/src/components/BadgeComponent.cpp | 14 +- es-core/src/components/CarouselComponent.cpp | 145 +++++++++++++------ es-core/src/components/CarouselComponent.h | 3 +- es-core/src/components/DateTimeComponent.cpp | 37 ++++- es-core/src/components/TextComponent.cpp | 45 +++++- es-core/src/components/TextListComponent.h | 22 ++- es-core/src/resources/Font.h | 2 +- 9 files changed, 247 insertions(+), 75 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 99f1f54f2..d15f016f5 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -44,6 +44,13 @@ std::vector ThemeData::sLegacySupportedFeatures { {"z-index"}, {"visible"}}; +std::vector ThemeData::sLegacyElements { + {"showSnapshotNoVideo"}, + {"showSnapshotDelay"}, + {"forceUppercase"}, + {"alignment"}, + {"logoAlignment"}}; + std::vector> ThemeData::sSupportedAspectRatios { {"16:9", "16:9"}, {"16:9_vertical", "16:9 vertical"}, @@ -124,7 +131,8 @@ std::map> {"origin", NORMALIZED_PAIR}, {"rotation", FLOAT}, {"rotationOrigin", NORMALIZED_PAIR}, - {"alignment", STRING}, + {"horizontalAlignment", STRING}, + {"alignment", STRING}, // For backward compatibility with legacy themes. {"direction", STRING}, {"lines", FLOAT}, {"itemsPerLine", FLOAT}, @@ -150,7 +158,9 @@ std::map> {"containerResetDelay", FLOAT}, {"fontPath", PATH}, {"fontSize", FLOAT}, - {"alignment", STRING}, + {"horizontalAlignment", STRING}, + {"verticalAlignment", STRING}, + {"alignment", STRING}, // For backward compatibility with legacy themes. {"color", COLOR}, {"backgroundColor", COLOR}, {"letterCase", STRING}, @@ -167,7 +177,9 @@ std::map> {"metadata", STRING}, {"fontPath", PATH}, {"fontSize", FLOAT}, - {"alignment", STRING}, + {"horizontalAlignment", STRING}, + {"verticalAlignment", STRING}, + {"alignment", STRING}, // For backward compatibility with legacy themes. {"color", COLOR}, {"backgroundColor", COLOR}, {"letterCase", STRING}, @@ -187,7 +199,9 @@ std::map> {"fontSize", FLOAT}, {"color", COLOR}, {"backgroundColor", COLOR}, - {"alignment", STRING}, + {"horizontalAlignment", STRING}, + {"verticalAlignment", STRING}, + {"alignment", STRING}, // For backward compatibility with legacy themes. {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"rating", @@ -214,7 +228,9 @@ std::map> {"logoScale", FLOAT}, {"logoRotation", FLOAT}, {"logoRotationOrigin", NORMALIZED_PAIR}, - {"logoAlignment", STRING}, + {"logoHorizontalAlignment", STRING}, + {"logoVerticalAlignment", STRING}, + {"logoAlignment", STRING}, // For backward compatibility with legacy themes. {"maxLogoCount", FLOAT}, {"text", STRING}, {"textColor", COLOR}, @@ -243,7 +259,8 @@ std::map> {"fontSize", FLOAT}, {"scrollHide", BOOLEAN}, {"scrollSound", PATH}, // For backward compatibility with legacy themes. - {"alignment", STRING}, + {"horizontalAlignment", STRING}, + {"alignment", STRING}, // For backward compatibility with legacy themes. {"horizontalMargin", FLOAT}, {"letterCase", STRING}, {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes. @@ -1069,14 +1086,14 @@ void ThemeData::parseElement(const pugi::xml_node& root, std::string nodeName = node.name(); + // Strictly enforce removal of legacy elements for non-legacy theme sets by creating + // an unthemed system if they're present in the configuration. if (!mLegacyTheme) { - if (nodeName == "showSnapshotNoVideo" || nodeName == "showSnapshotDelay") { - throw error << ": Legacy <" << nodeName - << "> property found for non-legacy theme set"; - } - else if (nodeName == "forceUppercase") { - throw error << ": Legacy <" << nodeName - << "> property found for non-legacy theme set"; + for (auto& legacyElement : sLegacyElements) { + if (nodeName == legacyElement) { + throw error << ": Legacy <" << nodeName + << "> property found for non-legacy theme set"; + } } } diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 15288ffa4..868f61b61 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -239,14 +239,15 @@ private: const std::map& typeMap, ThemeElement& element); - static std::map> sElementMap; - static std::map> sPropertyAttributeMap; - - static std::vector sLegacySupportedFeatures; - static std::vector sLegacySupportedViews; static std::vector sSupportedViews; + static std::vector sLegacySupportedViews; + static std::vector sLegacySupportedFeatures; + static std::vector sLegacyElements; static std::vector> sSupportedAspectRatios; + static std::map> sPropertyAttributeMap; + static std::map> sElementMap; + static inline std::map mThemeSets; std::map::iterator mCurrentThemeSet; diff --git a/es-core/src/components/BadgeComponent.cpp b/es-core/src/components/BadgeComponent.cpp index 7a7b7549d..e3243668f 100644 --- a/es-core/src/components/BadgeComponent.cpp +++ b/es-core/src/components/BadgeComponent.cpp @@ -195,7 +195,19 @@ void BadgeComponent::applyTheme(const std::shared_ptr& theme, if (!elem) return; - if (elem->has("alignment")) { + if (elem->has("horizontalAlignment")) { + const std::string horizontalAlignment {elem->get("horizontalAlignment")}; + if (horizontalAlignment != "left" && horizontalAlignment != "right") { + LOG(LogWarning) + << "BadgeComponent: Invalid theme configuration, set to \"" + << horizontalAlignment << "\""; + } + else { + mFlexboxComponent.setAlignment(horizontalAlignment); + } + } + // Legacy themes only. + else if (elem->has("alignment")) { const std::string alignment {elem->get("alignment")}; if (alignment != "left" && alignment != "right") { LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index 1cd128c53..d83501c5c 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -28,7 +28,8 @@ CarouselComponent::CarouselComponent() , mTextColor {0x000000FF} , mTextBackgroundColor {0xFFFFFF00} , mLineSpacing {1.5f} - , mLogoAlignment {ALIGN_CENTER} + , mLogoHorizontalAlignment {ALIGN_CENTER} + , mLogoVerticalAlignment {ALIGN_CENTER} , mMaxLogoCount {3} , mLogoSize {Renderer::getScreenWidth() * 0.25f, Renderer::getScreenHeight() * 0.155f} , mLogoScale {1.2f} @@ -107,31 +108,25 @@ void CarouselComponent::addEntry(const std::shared_ptr& theme, } entry.data.logo = text; - if (mLogoAlignment == ALIGN_LEFT || mLogoAlignment == ALIGN_RIGHT) { - text->setHorizontalAlignment(mLogoAlignment); - text->setVerticalAlignment(ALIGN_CENTER); - } - else if (mLogoAlignment == ALIGN_TOP || mLogoAlignment == ALIGN_BOTTOM) { - text->setVerticalAlignment(mLogoAlignment); - text->setHorizontalAlignment(ALIGN_CENTER); - } - else { - text->setHorizontalAlignment(ALIGN_CENTER); - text->setVerticalAlignment(ALIGN_CENTER); - } + text->setHorizontalAlignment(mLogoHorizontalAlignment); + text->setVerticalAlignment(mLogoVerticalAlignment); } - if (mLogoAlignment == ALIGN_LEFT) + // Set origin for the logos based on their alignment so they line up properly. + if (mLogoHorizontalAlignment == ALIGN_LEFT) entry.data.logo->setOrigin(0, 0.5); - else if (mLogoAlignment == ALIGN_RIGHT) + else if (mLogoHorizontalAlignment == ALIGN_RIGHT) entry.data.logo->setOrigin(1.0, 0.5); - else if (mLogoAlignment == ALIGN_TOP) - entry.data.logo->setOrigin(0.5, 0); - else if (mLogoAlignment == ALIGN_BOTTOM) - entry.data.logo->setOrigin(0.5, 1); else entry.data.logo->setOrigin(0.5, 0.5); + if (mLogoVerticalAlignment == ALIGN_TOP) + entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 0); + else if (mLogoVerticalAlignment == ALIGN_BOTTOM) + entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 1); + else + entry.data.logo->setOrigin(entry.data.logo->getOrigin().x, 0.5); + glm::vec2 denormalized {mLogoSize * entry.data.logo->getOrigin()}; entry.data.logo->setPosition(glm::vec3 {denormalized.x, denormalized.y, 0.0f}); @@ -221,9 +216,9 @@ void CarouselComponent::render(const glm::mat4& parentTrans) logoSpacing.y = ((mSize.y - (mLogoSize.y * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.y; yOff = (mSize.y - mLogoSize.y) / 2.0f - (mCamOffset * logoSpacing.y); - if (mLogoAlignment == ALIGN_LEFT) + if (mLogoHorizontalAlignment == ALIGN_LEFT) xOff = mLogoSize.x / 10.0f; - else if (mLogoAlignment == ALIGN_RIGHT) + else if (mLogoHorizontalAlignment == ALIGN_RIGHT) xOff = mSize.x - (mLogoSize.x * 1.1f); else xOff = (mSize.x - mLogoSize.x) / 2.0f; @@ -233,9 +228,9 @@ void CarouselComponent::render(const glm::mat4& parentTrans) logoSpacing.x = ((mSize.x - (mLogoSize.x * mMaxLogoCount)) / (mMaxLogoCount)) + mLogoSize.x; xOff = std::round((mSize.x - mLogoSize.x) / 2.0f - (mCamOffset * logoSpacing.x)); - if (mLogoAlignment == ALIGN_TOP) + if (mLogoVerticalAlignment == ALIGN_TOP) yOff = mLogoSize.y / 10.0f; - else if (mLogoAlignment == ALIGN_BOTTOM) + else if (mLogoVerticalAlignment == ALIGN_BOTTOM) yOff = mSize.y - (mLogoSize.y * 1.1f); else yOff = (mSize.y - mLogoSize.y) / 2.0f; @@ -321,14 +316,25 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, return; if (elem->has("type")) { - if (!(elem->get("type").compare("vertical"))) - mType = VERTICAL; - else if (!(elem->get("type").compare("vertical_wheel"))) - mType = VERTICAL_WHEEL; - else if (!(elem->get("type").compare("horizontal_wheel"))) - mType = HORIZONTAL_WHEEL; - else + const std::string type {elem->get("type")}; + if (type == "horizontal") { mType = HORIZONTAL; + } + else if (type == "horizontal_wheel") { + mType = HORIZONTAL_WHEEL; + } + else if (type == "vertical") { + mType = VERTICAL; + } + else if (type == "vertical_wheel") { + mType = VERTICAL_WHEEL; + } + else { + LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " + " set to \"" + << type << "\""; + mType = HORIZONTAL; + } } if (elem->has("color")) { @@ -338,7 +344,7 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("colorEnd")) mCarouselColorEnd = elem->get("colorEnd"); if (elem->has("gradientType")) - mColorGradientHorizontal = !(elem->get("gradientType").compare("horizontal")); + mColorGradientHorizontal = (elem->get("gradientType") == "horizontal"); if (elem->has("logoScale")) mLogoScale = glm::clamp(elem->get("logoScale"), 0.5f, 3.0f); @@ -365,23 +371,74 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, mLogoRotation = elem->get("logoRotation"); if (elem->has("logoRotationOrigin")) mLogoRotationOrigin = elem->get("logoRotationOrigin"); - if (elem->has("logoAlignment")) { - if (!(elem->get("logoAlignment").compare("left")) && mType != HORIZONTAL) { - mLogoAlignment = ALIGN_LEFT; + + if (elem->has("logoHorizontalAlignment")) { + const std::string alignment {elem->get("logoHorizontalAlignment")}; + if (alignment == "left" && mType != HORIZONTAL) { + mLogoHorizontalAlignment = ALIGN_LEFT; } - else if (!(elem->get("logoAlignment").compare("right")) && - mType != HORIZONTAL) { - mLogoAlignment = ALIGN_RIGHT; + else if (alignment == "right" && mType != HORIZONTAL) { + mLogoHorizontalAlignment = ALIGN_RIGHT; } - else if (!(elem->get("logoAlignment").compare("top")) && mType != VERTICAL) { - mLogoAlignment = ALIGN_TOP; - } - else if (!(elem->get("logoAlignment").compare("bottom")) && - mType != VERTICAL) { - mLogoAlignment = ALIGN_BOTTOM; + else if (alignment == "center") { + mLogoHorizontalAlignment = ALIGN_CENTER; } else { - mLogoAlignment = ALIGN_CENTER; + LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " + " set to \"" + << alignment << "\""; + mLogoHorizontalAlignment = ALIGN_CENTER; + } + } + + if (elem->has("logoVerticalAlignment")) { + const std::string alignment {elem->get("logoVerticalAlignment")}; + if (alignment == "top" && mType != VERTICAL) { + mLogoVerticalAlignment = ALIGN_TOP; + } + else if (alignment == "bottom" && mType != VERTICAL) { + mLogoVerticalAlignment = ALIGN_BOTTOM; + } + else if (alignment == "center") { + mLogoVerticalAlignment = ALIGN_CENTER; + } + else { + LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " + " set to \"" + << alignment << "\""; + mLogoVerticalAlignment = ALIGN_CENTER; + } + } + + // Legacy themes only. + if (elem->has("logoAlignment")) { + const std::string alignment {elem->get("logoAlignment")}; + if (alignment == "left" && mType != HORIZONTAL) { + mLogoHorizontalAlignment = ALIGN_LEFT; + mLogoVerticalAlignment = ALIGN_CENTER; + } + else if (alignment == "right" && mType != HORIZONTAL) { + mLogoHorizontalAlignment = ALIGN_RIGHT; + mLogoVerticalAlignment = ALIGN_CENTER; + } + else if (alignment == "top" && mType != VERTICAL) { + mLogoVerticalAlignment = ALIGN_TOP; + mLogoHorizontalAlignment = ALIGN_CENTER; + } + else if (alignment == "bottom" && mType != VERTICAL) { + mLogoVerticalAlignment = ALIGN_BOTTOM; + mLogoHorizontalAlignment = ALIGN_CENTER; + } + else if (alignment == "center") { + mLogoHorizontalAlignment = ALIGN_CENTER; + mLogoVerticalAlignment = ALIGN_CENTER; + } + else { + LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " + " set to \"" + << alignment << "\""; + mLogoHorizontalAlignment = ALIGN_CENTER; + mLogoVerticalAlignment = ALIGN_CENTER; } } diff --git a/es-core/src/components/CarouselComponent.h b/es-core/src/components/CarouselComponent.h index 04390f7b8..ec5708b62 100644 --- a/es-core/src/components/CarouselComponent.h +++ b/es-core/src/components/CarouselComponent.h @@ -80,7 +80,8 @@ private: unsigned int mTextBackgroundColor; std::string mText; float mLineSpacing; - Alignment mLogoAlignment; + Alignment mLogoHorizontalAlignment; + Alignment mLogoVerticalAlignment; int mMaxLogoCount; glm::vec2 mLogoSize; float mLogoScale; diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index 59efb5440..e4f077c2a 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -132,8 +132,8 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, setRenderBackground(true); } - if (properties & ALIGNMENT && elem->has("alignment")) { - std::string str = elem->get("alignment"); + if (properties & ALIGNMENT && elem->has("horizontalAlignment")) { + std::string str {elem->get("horizontalAlignment")}; if (str == "left") setHorizontalAlignment(ALIGN_LEFT); else if (str == "center") @@ -141,7 +141,38 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, else if (str == "right") setHorizontalAlignment(ALIGN_RIGHT); else - LOG(LogError) << "Unknown text alignment string: " << str; + LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property " + " set to \"" + << str << "\""; + } + + if (properties & ALIGNMENT && elem->has("verticalAlignment")) { + std::string str {elem->get("verticalAlignment")}; + if (str == "top") + setVerticalAlignment(ALIGN_TOP); + else if (str == "center") + setVerticalAlignment(ALIGN_CENTER); + else if (str == "bottom") + setVerticalAlignment(ALIGN_BOTTOM); + else + LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property " + " set to \"" + << str << "\""; + } + + // Legacy themes only. + if (properties & ALIGNMENT && elem->has("alignment")) { + std::string str {elem->get("alignment")}; + if (str == "left") + setHorizontalAlignment(ALIGN_LEFT); + else if (str == "center") + setHorizontalAlignment(ALIGN_CENTER); + else if (str == "right") + setHorizontalAlignment(ALIGN_RIGHT); + else + LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property " + " set to \"" + << str << "\""; } if (properties & METADATA && elem->has("metadata")) diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 81603a8ec..99f48356e 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -362,9 +362,12 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, GuiComponent::applyTheme(theme, view, element, properties); std::string elementType {"text"}; + std::string componentName {"TextComponent"}; - if (element.substr(0, 13) == "gamelistinfo_") + if (element.substr(0, 13) == "gamelistinfo_") { elementType = "gamelistinfo"; + componentName = "gamelistInfoComponent"; + } const ThemeData::ThemeElement* elem = theme->getElement(view, element, elementType); if (!elem) @@ -379,8 +382,8 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, setRenderBackground(true); } - if (properties & ALIGNMENT && elem->has("alignment")) { - std::string str = elem->get("alignment"); + if (properties & ALIGNMENT && elem->has("horizontalAlignment")) { + std::string str {elem->get("horizontalAlignment")}; if (str == "left") setHorizontalAlignment(ALIGN_LEFT); else if (str == "center") @@ -388,7 +391,41 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, else if (str == "right") setHorizontalAlignment(ALIGN_RIGHT); else - LOG(LogError) << "Unknown text alignment string: " << str; + LOG(LogWarning) << componentName + << ": Invalid theme configuration, property " + " set to \"" + << str << "\""; + } + + if (properties & ALIGNMENT && elem->has("verticalAlignment")) { + std::string str {elem->get("verticalAlignment")}; + if (str == "top") + setVerticalAlignment(ALIGN_TOP); + else if (str == "center") + setVerticalAlignment(ALIGN_CENTER); + else if (str == "bottom") + setVerticalAlignment(ALIGN_BOTTOM); + else + LOG(LogWarning) << componentName + << ": Invalid theme configuration, property " + " set to \"" + << str << "\""; + } + + // Legacy themes only. + if (properties & ALIGNMENT && elem->has("alignment")) { + std::string str {elem->get("alignment")}; + if (str == "left") + setHorizontalAlignment(ALIGN_LEFT); + else if (str == "center") + setHorizontalAlignment(ALIGN_CENTER); + else if (str == "right") + setHorizontalAlignment(ALIGN_RIGHT); + else + LOG(LogWarning) << componentName + << ": Invalid theme configuration, property " + " set to \"" + << str << "\""; } if (properties & TEXT && elem->has("text")) diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 0f5c66028..15fcba8ae 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -491,8 +491,8 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, setSelectorHeight(selectorHeight); if (properties & ALIGNMENT) { - if (elem->has("alignment")) { - const std::string& str = elem->get("alignment"); + if (elem->has("horizontalAlignment")) { + const std::string& str {elem->get("horizontalAlignment")}; if (str == "left") setAlignment(ALIGN_LEFT); else if (str == "center") @@ -500,7 +500,23 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, else if (str == "right") setAlignment(ALIGN_RIGHT); else - LOG(LogError) << "Unknown TextListComponent alignment \"" << str << "\"!"; + LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property " + " set to \"" + << str << "\""; + } + // Legacy themes only. + else if (elem->has("alignment")) { + const std::string& str {elem->get("alignment")}; + if (str == "left") + setAlignment(ALIGN_LEFT); + else if (str == "center") + setAlignment(ALIGN_CENTER); + else if (str == "right") + setAlignment(ALIGN_RIGHT); + else + LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property " + " set to \"" + << str << "\""; } if (elem->has("horizontalMargin")) { mHorizontalMargin = elem->get("horizontalMargin") * diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index 481283bbe..61ad7fbfa 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -37,7 +37,7 @@ class TextCache; enum Alignment { ALIGN_LEFT, - ALIGN_CENTER, // Centers both horizontally and vertically. + ALIGN_CENTER, // Used for both horizontal and vertical alignments. ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM From 44ac75ec28187f78d63e4f41b9a898cac355d2d6 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 00:22:20 +0100 Subject: [PATCH 27/82] (rbsimple-DE) Updated for the new alignment properties. Also added ScrollableContainer parameters for the game description container. --- themes/rbsimple-DE/theme.xml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 3f0b721ce..1ba2575c6 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -36,7 +36,7 @@ based on: 'recalbox-multi' by the Recalbox community 1 0.056 0.5 0.5 uppercase - center + center sy_gamecount 50 @@ -45,7 +45,7 @@ based on: 'recalbox-multi' by the Recalbox community - left + left ./core/fonts/Exo2-SemiBoldCondensed.otf 0.021 0.4 0.03 @@ -174,6 +174,9 @@ based on: 'recalbox-multi' by the Recalbox community true + 1 + 4.5 + 7 md_description uppercase ./core/fonts/Exo2-SemiBoldCondensed.otf @@ -281,7 +284,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.025 0.201 0.39 0.711 - left + left 0.01 @@ -289,13 +292,13 @@ based on: 'recalbox-multi' by the Recalbox community 0.025 0.2 0.1 0.773 0.162 - right + right 0.880 0.757 0.13 0.1635 0.5 0.5 - left + left row 2 3 @@ -317,7 +320,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.04 0.201 0.92 0.71 - center + center 0.01 From 5aa8303ae6c0fe1ab5f5ba43bec3c28893523b72 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 00:32:34 +0100 Subject: [PATCH 28/82] Documentation update. --- CHANGELOG.md | 2 ++ THEMES-DEV.md | 73 +++++++++++++++++++++++++++++++++++++----------- THEMES-LEGACY.md | 2 ++ THEMES.md | 2 ++ 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f836ae8..5cac6d4b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ * Added theme support for Lottie animations (vector graphics) * Replaced the forceUppercase theme property with a more versatile letterCase property (forceUppercase is retained for legacy theme compatibility) * Made it possible to set any text element as a scrollable container using either metadata values or literal strings +* Added support for defining the scrollable container speed, start delay and reset delay from the theme configuration +* Improved theme element placement by replacing the "alignment" and "logoAlignment" properties with specific horizontal and vertical properties * Made it possible to use almost all game metadata field when theming text elements * Added scraper support for displaying the returned platform if it does not match the game platform, or if multiple platforms are defined for the system * Added scraping of fan art and updated the media viewer to display these images diff --git a/THEMES-DEV.md b/THEMES-DEV.md index 95b3b1d32..bacc8d9a0 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -78,6 +78,7 @@ The following are the most important changes compared to the legacy theme struct * The concept of _features_ is gone * The `` tag is gone as tracking theme versions doesn't make much sense after all * The `video` element properties `showSnapshotNoVideo` and `showSnapshotDelay` have been removed +* The ambiguous `alignment` property has been replaced with the `horizontalAlignment` and `verticalAlignment` properties (the same is true for `logoAlignment` for the `carousel` component) * The `forceUppercase` property has been replaced with the more versatile `letterCase` property * Correct theme structure is enforced more strictly than before, and deviations will generate error log messages and make the theme loading fail * Many additional elements and properties have been added, refer to the [Reference](THEMES-DEV.md#reference) section for more information @@ -220,11 +221,13 @@ Enforcement of a correct theme configuration is quite strict, and most errors wi Jan 28 17:17:30 Error: ThemeData::parseElement(): "/home/myusername/.emulationstation/themes/mythemeset-DE/theme.xml": Property "origin" for element "image" has no value defined ``` -Sanitization for valid data format and structure is done in this manner, but verification that property values are actually correct (or reasonable) is handled by the individual component that takes care of creating and rendering the specific theme element. What happens in most instances is that a log warning entry is created and the invalid property is reset to its default value. So for these situations, the system will not become unthemed. Here's an example where a badges element accidentally had its alignment property set to _leftr_ instead of _left_: +Sanitization for valid data format and structure is done in this manner, but verification that property values are actually correct (or reasonable) is handled by the individual component that takes care of creating and rendering the specific theme element. What happens in many instances is that a log warning entry is created and the invalid property is reset to its default value. So for these situations, the system will not become unthemed. Here's an example where a badges element accidentally had its horizontalAlignment property set to _leftr_ instead of _left_: ``` -Jan 28 17:25:27 Warn: BadgeComponent: Invalid theme configuration, set to "leftr" +Jan 28 17:25:27 Warn: BadgeComponent: Invalid theme configuration, set to "leftr" ``` +Note however that warnings are not printed for all invalid properties as that would lead to an excessive amount of logging code. This is especially true for numeric values which are commonly just clamped to the allowable range without notifying the theme author. So make sure to check the [Reference](THEMES-DEV.md#reference) section of this document for valid values for each property. + ### Variants A core concept of ES-DE is the use of theme set _variants_ to provide different theme profiles. These are not fixed presets and a theme author can instead name and define whatever variants he wants for his theme (or possibly use no variants at all as they are optional). @@ -891,6 +894,8 @@ It's strongly recommended to use the same image dimensions for all badges as var * `rotationOrigin` - type: NORMALIZED_PAIR - Point around which the image will be rotated. - Default is `0.5 0.5`. +* `horizontalAlignment` - type: STRING. + - Valid values are `left` or `right` * `direction` - type: STRING - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. - Default is `row` @@ -977,12 +982,30 @@ It's strongly recommended to use the same image dimensions for all badges as var - `md_altemulator` - The alternative emulator for the game. Will be blank if none has been selected. * `container` - type: BOOLEAN - Whether the text should be placed inside a scrollable container. Only available for the gamelist view. +* `containerScrollSpeed` - type: FLOAT + - A base speed is automatically calculated based on container and font sizes, so this property applies relative to the auto-calculated value. + - Minimum value is `0.1` and maximum value is `10` + - Default is `1` +* `containerStartDelay` - type: FLOAT + - Delay in seconds before scrolling starts. Note that the text fade-in animation that plays when resetting from the end position will cause a slight delay even if this property is set to zero. + - Minimum value is `0` and maximum value is `10` + - Default is `4.5` +* `containerResetDelay` - type: FLOAT + - Delay in seconds before resetting to the start position after reaching the scrolling end position. + - Minimum value is `0` and maximum value is `20` + - Default is `7` * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). -* `alignment` - type: STRING - - Valid values are `left`, `center`, or `right`. Controls alignment on the X axis and `center` will also align vertically. +* `horizontalAlignment` - type: STRING + - Controls alignment on the X axis. + - Valid values are `left`, `center` or `right` + - Default is `left` +* `verticalAlignment` - type: STRING + - Controls alignment on the Y axis. + - Valid values are `top`, `center` or `bottom` + - Default is `center` * `color` - type: COLOR * `backgroundColor` - type: COLOR * `letterCase` - type: STRING @@ -1023,8 +1046,14 @@ It's strongly recommended to use the same image dimensions for all badges as var - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). -* `alignment` - type: STRING - - Valid values are `left`, `center`, or `right`. Controls alignment on the X axis and `center` will also align vertically. +* `horizontalAlignment` - type: STRING + - Controls alignment on the X axis. + - Valid values are `left`, `center` or `right` + - Default is `left` +* `verticalAlignment` - type: STRING + - Controls alignment on the Y axis. + - Valid values are `top`, `center` or `bottom` + - Default is `center` * `color` - type: COLOR * `backgroundColor` - type: COLOR * `letterCase` - type: STRING @@ -1074,8 +1103,14 @@ Displays the game count (all games as well as favorites), any applied filters, a * `fontSize` - type: FLOAT - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). * `color` - type: COLOR -* `alignment` - type: STRING - - Valid values are `left`, `center`, or `right`. Controls alignment on the X axis and `center` will also align vertically. +* `horizontalAlignment` - type: STRING + - Controls alignment on the X axis. + - Valid values are `left`, `center` or `right` + - Default is `left` +* `verticalAlignment` - type: STRING + - Controls alignment on the Y axis. + - Valid values are `top`, `center` or `bottom` + - Default is `center` * `visible` - type: BOOLEAN - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. - Default is `true` @@ -1134,7 +1169,7 @@ Displays the game count (all games as well as favorites), any applied filters, a * `defaultLogo` - type: PATH - Path to the default logo file which will be displayed if the image defined via the `logo` property is not found. Most common extensions are supported (including .jpg, .png, and unanimated .gif). * `logoSize` - type: NORMALIZED_PAIR - - Minimum value is `0.05` and maximum value is `1` + - Minimum value per axis is `0.05` and maximum value per axis is `1` - Default is `0.25 0.155` * `logoScale` - type: FLOAT. - Selected logo is increased in size by this scale @@ -1148,11 +1183,13 @@ Displays the game count (all games as well as favorites), any applied filters, a - Point around which the logos will be rotated. - This property only applies when `type` is "horizontal_wheel" or "vertical_wheel". - Default is `-3 0.5` -* `logoAlignment` - type: STRING - - Sets `logo` and `text` alignment relative to the carousel. - - Valid values are `top`, `bottom` or `center` when `type` is "horizontal" - - Valid values are `left`, `right` or `center` when `type` is "vertical" - - All values are valid when `type` is "horizontal_wheel" or "vertical_wheel". +* `logoHorizontalAlignment` - type: STRING + - Sets `logo` and `text` alignment relative to the carousel on the X axis, which applies when `type` is "vertical", "horizontal_wheel" or "vertical_wheel". + - Valid values are `left`, `center` or `right` + - Default is `center` +* `logoVerticalAlignment` - type: STRING + - Sets `logo` and `text` alignment relative to the carousel on the Y axis, which applies when `type` is "horizontal", "horizontal_wheel" or "vertical_wheel". + - Valid values are `top`, `center` or `bottom` - Default is `center` * `maxLogoCount` - type: FLOAT - Sets the number of logos to display in the carousel. @@ -1206,10 +1243,12 @@ This is a list containing rows of text which can be navigated using the keyboard - Secondary color; what this means depends on the text list. For example, for game lists, it is the color of a folder. * `fontPath` - type: PATH * `fontSize` - type: FLOAT -* `alignment` - type: STRING - - Valid values are `left`, `center`, or `right`. Controls alignment on the X axis. +* `horizontalAlignment` - type: STRING + - Controls alignment on the X axis. + - Valid values are `left`, `center` or `right` + - Default is `left` * `horizontalMargin` - type: FLOAT - - Horizontal offset for text from the alignment point. If `alignment` is "left", offsets the text to the right. If `alignment` is "right", offsets text to the left. No effect if `alignment` is "center". Given as a percentage of the element's parent's width (same unit as `size`'s X value). + - Horizontal offset for text from the alignment point. If `horizontalAlignment` is "left", offsets the text to the right. If `horizontalAlignment` is "right", offsets text to the left. No effect if `horizontalAlignment` is "center". Given as a percentage of the element's parent's width (same unit as `size`'s X value). * `letterCase` - type: STRING - Valid values are `none`, `uppercase`, `lowercase` or `capitalize` - Default is `none` (original letter case is retained) diff --git a/THEMES-LEGACY.md b/THEMES-LEGACY.md index 01f5a5323..c3a77a8a0 100644 --- a/THEMES-LEGACY.md +++ b/THEMES-LEGACY.md @@ -948,6 +948,8 @@ It's strongly recommended to use the same image dimensions for all badges as var - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`. * `rotationOrigin` - type: NORMALIZED_PAIR. - Point around which the image will be rotated. Default is `0.5 0.5`. +* `alignment` - type: STRING. + - Valid values are `left` or `right` * `direction` - type: STRING. - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. Default is `row`. * `lines` - type: FLOAT. diff --git a/THEMES.md b/THEMES.md index aa715f0df..48f50fd21 100644 --- a/THEMES.md +++ b/THEMES.md @@ -920,6 +920,8 @@ It's strongly recommended to use the same image dimensions for all badges as var - angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Default is `0`. * `rotationOrigin` - type: NORMALIZED_PAIR. - Point around which the image will be rotated. Default is `0.5 0.5`. +* `alignment` - type: STRING. + - Valid values are `left` or `right` * `direction` - type: STRING. - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. Default is `row`. * `lines` - type: FLOAT. From 85cb10d71a0db54c7e8ac10400ebd48d01b74f31 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 18:39:16 +0100 Subject: [PATCH 29/82] Changed some string::compare functions to == operators. Also removed an unused theme property. --- es-app/src/views/GamelistLegacy.h | 6 +++--- es-app/src/views/GamelistView.cpp | 6 +++--- es-app/src/views/ViewController.cpp | 6 +++--- es-core/src/ThemeData.cpp | 1 - 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h index 313bd2bf7..d0da6c2e1 100644 --- a/es-app/src/views/GamelistLegacy.h +++ b/es-app/src/views/GamelistLegacy.h @@ -503,17 +503,17 @@ void GamelistView::legacyUpdateInfoPanel() BadgeComponent::BadgeInfo badgeInfo; badgeInfo.badgeType = badge; if (badge == "controller") { - if (file->metadata.get("controller").compare("") != 0) { + if (file->metadata.get("controller") != "") { badgeInfo.gameController = file->metadata.get("controller"); badgeSlots.push_back(badgeInfo); } } else if (badge == "altemulator") { - if (file->metadata.get(badge).compare("") != 0) + if (file->metadata.get(badge) != "") badgeSlots.push_back(badgeInfo); } else { - if (file->metadata.get(badge).compare("true") == 0) + if (file->metadata.get(badge) == "true") badgeSlots.push_back(badgeInfo); } } diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index 247dbdb51..152f59c41 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -593,17 +593,17 @@ void GamelistView::updateInfoPanel() BadgeComponent::BadgeInfo badgeInfo; badgeInfo.badgeType = badge; if (badge == "controller") { - if (file->metadata.get("controller").compare("") != 0) { + if (file->metadata.get("controller") != "") { badgeInfo.gameController = file->metadata.get("controller"); badgeSlots.push_back(badgeInfo); } } else if (badge == "altemulator") { - if (file->metadata.get(badge).compare("") != 0) + if (file->metadata.get(badge) != "") badgeSlots.push_back(badgeInfo); } else { - if (file->metadata.get(badge).compare("true") == 0) + if (file->metadata.get(badge) == "true") badgeSlots.push_back(badgeInfo); } } diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index f9beda556..25e8e3076 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -728,11 +728,11 @@ std::shared_ptr ViewController::getGamelistView(SystemData* system GamelistViewStyle selectedViewStyle = AUTOMATIC; std::string viewPreference {Settings::getInstance()->getString("GamelistViewStyle")}; - if (viewPreference.compare("basic") == 0) + if (viewPreference == "basic") selectedViewStyle = BASIC; - if (viewPreference.compare("detailed") == 0) + else if (viewPreference == "detailed") selectedViewStyle = DETAILED; - if (viewPreference.compare("video") == 0) + else if (viewPreference == "video") selectedViewStyle = VIDEO; if (selectedViewStyle == AUTOMATIC) { diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index d15f016f5..7844d69bd 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -257,7 +257,6 @@ std::map> {"secondaryColor", COLOR}, {"fontPath", PATH}, {"fontSize", FLOAT}, - {"scrollHide", BOOLEAN}, {"scrollSound", PATH}, // For backward compatibility with legacy themes. {"horizontalAlignment", STRING}, {"alignment", STRING}, // For backward compatibility with legacy themes. From 9a24423c822328c3619c8730450c238f19828d51 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 18:40:39 +0100 Subject: [PATCH 30/82] Fixed an issue where transition animations could stop working after switching theme sets. --- es-core/src/Window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 89abbdd7e..9edb35adc 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -380,7 +380,7 @@ void Window::update(int deltaTime) // will be moved. This is required as theme set changes always makes a transition to // the system view. If we wouldn't make this update, the camera movement would take // place once the menu has been closed. - if (mChangedThemeSet && mGuiStack.size() > 1) { + if (mChangedThemeSet) { mGuiStack.front()->update(deltaTime); mChangedThemeSet = false; } From 89efdef39c946efd08e0846fd2831c549fddf35f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 18:44:24 +0100 Subject: [PATCH 31/82] Fixed an issue where horizontal and vertical color gradients were mixed up. Also added logging in case an invalid gradient type is used in a theme. --- es-core/src/components/CarouselComponent.cpp | 18 ++++++++++++++-- es-core/src/components/ImageComponent.cpp | 22 +++++++++++++++----- es-core/src/components/TextListComponent.h | 18 +++++++++++++--- es-core/src/renderers/Renderer.cpp | 4 ++-- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index d83501c5c..44e044d50 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -343,8 +343,22 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, } if (elem->has("colorEnd")) mCarouselColorEnd = elem->get("colorEnd"); - if (elem->has("gradientType")) - mColorGradientHorizontal = (elem->get("gradientType") == "horizontal"); + + if (elem->has("gradientType")) { + const std::string gradientType {elem->get("gradientType")}; + if (gradientType == "horizontal") { + mColorGradientHorizontal = true; + } + else if (gradientType == "vertical") { + mColorGradientHorizontal = false; + } + else { + mColorGradientHorizontal = true; + LOG(LogWarning) << "CarouselComponent: Invalid theme configuration, property " + " set to \"" + << gradientType << "\""; + } + } if (elem->has("logoScale")) mLogoScale = glm::clamp(elem->get("logoScale"), 0.5f, 3.0f); diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 5253357d1..7962e1041 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -377,8 +377,8 @@ void ImageComponent::updateColors() static_cast((mColorShiftEnd & 0xFF) * opacity)); mVertices[0].col = color; - mVertices[1].col = mColorGradientHorizontal ? colorEnd : color; - mVertices[2].col = mColorGradientHorizontal ? color : colorEnd; + mVertices[1].col = mColorGradientHorizontal ? color : colorEnd; + mVertices[2].col = mColorGradientHorizontal ? colorEnd : color; mVertices[3].col = colorEnd; } @@ -513,9 +513,21 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, setColorShift(elem->get("color")); if (elem->has("colorEnd")) setColorShiftEnd(elem->get("colorEnd")); - if (elem->has("gradientType")) - setColorGradientHorizontal( - !(elem->get("gradientType").compare("horizontal"))); + if (elem->has("gradientType")) { + const std::string gradientType {elem->get("gradientType")}; + if (gradientType == "horizontal") { + setColorGradientHorizontal(true); + } + else if (gradientType == "vertical") { + setColorGradientHorizontal(false); + } + else { + setColorGradientHorizontal(true); + LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property " + " set to \"" + << gradientType << "\""; + } + } } if (elem->has("scrollFadeIn") && elem->get("scrollFadeIn")) diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 15fcba8ae..e08d8ac46 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -474,9 +474,21 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, } if (elem->has("selectorColorEnd")) setSelectorColorEnd(elem->get("selectorColorEnd")); - if (elem->has("selectorGradientType")) - setSelectorColorGradientHorizontal( - !(elem->get("selectorGradientType").compare("horizontal"))); + if (elem->has("selectorGradientType")) { + const std::string gradientType {elem->get("selectorGradientType")}; + if (gradientType == "horizontal") { + setSelectorColorGradientHorizontal(true); + } + else if (gradientType == "vertical") { + setSelectorColorGradientHorizontal(false); + } + else { + setSelectorColorGradientHorizontal(true); + LOG(LogWarning) << "TextListComponent: Invalid theme configuration, property " + " set to \"" + << gradientType << "\""; + } + } if (elem->has("selectedColor")) setSelectedColor(elem->get("selectedColor")); if (elem->has("primaryColor")) diff --git a/es-core/src/renderers/Renderer.cpp b/es-core/src/renderers/Renderer.cpp index b75dc6d5f..8c0095cf4 100644 --- a/es-core/src/renderers/Renderer.cpp +++ b/es-core/src/renderers/Renderer.cpp @@ -477,8 +477,8 @@ namespace Renderer // clang-format off vertices[0] = {{x, y }, {0.0f, 0.0f}, rColor}; - vertices[1] = {{x, y + hL}, {0.0f, 0.0f}, horizontalGradient ? rColorEnd : rColor}; - vertices[2] = {{x + wL, y }, {0.0f, 0.0f}, horizontalGradient ? rColor : rColorEnd}; + vertices[1] = {{x, y + hL}, {0.0f, 0.0f}, horizontalGradient ? rColor : rColorEnd}; + vertices[2] = {{x + wL, y }, {0.0f, 0.0f}, horizontalGradient ? rColorEnd : rColor}; vertices[3] = {{x + wL, y + hL}, {0.0f, 0.0f}, rColorEnd}; // clang-format on From 8fd05bb2a28f1ec897359c8ac682aa10df97165e Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 21:36:22 +0100 Subject: [PATCH 32/82] Fixed a flickering text issue in GuiDetectDevice. --- es-core/src/guis/GuiDetectDevice.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/es-core/src/guis/GuiDetectDevice.cpp b/es-core/src/guis/GuiDetectDevice.cpp index 6a12563b8..a903692dc 100644 --- a/es-core/src/guis/GuiDetectDevice.cpp +++ b/es-core/src/guis/GuiDetectDevice.cpp @@ -15,7 +15,7 @@ #include "utils/FileSystemUtil.h" #include "utils/StringUtil.h" -#define HOLD_TIME 1000 +#define HOLD_TIME 1000.0f GuiDetectDevice::GuiDetectDevice(bool firstRun, bool forcedConfig, @@ -68,8 +68,8 @@ GuiDetectDevice::GuiDetectDevice(bool firstRun, mGrid.setEntry(mMsg1, glm::ivec2 {0, 2}, false, true); - const std::string msg2str = - firstRun ? "PRESS ESC TO SKIP (OR F4 TO QUIT AT ANY TIME)" : "PRESS ESC TO CANCEL"; + const std::string msg2str {firstRun ? "PRESS ESC TO SKIP (OR F4 TO QUIT AT ANY TIME)" : + "PRESS ESC TO CANCEL"}; mMsg2 = std::make_shared(msg2str, Font::get(FONT_SIZE_SMALL), 0x777777FF, ALIGN_CENTER); mGrid.setEntry(mMsg2, glm::ivec2 {0, 3}, false, true); @@ -81,8 +81,8 @@ GuiDetectDevice::GuiDetectDevice(bool firstRun, // Adjust the width relative to the aspect ratio of the screen to make the GUI look coherent // regardless of screen type. The 1.778 aspect ratio value is the 16:9 reference. - float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); - float width = glm::clamp(0.60f * aspectValue, 0.50f, 0.80f) * Renderer::getScreenWidth(); + float aspectValue {1.778f / Renderer::getScreenAspectRatio()}; + float width {glm::clamp(0.60f * aspectValue, 0.50f, 0.80f) * Renderer::getScreenWidth()}; setSize(width, Renderer::getScreenHeight() * 0.5f); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, @@ -96,10 +96,8 @@ void GuiDetectDevice::onSizeChanged() // Grid. mGrid.setSize(mSize); mGrid.setRowHeightPerc(0, mTitle->getFont()->getHeight() / mSize.y); - // mGrid.setRowHeightPerc(1, mDeviceInfo->getFont()->getHeight() / mSize.y()); mGrid.setRowHeightPerc(2, mMsg1->getFont()->getHeight() / mSize.y); mGrid.setRowHeightPerc(3, mMsg2->getFont()->getHeight() / mSize.y); - // mGrid.setRowHeightPerc(4, mDeviceHeld->getFont()->getHeight() / mSize.y()); } bool GuiDetectDevice::input(InputConfig* config, Input input) @@ -140,9 +138,9 @@ bool GuiDetectDevice::input(InputConfig* config, Input input) void GuiDetectDevice::update(int deltaTime) { if (mHoldingConfig) { - // If ES starts and if a known device is connected after startup skip controller - // configuration unless the flag to force the configuration was passed on the - // command line. + // If ES-DE starts and if a known device is connected after startup, then skip + // controller configuration unless the flag to force the configuration was passed + // on the command line. if (!mForcedConfig && mFirstRun && Utils::FileSystem::exists(InputManager::getConfigPath()) && InputManager::getInstance().getNumConfiguredDevices() > 0) { @@ -152,11 +150,11 @@ void GuiDetectDevice::update(int deltaTime) } else { mHoldTime -= deltaTime; - const float t = static_cast(mHoldTime) / HOLD_TIME; - unsigned int c = static_cast(t * 255); - mDeviceHeld->setColor((c << 24) | (c << 16) | (c << 8) | 0xFF); + // Fade in device name. + const float t {std::fabs((static_cast(mHoldTime) / HOLD_TIME) - 1.0f)}; + mDeviceHeld->setColor(0x44444400 | static_cast(t * 255.0f)); if (mHoldTime <= 0) { - // Picked one! + // A device was selected. mWindow->pushGui(new GuiInputConfig(mHoldingConfig, true, mDoneCallback)); delete this; } From 2c2e62416067a6160f6c3667f388e12ed59d5c56 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 22:10:25 +0100 Subject: [PATCH 33/82] Changed the opacity data type and functions from unsigned char to float. --- es-app/src/Screensaver.cpp | 4 +- es-app/src/guis/GuiMenu.cpp | 18 +++++---- es-app/src/guis/GuiScraperMenu.cpp | 8 ++-- es-app/src/guis/GuiScraperMulti.cpp | 8 ++-- es-app/src/guis/GuiScraperSearch.cpp | 4 +- es-app/src/guis/GuiScraperSingle.cpp | 4 +- es-app/src/views/GamelistLegacy.h | 13 +++---- es-app/src/views/GamelistView.cpp | 14 +++---- es-core/src/GuiComponent.cpp | 4 +- es-core/src/GuiComponent.h | 8 ++-- es-core/src/Window.cpp | 29 +++++++------- es-core/src/Window.h | 6 +-- es-core/src/components/BadgeComponent.cpp | 5 +-- es-core/src/components/CarouselComponent.cpp | 2 +- es-core/src/components/ComponentList.cpp | 12 +++--- .../src/components/DateTimeEditComponent.cpp | 2 +- es-core/src/components/FlexboxComponent.cpp | 6 +-- es-core/src/components/HelpComponent.cpp | 2 +- es-core/src/components/HelpComponent.h | 2 +- es-core/src/components/IList.h | 21 ++++------ es-core/src/components/ImageComponent.cpp | 18 ++++----- es-core/src/components/ImageComponent.h | 4 +- es-core/src/components/NinePatchComponent.cpp | 4 +- es-core/src/components/OptionListComponent.h | 8 ++-- es-core/src/components/RatingComponent.cpp | 8 ++-- es-core/src/components/RatingComponent.h | 2 +- .../src/components/ScrollIndicatorComponent.h | 30 +++++++------- .../src/components/ScrollableContainer.cpp | 5 +-- es-core/src/components/SwitchComponent.h | 4 +- es-core/src/components/TextComponent.cpp | 39 +++++++++---------- es-core/src/components/TextComponent.h | 8 ++-- es-core/src/components/TextEditComponent.cpp | 4 +- es-core/src/components/VideoComponent.h | 2 +- es-core/src/guis/GuiDetectDevice.cpp | 2 +- es-core/src/guis/GuiInfoPopup.cpp | 13 ++++--- es-core/src/guis/GuiInfoPopup.h | 2 +- es-core/src/guis/GuiInputConfig.cpp | 8 ++-- 37 files changed, 159 insertions(+), 174 deletions(-) diff --git a/es-app/src/Screensaver.cpp b/es-app/src/Screensaver.cpp index 3ceb763ef..678a93fb1 100644 --- a/es-app/src/Screensaver.cpp +++ b/es-app/src/Screensaver.cpp @@ -27,7 +27,7 @@ #include #endif -#define FADE_TIME 300 +#define FADE_TIME 300.0f Screensaver::Screensaver() : mWindow {Window::getInstance()} @@ -255,7 +255,7 @@ void Screensaver::renderScreensaver() // Only render the image if the state requires it. if (static_cast(mState) >= STATE_FADE_IN_VIDEO) { if (mImageScreensaver->hasImage()) { - mImageScreensaver->setOpacity(255 - static_cast(mOpacity * 255)); + mImageScreensaver->setOpacity(1.0f - mOpacity); glm::mat4 trans {Renderer::getIdentity()}; mImageScreensaver->render(trans); } diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index e4ecc7768..79139934f 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -299,8 +299,10 @@ void GuiMenu::openUIOptions() } if (!selectedSet->second.capabilities.legacyTheme && selectableVariants > 0) { themeVariant->setEnabled(true); - themeVariant->setOpacity(255); - themeVariant->getParent()->getChild(themeVariant->getChildIndex() - 1)->setOpacity(255); + themeVariant->setOpacity(1.0f); + themeVariant->getParent() + ->getChild(themeVariant->getChildIndex() - 1) + ->setOpacity(1.0f); } else { themeVariant->setEnabled(false); @@ -313,10 +315,10 @@ void GuiMenu::openUIOptions() if (!selectedSet->second.capabilities.legacyTheme && selectedSet->second.capabilities.aspectRatios.size() > 0) { themeAspectRatio->setEnabled(true); - themeAspectRatio->setOpacity(255); + themeAspectRatio->setOpacity(1.0f); themeAspectRatio->getParent() ->getChild(themeAspectRatio->getChildIndex() - 1) - ->setOpacity(255); + ->setOpacity(1.0f); } else { themeAspectRatio->setEnabled(false); @@ -340,16 +342,16 @@ void GuiMenu::openUIOptions() } else { gamelist_view_style->setEnabled(true); - gamelist_view_style->setOpacity(255); + gamelist_view_style->setOpacity(1.0f); gamelist_view_style->getParent() ->getChild(gamelist_view_style->getChildIndex() - 1) - ->setOpacity(255); + ->setOpacity(1.0f); transition_style->setEnabled(true); - transition_style->setOpacity(255); + transition_style->setOpacity(1.0f); transition_style->getParent() ->getChild(transition_style->getChildIndex() - 1) - ->setOpacity(255); + ->setOpacity(1.0f); } }; diff --git a/es-app/src/guis/GuiScraperMenu.cpp b/es-app/src/guis/GuiScraperMenu.cpp index a89e478e3..42b21518c 100644 --- a/es-app/src/guis/GuiScraperMenu.cpp +++ b/es-app/src/guis/GuiScraperMenu.cpp @@ -915,10 +915,10 @@ void GuiScraperMenu::openOtherOptions() } else { scraper_semiautomatic->setEnabled(true); - scraper_semiautomatic->setOpacity(255); + scraper_semiautomatic->setOpacity(1.0f); scraper_semiautomatic->getParent() ->getChild(scraper_semiautomatic->getChildIndex() - 1) - ->setOpacity(255); + ->setOpacity(1.0f); } }; @@ -932,10 +932,10 @@ void GuiScraperMenu::openOtherOptions() } else { scraper_exclude_recursively->setEnabled(true); - scraper_exclude_recursively->setOpacity(255); + scraper_exclude_recursively->setOpacity(1.0f); scraper_exclude_recursively->getParent() ->getChild(scraper_exclude_recursively->getChildIndex() - 1) - ->setOpacity(255); + ->setOpacity(1.0f); } }; diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 3cfa62ffb..83f9bcf0b 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -70,8 +70,8 @@ GuiScraperMulti::GuiScraperMulti(const std::queue& searches mSearchComp->setSkipCallback(std::bind(&GuiScraperMulti::skip, this)); mSearchComp->setCancelCallback(std::bind(&GuiScraperMulti::finish, this)); mSearchComp->setRefineCallback([&] { - mScrollUp->setOpacity(0); - mScrollDown->setOpacity(0); + mScrollUp->setOpacity(0.0f); + mScrollDown->setOpacity(0.0f); mResultList->resetScrollIndicatorStatus(); }); @@ -230,8 +230,8 @@ void GuiScraperMulti::doNextSearch() scrapeName = Utils::FileSystem::getFileName(mSearchQueue.front().game->getPath()); } - mScrollUp->setOpacity(0); - mScrollDown->setOpacity(0); + mScrollUp->setOpacity(0.0f); + mScrollDown->setOpacity(0.0f); mResultList->resetScrollIndicatorStatus(); // Extract possible subfolders from the path. diff --git a/es-app/src/guis/GuiScraperSearch.cpp b/es-app/src/guis/GuiScraperSearch.cpp index 6c0229709..4b34ae21c 100644 --- a/es-app/src/guis/GuiScraperSearch.cpp +++ b/es-app/src/guis/GuiScraperSearch.cpp @@ -557,7 +557,7 @@ void GuiScraperSearch::updateInfoPane() // Metadata. if (mScrapeRatings) { mMD_Rating->setValue(Utils::String::toUpper(res.mdl.get("rating"))); - mMD_Rating->setOpacity(255); + mMD_Rating->setOpacity(1.0f); } mMD_ReleaseDate->setValue(Utils::String::toUpper(res.mdl.get("releasedate"))); mMD_Developer->setText(Utils::String::toUpper(res.mdl.get("developer"))); @@ -574,7 +574,7 @@ void GuiScraperSearch::updateInfoPane() // Metadata. if (mScrapeRatings) { mMD_Rating->setValue(""); - mMD_Rating->setOpacity(0); + mMD_Rating->setOpacity(0.0f); } // Set the release date to this value to force DateTimeEditComponent to put a // blank instead of the text 'unknown' prior to the scrape result being returned. diff --git a/es-app/src/guis/GuiScraperSingle.cpp b/es-app/src/guis/GuiScraperSingle.cpp index 4d4ca40b6..7ab9bad90 100644 --- a/es-app/src/guis/GuiScraperSingle.cpp +++ b/es-app/src/guis/GuiScraperSingle.cpp @@ -113,8 +113,8 @@ GuiScraperSingle::GuiScraperSingle(ScraperSearchParams& params, }); mSearch->setCancelCallback([&] { delete this; }); mSearch->setRefineCallback([&] { - mScrollUp->setOpacity(0); - mScrollDown->setOpacity(0); + mScrollUp->setOpacity(0.0f); + mScrollDown->setOpacity(0.0f); mResultList->resetScrollIndicatorStatus(); }); diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h index d0da6c2e1..3fab1be7f 100644 --- a/es-app/src/views/GamelistLegacy.h +++ b/es-app/src/views/GamelistLegacy.h @@ -466,8 +466,8 @@ void GamelistView::legacyUpdateInfoPanel() if (mViewStyle == ViewController::DETAILED) { // Fade in the game image. auto func = [this](float t) { - mImageComponents[LegacyImage::MD_IMAGE]->setOpacity(static_cast( - glm::mix(static_cast(FADE_IN_START_OPACITY), 1.0f, t) * 255)); + mImageComponents[LegacyImage::MD_IMAGE]->setOpacity( + glm::mix(FADE_IN_START_OPACITY, 1.0f, t)); }; mImageComponents[LegacyImage::MD_IMAGE]->setAnimation( new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); @@ -475,8 +475,7 @@ void GamelistView::legacyUpdateInfoPanel() else if (mViewStyle == ViewController::VIDEO) { // Fade in the static image. auto func = [this](float t) { - mVideoComponents.front()->setOpacity(static_cast( - glm::mix(static_cast(FADE_IN_START_OPACITY), 1.0f, t) * 255)); + mVideoComponents.front()->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t)); }; mVideoComponents.front()->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); @@ -560,10 +559,8 @@ void GamelistView::legacyUpdateInfoPanel() // 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(glm::mix(0.0f, 1.0f, t) * 255)); - }; + (!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0.0f : 1.0f))) { + auto func = [comp](float t) { comp->setOpacity(glm::mix(0.0f, 1.0f, t)); }; comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut); } } diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index 152f59c41..14d47e88a 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -210,7 +210,7 @@ void GamelistView::update(int deltaTime) video->onHide(); else if (!video->hasStaticImage()) video->onHide(); - else if (video->getOpacity() == 0) + else if (video->getOpacity() == 0.0f) video->onHide(); } else if (mVideoPlaying && !video->isVideoPaused() && !mWindow->isScreensaverActive()) { @@ -562,8 +562,7 @@ void GamelistView::updateInfoPanel() for (auto& image : mImageComponents) { if (image->getScrollFadeIn()) { auto func = [&image](float t) { - image->setOpacity(static_cast( - glm::mix(static_cast(FADE_IN_START_OPACITY), 1.0f, t) * 255)); + image->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t)); }; image->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); } @@ -573,8 +572,7 @@ void GamelistView::updateInfoPanel() for (auto& video : mVideoComponents) { if (video->getScrollFadeIn()) { auto func = [&video](float t) { - video->setOpacity(static_cast( - glm::mix(static_cast(FADE_IN_START_OPACITY), 1.0f, t) * 255)); + video->setOpacity(glm::mix(FADE_IN_START_OPACITY, 1.0f, t)); }; video->setAnimation(new LambdaAnimation(func, FADE_IN_TIME), 0, nullptr, false); } @@ -762,10 +760,8 @@ void GamelistView::updateInfoPanel() // 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(glm::mix(0.0f, 1.0f, t) * 255)); - }; + (!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0.0f : 1.0f))) { + auto func = [comp](float t) { comp->setOpacity(glm::mix(0.0f, 1.0f, t)); }; comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut); } } diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 7bee95f34..978af1a6c 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -19,7 +19,6 @@ GuiComponent::GuiComponent() : mWindow {Window::getInstance()} , mParent {nullptr} - , mOpacity {255} , mColor {0} , mSaturation {1.0f} , mColorShift {0} @@ -31,6 +30,7 @@ GuiComponent::GuiComponent() , mOrigin {0.0f, 0.0f} , mRotationOrigin {0.5f, 0.5f} , mSize {0.0f, 0.0f} + , mOpacity {1.0f} , mRotation {0.0f} , mScale {1.0f} , mDefaultZIndex {0.0f} @@ -180,7 +180,7 @@ const int GuiComponent::getChildIndex() const return -1; } -void GuiComponent::setOpacity(unsigned char opacity) +void GuiComponent::setOpacity(float opacity) { if (mOpacity == opacity) return; diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 8a08ee7ef..49cd66f57 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -25,7 +25,7 @@ #define ICONCOLOR_USERMARKED 0x7777FFFF #define TEXTCOLOR_SCRAPERMARKED 0x992222FF #define TEXTCOLOR_USERMARKED 0x222299FF -#define DISABLED_OPACITY 80 +#define DISABLED_OPACITY 0.314f class Animation; class AnimationController; @@ -191,8 +191,8 @@ public: virtual bool isListScrolling() { return false; } virtual void stopListScrolling() {} - virtual unsigned char getOpacity() const { return mOpacity; } - virtual void setOpacity(unsigned char opacity); + virtual float getOpacity() const { return mOpacity; } + virtual void setOpacity(float opacity); virtual unsigned int getColor() const { return mColor; } virtual unsigned int getColorShift() const { return mColorShift; } virtual float getLineSpacing() { return 0.0f; } @@ -277,7 +277,6 @@ protected: std::string mMetadataField; - unsigned char mOpacity; unsigned int mColor; float mSaturation; unsigned int mColorShift; @@ -291,6 +290,7 @@ protected: glm::vec2 mRotationOrigin; glm::vec2 mSize; + float mOpacity; float mRotation; float mScale; float mDefaultZIndex; diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 9edb35adc..8cf532fe3 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -29,7 +29,7 @@ Window::Window() noexcept , mMediaViewer {nullptr} , mLaunchScreen {nullptr} , mInfoPopup {nullptr} - , mListScrollOpacity {0} + , mListScrollOpacity {0.0f} , mFrameTimeElapsed {0} , mFrameCountElapsed {0} , mAverageDeltaTime {10} @@ -121,7 +121,7 @@ bool Window::init() mHelp = new HelpComponent; mBackgroundOverlay = new ImageComponent; - mBackgroundOverlayOpacity = 0; + mBackgroundOverlayOpacity = 0.0f; // Keep a reference to the default fonts, so they don't keep getting destroyed/recreated. if (mDefaultFonts.empty()) { @@ -443,7 +443,7 @@ void Window::render() // a new cached background has been generated. if (mGuiStack.size() > 1 && mCachedBackground) { if ((Settings::getInstance()->getString("MenuOpeningEffect") == "scale-up" && - mBackgroundOverlayOpacity == 255) || + mBackgroundOverlayOpacity == 1.0f) || Settings::getInstance()->getString("MenuOpeningEffect") != "scale-up") renderBottom = false; } @@ -509,11 +509,11 @@ void Window::render() // The following is done to avoid fading in if the cached image was // invalidated (rather than the menu being opened). if (mInvalidatedCachedBackground) { - mBackgroundOverlayOpacity = 255; + mBackgroundOverlayOpacity = 1.0f; mInvalidatedCachedBackground = false; } else { - mBackgroundOverlayOpacity = 25; + mBackgroundOverlayOpacity = 0.1f; } delete[] processedTexture; @@ -530,8 +530,9 @@ void Window::render() // Fade in the cached background if the menu opening effect has been set to scale-up. if (Settings::getInstance()->getString("MenuOpeningEffect") == "scale-up") { mBackgroundOverlay->setOpacity(mBackgroundOverlayOpacity); - if (mBackgroundOverlayOpacity < 255) - mBackgroundOverlayOpacity = glm::clamp(mBackgroundOverlayOpacity + 30, 0, 255); + if (mBackgroundOverlayOpacity < 1.0f) + mBackgroundOverlayOpacity = + glm::clamp(mBackgroundOverlayOpacity + 0.118f, 0.0f, 1.0f); } #endif // USE_OPENGL_21 @@ -558,18 +559,20 @@ void Window::render() } // Render the quick list scrolling overlay, which is triggered in IList. - if (mListScrollOpacity != 0) { + if (mListScrollOpacity != 0.0f) { Renderer::setMatrix(Renderer::getIdentity()); Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight()), - 0x00000000 | mListScrollOpacity, 0x00000000 | mListScrollOpacity); + 0x00000000 | static_cast(mListScrollOpacity * 255.0f), + 0x00000000 | static_cast(mListScrollOpacity * 255.0f)); glm::vec2 offset {mListScrollFont->sizeText(mListScrollText)}; offset.x = (Renderer::getScreenWidth() - offset.x) * 0.5f; offset.y = (Renderer::getScreenHeight() - offset.y) * 0.5f; - TextCache* cache = mListScrollFont->buildTextCache(mListScrollText, offset.x, offset.y, - 0xFFFFFF00 | mListScrollOpacity); + TextCache* cache {mListScrollFont->buildTextCache( + mListScrollText, offset.x, offset.y, + 0xFFFFFF00 | static_cast(mListScrollOpacity * 255.0f))}; mListScrollFont->renderTextCache(cache); delete cache; } @@ -648,9 +651,9 @@ void Window::renderLoadingScreen(std::string text) Renderer::swapBuffers(); } -void Window::renderListScrollOverlay(unsigned char opacity, const std::string& text) +void Window::renderListScrollOverlay(const float opacity, const std::string& text) { - mListScrollOpacity = static_cast(opacity * 0.6f); + mListScrollOpacity = opacity * 0.6f; mListScrollText = text; } diff --git a/es-core/src/Window.h b/es-core/src/Window.h index e96bc6983..8f9d12c83 100644 --- a/es-core/src/Window.h +++ b/es-core/src/Window.h @@ -100,7 +100,7 @@ public: void renderLoadingScreen(std::string text); // The list scroll overlay is triggered from IList when the highest scrolling tier is reached. - void renderListScrollOverlay(unsigned char opacity, const std::string& text); + void renderListScrollOverlay(const float opacity, const std::string& text); void renderHelpPromptsEarly(); // Used to render HelpPrompts before a fade. void setHelpPrompts(const std::vector& prompts, const HelpStyle& style); @@ -162,7 +162,7 @@ private: HelpComponent* mHelp; ImageComponent* mBackgroundOverlay; - unsigned char mBackgroundOverlayOpacity; + float mBackgroundOverlayOpacity; std::vector mGuiStack; std::vector> mDefaultFonts; std::unique_ptr mFrameDataText; @@ -180,7 +180,7 @@ private: std::string mListScrollText; std::shared_ptr mListScrollFont; - unsigned char mListScrollOpacity; + float mListScrollOpacity; int mFrameTimeElapsed; int mFrameCountElapsed; diff --git a/es-core/src/components/BadgeComponent.cpp b/es-core/src/components/BadgeComponent.cpp index e3243668f..eeb5dec1f 100644 --- a/es-core/src/components/BadgeComponent.cpp +++ b/es-core/src/components/BadgeComponent.cpp @@ -104,7 +104,6 @@ void BadgeComponent::setBadges(const std::vector& badges) [badge](FlexboxComponent::FlexboxItem item) { return item.label == badge.badgeType; }); if (it != mFlexboxItems.end()) { - // Don't show the alternative emulator badge if the corresponding setting has been // disabled. if (badge.badgeType == "altemulator" && @@ -172,13 +171,13 @@ void BadgeComponent::render(const glm::mat4& parentTrans) if (!isVisible()) return; - if (mOpacity == 255) { + if (mOpacity == 1.0f) { mFlexboxComponent.render(parentTrans); } else { mFlexboxComponent.setOpacity(mOpacity); mFlexboxComponent.render(parentTrans); - mFlexboxComponent.setOpacity(255); + mFlexboxComponent.setOpacity(1.0f); } } diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index 44e044d50..e8a208a0e 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -291,7 +291,7 @@ void CarouselComponent::render(const glm::mat4& parentTrans) scale = glm::clamp(scale, 1.0f / mLogoScale + 0.01f, 1.0f); comp->setScale(scale); - comp->setOpacity(static_cast(opacity)); + comp->setOpacity(static_cast(opacity) / 255.0f); comp->render(logoTrans); } } diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index a9e36f6f2..b2c7e4e2e 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -376,19 +376,17 @@ void ComponentList::render(const glm::mat4& parentTrans) // Custom rendering. Renderer::setMatrix(trans); - float opacity = mOpacity / 255.0f; - // Draw selector bar. if (mFocused) { const float selectedRowHeight = getRowHeight(mEntries.at(mCursor).data); - if (opacity == 1) { + if (mOpacity == 1.0f) { Renderer::drawRect(0.0f, mSelectorBarOffset, std::ceil(mSize.x), selectedRowHeight, - 0xFFFFFFFF, 0xFFFFFFFF, false, opacity, trans, + 0xFFFFFFFF, 0xFFFFFFFF, false, mOpacity, trans, Renderer::Blend::ONE_MINUS_DST_COLOR, Renderer::Blend::ZERO); Renderer::drawRect(0.0f, mSelectorBarOffset, std::ceil(mSize.x), selectedRowHeight, - 0x777777FF, 0x777777FF, false, opacity, trans, Renderer::Blend::ONE, + 0x777777FF, 0x777777FF, false, mOpacity, trans, Renderer::Blend::ONE, Renderer::Blend::ONE); } @@ -404,12 +402,12 @@ void ComponentList::render(const glm::mat4& parentTrans) float y = 0; for (unsigned int i = 0; i < mEntries.size(); ++i) { Renderer::drawRect(0.0f, y, std::ceil(mSize.x), 1.0f * Renderer::getScreenHeightModifier(), - 0xC6C7C6FF, 0xC6C7C6FF, false, opacity, trans); + 0xC6C7C6FF, 0xC6C7C6FF, false, mOpacity, trans); y += getRowHeight(mEntries.at(i).data); } Renderer::drawRect(0.0f, y, std::ceil(mSize.x), 1.0f * Renderer::getScreenHeightModifier(), - 0xC6C7C6FF, 0xC6C7C6FF, false, opacity, trans); + 0xC6C7C6FF, 0xC6C7C6FF, false, mOpacity, trans); Renderer::popClipRect(); } diff --git a/es-core/src/components/DateTimeEditComponent.cpp b/es-core/src/components/DateTimeEditComponent.cpp index c21df7d46..4547c1459 100644 --- a/es-core/src/components/DateTimeEditComponent.cpp +++ b/es-core/src/components/DateTimeEditComponent.cpp @@ -193,7 +193,7 @@ void DateTimeEditComponent::render(const glm::mat4& parentTrans) 0x00000033, 0x00000033); } - mTextCache->setColor((mColor & 0xFFFFFF00) | getOpacity()); + mTextCache->setColor((mColor & 0xFFFFFF00) | static_cast(getOpacity() * 255.0f)); font->renderTextCache(mTextCache.get()); if (mEditing && mTime != 0) { diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 9d31b3c7c..85882bfcf 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -50,7 +50,7 @@ void FlexboxComponent::render(const glm::mat4& parentTrans) for (auto& item : mItems) { if (!item.visible) continue; - if (mOpacity == 255) { + if (mOpacity == 1.0f) { item.baseImage.render(trans); if (item.overlayImage.getTexture() != nullptr) item.overlayImage.render(trans); @@ -58,11 +58,11 @@ void FlexboxComponent::render(const glm::mat4& parentTrans) else { item.baseImage.setOpacity(mOpacity); item.baseImage.render(trans); - item.baseImage.setOpacity(255); + item.baseImage.setOpacity(1.0f); if (item.overlayImage.getTexture() != nullptr) { item.overlayImage.setOpacity(mOpacity); item.overlayImage.render(trans); - item.overlayImage.setOpacity(255); + item.overlayImage.setOpacity(1.0f); } } } diff --git a/es-core/src/components/HelpComponent.cpp b/es-core/src/components/HelpComponent.cpp index 023f707cf..5e99f3f0c 100644 --- a/es-core/src/components/HelpComponent.cpp +++ b/es-core/src/components/HelpComponent.cpp @@ -280,7 +280,7 @@ std::shared_ptr HelpComponent::getIconTexture(const char* name) return tex; } -void HelpComponent::setOpacity(unsigned char opacity) +void HelpComponent::setOpacity(float opacity) { GuiComponent::setOpacity(opacity); diff --git a/es-core/src/components/HelpComponent.h b/es-core/src/components/HelpComponent.h index d78416383..58bfef6de 100644 --- a/es-core/src/components/HelpComponent.h +++ b/es-core/src/components/HelpComponent.h @@ -27,7 +27,7 @@ public: void setPrompts(const std::vector& prompts); void render(const glm::mat4& parent) override; - void setOpacity(unsigned char opacity) override; + void setOpacity(float opacity) override; void setStyle(const HelpStyle& style); diff --git a/es-core/src/components/IList.h b/es-core/src/components/IList.h index b8e4e96d6..80c25af51 100644 --- a/es-core/src/components/IList.h +++ b/es-core/src/components/IList.h @@ -73,7 +73,7 @@ protected: int mScrollTierAccumulator; int mScrollCursorAccumulator; - unsigned char mTitleOverlayOpacity; + float mTitleOverlayOpacity; unsigned int mTitleOverlayColor; const ScrollTierList& mTierList; @@ -95,7 +95,7 @@ public: mScrollTierAccumulator = 0; mScrollCursorAccumulator = 0; - mTitleOverlayOpacity = 0x00; + mTitleOverlayOpacity = 0.0f; mTitleOverlayColor = 0xFFFFFF00; } @@ -105,7 +105,7 @@ public: void stopScrolling() { - mTitleOverlayOpacity = 0; + mTitleOverlayOpacity = 0.0f; listInput(0); if (mScrollVelocity == 0) @@ -243,15 +243,10 @@ protected: { // Update the title overlay opacity. // Fade in if scroll tier is >= 1, otherwise fade out. - const int dir = (mScrollTier >= mTierList.count - 1) ? 1 : -1; + const float dir {(mScrollTier >= mTierList.count - 1) ? 1.0f : -1.0f}; // We simply translate the time directly to opacity, i.e. no scaling is performed. - int op = mTitleOverlayOpacity + deltaTime * dir; - if (op >= 255) - mTitleOverlayOpacity = 255; - else if (op <= 0) - mTitleOverlayOpacity = 0; - else - mTitleOverlayOpacity = static_cast(op); + mTitleOverlayOpacity = glm::clamp( + mTitleOverlayOpacity + (static_cast(deltaTime) / 255.0f) * dir, 0.0f, 1.0f); if (mScrollVelocity == 0 || size() < 2) return; @@ -285,8 +280,8 @@ protected: if (!Settings::getInstance()->getBool("ListScrollOverlay")) return; - if (size() == 0 || mTitleOverlayOpacity == 0) { - mWindow->renderListScrollOverlay(0, ""); + if (size() == 0 || mTitleOverlayOpacity == 0.0f) { + mWindow->renderListScrollOverlay(0.0f, ""); return; } diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 7962e1041..f1b6bb7dc 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -37,7 +37,7 @@ ImageComponent::ImageComponent(bool forceLoad, bool dynamic) , mColorShift {0xFFFFFFFF} , mColorShiftEnd {0xFFFFFFFF} , mColorGradientHorizontal {true} - , mFadeOpacity {0} + , mFadeOpacity {0.0f} , mFading {false} , mForceLoad {forceLoad} , mDynamic {dynamic} @@ -319,7 +319,7 @@ void ImageComponent::setColorGradientHorizontal(bool horizontal) updateColors(); } -void ImageComponent::setOpacity(unsigned char opacity) +void ImageComponent::setOpacity(float opacity) { mOpacity = opacity; updateColors(); @@ -369,7 +369,7 @@ void ImageComponent::updateVertices() void ImageComponent::updateColors() { - const float opacity = (mOpacity * (mFading ? mFadeOpacity / 255.0f : 1.0f)) / 255.0f; + const float opacity = (mOpacity * (mFading ? mFadeOpacity : 1.0f)); const unsigned int color = Renderer::convertRGBAToABGR( (mColorShift & 0xFFFFFF00) | static_cast((mColorShift & 0xFF) * opacity)); const unsigned int colorEnd = @@ -391,7 +391,7 @@ void ImageComponent::render(const glm::mat4& parentTrans) glm::mat4 trans {parentTrans * getTransform()}; Renderer::setMatrix(trans); - if (mTexture && mOpacity > 0) { + if (mTexture && mOpacity > 0.0f) { if (Settings::getInstance()->getBool("DebugImage")) { glm::vec2 targetSizePos {(mTargetSize - mSize) * mOrigin * glm::vec2 {-1.0f}}; Renderer::drawRect(targetSizePos.x, targetSizePos.y, mTargetSize.x, mTargetSize.y, @@ -447,7 +447,7 @@ void ImageComponent::fadeIn(bool textureLoaded) // Start the fade if this is the first time we've encountered the unloaded texture. if (!mFading) { // Start with a zero opacity and flag it as fading. - mFadeOpacity = 0; + mFadeOpacity = 0.0f; mFading = true; updateColors(); } @@ -456,14 +456,14 @@ void ImageComponent::fadeIn(bool textureLoaded) // The texture is loaded and we need to fade it in. The fade is based on the frame // rate and is 1/4 second if running at 60 frames per second although the actual // value is not that important. - int opacity = mFadeOpacity + 255 / 15; + float opacity {mFadeOpacity + 1.0f / 15.0f}; // See if we've finished fading. - if (opacity >= 255) { - mFadeOpacity = 255; + if (opacity >= 1.0f) { + mFadeOpacity = 1.0f; mFading = false; } else { - mFadeOpacity = static_cast(opacity); + mFadeOpacity = opacity; } updateColors(); } diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index be91b2fbf..9faabfac0 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -71,7 +71,7 @@ public: unsigned int getColorShift() const override { return mColorShift; } - void setOpacity(unsigned char opacity) override; + void setOpacity(float opacity) override; void setSaturation(float saturation) override; void setFlipX(bool flip); // Mirror on the X axis. @@ -124,7 +124,7 @@ private: std::string mDefaultPath; std::shared_ptr mTexture; - unsigned char mFadeOpacity; + float mFadeOpacity; bool mFading; bool mForceLoad; bool mDynamic; diff --git a/es-core/src/components/NinePatchComponent.cpp b/es-core/src/components/NinePatchComponent.cpp index 2403c3961..e74a7ed2f 100644 --- a/es-core/src/components/NinePatchComponent.cpp +++ b/es-core/src/components/NinePatchComponent.cpp @@ -135,9 +135,9 @@ void NinePatchComponent::render(const glm::mat4& parentTrans) if (mTexture && mVertices != nullptr) { Renderer::setMatrix(trans); - if (mOpacity < 255) { + if (mOpacity < 1.0f) { mVertices[0].shaders = Renderer::SHADER_OPACITY; - mVertices[0].opacity = mOpacity / 255.0f; + mVertices[0].opacity = mOpacity; } else if (mVertices[0].shaders & Renderer::SHADER_OPACITY) { // We have reached full opacity, so disable the opacity shader and set diff --git a/es-core/src/components/OptionListComponent.h b/es-core/src/components/OptionListComponent.h index 5cfd049f1..aa042cafe 100644 --- a/es-core/src/components/OptionListComponent.h +++ b/es-core/src/components/OptionListComponent.h @@ -500,8 +500,8 @@ private: else { mEnabled = true; list->getChild(i)->setEnabled(true); - list->getChild(i)->setOpacity(255); - list->getChild(i + 1)->setOpacity(255); + list->getChild(i)->setOpacity(1.0f); + list->getChild(i + 1)->setOpacity(1.0f); } } } @@ -545,8 +545,8 @@ private: mParent->mEntries.at(i).selected = false; checkBoxes.at(i)->setImage(UNCHECKED_PATH); if (mParent->mMultiExclusiveSelect) { - checkBoxes.at(i)->setOpacity(255); - textEntries.at(i)->setOpacity(255); + checkBoxes.at(i)->setOpacity(1.0f); + textEntries.at(i)->setOpacity(1.0f); textEntries.at(i)->setEnabled(true); } } diff --git a/es-core/src/components/RatingComponent.cpp b/es-core/src/components/RatingComponent.cpp index 15232fe2d..e34a40fb4 100644 --- a/es-core/src/components/RatingComponent.cpp +++ b/es-core/src/components/RatingComponent.cpp @@ -82,10 +82,10 @@ std::string RatingComponent::getRatingValue() const return ss.str(); } -void RatingComponent::setOpacity(unsigned char opacity) +void RatingComponent::setOpacity(float opacity) { mOpacity = opacity; - mColorShift = (mColorShift >> 8 << 8) | mOpacity; + mColorShift = (mColorShift >> 8 << 8) | static_cast(mOpacity * 255.0f); updateColors(); } @@ -96,7 +96,7 @@ void RatingComponent::setColorShift(unsigned int color) // Grab the opacity from the color shift because we may need // to apply it if fading in textures. - mOpacity = color & 0xff; + mOpacity = static_cast(color & 0xff) / 255.0f; updateColors(); } @@ -155,7 +155,7 @@ void RatingComponent::render(const glm::mat4& parentTrans) Renderer::setMatrix(trans); - if (mOpacity > 0) { + if (mOpacity > 0.0f) { if (Settings::getInstance()->getBool("DebugImage")) { Renderer::drawRect(0.0f, 0.0f, mSize.y * NUM_RATING_STARS, mSize.y, 0xFF000033, 0xFF000033); diff --git a/es-core/src/components/RatingComponent.h b/es-core/src/components/RatingComponent.h index 54b66350b..98ce702c8 100644 --- a/es-core/src/components/RatingComponent.h +++ b/es-core/src/components/RatingComponent.h @@ -32,7 +32,7 @@ public: void onSizeChanged() override; - void setOpacity(unsigned char opacity) override; + void setOpacity(float opacity) override; // Multiply all pixels in the image by this color when rendering. void setColorShift(unsigned int color) override; diff --git a/es-core/src/components/ScrollIndicatorComponent.h b/es-core/src/components/ScrollIndicatorComponent.h index 143c844f7..f61c23a7f 100644 --- a/es-core/src/components/ScrollIndicatorComponent.h +++ b/es-core/src/components/ScrollIndicatorComponent.h @@ -27,8 +27,8 @@ public: scrollUp->setImage(":/graphics/scroll_up.svg"); scrollDown->setImage(":/graphics/scroll_down.svg"); - scrollUp->setOpacity(0); - scrollDown->setOpacity(0); + scrollUp->setOpacity(0.0f); + scrollDown->setOpacity(0.0f); if (!Settings::getInstance()->getBool("ScrollIndicators")) { // If the scroll indicators setting is disabled, then show a permanent down arrow @@ -38,7 +38,7 @@ public: if (state == ComponentList::SCROLL_UP || state == ComponentList::SCROLL_UP_DOWN || state == ComponentList::SCROLL_DOWN) { - scrollDown->setOpacity(255); + scrollDown->setOpacity(1.0f); } }); } @@ -60,7 +60,7 @@ public: if (state == ComponentList::SCROLL_UP && mPreviousScrollState == ComponentList::SCROLL_NONE) { - scrollUp->setOpacity(255); + scrollUp->setOpacity(1.0f); } else if (state == ComponentList::SCROLL_UP && mPreviousScrollState == ComponentList::SCROLL_UP_DOWN) { @@ -70,12 +70,12 @@ public: mPreviousScrollState == ComponentList::SCROLL_DOWN) { upFadeIn = true; fadeTime *= 2.0f; - scrollDown->setOpacity(0); + scrollDown->setOpacity(0.0f); } else if (state == ComponentList::SCROLL_UP_DOWN && mPreviousScrollState == ComponentList::SCROLL_NONE) { - scrollUp->setOpacity(255); - scrollDown->setOpacity(255); + scrollUp->setOpacity(1.0f); + scrollDown->setOpacity(1.0f); } else if (state == ComponentList::SCROLL_UP_DOWN && mPreviousScrollState == ComponentList::SCROLL_DOWN) { @@ -87,7 +87,7 @@ public: } else if (state == ComponentList::SCROLL_DOWN && mPreviousScrollState == ComponentList::SCROLL_NONE) { - scrollDown->setOpacity(255); + scrollDown->setOpacity(1.0f); } else if (state == ComponentList::SCROLL_DOWN && mPreviousScrollState == ComponentList::SCROLL_UP_DOWN) { @@ -97,7 +97,7 @@ public: mPreviousScrollState == ComponentList::SCROLL_UP) { downFadeIn = true; fadeTime *= 2.0f; - scrollUp->setOpacity(0); + scrollUp->setOpacity(0.0f); } // If jumping more than one row using the shoulder or trigger buttons, then @@ -107,8 +107,7 @@ public: if (upFadeIn) { auto upFadeInFunc = [scrollUp](float t) { - scrollUp->setOpacity( - static_cast(glm::mix(0.0f, 1.0f, t) * 255)); + scrollUp->setOpacity(glm::mix(0.0f, 1.0f, t)); }; scrollUp->setAnimation( new LambdaAnimation(upFadeInFunc, static_cast(fadeTime)), 0, @@ -117,8 +116,7 @@ public: if (upFadeOut) { auto upFadeOutFunc = [scrollUp](float t) { - scrollUp->setOpacity( - static_cast(glm::mix(0.0f, 1.0f, t) * 255)); + scrollUp->setOpacity(glm::mix(0.0f, 1.0f, t)); }; scrollUp->setAnimation( new LambdaAnimation(upFadeOutFunc, static_cast(fadeTime)), 0, @@ -127,8 +125,7 @@ public: if (downFadeIn) { auto downFadeInFunc = [scrollDown](float t) { - scrollDown->setOpacity( - static_cast(glm::mix(0.0f, 1.0f, t) * 255)); + scrollDown->setOpacity(glm::mix(0.0f, 1.0f, t)); }; scrollDown->setAnimation( new LambdaAnimation(downFadeInFunc, static_cast(fadeTime)), 0, @@ -137,8 +134,7 @@ public: if (downFadeOut) { auto downFadeOutFunc = [scrollDown](float t) { - scrollDown->setOpacity( - static_cast(glm::mix(0.0f, 1.0f, t) * 255)); + scrollDown->setOpacity(glm::mix(0.0f, 1.0f, t)); }; scrollDown->setAnimation( new LambdaAnimation(downFadeOutFunc, static_cast(fadeTime)), 0, diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index a937db9d2..c477e3c56 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -197,9 +197,8 @@ void ScrollableContainer::update(int deltaTime) 255.0f}; auto func = [this, maxOpacity](float t) { unsigned int color {mChildren.front()->getColor()}; - unsigned int opacity { - static_cast(glm::mix(0.0f, maxOpacity, t) * 255)}; - color = (color & 0xFFFFFF00) + opacity; + float opacity {glm::mix(0.0f, maxOpacity, t)}; + color = (color & 0xFFFFFF00) + static_cast(opacity * 255.0f); this->mChildren.front()->setColor(color); mScrollPos = glm::vec2 {}; mAutoScrollResetAccumulator = 0; diff --git a/es-core/src/components/SwitchComponent.h b/es-core/src/components/SwitchComponent.h index c3b660ba9..26acf5d50 100644 --- a/es-core/src/components/SwitchComponent.h +++ b/es-core/src/components/SwitchComponent.h @@ -32,8 +32,8 @@ public: void setChangedColor(unsigned int color) override { mColorChangedValue = color; } void setCallback(const std::function& callbackFunc) { mToggleCallback = callbackFunc; } - unsigned char getOpacity() const override { return mImage.getOpacity(); } - void setOpacity(unsigned char opacity) override { mImage.setOpacity(opacity); } + float getOpacity() const override { return mImage.getOpacity(); } + void setOpacity(float opacity) override { mImage.setOpacity(opacity); } // Multiply all pixels in the image by this color when rendering. void setColorShift(unsigned int color) override { mImage.setColorShift(color); } diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 99f48356e..1288091b5 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -16,8 +16,8 @@ TextComponent::TextComponent() : mFont {Font::get(FONT_SIZE_MEDIUM)} , mColor {0x000000FF} , mBgColor {0x00000000} - , mColorOpacity {0x000000FF} - , mBgColorOpacity {0x00000000} + , mColorOpacity {1.0f} + , mBgColorOpacity {0.0f} , mRenderBackground {false} , mUppercase {false} , mLowercase {false} @@ -41,8 +41,8 @@ TextComponent::TextComponent(const std::string& text, : mFont {nullptr} , mColor {0x000000FF} , mBgColor {0x00000000} - , mColorOpacity {0x000000FF} - , mBgColorOpacity {0x00000000} + , mColorOpacity {1.0f} + , mBgColorOpacity {0.0f} , mRenderBackground {false} , mUppercase {false} , mLowercase {false} @@ -81,7 +81,7 @@ void TextComponent::setFont(const std::shared_ptr& font) void TextComponent::setColor(unsigned int color) { mColor = color; - mColorOpacity = mColor & 0x000000FF; + mColorOpacity = static_cast(mColor & 0x000000FF) / 255.0f; onColorChanged(); } @@ -89,22 +89,18 @@ void TextComponent::setColor(unsigned int color) void TextComponent::setBackgroundColor(unsigned int color) { mBgColor = color; - mBgColorOpacity = mBgColor & 0x000000FF; + mBgColorOpacity = static_cast(mBgColor & 0x000000FF) / 255.0f; } // Scale the opacity. -void TextComponent::setOpacity(unsigned char opacity) +void TextComponent::setOpacity(float opacity) { // This function is mostly called to do fade in and fade out of the text component element. - // Therefore we assume here that opacity is a fractional value (expressed as an unsigned - // char 0 - 255) of the opacity originally set with setColor() or setBackgroundColor(). - unsigned char o = static_cast(static_cast(opacity) / 255.0f * - static_cast(mColorOpacity)); - mColor = (mColor & 0xFFFFFF00) | static_cast(o); + float o {opacity * mColorOpacity}; + mColor = (mColor & 0xFFFFFF00) | static_cast(o * 255.0f); - unsigned char bgo = static_cast(static_cast(opacity) / 255.0f * - static_cast(mBgColorOpacity)); - mBgColor = (mBgColor & 0xFFFFFF00) | static_cast(bgo); + float bgo {opacity * mBgColorOpacity}; + mBgColor = (mBgColor & 0xFFFFFF00) | static_cast(bgo * 255.0f); onColorChanged(); GuiComponent::setOpacity(opacity); @@ -307,14 +303,15 @@ void TextComponent::onTextChanged() text.append(abbrev); - mTextCache = std::shared_ptr( - f->buildTextCache(text, glm::vec2 {}, (mColor >> 8 << 8) | mOpacity, mSize.x, - mHorizontalAlignment, mLineSpacing, mNoTopMargin)); + mTextCache = std::shared_ptr(f->buildTextCache( + text, glm::vec2 {}, (mColor >> 8 << 8) | static_cast(mOpacity * 255.0f), + mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin)); } else { - mTextCache = std::shared_ptr(f->buildTextCache( - f->wrapText(text, mSize.x), glm::vec2 {}, (mColor >> 8 << 8) | mOpacity, mSize.x, - mHorizontalAlignment, mLineSpacing, mNoTopMargin)); + mTextCache = std::shared_ptr( + f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {}, + (mColor >> 8 << 8) | static_cast(mOpacity * 255.0f), + mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin)); } // This is required to set the color transparency. diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index bd6dc1b89..3a00c6db2 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -57,8 +57,8 @@ public: std::string getHiddenValue() const override { return mHiddenText; } void setHiddenValue(const std::string& value) override { setHiddenText(value); } - unsigned char getOpacity() const override { return mColor & 0x000000FF; } - void setOpacity(unsigned char opacity) override; + float getOpacity() const override { return static_cast((mColor & 0x000000FF) / 255.0f); } + void setOpacity(float opacity) override; void setSelectable(bool status) { mSelectable = status; } @@ -87,8 +87,8 @@ private: unsigned int mColor; unsigned int mBgColor; - unsigned char mColorOpacity; - unsigned char mBgColorOpacity; + float mColorOpacity; + float mBgColorOpacity; bool mRenderBackground; bool mUppercase; diff --git a/es-core/src/components/TextEditComponent.cpp b/es-core/src/components/TextEditComponent.cpp index 614fe527e..73d961ce5 100644 --- a/es-core/src/components/TextEditComponent.cpp +++ b/es-core/src/components/TextEditComponent.cpp @@ -258,8 +258,8 @@ void TextEditComponent::setCursor(size_t pos) void TextEditComponent::onTextChanged() { std::string wrappedText = (isMultiline() ? mFont->wrapText(mText, getTextAreaSize().x) : mText); - mTextCache = std::unique_ptr( - mFont->buildTextCache(wrappedText, 0.0f, 0.0f, 0x77777700 | getOpacity())); + mTextCache = std::unique_ptr(mFont->buildTextCache( + wrappedText, 0.0f, 0.0f, 0x77777700 | static_cast(mOpacity * 255.0f))); if (mCursor > static_cast(mText.length())) mCursor = static_cast(mText.length()); diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index f18d5522b..947e3e718 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -46,7 +46,7 @@ public: // Sets whether we're in screensaver mode. void setScreensaverMode(bool isScreensaver) { mScreensaverMode = isScreensaver; } // Set the opacity for the embedded static image. - void setOpacity(unsigned char opacity) override { mOpacity = opacity; } + void setOpacity(float opacity) override { mOpacity = opacity; } bool hasStaticVideo() { return !mConfig.staticVideoPath.empty(); } bool hasStaticImage() { return mStaticImage.getTextureSize() != glm::ivec2 {0, 0}; } diff --git a/es-core/src/guis/GuiDetectDevice.cpp b/es-core/src/guis/GuiDetectDevice.cpp index a903692dc..bff59f5b4 100644 --- a/es-core/src/guis/GuiDetectDevice.cpp +++ b/es-core/src/guis/GuiDetectDevice.cpp @@ -123,7 +123,7 @@ bool GuiDetectDevice::input(InputConfig* config, Input input) if (input.value && mHoldingConfig == nullptr) { // Started holding. mHoldingConfig = config; - mHoldTime = HOLD_TIME; + mHoldTime = static_cast(HOLD_TIME); mDeviceHeld->setText(Utils::String::toUpper(config->getDeviceName())); } else if (!input.value && mHoldingConfig == config) { diff --git a/es-core/src/guis/GuiInfoPopup.cpp b/es-core/src/guis/GuiInfoPopup.cpp index b332bb442..d7b8b596c 100644 --- a/es-core/src/guis/GuiInfoPopup.cpp +++ b/es-core/src/guis/GuiInfoPopup.cpp @@ -17,6 +17,7 @@ GuiInfoPopup::GuiInfoPopup(std::string message, int duration) : mMessage {message} , mDuration {duration} + , mAlpha {1.0f} , mRunning {true} { mFrame = new NinePatchComponent; @@ -102,18 +103,18 @@ bool GuiInfoPopup::updateState() return false; } else if (curTime - mStartTime <= 500) { - mAlpha = ((curTime - mStartTime) * 255 / 500); + mAlpha = static_cast((curTime - mStartTime) / 500.0f); } else if (curTime - mStartTime < mDuration - 500) { - mAlpha = 255; + mAlpha = 1.0f; } else { - mAlpha = ((-(curTime - mStartTime - mDuration) * 255) / 500); + mAlpha = static_cast((-(curTime - mStartTime - mDuration)) / 500.0f); } - mGrid->setOpacity(static_cast(mAlpha)); + mGrid->setOpacity(mAlpha); // Apply fade-in effect to popup frame. - mFrame->setEdgeColor(0xFFFFFF00 | static_cast(mAlpha)); - mFrame->setCenterColor(0xFFFFFF00 | static_cast(mAlpha)); + mFrame->setEdgeColor(0xFFFFFF00 | static_cast(mAlpha * 255.0f)); + mFrame->setCenterColor(0xFFFFFF00 | static_cast(mAlpha * 255.0f)); return true; } diff --git a/es-core/src/guis/GuiInfoPopup.h b/es-core/src/guis/GuiInfoPopup.h index e5b2fe4a4..9d11072f3 100644 --- a/es-core/src/guis/GuiInfoPopup.h +++ b/es-core/src/guis/GuiInfoPopup.h @@ -32,7 +32,7 @@ private: std::string mMessage; int mDuration; - int mAlpha; + float mAlpha; int mStartTime; bool mRunning; }; diff --git a/es-core/src/guis/GuiInputConfig.cpp b/es-core/src/guis/GuiInputConfig.cpp index 7036036dd..591a09866 100644 --- a/es-core/src/guis/GuiInputConfig.cpp +++ b/es-core/src/guis/GuiInputConfig.cpp @@ -71,7 +71,7 @@ GuiInputConfig::GuiInputConfig(InputConfig* target, mSubtitle2 = std::make_shared( "HOLD ANY BUTTON 1 SECOND TO SKIP", Font::get(FONT_SIZE_SMALL), 0x999999FF, ALIGN_CENTER); // The opacity will be set to visible for any row that is skippable. - mSubtitle2->setOpacity(0); + mSubtitle2->setOpacity(0.0f); mGrid.setEntry(mSubtitle2, glm::ivec2 {0, 3}, false, true); @@ -157,8 +157,10 @@ GuiInputConfig::GuiInputConfig(InputConfig* target, // Only show "HOLD TO SKIP" if this input is skippable. mList->setCursorChangedCallback([this](CursorState) { - bool skippable = sGuiInputConfigList[mList->getCursorId()].skippable; - mSubtitle2->setOpacity(skippable * 255); + if (sGuiInputConfigList[mList->getCursorId()].skippable) + mSubtitle2->setOpacity(1.0f); + else + mSubtitle2->setOpacity(0.0f); }); // Make the first one say "PRESS ANYTHING" if we're re-configuring everything. From ead1d5af9ce73b1c2a897d5c93a0b00ecaf4ab95 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 23:33:29 +0100 Subject: [PATCH 34/82] Fixed a small alignment issue in ComponentList. --- es-core/src/components/ComponentList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp index b2c7e4e2e..56dee14b0 100644 --- a/es-core/src/components/ComponentList.cpp +++ b/es-core/src/components/ComponentList.cpp @@ -289,7 +289,7 @@ void ComponentList::render(const glm::mat4& parentTrans) dim.x = (trans[0].x * dim.x + trans[3].x) - trans[3].x; dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y; - const int clipRectPosX {static_cast(std::ceil(trans[3].x))}; + const int clipRectPosX {static_cast(std::round(trans[3].x))}; const int clipRectPosY {static_cast(std::round(trans[3].y))}; const int clipRectSizeX {static_cast(std::round(dim.x))}; const int clipRectSizeY {static_cast(std::round(dim.y))}; From a42d63e5675239a54f8ba59607ca66350eadb5a2 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 23:38:23 +0100 Subject: [PATCH 35/82] Code cleanup by moving a lot of casts to the appropriate getter functions. --- es-app/src/MediaViewer.cpp | 15 ++++----- es-app/src/Screensaver.cpp | 30 +++++++++--------- es-app/src/guis/GuiAlternativeEmulators.cpp | 14 ++++----- .../src/guis/GuiCollectionSystemsOptions.cpp | 2 +- es-app/src/guis/GuiGamelistOptions.cpp | 3 +- es-app/src/guis/GuiLaunchScreen.cpp | 27 ++++++++-------- es-app/src/guis/GuiMetaDataEd.cpp | 20 ++++++------ es-app/src/guis/GuiOfflineGenerator.cpp | 4 +-- es-app/src/guis/GuiScraperMulti.cpp | 3 +- es-app/src/guis/GuiScraperSingle.cpp | 13 ++++---- es-app/src/guis/GuiSettings.cpp | 3 +- es-app/src/views/GamelistBase.cpp | 3 +- es-app/src/views/SystemView.cpp | 3 +- es-app/src/views/ViewController.cpp | 31 +++++++++---------- es-core/src/GuiComponent.cpp | 6 ++-- es-core/src/HelpStyle.cpp | 3 +- es-core/src/Window.cpp | 20 ++++++------ es-core/src/components/BusyComponent.cpp | 6 ++-- es-core/src/components/FlexboxComponent.cpp | 6 ++-- es-core/src/components/GridTileComponent.cpp | 3 +- es-core/src/components/ImageComponent.cpp | 6 ++-- es-core/src/components/ImageGridComponent.h | 3 +- es-core/src/components/MenuComponent.cpp | 4 +-- es-core/src/components/TextListComponent.h | 6 ++-- es-core/src/components/VideoComponent.cpp | 6 ++-- es-core/src/guis/GuiTextEditKeyboardPopup.cpp | 8 ++--- es-core/src/renderers/Renderer.cpp | 12 +++---- es-core/src/renderers/Renderer.h | 12 +++---- es-core/src/renderers/Renderer_GL21.cpp | 6 ++-- es-core/src/renderers/Renderer_GLES10.cpp | 6 ++-- es-core/src/renderers/Renderer_GLES20.cpp | 6 ++-- 31 files changed, 137 insertions(+), 153 deletions(-) diff --git a/es-app/src/MediaViewer.cpp b/es-app/src/MediaViewer.cpp index dbe279207..ca56f5b66 100644 --- a/es-app/src/MediaViewer.cpp +++ b/es-app/src/MediaViewer.cpp @@ -83,8 +83,8 @@ void MediaViewer::render(const glm::mat4& /*parentTrans*/) Renderer::setMatrix(trans); // Render a black background below the game media. - Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF); + Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), + 0x000000FF, 0x000000FF); if (mVideo && !mDisplayingImage) { mVideo->render(trans); @@ -96,7 +96,7 @@ void MediaViewer::render(const glm::mat4& /*parentTrans*/) shaders = Renderer::SHADER_SCANLINES; if (Settings::getInstance()->getBool("MediaViewerVideoBlur")) { shaders |= Renderer::SHADER_BLUR_HORIZONTAL; - float heightModifier = Renderer::getScreenHeightModifier(); + float heightModifier {Renderer::getScreenHeightModifier()}; // clang-format off if (heightModifier < 1) videoParameters.blurPasses = 2; // Below 1080 @@ -265,11 +265,9 @@ void MediaViewer::playVideo() mVideo->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f); if (Settings::getInstance()->getBool("MediaViewerStretchVideos")) - mVideo->setResize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mVideo->setResize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); else - mVideo->setMaxSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mVideo->setMaxSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); mVideo->setVideo(mVideoFile); mVideo->setMediaViewerMode(true); @@ -288,7 +286,6 @@ void MediaViewer::showImage(int index) mImage->setImage(mImageFiles[index]); mImage->setOrigin(0.5f, 0.5f); mImage->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f); - mImage->setMaxSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mImage->setMaxSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); } } diff --git a/es-app/src/Screensaver.cpp b/es-app/src/Screensaver.cpp index 678a93fb1..6dca52057 100644 --- a/es-app/src/Screensaver.cpp +++ b/es-app/src/Screensaver.cpp @@ -126,11 +126,11 @@ void Screensaver::startScreensaver(bool generateMediaList) Renderer::getScreenHeight() / 2.0f); if (Settings::getInstance()->getBool("ScreensaverStretchImages")) - mImageScreensaver->setResize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mImageScreensaver->setResize(Renderer::getScreenWidth(), + Renderer::getScreenHeight()); else - mImageScreensaver->setMaxSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mImageScreensaver->setMaxSize(Renderer::getScreenWidth(), + Renderer::getScreenHeight()); } mTimer = 0; return; @@ -163,11 +163,11 @@ void Screensaver::startScreensaver(bool generateMediaList) Renderer::getScreenHeight() / 2.0f); if (Settings::getInstance()->getBool("ScreensaverStretchVideos")) - mVideoScreensaver->setResize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mVideoScreensaver->setResize(Renderer::getScreenWidth(), + Renderer::getScreenHeight()); else - mVideoScreensaver->setMaxSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mVideoScreensaver->setMaxSize(Renderer::getScreenWidth(), + Renderer::getScreenHeight()); mVideoScreensaver->setVideo(path); mVideoScreensaver->setScreensaverMode(true); @@ -237,8 +237,8 @@ void Screensaver::renderScreensaver() if (mVideoScreensaver && screensaverType == "video") { // Render a black background below the video. Renderer::setMatrix(Renderer::getIdentity()); - Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF); + Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), + 0x000000FF, 0x000000FF); // Only render the video if the state requires it. if (static_cast(mState) >= STATE_FADE_IN_VIDEO) { @@ -249,8 +249,8 @@ void Screensaver::renderScreensaver() else if (mImageScreensaver && screensaverType == "slideshow") { // Render a black background below the image. Renderer::setMatrix(Renderer::getIdentity()); - Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF); + Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), + 0x000000FF, 0x000000FF); // Only render the image if the state requires it. if (static_cast(mState) >= STATE_FADE_IN_VIDEO) { @@ -581,8 +581,8 @@ void Screensaver::generateOverlayInfo() if (mGameName == "" || mSystemName == "") return; - float posX = static_cast(Renderer::getWindowWidth()) * 0.023f; - float posY = static_cast(Renderer::getWindowHeight()) * 0.02f; + float posX {Renderer::getWindowWidth() * 0.023f}; + float posY {Renderer::getWindowHeight() * 0.02f}; std::string favoriteChar; if (mCurrentGame && mCurrentGame->getFavorite()) @@ -609,7 +609,7 @@ void Screensaver::generateOverlayInfo() else textSizeX = mGameOverlayFont[0].get()->sizeText(systemName).x; - float marginX = Renderer::getWindowWidth() * 0.01f; + float marginX {Renderer::getWindowWidth() * 0.01f}; mGameOverlayRectangleCoords.clear(); mGameOverlayRectangleCoords.push_back(posX - marginX); diff --git a/es-app/src/guis/GuiAlternativeEmulators.cpp b/es-app/src/guis/GuiAlternativeEmulators.cpp index 801ab3806..c026033ee 100644 --- a/es-app/src/guis/GuiAlternativeEmulators.cpp +++ b/es-app/src/guis/GuiAlternativeEmulators.cpp @@ -193,19 +193,19 @@ void GuiAlternativeEmulators::selectorWindow(SystemData* system) // Set a maximum width depending on the aspect ratio of the screen, to make the screen look // somewhat coherent regardless of screen type. The 1.778 aspect ratio value is the 16:9 // reference. - float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); - float maxWidthModifier = glm::clamp(0.72f * aspectValue, 0.50f, 0.92f); - float maxWidth = static_cast(Renderer::getScreenWidth()) * maxWidthModifier; + float aspectValue {1.778f / Renderer::getScreenAspectRatio()}; + float maxWidthModifier {glm::clamp(0.72f * aspectValue, 0.50f, 0.92f)}; + float maxWidth {Renderer::getScreenWidth() * maxWidthModifier}; // Set the width of the selector window to the menu width, unless the system full name is // too large to fit. If so, allow the size to be exceeded up to the maximum size calculated // above. - float systemTextWidth = + float systemTextWidth { Font::get(FONT_SIZE_LARGE)->sizeText(Utils::String::toUpper(system->getFullName())).x * - 1.05f; + 1.05f}; - float width = 0.0f; - float menuWidth = mMenu.getSize().x; + float width {0.0f}; + float menuWidth {mMenu.getSize().x}; if (systemTextWidth <= menuWidth) width = menuWidth; diff --git a/es-app/src/guis/GuiCollectionSystemsOptions.cpp b/es-app/src/guis/GuiCollectionSystemsOptions.cpp index 38c518a44..d0ac440c4 100644 --- a/es-app/src/guis/GuiCollectionSystemsOptions.cpp +++ b/es-app/src/guis/GuiCollectionSystemsOptions.cpp @@ -304,7 +304,7 @@ GuiCollectionSystemsOptions::GuiCollectionSystemsOptions(std::string title) glm::vec2 menuSize {ss->getMenuSize()}; glm::vec3 menuPos {ss->getMenuPosition()}; ss->setMenuSize(glm::vec2 {menuSize.x * 1.08f, menuSize.y}); - menuPos.x = static_cast((Renderer::getScreenWidth()) - ss->getMenuSize().x) / 2.0f; + menuPos.x = (Renderer::getScreenWidth() - ss->getMenuSize().x) / 2.0f; ss->setMenuPosition(menuPos); mWindow->pushGui(ss); }); diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index 6cb7c9904..5761cc20a 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -243,8 +243,7 @@ GuiGamelistOptions::GuiGamelistOptions(SystemData* system) } // Center the menu. - setSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); mMenu.setPosition((mSize.x - mMenu.getSize().x) / 2.0f, (mSize.y - mMenu.getSize().y) / 2.0f); } diff --git a/es-app/src/guis/GuiLaunchScreen.cpp b/es-app/src/guis/GuiLaunchScreen.cpp index a661ab76d..a9e2c08bc 100644 --- a/es-app/src/guis/GuiLaunchScreen.cpp +++ b/es-app/src/guis/GuiLaunchScreen.cpp @@ -99,29 +99,29 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game) // Adjust the width depending on the aspect ratio of the screen, to make the screen look // somewhat coherent regardless of screen type. The 1.778 aspect ratio value is the 16:9 // reference. - float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); + float aspectValue {1.778f / Renderer::getScreenAspectRatio()}; - float maxWidthModifier = glm::clamp(0.78f * aspectValue, 0.78f, 0.90f); - float minWidthModifier = glm::clamp(0.50f * aspectValue, 0.50f, 0.65f); + float maxWidthModifier {glm::clamp(0.78f * aspectValue, 0.78f, 0.90f)}; + float minWidthModifier {glm::clamp(0.50f * aspectValue, 0.50f, 0.65f)}; - float maxWidth = static_cast(Renderer::getScreenWidth()) * maxWidthModifier; - float minWidth = static_cast(Renderer::getScreenWidth()) * minWidthModifier; + float maxWidth {Renderer::getScreenWidth() * maxWidthModifier}; + float minWidth {Renderer::getScreenWidth() * minWidthModifier}; - float fontWidth = + float fontWidth { Font::get(static_cast(gameNameFontSize * std::min(Renderer::getScreenHeight(), Renderer::getScreenWidth()))) ->sizeText(Utils::String::toUpper(game->getName())) - .x; + .x}; // Add a bit of width to compensate for the left and right spacers. - fontWidth += static_cast(Renderer::getScreenWidth()) * 0.05f; + fontWidth += Renderer::getScreenWidth() * 0.05f; - float width = glm::clamp(fontWidth, minWidth, maxWidth); + float width {glm::clamp(fontWidth, minWidth, maxWidth)}; if (mImagePath != "") - setSize(width, static_cast(Renderer::getScreenHeight()) * 0.60f); + setSize(width, Renderer::getScreenHeight() * 0.60f); else - setSize(width, static_cast(Renderer::getScreenHeight()) * 0.38f); + setSize(width, Renderer::getScreenHeight() * 0.38f); // Set row heights. if (mImagePath != "") @@ -161,7 +161,7 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game) // of the images. if (mImagePath != "") { mMarquee->setImage(game->getMarqueePath(), false, true); - mMarquee->cropTransparentPadding(static_cast(Renderer::getScreenWidth()) * + mMarquee->cropTransparentPadding(Renderer::getScreenWidth() * (0.25f * (1.778f / Renderer::getScreenAspectRatio())), mGrid->getRowHeight(3)); @@ -178,8 +178,7 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game) setOrigin({0.5f, 0.5f}); // Center on the X axis and keep slightly off-center on the Y axis. - setPosition(static_cast(Renderer::getScreenWidth()) / 2.0f, - static_cast(Renderer::getScreenHeight()) / 2.25f); + setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.25f); mBackground.fitTo(mSize, glm::vec3 {}, glm::vec2 {-32.0f, -32.0f}); mBackground.setEdgeColor(0xEEEEEEFF); diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index d59bceaee..950a0a3a0 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -256,10 +256,9 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md, s->addRow(row, false); } - float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); - float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f); - float maxWidth = - static_cast(Renderer::getScreenWidth()) * maxWidthModifier; + float aspectValue {1.778f / Renderer::getScreenAspectRatio()}; + float maxWidthModifier {glm::clamp(0.64f * aspectValue, 0.42f, 0.92f)}; + float maxWidth {Renderer::getScreenWidth() * maxWidthModifier}; s->setMenuSize(glm::vec2 {maxWidth, s->getMenuSize().y}); s->setMenuPosition( @@ -388,10 +387,11 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md, s->addRow(row, false); } - float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); - float maxWidthModifier = glm::clamp(0.64f * aspectValue, 0.42f, 0.92f); - float maxWidth = - static_cast(Renderer::getScreenWidth()) * maxWidthModifier; + const float aspectValue {1.778f / Renderer::getScreenAspectRatio()}; + const float maxWidthModifier { + glm::clamp(0.64f * aspectValue, 0.42f, 0.92f)}; + const float maxWidth {static_cast(Renderer::getScreenWidth()) * + maxWidthModifier}; s->setMenuSize(glm::vec2 {maxWidth, s->getMenuSize().y}); s->setMenuPosition(glm::vec3 {(s->getSize().x - maxWidth) / 2.0f, @@ -581,9 +581,7 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md, mGrid.setEntry(mButtons, glm::ivec2 {0, 5}, true, false, glm::ivec2 {2, 1}); // Resize + center. - float width = - static_cast(std::min(static_cast(Renderer::getScreenHeight() * 1.05f), - static_cast(Renderer::getScreenWidth() * 0.90f))); + float width = std::min(Renderer::getScreenHeight() * 1.05f, Renderer::getScreenWidth() * 0.90f); // Set height explicitly to ten rows for the component list. float height = mList->getRowHeight(0) * 10.0f + mTitle->getSize().y + mSubtitle->getSize().y + diff --git a/es-app/src/guis/GuiOfflineGenerator.cpp b/es-app/src/guis/GuiOfflineGenerator.cpp index 938a03093..dd86eb5e2 100644 --- a/es-app/src/guis/GuiOfflineGenerator.cpp +++ b/es-app/src/guis/GuiOfflineGenerator.cpp @@ -176,8 +176,8 @@ GuiOfflineGenerator::GuiOfflineGenerator(const std::queue& gameQueue) // For narrower displays (e.g. in 4:3 ratio), allow the window to fill 95% of the screen // width rather than the 85% allowed for wider displays. - float width = - Renderer::getScreenWidth() * ((Renderer::getScreenAspectRatio() < 1.4f) ? 0.95f : 0.85f); + float width {Renderer::getScreenWidth() * + ((Renderer::getScreenAspectRatio() < 1.4f) ? 0.95f : 0.85f)}; setSize(width, Renderer::getScreenHeight() * 0.75f); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 83f9bcf0b..10713d660 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -151,8 +151,7 @@ GuiScraperMulti::GuiScraperMulti(const std::queue& searches float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth(); - float height = (mTitle->getFont()->getLetterHeight() + - static_cast(Renderer::getScreenHeight()) * 0.0637f) + + float height = (mTitle->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) + mSystem->getFont()->getLetterHeight() + mSubtitle->getFont()->getHeight() * 1.75f + mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 7.0f; diff --git a/es-app/src/guis/GuiScraperSingle.cpp b/es-app/src/guis/GuiScraperSingle.cpp index 7ab9bad90..0f774888c 100644 --- a/es-app/src/guis/GuiScraperSingle.cpp +++ b/es-app/src/guis/GuiScraperSingle.cpp @@ -120,14 +120,13 @@ GuiScraperSingle::GuiScraperSingle(ScraperSearchParams& params, // Limit the width of the GUI on ultrawide monitors. The 1.778 aspect ratio value is // the 16:9 reference. - float aspectValue = 1.778f / Renderer::getScreenAspectRatio(); - float width = glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth(); + float aspectValue {1.778f / Renderer::getScreenAspectRatio()}; + float width {glm::clamp(0.95f * aspectValue, 0.70f, 0.95f) * Renderer::getScreenWidth()}; - float height = (mGameName->getFont()->getLetterHeight() + - static_cast(Renderer::getScreenHeight()) * 0.0637f) + - mSystemName->getFont()->getLetterHeight() + - static_cast(Renderer::getScreenHeight()) * 0.04f + - mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f; + float height { + (mGameName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.0637f) + + mSystemName->getFont()->getLetterHeight() + Renderer::getScreenHeight() * 0.04f + + mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f}; setSize(width, height); setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, diff --git a/es-app/src/guis/GuiSettings.cpp b/es-app/src/guis/GuiSettings.cpp index e5ffd94c0..559c904f1 100644 --- a/es-app/src/guis/GuiSettings.cpp +++ b/es-app/src/guis/GuiSettings.cpp @@ -39,8 +39,7 @@ GuiSettings::GuiSettings(std::string title) addChild(&mMenu); mMenu.addButton("BACK", "back", [this] { delete this; }); - setSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); mMenu.setPosition((mSize.x - mMenu.getSize().x) / 2.0f, Renderer::getScreenHeight() * 0.13f); } diff --git a/es-app/src/views/GamelistBase.cpp b/es-app/src/views/GamelistBase.cpp index 77b1f4996..7416e101e 100644 --- a/es-app/src/views/GamelistBase.cpp +++ b/es-app/src/views/GamelistBase.cpp @@ -26,8 +26,7 @@ GamelistBase::GamelistBase(FileData* root) , mIsFolder {false} , mVideoPlaying {false} { - setSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); mList.setSize(mSize.x, mSize.y * 0.8f); mList.setPosition(0.0f, mSize.y * 0.2f); diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 39a244cf4..12e2b7b54 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -37,8 +37,7 @@ SystemView::SystemView() , mViewNeedsReload {true} , mLegacyMode {false} { - setSize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); mCarousel = std::make_unique(); mCarousel->setCursorChangedCallback([&](const CursorState& state) { onCursorChanged(state); }); diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 25e8e3076..846027d04 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -340,7 +340,7 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition) mSystemViewTransition = true; auto systemList = getSystemListView(); - systemList->setPosition(getSystemId(system) * static_cast(Renderer::getScreenWidth()), + systemList->setPosition(getSystemId(system) * Renderer::getScreenWidth(), systemList->getPosition().y); systemList->goToSystem(system, false); @@ -353,17 +353,17 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition) if (Settings::getInstance()->getString("TransitionStyle") == "slide") { if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL || getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL) - mCamera[3].y += static_cast(Renderer::getScreenHeight()); + mCamera[3].y += Renderer::getScreenHeight(); else - mCamera[3].x -= static_cast(Renderer::getScreenWidth()); + mCamera[3].x -= Renderer::getScreenWidth(); updateHelpPrompts(); } else if (Settings::getInstance()->getString("TransitionStyle") == "fade") { if (getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL || getSystemListView()->getCarouselType() == CarouselComponent::HORIZONTAL_WHEEL) - mCamera[3].y += static_cast(Renderer::getScreenHeight()); + mCamera[3].y += Renderer::getScreenHeight(); else - mCamera[3].x += static_cast(Renderer::getScreenWidth()); + mCamera[3].x += Renderer::getScreenWidth(); } else { updateHelpPrompts(); @@ -476,8 +476,7 @@ void ViewController::goToGamelist(SystemData* system) float offsetX = sysList->getPosition().x; int sysId = getSystemId(system); - sysList->setPosition(sysId * static_cast(Renderer::getScreenWidth()), - sysList->getPosition().y); + sysList->setPosition(sysId * Renderer::getScreenWidth(), sysList->getPosition().y); offsetX = sysList->getPosition().x - offsetX; mCamera[3].x -= offsetX; } @@ -520,11 +519,11 @@ void ViewController::goToGamelist(SystemData* system) if (mState.viewing == NOTHING) { mCamera = glm::translate(mCamera, -mCurrentView->getPosition()); if (Settings::getInstance()->getString("TransitionStyle") == "slide") { - mCamera[3].y -= static_cast(Renderer::getScreenHeight()); + mCamera[3].y -= Renderer::getScreenHeight(); updateHelpPrompts(); } else if (Settings::getInstance()->getString("TransitionStyle") == "fade") { - mCamera[3].y += static_cast(Renderer::getScreenHeight() * 2); + mCamera[3].y += Renderer::getScreenHeight() * 2.0f; } else { updateHelpPrompts(); @@ -773,8 +772,7 @@ std::shared_ptr ViewController::getGamelistView(SystemData* system std::vector& sysVec = SystemData::sSystemVector; int id {static_cast(std::find(sysVec.cbegin(), sysVec.cend(), system) - sysVec.cbegin())}; - view->setPosition(id * static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight() * 2)); + view->setPosition(id * Renderer::getScreenWidth(), Renderer::getScreenHeight() * 2.0f); addChild(view.get()); @@ -790,7 +788,7 @@ std::shared_ptr ViewController::getSystemListView() mSystemListView = std::shared_ptr(new SystemView); addChild(mSystemListView.get()); - mSystemListView->setPosition(0, static_cast(Renderer::getScreenHeight())); + mSystemListView->setPosition(0, Renderer::getScreenHeight()); return mSystemListView; } @@ -870,9 +868,8 @@ void ViewController::render(const glm::mat4& parentTrans) // Camera position, position + size. glm::vec3 viewStart {transInverse[3]}; - glm::vec3 viewEnd {std::fabs(trans[3].x) + static_cast(Renderer::getScreenWidth()), - std::fabs(trans[3].y) + static_cast(Renderer::getScreenHeight()), - 0.0f}; + glm::vec3 viewEnd {std::fabs(trans[3].x) + Renderer::getScreenWidth(), + std::fabs(trans[3].y) + Renderer::getScreenHeight(), 0.0f}; // Keep track of UI mode changes. UIModeController::getInstance()->monitorUIMode(); @@ -904,8 +901,8 @@ void ViewController::render(const glm::mat4& parentTrans) if (mFadeOpacity) { unsigned int fadeColor = 0x00000000 | static_cast(mFadeOpacity * 255); Renderer::setMatrix(parentTrans); - Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight()), fadeColor, fadeColor); + Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), + fadeColor, fadeColor); } } diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 978af1a6c..0abb63b66 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -321,9 +321,9 @@ void GuiComponent::applyTheme(const std::shared_ptr& theme, const std::string& element, unsigned int properties) { - glm::vec2 scale {getParent() ? getParent()->getSize() : - glm::vec2 {static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}}; + glm::vec2 scale {getParent() ? + getParent()->getSize() : + glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}}; const ThemeData::ThemeElement* elem = theme->getElement(view, element, ""); if (!elem) diff --git a/es-core/src/HelpStyle.cpp b/es-core/src/HelpStyle.cpp index 13b4ba2e2..e5d86a533 100644 --- a/es-core/src/HelpStyle.cpp +++ b/es-core/src/HelpStyle.cpp @@ -40,8 +40,7 @@ void HelpStyle::applyTheme(const std::shared_ptr& theme, const std::s if (elem->has("pos")) position = elem->get("pos") * - glm::vec2 {static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}; + glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}; if (elem->has("origin")) origin = elem->get("origin"); diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 8cf532fe3..b4a44b671 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -131,8 +131,7 @@ bool Window::init() } mBackgroundOverlay->setImage(":/graphics/screen_gradient.png"); - mBackgroundOverlay->setResize(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())); + mBackgroundOverlay->setResize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); #if defined(USE_OPENGL_21) mPostprocessedBackground = TextureResource::get(""); @@ -461,7 +460,8 @@ void Window::render() const auto backgroundStartTime = std::chrono::system_clock::now(); #endif unsigned char* processedTexture = - new unsigned char[Renderer::getScreenWidth() * Renderer::getScreenHeight() * 4]; + new unsigned char[Renderer::getScreenWidth() * Renderer::getScreenHeight() * + 4.0f]; // De-focus the background using multiple passes of gaussian blur, with the number // of iterations relative to the screen resolution. @@ -502,7 +502,8 @@ void Window::render() } mPostprocessedBackground->initFromPixels( - processedTexture, Renderer::getScreenWidth(), Renderer::getScreenHeight()); + processedTexture, static_cast(Renderer::getScreenWidth()), + static_cast(Renderer::getScreenHeight())); mBackgroundOverlay->setImage(mPostprocessedBackground); @@ -561,8 +562,7 @@ void Window::render() // Render the quick list scrolling overlay, which is triggered in IList. if (mListScrollOpacity != 0.0f) { Renderer::setMatrix(Renderer::getIdentity()); - Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight()), + Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x00000000 | static_cast(mListScrollOpacity * 255.0f), 0x00000000 | static_cast(mListScrollOpacity * 255.0f)); @@ -628,8 +628,8 @@ void Window::renderLoadingScreen(std::string text) { glm::mat4 trans {Renderer::getIdentity()}; Renderer::setMatrix(trans); - Renderer::drawRect(0.0f, 0.0f, static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF); + Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), + 0x000000FF, 0x000000FF); ImageComponent splash(true); splash.setImage(":/graphics/splash.svg"); @@ -641,8 +641,8 @@ void Window::renderLoadingScreen(std::string text) auto& font = mDefaultFonts.at(1); TextCache* cache = font->buildTextCache(text, 0.0f, 0.0f, 0x656565FF); - float x = std::round((Renderer::getScreenWidth() - cache->metrics.size.x) / 2.0f); - float y = std::round(Renderer::getScreenHeight() * 0.835f); + float x {std::round((Renderer::getScreenWidth() - cache->metrics.size.x) / 2.0f)}; + float y {std::round(Renderer::getScreenHeight() * 0.835f)}; trans = glm::translate(trans, glm::vec3 {x, y, 0.0f}); Renderer::setMatrix(trans); font->renderTextCache(cache); diff --git a/es-core/src/components/BusyComponent.cpp b/es-core/src/components/BusyComponent.cpp index 796094ada..68ae9620a 100644 --- a/es-core/src/components/BusyComponent.cpp +++ b/es-core/src/components/BusyComponent.cpp @@ -45,10 +45,10 @@ void BusyComponent::onSizeChanged() if (mSize.x == 0.0f || mSize.y == 0.0f) return; - const float middleSpacerWidth = 0.01f * Renderer::getScreenWidth(); - const float textHeight = mText->getFont()->getLetterHeight(); + const float middleSpacerWidth {0.01f * Renderer::getScreenWidth()}; + const float textHeight {mText->getFont()->getLetterHeight()}; mText->setSize(0, textHeight); - const float textWidth = mText->getSize().x + (4.0f * Renderer::getScreenWidthModifier()); + const float textWidth {mText->getSize().x + (4.0f * Renderer::getScreenWidthModifier())}; mGrid.setColWidthPerc(1, textHeight / mSize.x); // Animation is square. mGrid.setColWidthPerc(2, middleSpacerWidth / mSize.x); diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp index 85882bfcf..aa2567f27 100644 --- a/es-core/src/components/FlexboxComponent.cpp +++ b/es-core/src/components/FlexboxComponent.cpp @@ -91,10 +91,8 @@ void FlexboxComponent::computeLayout() mItemMargin.y = glm::clamp(mItemMargin.y, 0.0f, mSize.y / 2.0f); // Also keep the size within reason. - mSize.x = glm::clamp(mSize.x, static_cast(Renderer::getScreenWidth()) * 0.03f, - static_cast(Renderer::getScreenWidth())); - mSize.y = glm::clamp(mSize.y, static_cast(Renderer::getScreenHeight()) * 0.03f, - static_cast(Renderer::getScreenHeight())); + mSize.x = glm::clamp(mSize.x, Renderer::getScreenWidth() * 0.03f, Renderer::getScreenWidth()); + mSize.y = glm::clamp(mSize.y, Renderer::getScreenHeight() * 0.03f, Renderer::getScreenHeight()); if (mItemsPerLine * mLines < mItems.size()) { LOG(LogWarning) diff --git a/es-core/src/components/GridTileComponent.cpp b/es-core/src/components/GridTileComponent.cpp index 98125252b..7f17650bb 100644 --- a/es-core/src/components/GridTileComponent.cpp +++ b/es-core/src/components/GridTileComponent.cpp @@ -75,8 +75,7 @@ void GridTileComponent::update(int deltaTime) void applyThemeToProperties(const ThemeData::ThemeElement* elem, GridTileProperties* properties) { - glm::vec2 screen {static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}; + glm::vec2 screen {Renderer::getScreenWidth(), Renderer::getScreenHeight()}; if (elem->has("size")) properties->mSize = elem->get("size") * screen; diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index f1b6bb7dc..57ec218da 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -484,9 +484,9 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, if (!elem) return; - glm::vec2 scale {getParent() ? getParent()->getSize() : - glm::vec2(static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight()))}; + glm::vec2 scale {getParent() ? + getParent()->getSize() : + glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())}; if (properties & ThemeFlags::SIZE) { if (elem->has("size")) diff --git a/es-core/src/components/ImageGridComponent.h b/es-core/src/components/ImageGridComponent.h index 0175f512d..689052728 100644 --- a/es-core/src/components/ImageGridComponent.h +++ b/es-core/src/components/ImageGridComponent.h @@ -124,8 +124,7 @@ private: template ImageGridComponent::ImageGridComponent() { - glm::vec2 screen {static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}; + glm::vec2 screen {Renderer::getScreenWidth(), Renderer::getScreenHeight()}; mCamera = 0.0f; mCameraDirection = 1.0f; diff --git a/es-core/src/components/MenuComponent.cpp b/es-core/src/components/MenuComponent.cpp index 3b7dd20f9..d720ce961 100644 --- a/es-core/src/components/MenuComponent.cpp +++ b/es-core/src/components/MenuComponent.cpp @@ -108,9 +108,7 @@ void MenuComponent::updateSize() } } - float width = - static_cast(std::min(static_cast(Renderer::getScreenHeight() * 1.05f), - static_cast(Renderer::getScreenWidth() * 0.90f))); + float width {std::min(Renderer::getScreenHeight() * 1.05f, Renderer::getScreenWidth() * 0.90f)}; setSize(width, height); } diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index e08d8ac46..c4052de83 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -531,9 +531,9 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, << str << "\""; } if (elem->has("horizontalMargin")) { - mHorizontalMargin = elem->get("horizontalMargin") * - (this->mParent ? this->mParent->getSize().x : - static_cast(Renderer::getScreenWidth())); + mHorizontalMargin = + elem->get("horizontalMargin") * + (this->mParent ? this->mParent->getSize().x : Renderer::getScreenWidth()); } } diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 96fa2c761..63eb0402b 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -221,9 +221,9 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, if (!elem) return; - glm::vec2 scale {getParent() ? getParent()->getSize() : - glm::vec2 {static_cast(Renderer::getScreenWidth()), - static_cast(Renderer::getScreenHeight())}}; + glm::vec2 scale {getParent() ? + getParent()->getSize() : + glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}}; if (properties & ThemeFlags::SIZE) { if (elem->has("size")) { diff --git a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp index 9398b9fe4..bef250965 100644 --- a/es-core/src/guis/GuiTextEditKeyboardPopup.cpp +++ b/es-core/src/guis/GuiTextEditKeyboardPopup.cpp @@ -272,8 +272,8 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( if (mMultiLine) { setSize(width, KEYBOARD_HEIGHT + textHeight - mText->getFont()->getHeight()); - setPosition((static_cast(Renderer::getScreenWidth()) - mSize.x) / 2.0f, - (static_cast(Renderer::getScreenHeight()) - mSize.y) / 2.0f); + setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, + (Renderer::getScreenHeight() - mSize.y) / 2.0f); } else { if (mComplexMode) @@ -281,8 +281,8 @@ GuiTextEditKeyboardPopup::GuiTextEditKeyboardPopup( else setSize(width, KEYBOARD_HEIGHT); - setPosition((static_cast(Renderer::getScreenWidth()) - mSize.x) / 2.0f, - (static_cast(Renderer::getScreenHeight()) - mSize.y) / 2.0f); + setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f, + (Renderer::getScreenHeight() - mSize.y) / 2.0f); } if (!multiLine) diff --git a/es-core/src/renderers/Renderer.cpp b/es-core/src/renderers/Renderer.cpp index 8c0095cf4..52de45e0b 100644 --- a/es-core/src/renderers/Renderer.cpp +++ b/es-core/src/renderers/Renderer.cpp @@ -535,12 +535,12 @@ namespace Renderer const glm::mat4& getProjectionMatrix() { return mProjectionMatrix; } SDL_Window* getSDLWindow() { return sdlWindow; } - const int getWindowWidth() { return windowWidth; } - const int getWindowHeight() { return windowHeight; } - const int getScreenWidth() { return screenWidth; } - const int getScreenHeight() { return screenHeight; } - const int getScreenOffsetX() { return screenOffsetX; } - const int getScreenOffsetY() { return screenOffsetY; } + const float getWindowWidth() { return static_cast(windowWidth); } + const float getWindowHeight() { return static_cast(windowHeight); } + const float getScreenWidth() { return static_cast(screenWidth); } + const float getScreenHeight() { return static_cast(screenHeight); } + const float getScreenOffsetX() { return static_cast(screenOffsetX); } + const float getScreenOffsetY() { return static_cast(screenOffsetY); } const int getScreenRotate() { return screenRotate; } const float getScreenWidthModifier() { return screenWidthModifier; } const float getScreenHeightModifier() { return screenHeightModifier; } diff --git a/es-core/src/renderers/Renderer.h b/es-core/src/renderers/Renderer.h index 8e14bb575..83dfe75db 100644 --- a/es-core/src/renderers/Renderer.h +++ b/es-core/src/renderers/Renderer.h @@ -146,12 +146,12 @@ namespace Renderer const Blend::Factor srcBlendFactor = Blend::SRC_ALPHA, const Blend::Factor dstBlendFactor = Blend::ONE_MINUS_SRC_ALPHA); SDL_Window* getSDLWindow(); - const int getWindowWidth(); - const int getWindowHeight(); - const int getScreenWidth(); - const int getScreenHeight(); - const int getScreenOffsetX(); - const int getScreenOffsetY(); + const float getWindowWidth(); + const float getWindowHeight(); + const float getScreenWidth(); + const float getScreenHeight(); + const float getScreenOffsetX(); + const float getScreenOffsetY(); const int getScreenRotate(); const float getScreenWidthModifier(); const float getScreenHeightModifier(); diff --git a/es-core/src/renderers/Renderer_GL21.cpp b/es-core/src/renderers/Renderer_GL21.cpp index bd5e97d4d..59b44c115 100644 --- a/es-core/src/renderers/Renderer_GL21.cpp +++ b/es-core/src/renderers/Renderer_GL21.cpp @@ -389,7 +389,8 @@ namespace Renderer void setViewport(const Rect& viewport) { // glViewport starts at the bottom left of the window. - GL_CHECK_ERROR(glViewport(viewport.x, getWindowHeight() - viewport.y - viewport.h, + GL_CHECK_ERROR(glViewport(viewport.x, + static_cast(getWindowHeight()) - viewport.y - viewport.h, viewport.w, viewport.h)); } @@ -400,7 +401,8 @@ namespace Renderer } else { // glScissor starts at the bottom left of the window. - GL_CHECK_ERROR(glScissor(scissor.x, getWindowHeight() - scissor.y - scissor.h, + GL_CHECK_ERROR(glScissor(scissor.x, + static_cast(getWindowHeight()) - scissor.y - scissor.h, scissor.w, scissor.h)); GL_CHECK_ERROR(glEnable(GL_SCISSOR_TEST)); } diff --git a/es-core/src/renderers/Renderer_GLES10.cpp b/es-core/src/renderers/Renderer_GLES10.cpp index d966ac117..1f905f5c5 100644 --- a/es-core/src/renderers/Renderer_GLES10.cpp +++ b/es-core/src/renderers/Renderer_GLES10.cpp @@ -233,7 +233,8 @@ namespace Renderer void setViewport(const Rect& viewport) { // glViewport starts at the bottom left of the window. - GL_CHECK_ERROR(glViewport(viewport.x, getWindowHeight() - viewport.y - viewport.h, + GL_CHECK_ERROR(glViewport(viewport.x, + static_cast(getWindowHeight()) - viewport.y - viewport.h, viewport.w, viewport.h)); } @@ -244,7 +245,8 @@ namespace Renderer } else { // glScissor starts at the bottom left of the window. - GL_CHECK_ERROR(glScissor(scissor.x, getWindowHeight() - scissor.y - scissor.h, + GL_CHECK_ERROR(glScissor(scissor.x, + static_cast(getWindowHeight()) - scissor.y - scissor.h, scissor.w, scissor.h)); GL_CHECK_ERROR(glEnable(GL_SCISSOR_TEST)); } diff --git a/es-core/src/renderers/Renderer_GLES20.cpp b/es-core/src/renderers/Renderer_GLES20.cpp index a6e3b1042..715855b97 100644 --- a/es-core/src/renderers/Renderer_GLES20.cpp +++ b/es-core/src/renderers/Renderer_GLES20.cpp @@ -434,7 +434,8 @@ namespace Renderer void setViewport(const Rect& viewport) { // glViewport starts at the bottom left of the window. - GL_CHECK_ERROR(glViewport(viewport.x, getWindowHeight() - viewport.y - viewport.h, + GL_CHECK_ERROR(glViewport(viewport.x, + static_cast(getWindowHeight()) - viewport.y - viewport.h, viewport.w, viewport.h)); } @@ -445,7 +446,8 @@ namespace Renderer } else { // glScissor starts at the bottom left of the window. - GL_CHECK_ERROR(glScissor(scissor.x, getWindowHeight() - scissor.y - scissor.h, + GL_CHECK_ERROR(glScissor(scissor.x, + static_cast(getWindowHeight()) - scissor.y - scissor.h, scissor.w, scissor.h)); GL_CHECK_ERROR(glEnable(GL_SCISSOR_TEST)); } From 35970dd95d4ea3f4dce9486c07e9976a0c9299bf Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 11 Feb 2022 23:45:25 +0100 Subject: [PATCH 36/82] (Windows) Fixed a few MSVC compiler warnings. --- es-core/src/Window.cpp | 6 +++--- es-core/src/renderers/Renderer_GL21.cpp | 8 ++++---- es-core/src/resources/Font.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index b4a44b671..b2e91f680 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -459,9 +459,9 @@ void Window::render() #if (CLOCK_BACKGROUND_CREATION) const auto backgroundStartTime = std::chrono::system_clock::now(); #endif - unsigned char* processedTexture = - new unsigned char[Renderer::getScreenWidth() * Renderer::getScreenHeight() * - 4.0f]; + unsigned char* processedTexture { + new unsigned char[static_cast(Renderer::getScreenWidth()) * + static_cast(Renderer::getScreenHeight()) * 4]}; // De-focus the background using multiple passes of gaussian blur, with the number // of iterations relative to the screen resolution. diff --git a/es-core/src/renderers/Renderer_GL21.cpp b/es-core/src/renderers/Renderer_GL21.cpp index 59b44c115..0dbc07221 100644 --- a/es-core/src/renderers/Renderer_GL21.cpp +++ b/es-core/src/renderers/Renderer_GL21.cpp @@ -453,10 +453,10 @@ namespace Renderer { Vertex vertices[4]; std::vector shaderList; - GLuint width = getScreenWidth(); - GLuint height = getScreenHeight(); - float widthf = static_cast(width); - float heightf = static_cast(height); + GLuint width {static_cast(getScreenWidth())}; + GLuint height {static_cast(getScreenHeight())}; + float widthf {static_cast(width)}; + float heightf {static_cast(height)}; // Set vertex positions and texture coordinates to full screen as all // postprocessing is applied to the complete screen area. diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index f5c24aca1..e76259281 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -84,7 +84,7 @@ Font::Font(int size, const std::string& path) LOG(LogWarning) << "Requested font size too small, changing to minimum supported size"; } else if (mSize > Renderer::getScreenHeight()) { - mSize = Renderer::getScreenHeight(); + mSize = static_cast(Renderer::getScreenHeight()); LOG(LogWarning) << "Requested font size too large, changing to maximum supported size"; } From c24cf1e57ae940e38c2dbadcda3593ec282c6468 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 13:36:40 +0100 Subject: [PATCH 37/82] Fixed an issue where RatingComponent outlines would not fade correctly. --- es-core/src/components/RatingComponent.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/es-core/src/components/RatingComponent.cpp b/es-core/src/components/RatingComponent.cpp index e34a40fb4..294525a25 100644 --- a/es-core/src/components/RatingComponent.cpp +++ b/es-core/src/components/RatingComponent.cpp @@ -119,11 +119,11 @@ void RatingComponent::onSizeChanged() void RatingComponent::updateVertices() { - const float numStars = NUM_RATING_STARS; - const float h = getSize().y; // Ss the same as a single star's width. - const float w = getSize().y * mValue * numStars; - const float fw = getSize().y * numStars; - const unsigned int color = Renderer::convertRGBAToABGR(mColorShift); + const float numStars {NUM_RATING_STARS}; + const float h {getSize().y}; // Ss the same as a single star's width. + const float w {getSize().y * mValue * numStars}; + const float fw {getSize().y * numStars}; + const unsigned int color {Renderer::convertRGBAToABGR(mColorShift)}; // clang-format off mVertices[0] = {{0.0f, 0.0f}, {0.0f, 1.0f}, color}; @@ -140,7 +140,7 @@ void RatingComponent::updateVertices() void RatingComponent::updateColors() { - const unsigned int color = Renderer::convertRGBAToABGR(mColorShift); + const unsigned int color {Renderer::convertRGBAToABGR(mColorShift)}; for (int i = 0; i < 8; ++i) mVertices[i].col = color; @@ -165,7 +165,7 @@ void RatingComponent::render(const glm::mat4& parentTrans) if (mUnfilledColor != mColorShift) { const unsigned int color = Renderer::convertRGBAToABGR(mUnfilledColor); for (int i = 0; i < 8; ++i) - mVertices[i].col = color; + mVertices[i].col = (color & 0x00FFFFFF) + (mVertices[i].col & 0xFF000000); } Renderer::drawTriangleStrips(&mVertices[4], 4); From f585f87497f4a7992e680374bd5f1a23f4e26757 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 17:38:55 +0100 Subject: [PATCH 38/82] Added support for setting component opacity from the theme configuration. --- es-core/src/GuiComponent.cpp | 10 ++++---- es-core/src/GuiComponent.h | 3 ++- es-core/src/ThemeData.cpp | 13 ++++++++++- es-core/src/ThemeData.h | 3 ++- es-core/src/components/BadgeComponent.cpp | 6 ++--- es-core/src/components/ImageComponent.cpp | 6 ++--- es-core/src/components/LottieComponent.cpp | 6 ++--- es-core/src/components/RatingComponent.cpp | 6 +++-- .../src/components/ScrollableContainer.cpp | 2 +- es-core/src/components/SwitchComponent.h | 2 +- es-core/src/components/TextComponent.cpp | 23 +++++++++---------- es-core/src/components/TextComponent.h | 5 +++- 12 files changed, 51 insertions(+), 34 deletions(-) diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 0abb63b66..09609ebcf 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -31,6 +31,7 @@ GuiComponent::GuiComponent() , mRotationOrigin {0.5f, 0.5f} , mSize {0.0f, 0.0f} , mOpacity {1.0f} + , mThemeOpacity {1.0f} , mRotation {0.0f} , mScale {1.0f} , mDefaultZIndex {0.0f} @@ -356,10 +357,11 @@ void GuiComponent::applyTheme(const std::shared_ptr& theme, else setZIndex(getDefaultZIndex()); - if (properties & ThemeFlags::VISIBLE && elem->has("visible")) - setVisible(elem->get("visible")); - else - setVisible(true); + if (properties & ThemeFlags::OPACITY && elem->has("opacity")) + mThemeOpacity = glm::clamp(elem->get("opacity"), 0.0f, 1.0f); + + if (properties & ThemeFlags::VISIBLE && elem->has("visible") && !elem->get("visible")) + mThemeOpacity = 0.0f; } void GuiComponent::updateHelpPrompts() diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 49cd66f57..fb690b8e6 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -191,7 +191,7 @@ public: virtual bool isListScrolling() { return false; } virtual void stopListScrolling() {} - virtual float getOpacity() const { return mOpacity; } + virtual const float getOpacity() const { return mOpacity; } virtual void setOpacity(float opacity); virtual unsigned int getColor() const { return mColor; } virtual unsigned int getColorShift() const { return mColorShift; } @@ -291,6 +291,7 @@ protected: glm::vec2 mSize; float mOpacity; + float mThemeOpacity; float mRotation; float mScale; float mDefaultZIndex; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 7844d69bd..88397bfdc 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -94,6 +94,7 @@ std::map> {"colorEnd", COLOR}, {"gradientType", STRING}, {"scrollFadeIn", BOOLEAN}, + {"opacity", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"video", @@ -107,8 +108,11 @@ std::map> {"default", PATH}, {"defaultImage", PATH}, {"imageMetadata", STRING}, + {"pillarboxes", BOOLEAN}, + {"scanlines", BOOLEAN}, {"delay", FLOAT}, {"scrollFadeIn", BOOLEAN}, + {"opacity", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}, {"showSnapshotNoVideo", BOOLEAN}, // For backward compatibility with legacy themes. @@ -123,6 +127,7 @@ std::map> {"speed", FLOAT}, {"direction", STRING}, {"keepAspectRatio", BOOLEAN}, + {"opacity", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"badges", @@ -142,6 +147,7 @@ std::map> {"controllerSize", FLOAT}, {"customBadgeIcon", PATH}, {"customControllerIcon", PATH}, + {"opacity", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"text", @@ -166,6 +172,7 @@ std::map> {"letterCase", STRING}, {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes. {"lineSpacing", FLOAT}, + {"opacity", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"datetime", @@ -187,6 +194,7 @@ std::map> {"lineSpacing", FLOAT}, {"format", STRING}, {"displayRelative", BOOLEAN}, + {"opacity", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"gamelistinfo", @@ -202,6 +210,7 @@ std::map> {"horizontalAlignment", STRING}, {"verticalAlignment", STRING}, {"alignment", STRING}, // For backward compatibility with legacy themes. + {"opacity", FLOAT}, {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"rating", @@ -213,6 +222,8 @@ std::map> {"color", COLOR}, {"filledPath", PATH}, {"unfilledPath", PATH}, + {"opacity", FLOAT}, + {"visible", BOOLEAN}, {"zIndex", FLOAT}}}, {"carousel", {{"type", STRING}, @@ -240,7 +251,7 @@ std::map> {"fontSize", FLOAT}, {"lineSpacing", FLOAT}, {"zIndex", FLOAT}, - {"legacyZIndexMode", STRING}}}, + {"legacyZIndexMode", STRING}}}, // For backward compatibility with legacy themes. {"textlist", {{"pos", NORMALIZED_PAIR}, {"size", NORMALIZED_PAIR}, diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 868f61b61..911cf23c7 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -56,7 +56,8 @@ namespace ThemeFlags DELAY = 0x00002000, Z_INDEX = 0x00004000, ROTATION = 0x00008000, - VISIBLE = 0x00010000, + OPACITY = 0x00010000, + VISIBLE = 0x00020000, ALL = 0xFFFFFFFF }; // clang-format on diff --git a/es-core/src/components/BadgeComponent.cpp b/es-core/src/components/BadgeComponent.cpp index eeb5dec1f..989133f67 100644 --- a/es-core/src/components/BadgeComponent.cpp +++ b/es-core/src/components/BadgeComponent.cpp @@ -168,14 +168,14 @@ const std::string BadgeComponent::getDisplayName(const std::string& shortName) void BadgeComponent::render(const glm::mat4& parentTrans) { - if (!isVisible()) + if (!isVisible() || mThemeOpacity == 0.0f) return; - if (mOpacity == 1.0f) { + if (mOpacity * mThemeOpacity == 1.0f) { mFlexboxComponent.render(parentTrans); } else { - mFlexboxComponent.setOpacity(mOpacity); + mFlexboxComponent.setOpacity(mOpacity * mThemeOpacity); mFlexboxComponent.render(parentTrans); mFlexboxComponent.setOpacity(1.0f); } diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 57ec218da..50d6ab2dd 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -369,7 +369,7 @@ void ImageComponent::updateVertices() void ImageComponent::updateColors() { - const float opacity = (mOpacity * (mFading ? mFadeOpacity : 1.0f)); + const float opacity = (mOpacity * mThemeOpacity * (mFading ? mFadeOpacity : 1.0f)); const unsigned int color = Renderer::convertRGBAToABGR( (mColorShift & 0xFFFFFF00) | static_cast((mColorShift & 0xFF) * opacity)); const unsigned int colorEnd = @@ -384,8 +384,8 @@ void ImageComponent::updateColors() void ImageComponent::render(const glm::mat4& parentTrans) { - if (!isVisible() || mTexture == nullptr || mTargetSize == glm::vec2 {0.0f, 0.0f} || - mSize == glm::vec2 {0.0f, 0.0f}) + if (!isVisible() || mThemeOpacity == 0.0f || mTexture == nullptr || + mTargetSize == glm::vec2 {0.0f, 0.0f} || mSize == glm::vec2 {0.0f, 0.0f}) return; glm::mat4 trans {parentTrans * getTransform()}; diff --git a/es-core/src/components/LottieComponent.cpp b/es-core/src/components/LottieComponent.cpp index 834f0e80a..560267201 100644 --- a/es-core/src/components/LottieComponent.cpp +++ b/es-core/src/components/LottieComponent.cpp @@ -330,10 +330,7 @@ void LottieComponent::update(int deltaTime) void LottieComponent::render(const glm::mat4& parentTrans) { - if (!isVisible()) - return; - - if (mAnimation == nullptr) + if (!isVisible() || mThemeOpacity == 0.0f || mAnimation == nullptr) return; glm::mat4 trans {parentTrans * getTransform()}; @@ -472,6 +469,7 @@ void LottieComponent::render(const glm::mat4& parentTrans) #if defined(USE_OPENGL_21) // Perform color space conversion from BGRA to RGBA. + vertices[0].opacity = mThemeOpacity; vertices[0].shaders = Renderer::SHADER_BGRA_TO_RGBA; #endif diff --git a/es-core/src/components/RatingComponent.cpp b/es-core/src/components/RatingComponent.cpp index 294525a25..af178780c 100644 --- a/es-core/src/components/RatingComponent.cpp +++ b/es-core/src/components/RatingComponent.cpp @@ -85,7 +85,8 @@ std::string RatingComponent::getRatingValue() const void RatingComponent::setOpacity(float opacity) { mOpacity = opacity; - mColorShift = (mColorShift >> 8 << 8) | static_cast(mOpacity * 255.0f); + mColorShift = + (mColorShift >> 8 << 8) | static_cast(mOpacity * mThemeOpacity * 255.0f); updateColors(); } @@ -148,7 +149,8 @@ void RatingComponent::updateColors() void RatingComponent::render(const glm::mat4& parentTrans) { - if (!isVisible() || mFilledTexture == nullptr || mUnfilledTexture == nullptr) + if (!isVisible() || mFilledTexture == nullptr || mUnfilledTexture == nullptr || + mThemeOpacity == 0.0f) return; glm::mat4 trans {parentTrans * getTransform()}; diff --git a/es-core/src/components/ScrollableContainer.cpp b/es-core/src/components/ScrollableContainer.cpp index c477e3c56..7ea5edc0e 100644 --- a/es-core/src/components/ScrollableContainer.cpp +++ b/es-core/src/components/ScrollableContainer.cpp @@ -214,7 +214,7 @@ void ScrollableContainer::update(int deltaTime) void ScrollableContainer::render(const glm::mat4& parentTrans) { - if (!isVisible()) + if (!isVisible() || mThemeOpacity == 0.0f || mChildren.front()->getValue() == "") return; glm::mat4 trans {parentTrans * getTransform()}; diff --git a/es-core/src/components/SwitchComponent.h b/es-core/src/components/SwitchComponent.h index 26acf5d50..653f3d13a 100644 --- a/es-core/src/components/SwitchComponent.h +++ b/es-core/src/components/SwitchComponent.h @@ -32,7 +32,7 @@ public: void setChangedColor(unsigned int color) override { mColorChangedValue = color; } void setCallback(const std::function& callbackFunc) { mToggleCallback = callbackFunc; } - float getOpacity() const override { return mImage.getOpacity(); } + float const getOpacity() const override { return mImage.getOpacity(); } void setOpacity(float opacity) override { mImage.setOpacity(opacity); } // Multiply all pixels in the image by this color when rendering. void setColorShift(unsigned int color) override { mImage.setColorShift(color); } diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 1288091b5..77cc15679 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -95,12 +95,11 @@ void TextComponent::setBackgroundColor(unsigned int color) // Scale the opacity. void TextComponent::setOpacity(float opacity) { - // This function is mostly called to do fade in and fade out of the text component element. - float o {opacity * mColorOpacity}; - mColor = (mColor & 0xFFFFFF00) | static_cast(o * 255.0f); + float textOpacity {opacity * mColorOpacity * mThemeOpacity}; + mColor = (mColor & 0xFFFFFF00) | static_cast(textOpacity * 255.0f); - float bgo {opacity * mBgColorOpacity}; - mBgColor = (mBgColor & 0xFFFFFF00) | static_cast(bgo * 255.0f); + float textBackgroundOpacity {opacity * mBgColorOpacity * mThemeOpacity}; + mBgColor = (mBgColor & 0xFFFFFF00) | static_cast(textBackgroundOpacity * 255.0f); onColorChanged(); GuiComponent::setOpacity(opacity); @@ -149,7 +148,7 @@ void TextComponent::setCapitalize(bool capitalize) void TextComponent::render(const glm::mat4& parentTrans) { - if (!isVisible()) + if (!isVisible() || mThemeOpacity == 0.0f) return; glm::mat4 trans {parentTrans * getTransform()}; @@ -302,18 +301,18 @@ void TextComponent::onTextChanged() } text.append(abbrev); - mTextCache = std::shared_ptr(f->buildTextCache( - text, glm::vec2 {}, (mColor >> 8 << 8) | static_cast(mOpacity * 255.0f), - mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin)); + text, glm::vec2 {}, mColor, mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin)); } else { mTextCache = std::shared_ptr( - f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {}, - (mColor >> 8 << 8) | static_cast(mOpacity * 255.0f), - mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin)); + f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {}, mColor, mSize.x, + mHorizontalAlignment, mLineSpacing, mNoTopMargin)); } + if (mOpacity != 1.0f || mThemeOpacity != 1.0f) + setOpacity(mOpacity); + // This is required to set the color transparency. onColorChanged(); } diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index 3a00c6db2..ea6b2a2a0 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -57,7 +57,10 @@ public: std::string getHiddenValue() const override { return mHiddenText; } void setHiddenValue(const std::string& value) override { setHiddenText(value); } - float getOpacity() const override { return static_cast((mColor & 0x000000FF) / 255.0f); } + float const getOpacity() const override + { + return static_cast((mColor & 0x000000FF) / 255.0f); + } void setOpacity(float opacity) override; void setSelectable(bool status) { mSelectable = status; } From 69c1a1259db22e49fe2735aae021637deffc2935 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 17:40:25 +0100 Subject: [PATCH 39/82] Added opacity support to the BGRA to RGBA shader. --- es-core/src/renderers/Renderer_GL21.cpp | 2 ++ resources/shaders/glsl/bgra_to_rgba.glsl | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/es-core/src/renderers/Renderer_GL21.cpp b/es-core/src/renderers/Renderer_GL21.cpp index 0dbc07221..199edb421 100644 --- a/es-core/src/renderers/Renderer_GL21.cpp +++ b/es-core/src/renderers/Renderer_GL21.cpp @@ -363,6 +363,8 @@ namespace Renderer if (runShader) { runShader->activateShaders(); runShader->setModelViewProjectionMatrix(getProjectionMatrix() * trans); + if (vertices->opacity < 1.0f) + runShader->setOpacity(vertices->opacity); GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices)); runShader->deactivateShaders(); } diff --git a/resources/shaders/glsl/bgra_to_rgba.glsl b/resources/shaders/glsl/bgra_to_rgba.glsl index aae41ab9a..618b868bd 100644 --- a/resources/shaders/glsl/bgra_to_rgba.glsl +++ b/resources/shaders/glsl/bgra_to_rgba.glsl @@ -21,13 +21,14 @@ void main(void) #elif defined(FRAGMENT) // Fragment section of code: +uniform float opacity = 1.0; uniform sampler2D myTexture; varying vec2 vTexCoord; void main() { vec4 color = texture2D(myTexture, vTexCoord); - gl_FragColor = vec4(color.bgra); + gl_FragColor = vec4(color.bgr, color.a * opacity); } #endif From 6572fa8f23358724dc4730e98d233ccac3e53083 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 17:43:20 +0100 Subject: [PATCH 40/82] The displayRelative property can now be overridden for the md_lastplayed metadata type. --- es-core/src/components/DateTimeComponent.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index e4f077c2a..87e78d0f1 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -117,9 +117,6 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, if (!elem) return; - if (elem->has("displayRelative")) - setDisplayRelative(elem->get("displayRelative")); - if (elem->has("format")) setFormat(elem->get("format")); @@ -178,6 +175,12 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, if (properties & METADATA && elem->has("metadata")) setMetadataField(elem->get("metadata")); + if (mMetadataField == "md_lastplayed") + setDisplayRelative(true); + + if (elem->has("displayRelative")) + setDisplayRelative(elem->get("displayRelative")); + if (properties & LETTER_CASE && elem->has("letterCase")) { std::string letterCase {elem->get("letterCase")}; if (letterCase == "uppercase") { From e0540ee03b640692bcad42d19f8ef5290351d9ad Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 17:46:31 +0100 Subject: [PATCH 41/82] Video pillarboxes and scanline rendering can now be controlled from the theme configuration. --- es-core/src/components/VideoComponent.cpp | 17 +++++++++++++- es-core/src/components/VideoComponent.h | 5 ++++ .../src/components/VideoFFmpegComponent.cpp | 23 +++++++++++++++---- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 63eb0402b..6f5769f57 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -36,6 +36,9 @@ VideoComponent::VideoComponent() , mGameLaunched {false} , mBlockPlayer {false} , mTargetIsMax {false} + , mDrawPillarboxes {true} + , mRenderScanlines {false} + , mLegacyTheme {false} , mFadeIn {1.0f} { // Setup the default configuration. @@ -201,7 +204,7 @@ void VideoComponent::renderSnapshot(const glm::mat4& parentTrans) // simply looks better than leaving an empty space where the video would have been located. if (mWindow->getGuiStackSize() > 1 || (mConfig.showSnapshotNoVideo && mVideoPath.empty()) || (mStartDelayed && mConfig.showSnapshotDelay)) { - mStaticImage.setOpacity(mOpacity); + mStaticImage.setOpacity(mOpacity * mThemeOpacity); mStaticImage.render(parentTrans); } } @@ -218,6 +221,8 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, const ThemeData::ThemeElement* elem = theme->getElement(view, element, "video"); + mLegacyTheme = theme->isLegacyTheme(); + if (!elem) return; @@ -268,6 +273,16 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, if (properties & METADATA && elem->has("imageMetadata")) setMetadataField(elem->get("imageMetadata")); + if (elem->has("pillarboxes")) + mDrawPillarboxes = elem->get("pillarboxes"); + + // Scanlines are not compatible with video transparency. + if (elem->has("scanlines")) { + mRenderScanlines = elem->get("scanlines"); + if (mRenderScanlines && mThemeOpacity != 0.0f) + mThemeOpacity = 1.0f; + } + if (elem->has("scrollFadeIn") && elem->get("scrollFadeIn")) mComponentThemeFlags |= ComponentThemeFlags::SCROLL_FADE_IN; } diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index 947e3e718..4cde87992 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -47,6 +47,8 @@ public: void setScreensaverMode(bool isScreensaver) { mScreensaverMode = isScreensaver; } // Set the opacity for the embedded static image. void setOpacity(float opacity) override { mOpacity = opacity; } + // Set whether to draw black pillarboxes/letterboxes behind videos. + void setDrawPillarboxes(bool state) { mDrawPillarboxes = state; } bool hasStaticVideo() { return !mConfig.staticVideoPath.empty(); } bool hasStaticImage() { return mStaticImage.getTextureSize() != glm::ivec2 {0, 0}; } @@ -139,6 +141,9 @@ protected: bool mGameLaunched; bool mBlockPlayer; bool mTargetIsMax; + bool mDrawPillarboxes; + bool mRenderScanlines; + bool mLegacyTheme; float mFadeIn; // Used for fading in the video screensaver. Configuration mConfig; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index b9a590855..825c042d8 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -123,6 +123,9 @@ void VideoFFmpegComponent::resize() void VideoFFmpegComponent::render(const glm::mat4& parentTrans) { + if (!isVisible() || mThemeOpacity == 0.0f) + return; + VideoComponent::render(parentTrans); glm::mat4 trans {parentTrans * getTransform()}; GuiComponent::renderChildren(trans); @@ -141,11 +144,18 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) Renderer::Vertex vertices[4]; Renderer::setMatrix(parentTrans); + unsigned int rectColor {0x000000FF}; + + if (mThemeOpacity != 1.0f) { + color = (static_cast(mThemeOpacity * 255.0f) << 24) + 0x00FFFFFF; + rectColor = static_cast(mThemeOpacity * 255.0f); + } + // Render the black rectangle behind the video. if (mVideoRectangleCoords.size() == 4) { Renderer::drawRect(mVideoRectangleCoords[0], mVideoRectangleCoords[1], mVideoRectangleCoords[2], mVideoRectangleCoords[3], // Line break. - 0x000000FF, 0x000000FF); + rectColor, rectColor); } // clang-format off @@ -205,9 +215,11 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) // Render scanlines if this option is enabled. However, if this is the media viewer // or the video screensaver, then skip this as the scanline rendering is then handled // in those modules as a postprocessing step. - if ((!mScreensaverMode && !mMediaViewerMode) && - Settings::getInstance()->getBool("GamelistVideoScanlines")) - vertices[0].shaders = Renderer::SHADER_SCANLINES; + if (!mScreensaverMode && !mMediaViewerMode) { + if ((mLegacyTheme && Settings::getInstance()->getBool("GamelistVideoScanlines")) || + (!mLegacyTheme && mRenderScanlines)) + vertices[0].shaders = Renderer::SHADER_SCANLINES; + } #endif // Render it. @@ -867,7 +879,8 @@ void VideoFFmpegComponent::calculateBlackRectangle() if (mVideoAreaPos != glm::vec2 {} && mVideoAreaSize != glm::vec2 {}) { mVideoRectangleCoords.clear(); - if (Settings::getInstance()->getBool("GamelistVideoPillarbox")) { + if ((mLegacyTheme && Settings::getInstance()->getBool("GamelistVideoPillarbox")) || + (!mLegacyTheme && mDrawPillarboxes)) { float rectHeight; float rectWidth; // Video is in landscape orientation. From 1cba6e202e244eba843ca20df911b78fae30a845 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 17:47:59 +0100 Subject: [PATCH 42/82] Disabled the pillarboxes and scanline rendering menu options if using a non-legacy theme set. --- es-app/src/guis/GuiMenu.cpp | 204 +++++++++++++++++++++--------------- 1 file changed, 117 insertions(+), 87 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 79139934f..584ceb441 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -283,81 +283,6 @@ void GuiMenu::openUIOptions() } }); - // When the theme set entries are scrolled or selected, update the relevant rows. - auto scrollThemeSetFunc = [=](const std::string& themeName, bool firstRun = false) { - auto selectedSet = themeSets.find(themeName); - if (selectedSet == themeSets.cend()) - return; - if (!firstRun) { - themeVariantsFunc(themeName, themeVariant->getSelected()); - themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected()); - } - int selectableVariants {0}; - for (auto& variant : selectedSet->second.capabilities.variants) { - if (variant.selectable) - ++selectableVariants; - } - if (!selectedSet->second.capabilities.legacyTheme && selectableVariants > 0) { - themeVariant->setEnabled(true); - themeVariant->setOpacity(1.0f); - themeVariant->getParent() - ->getChild(themeVariant->getChildIndex() - 1) - ->setOpacity(1.0f); - } - else { - themeVariant->setEnabled(false); - themeVariant->setOpacity(DISABLED_OPACITY); - themeVariant->getParent() - ->getChild(themeVariant->getChildIndex() - 1) - ->setOpacity(DISABLED_OPACITY); - } - - if (!selectedSet->second.capabilities.legacyTheme && - selectedSet->second.capabilities.aspectRatios.size() > 0) { - themeAspectRatio->setEnabled(true); - themeAspectRatio->setOpacity(1.0f); - themeAspectRatio->getParent() - ->getChild(themeAspectRatio->getChildIndex() - 1) - ->setOpacity(1.0f); - } - else { - themeAspectRatio->setEnabled(false); - themeAspectRatio->setOpacity(DISABLED_OPACITY); - themeAspectRatio->getParent() - ->getChild(themeAspectRatio->getChildIndex() - 1) - ->setOpacity(DISABLED_OPACITY); - } - if (!selectedSet->second.capabilities.legacyTheme) { - gamelist_view_style->setEnabled(false); - gamelist_view_style->setOpacity(DISABLED_OPACITY); - gamelist_view_style->getParent() - ->getChild(gamelist_view_style->getChildIndex() - 1) - ->setOpacity(DISABLED_OPACITY); - // TEMPORARY - // transition_style->setEnabled(false); - transition_style->setOpacity(DISABLED_OPACITY); - transition_style->getParent() - ->getChild(transition_style->getChildIndex() - 1) - ->setOpacity(DISABLED_OPACITY); - } - else { - gamelist_view_style->setEnabled(true); - gamelist_view_style->setOpacity(1.0f); - gamelist_view_style->getParent() - ->getChild(gamelist_view_style->getChildIndex() - 1) - ->setOpacity(1.0f); - - transition_style->setEnabled(true); - transition_style->setOpacity(1.0f); - transition_style->getParent() - ->getChild(transition_style->getChildIndex() - 1) - ->setOpacity(1.0f); - } - }; - - scrollThemeSetFunc(selectedSet->first, true); - theme_set->setCallback(scrollThemeSetFunc); - // Optionally start in selected system/gamelist. auto startupSystem = std::make_shared>( getHelpStyle(), "GAMELIST ON STARTUP", false); @@ -587,28 +512,28 @@ void GuiMenu::openUIOptions() #endif // Display pillarboxes (and letterboxes) for videos in the gamelists. - auto gamelist_video_pillarbox = std::make_shared(); - gamelist_video_pillarbox->setState(Settings::getInstance()->getBool("GamelistVideoPillarbox")); - s->addWithLabel("DISPLAY PILLARBOXES FOR GAMELIST VIDEOS", gamelist_video_pillarbox); - s->addSaveFunc([gamelist_video_pillarbox, s] { - if (gamelist_video_pillarbox->getState() != + auto gamelistVideoPillarbox = std::make_shared(); + gamelistVideoPillarbox->setState(Settings::getInstance()->getBool("GamelistVideoPillarbox")); + s->addWithLabel("DISPLAY PILLARBOXES FOR GAMELIST VIDEOS", gamelistVideoPillarbox); + s->addSaveFunc([gamelistVideoPillarbox, s] { + if (gamelistVideoPillarbox->getState() != Settings::getInstance()->getBool("GamelistVideoPillarbox")) { Settings::getInstance()->setBool("GamelistVideoPillarbox", - gamelist_video_pillarbox->getState()); + gamelistVideoPillarbox->getState()); s->setNeedsSaving(); } }); #if defined(USE_OPENGL_21) // Render scanlines for videos in the gamelists. - auto gamelist_video_scanlines = std::make_shared(); - gamelist_video_scanlines->setState(Settings::getInstance()->getBool("GamelistVideoScanlines")); - s->addWithLabel("RENDER SCANLINES FOR GAMELIST VIDEOS", gamelist_video_scanlines); - s->addSaveFunc([gamelist_video_scanlines, s] { - if (gamelist_video_scanlines->getState() != + auto gamelistVideoScanlines = std::make_shared(); + gamelistVideoScanlines->setState(Settings::getInstance()->getBool("GamelistVideoScanlines")); + s->addWithLabel("RENDER SCANLINES FOR GAMELIST VIDEOS", gamelistVideoScanlines); + s->addSaveFunc([gamelistVideoScanlines, s] { + if (gamelistVideoScanlines->getState() != Settings::getInstance()->getBool("GamelistVideoScanlines")) { Settings::getInstance()->setBool("GamelistVideoScanlines", - gamelist_video_scanlines->getState()); + gamelistVideoScanlines->getState()); s->setNeedsSaving(); } }); @@ -776,6 +701,111 @@ void GuiMenu::openUIOptions() } }); + // When the theme set entries are scrolled or selected, update the relevant rows. + auto scrollThemeSetFunc = [=](const std::string& themeName, bool firstRun = false) { + auto selectedSet = themeSets.find(themeName); + if (selectedSet == themeSets.cend()) + return; + if (!firstRun) { + themeVariantsFunc(themeName, themeVariant->getSelected()); + themeAspectRatiosFunc(themeName, themeAspectRatio->getSelected()); + } + int selectableVariants {0}; + for (auto& variant : selectedSet->second.capabilities.variants) { + if (variant.selectable) + ++selectableVariants; + } + if (!selectedSet->second.capabilities.legacyTheme && selectableVariants > 0) { + themeVariant->setEnabled(true); + themeVariant->setOpacity(1.0f); + themeVariant->getParent() + ->getChild(themeVariant->getChildIndex() - 1) + ->setOpacity(1.0f); + } + else { + themeVariant->setEnabled(false); + themeVariant->setOpacity(DISABLED_OPACITY); + themeVariant->getParent() + ->getChild(themeVariant->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + } + + if (!selectedSet->second.capabilities.legacyTheme && + selectedSet->second.capabilities.aspectRatios.size() > 0) { + themeAspectRatio->setEnabled(true); + themeAspectRatio->setOpacity(1.0f); + themeAspectRatio->getParent() + ->getChild(themeAspectRatio->getChildIndex() - 1) + ->setOpacity(1.0f); + } + else { + themeAspectRatio->setEnabled(false); + themeAspectRatio->setOpacity(DISABLED_OPACITY); + themeAspectRatio->getParent() + ->getChild(themeAspectRatio->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + } + if (!selectedSet->second.capabilities.legacyTheme) { + gamelist_view_style->setEnabled(false); + gamelist_view_style->setOpacity(DISABLED_OPACITY); + gamelist_view_style->getParent() + ->getChild(gamelist_view_style->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + // TEMPORARY + // transition_style->setEnabled(false); + transition_style->setOpacity(DISABLED_OPACITY); + transition_style->getParent() + ->getChild(transition_style->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + + // Pillarboxes are theme-controlled for newer themes. + gamelistVideoPillarbox->setEnabled(false); + gamelistVideoPillarbox->setOpacity(DISABLED_OPACITY); + gamelistVideoPillarbox->getParent() + ->getChild(gamelistVideoPillarbox->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); + + // Scanlines are theme-controlled for newer themes. +#if defined(USE_OPENGL_21) + gamelistVideoScanlines->setEnabled(false); + gamelistVideoScanlines->setOpacity(DISABLED_OPACITY); + gamelistVideoScanlines->getParent() + ->getChild(gamelistVideoScanlines->getChildIndex() - 1) + ->setOpacity(DISABLED_OPACITY); +#endif + } + else { + gamelist_view_style->setEnabled(true); + gamelist_view_style->setOpacity(1.0f); + gamelist_view_style->getParent() + ->getChild(gamelist_view_style->getChildIndex() - 1) + ->setOpacity(1.0f); + + transition_style->setEnabled(true); + transition_style->setOpacity(1.0f); + transition_style->getParent() + ->getChild(transition_style->getChildIndex() - 1) + ->setOpacity(1.0f); + + gamelistVideoPillarbox->setEnabled(true); + gamelistVideoPillarbox->setOpacity(1.0f); + gamelistVideoPillarbox->getParent() + ->getChild(gamelistVideoPillarbox->getChildIndex() - 1) + ->setOpacity(1.0f); + +#if defined(USE_OPENGL_21) + gamelistVideoScanlines->setEnabled(true); + gamelistVideoScanlines->setOpacity(1.0f); + gamelistVideoScanlines->getParent() + ->getChild(gamelistVideoScanlines->getChildIndex() - 1) + ->setOpacity(1.0f); +#endif + } + }; + + scrollThemeSetFunc(selectedSet->first, true); + theme_set->setCallback(scrollThemeSetFunc); + s->setSize(mSize); mWindow->pushGui(s); } From 8103bef3b8b4efaa0dab82b36f2b5568ca6d3afb Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 17:50:44 +0100 Subject: [PATCH 43/82] Fixed a minor animation glitch in the gamelist view. Also removed the explicit setDisplayRelative() call which made it impossible to override this theme setting. --- es-app/src/views/GamelistLegacy.h | 8 +++++--- es-app/src/views/GamelistView.cpp | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h index 3fab1be7f..2d13ef8c7 100644 --- a/es-app/src/views/GamelistLegacy.h +++ b/es-app/src/views/GamelistLegacy.h @@ -555,7 +555,11 @@ void GamelistView::legacyUpdateInfoPanel() comps.push_back(mRatingComponents.front().get()); for (auto it = comps.cbegin(); it != comps.cend(); ++it) { - GuiComponent* comp = *it; + GuiComponent* comp {*it}; + if (!fadingOut && !comp->isAnimationPlaying(0)) { + comp->setOpacity(1.0f); + continue; + } // 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) || @@ -635,9 +639,7 @@ void GamelistView::legacyInitMDValues() } mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->setFont(defaultFont); - // mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->setColor(0xFFFFFFFF); mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->setFont(defaultFont); - // mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->setColor(0xFFFFFFFF); values.emplace_back(mRatingComponents.front().get()); values.emplace_back(mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE].get()); diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index 14d47e88a..5d00fb1e6 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -714,14 +714,11 @@ void GamelistView::updateInfoPanel() } else if (metadata == "md_lastplayed") { date->setValue(file->metadata.get("lastplayed")); - date->setDisplayRelative(true); } else { date->setValue("19700101T000000"); } } - - fadingOut = false; } std::vector comps; @@ -756,7 +753,11 @@ void GamelistView::updateInfoPanel() } for (auto it = comps.cbegin(); it != comps.cend(); ++it) { - GuiComponent* comp = *it; + GuiComponent* comp {*it}; + if (!fadingOut && !comp->isAnimationPlaying(0)) { + comp->setOpacity(1.0f); + continue; + } // 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) || From efbd44ecc2f3616336210f08dbd5484089bfda3f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 12 Feb 2022 18:01:44 +0100 Subject: [PATCH 44/82] Documentation update. --- CHANGELOG.md | 9 +++ THEMES-DEV.md | 205 ++++++++++++++++++++++++++++++++++++++++------- USERGUIDE-DEV.md | 8 +- 3 files changed, 189 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cac6d4b2..f954fe5a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ * Replaced the forceUppercase theme property with a more versatile letterCase property (forceUppercase is retained for legacy theme compatibility) * Made it possible to set any text element as a scrollable container using either metadata values or literal strings * Added support for defining the scrollable container speed, start delay and reset delay from the theme configuration +* Added theme support for defining the opacity for most element types +* Added theme support for enabling and disabling video pillarboxes and scanline rendering +* Disabled the pillarboxes and scanline rendering menu options when using a non-legacy theme set * Improved theme element placement by replacing the "alignment" and "logoAlignment" properties with specific horizontal and vertical properties * Made it possible to use almost all game metadata field when theming text elements * Added scraper support for displaying the returned platform if it does not match the game platform, or if multiple platforms are defined for the system @@ -50,6 +53,7 @@ * Migrated the carousel code from SystemView to a separate new CarouselComponent * Changed all occurances of "GameList" to "Gamelist" throughout the codebase * Removed a huge amount of unnecessary Window* function parameters throughout the codebase +* Changed the opacity data type and functions from unsigned char to float throughout the codebase * Refactored the six gamelist classes into two new classes; GamelistBase and GamelistView * Rewrote the gamelist logic to handle an arbitrary amount of components per type and split the legacy code into a separate file * Renamed Gamelist.cpp to GamelistFileParser.cpp and moved it to its own namespace instead of using the global namespace @@ -68,13 +72,18 @@ * Changing some values using the metadata editor could lead to an incorrect sort order if the changes were done from within a grouped custom collection * Changing the setting "Group unthemed custom collections" could lead to incorrect custom collections sorting under some circumstances * When multi-scraping in semi-automatic mode and a long game name was scrolling, the start position was not reset when scraping the next game +* Slide and fade transitions would sometimes stop working after changing theme sets +* Horizontal and vertical gradients were mixed up (showing the opposite gradient type if set in a theme) * The VideoComponent static images were not fading out smoothly on gamelist fast-scrolling (only fixed for non-legacy themes) +* Rating icon outlines would not fade out correctly when fast-scrolling in a gamelist * Clearing a game in the metadata editor would sometimes not remove all media files (if there were both a .jpg and a .png for a certain file type) * The ScummVM platform entry was missing for TheGamesDB which resulted in very inaccurate scraper searches * During multi-scraping the busy indicator was not displayed after a result was acquired but before the thumbnail was completely downloaded * Text opacity did not work correctly in some places, such as for the help prompts * ScrollableContainer faded semi-transparent text to fully opaque when resetting * ScrollableContainer faded in the background text color in addition to the text color when resetting +* The device text flickered in GuiDetectDevice when configuring a controller +* The selector bar was not aligned correctly during menu scale-up animations ## Version 1.2.0 diff --git a/THEMES-DEV.md b/THEMES-DEV.md index bacc8d9a0..314cef6ab 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -743,6 +743,16 @@ The order in which you define properties does not matter and you only need to de #### image +Displays a raster image or a scalable vector graphics (SVG) image. + +Supported views: +* `system ` +* `gamelist` + +Instances per view: +* `unlimited` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - If only one axis is specified (and the other is zero), the other will be automatically calculated in accordance with the image's aspect ratio. @@ -786,8 +796,12 @@ The order in which you define properties does not matter and you only need to de * `scrollFadeIn` - type: BOOLEAN - If enabled, a short fade-in animation will be applied when scrolling through games in the gamelist view. This usually looks best if used for the main game image. - Default is `false` +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` * `visible` - type: BOOLEAN - - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. @@ -795,6 +809,15 @@ The order in which you define properties does not matter and you only need to de #### video +Plays a video and provides support for displaying a static image for a defined time period before starting the video player. Although an unlimited number of videos could in theory be defined per view it's strongly recommended to keep it at a single instance. Playing multiple videos simultaneously takes a lot of CPU resources and ES-DE currently has no audio mixing capabilities so the audio would not play correctly. + +Supported views: +* `gamelist` + +Instances per view: +* `unlimited` (recommended to keep at a single instance) + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - If only one axis is specified (and the other is zero), the other will be automatically calculated in accordance with the video's aspect ratio. @@ -809,9 +832,6 @@ The order in which you define properties does not matter and you only need to de * `rotationOrigin` - type: NORMALIZED_PAIR - Point around which the text will be rotated. - Default is `0.5 0.5` -* `delay` - type: FLOAT - - Delay in seconds before video will start playing. During the delay period the game image defined via the `imageMetadata` property will be displayed. If that property is not set, then the `delay` property will be ignored. - - Default is `0` * `path` - type: PATH - Path to a video file. Setting a value for this property will make the video static, i.e. it will only play this video regardless of whether there is a game video available or not. If the `default` property has also been set, it will be overridden as the `path` property takes precedence. * `default` - type: PATH @@ -830,11 +850,24 @@ The order in which you define properties does not matter and you only need to de - `md_backcover` - This will look for a box back cover image. - `md_3dbox` - This will look for a 3D box image. - `md_fanart` - This will look for a fan art image. +* `pillarboxes` - type: BOOLEAN + - Whether to render black pillarboxes (and to a lesses extent letterboxes) for videos with aspect ratios where this is applicable. This is for instance useful for arcade game videos in vertical orientation. + - Default is `true` +* `scanlines` - type: BOOLEAN + - Whether to use a shader to render scanlines. This property is not compatible with `opacity` so enabling it will set the opacity to `1` (unless it was set to `0` in which case the entire video element is hidden). + - Default is `false` +* `delay` - type: FLOAT + - Delay in seconds before video will start playing. During the delay period the game image defined via the `imageMetadata` property will be displayed. If that property is not set, then the `delay` property will be ignored. + - Default is `0` * `scrollFadeIn` - type: BOOLEAN - If enabled, a short fade-in animation will be applied when scrolling through games in the gamelist view. This animation is only applied to images and not to actual videos, so if no image metadata has been defined then this property has no effect. For this to work correctly the `delay` property also needs to be set. - Default is `false` +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` * `visible` - type: BOOLEAN - - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. @@ -842,8 +875,15 @@ The order in which you define properties does not matter and you only need to de #### animation -Lottie (vector graphics) animation. +Lottie (vector graphics) animation. Note that these animations take a lot of memory and CPU resources if scaled up to large sizes so it's adviced to not add too many of them to the same view and to not make them too large. +Supported views: +* `gamelist` + +Instances per view: +* `unlimited` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - If only one axis is specified (and the other is zero), the other will be automatically calculated in accordance with the animation's aspect ratio. Note that this is sometimes not entirely accurate as some animations contain invalid size information. @@ -860,16 +900,20 @@ Lottie (vector graphics) animation. - Path to the animation file. Only the .json extension is supported. * `speed` - type: FLOAT. - The relative speed at which to play the animation. - - Minimum value is `0.2` and maximum value is `3.0` - - Default is `1.0` + - Minimum value is `0.2` and maximum value is `3` + - Default is `1` * `direction` - type: STRING - The direction that the animation should be played. Valid values are `normal` (forwards), `reverse` (backwards), `alternate` (bouncing forwards/backwards) and `alternateReverse` (bouncing backwards/forwards, i.e. starting with playing backwards). - Default is `normal` * `keepAspectRatio` - type: BOOLEAN. - If true, aspect ratio will be preserved. If false, animation will stretch to the defined size. Note that setting to `false` is incompatible with only defining one of the axes for the `size` element. - Default is `true` +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` * `visible` - type: BOOLEAN - - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. @@ -877,8 +921,15 @@ Lottie (vector graphics) animation. #### badges -It's strongly recommended to use the same image dimensions for all badges as varying aspect ratios will lead to alignment issues. For the controller images it's recommended to keep to the square canvas size used by the default bundled graphics as otherwise sizing and placement will be inconsistent (unless all controller graphic files are customized of course). +Displays graphical symbols representing a number of metadata fields for the currently selected game. It's strongly recommended to use the same image dimensions for all badges as varying aspect ratios will lead to alignment issues. For the controller images it's recommended to keep to the square canvas size used by the default bundled graphics as otherwise sizing and placement will be inconsistent (unless all controller graphic files are customized of course). +Supported views: +* `gamelist` + +Instances per view: +* `unlimited` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - Possible combinations: @@ -925,14 +976,18 @@ It's strongly recommended to use the same image dimensions for all badges as var * `controllerSize` - type: FLOAT - The size of the controller icon relative to the parent `controller` badge. - Setting the value to `1.0` sizes the controller icon to the same width as the parent badge. The image aspect ratio is always maintained. - - Minimum value is `0.1` and maximum value is `2.0` + - Minimum value is `0.1` and maximum value is `2` * `customBadgeIcon` - type: PATH - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are the ones listed above. * `customControllerIcon` - type: PATH - A controller icon override. Specify the controller type in the attribute `controller`. These are the available types: - `gamepad_generic`, `gamepad_nintendo_nes`, `gamepad_nintendo_snes`, `gamepad_nintendo_64`, `gamepad_playstation`, `gamepad_sega_md_3_buttons`, `gamepad_sega_md_6_buttons`, `gamepad_xbox`, `joystick_generic`, `joystick_arcade_no_buttons`, `joystick_arcade_1_button`, `joystick_arcade_2_buttons`, `joystick_arcade_3_buttons`, `joystick_arcade_4_buttons`, `joystick_arcade_5_buttons`, `joystick_arcade_6_buttons`, `keyboard_generic`, `keyboard_and_mouse_generic`, `mouse_generic`, `mouse_amiga`, `lightgun_generic`, `lightgun_nintendo`, `steering_wheel_generic`, `flight_stick_generic`, `spinner_generic`, `trackball_generic`, `wii_remote_nintendo`, `wii_remote_and_nunchuk_nintendo`, `joycon_left_or_right_nintendo`, `joycon_pair_nintendo`, `unknown` +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` * `visible` - type: BOOLEAN - - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. @@ -940,6 +995,16 @@ It's strongly recommended to use the same image dimensions for all badges as var #### text +Displays text. This can be literal strings or values based on game metadata or system variables, as described below. For the `gamelist` view it's also possible to place the text inside a scrollable container which is for example useful for longer texts like the game descriptions. + +Supported views: +* `system` (no container support for this view) +* `gamelist` + +Instances per view: +* `unlimited` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - Possible combinations: @@ -983,7 +1048,7 @@ It's strongly recommended to use the same image dimensions for all badges as var * `container` - type: BOOLEAN - Whether the text should be placed inside a scrollable container. Only available for the gamelist view. * `containerScrollSpeed` - type: FLOAT - - A base speed is automatically calculated based on container and font sizes, so this property applies relative to the auto-calculated value. + - A base speed is automatically calculated based on the container and font sizes, so this property applies relative to the auto-calculated value. - Minimum value is `0.1` and maximum value is `10` - Default is `1` * `containerStartDelay` - type: FLOAT @@ -1014,14 +1079,28 @@ It's strongly recommended to use the same image dimensions for all badges as var * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). - Default is `1.5` +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` * `visible` - type: BOOLEAN - - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. This property will be ignored for elements defined for the `gamelist` view where a `metadata` property has been set. That is so because the "Hide metadata fields" functionality will hide and unhide metadata text fields as needed. + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. - Default is `40` #### datetime + +Displays a date and time as a text string. The format is ISO 8601 (YYYY-MM-DD) by default, but this can be changed using the `format` property. The text _unknown_ will be shown by default if there is no time stamp available. If the property `displayRelative` has been set, the text will be shown as _never_ in case of no time stamp. + +Supported views: +* `gamelist` + +Instances per view: +* `unlimited` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - Possible combinations: @@ -1041,7 +1120,7 @@ It's strongly recommended to use the same image dimensions for all badges as var - This displays the metadata values that are available for the game. If an invalid metadata field is defined, the text "unknown" will be printed. - Possible values: - `md_releasedate` - The release date of the game. - - `md_lastplayed` - The time the game was last played. This will be displayed as a value relative to the current date and time. + - `md_lastplayed` - The time the game was last played. This will be displayed as a value relative to the current date and time by default, but can be overridden using the `displayRelative` property. * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT @@ -1062,7 +1141,8 @@ It's strongly recommended to use the same image dimensions for all badges as var * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). - Default is `1.5` -* `format` - type: STRING. Specifies format for rendering datetime. +* `format` - type: STRING + - Specifies the date and time format. Has no effect if `displayRelative` has been set to true. - %Y: The year, including the century (1900) - %m: The month number [01,12] - %d: The day of the month [01,31] @@ -1070,9 +1150,13 @@ It's strongly recommended to use the same image dimensions for all badges as var - %M: The minute [00,59] - %S: The second [00,59] * `displayRelative` - type: BOOLEAN. - - Renders the datetime as a a relative string (ex: 'x days ago'). + - Renders the datetime as a relative string (e.g. 'x days ago'). +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` * `visible` - type: BOOLEAN - - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. This property will be ignored for elements defined for the `gamelist` view. That is so because the "Hide metadata fields" functionality will hide and unhide metadata text fields as needed. + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. @@ -1082,6 +1166,13 @@ It's strongly recommended to use the same image dimensions for all badges as var Displays the game count (all games as well as favorites), any applied filters, and a folder icon if a folder has been entered. If this text is left aligned or center aligned, the folder icon will be placed to the right of the other information, and if it's right aligned, the folder icon will be placed to the left. +Supported views: +* `gamelist` + +Instances per view: +* `unlimited` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - Possible combinations: @@ -1097,12 +1188,12 @@ Displays the game count (all games as well as favorites), any applied filters, a * `rotationOrigin` - type: NORMALIZED_PAIR - Point around which the element will be rotated. - Default is `0.5 0.5` -* `backgroundColor` - type: COLOR * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). * `color` - type: COLOR +* `backgroundColor` - type: COLOR * `horizontalAlignment` - type: STRING - Controls alignment on the X axis. - Valid values are `left`, `center` or `right` @@ -1111,8 +1202,12 @@ Displays the game count (all games as well as favorites), any applied filters, a - Controls alignment on the Y axis. - Valid values are `top`, `center` or `bottom` - Default is `center` +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` * `visible` - type: BOOLEAN - - If true, element will be rendered, otherwise rendering will be skipped. Can be used to hide elements from a particular view. + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. @@ -1120,6 +1215,15 @@ Displays the game count (all games as well as favorites), any applied filters, a #### rating +Displays a graphical representation of the game rating, from 0 to 5. + +Supported views: +* `gamelist` + +Instances per view: +* `unlimited` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - Only one value is actually used. The other value should be zero. (e.g. specify width OR height, but not both. This is done to maintain the aspect ratio.) @@ -1138,12 +1242,28 @@ Displays the game count (all games as well as favorites), any applied filters, a - Path to the "filled" rating image. Image must be square (width equals height). * `unfilledPath` - type: PATH - Path to the "unfilled" rating image. Image must be square (width equals height). +* `opacity` - type: FLOAT + - Controls the level of transparency. If set to `0` the element will be disabled. + - Minimum value is `0` and maximum value is `1` + - Default is `1` +* `visible` - type: BOOLEAN + - If set to false, the element will be disabled. This is equivalent to setting `opacity` to `0` + - Default is `true` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. - Default is `45` #### carousel +Displays a carousel for selecting game systems. + +Supported views: +* `system` + +Instances per view: +* `single` + +Properties: * `type` - type: STRING - Sets the carousel type and scroll direction. - Valid values are `horizontal`, `vertical`, `horizontal_wheel` or `vertical_wheel`. @@ -1159,7 +1279,7 @@ Displays the game count (all games as well as favorites), any applied filters, a - Default is `FFFFFFD8` * `colorEnd` - type: COLOR - Setting this to something other than what is defined for `color` creates a color gradient on the background panel. - - Default is `0xFFFFFFD8` + - Default is `FFFFFFD8` * `gradientType` - type: STRING - The direction to apply the color gradient if both `color` and `colorEnd` have been defined. - Possible values are `horizontal` or `vertical` @@ -1219,22 +1339,37 @@ Displays the game count (all games as well as favorites), any applied filters, a #### textlist -This is a list containing rows of text which can be navigated using the keyboard or a controller. +A text list for navigating and selecting games. +Supported views: +* `gamelist` + +Instances per view: +* `single` + +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. -* `selectorColor` - type: COLOR - - Color of the "selector bar." -* `selectorImagePath` - type: PATH - - Path to image to render in place of "selector bar." -* `selectorImageTile` - type: BOOLEAN - - If true, the selector image will be tiled instead of stretched to fit its size. * `selectorHeight` - type: FLOAT - Height of the "selector bar". * `selectorOffsetY` - type: FLOAT - Allows moving of the "selector bar" up or down from its computed position. Useful for fine tuning the position of the "selector bar" relative to the text. +* `selectorColor` - type: COLOR + - Color of the selector bar. + - Default is `000000FF` +* `selectorColorEnd` - type: COLOR + - Setting this to something other than what is defined for `selectorColor` creates a color gradient. + - Default is `000000FF` +* `selectorGradientType` - type: STRING + - The direction to apply the color gradient if both `selectorColor` and `selectorColorEnd` have been defined. + - Possible values are `horizontal` or `vertical` + - Default is `horizontal` +* `selectorImagePath` - type: PATH + - Path to image to render in place of "selector bar." +* `selectorImageTile` - type: BOOLEAN + - If true, the selector image will be tiled instead of stretched to fit its size. * `selectedColor` - type: COLOR - Color of the highlighted entry text. * `primaryColor` - type: COLOR @@ -1261,8 +1396,16 @@ This is a list containing rows of text which can be navigated using the keyboard #### helpsystem -The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Keep in mind the "default" settings (including position) are used whenever the user opens a menu. +The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Note that this element does not have a zIndex value, instead it's always rendered on top of all other elements. +Supported views: +* `system` +* `gamelist` + +Instances per view: +* `single` + +Properties: * `pos` - type: NORMALIZED_PAIR - Default is `0.012 0.9515` * `origin` - type: NORMALIZED_PAIR @@ -1326,6 +1469,8 @@ The help system is a special element that displays a context-sensitive list of a #### imagegrid +Deprecated. + * `pos` - type: NORMALIZED_PAIR. * `size` - type: NORMALIZED_PAIR. - The size of the grid. Take care the selected tile can go out of the grid size, so don't position the grid too close to another element or the screen border. @@ -1355,6 +1500,8 @@ The help system is a special element that displays a context-sensitive list of a #### gridtile +Deprecated. + * `size` - type: NORMALIZED_PAIR. - The size of the default gridtile is used to calculate how many tiles can fit in the imagegrid. If not explicitly set, the size of the selected gridtile is equal the size of the default gridtile * 1.2 * `padding` - type: NORMALIZED_PAIR. diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index b28407d28..6d96e5c5c 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -1247,13 +1247,13 @@ Submenu containing all the settings for the screensaver. These are described in This option will blur the background behind the menu slightly. Normally this can be left enabled, but if you have a really slow GPU, disabling this option may make the application feel a bit more responsive. -**Display pillarboxes for gamelist videos** +**Display pillarboxes for gamelist videos** _Only for legacy theme sets_ -With this option enabled, there are black pillarboxes (and to a lesser extent letterboxes) displayed around videos with non-standard aspect ratios. This will probably be most commonly used for vertical arcade shooters, or for game systems that has a screen in portrait orientation. For wider than normal videos, letterboxes are added, but this is quite rare compared to videos in portrait orientation. This option looks good with some theme sets such as rbsimple-DE, but on others it may be more visually pleasing to disable it. On less wide displays such as those in 4:3 aspect ratio this option should probably be disabled as it may otherwise add quite excessive letterboxing. +With this option enabled, there are black pillarboxes (and to a lesser extent letterboxes) displayed around videos with non-standard aspect ratios. This will probably be most commonly used for vertical arcade shooters, or for game systems that has a screen in portrait orientation. For wider than normal videos, letterboxes are added, but this is quite rare compared to videos in portrait orientation. This option looks good with some theme sets such as rbsimple-DE, but on others it may be more visually pleasing to disable it. On less wide displays such as those in 4:3 aspect ratio this option should probably be disabled as it may otherwise add quite excessive letterboxing. This option is only available for legacy theme sets as it's otherwise managed by the theme author. -**Render scanlines for gamelist videos** +**Render scanlines for gamelist videos** _Only for legacy theme sets_ -Whether to use a shader to render scanlines for videos in the gamelist view. The effect is usually pretty subtle as the video is normally renderered in a limited size in the GUI and the scanlines are sized relative to the video window size. +Whether to use a shader to render scanlines for videos in the gamelist view. The effect is usually pretty subtle as the video is normally renderered in a limited size in the GUI and the scanlines are sized relative to the video window size. This option is only available for legacy theme sets as it's otherwise managed by the theme author. **Sort folders on top of gamelists** From 9f04d7aad8e27064f636815b6f16ca2cfe72cd16 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 11:23:41 +0100 Subject: [PATCH 45/82] Fixed an issue where marquee images would not show for legacy themes. --- es-core/src/GuiComponent.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 09609ebcf..f83d36678 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -362,6 +362,8 @@ void GuiComponent::applyTheme(const std::shared_ptr& theme, if (properties & ThemeFlags::VISIBLE && elem->has("visible") && !elem->get("visible")) mThemeOpacity = 0.0f; + else + setVisible(true); } void GuiComponent::updateHelpPrompts() From 1bca9e185e0ec04808a4bb6764dbb37a224e0bc1 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 11:45:06 +0100 Subject: [PATCH 46/82] Changed to the more specific imageType and systemdata theme properties. Also made it possible to set the image interpolation method from the theme, and added clamping to the lineSpacing property. --- es-app/src/guis/GuiLaunchScreen.cpp | 3 +- es-app/src/views/GamelistLegacy.h | 78 +++++---- es-app/src/views/GamelistView.cpp | 168 +++++++++---------- es-app/src/views/SystemView.cpp | 26 +-- es-core/src/GuiComponent.h | 12 +- es-core/src/ThemeData.cpp | 7 +- es-core/src/components/CarouselComponent.cpp | 4 +- es-core/src/components/DateTimeComponent.cpp | 6 +- es-core/src/components/ImageComponent.cpp | 29 +++- es-core/src/components/ImageComponent.h | 5 +- es-core/src/components/TextComponent.cpp | 7 +- es-core/src/components/TextListComponent.h | 2 +- es-core/src/components/VideoComponent.cpp | 29 +++- es-core/src/components/VideoComponent.h | 2 +- 14 files changed, 213 insertions(+), 165 deletions(-) diff --git a/es-app/src/guis/GuiLaunchScreen.cpp b/es-app/src/guis/GuiLaunchScreen.cpp index a9e2c08bc..2ea9aa3c5 100644 --- a/es-app/src/guis/GuiLaunchScreen.cpp +++ b/es-app/src/guis/GuiLaunchScreen.cpp @@ -160,7 +160,8 @@ void GuiLaunchScreen::displayLaunchScreen(FileData* game) // width so that the sizes look somewhat consistent regardless of the aspect ratio // of the images. if (mImagePath != "") { - mMarquee->setImage(game->getMarqueePath(), false, true); + mMarquee->setLinearInterpolation(true); + mMarquee->setImage(game->getMarqueePath(), false); mMarquee->cropTransparentPadding(Renderer::getScreenWidth() * (0.25f * (1.778f / Renderer::getScreenAspectRatio())), mGrid->getRowHeight(3)); diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h index 2d13ef8c7..9b5deedd4 100644 --- a/es-app/src/views/GamelistLegacy.h +++ b/es-app/src/views/GamelistLegacy.h @@ -45,16 +45,16 @@ void GamelistView::legacyPopulateFields() // Thumbnails. mImageComponents.push_back(std::make_unique()); - mImageComponents.back()->setMetadataField("image_md_thumbnail"); + mImageComponents.back()->setThemeMetadata("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); mImageComponents.back()->setDefaultZIndex(25.0f); addChild(mImageComponents.back().get()); // Marquee. mImageComponents.push_back(std::make_unique()); - mImageComponents.back()->setMetadataField("image_md_marquee"); + mImageComponents.back()->setThemeMetadata("image_md_marquee"); + mImageComponents.back()->setLinearInterpolation(true); 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()); - mImageComponents.back()->setMetadataField("image_md_image"); + mImageComponents.back()->setThemeMetadata("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,7 +74,7 @@ void GamelistView::legacyPopulateFields() if (mViewStyle == ViewController::VIDEO) { // Video. mVideoComponents.push_back(std::make_unique()); - mVideoComponents.back()->setMetadataField("video_md_video"); + mVideoComponents.back()->setThemeMetadata("video_md_video"); mVideoComponents.back()->setOrigin(0.5f, 0.5f); mVideoComponents.back()->setPosition(mSize.x * 0.25f, mList.getPosition().y + mSize.y * 0.2125f); @@ -91,80 +91,80 @@ void GamelistView::legacyPopulateFields() // Metadata labels + values. mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Rating: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_rating"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_rating"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Released: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_releasedate"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_releasedate"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Developer: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_developer"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_developer"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Publisher: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_publisher"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_publisher"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Genre: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_genre"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_genre"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Players: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_players"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_players"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Last played: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_lastplayed"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_lastplayed"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setText("Times played: ", false); - mTextComponents.back()->setMetadataField("text_md_lbl_playcount"); + mTextComponents.back()->setThemeMetadata("text_md_lbl_playcount"); addChild(mTextComponents.back().get()); mRatingComponents.push_back(std::make_unique()); - mRatingComponents.back()->setMetadataField("rating_md_rating"); + mRatingComponents.back()->setThemeMetadata("rating_md_rating"); mRatingComponents.back()->setDefaultZIndex(40.0f); addChild(mRatingComponents.back().get()); mDateTimeComponents.push_back(std::make_unique()); - mDateTimeComponents.back()->setMetadataField("datetime_md_releasedate"); + mDateTimeComponents.back()->setThemeMetadata("datetime_md_releasedate"); addChild(mDateTimeComponents.back().get()); mTextComponents.push_back(std::make_unique()); - mTextComponents.back()->setMetadataField("text_md_developer"); + mTextComponents.back()->setThemeMetadata("text_md_developer"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); - mTextComponents.back()->setMetadataField("text_md_publisher"); + mTextComponents.back()->setThemeMetadata("text_md_publisher"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); - mTextComponents.back()->setMetadataField("text_md_genre"); + mTextComponents.back()->setThemeMetadata("text_md_genre"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); - mTextComponents.back()->setMetadataField("text_md_players"); + mTextComponents.back()->setThemeMetadata("text_md_players"); addChild(mTextComponents.back().get()); mDateTimeComponents.push_back(std::make_unique()); - mDateTimeComponents.back()->setMetadataField("datetime_md_lastplayed"); + mDateTimeComponents.back()->setThemeMetadata("datetime_md_lastplayed"); mDateTimeComponents.back()->setDisplayRelative(true); addChild(mDateTimeComponents.back().get()); mTextComponents.push_back(std::make_unique()); - mTextComponents.back()->setMetadataField("text_md_playcount"); + mTextComponents.back()->setThemeMetadata("text_md_playcount"); addChild(mTextComponents.back().get()); mTextComponents.push_back(std::make_unique()); - mTextComponents.back()->setMetadataField("text_md_name"); + mTextComponents.back()->setThemeMetadata("text_md_name"); mTextComponents.back()->setPosition(mSize.x, mSize.y); mTextComponents.back()->setFont(Font::get(FONT_SIZE_MEDIUM)); mTextComponents.back()->setHorizontalAlignment(ALIGN_CENTER); @@ -174,7 +174,7 @@ void GamelistView::legacyPopulateFields() // Badges. mBadgeComponents.push_back(std::make_unique()); - mBadgeComponents.back()->setMetadataField("badges_md_badges"); + mBadgeComponents.back()->setThemeMetadata("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); @@ -183,7 +183,7 @@ void GamelistView::legacyPopulateFields() // Scrollable container (game description). mContainerComponents.push_back(std::make_unique()); - mContainerComponents.back()->setMetadataField("text_md_description"); + mContainerComponents.back()->setThemeMetadata("text_md_description"); mContainerComponents.back()->setSize(mSize.x * (0.50f - 2.0f * padding), mSize.y - mContainerComponents.back()->getPosition().y); mContainerComponents.back()->setAutoScroll(true); @@ -196,7 +196,7 @@ void GamelistView::legacyPopulateFields() mContainerComponents.back()->addChild(mTextComponents.back().get()); mGamelistInfoComponents.push_back(std::make_unique()); - mGamelistInfoComponents.back()->setMetadataField("text_gamelistInfo"); + mGamelistInfoComponents.back()->setThemeMetadata("text_gamelistInfo"); mGamelistInfoComponents.back()->setOrigin(0.5f, 0.5f); mGamelistInfoComponents.back()->setFont(Font::get(FONT_SIZE_SMALL)); mGamelistInfoComponents.back()->setDefaultZIndex(50.0f); @@ -229,19 +229,19 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr& theme) mList.applyTheme(theme, getName(), "textlist_gamelist", ALL); mImageComponents[LegacyImage::MD_THUMBNAIL]->applyTheme( - theme, getName(), mImageComponents[LegacyImage::MD_THUMBNAIL]->getMetadataField(), ALL); + theme, getName(), mImageComponents[LegacyImage::MD_THUMBNAIL]->getThemeMetadata(), ALL); 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( - theme, getName(), mImageComponents[LegacyImage::MD_IMAGE]->getMetadataField(), + theme, getName(), mImageComponents[LegacyImage::MD_IMAGE]->getThemeMetadata(), POSITION | ThemeFlags::SIZE | Z_INDEX | ROTATION | VISIBLE); } else if (mViewStyle == ViewController::VIDEO) { mVideoComponents.front()->applyTheme( - theme, getName(), mVideoComponents.front()->getMetadataField(), + theme, getName(), mVideoComponents.front()->getThemeMetadata(), POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX | ROTATION | VISIBLE); } @@ -249,31 +249,31 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr& theme) legacyInitMDValues(); mTextComponents[LegacyText::MD_NAME]->applyTheme( - theme, getName(), mTextComponents[LegacyText::MD_NAME]->getMetadataField(), ALL); + theme, getName(), mTextComponents[LegacyText::MD_NAME]->getThemeMetadata(), ALL); for (size_t i = 0; i < mBadgeComponents.size(); ++i) - mBadgeComponents[i]->applyTheme(theme, getName(), mBadgeComponents[i]->getMetadataField(), + mBadgeComponents[i]->applyTheme(theme, getName(), mBadgeComponents[i]->getThemeMetadata(), ALL); for (size_t i = 0; i < mRatingComponents.size(); ++i) - mRatingComponents[i]->applyTheme(theme, getName(), mRatingComponents[i]->getMetadataField(), + mRatingComponents[i]->applyTheme(theme, getName(), mRatingComponents[i]->getThemeMetadata(), ALL); mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->applyTheme( - theme, getName(), mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->getMetadataField(), + theme, getName(), mDateTimeComponents[LegacyDateTime::MD_RELEASEDATE]->getThemeMetadata(), ALL); mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->applyTheme( - theme, getName(), mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->getMetadataField(), + theme, getName(), mDateTimeComponents[LegacyDateTime::MD_LASTPLAYED]->getThemeMetadata(), ALL); for (size_t i = LegacyText::MD_LBL_RATING; i < LegacyText::MD_NAME; ++i) { - mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getMetadataField(), + mTextComponents[i]->applyTheme(theme, getName(), mTextComponents[i]->getThemeMetadata(), ALL ^ ThemeFlags::TEXT); } for (auto& container : mContainerComponents) { - container->applyTheme(theme, getName(), container->getMetadataField(), + container->applyTheme(theme, getName(), container->getThemeMetadata(), POSITION | ThemeFlags::SIZE | Z_INDEX | VISIBLE); } @@ -284,7 +284,7 @@ void GamelistView::legacyOnThemeChanged(const std::shared_ptr& theme) ALL ^ (POSITION | ThemeFlags::SIZE | ThemeFlags::ORIGIN | TEXT | ROTATION)); for (auto& gamelistInfo : mGamelistInfoComponents) - gamelistInfo->applyTheme(theme, getName(), gamelistInfo->getMetadataField(), + gamelistInfo->applyTheme(theme, getName(), gamelistInfo->getThemeMetadata(), ALL ^ ThemeFlags::TEXT); // If there is no position defined in the theme for gamelistInfo, then hide it. @@ -379,8 +379,7 @@ void GamelistView::legacyUpdateInfoPanel() if (mRandomGame) { mImageComponents[LegacyImage::MD_THUMBNAIL]->setImage( mRandomGame->getThumbnailPath()); - mImageComponents[LegacyImage::MD_MARQUEE]->setImage(mRandomGame->getMarqueePath(), - false, true); + mImageComponents[LegacyImage::MD_MARQUEE]->setImage(mRandomGame->getMarqueePath()); if (mViewStyle == ViewController::VIDEO) { mVideoComponents.front()->setImage(mRandomGame->getImagePath()); // Always stop the video before setting a new video as it will otherwise @@ -410,8 +409,7 @@ void GamelistView::legacyUpdateInfoPanel() } else { mImageComponents[LegacyImage::MD_THUMBNAIL]->setImage(file->getThumbnailPath()); - mImageComponents[LegacyImage::MD_MARQUEE]->setImage(file->getMarqueePath(), false, - true); + mImageComponents[LegacyImage::MD_MARQUEE]->setImage(file->getMarqueePath()); if (mViewStyle == ViewController::VIDEO) { mVideoComponents.front()->setImage(file->getImagePath()); mVideoComponents.front()->onHide(); diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index 5d00fb1e6..c52a34655 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -107,7 +107,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) mImageComponents.push_back(std::make_unique()); mImageComponents.back()->setDefaultZIndex(30.0f); mImageComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); - if (mImageComponents.back()->getMetadataField() != "") + if (mImageComponents.back()->getThemeImageType() != "") mImageComponents.back()->setScrollHide(true); addChild(mImageComponents.back().get()); } @@ -116,7 +116,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) mVideoComponents.back()->setDefaultZIndex(30.0f); addChild(mVideoComponents.back().get()); mVideoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); - if (mVideoComponents.back()->getMetadataField() != "") + if (mVideoComponents.back()->getThemeImageType() != "") mVideoComponents.back()->setScrollHide(true); } else if (element.second.type == "animation") { @@ -155,7 +155,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) mTextComponents.push_back(std::make_unique()); mTextComponents.back()->setDefaultZIndex(40.0f); mTextComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); - if (mTextComponents.back()->getMetadataField() != "") + if (mTextComponents.back()->getThemeMetadata() != "") mTextComponents.back()->setScrollHide(true); addChild(mTextComponents.back().get()); } @@ -164,7 +164,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) mDateTimeComponents.push_back(std::make_unique()); mDateTimeComponents.back()->setDefaultZIndex(40.0f); mDateTimeComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); - if (mDateTimeComponents.back()->getMetadataField() != "") + if (mDateTimeComponents.back()->getThemeMetadata() != "") mDateTimeComponents.back()->setScrollHide(true); addChild(mDateTimeComponents.back().get()); } @@ -342,7 +342,7 @@ void GamelistView::updateInfoPanel() if (hideMetaDataFields) { for (auto& text : mTextComponents) { - if (text->getMetadataField() != "") + if (text->getThemeMetadata() != "") text->setVisible(false); } for (auto& date : mDateTimeComponents) @@ -352,13 +352,13 @@ void GamelistView::updateInfoPanel() for (auto& rating : mRatingComponents) rating->setVisible(false); for (auto& cText : mContainerTextComponents) { - if (cText->getMetadataField() != "md_description") + if (cText->getThemeMetadata() != "description") cText->setVisible(false); } } else { for (auto& text : mTextComponents) { - if (text->getMetadataField() != "") + if (text->getThemeMetadata() != "") text->setVisible(true); } for (auto& date : mDateTimeComponents) @@ -368,7 +368,7 @@ void GamelistView::updateInfoPanel() for (auto& rating : mRatingComponents) rating->setVisible(true); for (auto& cText : mContainerTextComponents) { - if (cText->getMetadataField() != "md_description") + if (cText->getThemeMetadata() != "description") cText->setVisible(true); } } @@ -388,48 +388,48 @@ void GamelistView::updateInfoPanel() file->getSystem()); if (mRandomGame) { for (auto& image : mImageComponents) { - if (image->getMetadataField() == "md_image") + if (image->getThemeImageType() == "image") image->setImage(mRandomGame->getImagePath()); - else if (image->getMetadataField() == "md_miximage") + else if (image->getThemeImageType() == "miximage") image->setImage(mRandomGame->getMiximagePath()); - else if (image->getMetadataField() == "md_marquee") - image->setImage(mRandomGame->getMarqueePath(), false, true); - else if (image->getMetadataField() == "md_screenshot") + else if (image->getThemeImageType() == "marquee") + image->setImage(mRandomGame->getMarqueePath()); + else if (image->getThemeImageType() == "screenshot") image->setImage(mRandomGame->getScreenshotPath()); - else if (image->getMetadataField() == "md_titlescreen") + else if (image->getThemeImageType() == "titlescreen") image->setImage(mRandomGame->getTitleScreenPath()); - else if (image->getMetadataField() == "md_cover") + else if (image->getThemeImageType() == "cover") image->setImage(mRandomGame->getCoverPath()); - else if (image->getMetadataField() == "md_backcover") + else if (image->getThemeImageType() == "backcover") image->setImage(mRandomGame->getBackCoverPath()); - else if (image->getMetadataField() == "md_3dbox") + else if (image->getThemeImageType() == "3dbox") image->setImage(mRandomGame->get3DBoxPath()); - else if (image->getMetadataField() == "md_fanart") + else if (image->getThemeImageType() == "fanart") image->setImage(mRandomGame->getFanArtPath()); - else if (image->getMetadataField() == "md_thumbnail") + else if (image->getThemeImageType() == "thumbnail") image->setImage(mRandomGame->getThumbnailPath()); } for (auto& video : mVideoComponents) { - if (video->getMetadataField() == "md_image") + if (video->getThemeImageType() == "image") video->setImage(mRandomGame->getImagePath()); - else if (video->getMetadataField() == "md_miximage") + else if (video->getThemeImageType() == "miximage") video->setImage(mRandomGame->getMiximagePath()); - else if (video->getMetadataField() == "md_marquee") - video->setImage(mRandomGame->getMarqueePath(), false, true); - else if (video->getMetadataField() == "md_screenshot") + else if (video->getThemeImageType() == "marquee") + video->setImage(mRandomGame->getMarqueePath()); + else if (video->getThemeImageType() == "screenshot") video->setImage(mRandomGame->getScreenshotPath()); - else if (video->getMetadataField() == "md_titlescreen") + else if (video->getThemeImageType() == "titlescreen") video->setImage(mRandomGame->getTitleScreenPath()); - else if (video->getMetadataField() == "md_cover") + else if (video->getThemeImageType() == "cover") video->setImage(mRandomGame->getCoverPath()); - else if (video->getMetadataField() == "md_backcover") + else if (video->getThemeImageType() == "backcover") video->setImage(mRandomGame->getBackCoverPath()); - else if (video->getMetadataField() == "md_3dbox") + else if (video->getThemeImageType() == "3dbox") video->setImage(mRandomGame->get3DBoxPath()); - else if (video->getMetadataField() == "md_fanart") + else if (video->getThemeImageType() == "fanart") video->setImage(mRandomGame->getFanArtPath()); - else if (video->getMetadataField() == "md_thumbnail") + else if (video->getThemeImageType() == "thumbnail") video->setImage(mRandomGame->getThumbnailPath()); // Always stop the video before setting a new video as it will otherwise @@ -447,7 +447,7 @@ void GamelistView::updateInfoPanel() } else { for (auto& image : mImageComponents) { - if (image->getMetadataField() != "") + if (image->getThemeImageType() != "") image->setImage(""); } @@ -466,48 +466,48 @@ void GamelistView::updateInfoPanel() } else { for (auto& image : mImageComponents) { - if (image->getMetadataField() == "md_image") + if (image->getThemeImageType() == "image") image->setImage(file->getImagePath()); - else if (image->getMetadataField() == "md_miximage") + else if (image->getThemeImageType() == "miximage") image->setImage(file->getMiximagePath()); - else if (image->getMetadataField() == "md_marquee") - image->setImage(file->getMarqueePath(), false, true); - else if (image->getMetadataField() == "md_screenshot") + else if (image->getThemeImageType() == "marquee") + image->setImage(file->getMarqueePath()); + else if (image->getThemeImageType() == "screenshot") image->setImage(file->getScreenshotPath()); - else if (image->getMetadataField() == "md_titlescreen") + else if (image->getThemeImageType() == "titlescreen") image->setImage(file->getTitleScreenPath()); - else if (image->getMetadataField() == "md_cover") + else if (image->getThemeImageType() == "cover") image->setImage(file->getCoverPath()); - else if (image->getMetadataField() == "md_backcover") + else if (image->getThemeImageType() == "backcover") image->setImage(file->getBackCoverPath()); - else if (image->getMetadataField() == "md_3dbox") + else if (image->getThemeImageType() == "3dbox") image->setImage(file->get3DBoxPath()); - else if (image->getMetadataField() == "md_fanart") + else if (image->getThemeImageType() == "fanart") image->setImage(file->getFanArtPath()); - else if (image->getMetadataField() == "md_thumbnail") + else if (image->getThemeImageType() == "thumbnail") image->setImage(file->getThumbnailPath()); } for (auto& video : mVideoComponents) { - if (video->getMetadataField() == "md_image") + if (video->getThemeImageType() == "image") video->setImage(file->getImagePath()); - else if (video->getMetadataField() == "md_miximage") + else if (video->getThemeImageType() == "miximage") video->setImage(file->getMiximagePath()); - else if (video->getMetadataField() == "md_marquee") - video->setImage(file->getMarqueePath(), false, true); - else if (video->getMetadataField() == "md_screenshot") + else if (video->getThemeImageType() == "marquee") + video->setImage(file->getMarqueePath()); + else if (video->getThemeImageType() == "screenshot") video->setImage(file->getScreenshotPath()); - else if (video->getMetadataField() == "md_titlescreen") + else if (video->getThemeImageType() == "titlescreen") video->setImage(file->getTitleScreenPath()); - else if (video->getMetadataField() == "md_cover") + else if (video->getThemeImageType() == "cover") video->setImage(file->getCoverPath()); - else if (video->getMetadataField() == "md_backcover") + else if (video->getThemeImageType() == "backcover") video->setImage(file->getBackCoverPath()); - else if (video->getMetadataField() == "md_3dbox") + else if (video->getThemeImageType() == "3dbox") video->setImage(file->get3DBoxPath()); - else if (video->getMetadataField() == "md_fanart") + else if (video->getThemeImageType() == "fanart") video->setImage(file->getFanArtPath()); - else if (video->getMetadataField() == "md_thumbnail") + else if (video->getThemeImageType() == "thumbnail") video->setImage(file->getThumbnailPath()); video->onHide(); @@ -609,23 +609,23 @@ void GamelistView::updateInfoPanel() } for (auto& text : mTextComponents) { - if (text->getMetadataField() == "md_name") + if (text->getThemeMetadata() == "name") text->setText(file->metadata.get("name")); } if (file->getType() == GAME) { if (!hideMetaDataFields) { for (auto& date : mDateTimeComponents) { - if (date->getMetadataField() == "md_lastplayed") + if (date->getThemeMetadata() == "lastplayed") date->setValue(file->metadata.get("lastplayed")); - else if (date->getMetadataField() == "md_playcount") + else if (date->getThemeMetadata() == "playcount") date->setValue(file->metadata.get("playcount")); } } else if (file->getType() == FOLDER) { if (!hideMetaDataFields) { for (auto& date : mDateTimeComponents) { - if (date->getMetadataField() == "md_lastplayed") { + if (date->getThemeMetadata() == "lastplayed") { date->setValue(file->metadata.get("lastplayed")); date->setVisible(false); date->setVisible(false); @@ -638,46 +638,46 @@ void GamelistView::updateInfoPanel() std::string metadata; auto getMetadataValue = [&file, &metadata]() -> std::string { - if (metadata == "md_name") + if (metadata == "name") return file->metadata.get("name"); - else if (metadata == "md_description") + else if (metadata == "description") return file->metadata.get("desc"); - else if (metadata == "md_developer") + else if (metadata == "developer") return file->metadata.get("developer"); - else if (metadata == "md_publisher") + else if (metadata == "publisher") return file->metadata.get("publisher"); - else if (metadata == "md_genre") + else if (metadata == "genre") return file->metadata.get("genre"); - else if (metadata == "md_players") + else if (metadata == "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") + else if (metadata == "favorite") + return file->metadata.get("favorite") == "true" ? "yes" : "no"; + else if (metadata == "completed") + return file->metadata.get("completed") == "true" ? "yes" : "no"; + else if (metadata == "kidgame") + return file->metadata.get("kidgame") == "true" ? "yes" : "no"; + else if (metadata == "broken") + return file->metadata.get("broken") == "true" ? "yes" : "no"; + else if (metadata == "playcount") return file->metadata.get("playcount"); - else if (metadata == "md_altemulator") + else if (metadata == "altemulator") return file->metadata.get("altemulator"); else return metadata; }; for (auto& text : mContainerTextComponents) { - metadata = text->getMetadataField(); + metadata = text->getThemeMetadata(); if (metadata == "") continue; - if (metadata == "md_rating") { + if (metadata == "rating") { text->setValue(mRatingComponents.front()->getRatingValue()); continue; } - else if (metadata == "md_controller") { - std::string controller = - BadgeComponent::getDisplayName(file->metadata.get("controller")); + else if (metadata == "controller") { + std::string controller { + BadgeComponent::getDisplayName(file->metadata.get("controller"))}; text->setValue(controller == "unknown" ? "" : controller); continue; } @@ -686,15 +686,15 @@ void GamelistView::updateInfoPanel() } for (auto& text : mTextComponents) { - metadata = text->getMetadataField(); + metadata = text->getThemeMetadata(); if (metadata == "") continue; - if (metadata == "md_rating") { + if (metadata == "rating") { text->setValue(mRatingComponents.front()->getRatingValue()); continue; } - else if (metadata == "md_controller") { + else if (metadata == "controller") { std::string controller = BadgeComponent::getDisplayName(file->metadata.get("controller")); text->setValue(controller == "unknown" ? "" : controller); @@ -705,14 +705,14 @@ void GamelistView::updateInfoPanel() } for (auto& date : mDateTimeComponents) { - std::string metadata = date->getMetadataField(); + std::string metadata = date->getThemeMetadata(); if (metadata == "") continue; - if (metadata == "md_releasedate") { + if (metadata == "releasedate") { date->setValue(file->metadata.get("releasedate")); } - else if (metadata == "md_lastplayed") { + else if (metadata == "lastplayed") { date->setValue(file->metadata.get("lastplayed")); } else { diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 12e2b7b54..46e51e2de 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -348,15 +348,15 @@ void SystemView::populate() elements.imageComponents.back()->setDefaultZIndex(30.0f); elements.imageComponents.back()->applyTheme(theme, "system", element.first, ThemeFlags::ALL); - if (elements.imageComponents.back()->getMetadataField() != "") + if (elements.imageComponents.back()->getThemeImageType() != "") elements.imageComponents.back()->setScrollHide(true); elements.children.emplace_back(elements.imageComponents.back().get()); } else if (element.second.type == "text") { - if (element.second.has("metadata") && - element.second.get("metadata").substr(0, 12) == - "sy_gamecount") { - if (element.second.has("metadata")) { + if (element.second.has("systemdata") && + element.second.get("systemdata").substr(0, 9) == + "gamecount") { + if (element.second.has("systemdata")) { elements.gameCountComponents.emplace_back( std::make_unique()); elements.gameCountComponents.back()->setDefaultZIndex(40.0f); @@ -409,13 +409,13 @@ void SystemView::populate() for (auto& elements : mSystemElements) { for (auto& text : elements.textComponents) { - if (text->getMetadataField() != "") { - if (text->getMetadataField() == "sy_name") + if (text->getThemeSystemdata() != "") { + if (text->getThemeSystemdata() == "name") text->setValue(elements.name); - else if (text->getMetadataField() == "sy_fullname") + else if (text->getThemeSystemdata() == "fullname") text->setValue(elements.fullName); else - text->setValue(text->getMetadataField()); + text->setValue(text->getThemeSystemdata()); } } } @@ -470,20 +470,20 @@ void SystemView::updateGameCount() } else { for (auto& gameCount : mSystemElements[mCarousel->getCursor()].gameCountComponents) { - if (gameCount->getMetadataField() == "sy_gamecount") { + if (gameCount->getThemeSystemdata() == "gamecount") { gameCount->setValue(ss.str()); } - else if (gameCount->getMetadataField() == "sy_gamecount_games") { + else if (gameCount->getThemeSystemdata() == "gamecount_games") { if (games) gameCount->setValue(ssGames.str()); else gameCount->setValue(ss.str()); } - else if (gameCount->getMetadataField() == "sy_gamecount_favorites") { + else if (gameCount->getThemeSystemdata() == "gamecount_favorites") { gameCount->setValue(ssFavorites.str()); } else { - gameCount->setValue(gameCount->getMetadataField()); + gameCount->setValue(gameCount->getThemeSystemdata()); } } } diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index fb690b8e6..544c5d2d5 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -211,8 +211,12 @@ public: virtual bool getEnabled() { return mEnabled; } virtual void setEnabled(bool state) { mEnabled = state; } - std::string getMetadataField() { return mMetadataField; } - void setMetadataField(const std::string& text) { mMetadataField = text; } + const std::string getThemeSystemdata() { return mThemeSystemdata; } + void setThemeSystemdata(const std::string& text) { mThemeSystemdata = text; } + const std::string getThemeMetadata() { return mThemeMetadata; } + void setThemeMetadata(const std::string& text) { mThemeMetadata = text; } + const std::string getThemeImageType() { return mThemeImageType; } + void setThemeImageType(const std::string& text) { mThemeImageType = text; } virtual std::shared_ptr getFont() const { return nullptr; } @@ -275,7 +279,9 @@ protected: GuiComponent* mParent; std::vector mChildren; - std::string mMetadataField; + std::string mThemeSystemdata; + std::string mThemeMetadata; + std::string mThemeImageType; unsigned int mColor; float mSaturation; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 88397bfdc..2b4c83109 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -89,7 +89,8 @@ std::map> {"path", PATH}, {"default", PATH}, {"tile", BOOLEAN}, - {"metadata", STRING}, + {"imageType", STRING}, + {"interpolation", STRING}, {"color", COLOR}, {"colorEnd", COLOR}, {"gradientType", STRING}, @@ -107,7 +108,8 @@ std::map> {"path", PATH}, {"default", PATH}, {"defaultImage", PATH}, - {"imageMetadata", STRING}, + {"imageType", STRING}, + {"interpolation", STRING}, {"pillarboxes", BOOLEAN}, {"scanlines", BOOLEAN}, {"delay", FLOAT}, @@ -157,6 +159,7 @@ std::map> {"rotation", FLOAT}, {"rotationOrigin", NORMALIZED_PAIR}, {"text", STRING}, + {"systemdata", STRING}, {"metadata", STRING}, {"container", BOOLEAN}, {"containerScrollSpeed", FLOAT}, diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index e8a208a0e..c651799fb 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -71,7 +71,7 @@ void CarouselComponent::addEntry(const std::shared_ptr& theme, if (entry.data.logoPath != "" && ResourceManager::getInstance().fileExists(entry.data.logoPath)) { auto logo = std::make_shared(false, false); - logo->setImage(entry.data.logoPath, false, false); + logo->setImage(entry.data.logoPath); logo->setMaxSize(glm::round(mLogoSize * mLogoScale)); logo->applyTheme(theme, "system", "", ThemeFlags::ALL); logo->setRotateByTargetSize(true); @@ -80,7 +80,7 @@ void CarouselComponent::addEntry(const std::shared_ptr& theme, else if (entry.data.defaultLogoPath != "" && ResourceManager::getInstance().fileExists(entry.data.defaultLogoPath)) { auto defaultLogo = std::make_shared(false, false); - defaultLogo->setImage(entry.data.defaultLogoPath, false, false); + defaultLogo->setImage(entry.data.defaultLogoPath); defaultLogo->setMaxSize(glm::round(mLogoSize * mLogoScale)); defaultLogo->applyTheme(theme, "system", "", ThemeFlags::ALL); defaultLogo->setRotateByTargetSize(true); diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index 87e78d0f1..b86d7e601 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -173,9 +173,9 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, } if (properties & METADATA && elem->has("metadata")) - setMetadataField(elem->get("metadata")); + mThemeMetadata = elem->get("metadata"); - if (mMetadataField == "md_lastplayed") + if (mThemeMetadata == "lastplayed") setDisplayRelative(true); if (elem->has("displayRelative")) @@ -204,7 +204,7 @@ void DateTimeComponent::applyTheme(const std::shared_ptr& theme, setUppercase(elem->get("forceUppercase")); if (properties & LINE_SPACING && elem->has("lineSpacing")) - setLineSpacing(elem->get("lineSpacing")); + setLineSpacing(glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f)); setFont(Font::getFromTheme(elem, properties, mFont)); } diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 50d6ab2dd..cc2ced6c7 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -42,6 +42,7 @@ ImageComponent::ImageComponent(bool forceLoad, bool dynamic) , mForceLoad {forceLoad} , mDynamic {dynamic} , mRotateByTargetSize {false} + , mLinearInterpolation {false} , mTopLeftCrop {0.0f, 0.0f} , mBottomRightCrop {1.0f, 1.0f} { @@ -132,7 +133,7 @@ void ImageComponent::resize() onSizeChanged(); } -void ImageComponent::setImage(const std::string& path, bool tile, bool linearMagnify) +void ImageComponent::setImage(const std::string& path, bool tile) { // Always load bundled graphic resources statically, unless mForceLoad has been set. // This eliminates annoying texture pop-in problems that would otherwise occur. @@ -144,11 +145,11 @@ void ImageComponent::setImage(const std::string& path, bool tile, bool linearMag if (mDefaultPath.empty() || !ResourceManager::getInstance().fileExists(mDefaultPath)) mTexture.reset(); else - mTexture = - TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, linearMagnify); + mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic, + mLinearInterpolation); } else { - mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, linearMagnify); + mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, mLinearInterpolation); } resize(); @@ -497,6 +498,22 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, setMinSize(elem->get("minSize") * scale); } + if (elem->has("interpolation")) { + const std::string interpolation {elem->get("interpolation")}; + if (interpolation == "linear") { + mLinearInterpolation = true; + } + else if (interpolation == "nearest") { + mLinearInterpolation = false; + } + else { + mLinearInterpolation = false; + LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property " + " set to \"" + << interpolation << "\""; + } + } + if (elem->has("default")) setDefaultImage(elem->get("default")); @@ -505,8 +522,8 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, setImage(elem->get("path"), tile); } - if (properties & METADATA && elem->has("metadata")) - setMetadataField(elem->get("metadata")); + if (properties & METADATA && elem->has("imageType")) + mThemeImageType = elem->get("imageType"); if (properties & COLOR) { if (elem->has("color")) diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 9faabfac0..0a8c5c242 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -24,7 +24,7 @@ public: // Loads the image at the given filepath. Will tile if tile is true (retrieves texture // as tiling, creates vertices accordingly). - void setImage(const std::string& path, bool tile = false, bool linearMagnify = false); + void setImage(const std::string& path, bool tile = false); // Loads an image from memory. void setImage(const char* data, size_t length, bool tile = false); // Use an already existing texture. @@ -79,6 +79,8 @@ public: // Flag indicating if rotation should be based on target size vs. actual size. void setRotateByTargetSize(bool rotate) { mRotateByTargetSize = rotate; } + // Whether to use smooth texture magnification by utilizing linear interpolation. + void setLinearInterpolation(bool state) { mLinearInterpolation = state; } // Returns the size of the current texture, or (0, 0) if none is loaded. // May be different than drawn size (use getSize() for that). @@ -129,6 +131,7 @@ private: bool mForceLoad; bool mDynamic; bool mRotateByTargetSize; + bool mLinearInterpolation; glm::vec2 mTopLeftCrop; glm::vec2 mBottomRightCrop; diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index 77cc15679..eb2578172 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -427,8 +427,11 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, if (properties & TEXT && elem->has("text")) setText(elem->get("text")); + if (properties & METADATA && elem->has("systemdata")) + mThemeSystemdata = elem->get("systemdata"); + if (properties & METADATA && elem->has("metadata")) - setMetadataField(elem->get("metadata")); + mThemeMetadata = elem->get("metadata"); if (properties & LETTER_CASE && elem->has("letterCase")) { std::string letterCase {elem->get("letterCase")}; @@ -453,7 +456,7 @@ void TextComponent::applyTheme(const std::shared_ptr& theme, setUppercase(elem->get("forceUppercase")); if (properties & LINE_SPACING && elem->has("lineSpacing")) - setLineSpacing(elem->get("lineSpacing")); + setLineSpacing(glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f)); setFont(Font::getFromTheme(elem, properties, mFont)); } diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index c4052de83..74b4d6636 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -561,7 +561,7 @@ void TextListComponent::applyTheme(const std::shared_ptr& theme, if (properties & LINE_SPACING) { if (elem->has("lineSpacing")) - setLineSpacing(elem->get("lineSpacing")); + setLineSpacing(glm::clamp(elem->get("lineSpacing"), 0.5f, 3.0f)); if (elem->has("selectorHeight")) setSelectorHeight(elem->get("selectorHeight") * Renderer::getScreenHeight()); if (elem->has("selectorOffsetY")) { diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 6f5769f57..29c7d6768 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -44,7 +44,7 @@ VideoComponent::VideoComponent() // Setup the default configuration. mConfig.showSnapshotDelay = false; mConfig.showSnapshotNoVideo = false; - mConfig.startDelay = 0; + mConfig.startDelay = 1500; if (mWindow->getGuiStackSize() > 1) topWindow(false); @@ -78,13 +78,13 @@ bool VideoComponent::setVideo(std::string path) return false; } -void VideoComponent::setImage(const std::string& path, bool tile, bool linearMagnify) +void VideoComponent::setImage(const std::string& path, bool tile) { // Check that the image has changed. if (path == mStaticImagePath) return; - mStaticImage.setImage(path, tile, linearMagnify); + mStaticImage.setImage(path, tile); mStaticImagePath = path; } @@ -246,6 +246,22 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, mVideoAreaPos = elem->get("pos") * scale; } + if (elem->has("interpolation")) { + const std::string interpolation {elem->get("interpolation")}; + if (interpolation == "linear") { + mStaticImage.setLinearInterpolation(true); + } + else if (interpolation == "nearest") { + mStaticImage.setLinearInterpolation(false); + } + else { + mStaticImage.setLinearInterpolation(false); + LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property " + " set to \"" + << interpolation << "\""; + } + } + if (elem->has("default")) mConfig.defaultVideoPath = elem->get("default"); @@ -258,7 +274,8 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, mConfig.staticVideoPath = elem->get("path"); if ((properties & ThemeFlags::DELAY) && elem->has("delay")) - mConfig.startDelay = static_cast((elem->get("delay") * 1000.0f)); + mConfig.startDelay = + static_cast(glm::clamp(elem->get("delay"), 0.0f, 15.0f) * 1000.0f); if (!theme->isLegacyTheme()) mConfig.showSnapshotNoVideo = true; @@ -270,8 +287,8 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, else if (elem->has("showSnapshotDelay")) mConfig.showSnapshotDelay = elem->get("showSnapshotDelay"); - if (properties & METADATA && elem->has("imageMetadata")) - setMetadataField(elem->get("imageMetadata")); + if (properties & METADATA && elem->has("imageType")) + mThemeImageType = elem->get("imageType"); if (elem->has("pillarboxes")) mDrawPillarboxes = elem->get("pillarboxes"); diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index 4cde87992..89ad4b94f 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -40,7 +40,7 @@ public: // 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(const std::string& path, bool tile = false, bool linearMagnify = false); + void setImage(const std::string& path, bool tile = false); // Sets whether we're in media viewer mode. void setMediaViewerMode(bool isMediaViewer) { mMediaViewerMode = isMediaViewer; } // Sets whether we're in screensaver mode. From d1f4453e3b68f8d4821f107a3b9789b641988200 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 11:47:47 +0100 Subject: [PATCH 47/82] (rbsimple-DE) Updated for the newest theme engine changes. --- themes/rbsimple-DE/theme.xml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 1ba2575c6..278f503d8 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -37,7 +37,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.5 0.5 uppercase center - sy_gamecount + gamecount 50 @@ -177,7 +177,7 @@ based on: 'recalbox-multi' by the Recalbox community 1 4.5 7 - md_description + description uppercase ./core/fonts/Exo2-SemiBoldCondensed.otf 0.024 @@ -219,7 +219,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.815 0.2755 - md_releasedate + releasedate 0.83 0.3005 @@ -230,7 +230,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.815 0.3355 - md_developer + developer 0.83 0.3605 @@ -238,7 +238,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.815 0.3955 - md_publisher + publisher 0.83 0.4205 @@ -246,7 +246,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.815 0.4555 - md_genre + genre 0.83 0.4805 @@ -254,7 +254,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.815 0.5155 - md_players + players 0.83 0.5405 @@ -262,7 +262,7 @@ based on: 'recalbox-multi' by the Recalbox community 0.815 0.5755 - md_lastplayed + lastplayed 0.83 0.6005 @@ -332,7 +332,8 @@ based on: 'recalbox-multi' by the Recalbox community 0.5 0.5 0.63 0.45 0.360 0.424 - md_image + image + nearest true @@ -343,7 +344,8 @@ based on: 'recalbox-multi' by the Recalbox community ``` -An element is a particular visual component such as an image, an animation or a piece of text. It has a mandatory _name_ attribute which is used by ES-DE to track each element entry. By using this name attribute it's possible to split up the definition of an element to different locations. For example you may want to define the color properties separately from where the size and position are configured (see the example below). +An element is a particular visual component such as an image, an animation or a piece of text. It has a mandatory _name_ attribute which is used by ES-DE to track each element entry. By using this name attribute it's possible to split up the definition of an element to different locations. For example you may want to define the color properties separately from where the size and position are configured (see the example below). The name attribute can be set to any string value. This is the element structure: @@ -267,20 +265,21 @@ Here are some example uses of the `` functionality: - - md_image - 40 + + titlescreen + true + 42 - @@ -290,9 +289,10 @@ Here are some example uses of the `` functionality: - - md_image - 40 + + titlescreen + true + 42 @@ -334,7 +334,7 @@ Once the aspect ratios have been defined, they are applied to the theme configur - + 0.3 0.56 @@ -342,7 +342,7 @@ Once the aspect ratios have been defined, they are applied to the theme configur - + 0.42 0.31 @@ -353,7 +353,7 @@ Once the aspect ratios have been defined, they are applied to the theme configur - + 0.3 0.56 @@ -770,21 +770,25 @@ Properties: * `path` - type: PATH - Path to the image file. Most common extensions are supported (including .jpg, .png, and unanimated .gif). * `default` - type: PATH - - Path to a default image file. The default image will be displayed when the selected game does not have an image of the type defined by the `metadata` property. It's also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. + - Path to a default image file. The default image will be displayed when the selected game does not have an image of the type defined by the `imageType` property. It's also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. * `tile` - type: BOOLEAN - If true, the image will be tiled instead of stretched to fit its size. Useful for backgrounds. -* `metadata` - type: STRING - - This displays a game image of a certain media type. If no image of the requested type is found, the space will be left blank unless the `default` property has been set. If the metadata type is set to an invalid value, no image will be displayed regardless of whether the `default` property has been defined or not. +* `imageType` - type: STRING + - This displays a game image of a certain media type. If no image of the requested type is found, the space will be left blank unless the `default` property has been set. If the imageType property is set to an invalid value, no image will be displayed even if a `default` property has been defined. - Possible values: - - `md_image` - This will look for a miximage, but if that is not found screenshot is tried next, then title screen and finally box front cover. - - `md_miximage` - This will look for a miximage. - - `md_marquee` - This will look for a marquee image. - - `md_screenshot` - This will look for a screenshot image. - - `md_titlescreen` - This will look for a title screen image. - - `md_cover` - This will look for a box front cover image. - - `md_backcover` - This will look for a box back cover image. - - `md_3dbox` - This will look for a 3D box image. - - `md_fanart` - This will look for a fan art image. + - `image` - This will look for a `miximage`, and if that is not found `screenshot` is tried next, then `titlescreen` and finally `cover` + - `miximage` - This will look for a miximage. + - `marquee` - This will look for a marquee (wheel) image. + - `screenshot` - This will look for a screenshot image. + - `titlescreen` - This will look for a title screen image. + - `cover` - This will look for a box front cover image. + - `backcover` - This will look for a box back cover image. + - `3dbox` - This will look for a 3D box image. + - `fanart` - This will look for a fan art image. +* `interpolation` - type: STRING + - Interpolation method to use when scaling raster images. Nearest neighbor (`nearest`) preserves sharp pixels and linear filtering (`linear`) makes the image smoother. This property has no effect on scalable vector graphics (SVG) images. + - Valid values are `nearest` or `linear` + - Default is `nearest` * `color` - type: COLOR - Multiply each pixel's color by this color. For example, an all-white image with `FF0000` would become completely red. You can also control the transparency of an image with `FFFFFFAA` - keeping all the pixels their normal color and only affecting the alpha channel. * `colorEnd` - type: COLOR @@ -811,13 +815,13 @@ Properties: Plays a video and provides support for displaying a static image for a defined time period before starting the video player. Although an unlimited number of videos could in theory be defined per view it's strongly recommended to keep it at a single instance. Playing multiple videos simultaneously takes a lot of CPU resources and ES-DE currently has no audio mixing capabilities so the audio would not play correctly. -Supported views: +**Supported views:** * `gamelist` -Instances per view: -* `unlimited` (recommended to keep at a single instance) +**Instances per view:** +* `unlimited` (but recommended to keep at a single instance) -Properties: +**Properties:** * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - If only one axis is specified (and the other is zero), the other will be automatically calculated in accordance with the video's aspect ratio. @@ -835,21 +839,25 @@ Properties: * `path` - type: PATH - Path to a video file. Setting a value for this property will make the video static, i.e. it will only play this video regardless of whether there is a game video available or not. If the `default` property has also been set, it will be overridden as the `path` property takes precedence. * `default` - type: PATH - - Path to a default video file. The default video will be played (unless the `path` property has been set) when the selected game does not have a video. If an image type has been defined using `imageMetadata` that image will be searched for first and only if no such image could be found this `default` video will be shown. This property is also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. + - Path to a default video file. The default video will be played (unless the `path` property has been set) when the selected game does not have a video. If an image type has been defined using `imageType` that image will be searched for first and only if no such image could be found this `default` video will be shown. This property is also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. * `defaultImage` - type: PATH - This works exactly as the `default` property but it allows displaying an image instead of playing a video. -* `imageMetadata` - type: STRING - - This displays a game image of a certain media type. This occurs if there is no video found for the game and the `path` and `default` properties have not been set, or if a video start delay has been defined via the `delay` attribute. +* `imageType` - type: STRING + - This displays a game image of a certain media type. This occurs if there is no video found for the game and the `path` property has not been set, or if a video start delay has been defined via the `delay` attribute. - Possible values: - - `md_image` - This will look for a miximage, but if that is not found screenshot is tried next, then title screen and finally box front cover. - - `md_miximage` - This will look for a miximage. - - `md_marquee` - This will look for a marquee image. - - `md_screenshot` - This will look for a screenshot image. - - `md_titlescreen` - This will look for a title screen image. - - `md_cover` - This will look for a box front cover image. - - `md_backcover` - This will look for a box back cover image. - - `md_3dbox` - This will look for a 3D box image. - - `md_fanart` - This will look for a fan art image. + - `image` - This will look for a `miximage`, and if that is not found `screenshot` is tried next, then `titlescreen` and finally `cover` + - `miximage` - This will look for a miximage. + - `marquee` - This will look for a marquee (wheel) image. + - `screenshot` - This will look for a screenshot image. + - `titlescreen` - This will look for a title screen image. + - `cover` - This will look for a box front cover image. + - `backcover` - This will look for a box back cover image. + - `3dbox` - This will look for a 3D box image. + - `fanart` - This will look for a fan art image. +* `interpolation` - type: STRING + - Interpolation method to use when scaling raster images. Nearest neighbor (`nearest`) preserves sharp pixels and linear filtering (`linear`) makes the image smoother. Note that this property only affects the static image, not the video scaling. This property also has no effect on scalable vector graphics (SVG) images. + - Valid values are `nearest` or `linear` + - Default is `nearest` * `pillarboxes` - type: BOOLEAN - Whether to render black pillarboxes (and to a lesses extent letterboxes) for videos with aspect ratios where this is applicable. This is for instance useful for arcade game videos in vertical orientation. - Default is `true` @@ -857,8 +865,9 @@ Properties: - Whether to use a shader to render scanlines. This property is not compatible with `opacity` so enabling it will set the opacity to `1` (unless it was set to `0` in which case the entire video element is hidden). - Default is `false` * `delay` - type: FLOAT - - Delay in seconds before video will start playing. During the delay period the game image defined via the `imageMetadata` property will be displayed. If that property is not set, then the `delay` property will be ignored. - - Default is `0` + - Delay in seconds before video will start playing. During the delay period the game image defined via the `imageType` property will be displayed. If that property is not set, then the `delay` property will be ignored. + - Minimum value is `0` and maximum value is `15` + - Default is `1.5` * `scrollFadeIn` - type: BOOLEAN - If enabled, a short fade-in animation will be applied when scrolling through games in the gamelist view. This animation is only applied to images and not to actual videos, so if no image metadata has been defined then this property has no effect. For this to work correctly the `delay` property also needs to be set. - Default is `false` @@ -934,7 +943,7 @@ Properties: * `size` - type: NORMALIZED_PAIR - Possible combinations: - `w h` - Dimensions of the badges container. The badges will be scaled to fit within these dimensions. - - Minimum value per axis is `0.03` and maximum value per axis is `1.0` + - Minimum value per axis is `0.03` and maximum value per axis is `1` - Default is `0.15 0.20` * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. @@ -971,17 +980,48 @@ Properties: - `altemulator` - Will be shown when an alternative emulator is setup for the game. * `controllerPos` - type: NORMALIZED_PAIR - The position of the controller icon relative to the parent `controller` badge. - - Minimum value per axis is `-1.0` and maximum value per axis is `2.0` + - Minimum value per axis is `-1` and maximum value per axis is `2` - Default is `0.5 0.5` which centers the controller icon on the badge. * `controllerSize` - type: FLOAT - The size of the controller icon relative to the parent `controller` badge. - - Setting the value to `1.0` sizes the controller icon to the same width as the parent badge. The image aspect ratio is always maintained. + - Setting the value to `1` sizes the controller icon to the same width as the parent badge. The image aspect ratio is always maintained. - Minimum value is `0.1` and maximum value is `2` * `customBadgeIcon` - type: PATH - A badge icon override. Specify the badge type in the attribute `badge`. The available badges are the ones listed above. * `customControllerIcon` - type: PATH - - A controller icon override. Specify the controller type in the attribute `controller`. These are the available types: - - `gamepad_generic`, `gamepad_nintendo_nes`, `gamepad_nintendo_snes`, `gamepad_nintendo_64`, `gamepad_playstation`, `gamepad_sega_md_3_buttons`, `gamepad_sega_md_6_buttons`, `gamepad_xbox`, `joystick_generic`, `joystick_arcade_no_buttons`, `joystick_arcade_1_button`, `joystick_arcade_2_buttons`, `joystick_arcade_3_buttons`, `joystick_arcade_4_buttons`, `joystick_arcade_5_buttons`, `joystick_arcade_6_buttons`, `keyboard_generic`, `keyboard_and_mouse_generic`, `mouse_generic`, `mouse_amiga`, `lightgun_generic`, `lightgun_nintendo`, `steering_wheel_generic`, `flight_stick_generic`, `spinner_generic`, `trackball_generic`, `wii_remote_nintendo`, `wii_remote_and_nunchuk_nintendo`, `joycon_left_or_right_nintendo`, `joycon_pair_nintendo`, `unknown` + - A controller icon override. Specify the controller type in the attribute `controller`. + - These are the available types: + - `gamepad_generic`, + `gamepad_nintendo_nes`, + `gamepad_nintendo_snes`, + `gamepad_nintendo_64`, + `gamepad_playstation`, + `gamepad_sega_md_3_buttons`, + `gamepad_sega_md_6_buttons`, + `gamepad_xbox`, + `joystick_generic`, + `joystick_arcade_no_buttons`, + `joystick_arcade_1_button`, + `joystick_arcade_2_buttons`, + `joystick_arcade_3_buttons`, + `joystick_arcade_4_buttons`, + `joystick_arcade_5_buttons`, + `joystick_arcade_6_buttons`, + `keyboard_generic`, + `keyboard_and_mouse_generic`, + `mouse_generic`, + `mouse_amiga`, + `lightgun_generic`, + `lightgun_nintendo`, + `steering_wheel_generic`, + `flight_stick_generic`, + `spinner_generic`, + `trackball_generic`, + `wii_remote_nintendo`, + `wii_remote_and_nunchuk_nintendo`, + `joycon_left_or_right_nintendo`, + `joycon_pair_nintendo`, + `unknown` * `opacity` - type: FLOAT - Controls the level of transparency. If set to `0` the element will be disabled. - Minimum value is `0` and maximum value is `1` @@ -998,7 +1038,7 @@ Properties: Displays text. This can be literal strings or values based on game metadata or system variables, as described below. For the `gamelist` view it's also possible to place the text inside a scrollable container which is for example useful for longer texts like the game descriptions. Supported views: -* `system` (no container support for this view) +* `system` * `gamelist` Instances per view: @@ -1022,31 +1062,33 @@ Properties: - Default is `0.5 0.5` * `text` - type: STRING - A string literal to display. +* `systemdata` - type: STRING + - This translates to some system data including values defined in es_systems.xml as well as some statistics. If an invalid systemdata field is defined, it will be printed as a string literal. This property can only be used in the `system` view. + - Valid values: + - `name` - Short system name as defined in es_systems.xml. + - `fullname` - Full system name as defined in es_systems.xml. + - `gamecount` - Number of games available for the system. Number of favorites are printed inside brackets if applicable. + - `gamecount_games` - Number of games available for the system. Does not print the favorites count. + - `gamecount_favorites` - Number of favorite games for the system, may be blank if favorites are not applicable. * `metadata` - type: STRING - - This translates to the metadata values that are available for the game. If an invalid metadata field is defined, it will be printed as a string literal. - - Possible values for the system view: - - `sy_name` - Short system name as defined in es_systems.xml. - - `sy_fullname` - Full system name as defined in es_systems.xml. - - `sy_gamecount` - Number of games available for the system. Number of favorites are printed inside brackets if applicable. - - `sy_gamecount_games` - Number of games available for the system. Does not print the favorites count. - - `sy_gamecount_favorites` - Number of favorite games for the system, may be blank if favorites are not applicable. - - Possible values for the gamelist view: - - `md_name` - Game name. - - `md_description` - Game description. Should be combined with the `container` property in most cases. - - `md_rating` - The numerical representation of the game rating, for example `3` or `4.5`. - - `md_developer` - Developer. - - `md_publisher` - Publisher. - - `md_genre` - Genre. - - `md_players` - The number of players. - - `md_favorite` - Whether the game is a favorite. Will be printed as either `Yes` or `No`. - - `md_completed` - Whether the game has been completed. Will be printed as either `Yes` or `No`. - - `md_kidgame` - Whether the game is suitable for children. Will be printed as either `Yes` or `No`. - - `md_broken` - Whether the game is broken/not working. Will be printed as either `Yes` or `No`. - - `md_playcount` - How many times the game has been played. - - `md_controller` - The controller for the game. Will be blank if none has been selected. - - `md_altemulator` - The alternative emulator for the game. Will be blank if none has been selected. + - This translates to the metadata values that are available for the game. If an invalid metadata field is defined, it will be printed as a string literal. This property can only be used in the `gamelist` view. + - Valid values: + - `name` - Game name. + - `description` - Game description. Should be combined with the `container` property in most cases. + - `rating` - The numerical representation of the game rating, for example `3` or `4.5`. + - `developer` - Developer. + - `publisher` - Publisher. + - `genre` - Genre. + - `players` - The number of players. + - `favorite` - Whether the game is a favorite. Will be printed as either `yes` or `no`. + - `completed` - Whether the game has been completed. Will be printed as either `yes` or `no`. + - `kidgame` - Whether the game is suitable for children. Will be printed as either `yes` or `no`. + - `broken` - Whether the game is broken/not working. Will be printed as either `yes` or `no`. + - `playcount` - How many times the game has been played. + - `controller` - The controller for the game. Will be blank if none has been selected. + - `altemulator` - The alternative emulator for the game. Will be blank if none has been selected. * `container` - type: BOOLEAN - - Whether the text should be placed inside a scrollable container. Only available for the gamelist view. + - Whether the text should be placed inside a scrollable container. Only available for the `gamelist` view. * `containerScrollSpeed` - type: FLOAT - A base speed is automatically calculated based on the container and font sizes, so this property applies relative to the auto-calculated value. - Minimum value is `0.1` and maximum value is `10` @@ -1078,6 +1120,7 @@ Properties: - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). + - Minimum value is `0.5` and maximum value is `3` - Default is `1.5` * `opacity` - type: FLOAT - Controls the level of transparency. If set to `0` the element will be disabled. @@ -1119,8 +1162,8 @@ Properties: * `metadata` - type: STRING - This displays the metadata values that are available for the game. If an invalid metadata field is defined, the text "unknown" will be printed. - Possible values: - - `md_releasedate` - The release date of the game. - - `md_lastplayed` - The time the game was last played. This will be displayed as a value relative to the current date and time by default, but can be overridden using the `displayRelative` property. + - `releasedate` - The release date of the game. + - `lastplayed` - The time the game was last played. This will be displayed as a value relative to the current date and time by default, but can be overridden using the `displayRelative` property. * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT @@ -1140,6 +1183,7 @@ Properties: - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). + - Minimum value is `0.5` and maximum value is `3` - Default is `1.5` * `format` - type: STRING - Specifies the date and time format. Has no effect if `displayRelative` has been set to true. @@ -1389,6 +1433,7 @@ Properties: - Default is `none` (original letter case is retained) * `lineSpacing` - type: FLOAT - Controls the space between lines (as a multiple of font height). + - Minimum value is `0.5` and maximum value is `3` - Default is `1.5` * `zIndex` - type: FLOAT - z-index value for element. Elements will be rendered in order of zIndex value from low to high. @@ -1432,6 +1477,10 @@ Properties: * `letterCase` - type: STRING - Valid values are `uppercase`, `lowercase` or `capitalize` - Default is `uppercase` +* `opacity` - type: FLOAT + - Controls the level of transparency. + - Minimum value is `0.2` and maximum value is `1` + - Default is `1` * `customButtonIcon` - type: PATH - A button icon override. Specify the button type in the attribute `button`. - The available buttons are: \ From 0d799575cab1abde166abd016b7ec47f191782c9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 12:39:17 +0100 Subject: [PATCH 50/82] Fixed an issue where the logo text was incorrectly displayed for legacy themes. --- es-app/src/views/GamelistLegacy.h | 1 + 1 file changed, 1 insertion(+) diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h index 9b5deedd4..bc68bdd7d 100644 --- a/es-app/src/views/GamelistLegacy.h +++ b/es-app/src/views/GamelistLegacy.h @@ -27,6 +27,7 @@ void GamelistView::legacyPopulateFields() mTextComponents.back()->setHorizontalAlignment(ALIGN_CENTER); mTextComponents.back()->setColor(0xFFFFFFFF); mTextComponents.back()->setDefaultZIndex(50.0f); + mTextComponents.back()->setVisible(false); addChild(mTextComponents.back().get()); // Logo. From 31c5b200d192250d18f4759b7c1cc4b08fb767e1 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 15:01:55 +0100 Subject: [PATCH 51/82] Added support for using unsigned integers for theme properties. --- es-core/src/ThemeData.cpp | 13 +++++++++---- es-core/src/ThemeData.h | 1 + es-core/src/components/BadgeComponent.cpp | 12 ++++++------ es-core/src/components/CarouselComponent.cpp | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 55c6f7f94..2ead841c9 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -88,8 +88,8 @@ std::map> {"rotationOrigin", NORMALIZED_PAIR}, {"path", PATH}, {"default", PATH}, - {"tile", BOOLEAN}, {"imageType", STRING}, + {"tile", BOOLEAN}, {"interpolation", STRING}, {"color", COLOR}, {"colorEnd", COLOR}, @@ -141,8 +141,8 @@ std::map> {"horizontalAlignment", STRING}, {"alignment", STRING}, // For backward compatibility with legacy themes. {"direction", STRING}, - {"lines", FLOAT}, - {"itemsPerLine", FLOAT}, + {"lines", UNSIGNED_INTEGER}, + {"itemsPerLine", UNSIGNED_INTEGER}, {"itemMargin", NORMALIZED_PAIR}, {"slots", STRING}, {"controllerPos", NORMALIZED_PAIR}, @@ -245,7 +245,7 @@ std::map> {"logoHorizontalAlignment", STRING}, {"logoVerticalAlignment", STRING}, {"logoAlignment", STRING}, // For backward compatibility with legacy themes. - {"maxLogoCount", FLOAT}, + {"maxLogoCount", UNSIGNED_INTEGER}, {"text", STRING}, {"textColor", COLOR}, {"textBackgroundColor", COLOR}, @@ -1208,6 +1208,11 @@ void ThemeData::parseElement(const pugi::xml_node& root, } break; } + case UNSIGNED_INTEGER: { + unsigned int integerVal {static_cast(strtoul(str.c_str(), 0, 0))}; + element.properties[node.name()] = integerVal; + break; + } case FLOAT: { float floatVal {static_cast(strtod(str.c_str(), 0))}; element.properties[node.name()] = floatVal; diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h index 911cf23c7..75a658e9f 100644 --- a/es-core/src/ThemeData.h +++ b/es-core/src/ThemeData.h @@ -217,6 +217,7 @@ public: PATH, STRING, COLOR, + UNSIGNED_INTEGER, FLOAT, BOOLEAN }; diff --git a/es-core/src/components/BadgeComponent.cpp b/es-core/src/components/BadgeComponent.cpp index 989133f67..54ded9daf 100644 --- a/es-core/src/components/BadgeComponent.cpp +++ b/es-core/src/components/BadgeComponent.cpp @@ -229,25 +229,25 @@ void BadgeComponent::applyTheme(const std::shared_ptr& theme, } if (elem->has("lines")) { - const float lines {elem->get("lines")}; - if (lines < 1.0f || lines > 10.0f) { + const unsigned int lines {elem->get("lines")}; + if (lines < 1 || lines > 10) { LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" << lines << "\""; } else { - mFlexboxComponent.setLines(static_cast(lines)); + mFlexboxComponent.setLines(lines); } } if (elem->has("itemsPerLine")) { - const float itemsPerLine {elem->get("itemsPerLine")}; - if (itemsPerLine < 1.0f || itemsPerLine > 10.0f) { + const unsigned int itemsPerLine {elem->get("itemsPerLine")}; + if (itemsPerLine < 1 || itemsPerLine > 10) { LOG(LogWarning) << "BadgeComponent: Invalid theme configuration, set to \"" << itemsPerLine << "\""; } else { - mFlexboxComponent.setItemsPerLine(static_cast(itemsPerLine)); + mFlexboxComponent.setItemsPerLine(itemsPerLine); } } diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index c651799fb..a1ff64b0f 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -379,7 +379,7 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, } if (elem->has("maxLogoCount")) mMaxLogoCount = - glm::clamp(static_cast(std::round(elem->get("maxLogoCount"))), 2, 30); + glm::clamp(static_cast(elem->get("maxLogoCount")), 2, 30); if (elem->has("logoRotation")) mLogoRotation = elem->get("logoRotation"); From cc8123f5a6092b95745474a50c98eca4ae18c8da Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 20:03:34 +0100 Subject: [PATCH 52/82] Added a GameSelectorComponent for displaying game media in SystemView. --- es-app/src/CollectionSystemsManager.cpp | 4 + es-app/src/FileData.cpp | 38 +++++- es-app/src/FileData.h | 14 +- es-core/CMakeLists.txt | 1 + es-core/src/ThemeData.cpp | 3 + .../src/components/GameSelectorComponent.h | 124 ++++++++++++++++++ 6 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 es-core/src/components/GameSelectorComponent.h diff --git a/es-app/src/CollectionSystemsManager.cpp b/es-app/src/CollectionSystemsManager.cpp index a5f1bb3ba..16fd70a29 100644 --- a/es-app/src/CollectionSystemsManager.cpp +++ b/es-app/src/CollectionSystemsManager.cpp @@ -1443,6 +1443,10 @@ void CollectionSystemsManager::trimCollectionCount(FileData* rootFolder, int lim (CollectionFileData*)rootFolder->getChildrenListToDisplay().back(); ViewController::getInstance()->getGamelistView(curSys).get()->remove(gameToRemove, false); } + // Also update the lists of last played and most played games as these could otherwise + // contain dangling pointers. + rootFolder->updateLastPlayedList(); + rootFolder->updateMostPlayedList(); } const bool CollectionSystemsManager::themeFolderExists(const std::string& folder) diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index b68258d94..f811de823 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -39,6 +39,8 @@ FileData::FileData(FileType type, , mEnvData {envData} , mSystem {system} , mOnlyFolders {false} + , mUpdateChildrenLastPlayed {false} + , mUpdateChildrenMostPlayed {false} , mDeletionFlag {false} { // Metadata needs at least a name field (since that's what getName() will return). @@ -736,6 +738,9 @@ void FileData::sort(const SortType& type, bool mFavoritesOnTop) sortFavoritesOnTop(*type.comparisonFunction, mGameCount); else sort(*type.comparisonFunction, mGameCount); + + updateLastPlayedList(); + updateMostPlayedList(); } void FileData::countGames(std::pair& gameCount) @@ -743,9 +748,6 @@ void FileData::countGames(std::pair& gameCount) bool isKidMode = (Settings::getInstance()->getString("UIMode") == "kid" || Settings::getInstance()->getBool("ForceKid")); - (Settings::getInstance()->getString("UIMode") == "kid" || - Settings::getInstance()->getBool("ForceKid")); - for (unsigned int i = 0; i < mChildren.size(); ++i) { if (mChildren[i]->getType() == GAME && mChildren[i]->getCountAsGame()) { if (!isKidMode || (isKidMode && mChildren[i]->getKidgame())) { @@ -761,6 +763,36 @@ void FileData::countGames(std::pair& gameCount) mGameCount = gameCount; } +void FileData::updateLastPlayedList() +{ + if (!mUpdateChildrenLastPlayed) + return; + + mChildrenLastPlayed.clear(); + mChildrenLastPlayed = getChildrenRecursive(); + + std::stable_sort(mChildrenLastPlayed.begin(), mChildrenLastPlayed.end()); + std::sort(std::begin(mChildrenLastPlayed), std::end(mChildrenLastPlayed), + [](FileData* a, FileData* b) { + return a->metadata.get("lastplayed") > b->metadata.get("lastplayed"); + }); +} + +void FileData::updateMostPlayedList() +{ + if (!mUpdateChildrenMostPlayed) + return; + + mChildrenMostPlayed.clear(); + mChildrenMostPlayed = getChildrenRecursive(); + + std::stable_sort(mChildrenMostPlayed.begin(), mChildrenMostPlayed.end()); + std::sort(std::begin(mChildrenMostPlayed), std::end(mChildrenMostPlayed), + [](FileData* a, FileData* b) { + return a->metadata.getInt("playcount") > b->metadata.getInt("playcount"); + }); +} + const FileData::SortType& FileData::getSortTypeFromString(const std::string& desc) const { std::vector SortTypes = FileSorts::SortTypes; diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index 73332be6f..b40659000 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -56,6 +56,13 @@ public: const std::vector& getChildren() const { return mChildren; } SystemData* getSystem() const { return mSystem; } SystemEnvironmentData* getSystemEnvData() const { return mEnvData; } + + // These functions are used by GameSelectorComponent. + const std::vector& getChildrenLastPlayed() const { return mChildrenLastPlayed; } + const std::vector& getChildrenMostPlayed() const { return mChildrenMostPlayed; } + void setUpdateChildrenLastPlayed(bool state) { mUpdateChildrenLastPlayed = state; } + void setUpdateChildrenMostPlayed(bool state) { mUpdateChildrenMostPlayed = state; } + const bool getOnlyFoldersFlag() const { return mOnlyFolders; } const bool getHasFoldersFlag() const { return mHasFolders; } static const std::string getROMDirectory(); @@ -127,7 +134,8 @@ public: MetaDataList metadata; // Only count the games, a cheaper alternative to a full sort when that is not required. void countGames(std::pair& gameCount); - + void updateLastPlayedList(); + void updateMostPlayedList(); void setSortTypeString(std::string typestring) { mSortTypeString = typestring; } const std::string& getSortTypeString() const { return mSortTypeString; } const FileData::SortType& getSortTypeFromString(const std::string& desc) const; @@ -146,10 +154,14 @@ private: std::unordered_map mChildrenByFilename; std::vector mChildren; std::vector mFilteredChildren; + std::vector mChildrenLastPlayed; + std::vector mChildrenMostPlayed; // The pair includes all games, and favorite games. std::pair mGameCount; bool mOnlyFolders; bool mHasFolders; + bool mUpdateChildrenLastPlayed; + bool mUpdateChildrenMostPlayed; // Used for flagging a game for deletion from its gamelist.xml file. bool mDeletionFlag; }; diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index ddbde8dc3..779cd75f2 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -42,6 +42,7 @@ set(CORE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/FlexboxComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GameSelectorComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 2ead841c9..d383deb92 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -279,6 +279,9 @@ std::map> {"forceUppercase", BOOLEAN}, // For backward compatibility with legacy themes. {"lineSpacing", FLOAT}, {"zIndex", FLOAT}}}, + {"gameselector", + {{"selection", STRING}, + {"count", UNSIGNED_INTEGER}}}, {"helpsystem", {{"pos", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, diff --git a/es-core/src/components/GameSelectorComponent.h b/es-core/src/components/GameSelectorComponent.h new file mode 100644 index 000000000..26f72041a --- /dev/null +++ b/es-core/src/components/GameSelectorComponent.h @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +// +// EmulationStation Desktop Edition +// GameSelectorComponent.h +// +// Makes a selection of games based on theme-controlled criteria. +// + +#ifndef ES_CORE_COMPONENTS_GAME_SELECTOR_COMPONENT_H +#define ES_CORE_COMPONENTS_GAME_SELECTOR_COMPONENT_H + +#include "GuiComponent.h" +#include "Log.h" +#include "ThemeData.h" + +class GameSelectorComponent : public GuiComponent +{ +public: + GameSelectorComponent(SystemData* system) + : mSystem {system} + , mGameSelection {GameSelection::RANDOM} + , mGameCount {1} + { + } + + const std::vector& getGames() const { return mGames; } + + void refreshGames() + { + mGames.clear(); + + bool isKidMode {(Settings::getInstance()->getString("UIMode") == "kid" || + Settings::getInstance()->getBool("ForceKid"))}; + + if (mGameSelection == GameSelection::RANDOM) { + for (int i = 0; i < mGameCount; ++i) { + FileData* randomGame {mSystem->getRandomGame()}; + if (randomGame != nullptr) + mGames.emplace_back(randomGame); + } + } + else if (mGameSelection == GameSelection::LAST_PLAYED) { + for (auto& child : mSystem->getRootFolder()->getChildrenLastPlayed()) { + if (child->getType() != GAME) + continue; + if (!child->getCountAsGame()) + continue; + if (isKidMode && !child->getKidgame()) + continue; + if (child->metadata.get("lastplayed") == "0") + continue; + mGames.emplace_back(child); + if (static_cast(mGames.size()) == mGameCount) + break; + } + } + else if (mGameSelection == GameSelection::MOST_PLAYED) { + for (auto& child : mSystem->getRootFolder()->getChildrenMostPlayed()) { + if (child->getType() != GAME) + continue; + if (!child->getCountAsGame()) + continue; + if (isKidMode && !child->getKidgame()) + continue; + if (child->metadata.get("playcount") == "0") + continue; + mGames.emplace_back(child); + if (static_cast(mGames.size()) == mGameCount) + break; + } + } + } + + void applyTheme(const std::shared_ptr& theme, + const std::string& view, + const std::string& element, + unsigned int properties) + { + const ThemeData::ThemeElement* elem {theme->getElement(view, element, "gameselector")}; + if (!elem) + return; + + if (elem->has("selection")) { + const std::string selection {elem->get("selection")}; + if (selection == "random") { + mGameSelection = GameSelection::RANDOM; + } + else if (selection == "lastplayed") { + mGameSelection = GameSelection::LAST_PLAYED; + mSystem->getRootFolder()->setUpdateChildrenLastPlayed(true); + mSystem->getRootFolder()->updateLastPlayedList(); + } + else if (selection == "mostplayed") { + mGameSelection = GameSelection::MOST_PLAYED; + mSystem->getRootFolder()->setUpdateChildrenMostPlayed(true); + mSystem->getRootFolder()->updateMostPlayedList(); + } + else { + mGameSelection = GameSelection::RANDOM; + LOG(LogWarning) << "GameSelectorComponent: Invalid theme configuration, property " + " set to \"" + << selection << "\""; + } + } + + if (elem->has("count")) + mGameCount = glm::clamp(static_cast(elem->get("count")), 1, 30); + } + +private: + enum class GameSelection { + RANDOM, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). + LAST_PLAYED, + MOST_PLAYED + }; + + SystemData* mSystem; + std::vector mGames; + + GameSelection mGameSelection; + int mGameCount; +}; + +#endif // ES_CORE_COMPONENTS_GAME_SELECTOR_COMPONENT_H From afa62215821fa21056fd2ac49a79bbbaa5af4458 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 20:05:32 +0100 Subject: [PATCH 53/82] Fixed a potential crash in GamelistBase. --- es-app/src/views/GamelistBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-app/src/views/GamelistBase.cpp b/es-app/src/views/GamelistBase.cpp index 7416e101e..bb759992a 100644 --- a/es-app/src/views/GamelistBase.cpp +++ b/es-app/src/views/GamelistBase.cpp @@ -428,7 +428,7 @@ bool GamelistBase::input(InputConfig* config, Input input) setCursor(getFirstEntry()); view->setCursor(view->getFirstEntry()); } - else if (selectLastEntry) { + else if (selectLastEntry && mList.size() > 0) { setCursor(getLastEntry()); view->setCursor(view->getLastEntry()); } From 81b819a96a346bc8337c056566f4f0b9b2c2e9a9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 20:06:35 +0100 Subject: [PATCH 54/82] Fixed some incorrect debug log messages in GamelistFileParser. --- es-app/src/GamelistFileParser.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/es-app/src/GamelistFileParser.cpp b/es-app/src/GamelistFileParser.cpp index 1e5434e87..7093a6585 100644 --- a/es-app/src/GamelistFileParser.cpp +++ b/es-app/src/GamelistFileParser.cpp @@ -105,7 +105,7 @@ namespace GamelistFileParser std::string xmlpath = system->getGamelistPath(false); if (!Utils::FileSystem::exists(xmlpath)) { - LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName() + LOG(LogDebug) << "GamelistFileParser::parseGamelist(): System \"" << system->getName() << "\" does not have a gamelist.xml file"; return; } @@ -143,7 +143,8 @@ namespace GamelistFileParser } if (validLabel) { system->setAlternativeEmulator(label); - LOG(LogDebug) << "Gamelist::parseGamelist(): System \"" << system->getName() + LOG(LogDebug) << "GamelistFileParser::parseGamelist(): System \"" + << system->getName() << "\" has a valid alternativeEmulator entry: \"" << label << "\""; } @@ -181,8 +182,8 @@ namespace GamelistFileParser if (!showHiddenFiles && (Utils::FileSystem::isHidden(path) || Utils::FileSystem::isHidden(Utils::FileSystem::getParent(path)))) { - LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden file \"" << path - << "\""; + LOG(LogDebug) << "GamelistFileParser::parseGamelist(): Skipping hidden file \"" + << path << "\""; continue; } @@ -205,7 +206,7 @@ namespace GamelistFileParser else { // Skip arcade asset entries as these will not be used in any way inside // the application. - LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping arcade asset \"" + LOG(LogDebug) << "GamelistFileParser::parseGamelist(): Skipping arcade asset \"" << file->getName() << "\""; delete file; continue; @@ -216,7 +217,7 @@ namespace GamelistFileParser // application restart. if (!Settings::getInstance()->getBool("ShowHiddenGames")) { if (file->getHidden()) { - LOG(LogDebug) << "Gamelist::parseGamelist(): Skipping hidden " + LOG(LogDebug) << "GamelistFileParser::parseGamelist(): Skipping hidden " << (type == GAME ? "file" : "folder") << " entry \"" << file->getName() << "\"" << " (\"" << file->getPath() << "\")"; @@ -397,21 +398,22 @@ namespace GamelistFileParser if (updateAlternativeEmulator) { if (hasAlternativeEmulatorTag && system->getAlternativeEmulator() == "") { - LOG(LogDebug) << "Gamelist::updateGamelist(): Removed the " + LOG(LogDebug) << "GamelistFileParser::updateGamelist(): Removed the " "alternativeEmulator tag for system \"" << system->getName() << "\" as the default emulator \"" << system->getSystemEnvData()->mLaunchCommands.front().second << "\" was selected"; } else if (system->getAlternativeEmulator() != "") { - LOG(LogDebug) << "Gamelist::updateGamelist(): " + LOG(LogDebug) << "GamelistFileParser::updateGamelist(): " "Added/updated the alternativeEmulator tag for system \"" << system->getName() << "\" to \"" << system->getAlternativeEmulator() << "\""; } } if (numUpdated > 0) { - LOG(LogDebug) << "Gamelist::updateGamelist(): Added/updated " << numUpdated + LOG(LogDebug) << "GamelistFileParser::updateGamelist(): Added/updated " + << numUpdated << (numUpdated == 1 ? " entity in \"" : " entities in \"") << xmlWritePath << "\""; } From 57a594225a08dcf3bfaa6f325956f90cb20ce91c Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 22:30:03 +0100 Subject: [PATCH 55/82] Added initial game selector support to SystemView. --- es-app/src/views/SystemView.cpp | 49 +++++++++++++++++++++++++++++++-- es-app/src/views/SystemView.h | 8 +++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 46e51e2de..1d2c24a15 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -63,6 +63,7 @@ void SystemView::goToSystem(SystemData* system, bool animate) { mCarousel->setCursor(system); updateGameCount(); + updateGameSelector(); if (!animate) finishSystemAnimation(0); @@ -173,11 +174,10 @@ HelpStyle SystemView::getHelpStyle() void SystemView::onCursorChanged(const CursorState& /*state*/) { - // Update help style. updateHelpPrompts(); + updateGameSelector(); int scrollVelocity {mCarousel->getScrollingVelocity()}; - float startPos {mCamOffset}; float posMax {static_cast(mCarousel->getNumEntries())}; float target {static_cast(mCarousel->getCursor())}; @@ -336,6 +336,12 @@ void SystemView::populate() elements.name = it->getName(); elements.fullName = it->getFullName(); for (auto& element : theme->getViewElements("system").elements) { + if (element.second.type == "gameselector") { + elements.gameSelector = std::make_unique(it); + elements.gameSelector->applyTheme(theme, "system", element.first, + ThemeFlags::ALL); + elements.gameSelector->refreshGames(); + } if (element.second.type == "carousel") { mCarousel->applyTheme(theme, "system", element.first, ThemeFlags::ALL); if (element.second.has("logo")) @@ -420,6 +426,8 @@ void SystemView::populate() } } + updateGameSelector(); + if (mCarousel->getNumEntries() == 0) { // Something is wrong, there is not a single system to show, check if UI mode is not full. if (!UIModeController::getInstance()->isUIModeFull()) { @@ -489,6 +497,43 @@ void SystemView::updateGameCount() } } +void SystemView::updateGameSelector() +{ + int cursor {mCarousel->getCursor()}; + + if (mSystemElements[cursor].gameSelector != nullptr) { + mSystemElements[mCarousel->getCursor()].gameSelector->refreshGames(); + std::vector games {mSystemElements[cursor].gameSelector->getGames()}; + if (!games.empty()) { + if (!mLegacyMode) { + for (auto& image : mSystemElements[cursor].imageComponents) { + const std::string imageType {image->getThemeImageType()}; + if (imageType == "image") + image->setImage(games.front()->getImagePath()); + else if (image->getThemeImageType() == "miximage") + image->setImage(games.front()->getMiximagePath()); + else if (image->getThemeImageType() == "marquee") + image->setImage(games.front()->getMarqueePath()); + else if (image->getThemeImageType() == "screenshot") + image->setImage(games.front()->getScreenshotPath()); + else if (image->getThemeImageType() == "titlescreen") + image->setImage(games.front()->getTitleScreenPath()); + else if (image->getThemeImageType() == "cover") + image->setImage(games.front()->getCoverPath()); + else if (image->getThemeImageType() == "backcover") + image->setImage(games.front()->getBackCoverPath()); + else if (image->getThemeImageType() == "3dbox") + image->setImage(games.front()->get3DBoxPath()); + else if (image->getThemeImageType() == "fanart") + image->setImage(games.front()->getFanArtPath()); + else if (image->getThemeImageType() == "thumbnail") + image->setImage(games.front()->getThumbnailPath()); + } + } + } + } +} + void SystemView::legacyApplyTheme(const std::shared_ptr& theme) { if (theme->hasView("system")) diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index 631119100..7f3d88ee5 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -9,13 +9,13 @@ #ifndef ES_APP_VIEWS_SYSTEM_VIEW_H #define ES_APP_VIEWS_SYSTEM_VIEW_H +#include "FileData.h" #include "GuiComponent.h" #include "Sound.h" #include "SystemData.h" #include "components/CarouselComponent.h" -#include "components/DateTimeComponent.h" +#include "components/GameSelectorComponent.h" #include "components/LottieComponent.h" -#include "components/ScrollableContainer.h" #include "components/TextComponent.h" #include "components/TextListComponent.h" #include "components/VideoFFmpegComponent.h" @@ -28,6 +28,7 @@ class SystemData; struct SystemViewElements { std::string name; std::string fullName; + std::unique_ptr gameSelector; std::vector legacyExtras; std::vector children; @@ -36,8 +37,6 @@ struct SystemViewElements { std::vector> imageComponents; std::vector> videoComponents; std::vector> lottieAnimComponents; - std::vector> containerComponents; - std::vector> containerTextComponents; }; class SystemView : public GuiComponent @@ -78,6 +77,7 @@ protected: private: void populate(); void updateGameCount(); + void updateGameSelector(); void legacyApplyTheme(const std::shared_ptr& theme); void renderElements(const glm::mat4& parentTrans, bool abovePrimary); From eb3729a5fbd96425ff1fb63da4062b1a02eae674 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 13 Feb 2022 23:15:43 +0100 Subject: [PATCH 56/82] Fixed a few crashes related to GameSelectorComponent. --- es-app/src/SystemData.cpp | 19 ++++++++++++------- es-app/src/SystemData.h | 2 +- .../src/components/GameSelectorComponent.h | 4 +++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 84359029c..f52f52efb 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -1096,7 +1096,7 @@ SystemData* SystemData::getRandomSystem(const SystemData* currentSystem) return randomSystem; } -FileData* SystemData::getRandomGame(const FileData* currentGame) +FileData* SystemData::getRandomGame(const FileData* currentGame, bool gameSelectorMode) { std::vector gameList; bool onlyFolders = false; @@ -1109,12 +1109,17 @@ FileData* SystemData::getRandomGame(const FileData* currentGame) gameList = mRootFolder->getParent()->getChildrenListToDisplay(); } else { - gameList = ViewController::getInstance() - ->getGamelistView(mRootFolder->getSystem()) - .get() - ->getCursor() - ->getParent() - ->getChildrenListToDisplay(); + if (gameSelectorMode) { + gameList = mRootFolder->getFilesRecursive(GAME, false, false); + } + else { + gameList = ViewController::getInstance() + ->getGamelistView(mRootFolder->getSystem()) + .get() + ->getCursor() + ->getParent() + ->getChildrenListToDisplay(); + } } if (gameList.size() > 0 && gameList.front()->getParent()->getOnlyFoldersFlag()) diff --git a/es-app/src/SystemData.h b/es-app/src/SystemData.h index b0b4dad10..c22d84d71 100644 --- a/es-app/src/SystemData.h +++ b/es-app/src/SystemData.h @@ -128,7 +128,7 @@ public: SystemData* getNext() const; SystemData* getPrev() const; static SystemData* getRandomSystem(const SystemData* currentSystem); - FileData* getRandomGame(const FileData* currentGame = nullptr); + FileData* getRandomGame(const FileData* currentGame = nullptr, bool gameSelectorMode = false); FileData* getPlaceholder() { return mPlaceholder; } void sortSystem(bool reloadGamelist = true, bool jumpToFirstRow = false); diff --git a/es-core/src/components/GameSelectorComponent.h b/es-core/src/components/GameSelectorComponent.h index 26f72041a..42fae0fe3 100644 --- a/es-core/src/components/GameSelectorComponent.h +++ b/es-core/src/components/GameSelectorComponent.h @@ -34,7 +34,9 @@ public: if (mGameSelection == GameSelection::RANDOM) { for (int i = 0; i < mGameCount; ++i) { - FileData* randomGame {mSystem->getRandomGame()}; + if (mSystem->getRootFolder()->getGameCount().first == 0) + break; + FileData* randomGame {mSystem->getRandomGame(nullptr, true)}; if (randomGame != nullptr) mGames.emplace_back(randomGame); } From 4f019c3775d185f67b2840eb6509ec9cc2718098 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 14 Feb 2022 19:32:07 +0100 Subject: [PATCH 57/82] Added support for defining multiple imageType entries. Also made some improvements to GameSelectorComponent and related logic. --- es-app/src/FileData.cpp | 6 + es-app/src/FileData.h | 3 + es-app/src/views/GamelistView.cpp | 177 +++++++------- es-app/src/views/GamelistView.h | 1 + es-app/src/views/SystemView.cpp | 229 +++++++++++++++--- es-app/src/views/SystemView.h | 4 +- es-core/src/GuiComponent.cpp | 3 + es-core/src/GuiComponent.h | 13 +- es-core/src/ThemeData.cpp | 4 +- es-core/src/components/CarouselComponent.cpp | 1 + .../src/components/GameSelectorComponent.h | 31 ++- es-core/src/components/GridTileComponent.cpp | 4 +- es-core/src/components/GridTileComponent.h | 6 +- es-core/src/components/ImageComponent.cpp | 12 +- es-core/src/components/ImageComponent.h | 2 +- es-core/src/components/VideoComponent.cpp | 24 +- es-core/src/components/VideoComponent.h | 3 +- 17 files changed, 369 insertions(+), 154 deletions(-) diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index f811de823..2af22e8e4 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -765,6 +765,9 @@ void FileData::countGames(std::pair& gameCount) void FileData::updateLastPlayedList() { + if (mUpdateListCallback) + mUpdateListCallback(); + if (!mUpdateChildrenLastPlayed) return; @@ -780,6 +783,9 @@ void FileData::updateLastPlayedList() void FileData::updateMostPlayedList() { + if (mUpdateListCallback) + mUpdateListCallback(); + if (!mUpdateChildrenMostPlayed) return; diff --git a/es-app/src/FileData.h b/es-app/src/FileData.h index b40659000..b04e3fa9e 100644 --- a/es-app/src/FileData.h +++ b/es-app/src/FileData.h @@ -14,6 +14,7 @@ #include "MetaData.h" #include "utils/FileSystemUtil.h" +#include #include class SystemData; @@ -62,6 +63,7 @@ public: const std::vector& getChildrenMostPlayed() const { return mChildrenMostPlayed; } void setUpdateChildrenLastPlayed(bool state) { mUpdateChildrenLastPlayed = state; } void setUpdateChildrenMostPlayed(bool state) { mUpdateChildrenMostPlayed = state; } + void setUpdateListCallback(const std::function& func) { mUpdateListCallback = func; } const bool getOnlyFoldersFlag() const { return mOnlyFolders; } const bool getHasFoldersFlag() const { return mHasFolders; } @@ -156,6 +158,7 @@ private: std::vector mFilteredChildren; std::vector mChildrenLastPlayed; std::vector mChildrenMostPlayed; + std::function mUpdateListCallback; // The pair includes all games, and favorite games. std::pair mGameCount; bool mOnlyFolders; diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index c52a34655..8dba275ca 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -107,7 +107,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) mImageComponents.push_back(std::make_unique()); mImageComponents.back()->setDefaultZIndex(30.0f); mImageComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); - if (mImageComponents.back()->getThemeImageType() != "") + if (mImageComponents.back()->getThemeImageTypes().size() != 0) mImageComponents.back()->setScrollHide(true); addChild(mImageComponents.back().get()); } @@ -116,7 +116,7 @@ void GamelistView::onThemeChanged(const std::shared_ptr& theme) mVideoComponents.back()->setDefaultZIndex(30.0f); addChild(mVideoComponents.back().get()); mVideoComponents.back()->applyTheme(theme, "gamelist", element.first, ALL); - if (mVideoComponents.back()->getThemeImageType() != "") + if (mVideoComponents.back()->getThemeImageTypes().size() != 0) mVideoComponents.back()->setScrollHide(true); } else if (element.second.type == "animation") { @@ -387,50 +387,11 @@ void GamelistView::updateInfoPanel() mRandomGame = CollectionSystemsManager::getInstance()->updateCollectionFolderMetadata( file->getSystem()); if (mRandomGame) { - for (auto& image : mImageComponents) { - if (image->getThemeImageType() == "image") - image->setImage(mRandomGame->getImagePath()); - else if (image->getThemeImageType() == "miximage") - image->setImage(mRandomGame->getMiximagePath()); - else if (image->getThemeImageType() == "marquee") - image->setImage(mRandomGame->getMarqueePath()); - else if (image->getThemeImageType() == "screenshot") - image->setImage(mRandomGame->getScreenshotPath()); - else if (image->getThemeImageType() == "titlescreen") - image->setImage(mRandomGame->getTitleScreenPath()); - else if (image->getThemeImageType() == "cover") - image->setImage(mRandomGame->getCoverPath()); - else if (image->getThemeImageType() == "backcover") - image->setImage(mRandomGame->getBackCoverPath()); - else if (image->getThemeImageType() == "3dbox") - image->setImage(mRandomGame->get3DBoxPath()); - else if (image->getThemeImageType() == "fanart") - image->setImage(mRandomGame->getFanArtPath()); - else if (image->getThemeImageType() == "thumbnail") - image->setImage(mRandomGame->getThumbnailPath()); - } + for (auto& image : mImageComponents) + setGameImage(mRandomGame, image.get()); for (auto& video : mVideoComponents) { - if (video->getThemeImageType() == "image") - video->setImage(mRandomGame->getImagePath()); - else if (video->getThemeImageType() == "miximage") - video->setImage(mRandomGame->getMiximagePath()); - else if (video->getThemeImageType() == "marquee") - video->setImage(mRandomGame->getMarqueePath()); - else if (video->getThemeImageType() == "screenshot") - video->setImage(mRandomGame->getScreenshotPath()); - else if (video->getThemeImageType() == "titlescreen") - video->setImage(mRandomGame->getTitleScreenPath()); - else if (video->getThemeImageType() == "cover") - video->setImage(mRandomGame->getCoverPath()); - else if (video->getThemeImageType() == "backcover") - video->setImage(mRandomGame->getBackCoverPath()); - else if (video->getThemeImageType() == "3dbox") - video->setImage(mRandomGame->get3DBoxPath()); - else if (video->getThemeImageType() == "fanart") - video->setImage(mRandomGame->getFanArtPath()); - else if (video->getThemeImageType() == "thumbnail") - video->setImage(mRandomGame->getThumbnailPath()); + setGameImage(mRandomGame, video.get()); // 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 @@ -447,7 +408,7 @@ void GamelistView::updateInfoPanel() } else { for (auto& image : mImageComponents) { - if (image->getThemeImageType() != "") + if (image->getThemeImageTypes().size() != 0) image->setImage(""); } @@ -465,51 +426,11 @@ void GamelistView::updateInfoPanel() } } else { - for (auto& image : mImageComponents) { - if (image->getThemeImageType() == "image") - image->setImage(file->getImagePath()); - else if (image->getThemeImageType() == "miximage") - image->setImage(file->getMiximagePath()); - else if (image->getThemeImageType() == "marquee") - image->setImage(file->getMarqueePath()); - else if (image->getThemeImageType() == "screenshot") - image->setImage(file->getScreenshotPath()); - else if (image->getThemeImageType() == "titlescreen") - image->setImage(file->getTitleScreenPath()); - else if (image->getThemeImageType() == "cover") - image->setImage(file->getCoverPath()); - else if (image->getThemeImageType() == "backcover") - image->setImage(file->getBackCoverPath()); - else if (image->getThemeImageType() == "3dbox") - image->setImage(file->get3DBoxPath()); - else if (image->getThemeImageType() == "fanart") - image->setImage(file->getFanArtPath()); - else if (image->getThemeImageType() == "thumbnail") - image->setImage(file->getThumbnailPath()); - } + for (auto& image : mImageComponents) + setGameImage(file, image.get()); for (auto& video : mVideoComponents) { - if (video->getThemeImageType() == "image") - video->setImage(file->getImagePath()); - else if (video->getThemeImageType() == "miximage") - video->setImage(file->getMiximagePath()); - else if (video->getThemeImageType() == "marquee") - video->setImage(file->getMarqueePath()); - else if (video->getThemeImageType() == "screenshot") - video->setImage(file->getScreenshotPath()); - else if (video->getThemeImageType() == "titlescreen") - video->setImage(file->getTitleScreenPath()); - else if (video->getThemeImageType() == "cover") - video->setImage(file->getCoverPath()); - else if (video->getThemeImageType() == "backcover") - video->setImage(file->getBackCoverPath()); - else if (video->getThemeImageType() == "3dbox") - video->setImage(file->get3DBoxPath()); - else if (video->getThemeImageType() == "fanart") - video->setImage(file->getFanArtPath()); - else if (video->getThemeImageType() == "thumbnail") - video->setImage(file->getThumbnailPath()); - + setGameImage(file, video.get()); video->onHide(); if (video->hasStaticVideo()) @@ -767,3 +688,83 @@ void GamelistView::updateInfoPanel() } } } + +void GamelistView::setGameImage(FileData* file, GuiComponent* comp) +{ + std::string path; + for (auto& imageType : comp->getThemeImageTypes()) { + if (imageType == "image") { + path = file->getImagePath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "miximage") { + path = file->getMiximagePath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "marquee") { + path = file->getMarqueePath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "screenshot") { + path = file->getScreenshotPath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "titlescreen") { + path = file->getTitleScreenPath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "cover") { + path = file->getCoverPath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "backcover") { + path = file->getBackCoverPath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "3dbox") { + path = file->get3DBoxPath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "fanart") { + path = file->getFanArtPath(); + if (path != "") { + comp->setImage(path); + break; + } + } + else if (imageType == "thumbnail") { + path = file->getThumbnailPath(); + if (path != "") { + comp->setImage(path); + break; + } + } + } + // This is needed so the default image is set if no game media was found. + if (path == "" && comp->getThemeImageTypes().size() > 0) + comp->setImage(""); +} diff --git a/es-app/src/views/GamelistView.h b/es-app/src/views/GamelistView.h index 340637ca1..fb9304070 100644 --- a/es-app/src/views/GamelistView.h +++ b/es-app/src/views/GamelistView.h @@ -57,6 +57,7 @@ public: private: void updateInfoPanel(); + void setGameImage(FileData* file, GuiComponent* comp); // Legacy (backward compatibility) functions. void legacyPopulateFields(); diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 1d2c24a15..2294e7ee2 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -62,8 +62,14 @@ SystemView::~SystemView() void SystemView::goToSystem(SystemData* system, bool animate) { mCarousel->setCursor(system); + + for (auto& selector : mSystemElements[mCarousel->getCursor()].gameSelectors) { + if (selector->getGameSelection() == GameSelectorComponent::GameSelection::RANDOM) + selector->setNeedsRefresh(); + } + + updateGameSelectors(); updateGameCount(); - updateGameSelector(); if (!animate) finishSystemAnimation(0); @@ -174,13 +180,20 @@ HelpStyle SystemView::getHelpStyle() void SystemView::onCursorChanged(const CursorState& /*state*/) { + int cursor {mCarousel->getCursor()}; + + for (auto& selector : mSystemElements[cursor].gameSelectors) { + if (selector->getGameSelection() == GameSelectorComponent::GameSelection::RANDOM) + selector->setNeedsRefresh(); + } + + updateGameSelectors(); updateHelpPrompts(); - updateGameSelector(); int scrollVelocity {mCarousel->getScrollingVelocity()}; float startPos {mCamOffset}; float posMax {static_cast(mCarousel->getNumEntries())}; - float target {static_cast(mCarousel->getCursor())}; + float target {static_cast(cursor)}; // Find the shortest path to the target. float endPos {target}; // Directly. @@ -337,10 +350,11 @@ void SystemView::populate() elements.fullName = it->getFullName(); for (auto& element : theme->getViewElements("system").elements) { if (element.second.type == "gameselector") { - elements.gameSelector = std::make_unique(it); - elements.gameSelector->applyTheme(theme, "system", element.first, - ThemeFlags::ALL); - elements.gameSelector->refreshGames(); + elements.gameSelectors.emplace_back( + std::make_unique(it)); + elements.gameSelectors.back()->applyTheme(theme, "system", element.first, + ThemeFlags::ALL); + elements.gameSelectors.back()->setNeedsRefresh(); } if (element.second.type == "carousel") { mCarousel->applyTheme(theme, "system", element.first, ThemeFlags::ALL); @@ -354,7 +368,7 @@ void SystemView::populate() elements.imageComponents.back()->setDefaultZIndex(30.0f); elements.imageComponents.back()->applyTheme(theme, "system", element.first, ThemeFlags::ALL); - if (elements.imageComponents.back()->getThemeImageType() != "") + if (elements.imageComponents.back()->getThemeImageTypes().size() != 0) elements.imageComponents.back()->setScrollHide(true); elements.children.emplace_back(elements.imageComponents.back().get()); } @@ -426,7 +440,7 @@ void SystemView::populate() } } - updateGameSelector(); + updateGameSelectors(); if (mCarousel->getNumEntries() == 0) { // Something is wrong, there is not a single system to show, check if UI mode is not full. @@ -497,39 +511,182 @@ void SystemView::updateGameCount() } } -void SystemView::updateGameSelector() +void SystemView::updateGameSelectors() { + if (mLegacyMode) + return; + int cursor {mCarousel->getCursor()}; - if (mSystemElements[cursor].gameSelector != nullptr) { - mSystemElements[mCarousel->getCursor()].gameSelector->refreshGames(); - std::vector games {mSystemElements[cursor].gameSelector->getGames()}; + if (mSystemElements[cursor].gameSelectors.size() == 0) + return; + + bool multipleSelectors {mSystemElements[cursor].gameSelectors.size() > 1}; + + for (auto& image : mSystemElements[cursor].imageComponents) { + if (image->getThemeImageTypes().size() == 0) + continue; + GameSelectorComponent* gameSelector {nullptr}; + if (multipleSelectors) { + const std::string& imageSelector {image->getThemeGameSelector()}; + if (imageSelector == "") { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + LOG(LogWarning) << "SystemView::updateGameSelectors(): Multiple gameselector " + "elements defined but image element does not state which one to " + "use, so selecting first entry"; + } + else { + for (auto& selector : mSystemElements[cursor].gameSelectors) { + if (selector->getSelectorName() == imageSelector) + gameSelector = selector.get(); + } + if (gameSelector == nullptr) + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + } + else { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + gameSelector->refreshGames(); + std::vector games {gameSelector->getGames()}; if (!games.empty()) { - if (!mLegacyMode) { - for (auto& image : mSystemElements[cursor].imageComponents) { - const std::string imageType {image->getThemeImageType()}; - if (imageType == "image") - image->setImage(games.front()->getImagePath()); - else if (image->getThemeImageType() == "miximage") - image->setImage(games.front()->getMiximagePath()); - else if (image->getThemeImageType() == "marquee") - image->setImage(games.front()->getMarqueePath()); - else if (image->getThemeImageType() == "screenshot") - image->setImage(games.front()->getScreenshotPath()); - else if (image->getThemeImageType() == "titlescreen") - image->setImage(games.front()->getTitleScreenPath()); - else if (image->getThemeImageType() == "cover") - image->setImage(games.front()->getCoverPath()); - else if (image->getThemeImageType() == "backcover") - image->setImage(games.front()->getBackCoverPath()); - else if (image->getThemeImageType() == "3dbox") - image->setImage(games.front()->get3DBoxPath()); - else if (image->getThemeImageType() == "fanart") - image->setImage(games.front()->getFanArtPath()); - else if (image->getThemeImageType() == "thumbnail") - image->setImage(games.front()->getThumbnailPath()); + std::string path; + for (auto& imageType : image->getThemeImageTypes()) { + if (imageType == "image") { + path = games.front()->getImagePath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "miximage") { + path = games.front()->getMiximagePath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "marquee") { + path = games.front()->getMarqueePath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "screenshot") { + path = games.front()->getScreenshotPath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "titlescreen") { + path = games.front()->getTitleScreenPath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "cover") { + path = games.front()->getCoverPath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "backcover") { + path = games.front()->getBackCoverPath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "3dbox") { + path = games.front()->get3DBoxPath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "fanart") { + path = games.front()->getFanArtPath(); + if (path != "") { + image->setImage(path); + break; + } + } + else if (imageType == "thumbnail") { + path = games.front()->getThumbnailPath(); + if (path != "") { + image->setImage(path); + break; + } } } + // This is needed so the default image is set if no game media was found. + if (path == "" && image->getThemeImageTypes().size() > 0) + image->setImage(""); + } + else { + image->setImage(""); + } + } + + for (auto& text : mSystemElements[cursor].textComponents) { + if (text->getThemeMetadata() == "") + continue; + GameSelectorComponent* gameSelector {nullptr}; + if (multipleSelectors) { + const std::string& textSelector {text->getThemeGameSelector()}; + if (textSelector == "") { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + LOG(LogWarning) << "SystemView::updateGameSelectors(): Multiple gameselector " + "elements defined but text element does not state which one to " + "use, so selecting first entry"; + } + else { + for (auto& selector : mSystemElements[cursor].gameSelectors) { + if (selector->getSelectorName() == textSelector) + gameSelector = selector.get(); + } + if (gameSelector == nullptr) + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + } + else { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + gameSelector->refreshGames(); + std::vector games {gameSelector->getGames()}; + if (!games.empty()) { + const std::string metadata {text->getThemeMetadata()}; + if (metadata == "name") + text->setValue(games.front()->metadata.get("name")); + if (metadata == "description") + text->setValue(games.front()->metadata.get("desc")); + if (metadata == "developer") + text->setValue(games.front()->metadata.get("developer")); + if (metadata == "publisher") + text->setValue(games.front()->metadata.get("publisher")); + if (metadata == "genre") + text->setValue(games.front()->metadata.get("genre")); + if (metadata == "players") + text->setValue(games.front()->metadata.get("players")); + if (metadata == "favorite") + text->setValue(games.front()->metadata.get("favorite") == "true" ? "yes" : "no"); + if (metadata == "completed") + text->setValue(games.front()->metadata.get("completed") == "true" ? "yes" : "no"); + if (metadata == "kidgame") + text->setValue(games.front()->metadata.get("kidgame") == "true" ? "yes" : "no"); + if (metadata == "broken") + text->setValue(games.front()->metadata.get("broken") == "true" ? "yes" : "no"); + if (metadata == "playcount") + text->setValue(games.front()->metadata.get("playcount")); + if (metadata == "altemulator") + text->setValue(games.front()->metadata.get("altemulator")); + } + else { + text->setValue(""); } } } diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index 7f3d88ee5..f16da5f70 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -28,7 +28,7 @@ class SystemData; struct SystemViewElements { std::string name; std::string fullName; - std::unique_ptr gameSelector; + std::vector> gameSelectors; std::vector legacyExtras; std::vector children; @@ -77,7 +77,7 @@ protected: private: void populate(); void updateGameCount(); - void updateGameSelector(); + void updateGameSelectors(); void legacyApplyTheme(const std::shared_ptr& theme); void renderElements(const glm::mat4& parentTrans, bool abovePrimary); diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index f83d36678..f0051ea7f 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -364,6 +364,9 @@ void GuiComponent::applyTheme(const std::shared_ptr& theme, mThemeOpacity = 0.0f; else setVisible(true); + + if (properties && elem->has("gameselector")) + mThemeGameSelector = elem->get("gameselector"); } void GuiComponent::updateHelpPrompts() diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 544c5d2d5..8cb03502e 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -207,16 +207,18 @@ public: virtual void setOriginalColor(unsigned int color) { mColorOriginalValue = color; } virtual void setChangedColor(unsigned int color) { mColorChangedValue = color; } + virtual void setImage(const std::string& path, bool tile = false) {} + // These functions are used to enable and disable options in menus, i.e. switches and similar. virtual bool getEnabled() { return mEnabled; } virtual void setEnabled(bool state) { mEnabled = state; } - const std::string getThemeSystemdata() { return mThemeSystemdata; } + const std::string& getThemeSystemdata() { return mThemeSystemdata; } void setThemeSystemdata(const std::string& text) { mThemeSystemdata = text; } - const std::string getThemeMetadata() { return mThemeMetadata; } + const std::string& getThemeMetadata() { return mThemeMetadata; } void setThemeMetadata(const std::string& text) { mThemeMetadata = text; } - const std::string getThemeImageType() { return mThemeImageType; } - void setThemeImageType(const std::string& text) { mThemeImageType = text; } + const std::vector& getThemeImageTypes() { return mThemeImageTypes; } + const std::string& getThemeGameSelector() { return mThemeGameSelector; } virtual std::shared_ptr getFont() const { return nullptr; } @@ -279,9 +281,10 @@ protected: GuiComponent* mParent; std::vector mChildren; + std::vector mThemeImageTypes; std::string mThemeSystemdata; std::string mThemeMetadata; - std::string mThemeImageType; + std::string mThemeGameSelector; unsigned int mColor; float mSaturation; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index d383deb92..1a1501931 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -89,6 +89,7 @@ std::map> {"path", PATH}, {"default", PATH}, {"imageType", STRING}, + {"gameselector", STRING}, {"tile", BOOLEAN}, {"interpolation", STRING}, {"color", COLOR}, @@ -161,6 +162,7 @@ std::map> {"text", STRING}, {"systemdata", STRING}, {"metadata", STRING}, + {"gameselector", STRING}, {"container", BOOLEAN}, {"containerScrollSpeed", FLOAT}, {"containerStartDelay", FLOAT}, @@ -281,7 +283,7 @@ std::map> {"zIndex", FLOAT}}}, {"gameselector", {{"selection", STRING}, - {"count", UNSIGNED_INTEGER}}}, + {"gameCount", UNSIGNED_INTEGER}}}, {"helpsystem", {{"pos", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, diff --git a/es-core/src/components/CarouselComponent.cpp b/es-core/src/components/CarouselComponent.cpp index a1ff64b0f..6226722c8 100644 --- a/es-core/src/components/CarouselComponent.cpp +++ b/es-core/src/components/CarouselComponent.cpp @@ -311,6 +311,7 @@ void CarouselComponent::applyTheme(const std::shared_ptr& theme, mCarouselColor = 0xFFFFFFD8; mCarouselColorEnd = 0xFFFFFFD8; mDefaultZIndex = 50.0f; + mText = ""; if (!elem) return; diff --git a/es-core/src/components/GameSelectorComponent.h b/es-core/src/components/GameSelectorComponent.h index 42fae0fe3..c828fdf17 100644 --- a/es-core/src/components/GameSelectorComponent.h +++ b/es-core/src/components/GameSelectorComponent.h @@ -19,15 +19,30 @@ public: GameSelectorComponent(SystemData* system) : mSystem {system} , mGameSelection {GameSelection::RANDOM} + , mNeedsRefresh {false} , mGameCount {1} { + mSystem->getRootFolder()->setUpdateListCallback([&]() { mNeedsRefresh = true; }); } + enum class GameSelection { + RANDOM, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). + LAST_PLAYED, + MOST_PLAYED + }; + const std::vector& getGames() const { return mGames; } + void setNeedsRefresh() { mNeedsRefresh = true; } + const bool getNeedsRefresh() { return mNeedsRefresh; } + const GameSelection getGameSelection() const { return mGameSelection; } + const std::string& getSelectorName() const { return mSelectorName; } void refreshGames() { + if (!mNeedsRefresh) + return; mGames.clear(); + mNeedsRefresh = false; bool isKidMode {(Settings::getInstance()->getString("UIMode") == "kid" || Settings::getInstance()->getBool("ForceKid"))}; @@ -82,6 +97,10 @@ public: if (!elem) return; + // Remove the leading "gameselector_" part of the element string in order to directly + // match with the optional "gameselector" property used in other elements. + mSelectorName = element.substr(13, std::string::npos); + if (elem->has("selection")) { const std::string selection {elem->get("selection")}; if (selection == "random") { @@ -105,21 +124,17 @@ public: } } - if (elem->has("count")) - mGameCount = glm::clamp(static_cast(elem->get("count")), 1, 30); + if (elem->has("gameCount")) + mGameCount = glm::clamp(static_cast(elem->get("gameCount")), 1, 30); } private: - enum class GameSelection { - RANDOM, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). - LAST_PLAYED, - MOST_PLAYED - }; - SystemData* mSystem; std::vector mGames; + std::string mSelectorName; GameSelection mGameSelection; + bool mNeedsRefresh; int mGameCount; }; diff --git a/es-core/src/components/GridTileComponent.cpp b/es-core/src/components/GridTileComponent.cpp index 7f17650bb..c5a74eec2 100644 --- a/es-core/src/components/GridTileComponent.cpp +++ b/es-core/src/components/GridTileComponent.cpp @@ -148,7 +148,7 @@ bool GridTileComponent::isSelected() const return mSelected; } -void GridTileComponent::setImage(const std::string& path) +void GridTileComponent::setImageOLD(const std::string& path) { mImage->setImage(path); @@ -156,7 +156,7 @@ void GridTileComponent::setImage(const std::string& path) resize(); } -void GridTileComponent::setImage(const std::shared_ptr& texture) +void GridTileComponent::setImageOLD(const std::shared_ptr& texture) { mImage->setImage(texture); diff --git a/es-core/src/components/GridTileComponent.h b/es-core/src/components/GridTileComponent.h index f0b0b2df6..1347b5006 100644 --- a/es-core/src/components/GridTileComponent.h +++ b/es-core/src/components/GridTileComponent.h @@ -39,10 +39,10 @@ public: glm::vec2 getSelectedTileSize() const; bool isSelected() const; - void reset() { setImage(""); } + void reset() { setImageOLD(""); } - void setImage(const std::string& path); - void setImage(const std::shared_ptr& texture); + void setImageOLD(const std::string& path); + void setImageOLD(const std::shared_ptr& texture); void setSelected(bool selected, bool allowAnimation = true, glm::vec3* pPosition = nullptr, diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index cc2ced6c7..155408cc3 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -14,6 +14,7 @@ #include "Window.h" #include "resources/TextureResource.h" #include "utils/CImgUtil.h" +#include "utils/StringUtil.h" glm::ivec2 ImageComponent::getTextureSize() const { @@ -522,8 +523,15 @@ void ImageComponent::applyTheme(const std::shared_ptr& theme, setImage(elem->get("path"), tile); } - if (properties & METADATA && elem->has("imageType")) - mThemeImageType = elem->get("imageType"); + if (properties && elem->has("imageType")) { + std::string imageTypes {elem->get("imageType")}; + for (auto& character : imageTypes) { + if (std::isspace(character)) + character = ','; + } + imageTypes = Utils::String::replace(imageTypes, ",,", ","); + mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ","); + } if (properties & COLOR) { if (elem->has("color")) diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 0a8c5c242..e06d41924 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -24,7 +24,7 @@ public: // Loads the image at the given filepath. Will tile if tile is true (retrieves texture // as tiling, creates vertices accordingly). - void setImage(const std::string& path, bool tile = false); + void setImage(const std::string& path, bool tile = false) override; // Loads an image from memory. void setImage(const char* data, size_t length, bool tile = false); // Use an already existing texture. diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 29c7d6768..0844a9fab 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -12,6 +12,7 @@ #include "Window.h" #include "resources/ResourceManager.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" #include @@ -80,12 +81,17 @@ bool VideoComponent::setVideo(std::string path) void VideoComponent::setImage(const std::string& path, bool tile) { + std::string imagePath {path}; + + if (imagePath == "") + imagePath = mDefaultImagePath; + // Check that the image has changed. - if (path == mStaticImagePath) + if (imagePath == mStaticImagePath) return; - mStaticImage.setImage(path, tile); - mStaticImagePath = path; + mStaticImage.setImage(imagePath, tile); + mStaticImagePath = imagePath; } void VideoComponent::onShow() @@ -268,6 +274,7 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("defaultImage")) { mStaticImage.setDefaultImage(elem->get("defaultImage")); mStaticImage.setImage(mStaticImagePath); + mDefaultImagePath = elem->get("defaultImage"); } if (elem->has("path")) @@ -287,8 +294,15 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, else if (elem->has("showSnapshotDelay")) mConfig.showSnapshotDelay = elem->get("showSnapshotDelay"); - if (properties & METADATA && elem->has("imageType")) - mThemeImageType = elem->get("imageType"); + if (properties && elem->has("imageType")) { + std::string imageTypes {elem->get("imageType")}; + for (auto& character : imageTypes) { + if (std::isspace(character)) + character = ','; + } + imageTypes = Utils::String::replace(imageTypes, ",,", ","); + mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ","); + } if (elem->has("pillarboxes")) mDrawPillarboxes = elem->get("pillarboxes"); diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index 89ad4b94f..cdefc1f80 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -40,7 +40,7 @@ public: // 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(const std::string& path, bool tile = false); + void setImage(const std::string& path, bool tile = false) override; // Sets whether we're in media viewer mode. void setMediaViewerMode(bool isMediaViewer) { mMediaViewerMode = isMediaViewer; } // Sets whether we're in screensaver mode. @@ -125,6 +125,7 @@ protected: glm::vec2 mVideoAreaSize; std::shared_ptr mTexture; std::string mStaticImagePath; + std::string mDefaultImagePath; std::string mVideoPath; std::string mPlayingVideoPath; From 4a20ed8007df10984c959439d45ee8558c2b5849 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 14 Feb 2022 19:56:28 +0100 Subject: [PATCH 58/82] (rbsimple-DE) Added a temporary theme engine test variant. --- themes/rbsimple-DE/capabilities.xml | 5 + themes/rbsimple-DE/theme.xml | 5 + themes/rbsimple-DE/theme_engine_test.xml | 259 +++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 themes/rbsimple-DE/theme_engine_test.xml diff --git a/themes/rbsimple-DE/capabilities.xml b/themes/rbsimple-DE/capabilities.xml index 16eee7d9b..907dffc42 100644 --- a/themes/rbsimple-DE/capabilities.xml +++ b/themes/rbsimple-DE/capabilities.xml @@ -42,6 +42,11 @@ Theme capabilities for rbsimple-DE. + + + true + + false diff --git a/themes/rbsimple-DE/theme.xml b/themes/rbsimple-DE/theme.xml index 278f503d8..9bc1bb009 100644 --- a/themes/rbsimple-DE/theme.xml +++ b/themes/rbsimple-DE/theme.xml @@ -92,6 +92,11 @@ based on: 'recalbox-multi' by the Recalbox community + + ./colors_dark.xml + ./theme_engine_test.xml + + true diff --git a/themes/rbsimple-DE/theme_engine_test.xml b/themes/rbsimple-DE/theme_engine_test.xml new file mode 100644 index 000000000..89ca5031c --- /dev/null +++ b/themes/rbsimple-DE/theme_engine_test.xml @@ -0,0 +1,259 @@ + + + + 00000099 + F0F0F0 + + + D6D6D6 + 55555500 + + + D6D6D6 + 555555D8 + + + B6B6B6 + + + E6E6E6 + 55555500 + + + E6E6E6 + 55555500 + + + E6E6E6 + 55555500 + + + D6D6D6 + + + + 000000 + + + B6B6B6 + + + + + + D6D6D6 + D6D6D6 + 969696 + 969696 + + + + + + + false + + + false + + + false + + + false + + + + + + vertical_wheel + 0.25 1.0 + 0. 0 + ./${system.theme}/images/logo.svg + 1.23 + 0.15 0.125 + 6.5 + -5 0.4 + left + ./core/fonts/Exo2-RegularCondensed.otf + 0.070 + uppercase + 1.2 + ${system.fullName} + 10 + + + ./core/fonts/Exo2-RegularCondensed.otf + 0.048 + 0.26 0.09 + 0.35 0.1 + 0 0 + uppercase + left + gamecount + 50 + + + + ./core/fonts/Exo2-RegularCondensed.otf + 0.035 + 0.42 0.5437 + 0.25 0.056 + 0.5 0.5 + uppercase + center + gamecount_favorites + 50 + false + + + + ./core/fonts/Exo2-RegularCondensed.otf + 0.085 + 0.26 0.01 + 0.5 0.1 + 0 0 + uppercase + left + fullname + 50 + + + + ./core/fonts/Exo2-RegularCondensed.otf + 0.045 + 0.775 0.60 + 0.2 0.05 + 0 0 + none + selector_recent + right + name + 50 + + + + ./core/fonts/Exo2-RegularCondensed.otf + 0.035 + 0.775 0.03 + 0.2 0 + 0 0 + uppercase + right + Last played + 50 + + + + lastplayed + 3 + + + + random + 1 + + + + 0 0 + 0 0 + 1 1 + selector_random + fanart, titlescreen + nearest + true + 3 + 0.5 + + + + 0 0 + 0.775 0.08 + 0.2 0 + selector_recent + cover + nearest + true + 3 + 1 + + + + 0.528 0.954 + + + + false + + + false + + + + 0.58 0.695 + right + + + 0.58 0.716 + right + + + 0.58 0.737 + right + + + 0.58 0.758 + right + + + 0.58 0.799 + right + + + 0.58 0.820 + right + + + 0.58 0.841 + right + + + 0.58 0.862 + right + + + 0.58 0.883 + right + + + 0.58 0.904 + right + + + + + + 0.5 0.5 + 0.63 0.45 + 0.360 0.424 + image + nearest + true + + + + \ No newline at end of file From d6c6ea839c96b944778829e015eafaf2956e4f46 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 14 Feb 2022 20:13:52 +0100 Subject: [PATCH 59/82] Documentation update. --- CHANGELOG.md | 4 ++- THEMES-DEV.md | 67 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e51652d00..a2b37947e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,10 @@ * Made gamelist theming much more flexible by allowing any number of elements of any types to be defined * Deprecated multiple older theming concepts like features, extras and hardcoded metadata attributes * Reorganized the UI Settings menu a bit and added entries to set the variant and aspect ratio for newer theme sets -* Added support for defining what type of image metadata to use for all image elements (and also for the video component static image) +* Added support for defining which types of game media to use for all image elements (and also for the video component static image) * Added a legacy (backward compatibility) mode for still supporting older RetroPie EmulationStation themes * Added theme support for Lottie animations (vector graphics) +* Added a GameSelectorComponent for displaying game media and metadata in the system view * Replaced the forceUppercase theme property with a more versatile letterCase property (forceUppercase is retained for legacy theme compatibility) * Made it possible to set any text element as a scrollable container using either metadata values or literal strings * Added support for defining the scrollable container speed, start delay and reset delay from the theme configuration @@ -27,6 +28,7 @@ * Improved theme element placement by replacing the "alignment" and "logoAlignment" properties with specific horizontal and vertical properties * Made it possible to use almost all game metadata field when theming text elements * Made it possible to set the image interpolation method (nearest neighbor or linear filtering) per image from the theme configuration +* Added support for using unsigned integers for theme properties * Added scraper support for displaying the returned platform if it does not match the game platform, or if multiple platforms are defined for the system * Added scraping of fan art and updated the media viewer to display these images * Added scraping of box back covers when using TheGamesDB diff --git a/THEMES-DEV.md b/THEMES-DEV.md index c8181df63..861684b68 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -732,6 +732,7 @@ This section describes each element and their properties in detail and also cont * PATH - a path. If the first character is a `~`, it will be expanded into the environment variable for the home path (`$HOME` for Unix and macOS or `%HOMEPATH%` for Windows) unless overridden using the --home command line option. If the first character is a `.`, it will be expanded to the theme file's directory, allowing you to specify resources relative to the theme file, like so: `./../core/fonts/myfont.ttf`. * BOOLEAN - `true`/`1` or `false`/`0`. * COLOR - a hexidecimal RGB or RGBA color (6 or 8 digits). If 6 digits, will assume the alpha channel is `FF` (completely opaque). +* UNSIGNED_INTEGER - an unsigned integer. * FLOAT - a decimal. * STRING - a string of text. @@ -768,14 +769,12 @@ Properties: - Point around which the image will be rotated. - Default is `0.5 0.5` * `path` - type: PATH - - Path to the image file. Most common extensions are supported (including .jpg, .png, and unanimated .gif). + - Explicit path to an image file. Most common extensions are supported (including .jpg, .png, and unanimated .gif). If `imageType` is also defined then this will take precedence as these two properties are not intended to be used together. If you need a fallback image in case of missing game media, use the `default` property instead. * `default` - type: PATH - - Path to a default image file. The default image will be displayed when the selected game does not have an image of the type defined by the `imageType` property. It's also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. -* `tile` - type: BOOLEAN - - If true, the image will be tiled instead of stretched to fit its size. Useful for backgrounds. + - Path to a default image file. The default image will be displayed when the selected game does not have an image of the type defined by the `imageType` property (i.e. this `default` property does nothing unless a valid `imageType` property has been set). It's also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. * `imageType` - type: STRING - - This displays a game image of a certain media type. If no image of the requested type is found, the space will be left blank unless the `default` property has been set. If the imageType property is set to an invalid value, no image will be displayed even if a `default` property has been defined. - - Possible values: + - This displays a game image of a certain media type. Multiple types can be defined, in which case the entries should be delimited by commas or by whitespace characters (tabs, spaces or line breaks). The media will be searched for in the order that the entries have been defined. If no image is found, the space will be left blank unless the `default` property has been set. To use this property from the `system` view, you will first need to add a `gameselector` element. + - Valid values: - `image` - This will look for a `miximage`, and if that is not found `screenshot` is tried next, then `titlescreen` and finally `cover` - `miximage` - This will look for a miximage. - `marquee` - This will look for a marquee (wheel) image. @@ -785,6 +784,10 @@ Properties: - `backcover` - This will look for a box back cover image. - `3dbox` - This will look for a 3D box image. - `fanart` - This will look for a fan art image. +* `gameselector` - type: STRING + - If more than one gameselector elements have been defined, this property makes it possible to state which one to use. If multiple gameselector elements have been defined and this property is missing then the first entry will be chosen and a warning message will be logged. If only a single gameselector has been defined, this property is ignored. The value of this property must match the `name` attribute value of the gameselector element. This property is only needed for the `system` view and only if the `imageType` property is utilized. +* `tile` - type: BOOLEAN + - If true, the image will be tiled instead of stretched to fit its size. Useful for backgrounds. * `interpolation` - type: STRING - Interpolation method to use when scaling raster images. Nearest neighbor (`nearest`) preserves sharp pixels and linear filtering (`linear`) makes the image smoother. This property has no effect on scalable vector graphics (SVG) images. - Valid values are `nearest` or `linear` @@ -795,7 +798,7 @@ Properties: - Works exactly in the same way as `color` but can be set as the end color to apply a color shift gradient to the image. * `gradientType` - type: STRING - The direction to apply the color shift gradient if both `color` and `colorEnd` have been defined. - - Possible values are `horizontal` or `vertical` + - Valid values are `horizontal` or `vertical` - Default is `horizontal` * `scrollFadeIn` - type: BOOLEAN - If enabled, a short fade-in animation will be applied when scrolling through games in the gamelist view. This usually looks best if used for the main game image. @@ -815,13 +818,13 @@ Properties: Plays a video and provides support for displaying a static image for a defined time period before starting the video player. Although an unlimited number of videos could in theory be defined per view it's strongly recommended to keep it at a single instance. Playing multiple videos simultaneously takes a lot of CPU resources and ES-DE currently has no audio mixing capabilities so the audio would not play correctly. -**Supported views:** +Supported views: * `gamelist` -**Instances per view:** +Instances per view: * `unlimited` (but recommended to keep at a single instance) -**Properties:** +Properties: * `pos` - type: NORMALIZED_PAIR * `size` - type: NORMALIZED_PAIR - If only one axis is specified (and the other is zero), the other will be automatically calculated in accordance with the video's aspect ratio. @@ -839,12 +842,12 @@ Plays a video and provides support for displaying a static image for a defined t * `path` - type: PATH - Path to a video file. Setting a value for this property will make the video static, i.e. it will only play this video regardless of whether there is a game video available or not. If the `default` property has also been set, it will be overridden as the `path` property takes precedence. * `default` - type: PATH - - Path to a default video file. The default video will be played (unless the `path` property has been set) when the selected game does not have a video. If an image type has been defined using `imageType` that image will be searched for first and only if no such image could be found this `default` video will be shown. This property is also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. + - Path to a default video file. The default video will be played when the selected game does not have a video. This property is also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. * `defaultImage` - type: PATH - - This works exactly as the `default` property but it allows displaying an image instead of playing a video. + - Path to a default image file. The default image will be displayed when the selected game does not have an image of the type defined by the `imageType` property (i.e. this `default` property does nothing unless a `imageType` property has been set). It's also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. * `imageType` - type: STRING - - This displays a game image of a certain media type. This occurs if there is no video found for the game and the `path` property has not been set, or if a video start delay has been defined via the `delay` attribute. - - Possible values: + - This displays a game image of a certain media type. Multiple types can be defined, in which case the entries should be delimited by commas or by whitespace characters (tabs, spaces or line breaks). The media will be searched for in the order that the entries have been defined. If no image is found, the space will be left blank unless the `default` property has been set. To use this property from the `system` view, you will first need to add a `gameselector` element. If `delay` is set to zero, then this property is ignored. + - Valid values: - `image` - This will look for a `miximage`, and if that is not found `screenshot` is tried next, then `titlescreen` and finally `cover` - `miximage` - This will look for a miximage. - `marquee` - This will look for a marquee (wheel) image. @@ -959,10 +962,10 @@ Properties: * `direction` - type: STRING - Valid values are "row" or "column". Controls the primary layout direction (line axis) for the badges. Lines will fill up in the specified direction. - Default is `row` -* `lines` - type: FLOAT +* `lines` - type: UNSIGNED_INTEGER - The number of lines available. - Default is `2` -* `itemsPerLine` - type: FLOAT +* `itemsPerLine` - type: UNSIGNED_INTEGER - Number of badges that fit on a line. When more badges are available a new line will be started. - Default is `4` * `itemMargin` - type: NORMALIZED_PAIR @@ -1071,7 +1074,7 @@ Properties: - `gamecount_games` - Number of games available for the system. Does not print the favorites count. - `gamecount_favorites` - Number of favorite games for the system, may be blank if favorites are not applicable. * `metadata` - type: STRING - - This translates to the metadata values that are available for the game. If an invalid metadata field is defined, it will be printed as a string literal. This property can only be used in the `gamelist` view. + - This translates to the metadata values that are available for the game. If an invalid metadata field is defined, it will be printed as a string literal. To use this property from the `system` view, you will first need to add a `gameselector` element. - Valid values: - `name` - Game name. - `description` - Game description. Should be combined with the `container` property in most cases. @@ -1087,6 +1090,8 @@ Properties: - `playcount` - How many times the game has been played. - `controller` - The controller for the game. Will be blank if none has been selected. - `altemulator` - The alternative emulator for the game. Will be blank if none has been selected. +* `gameselector` - type: STRING + - If more than one gameselector elements have been defined, this property makes it possible to state which one to use. If multiple gameselector elements have been defined and this property is missing then the first entry will be chosen and a warning message will be logged. If only a single gameselector has been defined, this property is ignored. The value of this property must match the `name` attribute value of the gameselector element. This property is only needed for the `system` view and only if the `metadata` property is utilized. * `container` - type: BOOLEAN - Whether the text should be placed inside a scrollable container. Only available for the `gamelist` view. * `containerScrollSpeed` - type: FLOAT @@ -1161,7 +1166,7 @@ Properties: - Default is `0.5 0.5`. * `metadata` - type: STRING - This displays the metadata values that are available for the game. If an invalid metadata field is defined, the text "unknown" will be printed. - - Possible values: + - Valid values: - `releasedate` - The release date of the game. - `lastplayed` - The time the game was last played. This will be displayed as a value relative to the current date and time by default, but can be overridden using the `displayRelative` property. * `fontPath` - type: PATH @@ -1326,7 +1331,7 @@ Properties: - Default is `FFFFFFD8` * `gradientType` - type: STRING - The direction to apply the color gradient if both `color` and `colorEnd` have been defined. - - Possible values are `horizontal` or `vertical` + - Valid values are `horizontal` or `vertical` - Default is `horizontal` * `logo` - type: PATH - Path to the logo image file. Most common extensions are supported (including .jpg, .png, and unanimated .gif). @@ -1355,7 +1360,7 @@ Properties: - Sets `logo` and `text` alignment relative to the carousel on the Y axis, which applies when `type` is "horizontal", "horizontal_wheel" or "vertical_wheel". - Valid values are `top`, `center` or `bottom` - Default is `center` -* `maxLogoCount` - type: FLOAT +* `maxLogoCount` - type: UNSIGNED_INTEGER - Sets the number of logos to display in the carousel. - Minimum value is `2` and maximum value is `30` - Default is `3` @@ -1408,7 +1413,7 @@ Properties: - Default is `000000FF` * `selectorGradientType` - type: STRING - The direction to apply the color gradient if both `selectorColor` and `selectorColorEnd` have been defined. - - Possible values are `horizontal` or `vertical` + - Valid values are `horizontal` or `vertical` - Default is `horizontal` * `selectorImagePath` - type: PATH - Path to image to render in place of "selector bar." @@ -1439,6 +1444,26 @@ Properties: - z-index value for element. Elements will be rendered in order of zIndex value from low to high. - Default is `50` +#### gameselector + +Selects games from the gamelists when navigating the `system` view. This makes it possible to display game media and game metadata directly from this view. It's possible to make separate gameselector configurations per game system, so that for instance a random game could be displayed for one system and the most recently played game could be displayed for another system. It's also possible to define multiple gameselector elements with different selection criterias per game system which makes it possible to for example set a random fan art background image and at the same time display a box cover image of the most recently played game. The gameselector logic can be used for the `image` and `text` elements. + +Supported views: +* `system` + +Instances per view: +* `unlimited` + +Properties: +* `selection` - type: STRING + - This defines the game selection criteria. If set to `random`, the games are refreshed every time the view is navigated. For the other two values the refresh takes place on gamelist reload, i.e. when launching a game, adding a game as favorite, making changes via the metadata editor and so on. + - Valid values are `random`, `lastplayed` or `mostplayed` + - Default is `random` +* `gameCount` - type: UNSIGNED_INTEGER + - How many games to select. This property is only intended for future use. + - Minimum value is `1` and maximum value is `30` + - Default is `1` + #### helpsystem The help system is a special element that displays a context-sensitive list of actions the user can take at any time. You should try and keep the position constant throughout every screen. Note that this element does not have a zIndex value, instead it's always rendered on top of all other elements. From a9d1f6e30754144a980cf3baf0e778c41cbbe1ef Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 15 Feb 2022 21:26:40 +0100 Subject: [PATCH 60/82] Added support for setting the video fade-in time via the theme configuration. --- es-core/src/ThemeData.cpp | 1 + es-core/src/components/VideoComponent.cpp | 11 ++++++++--- es-core/src/components/VideoComponent.h | 3 ++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 1a1501931..c0248fc29 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -114,6 +114,7 @@ std::map> {"pillarboxes", BOOLEAN}, {"scanlines", BOOLEAN}, {"delay", FLOAT}, + {"fadeInTime", FLOAT}, {"scrollFadeIn", BOOLEAN}, {"opacity", FLOAT}, {"visible", BOOLEAN}, diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 0844a9fab..dbf8e42b6 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -41,6 +41,7 @@ VideoComponent::VideoComponent() , mRenderScanlines {false} , mLegacyTheme {false} , mFadeIn {1.0f} + , mFadeInTime {1000.0f} { // Setup the default configuration. mConfig.showSnapshotDelay = false; @@ -294,6 +295,9 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, else if (elem->has("showSnapshotDelay")) mConfig.showSnapshotDelay = elem->get("showSnapshotDelay"); + if (properties && elem->has("fadeInTime")) + mFadeInTime = glm::clamp(elem->get("fadeInTime"), 0.0f, 8.0f) * 1000.0f; + if (properties && elem->has("imageType")) { std::string imageTypes {elem->get("imageType")}; for (auto& character : imageTypes) { @@ -334,8 +338,9 @@ void VideoComponent::update(int deltaTime) manageState(); - // Fade in videos, the time period is a bit different between the screensaver, - // media viewer and gamelist view. + // Fade in videos, the time period is a bit different between the screensaver and media viewer. + // For the theme controlled videos in the gamelist and system views, the fade-in time is set + // via the theme configuration. if (mScreensaverMode && mFadeIn < 1.0f) { mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast(SCREENSAVER_FADE_IN_TIME)), 0.0f, 1.0f); @@ -345,7 +350,7 @@ void VideoComponent::update(int deltaTime) 0.0f, 1.0f); } else if (mFadeIn < 1.0f) { - mFadeIn = glm::clamp(mFadeIn + 0.01f, 0.0f, 1.0f); + mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast(mFadeInTime)), 0.0f, 1.0f); } GuiComponent::update(deltaTime); diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index cdefc1f80..32eb4a4e1 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -145,7 +145,8 @@ protected: bool mDrawPillarboxes; bool mRenderScanlines; bool mLegacyTheme; - float mFadeIn; // Used for fading in the video screensaver. + float mFadeIn; + float mFadeInTime; Configuration mConfig; }; From 5ac6bcb9024de4103a8609762a3fb6a2799eac20 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 15 Feb 2022 22:13:11 +0100 Subject: [PATCH 61/82] Added opacity support to the scanline shader. --- es-core/src/components/VideoComponent.cpp | 6 +----- .../src/components/VideoFFmpegComponent.cpp | 7 ++++++- es-core/src/renderers/Renderer_GL21.cpp | 4 ++-- resources/shaders/glsl/scanlines.glsl | 19 +++++++++++-------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index dbf8e42b6..13fa9e0b8 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -311,12 +311,8 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, if (elem->has("pillarboxes")) mDrawPillarboxes = elem->get("pillarboxes"); - // Scanlines are not compatible with video transparency. - if (elem->has("scanlines")) { + if (elem->has("scanlines")) mRenderScanlines = elem->get("scanlines"); - if (mRenderScanlines && mThemeOpacity != 0.0f) - mThemeOpacity = 1.0f; - } if (elem->has("scrollFadeIn") && elem->get("scrollFadeIn")) mComponentThemeFlags |= ComponentThemeFlags::SCROLL_FADE_IN; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 825c042d8..aba309809 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -216,9 +216,14 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) // or the video screensaver, then skip this as the scanline rendering is then handled // in those modules as a postprocessing step. if (!mScreensaverMode && !mMediaViewerMode) { + vertices[0].opacity = mFadeIn * mThemeOpacity; if ((mLegacyTheme && Settings::getInstance()->getBool("GamelistVideoScanlines")) || - (!mLegacyTheme && mRenderScanlines)) + (!mLegacyTheme && mRenderScanlines)) { vertices[0].shaders = Renderer::SHADER_SCANLINES; + } + else { + vertices[0].shaders = Renderer::SHADER_OPACITY; + } } #endif diff --git a/es-core/src/renderers/Renderer_GL21.cpp b/es-core/src/renderers/Renderer_GL21.cpp index 199edb421..baced99e9 100644 --- a/es-core/src/renderers/Renderer_GL21.cpp +++ b/es-core/src/renderers/Renderer_GL21.cpp @@ -352,6 +352,7 @@ namespace Renderer if (runShader) { runShader->activateShaders(); runShader->setModelViewProjectionMatrix(getProjectionMatrix() * trans); + runShader->setOpacity(vertices->opacity); runShader->setTextureSize({shaderWidth, shaderHeight}); GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices)); runShader->deactivateShaders(); @@ -363,8 +364,7 @@ namespace Renderer if (runShader) { runShader->activateShaders(); runShader->setModelViewProjectionMatrix(getProjectionMatrix() * trans); - if (vertices->opacity < 1.0f) - runShader->setOpacity(vertices->opacity); + runShader->setOpacity(vertices->opacity); GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, numVertices)); runShader->deactivateShaders(); } diff --git a/resources/shaders/glsl/scanlines.glsl b/resources/shaders/glsl/scanlines.glsl index 3d92c4e58..91d56ea0f 100644 --- a/resources/shaders/glsl/scanlines.glsl +++ b/resources/shaders/glsl/scanlines.glsl @@ -93,6 +93,7 @@ uniform COMPAT_PRECISION int FrameCount; uniform COMPAT_PRECISION vec2 OutputSize; uniform COMPAT_PRECISION vec2 TextureSize; uniform COMPAT_PRECISION vec2 InputSize; +uniform COMPAT_PRECISION float opacity = 1.0; uniform sampler2D Texture; COMPAT_VARYING vec4 TEX0; COMPAT_VARYING vec2 onex; @@ -126,10 +127,11 @@ uniform COMPAT_PRECISION float OutputGamma; #define TEX2D(coords) GAMMA_IN(COMPAT_TEXTURE(Source, coords)) // Macro for weights computing. -#define WEIGHT(w) \ -if (w > 1.0) w = 1.0; \ -w = 1.0 - w * w; \ -w = w * w; +#define WEIGHT(w) \ + if (w > 1.0) \ + w = 1.0; \ + w = 1.0 - w * w; \ + w = w * w; void main() { @@ -143,7 +145,7 @@ void main() float h_weight_00 = dx / SPOT_WIDTH; WEIGHT(h_weight_00); - color *= vec4( h_weight_00, h_weight_00, h_weight_00, h_weight_00 ); + color *= vec4(h_weight_00, h_weight_00, h_weight_00, h_weight_00); // Get closest horizontal neighbour to blend. vec2 coords01; @@ -184,12 +186,13 @@ void main() WEIGHT(v_weight_10); color = color + colorNB * vec4(v_weight_10 * h_weight_00, v_weight_10 * h_weight_00, - v_weight_10 * h_weight_00, v_weight_10 * h_weight_00); + v_weight_10 * h_weight_00, v_weight_10 * h_weight_00); colorNB = TEX2D(texture_coords + coords01 + coords10); color = color + colorNB * vec4(v_weight_10 * h_weight_01, v_weight_10 * h_weight_01, - v_weight_10 * h_weight_01, v_weight_10 * h_weight_01); + v_weight_10 * h_weight_01, v_weight_10 * h_weight_01); color *= vec4(COLOR_BOOST); - FragColor = clamp(GAMMA_OUT(color), 0.0, 1.0); + vec4 colorTemp = clamp(GAMMA_OUT(color), 0.0, 1.0); + FragColor = vec4(colorTemp.rgb, colorTemp.a * opacity); } #endif From 8f6565b9f06163f8849f80a27cda99bf294f2da5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 15 Feb 2022 22:17:24 +0100 Subject: [PATCH 62/82] Code formatted the blur GLSL shaders. --- resources/shaders/glsl/blur_horizontal.glsl | 44 +++++++++++++-------- resources/shaders/glsl/blur_vertical.glsl | 44 +++++++++++++-------- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/resources/shaders/glsl/blur_horizontal.glsl b/resources/shaders/glsl/blur_horizontal.glsl index be0da5559..87e8e835c 100644 --- a/resources/shaders/glsl/blur_horizontal.glsl +++ b/resources/shaders/glsl/blur_horizontal.glsl @@ -88,10 +88,10 @@ COMPAT_VARYING vec4 TEX0; void main() { - vec2 texcoord = vTexCoord; + vec2 texcoord = vTexCoord; vec2 PIXEL_SIZE = vec2(SourceSize.z, SourceSize.w); - #if __VERSION__ < 130 +#if __VERSION__ < 130 float sampleOffsets1 = 0.0; float sampleOffsets2 = 1.4347826; float sampleOffsets3 = 3.3478260; @@ -108,28 +108,40 @@ void main() vec4 color = COMPAT_TEXTURE(Source, texcoord); color = vec4(color.rgb, 1.0) * sampleWeights1; - color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights2; - color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights2; + color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights2; + color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets2 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights2; - color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights3; - color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights3; + color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights3; + color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets3 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights3; - color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights4; - color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights4; + color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights4; + color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets4 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights4; - color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights5; - color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights5; - #else + color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights5; + color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets5 * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights5; +#else - float sampleOffsets[5] = { 0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130 }; - float sampleWeights[5] = { 0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196 }; + float sampleOffsets[5] = {0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130}; + float sampleWeights[5] = {0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196}; vec4 color = COMPAT_TEXTURE(Source, texcoord) * sampleWeights[0]; for (int i = 1; i < 5; i++) { - color += COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights[i]; - color += COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) * sampleWeights[i]; + color += + COMPAT_TEXTURE(Source, texcoord + vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights[i]; + color += + COMPAT_TEXTURE(Source, texcoord - vec2(sampleOffsets[i] * HW * PIXEL_SIZE.x, 0.0)) * + sampleWeights[i]; } - #endif +#endif FragColor = vec4(color); } diff --git a/resources/shaders/glsl/blur_vertical.glsl b/resources/shaders/glsl/blur_vertical.glsl index ad59a4c21..4040a6c4c 100644 --- a/resources/shaders/glsl/blur_vertical.glsl +++ b/resources/shaders/glsl/blur_vertical.glsl @@ -88,10 +88,10 @@ COMPAT_VARYING vec4 TEX0; void main() { - vec2 texcoord = vTexCoord; + vec2 texcoord = vTexCoord; vec2 PIXEL_SIZE = vec2(SourceSize.z, SourceSize.w); - #if __VERSION__ < 130 +#if __VERSION__ < 130 float sampleOffsets1 = 0.0; float sampleOffsets2 = 1.4347826; float sampleOffsets3 = 3.3478260; @@ -108,28 +108,40 @@ void main() vec4 color = COMPAT_TEXTURE(Source, texcoord); color = vec4(color.rgb, 1.0) * sampleWeights1; - color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) * sampleWeights2; - color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) * sampleWeights2; + color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) * + sampleWeights2; + color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets2 * VW * PIXEL_SIZE.y)) * + sampleWeights2; - color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) * sampleWeights3; - color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) * sampleWeights3; + color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) * + sampleWeights3; + color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets3 * VW * PIXEL_SIZE.y)) * + sampleWeights3; - color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) * sampleWeights4; - color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) * sampleWeights4; + color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) * + sampleWeights4; + color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets4 * VW * PIXEL_SIZE.y)) * + sampleWeights4; - color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) * sampleWeights5; - color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) * sampleWeights5; - #else + color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) * + sampleWeights5; + color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets5 * VW * PIXEL_SIZE.y)) * + sampleWeights5; +#else - float sampleOffsets[5] = { 0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130 }; - float sampleWeights[5] = { 0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196 }; + float sampleOffsets[5] = {0.0, 1.4347826, 3.3478260, 5.2608695, 7.1739130}; + float sampleWeights[5] = {0.16818994, 0.27276957, 0.11690125, 0.024067905, 0.0021112196}; vec4 color = COMPAT_TEXTURE(Source, texcoord) * sampleWeights[0]; for (int i = 1; i < 5; i++) { - color += COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) * sampleWeights[i]; - color += COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) * sampleWeights[i]; + color += + COMPAT_TEXTURE(Source, texcoord + vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) * + sampleWeights[i]; + color += + COMPAT_TEXTURE(Source, texcoord - vec2(0.0, sampleOffsets[i] * VW * PIXEL_SIZE.y)) * + sampleWeights[i]; } - #endif +#endif FragColor = vec4(color); } From 0266a6e7e6239034d9810f7ea975ba1a33600db0 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 15 Feb 2022 22:34:26 +0100 Subject: [PATCH 63/82] Fixed a video fade-in issue when using the GLES renderer. --- es-core/src/components/VideoFFmpegComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index aba309809..451796dc3 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -147,8 +147,8 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) unsigned int rectColor {0x000000FF}; if (mThemeOpacity != 1.0f) { - color = (static_cast(mThemeOpacity * 255.0f) << 24) + 0x00FFFFFF; - rectColor = static_cast(mThemeOpacity * 255.0f); + color = (static_cast(mThemeOpacity * mFadeIn * 255.0f) << 24) + 0x00FFFFFF; + rectColor = static_cast(mThemeOpacity * mFadeIn * 255.0f); } // Render the black rectangle behind the video. From ee1a0f7cd3e342de41762677b402e14c156dade5 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 16 Feb 2022 19:32:02 +0100 Subject: [PATCH 64/82] Added support for using the tilde symbol for ROM path tags in es_systems.xml --- es-app/src/SystemData.cpp | 3 +++ es-core/src/utils/FileSystemUtil.cpp | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index f52f52efb..447a740ab 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -483,6 +483,9 @@ bool SystemData::loadConfig() #endif path = Utils::String::replace(path, "//", "/"); + // In case ~ is used, expand it to the home directory path. + path = Utils::FileSystem::expandHomePath(path); + // Check that the ROM directory for the system is valid or otherwise abort the // processing. if (!Utils::FileSystem::exists(path)) { diff --git a/es-core/src/utils/FileSystemUtil.cpp b/es-core/src/utils/FileSystemUtil.cpp index 3c0ace8aa..e67142925 100644 --- a/es-core/src/utils/FileSystemUtil.cpp +++ b/es-core/src/utils/FileSystemUtil.cpp @@ -464,10 +464,7 @@ namespace Utils std::string expandHomePath(const std::string& path) { // Expand home path if ~ is used. - std::string expandedPath = path; - - expandedPath = Utils::String::replace(path, "~", Utils::FileSystem::getHomePath()); - return expandedPath; + return Utils::String::replace(path, "~", Utils::FileSystem::getHomePath()); } std::string resolveRelativePath(const std::string& path, From c4eb1b8b970e38188a64f20567f4c3568930660d Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 17:04:23 +0100 Subject: [PATCH 65/82] Greatly simplified the video controls code. Also fixed a cosmetic issue with carousel fade transitions. --- es-app/src/FileData.cpp | 2 +- es-app/src/MediaViewer.cpp | 10 +- es-app/src/Screensaver.cpp | 8 +- es-app/src/guis/GuiGamelistOptions.cpp | 2 + es-app/src/guis/GuiMenu.cpp | 2 + es-app/src/guis/GuiMetaDataEd.cpp | 2 - es-app/src/guis/GuiScraperMulti.cpp | 1 - es-app/src/scrapers/Scraper.cpp | 2 +- es-app/src/views/GamelistBase.cpp | 13 +- es-app/src/views/GamelistLegacy.h | 27 +- es-app/src/views/GamelistView.cpp | 43 ++- es-app/src/views/GamelistView.h | 22 ++ es-app/src/views/ViewController.cpp | 16 +- es-app/src/views/ViewController.h | 6 + es-core/src/GuiComponent.cpp | 48 ---- es-core/src/GuiComponent.h | 17 +- es-core/src/Window.cpp | 42 +-- es-core/src/Window.h | 7 +- es-core/src/components/VideoComponent.cpp | 267 ++++-------------- es-core/src/components/VideoComponent.h | 58 ++-- .../src/components/VideoFFmpegComponent.cpp | 80 +++--- es-core/src/components/VideoFFmpegComponent.h | 20 +- 22 files changed, 229 insertions(+), 466 deletions(-) diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 2af22e8e4..5e7d6a604 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -1196,7 +1196,7 @@ void FileData::launchGame() // This blocks the video player, stops the scrolling of game names and descriptions and // keeps the screensaver from getting activated. if (runInBackground) - window->setLaunchedGame(); + window->setLaunchedGame(true); // Normalize deltaTime so that the screensaver does not start immediately // when returning from the game. window->normalizeNextUpdate(); diff --git a/es-app/src/MediaViewer.cpp b/es-app/src/MediaViewer.cpp index ca56f5b66..08592341d 100644 --- a/es-app/src/MediaViewer.cpp +++ b/es-app/src/MediaViewer.cpp @@ -43,9 +43,6 @@ bool MediaViewer::startMediaViewer(FileData* game) initiateViewer(); - if (mHasVideo) - ViewController::getInstance()->onPauseVideo(); - if (mHasVideo || mHasImages) return true; else @@ -55,7 +52,7 @@ bool MediaViewer::startMediaViewer(FileData* game) void MediaViewer::stopMediaViewer() { NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); - ViewController::getInstance()->onStopVideo(); + ViewController::getInstance()->stopViewVideos(); if (mVideo) { delete mVideo; @@ -257,10 +254,9 @@ void MediaViewer::playVideo() return; mDisplayingImage = false; - ViewController::getInstance()->onStopVideo(); + ViewController::getInstance()->pauseViewVideos(); mVideo = new VideoFFmpegComponent; - mVideo->topWindow(true); mVideo->setOrigin(0.5f, 0.5f); mVideo->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f); @@ -271,7 +267,7 @@ void MediaViewer::playVideo() mVideo->setVideo(mVideoFile); mVideo->setMediaViewerMode(true); - mVideo->onShow(); + mVideo->startVideoPlayer(); } void MediaViewer::showImage(int index) diff --git a/es-app/src/Screensaver.cpp b/es-app/src/Screensaver.cpp index 6dca52057..439f89fae 100644 --- a/es-app/src/Screensaver.cpp +++ b/es-app/src/Screensaver.cpp @@ -59,6 +59,8 @@ Screensaver::~Screensaver() void Screensaver::startScreensaver(bool generateMediaList) { + ViewController::getInstance()->pauseViewVideos(); + std::string path = ""; std::string screensaverType = Settings::getInstance()->getString("ScreensaverType"); mHasMediaFiles = false; @@ -157,7 +159,6 @@ void Screensaver::startScreensaver(bool generateMediaList) generateOverlayInfo(); mVideoScreensaver = new VideoFFmpegComponent; - mVideoScreensaver->topWindow(true); mVideoScreensaver->setOrigin(0.5f, 0.5f); mVideoScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f); @@ -171,7 +172,7 @@ void Screensaver::startScreensaver(bool generateMediaList) mVideoScreensaver->setVideo(path); mVideoScreensaver->setScreensaverMode(true); - mVideoScreensaver->onShow(); + mVideoScreensaver->startVideoPlayer(); mTimer = 0; return; } @@ -197,6 +198,8 @@ void Screensaver::stopScreensaver() if (mGameOverlay) mGameOverlay.reset(); + + ViewController::getInstance()->startViewVideos(); } void Screensaver::nextGame() @@ -215,6 +218,7 @@ void Screensaver::launchGame() ViewController::getInstance()->getGamelistView(mCurrentGame->getSystem()).get(); view->setCursor(mCurrentGame); ViewController::getInstance()->cancelViewTransitions(); + ViewController::getInstance()->pauseViewVideos(); } } diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index 5761cc20a..06469887a 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -254,6 +254,8 @@ GuiGamelistOptions::~GuiGamelistOptions() // the menu has been closed. ViewController::getInstance()->stopScrolling(); + ViewController::getInstance()->startViewVideos(); + if (mFiltersChanged) { if (!mCustomCollectionSystem) { ViewController::getInstance()->reloadGamelistView(mSystem); diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 584ceb441..5839a6032 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -87,6 +87,8 @@ GuiMenu::~GuiMenu() // was openened. Without this, the scrolling would run until manually stopped after // the menu has been closed. ViewController::getInstance()->stopScrolling(); + + ViewController::getInstance()->startViewVideos(); } void GuiMenu::openScraperOptions() diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 950a0a3a0..94b3e93ec 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -514,7 +514,6 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md, buttons.push_back(std::make_shared("SAVE", "save metadata", [&] { save(); - ViewController::getInstance()->onPauseVideo(); delete this; })); buttons.push_back( @@ -841,7 +840,6 @@ void GuiMetaDataEd::close() CollectionSystemsManager::getInstance()->refreshCollectionSystems(mScraperParams.game); mWindow->invalidateCachedBackground(); } - ViewController::getInstance()->onPauseVideo(); delete this; }; diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp index 10713d660..4b9f50d7c 100644 --- a/es-app/src/guis/GuiScraperMulti.cpp +++ b/es-app/src/guis/GuiScraperMulti.cpp @@ -175,7 +175,6 @@ GuiScraperMulti::~GuiScraperMulti() (*it)->sortSystem(); } } - ViewController::getInstance()->onPauseVideo(); } void GuiScraperMulti::onSizeChanged() diff --git a/es-app/src/scrapers/Scraper.cpp b/es-app/src/scrapers/Scraper.cpp index 6a42da6d3..b50fa9b82 100644 --- a/es-app/src/scrapers/Scraper.cpp +++ b/es-app/src/scrapers/Scraper.cpp @@ -261,7 +261,7 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, scrapeFiles.push_back(mediaFileInfo); #if defined(_WIN64) // Required due to the idiotic file locking that exists on this operating system. - ViewController::getInstance()->onStopVideo(); + ViewController::getInstance()->stopVideo(); #endif } diff --git a/es-app/src/views/GamelistBase.cpp b/es-app/src/views/GamelistBase.cpp index bb759992a..6d53aba9e 100644 --- a/es-app/src/views/GamelistBase.cpp +++ b/es-app/src/views/GamelistBase.cpp @@ -74,7 +74,7 @@ bool GamelistBase::input(InputConfig* config, Input input) if (config->isMappedTo("a", input)) { FileData* cursor = getCursor(); if (cursor->getType() == GAME) { - onPauseVideo(); + pauseViewVideos(); ViewController::getInstance()->cancelViewTransitions(); stopListScrolling(); launch(cursor); @@ -131,7 +131,7 @@ bool GamelistBase::input(InputConfig* config, Input input) } else { NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND); - onPauseVideo(); + muteViewVideos(); onFocusLost(); stopListScrolling(); SystemData* systemToView = getCursor()->getSystem(); @@ -175,7 +175,7 @@ bool GamelistBase::input(InputConfig* config, Input input) else if (config->isMappedLike(getQuickSystemSelectRightButton(), input)) { if (Settings::getInstance()->getBool("QuickSystemSelect") && SystemData::sSystemVector.size() > 1) { - onPauseVideo(); + muteViewVideos(); onFocusLost(); stopListScrolling(); ViewController::getInstance()->goToNextGamelist(); @@ -185,7 +185,7 @@ bool GamelistBase::input(InputConfig* config, Input input) else if (config->isMappedLike(getQuickSystemSelectLeftButton(), input)) { if (Settings::getInstance()->getBool("QuickSystemSelect") && SystemData::sSystemVector.size() > 1) { - onPauseVideo(); + muteViewVideos(); onFocusLost(); stopListScrolling(); ViewController::getInstance()->goToPrevGamelist(); @@ -457,6 +457,7 @@ bool GamelistBase::input(InputConfig* config, Input input) config->isMappedTo("back", input) && input.value) { ViewController::getInstance()->cancelViewTransitions(); stopListScrolling(); + pauseViewVideos(); mWindow->pushGui(new GuiGamelistOptions(this->mRoot->getSystem())); return true; } @@ -621,7 +622,7 @@ void GamelistBase::generateFirstLetterIndex(const std::vector& files) void GamelistBase::generateGamelistInfo(FileData* cursor, FileData* firstEntry) { // Generate data needed for the gamelistInfo field, which is displayed from the - // gamelist interfaces (Detailed/Video/Grid). + // gamelist interfaces. mIsFiltered = false; mIsFolder = false; FileData* rootFolder {firstEntry->getSystem()->getRootFolder()}; @@ -704,7 +705,7 @@ void GamelistBase::removeMedia(FileData* game) std::string path; // Stop the video player, especially important on Windows as the file would otherwise be locked. - onStopVideo(); + stopViewVideos(); // If there are no media files left in the directory after the deletion, then remove // the directory too. Remove any empty parent directories as well. diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h index bc68bdd7d..b77206d3e 100644 --- a/es-app/src/views/GamelistLegacy.h +++ b/es-app/src/views/GamelistLegacy.h @@ -366,6 +366,12 @@ void GamelistView::legacyUpdateInfoPanel() bool fadingOut = false; if (file == nullptr) { + if (mViewStyle == ViewController::VIDEO) { + mVideoComponents.front()->stopVideoPlayer(); + mVideoComponents.front()->setVideo(""); + if (!mVideoComponents.front()->hasStartDelay()) + mVideoComponents.front()->setImage(""); + } mVideoPlaying = false; fadingOut = true; } @@ -383,15 +389,12 @@ void GamelistView::legacyUpdateInfoPanel() mImageComponents[LegacyImage::MD_MARQUEE]->setImage(mRandomGame->getMarqueePath()); if (mViewStyle == ViewController::VIDEO) { mVideoComponents.front()->setImage(mRandomGame->getImagePath()); - // 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. - mVideoComponents.front()->onHide(); + mVideoComponents.front()->stopVideoPlayer(); if (!mVideoComponents.front()->setVideo(mRandomGame->getVideoPath())) mVideoComponents.front()->setDefaultVideo(); + + mVideoComponents.front()->startVideoPlayer(); } else { mImageComponents[LegacyImage::MD_IMAGE]->setImage(mRandomGame->getImagePath()); @@ -413,10 +416,12 @@ void GamelistView::legacyUpdateInfoPanel() mImageComponents[LegacyImage::MD_MARQUEE]->setImage(file->getMarqueePath()); if (mViewStyle == ViewController::VIDEO) { mVideoComponents.front()->setImage(file->getImagePath()); - mVideoComponents.front()->onHide(); + mVideoComponents.front()->stopVideoPlayer(); if (!mVideoComponents.front()->setVideo(file->getVideoPath())) mVideoComponents.front()->setDefaultVideo(); + + mVideoComponents.front()->startVideoPlayer(); } else { mImageComponents[LegacyImage::MD_IMAGE]->setImage(file->getImagePath()); @@ -550,6 +555,8 @@ void GamelistView::legacyUpdateInfoPanel() comps.emplace_back(mImageComponents[LegacyImage::MD_THUMBNAIL].get()); comps.emplace_back(mImageComponents[LegacyImage::MD_MARQUEE].get()); comps.emplace_back(mImageComponents[LegacyImage::MD_IMAGE].get()); + if (mVideoComponents.size() > 0) + comps.emplace_back(mVideoComponents.front().get()); comps.push_back(mBadgeComponents.front().get()); comps.push_back(mRatingComponents.front().get()); @@ -576,12 +583,6 @@ void GamelistView::legacyUpdate(int deltaTime) mImageComponents[LegacyImage::MD_IMAGE]->finishAnimation(0); if (mViewStyle == ViewController::VIDEO) { - if (!mVideoPlaying) - mVideoComponents.front()->onHide(); - else if (mVideoPlaying && !mVideoComponents.front()->isVideoPaused() && - !mWindow->isScreensaverActive()) - mVideoComponents.front()->onShow(); - if (ViewController::getInstance()->getGameLaunchTriggered() && mVideoComponents.front()->isAnimationPlaying(0)) mVideoComponents.front()->finishAnimation(0); diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index 8dba275ca..c168dab01 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -204,23 +204,6 @@ void GamelistView::update(int deltaTime) } } - for (auto& video : mVideoComponents) { - if (!mVideoPlaying) { - if (!video->getScrollHide()) - video->onHide(); - else if (!video->hasStaticImage()) - video->onHide(); - else if (video->getOpacity() == 0.0f) - video->onHide(); - } - else if (mVideoPlaying && !video->isVideoPaused() && !mWindow->isScreensaverActive()) { - video->onShow(); - } - - if (ViewController::getInstance()->getGameLaunchTriggered() && video->isAnimationPlaying(0)) - video->finishAnimation(0); - } - updateChildren(deltaTime); } @@ -375,6 +358,14 @@ void GamelistView::updateInfoPanel() bool fadingOut = false; if (file == nullptr) { + if (mVideoPlaying) { + for (auto& video : mVideoComponents) { + video->stopVideoPlayer(); + video->setVideo(""); + if (!video->hasStartDelay()) + video->setImage(""); + } + } mVideoPlaying = false; fadingOut = true; } @@ -393,17 +384,14 @@ void GamelistView::updateInfoPanel() for (auto& video : mVideoComponents) { setGameImage(mRandomGame, video.get()); - // 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(); + video->stopVideoPlayer(); if (video->hasStaticVideo()) video->setStaticVideo(); else if (!video->setVideo(mRandomGame->getVideoPath())) video->setDefaultVideo(); + + video->startVideoPlayer(); } } else { @@ -413,10 +401,10 @@ void GamelistView::updateInfoPanel() } for (auto& video : mVideoComponents) { + video->stopVideoPlayer(); video->setImage(""); video->setVideo(""); if (video->hasStaticVideo()) { - video->onStopVideo(); video->setStaticVideo(); } else { @@ -426,17 +414,20 @@ void GamelistView::updateInfoPanel() } } else { - for (auto& image : mImageComponents) + for (auto& image : mImageComponents) { setGameImage(file, image.get()); + } for (auto& video : mVideoComponents) { setGameImage(file, video.get()); - video->onHide(); + video->stopVideoPlayer(); if (video->hasStaticVideo()) video->setStaticVideo(); else if (!video->setVideo(file->getVideoPath())) video->setDefaultVideo(); + + video->startVideoPlayer(); } } diff --git a/es-app/src/views/GamelistView.h b/es-app/src/views/GamelistView.h index fb9304070..4f9ae35f0 100644 --- a/es-app/src/views/GamelistView.h +++ b/es-app/src/views/GamelistView.h @@ -41,6 +41,28 @@ public: } } + void startViewVideos() override + { + for (auto& video : mVideoComponents) + video->startVideoPlayer(); + } + void stopViewVideos() override + { + for (auto& video : mVideoComponents) + video->stopVideoPlayer(); + } + void pauseViewVideos() override + { + for (auto& video : mVideoComponents) { + video->pauseVideoPlayer(); + } + } + void muteViewVideos() override + { + for (auto& video : mVideoComponents) + video->muteVideoPlayer(); + } + const std::shared_ptr getTheme() const { return mTheme; } void setTheme(const std::shared_ptr& theme) { diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 846027d04..64ad24b9c 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -648,11 +648,6 @@ void ViewController::launch(FileData* game) return; } - // If the video view style is used, pause the video currently playing or block the - // video from starting to play if the static image is still shown. - if (mCurrentView) - mCurrentView->onPauseVideo(); - // Disable text scrolling and stop any Lottie animations. These will be enabled again in // FileData upon returning from the game. mWindow->setAllowTextScrolling(false); @@ -806,7 +801,7 @@ bool ViewController::input(InputConfig* config, Input input) if (mWindow->getGameLaunchedState()) { mWindow->setAllowTextScrolling(true); mWindow->setAllowFileAnimation(true); - mWindow->unsetLaunchedGame(); + mWindow->setLaunchedGame(false); // Filter out the "a" button so the game is not restarted if there was such a button press // queued when leaving the game. if (config->isMappedTo("a", input) && input.value != 0) @@ -825,9 +820,12 @@ bool ViewController::input(InputConfig* config, Input input) // to play when we've closed the menu. if (mSystemListView->isSystemAnimationPlaying(0)) mSystemListView->finishSystemAnimation(0); - // Stop the gamelist scrolling as well as it would otherwise - // also continue to run after closing the menu. + // Stop the gamelist scrolling as well as it would otherwise continue to run after + // closing the menu. mCurrentView->stopListScrolling(); + // Pause all videos as they would otherwise continue to play beneath the menu. + mCurrentView->pauseViewVideos(); + // Finally, if the camera is currently moving, reset its position. cancelViewTransitions(); @@ -985,7 +983,7 @@ void ViewController::reloadGamelistView(GamelistView* view, bool reloadTheme) // video player, prevent scrolling of game names and game descriptions and prevent the // screensaver from starting on schedule. if (mWindow->getGameLaunchedState()) - mWindow->setLaunchedGame(); + mWindow->setLaunchedGame(true); // Redisplay the current view. if (mCurrentView) diff --git a/es-app/src/views/ViewController.h b/es-app/src/views/ViewController.h index 1ce4dd95b..eb589ab80 100644 --- a/es-app/src/views/ViewController.h +++ b/es-app/src/views/ViewController.h @@ -66,6 +66,12 @@ public: void cancelViewTransitions(); void stopScrolling(); + // Basic video controls. + void startViewVideos() override { mCurrentView->startViewVideos(); } + void stopViewVideos() override { mCurrentView->stopViewVideos(); } + void pauseViewVideos() override { mCurrentView->pauseViewVideos(); } + void muteViewVideos() override { mCurrentView->muteViewVideos(); } + void onFileChanged(FileData* file, bool reloadGamelist); void triggerGameLaunch(FileData* game) { diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index f0051ea7f..21e6dda22 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -393,51 +393,3 @@ void GuiComponent::onHide() for (unsigned int i = 0; i < getChildCount(); ++i) getChild(i)->onHide(); } - -void GuiComponent::onStopVideo() -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->onStopVideo(); -} - -void GuiComponent::onPauseVideo() -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->onPauseVideo(); -} - -void GuiComponent::onUnpauseVideo() -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->onUnpauseVideo(); -} - -void GuiComponent::onScreensaverActivate() -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->onScreensaverActivate(); -} - -void GuiComponent::onScreensaverDeactivate() -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->onScreensaverDeactivate(); -} - -void GuiComponent::onGameLaunchedActivate() -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->onGameLaunchedActivate(); -} - -void GuiComponent::onGameLaunchedDeactivate() -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->onGameLaunchedDeactivate(); -} - -void GuiComponent::topWindow(bool isTop) -{ - for (unsigned int i = 0; i < getChildCount(); ++i) - getChild(i)->topWindow(isTop); -} diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 8cb03502e..9e2052a70 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -238,19 +238,16 @@ public: virtual void onShow(); virtual void onHide(); - virtual void onStopVideo(); - virtual void onPauseVideo(); - virtual void onUnpauseVideo(); - virtual bool isVideoPaused() { return false; } + + // System view and gamelist view video controls. + virtual void startViewVideos() {} + virtual void stopViewVideos() {} + virtual void pauseViewVideos() {} + virtual void muteViewVideos() {} + // For Lottie animations. virtual void resetFileAnimation() {}; - virtual void onScreensaverActivate(); - virtual void onScreensaverDeactivate(); - virtual void onGameLaunchedActivate(); - virtual void onGameLaunchedDeactivate(); - virtual void topWindow(bool isTop); - // Default implementation just handles and tags as normalized float pairs. // You probably want to keep this behavior for any derived classes as well as add your own. virtual void applyTheme(const std::shared_ptr& theme, diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index b2e91f680..2bf71f6df 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -75,10 +75,6 @@ Window* Window::getInstance() void Window::pushGui(GuiComponent* gui) { - if (mGuiStack.size() > 0) { - auto& top = mGuiStack.back(); - top->topWindow(false); - } mGuiStack.push_back(gui); gui->updateHelpPrompts(); } @@ -90,10 +86,8 @@ void Window::removeGui(GuiComponent* gui) it = mGuiStack.erase(it); // We just popped the stack and the stack is not empty. - if (it == mGuiStack.cend() && mGuiStack.size()) { + if (it == mGuiStack.cend() && mGuiStack.size()) mGuiStack.back()->updateHelpPrompts(); - mGuiStack.back()->topWindow(true); - } return; } @@ -752,10 +746,6 @@ void Window::stopInfoPopup() void Window::startScreensaver() { if (mScreensaver && !mRenderScreensaver) { - // Tell the GUI components the screensaver is starting. - for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it) - (*it)->onScreensaverActivate(); - setAllowTextScrolling(false); setAllowFileAnimation(false); mScreensaver->startScreensaver(true); @@ -771,14 +761,6 @@ bool Window::stopScreensaver() setAllowTextScrolling(true); setAllowFileAnimation(true); - // Tell the GUI components the screensaver has stopped. - for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it) { - (*it)->onScreensaverDeactivate(); - // If the menu is open, pause the video so it won't start playing beneath the menu. - if (mGuiStack.front() != mGuiStack.back()) - (*it)->onPauseVideo(); - } - return true; } @@ -830,10 +812,6 @@ void Window::closeLaunchScreen() mRenderLaunchScreen = false; } -void Window::increaseVideoPlayerCount() { ++mVideoPlayerCount; } - -void Window::decreaseVideoPlayerCount() { --mVideoPlayerCount; } - int Window::getVideoPlayerCount() { int videoPlayerCount; @@ -841,24 +819,6 @@ int Window::getVideoPlayerCount() return videoPlayerCount; } -void Window::setLaunchedGame() -{ - // Tell the GUI components that a game has been launched. - for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it) - (*it)->onGameLaunchedActivate(); - - mGameLaunchedState = true; -} - -void Window::unsetLaunchedGame() -{ - // Tell the GUI components that the user is back in ES-DE again. - for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it) - (*it)->onGameLaunchedDeactivate(); - - mGameLaunchedState = false; -} - void Window::invalidateCachedBackground() { mCachedBackground = false; diff --git a/es-core/src/Window.h b/es-core/src/Window.h index 8f9d12c83..82ec2263c 100644 --- a/es-core/src/Window.h +++ b/es-core/src/Window.h @@ -130,12 +130,11 @@ public: void setLaunchScreen(GuiLaunchScreen* launchScreen) { mLaunchScreen = launchScreen; } bool isLaunchScreenDisplayed() { return mRenderLaunchScreen; } - void increaseVideoPlayerCount(); - void decreaseVideoPlayerCount(); + void increaseVideoPlayerCount() { ++mVideoPlayerCount; } + void decreaseVideoPlayerCount() { --mVideoPlayerCount; } int getVideoPlayerCount(); - void setLaunchedGame(); - void unsetLaunchedGame(); + void setLaunchedGame(bool state) { mGameLaunchedState = state; } void invalidateCachedBackground(); bool isInvalidatingCachedBackground() { return mInvalidateCacheTimer > 0; } diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 13fa9e0b8..39f9cdf54 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -25,43 +25,36 @@ VideoComponent::VideoComponent() , mTargetSize {0.0f, 0.0f} , mVideoAreaPos {0.0f, 0.0f} , mVideoAreaSize {0.0f, 0.0f} - , mStartDelayed {false} + , mStartTime {0} , mIsPlaying {false} , mIsActuallyPlaying {false} - , mPause {false} - , mShowing {false} - , mDisable {false} + , mPaused {false} , mMediaViewerMode {false} - , mScreensaverActive {false} , mScreensaverMode {false} - , mGameLaunched {false} - , mBlockPlayer {false} , mTargetIsMax {false} , mDrawPillarboxes {true} , mRenderScanlines {false} , mLegacyTheme {false} + , mHasVideo {false} , mFadeIn {1.0f} , mFadeInTime {1000.0f} { - // Setup the default configuration. + // Setup default configuration. mConfig.showSnapshotDelay = false; mConfig.showSnapshotNoVideo = false; mConfig.startDelay = 1500; - - if (mWindow->getGuiStackSize() > 1) - topWindow(false); } VideoComponent::~VideoComponent() { // Stop any currently running video. - stopVideo(); + stopVideoPlayer(); } bool VideoComponent::setVideo(std::string path) { // Convert the path into a generic format. - std::string fullPath = Utils::FileSystem::getCanonicalPath(path); + std::string fullPath {Utils::FileSystem::getCanonicalPath(path)}; // Check that it's changed. if (fullPath == mVideoPath) @@ -72,10 +65,17 @@ bool VideoComponent::setVideo(std::string path) // If the file exists then set the new video. if (!fullPath.empty() && ResourceManager::getInstance().fileExists(fullPath)) { + mHasVideo = true; // Return true to show that we are going to attempt to play a video. return true; } + if (!mVideoPath.empty() || !mConfig.defaultVideoPath.empty() || + !mConfig.staticVideoPath.empty()) + mHasVideo = true; + else + mHasVideo = false; + // Return false to show that no video will be displayed. return false; } @@ -87,7 +87,7 @@ void VideoComponent::setImage(const std::string& path, bool tile) if (imagePath == "") imagePath = mDefaultImagePath; - // Check that the image has changed. + // Check if the image has changed. if (imagePath == mStaticImagePath) return; @@ -95,127 +95,6 @@ void VideoComponent::setImage(const std::string& path, bool tile) mStaticImagePath = imagePath; } -void VideoComponent::onShow() -{ - mBlockPlayer = false; - mPause = false; - mShowing = true; - manageState(); -} - -void VideoComponent::onHide() -{ - mShowing = false; - manageState(); -} - -void VideoComponent::onStopVideo() -{ - stopVideo(); - manageState(); -} - -void VideoComponent::onPauseVideo() -{ - mBlockPlayer = true; - mPause = true; - manageState(); -} - -void VideoComponent::onUnpauseVideo() -{ - mBlockPlayer = false; - mPause = false; - manageState(); -} - -void VideoComponent::onScreensaverActivate() -{ - mBlockPlayer = true; - mPause = true; - - if (Settings::getInstance()->getString("ScreensaverType") == "dim") - stopVideo(); - else - pauseVideo(); - manageState(); -} - -void VideoComponent::onScreensaverDeactivate() -{ - mBlockPlayer = false; - // Stop video when deactivating the screensaver to force a reload of the - // static image (if the theme is configured as such). - stopVideo(); - manageState(); -} - -void VideoComponent::onGameLaunchedActivate() -{ - mGameLaunched = true; - manageState(); -} - -void VideoComponent::onGameLaunchedDeactivate() -{ - mGameLaunched = false; - stopVideo(); - manageState(); -} - -void VideoComponent::topWindow(bool isTop) -{ - if (isTop) { - mBlockPlayer = false; - mPause = false; - - // Stop video when closing the menu to force a reload of the - // static image (if the theme is configured as such). - stopVideo(); - } - else { - mBlockPlayer = true; - mPause = true; - } - manageState(); -} - -void VideoComponent::render(const glm::mat4& parentTrans) -{ - if (!isVisible()) - return; - - glm::mat4 trans {parentTrans * getTransform()}; - GuiComponent::renderChildren(trans); - - Renderer::setMatrix(trans); - - // Handle the case where the video is delayed. - handleStartDelay(); - - // Handle looping of the video. - handleLooping(); - - // Pause video in case a game has been launched. - pauseVideo(); -} - -void VideoComponent::renderSnapshot(const glm::mat4& parentTrans) -{ - // This function is called when the video is not currently being played. We need to - // work out if we should display a static image. If the menu is open, then always render - // the static image as the metadata may have been changed. In that case the gamelist - // was reloaded and there would just be a blank space unless we render the image here. - // The side effect of this is that a static image is displayed even for themes that are - // set to start playing the video immediately. Although this may seem a bit inconsistent it - // simply looks better than leaving an empty space where the video would have been located. - if (mWindow->getGuiStackSize() > 1 || (mConfig.showSnapshotNoVideo && mVideoPath.empty()) || - (mStartDelayed && mConfig.showSnapshotDelay)) { - mStaticImage.setOpacity(mOpacity * mThemeOpacity); - mStaticImage.render(parentTrans); - } -} - void VideoComponent::applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, @@ -327,12 +206,29 @@ std::vector VideoComponent::getHelpPrompts() void VideoComponent::update(int deltaTime) { - if (mBlockPlayer) { - setImage(mStaticImagePath); + if (!mHasVideo) { + // We need this update so the static image gets updated (e.g. used for fade animations). + GuiComponent::update(deltaTime); return; } - manageState(); + // Hack to prevent the video from starting to play if the static image was shown when paused. + if (mPaused) + mStartTime = SDL_GetTicks() + mConfig.startDelay; + + if (mWindow->getGameLaunchedState()) + return; + + bool playVideo {false}; + + if (!mIsPlaying && mConfig.startDelay == 0) { + startVideoStream(); + } + else if (mStartTime == 0 || SDL_GetTicks() > mStartTime) { + mStartTime = 0; + playVideo = true; + startVideoStream(); + } // Fade in videos, the time period is a bit different between the screensaver and media viewer. // For the theme controlled videos in the gamelist and system views, the fade-in time is set @@ -349,90 +245,37 @@ void VideoComponent::update(int deltaTime) mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast(mFadeInTime)), 0.0f, 1.0f); } + if (mIsPlaying) + updatePlayer(); + + handleLooping(); + GuiComponent::update(deltaTime); } -void VideoComponent::startVideoWithDelay() +void VideoComponent::startVideoPlayer() { - mPause = false; + if (mIsPlaying) + stopVideoPlayer(); - // If not playing then either start the video or initiate the delay. - if (!mIsPlaying) { - // Set the video that we are going to be playing so we don't attempt to restart it. - mPlayingVideoPath = mVideoPath; - - if (mConfig.startDelay == 0) { - // No delay. Just start the video. - mStartDelayed = false; - startVideo(); - } - else { - // Configure the start delay. - mStartDelayed = true; - mStartTime = SDL_GetTicks() + mConfig.startDelay; - } - mIsPlaying = true; + if (mConfig.startDelay != 0 && mStaticImagePath != "") { + mStartTime = SDL_GetTicks() + mConfig.startDelay; + setImage(mStaticImagePath); } + + mPaused = false; } -void VideoComponent::handleStartDelay() +void VideoComponent::renderSnapshot(const glm::mat4& parentTrans) { - if (mBlockPlayer || mGameLaunched) + if (mLegacyTheme && !mHasVideo && !mConfig.showSnapshotNoVideo) return; - // Only play if any delay has timed out. - if (mStartDelayed) { - // If the setting to override the theme-supplied video delay setting has been enabled, - // then play the video immediately. - if (!Settings::getInstance()->getBool("PlayVideosImmediately")) { - // If there is a video file available but no static image, then start playing the - // video immediately regardless of theme configuration or settings. - if (mStaticImagePath != "") { - if (mStartTime > SDL_GetTicks()) { - // Timeout not yet completed. - return; - } - } - } - // Completed. - mStartDelayed = false; - // Clear the playing flag so startVideo works. - mIsPlaying = false; - startVideo(); + if (mHasVideo && (!mConfig.showSnapshotDelay || mConfig.startDelay == 0)) + return; + + if (mStaticImagePath != "") { + mStaticImage.setOpacity(mOpacity * mThemeOpacity); + mStaticImage.render(parentTrans); } } - -void VideoComponent::manageState() -{ - // We will only show the video if the component is on display and the screensaver - // is not active. - bool show = mShowing && !mScreensaverActive && !mDisable; - - // See if we're already playing. - if (mIsPlaying) { - // If we are not on display then stop the video from playing. - if (!show) { - stopVideo(); - } - else { - if (mVideoPath != mPlayingVideoPath) { - // Path changed. Stop the video. We will start it again below because - // mIsPlaying will be modified by stopVideo to be false. - stopVideo(); - } - } - updatePlayer(); - } - // Need to recheck variable rather than 'else' because it may be modified above. - if (!mIsPlaying) { - // If we are on display then see if we should start the video. - if (show && !mVideoPath.empty()) - startVideoWithDelay(); - } - - // If a game has just been launched and a video is actually shown, then request a - // pause of the video so it doesn't continue to play in the background while the - // game is running. - if (mGameLaunched && show && !mPause) - mPause = true; -} diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index 32eb4a4e1..f5fe43fa7 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -52,27 +52,19 @@ public: bool hasStaticVideo() { return !mConfig.staticVideoPath.empty(); } bool hasStaticImage() { return mStaticImage.getTextureSize() != glm::ivec2 {0, 0}; } - - void onShow() override; - void onHide() override; - void onStopVideo() override; - void onPauseVideo() override; - void onUnpauseVideo() override; - bool isVideoPaused() override { return mPause; } - void onScreensaverActivate() override; - void onScreensaverDeactivate() override; - void onGameLaunchedActivate() override; - void onGameLaunchedDeactivate() override; - void topWindow(bool isTop) override; + bool hasStartDelay() + { + if (mLegacyTheme) + return mConfig.showSnapshotDelay && mConfig.startDelay > 0; + else + return mConfig.startDelay > 0; + } // These functions update the embedded static image. void onOriginChanged() override { mStaticImage.setOrigin(mOrigin); } void onPositionChanged() override { mStaticImage.setPosition(mPosition); } void onSizeChanged() override { mStaticImage.onSizeChanged(); } - void render(const glm::mat4& parentTrans) override; - void renderSnapshot(const glm::mat4& parentTrans); - void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, @@ -95,27 +87,21 @@ public: virtual void setMaxSize(float width, float height) = 0; void setMaxSize(const glm::vec2& size) { setMaxSize(size.x, size.y); } -private: - // Start the video immediately. - virtual void startVideo() {} - // Stop the video. - virtual void stopVideo() {} - // Pause the video when a game has been launched. - virtual void pauseVideo() {} + // Basic video controls. + void startVideoPlayer(); + virtual void stopVideoPlayer() {} + virtual void pauseVideoPlayer() {} + // Handle looping of the video. Must be called periodically. virtual void handleLooping() {} + // Used to immediately mute audio even if there are still samples to play in the buffer. + virtual void muteVideoPlayer() {} virtual void updatePlayer() {} - // Start the video after any configured delay. - void startVideoWithDelay(); - // Handle any delay to the start of playing the video clip. Must be called periodically. - void handleStartDelay(); - // Manage the playing state of the component. - void manageState(); - - friend MediaViewer; - protected: + virtual void startVideoStream() {} + void renderSnapshot(const glm::mat4& parentTrans); + ImageComponent mStaticImage; unsigned mVideoWidth; @@ -128,23 +114,17 @@ protected: std::string mDefaultImagePath; std::string mVideoPath; - std::string mPlayingVideoPath; unsigned mStartTime; - bool mStartDelayed; std::atomic mIsPlaying; std::atomic mIsActuallyPlaying; - std::atomic mPause; - bool mShowing; - bool mDisable; + std::atomic mPaused; bool mMediaViewerMode; - bool mScreensaverActive; bool mScreensaverMode; - bool mGameLaunched; - bool mBlockPlayer; bool mTargetIsMax; bool mDrawPillarboxes; bool mRenderScanlines; bool mLegacyTheme; + bool mHasVideo; float mFadeIn; float mFadeInTime; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 451796dc3..752b291fe 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -54,8 +54,6 @@ VideoFFmpegComponent::VideoFFmpegComponent() { } -VideoFFmpegComponent::~VideoFFmpegComponent() { stopVideo(); } - void VideoFFmpegComponent::setResize(float width, float height) { // This resize function is used when stretching videos to full screen in the video screensaver. @@ -126,7 +124,9 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) if (!isVisible() || mThemeOpacity == 0.0f) return; - VideoComponent::render(parentTrans); + if (!mHasVideo && mStaticImagePath == "") + return; + glm::mat4 trans {parentTrans * getTransform()}; GuiComponent::renderChildren(trans); @@ -209,7 +209,8 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) pictureLock.unlock(); } - mTexture->bind(); + if (mTexture != nullptr) + mTexture->bind(); #if defined(USE_OPENGL_21) // Render scanlines if this option is enabled. However, if this is the media viewer @@ -239,7 +240,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) void VideoFFmpegComponent::updatePlayer() { - if (mPause || !mFormatContext) + if (mPaused || !mFormatContext) return; // Output any audio that has been added by the processing thread. @@ -282,7 +283,7 @@ void VideoFFmpegComponent::frameProcessing() if (mAudioCodecContext) audioFilter = setupAudioFilters(); - while (mIsPlaying && !mPause && videoFilter && (!mAudioCodecContext || audioFilter)) { + while (mIsPlaying && !mPaused && videoFilter && (!mAudioCodecContext || audioFilter)) { readFrames(); if (!mIsPlaying) break; @@ -1073,7 +1074,7 @@ bool VideoFFmpegComponent::decoderInitHW() AVCodecContext* checkCodecContext = avcodec_alloc_context3(mHardwareCodec); if (avcodec_parameters_to_context(checkCodecContext, mVideoStream->codecpar)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): " "Couldn't fill the video codec context parameters for file \"" << mVideoPath << "\""; avcodec_free_context(&checkCodecContext); @@ -1086,7 +1087,7 @@ bool VideoFFmpegComponent::decoderInitHW() checkCodecContext->hw_device_ctx = av_buffer_ref(mHwContext); if (avcodec_open2(checkCodecContext, mHardwareCodec, nullptr)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): " "Couldn't initialize the video codec context for file \"" << mVideoPath << "\""; } @@ -1174,7 +1175,7 @@ bool VideoFFmpegComponent::decoderInitHW() mVideoCodecContext = avcodec_alloc_context3(mHardwareCodec); if (!mVideoCodecContext) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): " "Couldn't allocate video codec context for file \"" << mVideoPath << "\""; avcodec_free_context(&mVideoCodecContext); @@ -1182,7 +1183,7 @@ bool VideoFFmpegComponent::decoderInitHW() } if (avcodec_parameters_to_context(mVideoCodecContext, mVideoStream->codecpar)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): " "Couldn't fill the video codec context parameters for file \"" << mVideoPath << "\""; avcodec_free_context(&mVideoCodecContext); @@ -1193,7 +1194,7 @@ bool VideoFFmpegComponent::decoderInitHW() mVideoCodecContext->hw_device_ctx = av_buffer_ref(mHwContext); if (avcodec_open2(mVideoCodecContext, mHardwareCodec, nullptr)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): " "Couldn't initialize the video codec context for file \"" << mVideoPath << "\""; avcodec_free_context(&mVideoCodecContext); @@ -1203,8 +1204,10 @@ bool VideoFFmpegComponent::decoderInitHW() return false; } -void VideoFFmpegComponent::startVideo() +void VideoFFmpegComponent::startVideoStream() { + mIsPlaying = true; + if (!mFormatContext) { mHardwareCodec = nullptr; mHwContext = nullptr; @@ -1240,14 +1243,14 @@ void VideoFFmpegComponent::startVideo() // File operations and basic setup. if (avformat_open_input(&mFormatContext, filePath.c_str(), nullptr, nullptr)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't open video file \"" << mVideoPath << "\""; return; } if (avformat_find_stream_info(mFormatContext, nullptr)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't read stream information from video file \"" << mVideoPath << "\""; return; @@ -1268,7 +1271,7 @@ void VideoFFmpegComponent::startVideo() av_find_best_stream(mFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &mHardwareCodec, 0); if (mVideoStreamIndex < 0) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't retrieve video stream for file \"" << mVideoPath << "\""; avformat_close_input(&mFormatContext); @@ -1280,7 +1283,7 @@ void VideoFFmpegComponent::startVideo() mVideoWidth = mFormatContext->streams[mVideoStreamIndex]->codecpar->width; mVideoHeight = mFormatContext->streams[mVideoStreamIndex]->codecpar->height; - LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): " + LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): " << "Playing video \"" << mVideoPath << "\" (codec: " << avcodec_get_name( mFormatContext->streams[mVideoStreamIndex]->codecpar->codec_id) @@ -1294,15 +1297,16 @@ void VideoFFmpegComponent::startVideo() if (mSWDecoder) { // The hardware decoder initialization failed, which can happen for a number of reasons. if (hwDecoding) { - LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): Hardware decoding failed, " - "falling back to software decoder"; + LOG(LogDebug) + << "VideoFFmpegComponent::startVideoStream(): Hardware decoding failed, " + "falling back to software decoder"; } mVideoCodec = const_cast(avcodec_find_decoder(mVideoStream->codecpar->codec_id)); if (!mVideoCodec) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't find a suitable video codec for file \"" << mVideoPath << "\""; return; @@ -1311,7 +1315,7 @@ void VideoFFmpegComponent::startVideo() mVideoCodecContext = avcodec_alloc_context3(mVideoCodec); if (!mVideoCodecContext) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't allocate video codec context for file \"" << mVideoPath << "\""; return; @@ -1321,14 +1325,14 @@ void VideoFFmpegComponent::startVideo() mVideoCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED; if (avcodec_parameters_to_context(mVideoCodecContext, mVideoStream->codecpar)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't fill the video codec context parameters for file \"" << mVideoPath << "\""; return; } if (avcodec_open2(mVideoCodecContext, mVideoCodec, nullptr)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't initialize the video codec context for file \"" << mVideoPath << "\""; return; @@ -1341,7 +1345,7 @@ void VideoFFmpegComponent::startVideo() av_find_best_stream(mFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); if (mAudioStreamIndex < 0) { - LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): " + LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): " "File does not seem to contain any audio streams"; } @@ -1366,14 +1370,14 @@ void VideoFFmpegComponent::startVideo() mAudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; if (avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't fill the audio codec context parameters for file \"" << mVideoPath << "\""; return; } if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr)) { - LOG(LogError) << "VideoFFmpegComponent::startVideo(): " + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " "Couldn't initialize the audio codec context for file \"" << mVideoPath << "\""; return; @@ -1402,17 +1406,17 @@ void VideoFFmpegComponent::startVideo() // Calculate pillarbox/letterbox sizes. calculateBlackRectangle(); - mIsPlaying = true; mFadeIn = 0.0f; } } -void VideoFFmpegComponent::stopVideo() +void VideoFFmpegComponent::stopVideoPlayer() { + muteVideoPlayer(); + mIsPlaying = false; mIsActuallyPlaying = false; - mStartDelayed = false; - mPause = false; + mPaused = false; mEndOfVideo = false; mTexture.reset(); @@ -1451,10 +1455,10 @@ void VideoFFmpegComponent::stopVideo() } } -void VideoFFmpegComponent::pauseVideo() +void VideoFFmpegComponent::pauseVideoPlayer() { - if (mPause && mWindow->getVideoPlayerCount() == 0) - AudioManager::getInstance().muteStream(); + muteVideoPlayer(); + mPaused = true; } void VideoFFmpegComponent::handleLooping() @@ -1467,8 +1471,16 @@ void VideoFFmpegComponent::handleLooping() mWindow->screensaverTriggerNextGame(); } else { - stopVideo(); - startVideo(); + stopVideoPlayer(); + startVideoStream(); } } } + +void VideoFFmpegComponent::muteVideoPlayer() +{ + if (AudioManager::sAudioDevice != 0) { + AudioManager::getInstance().clearStream(); + AudioManager::getInstance().muteStream(); + } +} diff --git a/es-core/src/components/VideoFFmpegComponent.h b/es-core/src/components/VideoFFmpegComponent.h index 06522b22d..ec3ef8459 100644 --- a/es-core/src/components/VideoFFmpegComponent.h +++ b/es-core/src/components/VideoFFmpegComponent.h @@ -33,7 +33,7 @@ class VideoFFmpegComponent : public VideoComponent { public: VideoFFmpegComponent(); - virtual ~VideoFFmpegComponent(); + virtual ~VideoFFmpegComponent() { stopVideoPlayer(); } // Resize the video to fit this size. If one axis is zero, scale that axis to maintain // aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are @@ -45,8 +45,17 @@ public: // This can be set before or after a video is loaded. // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive. void setMaxSize(float width, float height) override; + // Basic video controls. + void stopVideoPlayer() override; + void pauseVideoPlayer() override; + // Handle looping of the video. Must be called periodically. + void handleLooping() override; + // Used to immediately mute audio even if there are samples to play in the buffer. + void muteVideoPlayer() override; private: + void startVideoStream() override; + // Calculates the correct mSize from our resizing information (set by setResize/setMaxSize). // Used internally whenever the resizing parameters or texture change. void resize(); @@ -74,15 +83,6 @@ private: static void detectHWDecoder(); bool decoderInitHW(); - // Start the video immediately. - void startVideo() override; - // Stop the video. - void stopVideo() override; - // Pause the video when a game has been launched. - void pauseVideo() override; - // Handle looping the video. Must be called periodically. - void handleLooping() override; - static enum AVHWDeviceType sDeviceType; static enum AVPixelFormat sPixelFormat; static std::vector sSWDecodedVideos; From 374a66dd7620ee1d01f0299e2b78a7611e3efaea Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 17:16:38 +0100 Subject: [PATCH 66/82] Removed the 'Play videos immediately' setting. --- es-app/src/guis/GuiMenu.cpp | 13 ------------- es-core/src/Settings.cpp | 3 +-- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 5839a6032..803bca5a5 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -690,19 +690,6 @@ void GuiMenu::openUIOptions() } }); - // Play videos immediately (overrides theme setting). - auto play_videos_immediately = std::make_shared(); - play_videos_immediately->setState(Settings::getInstance()->getBool("PlayVideosImmediately")); - s->addWithLabel("PLAY VIDEOS IMMEDIATELY (OVERRIDE THEME)", play_videos_immediately); - s->addSaveFunc([play_videos_immediately, s] { - if (Settings::getInstance()->getBool("PlayVideosImmediately") != - play_videos_immediately->getState()) { - Settings::getInstance()->setBool("PlayVideosImmediately", - play_videos_immediately->getState()); - s->setNeedsSaving(); - } - }); - // When the theme set entries are scrolled or selected, update the relevant rows. auto scrollThemeSetFunc = [=](const std::string& themeName, bool firstRun = false) { auto selectedSet = themeSets.find(themeName); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 57b9ba94f..6a02f0cb6 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -192,8 +192,6 @@ void Settings::setDefaults() mBoolMap["GamelistFilters"] = {true, true}; mBoolMap["QuickSystemSelect"] = {true, true}; mBoolMap["ShowHelpPrompts"] = {true, true}; - mBoolMap["PlayVideosImmediately"] = {false, false}; - mBoolMap["EnableMenuKidMode"] = {false, false}; // Sound settings. mIntMap["SoundVolumeNavigation"] = {70, 70}; @@ -243,6 +241,7 @@ void Settings::setDefaults() mBoolMap["DisableComposition"] = {true, true}; #endif mBoolMap["DisplayGPUStatistics"] = {false, false}; + mBoolMap["EnableMenuKidMode"] = {false, false}; // macOS requires root privileges to reboot and power off so it doesn't make much // sense to enable this setting and menu entry for that operating system. #if !defined(__APPLE__) From 4b0d3a4ecb20399ba0346c03b7560e303f44a27e Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 20:24:25 +0100 Subject: [PATCH 67/82] Clamped the themeable origin values to 0.0 to 1.0 --- es-core/src/GuiComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp index 21e6dda22..e0eff08cc 100644 --- a/es-core/src/GuiComponent.cpp +++ b/es-core/src/GuiComponent.cpp @@ -342,7 +342,7 @@ void GuiComponent::applyTheme(const std::shared_ptr& theme, // Position + size also implies origin. if ((properties & ORIGIN || (properties & POSITION && properties & ThemeFlags::SIZE)) && elem->has("origin")) { - setOrigin(elem->get("origin")); + setOrigin(glm::clamp(elem->get("origin"), 0.0f, 1.0f)); } if (properties & ThemeFlags::ROTATION) { From 77beb3980152ab5ee24402b70d8399df05a0f8da Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 20:31:54 +0100 Subject: [PATCH 68/82] Fixed an issue where videos would not get centered if pillarboxes were enabled. --- .../src/components/VideoFFmpegComponent.cpp | 23 ++++++++++++++----- es-core/src/components/VideoFFmpegComponent.h | 1 + 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 752b291fe..ebf68d86c 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -24,7 +24,8 @@ std::vector VideoFFmpegComponent::sHWDecodedVideos; std::vector VideoFFmpegComponent::sSWDecodedVideos; VideoFFmpegComponent::VideoFFmpegComponent() - : mFrameProcessingThread {nullptr} + : mRectangleOffset {0.0f, 0.0f} + , mFrameProcessingThread {nullptr} , mFormatContext {nullptr} , mVideoStream {nullptr} , mAudioStream {nullptr} @@ -159,10 +160,10 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) } // clang-format off - vertices[0] = {{0.0f, 0.0f }, {0.0f, 0.0f}, color}; - vertices[1] = {{0.0f, mSize.y}, {0.0f, 1.0f}, color}; - vertices[2] = {{mSize.x, 0.0f }, {1.0f, 0.0f}, color}; - vertices[3] = {{mSize.x, mSize.y}, {1.0f, 1.0f}, color}; + vertices[0] = {{0.0f + mRectangleOffset.x, 0.0f + mRectangleOffset.y }, {0.0f, 0.0f}, color}; + vertices[1] = {{0.0f + mRectangleOffset.x, mSize.y + mRectangleOffset.y }, {0.0f, 1.0f}, color}; + vertices[2] = {{mSize.x + mRectangleOffset.x, 0.0f + + mRectangleOffset.y }, {1.0f, 0.0f}, color}; + vertices[3] = {{mSize.x + mRectangleOffset.x, mSize.y + + mRectangleOffset.y}, {1.0f, 1.0f}, color}; // clang-format on // Round vertices. @@ -882,8 +883,9 @@ void VideoFFmpegComponent::calculateBlackRectangle() // otherwise it will exactly match the video size. The reason to add a black rectangle // behind videos in this second instance is that the scanline rendering will make the // video partially transparent so this may avoid some unforseen issues with some themes. - if (mVideoAreaPos != glm::vec2 {} && mVideoAreaSize != glm::vec2 {}) { + if (mVideoAreaPos != glm::vec2 {0.0f, 0.0f} && mVideoAreaSize != glm::vec2 {0.0f, 0.0f}) { mVideoRectangleCoords.clear(); + mRectangleOffset = {0.0f, 0.0f}; if ((mLegacyTheme && Settings::getInstance()->getBool("GamelistVideoPillarbox")) || (!mLegacyTheme && mDrawPillarboxes)) { @@ -919,6 +921,15 @@ void VideoFFmpegComponent::calculateBlackRectangle() std::round(mVideoAreaPos.y - rectHeight * mOrigin.y)); mVideoRectangleCoords.emplace_back(std::round(rectWidth)); mVideoRectangleCoords.emplace_back(std::round(rectHeight)); + + // If an origin value other than 0.5 is used, then create an offset for centering + // the video inside the rectangle. + if (mOrigin != glm::vec2 {0.5f, 0.5f}) { + if (rectWidth > mSize.x) + mRectangleOffset.x -= (rectWidth - mSize.x) * (mOrigin.x - 0.5f); + else if (rectHeight > mSize.y) + mRectangleOffset.y -= (rectHeight - mSize.y) * (mOrigin.y - 0.5f); + } } // If the option to display pillarboxes is disabled, then make the rectangle equivalent // to the size of the video. diff --git a/es-core/src/components/VideoFFmpegComponent.h b/es-core/src/components/VideoFFmpegComponent.h index ec3ef8459..f648276bd 100644 --- a/es-core/src/components/VideoFFmpegComponent.h +++ b/es-core/src/components/VideoFFmpegComponent.h @@ -90,6 +90,7 @@ private: std::shared_ptr mTexture; std::vector mVideoRectangleCoords; + glm::vec2 mRectangleOffset; std::unique_ptr mFrameProcessingThread; std::mutex mPictureMutex; From 29514d4db90b25ff3d1d6bb8efbc90f06f144a83 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 21:22:46 +0100 Subject: [PATCH 69/82] Added video support to SystemView. --- es-app/src/views/SystemView.cpp | 208 ++++++++++++++++++++++++++++++-- es-app/src/views/SystemView.h | 24 ++++ es-core/src/ThemeData.cpp | 1 + 3 files changed, 224 insertions(+), 9 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 2294e7ee2..f50210a03 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -36,13 +36,17 @@ SystemView::SystemView() , mUpdatedGameCount {false} , mViewNeedsReload {true} , mLegacyMode {false} + , mHoldingKey {false} + , mNavigated {false} { setSize(Renderer::getScreenWidth(), Renderer::getScreenHeight()); mCarousel = std::make_unique(); mCarousel->setCursorChangedCallback([&](const CursorState& state) { onCursorChanged(state); }); - mCarousel->setCancelTransitionsCallback( - [&] { ViewController::getInstance()->cancelViewTransitions(); }); + mCarousel->setCancelTransitionsCallback([&] { + ViewController::getInstance()->cancelViewTransitions(); + mNavigated = true; + }); populate(); } @@ -68,8 +72,12 @@ void SystemView::goToSystem(SystemData* system, bool animate) selector->setNeedsRefresh(); } + for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) + video->setStaticVideo(); + updateGameSelectors(); updateGameCount(); + startViewVideos(); if (!animate) finishSystemAnimation(0); @@ -77,7 +85,11 @@ void SystemView::goToSystem(SystemData* system, bool animate) bool SystemView::input(InputConfig* config, Input input) { + mNavigated = false; + if (input.value != 0) { + mHoldingKey = true; + if (config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r && SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) { LOG(LogDebug) << "SystemView::input(): Reloading all"; @@ -87,6 +99,7 @@ bool SystemView::input(InputConfig* config, Input input) if (config->isMappedTo("a", input)) { mCarousel->stopScrolling(); + pauseViewVideos(); ViewController::getInstance()->goToGamelist(mCarousel->getSelected()); NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); return true; @@ -111,6 +124,9 @@ bool SystemView::input(InputConfig* config, Input input) return true; } } + else { + mHoldingKey = false; + } return mCarousel->input(config, input); } @@ -118,6 +134,10 @@ bool SystemView::input(InputConfig* config, Input input) void SystemView::update(int deltaTime) { mCarousel->update(deltaTime); + + for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) + video->update(deltaTime); + GuiComponent::update(deltaTime); } @@ -126,7 +146,14 @@ void SystemView::render(const glm::mat4& parentTrans) if (mCarousel->getNumEntries() == 0) return; // Nothing to render. - renderElements(parentTrans, false); + bool fade {false}; + + if (mNavigated && mCarousel->isAnimationPlaying(0) && + Settings::getInstance()->getString("TransitionStyle") == "fade") + fade = true; + + if (!fade) + renderElements(parentTrans, false); glm::mat4 trans {getTransform() * parentTrans}; // During fade transitions draw a black rectangle above all elements placed below the carousel. @@ -187,7 +214,11 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) selector->setNeedsRefresh(); } + for (auto& video : mSystemElements[cursor].videoComponents) + video->setStaticVideo(); + updateGameSelectors(); + startViewVideos(); updateHelpPrompts(); int scrollVelocity {mCarousel->getScrollingVelocity()}; @@ -368,10 +399,17 @@ void SystemView::populate() elements.imageComponents.back()->setDefaultZIndex(30.0f); elements.imageComponents.back()->applyTheme(theme, "system", element.first, ThemeFlags::ALL); - if (elements.imageComponents.back()->getThemeImageTypes().size() != 0) - elements.imageComponents.back()->setScrollHide(true); elements.children.emplace_back(elements.imageComponents.back().get()); } + else if (element.second.type == "video") { + elements.videoComponents.emplace_back( + std::make_unique()); + elements.videoComponents.back()->setDefaultZIndex(30.0f); + elements.videoComponents.back()->setStaticVideo(); + elements.videoComponents.back()->applyTheme(theme, "system", element.first, + ThemeFlags::ALL); + elements.children.emplace_back(elements.videoComponents.back().get()); + } else if (element.second.type == "text") { if (element.second.has("systemdata") && element.second.get("systemdata").substr(0, 9) == @@ -440,8 +478,6 @@ void SystemView::populate() } } - updateGameSelectors(); - if (mCarousel->getNumEntries() == 0) { // Something is wrong, there is not a single system to show, check if UI mode is not full. if (!UIModeController::getInstance()->isUIModeFull()) { @@ -533,15 +569,19 @@ void SystemView::updateGameSelectors() gameSelector = mSystemElements[cursor].gameSelectors.front().get(); LOG(LogWarning) << "SystemView::updateGameSelectors(): Multiple gameselector " "elements defined but image element does not state which one to " - "use, so selecting first entry"; + "use, selecting first entry"; } else { for (auto& selector : mSystemElements[cursor].gameSelectors) { if (selector->getSelectorName() == imageSelector) gameSelector = selector.get(); } - if (gameSelector == nullptr) + if (gameSelector == nullptr) { + LOG(LogWarning) + << "SystemView::updateGameSelectors(): Invalid gameselector \"" + << imageSelector << "\" defined for image element, selecting first entry"; gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } } } else { @@ -632,6 +672,156 @@ void SystemView::updateGameSelectors() } } + for (auto& video : mSystemElements[cursor].videoComponents) { + // If a static video has been set, then don't attempt to find a gameselector entry. + if (video->hasStaticVideo()) + continue; + GameSelectorComponent* gameSelector {nullptr}; + if (multipleSelectors) { + const std::string& videoSelector {video->getThemeGameSelector()}; + if (videoSelector == "") { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + LOG(LogWarning) << "SystemView::updateGameSelectors(): Multiple gameselector " + "elements defined but video element does not state which one to " + "use, selecting first entry"; + } + else { + for (auto& selector : mSystemElements[cursor].gameSelectors) { + if (selector->getSelectorName() == videoSelector) + gameSelector = selector.get(); + } + if (gameSelector == nullptr) { + LOG(LogWarning) + << "SystemView::updateGameSelectors(): Invalid gameselector \"" + << videoSelector << "\" defined for video element, selecting first entry"; + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + } + } + else { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + gameSelector->refreshGames(); + std::vector games {gameSelector->getGames()}; + if (!games.empty()) { + if (!video->setVideo(games.front()->getVideoPath())) + video->setDefaultVideo(); + } + } + + for (auto& video : mSystemElements[cursor].videoComponents) { + if (video->getThemeImageTypes().size() == 0) + continue; + GameSelectorComponent* gameSelector {nullptr}; + if (multipleSelectors) { + const std::string& imageSelector {video->getThemeGameSelector()}; + if (imageSelector == "") { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + LOG(LogWarning) << "SystemView::updateGameSelectors(): Multiple gameselector " + "elements defined but video element does not state which one to " + "use, selecting first entry"; + } + else { + for (auto& selector : mSystemElements[cursor].gameSelectors) { + if (selector->getSelectorName() == imageSelector) + gameSelector = selector.get(); + } + if (gameSelector == nullptr) { + LOG(LogWarning) + << "SystemView::updateGameSelectors(): Invalid gameselector \"" + << imageSelector << "\" defined for video element, selecting first entry"; + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + } + } + else { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + gameSelector->refreshGames(); + std::vector games {gameSelector->getGames()}; + if (!games.empty()) { + std::string path; + for (auto& imageType : video->getThemeImageTypes()) { + if (imageType == "image") { + path = games.front()->getImagePath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "miximage") { + path = games.front()->getMiximagePath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "marquee") { + path = games.front()->getMarqueePath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "screenshot") { + path = games.front()->getScreenshotPath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "titlescreen") { + path = games.front()->getTitleScreenPath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "cover") { + path = games.front()->getCoverPath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "backcover") { + path = games.front()->getBackCoverPath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "3dbox") { + path = games.front()->get3DBoxPath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "fanart") { + path = games.front()->getFanArtPath(); + if (path != "") { + video->setImage(path); + break; + } + } + else if (imageType == "thumbnail") { + path = games.front()->getThumbnailPath(); + if (path != "") { + video->setImage(path); + break; + } + } + } + // This is needed so the default image is set if no game media was found. + if (path == "" && video->getThemeImageTypes().size() > 0) + video->setImage(""); + } + else { + video->setImage(""); + } + } + for (auto& text : mSystemElements[cursor].textComponents) { if (text->getThemeMetadata() == "") continue; diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index f16da5f70..9ecb5b5c3 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -66,6 +66,28 @@ public: CarouselComponent::CarouselType getCarouselType() { return mCarousel->getType(); } SystemData* getFirstSystem() { return mCarousel->getFirst(); } + void startViewVideos() override + { + for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) + video->startVideoPlayer(); + } + void stopViewVideos() override + { + for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) + video->stopVideoPlayer(); + } + void pauseViewVideos() override + { + for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) { + video->pauseVideoPlayer(); + } + } + void muteViewVideos() override + { + for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) + video->muteVideoPlayer(); + } + void onThemeChanged(const std::shared_ptr& theme); std::vector getHelpPrompts() override; @@ -92,6 +114,8 @@ private: bool mUpdatedGameCount; bool mViewNeedsReload; bool mLegacyMode; + bool mHoldingKey; + bool mNavigated; }; #endif // ES_APP_VIEWS_SYSTEM_VIEW_H diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index c0248fc29..b6a28457b 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -110,6 +110,7 @@ std::map> {"default", PATH}, {"defaultImage", PATH}, {"imageType", STRING}, + {"gameselector", STRING}, {"interpolation", STRING}, {"pillarboxes", BOOLEAN}, {"scanlines", BOOLEAN}, From f803e23fd2bca6106dcd072d8aeb2935b6c02258 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 21:45:31 +0100 Subject: [PATCH 70/82] Added theme support for disabling audio playback for each defined video. --- es-core/src/ThemeData.cpp | 1 + es-core/src/components/VideoComponent.cpp | 4 ++ es-core/src/components/VideoComponent.h | 1 + .../src/components/VideoFFmpegComponent.cpp | 69 ++++++++++--------- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index b6a28457b..91f139afc 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -111,6 +111,7 @@ std::map> {"defaultImage", PATH}, {"imageType", STRING}, {"gameselector", STRING}, + {"audio", BOOLEAN}, {"interpolation", STRING}, {"pillarboxes", BOOLEAN}, {"scanlines", BOOLEAN}, diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 39f9cdf54..ab7123a4d 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -32,6 +32,7 @@ VideoComponent::VideoComponent() , mMediaViewerMode {false} , mScreensaverMode {false} , mTargetIsMax {false} + , mPlayAudio {true} , mDrawPillarboxes {true} , mRenderScanlines {false} , mLegacyTheme {false} @@ -132,6 +133,9 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, mVideoAreaPos = elem->get("pos") * scale; } + if (elem->has("audio")) + mPlayAudio = elem->get("audio"); + if (elem->has("interpolation")) { const std::string interpolation {elem->get("interpolation")}; if (interpolation == "linear") { diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index f5fe43fa7..5709fada3 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -121,6 +121,7 @@ protected: bool mMediaViewerMode; bool mScreensaverMode; bool mTargetIsMax; + bool mPlayAudio; bool mDrawPillarboxes; bool mRenderScanlines; bool mLegacyTheme; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index ebf68d86c..c3834b18b 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -1351,47 +1351,50 @@ void VideoFFmpegComponent::startVideoStream() } // Audio stream setup, optional as some videos do not have any audio tracks. + // Audio can also be disabled per video via the theme configuration. - mAudioStreamIndex = - av_find_best_stream(mFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); + if (mPlayAudio) { + mAudioStreamIndex = + av_find_best_stream(mFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); - if (mAudioStreamIndex < 0) { - LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): " - "File does not seem to contain any audio streams"; - } - - if (mAudioStreamIndex >= 0) { - mAudioStream = mFormatContext->streams[mAudioStreamIndex]; - mAudioCodec = - const_cast(avcodec_find_decoder(mAudioStream->codecpar->codec_id)); - - if (!mAudioCodec) { - LOG(LogError) << "Couldn't find a suitable audio codec for file \"" << mVideoPath - << "\""; - return; + if (mAudioStreamIndex < 0) { + LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): " + "File does not seem to contain any audio streams"; } - mAudioCodecContext = avcodec_alloc_context3(mAudioCodec); + if (mAudioStreamIndex >= 0) { + mAudioStream = mFormatContext->streams[mAudioStreamIndex]; + mAudioCodec = + const_cast(avcodec_find_decoder(mAudioStream->codecpar->codec_id)); - if (mAudioCodec->capabilities & AV_CODEC_CAP_TRUNCATED) - mAudioCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED; + if (!mAudioCodec) { + LOG(LogError) << "Couldn't find a suitable audio codec for file \"" + << mVideoPath << "\""; + return; + } - // Some formats want separate stream headers. - if (mAudioCodecContext->flags & AVFMT_GLOBALHEADER) - mAudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + mAudioCodecContext = avcodec_alloc_context3(mAudioCodec); - if (avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar)) { - LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " - "Couldn't fill the audio codec context parameters for file \"" - << mVideoPath << "\""; - return; - } + if (mAudioCodec->capabilities & AV_CODEC_CAP_TRUNCATED) + mAudioCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED; - if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr)) { - LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " - "Couldn't initialize the audio codec context for file \"" - << mVideoPath << "\""; - return; + // Some formats want separate stream headers. + if (mAudioCodecContext->flags & AVFMT_GLOBALHEADER) + mAudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + if (avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar)) { + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " + "Couldn't fill the audio codec context parameters for file \"" + << mVideoPath << "\""; + return; + } + + if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr)) { + LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): " + "Couldn't initialize the audio codec context for file \"" + << mVideoPath << "\""; + return; + } } } From c06dea5d2d411f9038ad8c8fc8fb1f5639c25332 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 22:44:02 +0100 Subject: [PATCH 71/82] Lottie animations are now paused during view transitions. --- es-app/src/views/GamelistView.cpp | 6 ++++++ es-app/src/views/GamelistView.h | 1 + es-app/src/views/ViewController.cpp | 6 ++++++ es-core/src/GuiComponent.h | 1 + es-core/src/components/LottieComponent.cpp | 6 ++++-- es-core/src/components/LottieComponent.h | 5 ++++- 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp index c168dab01..2572c6907 100644 --- a/es-app/src/views/GamelistView.cpp +++ b/es-app/src/views/GamelistView.cpp @@ -85,6 +85,12 @@ void GamelistView::onShow() updateInfoPanel(); } +void GamelistView::onTransition() +{ + for (auto& animation : mLottieAnimComponents) + animation->setPauseAnimation(true); +} + void GamelistView::onThemeChanged(const std::shared_ptr& theme) { auto themeSets = ThemeData::getThemeSets(); diff --git a/es-app/src/views/GamelistView.h b/es-app/src/views/GamelistView.h index 4f9ae35f0..a60cb0d58 100644 --- a/es-app/src/views/GamelistView.h +++ b/es-app/src/views/GamelistView.h @@ -23,6 +23,7 @@ public: // Called when a FileData* is added, has its metadata changed, or is removed. void onFileChanged(FileData* file, bool reloadGamelist) override; void onShow() override; + void onTransition() override; void preloadGamelist() { updateInfoPanel(); } void launch(FileData* game) override { ViewController::getInstance()->triggerGameLaunch(game); } diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 64ad24b9c..a1541ebe8 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -330,6 +330,9 @@ void ViewController::goToSystemView(SystemData* system, bool playTransition) mPreviousView = nullptr; } + if (mCurrentView != nullptr) + mCurrentView->onTransition(); + mPreviousView = mCurrentView; if (system->isGroupedCustomCollection()) @@ -415,6 +418,9 @@ void ViewController::goToGamelist(SystemData* system) bool wrapLastToFirst = false; bool slideTransitions = false; + if (mCurrentView != nullptr) + mCurrentView->onTransition(); + if (Settings::getInstance()->getString("TransitionStyle") == "slide") slideTransitions = true; diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h index 9e2052a70..bc2392703 100644 --- a/es-core/src/GuiComponent.h +++ b/es-core/src/GuiComponent.h @@ -238,6 +238,7 @@ public: virtual void onShow(); virtual void onHide(); + virtual void onTransition() {} // System view and gamelist view video controls. virtual void startViewVideos() {} diff --git a/es-core/src/components/LottieComponent.cpp b/es-core/src/components/LottieComponent.cpp index 560267201..7dffb9364 100644 --- a/es-core/src/components/LottieComponent.cpp +++ b/es-core/src/components/LottieComponent.cpp @@ -36,6 +36,7 @@ LottieComponent::LottieComponent() , mSkippedFrames {0} , mHoldFrame {false} , mPause {false} + , mExternalPause {false} , mAlternate {false} , mKeepAspectRatio {true} { @@ -190,6 +191,7 @@ void LottieComponent::setAnimation(const std::string& path) void LottieComponent::resetFileAnimation() { + mExternalPause = false; mTimeAccumulator = 0; mFrameNum = mStartDirection == "reverse" ? mTotalFrames - 1 : 0; @@ -336,7 +338,7 @@ void LottieComponent::render(const glm::mat4& parentTrans) glm::mat4 trans {parentTrans * getTransform()}; // This is necessary as there may otherwise be no texture to render when paused. - if (mPause && mTexture->getSize().x == 0.0f) { + if ((mExternalPause || mPause) && mTexture->getSize().x == 0.0f) { mTexture->initFromPixels(&mPictureRGBA.at(0), static_cast(mSize.x), static_cast(mSize.y)); } @@ -352,7 +354,7 @@ void LottieComponent::render(const glm::mat4& parentTrans) } // Don't render any new frames if paused or if a menu is open (unless invalidating background). - if (!mPause && doRender) { + if ((!mPause && !mExternalPause) && doRender) { if ((mDirection == "normal" && mFrameNum >= mTotalFrames) || (mDirection == "reverse" && mFrameNum > mTotalFrames)) { if (DEBUG_ANIMATION) { diff --git a/es-core/src/components/LottieComponent.h b/es-core/src/components/LottieComponent.h index c4f12c419..b087bfc39 100644 --- a/es-core/src/components/LottieComponent.h +++ b/es-core/src/components/LottieComponent.h @@ -33,6 +33,7 @@ public: { mMaxCacheSize = static_cast(glm::clamp(value, 0, 1024) * 1024 * 1024); } + void setPauseAnimation(bool state) { mExternalPause = state; } void resetFileAnimation() override; void onSizeChanged() override; @@ -42,8 +43,9 @@ public: const std::string& element, unsigned int properties) override; -private: void update(int deltaTime) override; + +private: void render(const glm::mat4& parentTrans) override; std::shared_ptr mTexture; @@ -76,6 +78,7 @@ private: bool mHoldFrame; bool mPause; + bool mExternalPause; bool mAlternate; bool mKeepAspectRatio; }; From 992c39d3a3f2e1e622aef7b87f31ee42a25bdfed Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 19 Feb 2022 22:46:52 +0100 Subject: [PATCH 72/82] Added Lottie animation support to SystemView. --- es-app/src/views/SystemView.cpp | 25 +++++++++++++++++++++++++ es-app/src/views/SystemView.h | 1 + 2 files changed, 26 insertions(+) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index f50210a03..f79db939f 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -46,6 +46,8 @@ SystemView::SystemView() mCarousel->setCancelTransitionsCallback([&] { ViewController::getInstance()->cancelViewTransitions(); mNavigated = true; + for (auto& anim : mSystemElements[mCarousel->getCursor()].lottieAnimComponents) + anim->setPauseAnimation(true); }); populate(); @@ -63,6 +65,12 @@ SystemView::~SystemView() } } +void SystemView::onTransition() +{ + for (auto& anim : mSystemElements[mCarousel->getCursor()].lottieAnimComponents) + anim->setPauseAnimation(true); +} + void SystemView::goToSystem(SystemData* system, bool animate) { mCarousel->setCursor(system); @@ -75,6 +83,9 @@ void SystemView::goToSystem(SystemData* system, bool animate) for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) video->setStaticVideo(); + for (auto& anim : mSystemElements[mCarousel->getCursor()].lottieAnimComponents) + anim->resetFileAnimation(); + updateGameSelectors(); updateGameCount(); startViewVideos(); @@ -138,6 +149,9 @@ void SystemView::update(int deltaTime) for (auto& video : mSystemElements[mCarousel->getCursor()].videoComponents) video->update(deltaTime); + for (auto& anim : mSystemElements[mCarousel->getCursor()].lottieAnimComponents) + anim->update(deltaTime); + GuiComponent::update(deltaTime); } @@ -217,6 +231,9 @@ void SystemView::onCursorChanged(const CursorState& /*state*/) for (auto& video : mSystemElements[cursor].videoComponents) video->setStaticVideo(); + for (auto& anim : mSystemElements[mCarousel->getCursor()].lottieAnimComponents) + anim->resetFileAnimation(); + updateGameSelectors(); startViewVideos(); updateHelpPrompts(); @@ -410,6 +427,14 @@ void SystemView::populate() ThemeFlags::ALL); elements.children.emplace_back(elements.videoComponents.back().get()); } + else if (element.second.type == "animation") { + elements.lottieAnimComponents.emplace_back( + std::make_unique()); + elements.lottieAnimComponents.back()->setDefaultZIndex(35.0f); + elements.lottieAnimComponents.back()->applyTheme( + theme, "system", element.first, ThemeFlags::ALL); + elements.children.emplace_back(elements.lottieAnimComponents.back().get()); + } else if (element.second.type == "text") { if (element.second.has("systemdata") && element.second.get("systemdata").substr(0, 9) == diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index 9ecb5b5c3..88ddc9a84 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -45,6 +45,7 @@ public: SystemView(); ~SystemView(); + void onTransition() override; void goToSystem(SystemData* system, bool animate); bool input(InputConfig* config, Input input) override; From 3795edb1b97b7544def733cd1b0d850a77c11ae7 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 15:03:31 +0100 Subject: [PATCH 73/82] Removed the broken rotation support for video elements. --- es-core/src/ThemeData.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 91f139afc..4cd199862 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -104,8 +104,6 @@ std::map> {"size", NORMALIZED_PAIR}, {"maxSize", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, - {"rotation", FLOAT}, - {"rotationOrigin", NORMALIZED_PAIR}, {"path", PATH}, {"default", PATH}, {"defaultImage", PATH}, From 73e0e59b8c3d5a8134f125a48147cef49ad95dfe Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 15:04:05 +0100 Subject: [PATCH 74/82] Fixed an issue where hidden videos would still play. --- es-core/src/components/VideoFFmpegComponent.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index c3834b18b..22115b59f 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -1217,6 +1217,9 @@ bool VideoFFmpegComponent::decoderInitHW() void VideoFFmpegComponent::startVideoStream() { + if (mThemeOpacity == 0.0f) + return; + mIsPlaying = true; if (!mFormatContext) { From e92af0548c7f6448860da48a4e050708e408792b Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 15:49:32 +0100 Subject: [PATCH 75/82] Added DateTimeComponent support to SystemView. --- es-app/src/views/SystemView.cpp | 58 +++++++++++++++++++++++++++++++-- es-app/src/views/SystemView.h | 2 ++ es-core/src/ThemeData.cpp | 1 + 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index f79db939f..d0a14fcd1 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -457,6 +457,14 @@ void SystemView::populate() elements.children.emplace_back(elements.textComponents.back().get()); } } + else if (element.second.type == "datetime") { + elements.dateTimeComponents.emplace_back( + std::make_unique()); + elements.dateTimeComponents.back()->setDefaultZIndex(40.0f); + elements.dateTimeComponents.back()->applyTheme( + theme, "system", element.first, ThemeFlags::ALL); + elements.children.emplace_back(elements.dateTimeComponents.back().get()); + } } } else { @@ -857,15 +865,19 @@ void SystemView::updateGameSelectors() gameSelector = mSystemElements[cursor].gameSelectors.front().get(); LOG(LogWarning) << "SystemView::updateGameSelectors(): Multiple gameselector " "elements defined but text element does not state which one to " - "use, so selecting first entry"; + "use, selecting first entry"; } else { for (auto& selector : mSystemElements[cursor].gameSelectors) { if (selector->getSelectorName() == textSelector) gameSelector = selector.get(); } - if (gameSelector == nullptr) + if (gameSelector == nullptr) { + LOG(LogWarning) + << "SystemView::updateGameSelectors(): Invalid gameselector \"" + << textSelector << "\" defined for text element, selecting first entry"; gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } } } else { @@ -904,6 +916,48 @@ void SystemView::updateGameSelectors() text->setValue(""); } } + + for (auto& dateTime : mSystemElements[cursor].dateTimeComponents) { + if (dateTime->getThemeMetadata() == "") + continue; + GameSelectorComponent* gameSelector {nullptr}; + if (multipleSelectors) { + const std::string& dateTimeSelector {dateTime->getThemeGameSelector()}; + if (dateTimeSelector == "") { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + LOG(LogWarning) << "SystemView::updateGameSelectors(): Multiple gameselector " + "elements defined but datetime element does not state which one " + "to use, selecting first entry"; + } + else { + for (auto& selector : mSystemElements[cursor].gameSelectors) { + if (selector->getSelectorName() == dateTimeSelector) + gameSelector = selector.get(); + } + if (gameSelector == nullptr) { + LOG(LogWarning) << "SystemView::updateGameSelectors(): Invalid gameselector \"" + << dateTimeSelector + << "\" defined for datetime element, selecting first entry"; + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + } + } + else { + gameSelector = mSystemElements[cursor].gameSelectors.front().get(); + } + gameSelector->refreshGames(); + std::vector games {gameSelector->getGames()}; + if (!games.empty()) { + const std::string metadata {dateTime->getThemeMetadata()}; + if (metadata == "releasedate") + dateTime->setValue(games.front()->metadata.get("releasedate")); + if (metadata == "lastplayed") + dateTime->setValue(games.front()->metadata.get("lastplayed")); + } + else { + dateTime->setValue("19700101T000000"); + } + } } void SystemView::legacyApplyTheme(const std::shared_ptr& theme) diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index 88ddc9a84..90881e528 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -14,6 +14,7 @@ #include "Sound.h" #include "SystemData.h" #include "components/CarouselComponent.h" +#include "components/DateTimeComponent.h" #include "components/GameSelectorComponent.h" #include "components/LottieComponent.h" #include "components/TextComponent.h" @@ -34,6 +35,7 @@ struct SystemViewElements { std::vector> gameCountComponents; std::vector> textComponents; + std::vector> dateTimeComponents; std::vector> imageComponents; std::vector> videoComponents; std::vector> lottieAnimComponents; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index 4cd199862..b74693ca3 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -188,6 +188,7 @@ std::map> {"rotation", FLOAT}, {"rotationOrigin", NORMALIZED_PAIR}, {"metadata", STRING}, + {"gameselector", STRING}, {"fontPath", PATH}, {"fontSize", FLOAT}, {"horizontalAlignment", STRING}, From a97e86a6aa303760b28f850cabbf94f274ea2550 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 16:07:27 +0100 Subject: [PATCH 76/82] Renamed the menu option to enable or disable audio playback for gamelist videos. --- es-app/src/guis/GuiMenu.cpp | 15 +++++++-------- es-core/src/Settings.cpp | 2 +- es-core/src/components/VideoFFmpegComponent.cpp | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 803bca5a5..4edd4ca38 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -856,14 +856,13 @@ void GuiMenu::openSoundOptions() if (UIModeController::getInstance()->isUIModeFull()) { // Play audio for gamelist videos. - auto gamelist_video_audio = std::make_shared(); - gamelist_video_audio->setState(Settings::getInstance()->getBool("GamelistVideoAudio")); - s->addWithLabel("PLAY AUDIO FOR VIDEOS IN THE GAMELIST VIEW", gamelist_video_audio); - s->addSaveFunc([gamelist_video_audio, s] { - if (gamelist_video_audio->getState() != - Settings::getInstance()->getBool("GamelistVideoAudio")) { - Settings::getInstance()->setBool("GamelistVideoAudio", - gamelist_video_audio->getState()); + auto viewsVideoAudio = std::make_shared(); + viewsVideoAudio->setState(Settings::getInstance()->getBool("ViewsVideoAudio")); + s->addWithLabel("PLAY AUDIO FOR GAMELIST AND SYSTEM VIEW VIDEOS", viewsVideoAudio); + s->addSaveFunc([viewsVideoAudio, s] { + if (viewsVideoAudio->getState() != + Settings::getInstance()->getBool("ViewsVideoAudio")) { + Settings::getInstance()->setBool("ViewsVideoAudio", viewsVideoAudio->getState()); s->setNeedsSaving(); } }); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 6a02f0cb6..0bc63acd8 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -196,7 +196,7 @@ void Settings::setDefaults() // Sound settings. mIntMap["SoundVolumeNavigation"] = {70, 70}; mIntMap["SoundVolumeVideos"] = {80, 80}; - mBoolMap["GamelistVideoAudio"] = {true, true}; + mBoolMap["ViewsVideoAudio"] = {true, true}; mBoolMap["MediaViewerVideoAudio"] = {true, true}; mBoolMap["ScreensaverVideoAudio"] = {true, true}; mBoolMap["NavigationSounds"] = {true, true}; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 22115b59f..f52e4d5fb 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -786,7 +786,7 @@ void VideoFFmpegComponent::outputFrames() bool outputSound = false; if ((!mScreensaverMode && !mMediaViewerMode) && - Settings::getInstance()->getBool("GamelistVideoAudio")) + Settings::getInstance()->getBool("ViewsVideoAudio")) outputSound = true; else if (mScreensaverMode && Settings::getInstance()->getBool("ScreensaverVideoAudio")) outputSound = true; From 01ccdc3df6bd58cb478350783d9a0b445e0b4568 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 16:09:44 +0100 Subject: [PATCH 77/82] (rbsimple-DE) Updated for the latest theme engine features. --- themes/rbsimple-DE/theme_engine_test.xml | 49 ++++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/themes/rbsimple-DE/theme_engine_test.xml b/themes/rbsimple-DE/theme_engine_test.xml index 89ca5031c..08d7530fd 100644 --- a/themes/rbsimple-DE/theme_engine_test.xml +++ b/themes/rbsimple-DE/theme_engine_test.xml @@ -20,10 +20,14 @@ E6E6E6 55555500 - + E6E6E6 55555500 + + E6E6E6 + 55555500 + E6E6E6 55555500 @@ -121,10 +125,10 @@ 50 - + ./core/fonts/Exo2-RegularCondensed.otf 0.045 - 0.775 0.60 + 0.775 0.59 0.2 0.05 0 0 none @@ -134,6 +138,20 @@ 50 + + ./core/fonts/Exo2-RegularCondensed.otf + 0.045 + 0.775 0.64 + 0.2 0.05 + 0 0 + capitalize + selector_recent + true + right + lastplayed + 50 + + ./core/fonts/Exo2-RegularCondensed.otf 0.035 @@ -156,7 +174,7 @@ 1 - + 0 0 0 0 1 1 @@ -168,7 +186,7 @@ 0.5 - + 0 0 0.775 0.08 0.2 0 @@ -180,6 +198,21 @@ 1 + + 0.528 0.954 @@ -232,7 +265,7 @@ right - + ---> + \ No newline at end of file From 5f4a26c38c17ada0ea6b31eb99f2b9e00c995a92 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 17:49:57 +0100 Subject: [PATCH 78/82] Set fan art scraping as enabled by default. --- es-core/src/Settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 0bc63acd8..d73082be3 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -100,7 +100,7 @@ void Settings::setDefaults() mBoolMap["ScrapeMarquees"] = {true, true}; mBoolMap["Scrape3DBoxes"] = {true, true}; mBoolMap["ScrapePhysicalMedia"] = {true, true}; - mBoolMap["ScrapeFanArt"] = {false, false}; + mBoolMap["ScrapeFanArt"] = {true, true}; mStringMap["MiximageResolution"] = {"1280x960", "1280x960"}; mStringMap["MiximageScreenshotScaling"] = {"sharp", "sharp"}; From c4843b3b1d689b30b327be508b57de9c0e19f6b7 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 18:49:35 +0100 Subject: [PATCH 79/82] (Windows) Fixed a compile error. --- es-app/src/FileData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 5e7d6a604..74fefd98c 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -1186,7 +1186,7 @@ void FileData::launchGame() // been set for the specific launch command, then block the video player, stop scrolling // game names and descriptions and keep the screensaver from getting activated. if (runInBackground) - window->setLaunchedGame(); + window->setLaunchedGame(true); else // Normalize deltaTime so that the screensaver does not start immediately // when returning from the game. From 46eec8ead6990adeb7d714fed5f4f55b45fd6cc9 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 18:54:38 +0100 Subject: [PATCH 80/82] (Windows) Fixed another compile error. --- es-app/src/scrapers/Scraper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/es-app/src/scrapers/Scraper.cpp b/es-app/src/scrapers/Scraper.cpp index b50fa9b82..3b03c5747 100644 --- a/es-app/src/scrapers/Scraper.cpp +++ b/es-app/src/scrapers/Scraper.cpp @@ -261,7 +261,7 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, scrapeFiles.push_back(mediaFileInfo); #if defined(_WIN64) // Required due to the idiotic file locking that exists on this operating system. - ViewController::getInstance()->stopVideo(); + ViewController::getInstance()->stopViewVideos(); #endif } From c7a4449e3660294ee769035a65eb82f3da1f7567 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 19:31:19 +0100 Subject: [PATCH 81/82] Fixed a use-after-free in GameSelectorComponent. --- es-core/src/components/GameSelectorComponent.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/es-core/src/components/GameSelectorComponent.h b/es-core/src/components/GameSelectorComponent.h index c828fdf17..8a7f7a2c0 100644 --- a/es-core/src/components/GameSelectorComponent.h +++ b/es-core/src/components/GameSelectorComponent.h @@ -25,6 +25,13 @@ public: mSystem->getRootFolder()->setUpdateListCallback([&]() { mNeedsRefresh = true; }); } + ~GameSelectorComponent() + { + if (std::find(SystemData::sSystemVector.cbegin(), SystemData::sSystemVector.cend(), + mSystem) != SystemData::sSystemVector.cend()) + mSystem->getRootFolder()->setUpdateListCallback(nullptr); + } + enum class GameSelection { RANDOM, // Replace with AllowShortEnumsOnASingleLine: false (clang-format >=11.0). LAST_PLAYED, From d7e76e78284b73a25b45880628af4c592548a367 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sun, 20 Feb 2022 19:32:01 +0100 Subject: [PATCH 82/82] Documentation update. --- CHANGELOG.md | 13 +++++++++++-- THEMES-DEV.md | 42 +++++++++++++++++++++++++++++++----------- USERGUIDE-DEV.md | 35 +++++++++++++++++++++++++++-------- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2b37947e..404d3ba39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,15 +15,20 @@ * Made gamelist theming much more flexible by allowing any number of elements of any types to be defined * Deprecated multiple older theming concepts like features, extras and hardcoded metadata attributes * Reorganized the UI Settings menu a bit and added entries to set the variant and aspect ratio for newer theme sets +* Removed the "Play videos immediately (override theme)" setting +* Renamed the sound menu option "Play audio for videos in the gamelist view" to "Play audio for gamelist and system view videos" * Added support for defining which types of game media to use for all image elements (and also for the video component static image) * Added a legacy (backward compatibility) mode for still supporting older RetroPie EmulationStation themes * Added theme support for Lottie animations (vector graphics) * Added a GameSelectorComponent for displaying game media and metadata in the system view +* Added support for displaying videos, Lottie animations and date/time elements to the system view * Replaced the forceUppercase theme property with a more versatile letterCase property (forceUppercase is retained for legacy theme compatibility) * Made it possible to set any text element as a scrollable container using either metadata values or literal strings * Added support for defining the scrollable container speed, start delay and reset delay from the theme configuration * Added theme support for defining the opacity for most element types +* Added theme support for defining the video fade-in time * Added theme support for enabling and disabling video pillarboxes and scanline rendering +* Added theme support for enabling or disabling audio playback for videos * Disabled the pillarboxes and scanline rendering menu options when using a non-legacy theme set * Improved theme element placement by replacing the "alignment" and "logoAlignment" properties with specific horizontal and vertical properties * Made it possible to use almost all game metadata field when theming text elements @@ -36,6 +41,7 @@ * Set the option "Play audio for screensaver videos" as enabled by default * Added the ability to set a manual sortname specifically for custom collections using the metadata editor * When scraping in semi-automatic mode, horizontal scrolling of long game names are no longer reset when automatically selecting the result +* Added support for using the tilde (~) symbol in the es_systems.xml path entries to expand to the user home directory * Reduced CPU usage significantly when a menu is open by not rendering the bottom of the stack * Reduced CPU usage significantly by only rendering the necessary systems in SystemView * Added an OpenGL ES 2.0 renderer (borrowed from the RetroPie fork of EmulationStation) @@ -45,7 +51,7 @@ * Changed the warning log level for missing theme files to debug level if the paths are set using variables * Added new theme system variables for differentiating between collections and non-collection systems * Added a color model conversion shader for converting from BGRA to RGBA -* Added renderer support for supplying a separate format than internalFormat when creating textures (although not really supported by the OpenGL standard) +* Added opacity support to the scanline shader * Added the rlottie library as a Git subtree * On Windows all dependencies were moved in-tree to the "external" directory to greatly simplify the build environment * Updated the build scripts to support native M1/ARM builds on macOS @@ -65,6 +71,7 @@ * Moved UIModeController.cpp from the es-app/views directory to es-app * Set the clang-format option SpaceBeforeCpp11BracedList to true and reformatted the codebase * Removed some unnecessary typedefs and replaced the remaining ones with the more modern "using" keyword +* Greatly simplified the video controls code (play/stop/pause etc.) * Removed the deprecated VideoVlcComponent * Lots of general code cleanup and refactoring * Updated and improved the theming documentation @@ -76,9 +83,11 @@ * Changing the setting "Group unthemed custom collections" could lead to incorrect custom collections sorting under some circumstances * When multi-scraping in semi-automatic mode and a long game name was scrolling, the start position was not reset when scraping the next game * Slide and fade transitions would sometimes stop working after changing theme sets +* Using fade transitions, when holding a direction button to scroll the system view carousel, the key repeat would cause an unwanted background rendering * Horizontal and vertical gradients were mixed up (showing the opposite gradient type if set in a theme) -* The VideoComponent static images were not fading out smoothly on gamelist fast-scrolling (only fixed for non-legacy themes) +* The VideoComponent static images were not fading out smoothly on gamelist fast-scrolling * Rating icon outlines would not fade out correctly when fast-scrolling in a gamelist +* If setting an origin other than 0.5 for a video with pillarboxes enabled, the video would not get centered on the black rectangle * Clearing a game in the metadata editor would sometimes not remove all media files (if there were both a .jpg and a .png for a certain file type) * The ScummVM platform entry was missing for TheGamesDB which resulted in very inaccurate scraper searches * During multi-scraping the busy indicator was not displayed after a result was acquired but before the thumbnail was completely downloaded diff --git a/THEMES-DEV.md b/THEMES-DEV.md index 861684b68..77b15a4c4 100644 --- a/THEMES-DEV.md +++ b/THEMES-DEV.md @@ -78,6 +78,7 @@ The following are the most important changes compared to the legacy theme struct * The `video` element properties `showSnapshotNoVideo` and `showSnapshotDelay` have been removed * The ambiguous `alignment` property has been replaced with the `horizontalAlignment` and `verticalAlignment` properties (the same is true for `logoAlignment` for the `carousel` component) * The `forceUppercase` property has been replaced with the more versatile `letterCase` property +* The carousel text element hacks `systemInfo` and `logoText` have been removed and replaced with proper carousel properties * Correct theme structure is enforced more strictly than before, and deviations will generate error log messages and make the theme loading fail * Many additional elements and properties have been added, refer to the [Reference](THEMES-DEV.md#reference) section for more information @@ -761,6 +762,7 @@ Properties: - The image will be resized as large as possible so that it fits within this size and maintains its aspect ratio. Use this instead of `size` when you don't know what kind of image you're using so it doesn't get grossly oversized on one axis (e.g. with a game's image metadata). * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0.5 0.5` * `rotation` - type: FLOAT - Angle in degrees that the image should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. @@ -816,9 +818,10 @@ Properties: #### video -Plays a video and provides support for displaying a static image for a defined time period before starting the video player. Although an unlimited number of videos could in theory be defined per view it's strongly recommended to keep it at a single instance. Playing multiple videos simultaneously takes a lot of CPU resources and ES-DE currently has no audio mixing capabilities so the audio would not play correctly. +Plays a video and provides support for displaying a static image for a defined time period before starting the video player. Although an unlimited number of videos could in theory be defined per view it's recommended to keep it at a single instance as playing videos takes a lot of CPU resources. But if still going for multiple videos, make sure to use the `audio` property to disable audio on all but one video as ES-DE currently has no audio mixing capabilities so the sound would not play correctly. To use videos in the `system` view, you either need to set a static video using the `path` property, or you need to create a `gameselector` element so game videos can be used. Supported views: +* `system ` * `gamelist` Instances per view: @@ -832,15 +835,10 @@ Properties: - The video will be resized as large as possible so that it fits within this size and maintains its aspect ratio. Use this instead of `size` when you don't know what kind of video you're using so it doesn't get grossly oversized on one axis (e.g. with a game's video metadata). * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. - - Default is `0.5 0.5` -* `rotation` - type: FLOAT - - Angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. - - Default is `0` -* `rotationOrigin` - type: NORMALIZED_PAIR - - Point around which the text will be rotated. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0.5 0.5` * `path` - type: PATH - - Path to a video file. Setting a value for this property will make the video static, i.e. it will only play this video regardless of whether there is a game video available or not. If the `default` property has also been set, it will be overridden as the `path` property takes precedence. + - Path to a video file. Setting a value for this property will make the video static, i.e. it will only play this video regardless of whether there is a game video available or not (this also applies to the `system` view if you have a `gameselector` element defined). If the `default` property has also been set, it will be overridden as the `path` property takes precedence. * `default` - type: PATH - Path to a default video file. The default video will be played when the selected game does not have a video. This property is also applied to any custom collection that does not contain any games when browsing the grouped custom collections system. * `defaultImage` - type: PATH @@ -857,6 +855,11 @@ Properties: - `backcover` - This will look for a box back cover image. - `3dbox` - This will look for a 3D box image. - `fanart` - This will look for a fan art image. +* `gameselector` - type: STRING + - If more than one gameselector elements have been defined, this property makes it possible to state which one to use. If multiple gameselector elements have been defined and this property is missing then the first entry will be chosen and a warning message will be logged. If only a single gameselector has been defined, this property is ignored. The value of this property must match the `name` attribute value of the gameselector element. +* `audio` - type: BOOLEAN + - Whether to enable or disable audio playback for the video. + - Default is `true` * `interpolation` - type: STRING - Interpolation method to use when scaling raster images. Nearest neighbor (`nearest`) preserves sharp pixels and linear filtering (`linear`) makes the image smoother. Note that this property only affects the static image, not the video scaling. This property also has no effect on scalable vector graphics (SVG) images. - Valid values are `nearest` or `linear` @@ -865,12 +868,16 @@ Properties: - Whether to render black pillarboxes (and to a lesses extent letterboxes) for videos with aspect ratios where this is applicable. This is for instance useful for arcade game videos in vertical orientation. - Default is `true` * `scanlines` - type: BOOLEAN - - Whether to use a shader to render scanlines. This property is not compatible with `opacity` so enabling it will set the opacity to `1` (unless it was set to `0` in which case the entire video element is hidden). + - Whether to use a shader to render scanlines. - Default is `false` * `delay` - type: FLOAT - Delay in seconds before video will start playing. During the delay period the game image defined via the `imageType` property will be displayed. If that property is not set, then the `delay` property will be ignored. - Minimum value is `0` and maximum value is `15` - Default is `1.5` +* `fadeInTime` - type: FLOAT + - Time in seconds to fade in the video from pure black. This is completely unrelated to the `scrollFadeIn` property. Note that if this is set to zero it may seem as if the property doesn't work correctly as many ScreenScraper videos have a fade-in baked into the actual video stream. Setting this property to lower than 0.3 seconds or so is generally a bad idea for videos that don't have a fade-in baked in as transitions from the static image will then look like a bad jump cut. + - Minimum value is `0` and maximum value is `8` + - Default is `1` * `scrollFadeIn` - type: BOOLEAN - If enabled, a short fade-in animation will be applied when scrolling through games in the gamelist view. This animation is only applied to images and not to actual videos, so if no image metadata has been defined then this property has no effect. For this to work correctly the `delay` property also needs to be set. - Default is `false` @@ -890,6 +897,7 @@ Properties: Lottie (vector graphics) animation. Note that these animations take a lot of memory and CPU resources if scaled up to large sizes so it's adviced to not add too many of them to the same view and to not make them too large. Supported views: +* `system ` * `gamelist` Instances per view: @@ -901,6 +909,7 @@ Properties: - If only one axis is specified (and the other is zero), the other will be automatically calculated in accordance with the animation's aspect ratio. Note that this is sometimes not entirely accurate as some animations contain invalid size information. * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0.5 0.5` * `rotation` - type: FLOAT - Angle in degrees that the animation should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. @@ -950,6 +959,7 @@ Properties: - Default is `0.15 0.20` * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0.5 0.5` * `rotation` - type: FLOAT - Angle in degrees that the badges should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. @@ -1056,6 +1066,7 @@ Properties: - `w h` - works like a "text box." If `h` is non-zero and `h` <= `fontSize` (implying it should be a single line of text), text that goes beyond `w` will be truncated with an elipses (...). * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0 0` * `rotation` - type: FLOAT - Angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. Rotation is not possible if the `container` property has been set to true. @@ -1143,6 +1154,7 @@ Properties: Displays a date and time as a text string. The format is ISO 8601 (YYYY-MM-DD) by default, but this can be changed using the `format` property. The text _unknown_ will be shown by default if there is no time stamp available. If the property `displayRelative` has been set, the text will be shown as _never_ in case of no time stamp. Supported views: +* `system` * `gamelist` Instances per view: @@ -1157,6 +1169,7 @@ Properties: - `w h` - works like a "text box." If `h` is non-zero and `h` <= `fontSize` (implying it should be a single line of text), text that goes beyond `w` will be truncated with an elipses (...). * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0 0` * `rotation` - type: FLOAT - Angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. @@ -1165,10 +1178,12 @@ Properties: - Point around which the text will be rotated. - Default is `0.5 0.5`. * `metadata` - type: STRING - - This displays the metadata values that are available for the game. If an invalid metadata field is defined, the text "unknown" will be printed. + - This displays the metadata values that are available for the game. If an invalid metadata field is defined, the text "unknown" will be printed. To use this property from the `system` view, you will first need to add a `gameselector` element. - Valid values: - `releasedate` - The release date of the game. - `lastplayed` - The time the game was last played. This will be displayed as a value relative to the current date and time by default, but can be overridden using the `displayRelative` property. +* `gameselector` - type: STRING + - If more than one gameselector elements have been defined, this property makes it possible to state which one to use. If multiple gameselector elements have been defined and this property is missing then the first entry will be chosen and a warning message will be logged. If only a single gameselector has been defined, this property is ignored. The value of this property must match the `name` attribute value of the gameselector element. This property is only needed for the `system` view and only if the `metadata` property is utilized. * `fontPath` - type: PATH - Path to a TrueType font (.ttf). * `fontSize` - type: FLOAT @@ -1230,6 +1245,7 @@ Properties: - `w h` - works like a "text box." If `h` is non-zero and `h` <= `fontSize` (implying it should be a single line of text), text that goes beyond `w` will be truncated with an elipses (...). * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0 0` * `rotation` - type: FLOAT - Angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. @@ -1278,6 +1294,7 @@ Properties: - Only one value is actually used. The other value should be zero. (e.g. specify width OR height, but not both. This is done to maintain the aspect ratio.) * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` - Default is `0 0` * `rotation` - type: FLOAT - Angle in degrees that the rating should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. @@ -1323,6 +1340,7 @@ Properties: - Default is `0 0.38375` * `origin` - type: NORMALIZED_PAIR - Where on the carousel `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the carousel exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` * `color` - type: COLOR - Color of the carousel background panel. Setting a value of `00000000` makes the background panel transparent. - Default is `FFFFFFD8` @@ -1401,6 +1419,7 @@ Properties: * `size` - type: NORMALIZED_PAIR * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. If the position and size attributes are themeable, origin is implied. + - Minimum value per axis is `0` and maximum value per axis is `1` * `selectorHeight` - type: FLOAT - Height of the "selector bar". * `selectorOffsetY` - type: FLOAT @@ -1446,7 +1465,7 @@ Properties: #### gameselector -Selects games from the gamelists when navigating the `system` view. This makes it possible to display game media and game metadata directly from this view. It's possible to make separate gameselector configurations per game system, so that for instance a random game could be displayed for one system and the most recently played game could be displayed for another system. It's also possible to define multiple gameselector elements with different selection criterias per game system which makes it possible to for example set a random fan art background image and at the same time display a box cover image of the most recently played game. The gameselector logic can be used for the `image` and `text` elements. +Selects games from the gamelists when navigating the `system` view. This makes it possible to display game media and game metadata directly from this view. It's possible to make separate gameselector configurations per game system, so that for instance a random game could be displayed for one system and the most recently played game could be displayed for another system. It's also possible to define multiple gameselector elements with different selection criterias per game system which makes it possible to for example set a random fan art background image and at the same time display a box cover image of the most recently played game. The gameselector logic can be used for the `image`, `video`, `text` and `datetime` elements. Supported views: * `system` @@ -1481,6 +1500,7 @@ Properties: * `origin` - type: NORMALIZED_PAIR - Where on the element `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the element exactly in the middle of the screen. + - Minimum value per axis is `0` and maximum value per axis is `1` * `textColor` - type: COLOR - Default is `777777FF` * `textColorDimmed` - type: COLOR diff --git a/USERGUIDE-DEV.md b/USERGUIDE-DEV.md index 6d96e5c5c..c5d4a7bc1 100644 --- a/USERGUIDE-DEV.md +++ b/USERGUIDE-DEV.md @@ -150,6 +150,29 @@ There will be a lot of directories created if using the es_systems.xml file bund ![alt text](images/es-de_ui_easy_setup.png "ES-DE Easy Setup") _This is the dialog shown if no game files were found. It lets you configure the ROM directory if you don't want to use the default one, and you can also generate the game systems directory structure. Note that the directory is the physical path, and that your operating system may present this as a localized path if you are using a language other than English._ +## Placing games into non-standard directories + +As explained above, the basic logic for how ES-DE works is that it expects game files to be placed into a standardized directory structure under the ROMs directory. The location of this directory is configurable so it could for instance be placed on an external storage device or on a file share served by a NAS. The way it's implemented is via the %ROMPATH% variable in the es_systems.xml file which will always point to this ROM directory. For example this is an entry for the Super Nintendo system: +``` +%ROMPATH%/snes +``` + +In theory it's possible to make a custom system entry and hardcode the path to a specific directory instead of using the %ROMPATH% variable, but this is not really supported and it also makes custom collections non-portable as the path to every game will be an absolute path rather than a path relative to the %ROMPATH% variable. So if you move your games to a different directory, you would manually need to modify all your custom collections configuration files as well as your custom es_systems.xml file. + +If you really insist on not placing your games into the ES-DE standard directory structure, a much better solution is to symlink the game directories into the standard directory. In this way you don't need to make a custom es_systems.xml file and any additional emulators and other configuration added to future ES-DE releases will just work after upgrading. + +This is an example of symlinking the Super Nintendo game directory on Unix and macOS: +``` +cd ~/ROMs +ln -s ~/my_games/super_nintendo/ snes +``` + +And on Windows (you need to run this as Administrator): +``` +cd C:\Users\Myusername\ROMs +mklink /D snes "C:\My Games\Super Nintendo\" +``` + ## Disabling game systems The way ES-DE works is that it will always try to load any system for which there are game files available, so to disable a system it needs to be hidden from ES-DE. This is easily accomplished by renaming the game directory to something that is not recognized, for example changing `~/ROMs/c64` to `~/ROMs/c64_DISABLED`. Another approach is to create a subdirectory named DISABLED (or whatever name you prefer that is not matching a supported system) in the ROMs directory and move the game folder there, such as `~/ROMs/DISABLED/c64`. This makes it easy to disable and re-enable game systems in ES-DE. Note that the gamelist.xml file and any game media files are retained while the system is disabled so this is an entirely safe thing to do. @@ -1081,7 +1104,7 @@ Images of cartridges, diskettes, tapes, CD-ROMs etc. that were used to distribut **Fan art images** -Fan art. Disabled by default as not everyone may want these images, and because they slow down the scraping. +Fan art. These can get quite large so if you don't need them then disable this option to speed up the scraping process. #### Miximage settings @@ -1303,10 +1326,6 @@ If enabled, it's possible to navigate between gamelists using the _Left_ and _Ri Activates or deactivates the built-in help system that provides contextual information regarding button usage. -**Play videos immediately (override theme)** - -Some themes (including rbsimple-DE) display the game images briefly before playing the game videos. This setting forces the videos to be played immediately, regardless of the configuration in the theme. Note though that if there is a video available for a game, but no images, the video will always start to play immediately no matter the theme configuration or whether this settings has been enabled or not. - #### Media viewer settings Settings for the media viewer that is accessible from the gamelist views. @@ -1419,9 +1438,9 @@ Sets the volume for the navigation sounds. Sets the volume for the video player. This applies to the gamelist view, the media viewer and the video screensaver. -**Play audio for videos in the gamelist view** +**Play audio for gamelist and system view videos** -With this turned off, audio won't play for videos in the gamelists. +With this turned off, audio won't play for videos in the gamelist or system views. Note that even with this option enabled videos may be muted as the audio can be disabled per video element from the theme configuration. **Play audio for media viewer videos** @@ -1473,7 +1492,7 @@ If the theme set in use provides themes for custom collections, then this entry **Create new custom collection** -This lets you create a completely custom collection with a name of your choice. +This lets you create a completely custom collection with a name of your choice. If the selected name collides with an existing name, a sequence number inside brackets will be appended to the collection name, such as _fighting (1)_ if a _fighting_ collection already existed. Note that custom collection names are always converted to lowercase. **Delete custom collection**