diff --git a/projects/frontend/es-app/src/MainRunner.cpp b/projects/frontend/es-app/src/MainRunner.cpp index 02fe7b0d1454130684c23ee5db0e9e135d91b0af..00b3d551039e9700043e9eb7d7105b36224ecfaa 100644 --- a/projects/frontend/es-app/src/MainRunner.cpp +++ b/projects/frontend/es-app/src/MainRunner.cpp @@ -33,6 +33,7 @@ #include "rendering/textures/TextureManager.h" #include "rendering/fonts/FontManager.h" #include "guis/menus/modaltasks/MenuModalInitDevice.h" +#include "guis/menus/modaltasks/MenuModalFactoryReset.h" #include #include #include @@ -75,6 +76,9 @@ MainRunner::ExitState MainRunner::Run() { try { + // Reinstall backuped files after a factoryu reset + MenuModalFactoryReset::ReinstallFilesAfterReboot(); + // Hardware board Board board(*this, mOptions); // Wifi @@ -641,7 +645,7 @@ bool MainRunner::TryToLoadConfiguredSystems(SystemManager& systemManager) { bool portable = BootConf::Instance().AsString("case") == "GPiV1:1" || Board::IsHandheldSystem(); - if (!systemManager.LoadSystemConfigurations(portable)) + if (!systemManager.LoadSystemConfigurations(portable, Board::Instance().CrtBoard().IsCrtAdapterAttached())) { { LOG(LogError) << "[MainRunner] Error while parsing systems configuration file!"; } { LOG(LogError) << "[MainRunner] IT LOOKS LIKE YOUR SYSTEMS CONFIGURATION FILE HAS NOT BEEN SET UP OR IS INVALID. YOU'LL NEED TO DO THIS BY HAND, UNFORTUNATELY.\n\n" diff --git a/projects/frontend/es-app/src/Upgrade.cpp b/projects/frontend/es-app/src/Upgrade.cpp index e62f604b1678c35c56c1b6d293c18ef9ae33a45a..171292c23b6b35eeda41b932e36007388e546499 100755 --- a/projects/frontend/es-app/src/Upgrade.cpp +++ b/projects/frontend/es-app/src/Upgrade.cpp @@ -300,7 +300,11 @@ String Upgrade::GetRemoteReleaseVersion() void Upgrade::DoManualCheck(IManualUpdateCheckInterface* callback) { - mDomainName = ""; - mManualCheckInterface = callback; - mSignal.Fire(); + if (callback == nullptr) mManualCheckInterface = nullptr; + else + { + mDomainName = ""; + mManualCheckInterface = callback; + mSignal.Fire(); + } } diff --git a/projects/frontend/es-app/src/components/RatingComponent.cpp b/projects/frontend/es-app/src/components/RatingComponent.cpp index 8ab9c0bf7f12fcfb0b8a371850e2f0b02b3a27bc..f6b3b9c99b0b7d8027d2571224dcaa20e9fd61c0 100644 --- a/projects/frontend/es-app/src/components/RatingComponent.cpp +++ b/projects/frontend/es-app/src/components/RatingComponent.cpp @@ -5,24 +5,25 @@ #include "WindowManager.h" #include -Path RatingComponent::sFilledTexture(":/star_filled.svg"); -Path RatingComponent::sUnfilledTexture(":/star_unfilled.svg"); +String RatingComponent::sFilledTexture(":/star_filled.svg"); +String RatingComponent::sUnfilledTexture(":/star_unfilled.svg"); -RatingComponent::RatingComponent(WindowManager&window, unsigned int color, float value) +RatingComponent::RatingComponent(WindowManager&window, unsigned int color, float value, const String& discriminent) : ThemableComponent(window) + , mDiscriminent(discriminent) , mValue(value) , mColor(color) , mOriginColor(color) , mId(0) , mInterface(nullptr) { - mFilledTexture = TextureManager::Instance().Create(sFilledTexture); - mUnfilledTexture = TextureManager::Instance().Create(sUnfilledTexture); + mFilledTexture = TextureManager::Instance().Create(Path(discriminent + '|' + sFilledTexture)); + mUnfilledTexture = TextureManager::Instance().Create(Path(discriminent + '|' + sUnfilledTexture)); mSize.Set(64 * sRatingStarCount, 64); } -RatingComponent::RatingComponent(WindowManager&window, float value) - : RatingComponent(window, 0xFFFFFFFF, value) +RatingComponent::RatingComponent(WindowManager&window, float value, const String& discriminent) + : RatingComponent(window, 0xFFFFFFFF, value, discriminent) { } @@ -53,9 +54,9 @@ void RatingComponent::onSizeChanged() { int heightPx = Math::roundi(mSize.y()); if (mFilledTexture.Valid()) - mFilledTexture.SetTargetSize(heightPx, heightPx); + mFilledTexture.SetTargetSize(heightPx, heightPx, true); if(mUnfilledTexture.Valid()) - mUnfilledTexture.SetTargetSize(heightPx, heightPx); + mUnfilledTexture.SetTargetSize(heightPx, heightPx, true); } } @@ -76,7 +77,7 @@ bool RatingComponent::ProcessInput(const InputCompactEvent& event) { if (event.ValidReleased() || event.AnyRightReleased()) { - if (mValue += .5f / sRatingStarCount; mValue > 1.1f) mValue = 0.f; + if (mValue += .5f / sRatingStarCount; mValue > 1.f) mValue = 0.f; if (mInterface != nullptr) mInterface->RatingChanged(mId, mValue); } @@ -94,8 +95,8 @@ void RatingComponent::OnApplyThemeElement(const ThemeElement& element, ThemeProp { if (hasFlag(properties, ThemePropertyCategory::Path)) { - mFilledTexture = TextureManager::Instance().Create(element.HasProperty(ThemePropertyName::FilledPath) ? element.AsPath(ThemePropertyName::FilledPath) : sFilledTexture); - mUnfilledTexture = TextureManager::Instance().Create(element.HasProperty(ThemePropertyName::UnfilledPath) ? element.AsPath(ThemePropertyName::UnfilledPath) : sUnfilledTexture); + mFilledTexture = TextureManager::Instance().Create(Path(mDiscriminent + '|' + (element.HasProperty(ThemePropertyName::FilledPath) ? element.AsString(ThemePropertyName::FilledPath) : sFilledTexture))); + mUnfilledTexture = TextureManager::Instance().Create(Path(mDiscriminent + '|' + (element.HasProperty(ThemePropertyName::UnfilledPath) ? element.AsString(ThemePropertyName::UnfilledPath) : sUnfilledTexture))); onSizeChanged(); } } diff --git a/projects/frontend/es-app/src/components/RatingComponent.h b/projects/frontend/es-app/src/components/RatingComponent.h index 3f9ec22bbc8040c278d8d0af09be4c6be428f271..ac2a9852e2ea02df54b59764945a24a657cb71a9 100644 --- a/projects/frontend/es-app/src/components/RatingComponent.h +++ b/projects/frontend/es-app/src/components/RatingComponent.h @@ -12,8 +12,8 @@ class RatingComponent : public ThemableComponent { public: - explicit RatingComponent(WindowManager&window, float value); - explicit RatingComponent(WindowManager&window, unsigned int color, float value); + explicit RatingComponent(WindowManager&window, float value, const String& discriminent); + explicit RatingComponent(WindowManager&window, unsigned int color, float value, const String& discriminent); [[nodiscard]] String getValue() const override { return String(mValue, 2); } void setValue(const String& value) override; // Should be a normalized float (in the range [0..1]) - if it's not, it will be clamped. @@ -57,12 +57,13 @@ class RatingComponent : public ThemableComponent private: static constexpr int sRatingStarCount = 5; - static Path sFilledTexture; - static Path sUnfilledTexture; + static String sFilledTexture; + static String sUnfilledTexture; Texture mFilledTexture; Texture mUnfilledTexture; + String mDiscriminent; float mValue; unsigned int mColor; unsigned int mOriginColor; diff --git a/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp b/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp index 267ae019fdd07ca0b3a79519029ccf6a26df214c..4ad1128d30865fef327e9c0bc55ec271714580bf 100755 --- a/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp +++ b/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp @@ -57,13 +57,14 @@ ScraperSearchComponent::ScraperSearchComponent(WindowManager& window, bool lowRe mGrid.setEntry(mResultName, Vector2i(1, 0), false, true, Vector2i(5, 1)); // Image thumbnail - mResultThumbnail = std::make_shared(mWindow, ImgProps::KeepRatio | ImgProps::NoCache); + mResultThumbnail = std::make_shared(mWindow, ImgProps::KeepRatio | ImgProps::NoCache); if(!lowResolution) mGrid.setEntry(mResultThumbnail, Vector2i(1, 1), false, false, Vector2i(1, 4)); // selected result desc + container mDescContainer = std::make_shared(mWindow); mResultDesc = std::make_shared(mWindow, "RESULT DESC", font, menuTheme.Text().color); + mResultDesc->setMultiline(true); mDescContainer->addChild(mResultDesc.get()); mDescContainer->setAutoScroll(true); // show description on the right @@ -86,19 +87,19 @@ ScraperSearchComponent::ScraperSearchComponent(WindowManager& window, bool lowRe mGrid.setEntry(mLabelPlayers, Vector2i(4, 3), false, false, Vector2i(1, 1)); // Value - mValueDeveloper = std::make_shared(mWindow, "", font, mdColor); + mValueDeveloper = std::make_shared(mWindow, "", font, mdColor); mGrid.setEntry(mValueDeveloper, Vector2i(3, 1), false, true, Vector2i(1, 1)); - mValuePublisher = std::make_shared(mWindow, "", font, mdColor); + mValuePublisher = std::make_shared(mWindow, "", font, mdColor); mGrid.setEntry(mValuePublisher, Vector2i(3, 2), false, true, Vector2i(1, 1)); - mValueGenre = std::make_shared(mWindow, "", font, mdColor); + mValueGenre = std::make_shared(mWindow, "", font, mdColor); mGrid.setEntry(mValueGenre, Vector2i(3, 3), false, true, Vector2i(1, 1)); - mValueRating = std::make_shared(mWindow, menuTheme.Text().color, 0.f); + mValueRating = std::make_shared(mWindow, menuTheme.Text().color, 0.f, "scraper"); mGrid.setEntry(mValueRating, Vector2i(5, 1), false, true, Vector2i(1, 1)); mValueReleaseDate = std::make_shared(mWindow); mValueReleaseDate->setColor(mdColor); mValueReleaseDate->setHorizontalAlignment(::HorizontalAlignment::Left); mGrid.setEntry(mValueReleaseDate, Vector2i(5, 2), false, true, Vector2i(1, 1)); - mValuePlayers = std::make_shared(mWindow, "", font, mdColor); + mValuePlayers = std::make_shared(mWindow, "", font, mdColor); mGrid.setEntry(mValuePlayers, Vector2i(5, 3), false, true, Vector2i(1, 1)); mRunning = true; @@ -145,7 +146,7 @@ void ScraperSearchComponent::onSizeChanged() // Resize title & description const float boxartCellScale = 0.9f; - mResultThumbnail->setResize(mGrid.getColWidth(1) * boxartCellScale, mGrid.getRowHeight(1, 4) * boxartCellScale); + mResultThumbnail->setSize(mGrid.getColWidth(1) * boxartCellScale, mGrid.getRowHeight(1, 4) * boxartCellScale); mResultThumbnail->setKeepRatio(true); mDescContainer->setSize(mGrid.getColWidth(2, 5) * boxartCellScale, mGrid.getRowHeight(4, 5) * boxartCellScale); mResultDesc->setSize(mDescContainer->getSize().x(), 0); // make desc text wrap at edge of container @@ -179,7 +180,7 @@ void ScraperSearchComponent::UpdateInfoPane(const FileData* game) if (game != nullptr) { mResultName->setText(game->Name().ToUpperCaseUTF8()); - mResultDesc->setText(game->Metadata().Description().ToUpperCaseUTF8()); + mResultDesc->setText(game->Metadata().Description()); mDescContainer->reset(); // Image diff --git a/projects/frontend/es-app/src/components/ScraperSearchComponent.h b/projects/frontend/es-app/src/components/ScraperSearchComponent.h index 06c657d90ac86a2f63220df7066340cf3e3ac623..3bed467613ed4537aa665e9c2a29be5aaa8ecd12 100644 --- a/projects/frontend/es-app/src/components/ScraperSearchComponent.h +++ b/projects/frontend/es-app/src/components/ScraperSearchComponent.h @@ -3,6 +3,8 @@ #include "components/base/Component.h" #include "components/ComponentGrid.h" #include "components/BusyComponent.h" +#include "components/PictureComponent.h" +#include "components/TextScrollComponent.h" #include class ComponentList; @@ -51,14 +53,14 @@ class ScraperSearchComponent : public Component std::shared_ptr mResultName; std::shared_ptr mDescContainer; std::shared_ptr mResultDesc; - std::shared_ptr mResultThumbnail; + std::shared_ptr mResultThumbnail; std::shared_ptr mValueRating; std::shared_ptr mValueReleaseDate; - std::shared_ptr mValueDeveloper; - std::shared_ptr mValuePublisher; - std::shared_ptr mValueGenre; - std::shared_ptr mValuePlayers; + std::shared_ptr mValueDeveloper; + std::shared_ptr mValuePublisher; + std::shared_ptr mValueGenre; + std::shared_ptr mValuePlayers; std::shared_ptr mLabelRating; std::shared_ptr mLabelReleaseDate; diff --git a/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h b/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h index e09d001ca138d49a433552e701842669dd486ec9..96aaae0286a71fcf80b08b773ca97beb38ab5e7f 100644 --- a/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h +++ b/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h @@ -140,6 +140,14 @@ class EmulatorDescriptor //! Has at least one core? [[nodiscard]] bool HasAny() const { return !mCores.empty(); } + [[nodiscard]] bool CRTAvailable() const + { + for(const CoreData& core : mCores) + if (core.CRTAvailable()) + return true; + return false; + } + /*! * @brief Check if the emulator has a core matching the given name * @param name Core name diff --git a/projects/frontend/es-app/src/emulators/EmulatorList.h b/projects/frontend/es-app/src/emulators/EmulatorList.h index f25b7d486049c1a9c672748285f33c941dcd1726..4dc91435d23848f1769198bb9808577da248c553 100644 --- a/projects/frontend/es-app/src/emulators/EmulatorList.h +++ b/projects/frontend/es-app/src/emulators/EmulatorList.h @@ -45,6 +45,14 @@ class EmulatorList return false; } + [[nodiscard]] bool CRTAvailable() const + { + for(int i=mEmulatorCount; --i>=0; ) + if (mEmulators[i].CRTAvailable()) + return true; + return false; + } + void Clear() { mEmulatorCount = 0; } void AddEmulator(const EmulatorDescriptor& emulatorDescriptor) diff --git a/projects/frontend/es-app/src/emulators/run/GameRunner.cpp b/projects/frontend/es-app/src/emulators/run/GameRunner.cpp index 27c94f92f444af9984f3d79555c525dd33766592..20d83ae7fd6645486d4432f0da417574773bd0ad 100644 --- a/projects/frontend/es-app/src/emulators/run/GameRunner.cpp +++ b/projects/frontend/es-app/src/emulators/run/GameRunner.cpp @@ -119,7 +119,6 @@ String GameRunner::CreateCommandLine(const FileData& game, const EmulatorData& e .Replace("%CRT%", BuildCRTOptions(game, emulator, data.Crt(), RotationManager::ShouldRotateGame(game), demo)) .Replace("%VIDEO_BACKEND%", video_backend); - int luminosity = RecalboxConf::Instance().GetGlobalLightgunLuminosity(); if (!game.Metadata().IsDefaultLightgunLuminosity()) luminosity = game.Metadata().LightgunLuminosity(); @@ -248,6 +247,10 @@ bool GameRunner::RunGame(FileData& game, const EmulatorData& emulator, const Gam // Update last played time game.Metadata().SetLastPlayedNow(); + // Ensure no font has been created during GL context shutdown + if (FontManager::Available()) + FontManager::Instance().ClearFontCaches(); + return exitCode == 0; } @@ -292,7 +295,6 @@ GameRunner::DemoRunGame(const FileData& game, const EmulatorData& emulator, int .Append(" -demoduration ").Append(duration) .Append(" -demoinfoduration ").Append(infoscreenduration); - int exitCode = -1; { Sdl2Runner sdl2Runner; @@ -310,6 +312,9 @@ GameRunner::DemoRunGame(const FileData& game, const EmulatorData& emulator, int NotificationManager::Instance().Notify(game, Notification::EndDemo); { LOG(LogInfo) << "[Run] Demo exit code : " << exitCode; } + // Ensure no font has been created during GL context shutdown + FontManager::Instance().ClearFontCaches(); + // Configgen returns an exitcode 0x33 when the user interact with any pad/mouse if (exitCode == 0x33) { @@ -421,6 +426,9 @@ bool GameRunner::RunKodi() default: break; } + // Ensure no font has been created during GL context shutdown + FontManager::Instance().ClearFontCaches(); + return exitCode == 0; } diff --git a/projects/frontend/es-app/src/games/MetadataDescriptor.cpp b/projects/frontend/es-app/src/games/MetadataDescriptor.cpp index 89f5733550fdd4e9966044a16fb0dceb6bbd4d42..50a9c72b5dc0a02dc0ea41cabde7c1372d2353bb 100644 --- a/projects/frontend/es-app/src/games/MetadataDescriptor.cpp +++ b/projects/frontend/es-app/src/games/MetadataDescriptor.cpp @@ -111,6 +111,7 @@ const MetadataDescriptor& MetadataDescriptor::Default() } initialized = true; defaultData.EnableDirtiness(); + defaultData.mLightgunLuminosity = 0; } return defaultData; diff --git a/projects/frontend/es-app/src/guis/GuiBiosMd5.cpp b/projects/frontend/es-app/src/guis/GuiBiosMd5.cpp index b1b1f432dd17b9004bc10ff01254918e7dfe2aa3..c72de7939767f28f2651bbeb3007e2867591654c 100644 --- a/projects/frontend/es-app/src/guis/GuiBiosMd5.cpp +++ b/projects/frontend/es-app/src/guis/GuiBiosMd5.cpp @@ -75,11 +75,11 @@ void GuiBiosMd5::BuildUI() // Title mTitle = std::make_shared(mWindow, _("MD5 LIST"), menuTheme.Title().font, menuTheme.Title().color, ::Alignment::Center); - mGrid.setEntry(mTitle, Vector2i(1, 0), false, false, Vector2i(1,1) ); + mGrid.setEntry(mTitle, Vector2i(1, 0), false, true, Vector2i(1,1) ); // Header mHeader = std::make_shared(mWindow, "", menuTheme.Footer().font, menuTheme.Footer().color, ::Alignment::Center); - mGrid.setEntry(mHeader, Vector2i(1, 1), false, false, Vector2i(1,1) ); + mGrid.setEntry(mHeader, Vector2i(1, 1), false, true, Vector2i(1,1) ); // List mList = std::make_shared>(mWindow); @@ -121,16 +121,6 @@ void GuiBiosMd5::onSizeChanged() mGrid.setRowHeightPerc(2, 1.0f - (titlePercent + headerPercent), false); mGrid.setSize(mSize); - // Resize all components - float marginPercent = 0.95f; - - // Title and Header need to be anchored to the grid manually - mTitle->setPosition(mGrid.getColWidth(0), 0); - mTitle->setSize(mGrid.getColWidth(1), mGrid.getRowHeight(0)); - - mHeader->setPosition(mGrid.getColWidth(0), mGrid.getRowHeight(0)); - mHeader->setSize(mGrid.getColWidth(1), mGrid.getRowHeight(1)); - // List need to be anchored to the grid manually mList->setPosition(mGrid.getColWidth(0), mGrid.getRowHeight(0, 1)); mList->setSize(mGrid.getColWidth(1), mGrid.getRowHeight(2)); diff --git a/projects/frontend/es-app/src/guis/GuiScraperSingleGameRun.h b/projects/frontend/es-app/src/guis/GuiScraperSingleGameRun.h index b1af263fad67b9cd1c8cd1437e6f1928207f812d..ae728d1def112b98936c986b94a22731239920a3 100644 --- a/projects/frontend/es-app/src/guis/GuiScraperSingleGameRun.h +++ b/projects/frontend/es-app/src/guis/GuiScraperSingleGameRun.h @@ -76,7 +76,7 @@ template class GuiScraperSingleGameRun : public Gui setPosition((Renderer::Instance().DisplayWidthAsFloat() - mSize.x()) / 2, (Renderer::Instance().DisplayHeightAsFloat() - mSize.y()) / 2); // Run! - mScraper->RunOn(ScrapingMethod::All, game, this, (long long)RecalboxSystem::GetMinimumFreeSpaceOnSharePartition()); + mScraper->RunOn(ScrapingMethod::All, game, this, RecalboxSystem::GetMinimumFreeSpaceOnSharePartition()); } void onSizeChanged() override @@ -84,11 +84,11 @@ template class GuiScraperSingleGameRun : public Gui mBox.fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32)); mGrid.setRowHeightPerc(0, 0.04f, false); - mGrid.setRowHeightPerc(1, mGameName->getFont()->Height() / mSize.y(), false); // game name + mGrid.setRowHeightPerc(1, mGameName->getFont()->Height() * 1.2f / mSize.y(), false); // game name mGrid.setRowHeightPerc(2, 0.04f, false); - mGrid.setRowHeightPerc(3, mSystemName->getFont()->Height() / mSize.y(), false); // system name + mGrid.setRowHeightPerc(3, mSystemName->getFont()->Height() * 1.2f / mSize.y(), false); // system name mGrid.setRowHeightPerc(4, 0.04f, false); - mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y() / mSize.y(), false); // buttons + mGrid.setRowHeightPerc(6, mButtonGrid->getSize().y() * 1.8f / mSize.y(), false); // buttons mGrid.setSize(mSize); } diff --git a/projects/frontend/es-app/src/guis/GuiThemeManager.cpp b/projects/frontend/es-app/src/guis/GuiThemeManager.cpp index cc74966bc49b1622baf24c7553280d98fe6aaef1..38dff3218ad7c77a7bb4703239a082c0b93ce4d3 100644 --- a/projects/frontend/es-app/src/guis/GuiThemeManager.cpp +++ b/projects/frontend/es-app/src/guis/GuiThemeManager.cpp @@ -49,6 +49,8 @@ */ +//! Unvalidated lists +bool GuiThemeManager::sRemoteListValidated = false; // Unvalidated (raw) theme list std::vector GuiThemeManager::sListItems; // Validated theme list @@ -205,36 +207,41 @@ int GuiThemeManager::Execute(GuiWaitLongExecution& from, const bool& (void)parameter; from.SetText(_("Loading theme list...")); - if (sDescriptors.empty()) + if (!sRemoteListValidated) { + sListItems.clear(); + sDescriptors.clear(); + sRemoteListValidated = true; + // Prepare http request HttpClient http; bool IsPatron = PatronInfo::Instance().IsPatron(); if (IsPatron) http.SetBearer(PatronInfo::Instance().Token()); - // Get final url - String publicHubUrl = Networks::QueryDNSRecord(sThemeHubDomain); - if (publicHubUrl.empty()) return -1; - String privateHubUrl = Networks::QueryDNSRecord(sPatronThemeHubDomain); - if (privateHubUrl.empty()) return -2; - // Get local themes Path folder("/recalbox/system/themes"); for(const LocalTheme& local : mLocalThemes) sListItems.push_back({folder.ToString(), local.mDescriptorFolder, true, ThemeType::Embedded, false }); // Get public themes - String url(publicHubUrl); url.Append("/-/raw/main/list.json"); - if (String content; http.Execute(url, content, false, 3)) DeserializeThemeList(publicHubUrl, content, false); - else return -3; + String publicHubUrl = Networks::QueryDNSRecord(sThemeHubDomain); + if (publicHubUrl.empty()) { LOG(LogError) << "[GuiThemeManager] Error getting public theme url."; sRemoteListValidated = false; } + else + { + String url(publicHubUrl); url.Append("/-/raw/main/list.json"); + if (String content; http.Execute(url, content, false, 3)) DeserializeThemeList(publicHubUrl, content, false); + else { LOG(LogError) << "[GuiThemeManager] Error loading public theme list."; sRemoteListValidated = false; } + } // get patron themes if (IsPatron) { - url.Assign(privateHubUrl); url.Append("/-/raw/main/list.json"); + String privateHubUrl = Networks::QueryDNSRecord(sPatronThemeHubDomain); + if (privateHubUrl.empty()) { LOG(LogError) << "[GuiThemeManager] Error getting private theme url."; sRemoteListValidated = false; } + String url(privateHubUrl); url.Append("/-/raw/main/list.json"); if (String content; http.Execute(url, content, false, 3)) DeserializeThemeList(privateHubUrl, content, true); - else { LOG(LogError) << "[GuiThemeManager] Error loading Patron theme list !"; } + else { LOG(LogError) << "[GuiThemeManager] Error loading Patron theme list !"; sRemoteListValidated = false; } } int index = 0; @@ -245,12 +252,12 @@ int GuiThemeManager::Execute(GuiWaitLongExecution& from, const bool& from.SetProgress((++index * 100) / (int)sListItems.size()); String baseUrl(item.BaseURL()); baseUrl.Append('/').Append(item.SubFolder()); - url.Assign(baseUrl); url.Append("/descriptor.json"); + String url(baseUrl); url.Append("/descriptor.json"); String content; if (url.StartsWith("http://") || url.StartsWith("https://")) { if (!http.Execute(url, content, false, 3)) - { LOG(LogError) << "[GuiThemeManager] Error downloading descriptor " << url; continue; } + { LOG(LogError) << "[GuiThemeManager] Error downloading descriptor " << url; sRemoteListValidated = false; continue; } } else content = Files::LoadFile(Path(url)); if (ThemeDescriptor descriptor = ThemeDescriptor(baseUrl, item.SubFolder(), content, item.Type(), item.Patron()); descriptor.mValid) @@ -458,7 +465,7 @@ void GuiThemeManager::AnalyseThemeDescriptors() found = false; for(ThemeData::Resolutions res : resolutions) if ((res & file.mResolutions) == res) - if (int resDistance = Math::absi((int)res - (int)currentResolution); resDistance <= fileBestResolutionDistance) { found = true; fileBestCompatibilityDistance = resDistance; } + if (int resDistance = Math::absi((int)res - (int)currentResolution); resDistance <= fileBestResolutionDistance) { found = true; fileBestResolutionDistance = resDistance; } if (!found) continue; // Record current file diff --git a/projects/frontend/es-app/src/guis/GuiThemeManager.h b/projects/frontend/es-app/src/guis/GuiThemeManager.h index bc296feaec68ecaa38cb4059c3fc048d6e851561..6b82785d42f1ca6a21683cd55f34a91de094fff0 100644 --- a/projects/frontend/es-app/src/guis/GuiThemeManager.h +++ b/projects/frontend/es-app/src/guis/GuiThemeManager.h @@ -231,6 +231,8 @@ class GuiThemeManager final : public Gui //! Installation theme download GuiWaitLongExecution* mGuiInstaller; + //! Static list validated ? + static bool sRemoteListValidated; //! Unvalidated (raw) theme list static std::vector sListItems; //! Validated theme list diff --git a/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.cpp b/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.cpp index 3b63eccf42ba7654081db4a0fcb6caf24ff42ed8..a7f7b4620ac836418160d3cd1aef4a7a4ededc5b 100644 --- a/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.cpp +++ b/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.cpp @@ -42,14 +42,14 @@ GuiUpdateRecalbox::GuiUpdateRecalbox(WindowManager& window, const String& tarUrl const float width = Renderer::Instance().DisplayWidthAsFloat() * (Renderer::Instance().Is480pOrLower() ? 0.8f : 0.6f); // Title - mTitle = std::make_shared(mWindow, _("DOWNLOADING UPDATE..."), menuTheme.Title().font, menuTheme.Title().color, ::Alignment::Center); - mGrid.setEntry(mTitle, Vector2i(1, 0), false, false, Vector2i(1,1) ); + mTitle = std::make_shared(mWindow, _("DOWNLOADING UPDATE..."), menuTheme.Title().font, menuTheme.Title().color, ::Alignment::Center); + mGrid.setEntry(mTitle, Vector2i(1, 0), false, true, Vector2i(1,1) ); // Text String text = _("We're downloading __Recalbox__ version **%s**!\n\nOnce the download is complete, Recalbox will reboot and start installing the new version.\nTypical installations take about 5-10mn. **DO NOT reboot or power off Recalbox** until the installation is complete.") .Replace("%s", newVersion); mText = std::make_shared(mWindow, text, *menuTheme.SmallText().font, menuTheme.SmallText().color, ::Alignment::CenterLeft); - mGrid.setEntry(mText, Vector2i(1, 1), false, false, Vector2i(1,1) ); + mGrid.setEntry(mText, Vector2i(1, 1), false, true, Vector2i(1,1) ); // Progress bar mBar = std::make_shared(mWindow, 1); diff --git a/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.h b/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.h index ad4b8599f97db9bf5d9300cc31887fe3737e398d..860a97dc04f9d5fee146cff81ec6ee87b59e01f7 100644 --- a/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.h +++ b/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.h @@ -89,7 +89,7 @@ class GuiUpdateRecalbox: public Gui NinePatchComponent mBackground; ComponentGrid mGrid; - std::shared_ptr mTitle; + std::shared_ptr mTitle; std::shared_ptr mText; std::shared_ptr mBar; std::shared_ptr mEta; diff --git a/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp b/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp index bba8254d06cd76480b91fc3e54fd4757b9505e2d..812c5bdc9f0e778380e2ba951b8d6c8adbd61ed2 100644 --- a/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp +++ b/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp @@ -17,6 +17,7 @@ #include "systems/DownloaderManager.h" #include "netplay/DefaultPasswords.h" #include "utils/SectionFile.h" +#include "guis/GuiScraperRun.h" #include #include #include @@ -798,7 +799,7 @@ void MenuBuilder::AddItem(const InheritableContext& context, const ItemDefinitio case MenuItemType::MetadataHidden: { AddSwitch(context, item, Context().Game()->Metadata().Hidden(), defaultGrayed); break; } case MenuItemType::MetadataAdult: { AddSwitch(context, item, Context().Game()->Metadata().Adult(), defaultGrayed); break; } case MenuItemType::MetadataRotation: { AddSwitch(context, item, Context().Game()->Metadata().Rotation() != RotationType::None, defaultGrayed); break; } - case MenuItemType::MetadataScrape: { AddAction(context, item, true, defaultGrayed); break; } + case MenuItemType::MetadataScrape: { AddAction(context, item, true, GuiScraperRun::IsRunning() || defaultGrayed); break; } case MenuItemType::MetastatPlaytime: { int seconds = Context().Game()->Metadata().TotalPlayTime(); diff --git a/projects/frontend/es-app/src/guis/menus/base/RatingImage.h b/projects/frontend/es-app/src/guis/menus/base/RatingImage.h index c6a68f3acd49314a49d746e83b24b10474a48a0b..eed862f1a2a0b44193fae6c6c022013c5cff7f52 100644 --- a/projects/frontend/es-app/src/guis/menus/base/RatingImage.h +++ b/projects/frontend/es-app/src/guis/menus/base/RatingImage.h @@ -15,8 +15,8 @@ class RatingImage * @param image Image resource */ explicit RatingImage(int height) - : mFilledStar(TextureManager::Instance().Create(Path(":/star_filled.svg"), TextureHolder::Properties::ImmediateLoad)) - , mUnfilledStar(TextureManager::Instance().Create(Path(":/star_unfilled.svg"), TextureHolder::Properties::ImmediateLoad)) + : mFilledStar(TextureManager::Instance().Create(Path(String(height) + '|' + ":/star_filled.svg"), TextureHolder::Properties::ImmediateLoad)) + , mUnfilledStar(TextureManager::Instance().Create(Path(String(height) + '|' + ":/star_unfilled.svg"), TextureHolder::Properties::ImmediateLoad)) , mLastX(-1) , mLastY(-1) , mLastValue(-1) diff --git a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalEmulatorsReset.cpp b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalEmulatorsReset.cpp index 3f2df9c26fef8afb139b9ee1ea54cd02d5f57c11..14f82c2730ad1847e5db3bbf19dbf391054b0483 100644 --- a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalEmulatorsReset.cpp +++ b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalEmulatorsReset.cpp @@ -9,6 +9,8 @@ bool MenuModalEmulatorsReset::TaskExecute(const bool& parameter) (void)parameter; String::List deleteMe ({ + ".cache", // All caches + ".local", // Come obscure emulator configs ".config", // emulator configurations #1 "configs", // emulator configurations #2 ".atari800.cfg", // Atari 8bit config diff --git a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.cpp b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.cpp index addeb7205611059b56b19c4242af5271aa6fedb9..34b98d260d76f0a8cf36f5607a1a6fd5a9e40951 100644 --- a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.cpp +++ b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.cpp @@ -3,10 +3,17 @@ // #include "MenuModalFactoryReset.h" +#include "utils/Files.h" #include #include #include +Path MenuModalFactoryReset::sFlagPath("/overlay/.configs/.files-pending"); +HashMap MenuModalFactoryReset::sBackups +{ + { Path("/recalbox/share/system/configs/retroarch/retroarchcustom.cfg.origin"), Path("/overlay/.configs/.retroarchcustom.cfg.origin.backup") }, +}; + bool MenuModalFactoryReset::TaskExecute(const bool& parameter) { (void)parameter; @@ -21,6 +28,15 @@ bool MenuModalFactoryReset::TaskExecute(const bool& parameter) "/boot/crt/", // CRT Configuration }); + // Backup some files + bool written = false; + for(const auto& it : sBackups) + if (it.first.Exists()) + { + Files::SaveFile(it.second, Files::LoadFile(it.first)); + if (!written) { Files::SaveFile(sFlagPath, "1"); written = true; } + } + // Make boot partition writable if (!RecalboxSystem::MakeBootReadWrite()) { LOG(LogError) << "[ResetFactory] Error making boot r/w"; } @@ -48,3 +64,18 @@ bool MenuModalFactoryReset::TaskExecute(const bool& parameter) return true; } + +void MenuModalFactoryReset::ReinstallFilesAfterReboot() +{ + // Backup some files + if (sFlagPath.Exists()) + { + for (const auto& it: sBackups) + if (it.second.Exists()) + { + Files::SaveFile(it.first, Files::LoadFile(it.second)); + (void) it.second.Delete(); + } + (void)sFlagPath.Delete(); + } +} diff --git a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.h b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.h index 532c504a7fc8e275a8db3bbc3a717e2efe9f37d0..4633a6e09d919d57f9bfd85c88f48772b49e09b1 100644 --- a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.h +++ b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalFactoryReset.h @@ -34,7 +34,17 @@ class MenuModalFactoryReset : private IMenuModalTask _("NO"), nullptr, _("YES"), ReallyWant)); } + /*! + * @brief Reinstall files that have been kept after a reset factory + */ + static void ReinstallFilesAfterReboot(); + private: + //! Flag path + static Path sFlagPath; + //! Static map of file being saved before a reset factory for reinstallation + static HashMap sBackups; + //! Constructor explicit MenuModalFactoryReset(WindowManager& window) : IMenuModalTask(window, _("FACTORY RESET IN PROGRESS"), false) diff --git a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalUpdateCheck.cpp b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalUpdateCheck.cpp index 394b83745b7a6784a124845a1641692e20a1caeb..d094f0abc4b3d067958d36462eb4c666a533259c 100644 --- a/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalUpdateCheck.cpp +++ b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalUpdateCheck.cpp @@ -9,14 +9,15 @@ bool MenuModalUpdateCheck::TaskExecute(const bool& parameter) { (void)parameter; Upgrade::Instance().DoManualCheck(this); - mSignal.WaitSignal(15000); - return true; + bool result = mSignal.WaitSignal(15000); + Upgrade::Instance().DoManualCheck(nullptr); + return result; } void MenuModalUpdateCheck::TaskComplete(const bool& result) { (void)result; - String message = mUpdateStatus ? (_F(_("A new version {0} is available!")) / mVersion).ToString() : _("No new version available yet."); + String message = result ? (mUpdateStatus ? (_F(_("A new version {0} is available!")) / mVersion).ToString() : _("No new version available yet.")) : _("Cannot check update ! Ensure your Recalbox is connected to the Internet, and try later."); mWindow.displayMessage(message); mSourceMenu.RequestMenuRefresh(); } diff --git a/projects/frontend/es-app/src/main.cpp b/projects/frontend/es-app/src/main.cpp index f1d6dabf38c14cd1ffdb4966fd01312a99a82359..829ef3de2d374329d2f49eb392a1394736da3378 100644 --- a/projects/frontend/es-app/src/main.cpp +++ b/projects/frontend/es-app/src/main.cpp @@ -7,11 +7,16 @@ #include "Options.h" #include +#include int main(int argc, char* argv[], char** env) { // Rename main thread pthread_setname_np(pthread_self(), "Main"); + // file limit up ! + struct rlimit limit { 4096, 4096 }; + if (setrlimit(RLIMIT_NOFILE, &limit) != 0) + { LOG(LogError) << "[Main] Error setting file limit."; } // Get arguments Options options(argc, argv); diff --git a/projects/frontend/es-app/src/systems/SystemManager.cpp b/projects/frontend/es-app/src/systems/SystemManager.cpp index b7b26988cfc5daa701db1dad21d3f4ef8242338c..f7a7d9aeee25d23e918d917a02f848303fe2060d 100644 --- a/projects/frontend/es-app/src/systems/SystemManager.cpp +++ b/projects/frontend/es-app/src/systems/SystemManager.cpp @@ -9,6 +9,7 @@ #include "games/classifications/Versions.h" #include "utils/hash/Crc32.h" #include "games/GameFilesUtils.h" +#include "hardware/Board.h" #include #include #include @@ -1081,7 +1082,7 @@ SystemData* SystemManager::ThreadPoolRunJob(SystemDescriptor& systemDescriptor) } // Creates systems from information located in a config file -bool SystemManager::LoadSystemConfigurations(bool portableSystem) +bool SystemManager::LoadSystemConfigurations(bool portableSystem, bool crt) { // Remove any existing system & save (useful to save boot on game metadata) DeleteAllSystems(true, false); @@ -1098,7 +1099,8 @@ bool SystemManager::LoadSystemConfigurations(bool portableSystem) DescriptorList list; for (int index = 0; index < deserializer.Count(); ++index) if (SystemDescriptor descriptor; deserializer.Deserialize(index, descriptor)) - list.push_back(descriptor); + if (!crt || descriptor.EmulatorTree().CRTAvailable()) + list.push_back(descriptor); return LoadSystems(list, portableSystem, false); } diff --git a/projects/frontend/es-app/src/systems/SystemManager.h b/projects/frontend/es-app/src/systems/SystemManager.h index c567c8b34cbe46a8854adf9c832dc1d1de9a2406..354889b792bad4d1d3b4d3130db8efd916bdeb8d 100644 --- a/projects/frontend/es-app/src/systems/SystemManager.h +++ b/projects/frontend/es-app/src/systems/SystemManager.h @@ -701,10 +701,10 @@ class SystemManager : private INoCopy // No copy allowed /*! * @brief Load the system config file at getConfigPath(). Returns true if no errors were encountered. An example will be written if the file doesn't exist. - * @param ForeReload force reloading from disk * @param portableSystem true if the current board is a portable system and does not need lightgun + * @param crt CRT plateform */ - bool LoadSystemConfigurations(bool portableSystem); + bool LoadSystemConfigurations(bool portableSystem, bool crt); /*! * @brief Load a single system to get mgame metadata in boot on game diff --git a/projects/frontend/es-app/src/views/GameClipView.cpp b/projects/frontend/es-app/src/views/GameClipView.cpp index fd03c345ab9a5b852efdd6204fc674745b57edf6..5bcb937e05ec9e999ad833c43748562e43bb5c3c 100644 --- a/projects/frontend/es-app/src/views/GameClipView.cpp +++ b/projects/frontend/es-app/src/views/GameClipView.cpp @@ -221,6 +221,7 @@ bool GameClipView::ProcessInput(const InputCompactEvent& event) void GameClipView::StartGameClip() { GetGame(); + ThemeManager::ChangeThemeContext("gameclip", mGameClipContainer.Extras(), &mGame->System(), mGame); mGameClipContainer.setGameInfo(mGame); } diff --git a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp index ed38eba3da3d76814f07f271d6cd57c8e11e5699..9d050bf921d6e06746541c6a01eacfa2764ecc83 100644 --- a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp +++ b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp @@ -46,10 +46,10 @@ void ArcadeGameListView::populateList(const FolderData& folder) FileSorts::Sorts sort = FileSorts::Clamp(RecalboxConf::Instance().GetSystemSort(mSystem), set); FileSorts& sorts = FileSorts::Instance(); BuildAndSortArcadeGames(items, sorts.ComparerArcadeFromSort(sort), sorts.IsAscending(sort)); - BuildList(); + BuildList(nullptr); } -void ArcadeGameListView::BuildList() +void ArcadeGameListView::BuildList(FileData* target) { Gamelist().ClearGames(); mHeaderText.setText(mSystem.FullName()); @@ -169,7 +169,7 @@ void ArcadeGameListView::BuildList() // Store if (lastHeader == nullptr || !lastHeader->IsFolded()) - Gamelist().AddGames(parent.mGame, GetDisplayName(parent), type, colorIndexOffset + GameColor); + Gamelist().AddGames(parent.mGame, GetDisplayName(parent, *parent.mGame), type, colorIndexOffset + GameColor); if (parent.mGame->IsGame()) previous = parent.mGame; } @@ -189,7 +189,7 @@ void ArcadeGameListView::BuildList() if (!Regions::IsIn4Regions(clone.mGame->Metadata().Region().Pack, currentRegion)) colorIndexOffset = HighlightColor; // Store - Gamelist().AddGames(clone.mGame, GetDisplayName(clone), GameType::ArcadeClone, colorIndexOffset + GameColor); + Gamelist().AddGames(clone.mGame, GetDisplayName(clone, *clone.mGame), GameType::ArcadeClone, colorIndexOffset + GameColor); } } } @@ -197,6 +197,11 @@ void ArcadeGameListView::BuildList() // Check emptyness if (!Gamelist().HasGame()) Gamelist().AddGames(&mEmptyListItem, _S(mEmptyListItem.Name())); + + if (target != nullptr) + Gamelist().SetSelectedGame(target); + else + Gamelist().SetSelectedGameIndex(0); } @@ -373,10 +378,7 @@ void ArcadeGameListView::FoldAll() parent.mFolded = true; // Rebuild the UI list - BuildList(); - - // Set cursor - Gamelist().SetSelectedGame(item); + BuildList(item); } void ArcadeGameListView::UnfoldAll() @@ -391,10 +393,7 @@ void ArcadeGameListView::UnfoldAll() parent.mFolded = false; // Rebuild the UI list - BuildList(); - - // Set cursor - Gamelist().SetSelectedGame(item); + BuildList(item); } void ArcadeGameListView::Fold(FileData* item) @@ -420,10 +419,7 @@ void ArcadeGameListView::Fold(FileData* item) } // Rebuild the UI list - if (rebuild) BuildList(); - - // Set cursor - Gamelist().SetSelectedGame(item); + if (rebuild) BuildList(item); } void ArcadeGameListView::Unfold(FileData* item) @@ -440,10 +436,7 @@ void ArcadeGameListView::Unfold(FileData* item) } // Rebuild the UI list - if (rebuild) BuildList(); - - // Set cursor - Gamelist().SetSelectedGame(item); + if (rebuild) BuildList(item); } std::vector ArcadeGameListView::GetManufacturerList() const @@ -483,14 +476,16 @@ const ArcadeTupple& ArcadeGameListView::Lookup(const FileData& item) String ArcadeGameListView::GetRawDisplayName(const FileData& game) { - return GetDisplayName(Lookup(game)); + return GetDisplayName(Lookup(game), game); } -String ArcadeGameListView::GetDisplayName(const ArcadeTupple& game) +String ArcadeGameListView::GetDisplayName(const ArcadeTupple& game, const FileData& original) { if (RecalboxConf::Instance().GetArcadeUseDatabaseNames() && game.mArcade != nullptr) return game.mArcade->ArcadeName(); - return RecalboxConf::Instance().GetDisplayByFileName() ? game.mGame->Metadata().RomFileOnly().ToString() : game.mGame->Name(); // TODO: Use gugue new displayable name ASAP + if (game.mGame != nullptr) + return RecalboxConf::Instance().GetDisplayByFileName() ? game.mGame->Metadata().RomFileOnly().ToString() : game.mGame->Name(); + return RecalboxConf::Instance().GetDisplayByFileName() ? original.Metadata().RomFileOnly().ToString() : original.Name(); } String ArcadeGameListView::GetDescription(FileData& game) diff --git a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h index 476b6baa168ef3e293ada94e40731ca85d9c05c4..e71fee92b4b1c16c88b9d05e58b76eb51c170422 100644 --- a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h +++ b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h @@ -114,7 +114,7 @@ class ArcadeGameListView : public DetailedGameListView /*! * @brief Rebuild the gamelist using internal structures */ - void BuildList(); + void BuildList(FileData* target); /*! * @brief Get arcade specific icons @@ -146,7 +146,7 @@ class ArcadeGameListView : public DetailedGameListView * @param game Game * @return Final display name */ - static String GetDisplayName(const ArcadeTupple& game); + static String GetDisplayName(const ArcadeTupple& game, const FileData& original); /*! * @brief Get description of the given game diff --git a/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp b/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp index 05a65a015a3f8f89286d07e61918d59ae86bef32..581c304bdb61b293b727bb39fc5278209db30be2 100755 --- a/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -17,7 +17,7 @@ DetailedGameListView::DetailedGameListView(WindowManager&window, SystemManager& , mCurrentList(&mTextList) , mElapsedTimeOnGame(0) , mIsScraping(false) - , mHeaderStars(window, 0.f) + , mHeaderStars(window, 0.f, "dlvh") , mImage(window, ImgProps::KeepRatio | ImgProps::NoCache) , mNoImage(window, ImgProps::KeepRatio) , mVideo(window, this) @@ -32,7 +32,7 @@ DetailedGameListView::DetailedGameListView(WindowManager&window, SystemManager& , mLblPlayCount(window) , mLblFavorite(window) , mFolderName(window) - , mRating(window, 0.f) + , mRating(window, 0.f, "dlv") , mReleaseDate(window) , mDeveloper(window) , mPublisher(window) diff --git a/projects/frontend/es-core/src/components/GameClipContainer.cpp b/projects/frontend/es-core/src/components/GameClipContainer.cpp index 9e760a74c9f319536c08afac4fc27623fbe3b5eb..fbe9addab6ffd1947187f748c34891656e8a0e71 100644 --- a/projects/frontend/es-core/src/components/GameClipContainer.cpp +++ b/projects/frontend/es-core/src/components/GameClipContainer.cpp @@ -32,7 +32,7 @@ GameClipContainer::GameClipContainer(WindowManager& window) , mLblFavorite(window) , mGameName(window) , mSystemName(window) - , mRating(window, 0.f) + , mRating(window, 0.f, "gc") , mReleaseDate(window) , mDeveloper(window) , mPublisher(window) diff --git a/projects/frontend/es-core/src/components/GameClipContainer.h b/projects/frontend/es-core/src/components/GameClipContainer.h index dbaba5789b7302ad7e1a2e80b4e2a6b8274ec527..c67a1bf9c2ffaaaabccdfb51276e13e2ef1bed40 100644 --- a/projects/frontend/es-core/src/components/GameClipContainer.h +++ b/projects/frontend/es-core/src/components/GameClipContainer.h @@ -99,4 +99,6 @@ class GameClipContainer : public Gui Vector2f getVideoCenter(); bool CollectHelpItems(Help& help) override; + + ThemeExtras::List& Extras() { return mThemeExtras.Extras(); } }; \ No newline at end of file diff --git a/projects/frontend/es-core/src/components/HelpComponent.cpp b/projects/frontend/es-core/src/components/HelpComponent.cpp index 5a5b197608bcd529e6290568caaf3b96859f800a..fe5d9bc52b6fcfb65bc42b02023e2bea3b31da5d 100644 --- a/projects/frontend/es-core/src/components/HelpComponent.cpp +++ b/projects/frontend/es-core/src/components/HelpComponent.cpp @@ -124,7 +124,7 @@ void HelpComponent::Refresh(const Help& newHelpItems, bool force) mScrolling = Scrolling::Initialize; setPosition(Vector3f(mPositionFromTheme.x() * Renderer::Instance().DisplayWidthAsFloat(), mPositionFromTheme.y() * Renderer::Instance().DisplayHeightAsFloat(), 0.0f).round()); - setSize((float)mTotalWidth, (float)height); + setSize(mSizeFromTheme.x() * Renderer::Instance().DisplayWidthAsFloat(), (float)height); } void HelpComponent::Render(const Transform4x4f& parentTrans) @@ -238,14 +238,13 @@ void HelpComponent::OnApplyThemeElement(const ThemeElement& element, ThemeProper { (void)properties; - // Reset size to its calculated size cause the caller already read & set the size property - setSize((float)mTotalWidth, (float)mHeight); - if(element.HasProperty(ThemePropertyName::Pos)) mPositionFromTheme = Vector3f(element.AsVector(ThemePropertyName::Pos)); else mPositionFromTheme = Vector3f(0.012f, 0.9515f, 0.f); if(element.HasProperty(ThemePropertyName::Size)) mSizeFromTheme = Vector2f(element.AsVector(ThemePropertyName::Size)); else mSizeFromTheme = Vector2f(1.f - mPositionFromTheme.x(), 1); + // Reset size to its calculated size cause the caller already read & set the size property + setSize(mSizeFromTheme.x() * Renderer::Instance().DisplayWidthAsFloat(), (float)mHeight); mTextColor = (element.HasProperty(ThemePropertyName::TextColor)) ? (unsigned int)element.AsInt(ThemePropertyName::TextColor) : 0x777777FF; mIconColor = (element.HasProperty(ThemePropertyName::IconColor)) ? (unsigned int)element.AsInt(ThemePropertyName::IconColor) : 0x777777FF; diff --git a/projects/frontend/es-core/src/components/IList.h b/projects/frontend/es-core/src/components/IList.h index 79db0d99dc3a196413b7d3632200933051bbc4f8..1fdd3fac29ae64b764ddba97aba7d6ce5e40911e 100644 --- a/projects/frontend/es-core/src/components/IList.h +++ b/projects/frontend/es-core/src/components/IList.h @@ -375,6 +375,8 @@ class IList : public Gui else mTitleOverlayOpacity = (unsigned char) op; + if (mFastScrollingEnable) mTitleOverlayOpacity = 0; + if (mScrollVelocity == 0 || size() < 2) return; @@ -391,7 +393,7 @@ class IList : public Gui } // are we ready to go even FASTER? - if (mFastScrollingEnable) + if (!mFastScrollingEnable || mTierList.count < 3) while (mScrollTier < mTierList.count - 1 && mScrollTierAccumulator >= mTierList.tiers[mScrollTier].length) { mScrollTierAccumulator -= mTierList.tiers[mScrollTier].length; diff --git a/projects/frontend/es-core/src/components/TextComponent.cpp b/projects/frontend/es-core/src/components/TextComponent.cpp index e2c6a2b4ebc41bc7d3ad91aee9b7d3a7d767e140..2dcbf668c57535369b938c17131029ff3c7b746c 100755 --- a/projects/frontend/es-core/src/components/TextComponent.cpp +++ b/projects/frontend/es-core/src/components/TextComponent.cpp @@ -10,7 +10,7 @@ TextComponent::TextComponent(WindowManager&window) , mColor(0x000000FF) , mOriginColor(0x000000FF) , mBgColor(0) - , mLineSpacing(1.5f) + , mLineSpacing(1.2f) , mColorOpacity(0xFF) , mBgColorOpacity(0) , mAlignment(::Alignment::CenterLeft) @@ -143,7 +143,7 @@ void TextComponent::Render(const Transform4x4f& parentTrans) Renderer::DrawRectangle(area, mBgColor); mChunks.Effect = mFontStyle; - mFont->DrawText(mChunks, area, 0.f, 0.f, mColor, mAlignment); + mFont->DrawText(mChunks, area, 0.f, 0.f, mColor, mAlignment, mLineSpacing - 1.0f); } void TextComponent::calculateExtent() @@ -153,7 +153,7 @@ void TextComponent::calculateExtent() else if(mAutoCalcExtentY) { if (mIsMultiline) - mSize.y() = (float)mFont->SplitText(mUppercase ? mText.ToUpperCaseUTF8() : mText, (int) mSize.x()).Chunks.Count() * (float)mFont->Height(); + mSize.y() = (float)(mFont->SplitText(mUppercase ? mText.ToUpperCaseUTF8() : mText, (int) mSize.x()).Chunks.Count() - 1) * (float)mFont->Height() * mLineSpacing + (float)mFont->Height(); else mSize.y() = (float)mFont->Height(); } @@ -213,7 +213,7 @@ void TextComponent::OnApplyThemeElement(const ThemeElement& element, ThemeProper { if (element.HasProperty(ThemePropertyName::FontPath)) setFont(&FontManager::Instance().FromTheme(element, properties, mFont)); if (element.HasProperty(ThemePropertyName::FontStyle)) mFontStyle.Deserialize(element.AsString(ThemePropertyName::FontStyle)); - setLineSpacing(element.HasProperty(ThemePropertyName::LineSpacing) ? element.AsFloat(ThemePropertyName::LineSpacing) : mLineSpacing); + setLineSpacing(element.HasProperty(ThemePropertyName::LineSpacing) ? element.AsFloat(ThemePropertyName::LineSpacing) * 0.8f : mLineSpacing); } onTextChanged(); diff --git a/projects/frontend/es-core/src/components/TextScrollComponent.cpp b/projects/frontend/es-core/src/components/TextScrollComponent.cpp index b95808b340caf16698a8d4c0c3b4abdf2f1e388c..9d9c4ed1e8bc844ba6d2ac8641912d84d860d247 100644 --- a/projects/frontend/es-core/src/components/TextScrollComponent.cpp +++ b/projects/frontend/es-core/src/components/TextScrollComponent.cpp @@ -228,6 +228,8 @@ void TextScrollComponent::onTextChanged() if (mUppercase) mDisplayableText.UpperCaseUTF8(); mTextWidth = mFont->TextWidth(mDisplayableText); mTextHeight = mFont->Height(); + mStep = ScrollSteps::LeftPause; + mMarqueeTime = 0; onSizeChanged(); } diff --git a/projects/frontend/es-core/src/guis/GuiInfoPopup.cpp b/projects/frontend/es-core/src/guis/GuiInfoPopup.cpp index 2be10bd963e57b48ed0ab00af0daacfae06a2912..85c859908ccce5b70e8bed4b96f9fdd7ac9d45fc 100644 --- a/projects/frontend/es-core/src/guis/GuiInfoPopup.cpp +++ b/projects/frontend/es-core/src/guis/GuiInfoPopup.cpp @@ -47,17 +47,17 @@ float GuiInfoPopup::AddComponents(WindowManager& window, ComponentGrid& grid, fl if(Renderer::Instance().Is480pOrLower()) { - fontIconSize = menuTheme.Text().font->RequestedHeight(); - fontTextSize = menuTheme.Text().font->RequestedHeight(); + fontIconSize = (float)menuTheme.Text().font->RequestedHeight() * 4; + fontTextSize = (float)menuTheme.Text().font->RequestedHeight(); fontPixelSize = true; } mMsgText = std::make_shared(window, mMessage, &FontManager::Instance().FromFont(*menuTheme.Text().font, fontTextSize, fontPixelSize), menuTheme.Text().color, ::Alignment::CenterLeft); mMsgText->setMultiline(true); - mMsgIcon = std::make_shared(window, iconText, &FontManager::Instance().FromDefault(fontIconSize, fontPixelSize), menuTheme.Text().color, ::Alignment::CenterLeft); + mMsgIcon = std::make_shared(window, iconText, &FontManager::Instance().FromDefault(fontIconSize, fontPixelSize), menuTheme.Text().color, ::Alignment::Center); - grid.setEntry(mMsgText, Vector2i(1, 0), false, false); - grid.setEntry(mMsgIcon, Vector2i(0, 0), false, false); + grid.setEntry(mMsgText, Vector2i(1, 0), false, true); + grid.setEntry(mMsgIcon, Vector2i(0, 0), false, true); float msgHeight = 0.0f; if (mIcon == PopupType::None) diff --git a/projects/frontend/es-core/src/guis/GuiInfoPopupBase.cpp b/projects/frontend/es-core/src/guis/GuiInfoPopupBase.cpp index 9c74b6c4cce31fa519789cd765bbfcd63cd4cb35..fad655cd54d7314ca386ba80c098fa407e9ec42d 100644 --- a/projects/frontend/es-core/src/guis/GuiInfoPopupBase.cpp +++ b/projects/frontend/es-core/src/guis/GuiInfoPopupBase.cpp @@ -58,7 +58,7 @@ void GuiInfoPopupBase::Initialize() mFrame.setImagePath(menuTheme.Background().path); mFrame.setCenterColor(mFrameColor); mFrame.setEdgeColor(mFrameColor); - mFrame.fitTo(mGrid.getSize(), Vector3f::Zero(), Vector2f(-32, -32)); + mFrame.fitTo(mGrid.getSize(), Vector3f::Zero(), Renderer::Instance().Is480pOrLower() ? Vector2f(-24, -24) :Vector2f(-32, -32)); addChild(&mFrame); addChild(&mGrid); diff --git a/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.cpp b/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.cpp index f3607d86a7c3b6a6e59d153d5e676ca3154dee3a..3b7a78c4ad50b30e05a738fee392b1772c27b6dc 100644 --- a/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.cpp +++ b/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.cpp @@ -7,6 +7,7 @@ #include "GuiInfoPopupSeamlessScraper.h" #include "scraping/ScrapeResult.h" +#include "hardware/Board.h" #include #include #include @@ -16,6 +17,7 @@ #include String GuiInfoPopupSeamlessScraper::mTextTemplate("Scraping game... %COUNT% yet to go.\n%SYSTEM%\n%NAME%"); +String GuiInfoPopupSeamlessScraper::mTextTemplateCRT("Scraping game... %COUNT% yet to go."); GuiInfoPopupSeamlessScraper::GuiInfoPopupSeamlessScraper(WindowManager& window) : StaticLifeCycleControler("SeamlessUI") @@ -42,20 +44,26 @@ float GuiInfoPopupSeamlessScraper::AddComponents(WindowManager& window, Componen fontPixelSize = true; } - mText = std::make_shared(window, String("Starting background scraper...").Append(String::CRLF).Append(String::CRLF), &FontManager::Instance().FromDefault(fontSizeText, fontPixelSize), menuTheme.Text().color, ::Alignment::TopCenter); + String msg(_("Starting background scraper...")); + bool crt = Board::Instance().CrtBoard().IsCrtAdapterAttached(); + if (!crt) msg.Append(String::CRLF).Append(String::CRLF); + mText = std::make_shared(window, msg, &FontManager::Instance().FromDefault(fontSizeText, fontPixelSize), menuTheme.Text().color, ::Alignment::TopCenter); mIcon = std::make_shared(window, iconText, &FontManager::Instance().FromDefault(fontSizeIcon, fontPixelSize), menuTheme.Text().color, ::Alignment::CenterLeft); mImage = std::make_shared(window); mNoImage = std::make_shared(window); grid.setEntry(mIcon , Vector2i(0, 0), false, true); grid.setEntry(mText , Vector2i(1, 0), false, true); - grid.setEntry(mImage , Vector2i(2, 0), false, true); - grid.setEntry(mNoImage , Vector2i(2, 0), false, true); + if (!crt) + { + grid.setEntry(mImage, Vector2i(2, 0), false, true); + grid.setEntry(mNoImage, Vector2i(2, 0), false, true); + } mText->setSize(maxWidth - mIcon->getSize().y(), 0); float msgHeight = Math::min(maxHeight, Math::max(mText->getSize().y(), mIcon->getSize().y())); grid.setColWidthPerc(0, (float)(mIcon->getFont()->Height() + paddingX) / maxWidth); - grid.setColWidthPerc(2, (msgHeight * 1.5f) / maxWidth); + grid.setColWidthPerc(2, crt ? (msgHeight * 1.5f) / maxWidth : 0.02f); mImage->setResize(0.f, msgHeight * 0.9f); mNoImage->setResize(0.f, msgHeight * 0.9f); mNoImage->setImage(Path(":/no_image.png"), false); @@ -67,7 +75,7 @@ void GuiInfoPopupSeamlessScraper::SetScrapeResult(const FileData& game) { if (Initialized()) { - String msg(mTextTemplate); + String msg(Board::Instance().CrtBoard().IsCrtAdapterAttached() ? _S(mTextTemplateCRT) : _S(mTextTemplate)); msg.Replace("%COUNT%", String(mCount)) .Replace("%NAME%", game.Metadata().Name()) .Replace("%SYSTEM%", game.System().FullName()); diff --git a/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.h b/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.h index 590a61590cfbd5e5427679741d050c6dcfb2eb27..9787988e4a3975ccd29e73e28a1fcbd10fdbed10 100644 --- a/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.h +++ b/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.h @@ -38,6 +38,8 @@ class GuiInfoPopupSeamlessScraper : public StaticLifeCycleControler mText; diff --git a/projects/frontend/es-core/src/input/InputDevice.cpp b/projects/frontend/es-core/src/input/InputDevice.cpp index 7c667201b980c6825fda1bade16f7e9b20657e50..6afd307a4d3492b14785c77454280c3c6bc22df6 100644 --- a/projects/frontend/es-core/src/input/InputDevice.cpp +++ b/projects/frontend/es-core/src/input/InputDevice.cpp @@ -548,72 +548,82 @@ int InputDevice::ConvertKeyToOnOff(int key, bool pressed, InputCompactEvent::Ent int InputDevice::ConvertHatToOnOff(int hat, int bits, InputCompactEvent::Entry& on, InputCompactEvent::Entry& off) { int elapsed = 0; - int previousBits = mPreviousHatsValues[hat]; - for (int i = (int)Entry::__Count; --i >= 0; ) - if ((mConfigurationBits & (1 << i)) != 0) - if (const InputEvent& config = mInputEvents[i]; config.Type() == InputEvent::EventType::Hat && config.Id() == hat) - { - // Hat bits are matching, set the target direction - if ((bits & config.Value()) == config.Value()) - StartEntry(ConvertEntry((Entry)i), on); - // Otherwise, if previous value matched, - else if ((previousBits & config.Value()) == config.Value()) // Previous value match bits? - elapsed = StopEntry(ConvertEntry((Entry)i), off); - } + if (hat < sMaxHats) + { + int previousBits = mPreviousHatsValues[hat]; + for (int i = (int)Entry::__Count; --i >= 0; ) + if ((mConfigurationBits & (1 << i)) != 0) + if (const InputEvent& config = mInputEvents[i]; config.Type() == InputEvent::EventType::Hat && config.Id() == hat) + { + // Hat bits are matching, set the target direction + if ((bits & config.Value()) == config.Value()) + StartEntry(ConvertEntry((Entry)i), on); + // Otherwise, if previous value matched, + else if ((previousBits & config.Value()) == config.Value()) // Previous value match bits? + elapsed = StopEntry(ConvertEntry((Entry)i), off); + } + + // Record previous value + mPreviousHatsValues[hat] = bits; + } + else { LOG(LogDebug) << "[InputDevice] Hat out of range !"; } - // Record previous value - mPreviousHatsValues[hat] = bits; return elapsed; } int InputDevice::ConvertAxisToOnOff(int axis, int value, InputCompactEvent::Entry& on, InputCompactEvent::Entry& off) { int elapsed = 0; - for (int i = (int)Entry::__Count; --i >= 0; ) - if ((mConfigurationBits & (1 << i)) != 0) - if (const InputEvent& config = mInputEvents[i]; config.Type() == InputEvent::EventType::Axis && config.Id() == axis) - { - // For axis, we must distinguish target axis from other binary targets. - // Axis to axis are converted to negative/center/positive. - // Axis to binaries are converted to on only for the right sign. off otherwise - // That is, axis on triggers will be properly converted to on/off buttons. - if (InputCompactEvent::Entry targetEntry = ConvertEntry((Entry)i); targetEntry >= InputCompactEvent::Entry::J1Left) + if (axis < sMaxAxis) + { + for (int i = (int)Entry::__Count; --i >= 0; ) + if ((mConfigurationBits & (1 << i)) != 0) + if (const InputEvent& config = mInputEvents[i]; config.Type() == InputEvent::EventType::Axis && config.Id() == axis) { - InputCompactEvent::Entry targetOpposite = (InputCompactEvent::Entry)((int)targetEntry << 1); - // Since configured event are negatives, if we got a positive value in current config, that means - // the joystick is inverted. - // Note: values are already normalized to -1/0/+1 - value = (config.Value() > 0) ? -value : value; - int previousValue = mPreviousAxisValues[axis]; - if (previousValue != value) - switch(value) + // For axis, we must distinguish target axis from other binary targets. + // Axis to axis are converted to negative/center/positive. + // Axis to binaries are converted to on only for the right sign. off otherwise + // That is, axis on triggers will be properly converted to on/off buttons. + if (InputCompactEvent::Entry targetEntry = ConvertEntry((Entry)i); targetEntry >= InputCompactEvent::Entry::J1Left) { - case -1: // configured axis direction? - { - StartEntry(targetEntry, on); - if (previousValue > 0) elapsed = StopEntry(targetOpposite, off); // In case joystick has been quickly moved from one direction to another - break; - } - case 1: // Opposite of configured axis direction? + InputCompactEvent::Entry targetOpposite = (InputCompactEvent::Entry)((int)targetEntry << 1); + // Since configured event are negatives, if we got a positive value in current config, that means + // the joystick is inverted. + // Note: values are already normalized to -1/0/+1 + value = (config.Value() > 0) ? -value : value; + int previousValue = mPreviousAxisValues[axis]; + if (previousValue != value) + switch(value) { - StartEntry(targetOpposite, on); - if (previousValue < 0) elapsed = StopEntry(targetEntry, off); // In case joystick has been quickly moved from one direction to another - break; - } - default: // Axis centered again - { - elapsed = StopEntry(previousValue < 0 ? targetEntry : targetOpposite, off); - break; + case -1: // configured axis direction? + { + StartEntry(targetEntry, on); + if (previousValue > 0) elapsed = StopEntry(targetOpposite, off); // In case joystick has been quickly moved from one direction to another + break; + } + case 1: // Opposite of configured axis direction? + { + StartEntry(targetOpposite, on); + if (previousValue < 0) elapsed = StopEntry(targetEntry, off); // In case joystick has been quickly moved from one direction to another + break; + } + default: // Axis centered again + { + elapsed = StopEntry(previousValue < 0 ? targetEntry : targetOpposite, off); + break; + } } } + // Axis has a binary on/off + else if (value == config.Value() || mPreviousAxisValues[axis] == config.Value()) + elapsed = SetEntry(targetEntry, value == config.Value(), on, off); } - // Axis has a binary on/off - else if (value == config.Value() || mPreviousAxisValues[axis] == config.Value()) - elapsed = SetEntry(targetEntry, value == config.Value(), on, off); - } - // Record previous value - mPreviousAxisValues[axis] = value; + // Record previous value + mPreviousAxisValues[axis] = value; + } + else { LOG(LogDebug) << "[InputDevice] Axis out of range !"; } + return elapsed; } @@ -761,12 +771,14 @@ bool InputDevice::CheckNeutralPosition(bool skipAxis) const // Check axis if (skipAxis) return true; for(int i = mDeviceNbAxes; --i >= 0; ) - { - int axis = SDL_JoystickGetAxis(mDeviceSDL, i); - axis = axis < -sJoystickDeadZone ? -1 : (axis > sJoystickDeadZone ? 1 : 0); - if (axis != mNeutralAxisValues[i]) - return false; - } + if (i < sMaxAxis) + { + int axis = SDL_JoystickGetAxis(mDeviceSDL, i); + axis = axis < -sJoystickDeadZone ? -1 : (axis > sJoystickDeadZone ? 1 : 0); + if (axis != mNeutralAxisValues[i]) + return false; + } + else { LOG(LogDebug) << "[InputDevice] Axis out of range !"; } return true; } @@ -828,11 +840,13 @@ void InputDevice::RecordAxisNeutralPosition() { // Fill neutral values for (int i = mDeviceNbAxes; --i >= 0;) - { - Sint16 state = 0; - if (SDL_JoystickGetAxisInitialState(mDeviceSDL, i, &state) == SDL_TRUE) - mNeutralAxisValues[i] = state < -sJoystickDeadZone ? -1 : (state > sJoystickDeadZone ? 1 : 0); - } + if (i < sMaxAxis) + { + Sint16 state = 0; + if (SDL_JoystickGetAxisInitialState(mDeviceSDL, i, &state) == SDL_TRUE) + mNeutralAxisValues[i] = state < -sJoystickDeadZone ? -1 : (state > sJoystickDeadZone ? 1 : 0); + } + else { LOG(LogDebug) << "[InputDevice] Axis out of range !"; } } int InputDevice::CaptureCurrentButtonState() diff --git a/projects/frontend/es-core/src/input/InputManager.cpp b/projects/frontend/es-core/src/input/InputManager.cpp index f31034640865964b34dd76a98df51cad949ffbaf..e10f1cbe18c22995bcc766b0c0814da1df827baf 100644 --- a/projects/frontend/es-core/src/input/InputManager.cpp +++ b/projects/frontend/es-core/src/input/InputManager.cpp @@ -448,16 +448,18 @@ bool InputManager::LookupDeviceXmlConfiguration(InputDevice& device) { // check the guid bool guid; - if (strlen(item.attribute("deviceName").value()) == 0) { + if (strlen(item.attribute("deviceName").value()) == 0 || device.IsKeyboard()) + { // sdl 2.26+ - guid = (strcmp(device.GUID().c_str(), item.attribute("deviceGUID").value()) == 0) || device.IsKeyboard(); + String tempGUID(item.attribute("deviceGUID").as_string()); + guid = device.GUID().LowerCase() == tempGUID.LowerCase(); }else { // sdl 2.0 String CRC = String::ToHexa(gen_crc16((uint8_t*)item.attribute("deviceName").value(), strlen(item.attribute("deviceName").value()), true), 4, String::Hexa::None); String tempGUID = ""; if (strlen(item.attribute("deviceGUID").value()) > 8) tempGUID = String(item.attribute("deviceGUID").value()).replace(4, 4, CRC); - guid = (device.GUID().ToLowerCase() == tempGUID.ToLowerCase()) || device.IsKeyboard(); + guid = (device.GUID().ToLowerCase() == tempGUID.ToLowerCase()); } //bool name = strcmp(device.Name().c_str(), item.attribute("deviceName").value()) == 0; bool axes = (device.AxeCount() == item.attribute("deviceNbAxes").as_int()) || device.IsKeyboard(); diff --git a/projects/frontend/es-core/src/osd/ClockOsd.cpp b/projects/frontend/es-core/src/osd/ClockOsd.cpp index b44dece7fb974a1fe109a9c0df1b97d09cea828e..40098da5938fb84a66ffbd0e7ca379fd5f1b6fb6 100644 --- a/projects/frontend/es-core/src/osd/ClockOsd.cpp +++ b/projects/frontend/es-core/src/osd/ClockOsd.cpp @@ -8,7 +8,9 @@ ClockOSD::ClockOSD(WindowManager& window, Side side) : BaseOSD(window, side, false) - , mClockFont(&FontManager::Instance().FromFile(Path(FONT_PATH_REGULAR), (FONT_SIZE_SMALL), false)) + , mClockFont(Renderer::Instance().Is480pOrLower() + ? &FontManager::Instance().FromFile(Path(FONT_PATH_CRT), 7, true) + : &FontManager::Instance().FromFile(Path(FONT_PATH_REGULAR), (FONT_SIZE_SMALL), false)) { Vector2f size = mClockFont->TextSize(String(' ').Append(Clock()).Append(' ')); mClockArea = Rectangle(0, 0, (int)size.x(), (int)size.y()); diff --git a/projects/frontend/es-core/src/osd/FpsOSD.cpp b/projects/frontend/es-core/src/osd/FpsOSD.cpp index dc8f9bfad5c9e905a5b7926c6b775916a9726e5a..21d1727ed24a5ee41313b47895d380856e81b286 100644 --- a/projects/frontend/es-core/src/osd/FpsOSD.cpp +++ b/projects/frontend/es-core/src/osd/FpsOSD.cpp @@ -10,7 +10,9 @@ FpsOSD::FpsOSD(WindowManager& window, const Options& options, Side side) : BaseOSD(window, side, false) , mOptions(options) - , mFPSFont(&FontManager::Instance().FromFile(Path(FONT_PATH_REGULAR), FONT_SIZE_SMALL, false)) + , mFPSFont(Renderer::Instance().Is480pOrLower() + ? &FontManager::Instance().FromFile(Path(FONT_PATH_CRT), 7, true) + : &FontManager::Instance().FromFile(Path(FONT_PATH_REGULAR), (FONT_SIZE_SMALL), false)) , mFrameStart(0) , mFrameTimingComputations(0) , mFrameTimingTotal(0) diff --git a/projects/frontend/es-core/src/rendering/fonts/Font.cpp b/projects/frontend/es-core/src/rendering/fonts/Font.cpp index 97ff967b635bbda7f7bdc7ad77ffc1c1c19ec99e..9782550f1a96dd96026224c4ec5086c3aea95924 100644 --- a/projects/frontend/es-core/src/rendering/fonts/Font.cpp +++ b/projects/frontend/es-core/src/rendering/fonts/Font.cpp @@ -22,7 +22,7 @@ NewFont::NewFont(IFontFaceProvider& faceProvider, float heightInPixel) , mRowHeight(0) , mDefault(true) { - assert(heightInPixel > 0 && "Font heint <= 0 !"); + assert(heightInPixel > 0 && "Font height <= 0 !"); // Get font properties FT_Face face = FaceForChar(0); if (face != nullptr) @@ -587,20 +587,21 @@ void NewFont::DrawText(const String& string, int start, int length, const Rectan Renderer::Instance().Unclip(); } -void NewFont::DrawText(NewFont::TextChunkList& strings, int x, int y, Colors::ColorARGB color, ::HorizontalAlignment alignment) +void NewFont::DrawText(NewFont::TextChunkList& strings, int x, int y, Colors::ColorARGB color, ::HorizontalAlignment alignment, float interline) { - y += strings.Chunks.Count() * (mBottomToBaseline + mTopToBaseline); + int realHeight = Math::roundi((float)mRealMaxHeightInPixel * (1.f + interline)); + y += strings.Chunks.Count() * realHeight; for(int i = strings.Chunks.Count(); --i >= 0; ) { const TextChunk& chunk = strings.Chunks[i]; DrawTextRaw(strings.RefString, chunk.Start, chunk.Length, alignment == HorizontalAlignment::Left ? x : x - (alignment == HorizontalAlignment::Center ? chunk.Width / 2 : chunk.Width), - y -= (mBottomToBaseline + mTopToBaseline), color, strings.Effect); + y -= realHeight, color, strings.Effect); } } void NewFont::DrawText(NewFont::TextChunkList& strings, const Rectangle& into, float xoffset, float yoffset, - Colors::ColorARGB color, ::Alignment alignment) + Colors::ColorARGB color, ::Alignment alignment, float interline) { // Position float x = into.Left(); @@ -612,7 +613,7 @@ void NewFont::DrawText(NewFont::TextChunkList& strings, const Rectangle& into, f case HorizontalAlignment::Right: x += into.Width(); break; } float y = into.Top(); - float totalHeight = (float)(strings.Chunks.Count() * mRealMaxHeightInPixel); + float totalHeight = ((float)((strings.Chunks.Count() - 1) * mRealMaxHeightInPixel) * (1.f + interline)) + (float)mRealMaxHeightInPixel; switch(AlignmentExtractVertical(alignment)) { case VerticalAlignment::Top: break; @@ -627,7 +628,7 @@ void NewFont::DrawText(NewFont::TextChunkList& strings, const Rectangle& into, f // Get real x/y float xx = ((hz == HorizontalAlignment::Left ? x : x - (float)(hz == HorizontalAlignment::Center ? chunk.Width / 2 : chunk.Width)) + xoffset); float yy = y + yoffset; - y += (float)mRealMaxHeightInPixel; + y += (float)mRealMaxHeightInPixel * (1.f + interline); // Y clipping if (yy + (float)mBottomToBaseline < into.Top()) continue; // Not yet into visible area if (yy - (float)mTopToBaseline > into.Bottom()) break; // Reverse draw => out of area @@ -640,10 +641,15 @@ void NewFont::DrawText(NewFont::TextChunkList& strings, const Rectangle& into, f void NewFont::ClearCaches() { // Clear default texture - mCurrentTexture = Texture(); - + mCurrentTexture.Clear(); // Clear store & free all glyph texture mGlyphStore.CleanUp(); + // Reset current texture pointers + mTextureWidth = 0; + mTextureHeight = 0; + mCursorX = 0; + mCursorY = 0; + mRowHeight = 0; } #ifdef DEBUG diff --git a/projects/frontend/es-core/src/rendering/fonts/Font.h b/projects/frontend/es-core/src/rendering/fonts/Font.h index 91829ec68646a0461e42437d1def8c776522630b..e866ad910afa8eeb2650bdfacf48f406eb07baf6 100644 --- a/projects/frontend/es-core/src/rendering/fonts/Font.h +++ b/projects/frontend/es-core/src/rendering/fonts/Font.h @@ -277,8 +277,9 @@ class NewFont : private INoCopy * @param x X coordinate to draw text to * @param y Y coordinate to draw text to * @param alignment Horizontal alignment applied on X coordinate + * @param interline Interline in ratio of font height */ - void DrawText(TextChunkList& strings, int x, int y, Colors::ColorARGB color, ::HorizontalAlignment alignment); + void DrawText(TextChunkList& strings, int x, int y, Colors::ColorARGB color, ::HorizontalAlignment alignment, float interline = 0); /*! * @brief Draw a single line text in the given rectangle @@ -341,8 +342,9 @@ class NewFont : private INoCopy * @param yoffset Y Offset applied to the text inside the rectangle * @param color Text color * @param alignment Text alignment inside the rectangle + * @param interline Interline in ratio of font height */ - void DrawText(TextChunkList& strings, const Rectangle& into, float xoffset, float yoffset, Colors::ColorARGB color, ::Alignment alignment); + void DrawText(TextChunkList& strings, const Rectangle& into, float xoffset, float yoffset, Colors::ColorARGB color, ::Alignment alignment, float interline = 0); //! Get requested height [[nodiscard]] int RequestedHeight() const { return (int)mRequestedHeightInPixel; } diff --git a/projects/frontend/es-core/src/rendering/fonts/GlyphStorage.h b/projects/frontend/es-core/src/rendering/fonts/GlyphStorage.h index 7c5f76fd9410664be21bde72edc690c89d575fb8..b721181dd6428dd25198c74c49638d3b1d0a6608 100644 --- a/projects/frontend/es-core/src/rendering/fonts/GlyphStorage.h +++ b/projects/frontend/es-core/src/rendering/fonts/GlyphStorage.h @@ -39,6 +39,7 @@ class GlyphStorage delete[] mGlyphStore[i]; // Clear array list mGlyphStore.Clear(); + mIndex = 0; } //! Allocate a new empty Glyph diff --git a/projects/frontend/es-core/src/rendering/images/IImage.cpp b/projects/frontend/es-core/src/rendering/images/IImage.cpp index bec7054b337a444c3b66a36169729dcb4d119f49..a2a0c43858d1ba7d8084900d9d212a52bac99223 100644 --- a/projects/frontend/es-core/src/rendering/images/IImage.cpp +++ b/projects/frontend/es-core/src/rendering/images/IImage.cpp @@ -23,7 +23,7 @@ bool IImage::LoadResource(ByteArray& to) const Res2hEntry& embeddedEntry = **found; { // Lock Mutex::AutoLock lock(mLocker); - to.ExpandTo((int) embeddedEntry.size + 1); + to.ExpandTo((int)embeddedEntry.size + 1); memcpy(to.BufferReadWrite(), embeddedEntry.data, embeddedEntry.size); to((int)embeddedEntry.size) = 0; } @@ -56,3 +56,14 @@ bool IImage::LoadResource(ByteArray& to) } return false; } + +Path IImage::Trim(const Path& path) +{ + String spath = path.ToString(); + if (int pos = spath.Find('|'); pos > 0) + { + spath.Delete(0, pos + 1); + return Path(spath); + } + return path; +} diff --git a/projects/frontend/es-core/src/rendering/images/IImage.h b/projects/frontend/es-core/src/rendering/images/IImage.h index f47b7238d4b4e95c2d7344421bc73c75a3edf143..d0d7c051b485155e7a5f387bb34ec7616a026e98 100644 --- a/projects/frontend/es-core/src/rendering/images/IImage.h +++ b/projects/frontend/es-core/src/rendering/images/IImage.h @@ -19,7 +19,7 @@ class IImage * @param imagePath Image file path */ explicit IImage(const Path& imagePath, IHttpConfiguration& httpConfigure) - : mPath(imagePath) + : mPath(Trim(imagePath)) , mHttpConfiguration(httpConfigure) { } @@ -69,5 +69,13 @@ class IImage * @return True if the resource has been properly loaded, false otherwise */ bool LoadResource(ByteArray& to); + + private: + /*! + * @brief Trip extra argument in the path, starting with '?' (illegal character in path) + * @param path Source path + * @return Trimmed path + */ + static Path Trim(const Path& path); }; diff --git a/projects/frontend/es-core/src/rendering/textures/Texture.cpp b/projects/frontend/es-core/src/rendering/textures/Texture.cpp index 5f53903aa889e8d0f80faf5184f0047fc4aa720b..eac1a46482c067f5feb6cf04357298b6b86568b8 100644 --- a/projects/frontend/es-core/src/rendering/textures/Texture.cpp +++ b/projects/frontend/es-core/src/rendering/textures/Texture.cpp @@ -125,9 +125,9 @@ int Texture::Height() const return 0; } -void Texture::SetTargetSize(int width, int height) +void Texture::SetTargetSize(int width, int height, bool force) { - if (mTexture != nullptr) mTexture->SetTargetSize(width, height); + if (mTexture != nullptr) mTexture->SetTargetSize(width, height, force); else { LOG(LogError) << "Set target size of null texture!"; diff --git a/projects/frontend/es-core/src/rendering/textures/Texture.h b/projects/frontend/es-core/src/rendering/textures/Texture.h index f26a5b6ef133e5776e96b90874ad6676cf34b5c1..e829e07417a70443448969bd10b6d1e522b73063 100644 --- a/projects/frontend/es-core/src/rendering/textures/Texture.h +++ b/projects/frontend/es-core/src/rendering/textures/Texture.h @@ -196,9 +196,10 @@ class Texture * @brief Set target size (SVG only, FreeTexture only) * @param width Required width * @param height Required height + * @param force Bypass lowest size control and force a new rasterization for SVG images * @note If the texture is already loaded, it is unloaded */ - void SetTargetSize(int width, int height); + void SetTargetSize(int width, int height, bool force = false); /*! * @brief Set file path diff --git a/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp b/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp index a2a8ad24e857e37bd79174ba08897ed19e9b7327..6a63d986057fad9e2cafb0ea215d6b66dee6a497 100755 --- a/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp +++ b/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp @@ -304,7 +304,7 @@ Vector2i TextureHolder::Size() return { mWidth, mHeight }; } -void TextureHolder::SetTargetSize(int width, int height) +void TextureHolder::SetTargetSize(int width, int height, bool force) { // Ignore same size if ((mTargetWidth == width) && (mTargetHeight == height)) return; @@ -312,10 +312,10 @@ void TextureHolder::SetTargetSize(int width, int height) if (mIsSVG) { // SVG may be rasterized again only in a higher size - if (width < mTargetWidth || height < mTargetHeight) + if ((width < mTargetWidth || height < mTargetHeight) && !force) { LOG(LogWarning) << "[TextureHolder] Texture " << (FilePath().IsEmpty() ? "Unknown" : FilePath().ToChars()) << " request a resize from (" << mTargetWidth << ", " << mTargetHeight << ") to (" << width << ", " << height << "). Request ignored"; - //return; + return; } if (mStatus == Status::Loaded) diff --git a/projects/frontend/es-core/src/rendering/textures/TextureHolder.h b/projects/frontend/es-core/src/rendering/textures/TextureHolder.h index c20bbc2fb4b2b8fb0e3d2a442ec832c728a12aad..1062928cb30dca12a1bb668098840999600f7f11 100755 --- a/projects/frontend/es-core/src/rendering/textures/TextureHolder.h +++ b/projects/frontend/es-core/src/rendering/textures/TextureHolder.h @@ -193,9 +193,10 @@ class TextureHolder : private INoCopy, public IUnloadable * @brief Set target size (SVG only, FreeTexture only) * @param width Required width * @param height Required height + * @param force Bypass lowest size control and force a new rasterization for SVG images * @note If the texture is already loaded, it is unloaded */ - void SetTargetSize(int width, int height); + void SetTargetSize(int width, int height, bool force); /*! * @brief Set file path diff --git a/projects/frontend/es-core/src/themes/ThemeData.cpp b/projects/frontend/es-core/src/themes/ThemeData.cpp index 4e7a635b3b56df9413e928da31d7f882fdf93bb6..e18e93455fef60c4e1d028439bf7600700d86a36 100755 --- a/projects/frontend/es-core/src/themes/ThemeData.cpp +++ b/projects/frontend/es-core/src/themes/ThemeData.cpp @@ -116,14 +116,6 @@ void ThemeData::loadFile(const String& systemThemeFolder, const Path& path) if(!root) { LOGT(LogError) << "[Theme] " << FileList() << "Missing tag!"; } mCompatiblity = ExtractCompatibility(root); - // parse version - mVersion = root.child("formatVersion").text().as_float(-404); - if(mVersion == -404) - { LOGT(LogError) << "[Theme] " << FileList() << " tag missing!\n It's either out of date or you need to add " + String(CURRENT_THEME_FORMAT_VERSION) + " inside your tag."; } - - if(mVersion < MINIMUM_THEME_FORMAT_VERSION) - { LOGT(LogError) << "[Theme] " << FileList() << "Theme uses format version " + String(mVersion, 2) + ". Minimum supported version is " + String(MINIMUM_THEME_FORMAT_VERSION) + '.'; } - Clear(path); // Extract options @@ -253,12 +245,12 @@ bool ThemeData::parseSubset(const pugi::xml_node& node) for (pugi::xml_attribute& attribute : node.attributes()) { String name = attribute.name(); - int locale = ExtractLocalizedCode(name); + //int locale = ExtractLocalizedCode(name); if (name == "name") - if (locale == mLangageCodeInteger || locale == mLanguageCountryCodeInteger || locale == mRegionCodeInteger) + //if (locale == mLangageCodeInteger || locale == mLanguageCountryCodeInteger || locale == mRegionCodeInteger) nameAttr = mVariables.Resolve(attribute.as_string()); } - if (nameAttr.empty()) nameAttr = mVariables.Resolve(node.attribute("name").as_string()); + //if (nameAttr.empty()) nameAttr = mVariables.Resolve(node.attribute("name").as_string()); return (nameAttr == RecalboxConf::Instance().GetThemeOption(mThemeName, subsetAttr)); } @@ -325,7 +317,8 @@ void ThemeData::parseView(const pugi::xml_node& root, ThemeView& view, bool forc continue; } - if (!node.attribute("name")) { LOGT(LogError) << "[Theme] " << FileList() << "Element of type \"" << typeString << R"(" missing "name" attribute!)"; continue; } + if (!node.attribute("name")) + { LOGT(LogError) << "[Theme] " << FileList() << "Element of type \"" << typeString << R"(" missing "name" attribute!)"; continue; } if (!IsMatchingLocaleOrRegionOrNeutral(typeString)) continue; // Process normal item type diff --git a/projects/frontend/es-core/src/themes/ThemeOption.h b/projects/frontend/es-core/src/themes/ThemeOption.h index d1298478bc8b28d59c6bcfb73391c3801896b26a..ec4f015ba64ecf3e37617bd75b7307168d75628e 100644 --- a/projects/frontend/es-core/src/themes/ThemeOption.h +++ b/projects/frontend/es-core/src/themes/ThemeOption.h @@ -28,7 +28,9 @@ struct ThemeOption if (!hasSeparator) return; size_t pos = mRaw.find_first_not_of("0123456789"); if (pos == 0) return; - mIndex = (pos == std::string::npos) ? mRaw.AsInt() : mRaw.AsInt(mRaw[pos]); + char end = (pos == std::string::npos) ? '\0' : mRaw[pos]; + if (end != ' ' && end != '.' && end != '-') return; + mIndex = mRaw.AsInt(end); mIndexed = true; } diff --git a/projects/frontend/es-core/src/utils/cplusplus/StaticLifeCycleControler.h b/projects/frontend/es-core/src/utils/cplusplus/StaticLifeCycleControler.h index 1652b39b3f05af918fad259d3aa6e3bdd7074db1..ef65775c67d95296e3661383219e3addca72ac51 100644 --- a/projects/frontend/es-core/src/utils/cplusplus/StaticLifeCycleControler.h +++ b/projects/frontend/es-core/src/utils/cplusplus/StaticLifeCycleControler.h @@ -43,6 +43,12 @@ template class StaticLifeCycleControler : public INoCopy */ static T& Instance(); + /*! + * @brief Check if instance available + * @return true if available + */ + static bool Available(); + /*! * @brief Check if the object is available * @return True if the object has been instantiated @@ -86,3 +92,8 @@ T& StaticLifeCycleControler::Instance() __builtin_unreachable(); } +template +bool StaticLifeCycleControler::Available() +{ + return sInstance != nullptr; +}