From cc94b28d2865bf197608a2f398ba38dc745ee406 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 14 Nov 2025 11:29:50 +0100 Subject: [PATCH 01/35] fix(frontend): fix missing chars (yes, again) --- .../frontend/es-app/src/emulators/run/GameRunner.cpp | 11 +++++++++-- .../frontend/es-core/src/rendering/fonts/Font.cpp | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/projects/frontend/es-app/src/emulators/run/GameRunner.cpp b/projects/frontend/es-app/src/emulators/run/GameRunner.cpp index 27c94f92f4..cd42d2239b 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,9 @@ 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 + FontManager::Instance().ClearFontCaches(); + return exitCode == 0; } @@ -292,7 +294,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 +311,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 +425,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-core/src/rendering/fonts/Font.cpp b/projects/frontend/es-core/src/rendering/fonts/Font.cpp index 97ff967b63..da1bf792c4 100644 --- a/projects/frontend/es-core/src/rendering/fonts/Font.cpp +++ b/projects/frontend/es-core/src/rendering/fonts/Font.cpp @@ -640,7 +640,7 @@ 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(); -- GitLab From d7e259331b1a9d3956f8a0df1935962c16b5bce4 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Mon, 17 Nov 2025 15:40:33 +0100 Subject: [PATCH 02/35] fix(frontend): fix crash when using non-recognized naomi roms --- .../es-app/src/views/gamelist/ArcadeGameListView.cpp | 12 +++++++----- .../es-app/src/views/gamelist/ArcadeGameListView.h | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp index ed38eba3da..7814b124e2 100644 --- a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp +++ b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp @@ -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); } } } @@ -483,14 +483,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 476b6baa16..8badef0e09 100644 --- a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h +++ b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h @@ -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 -- GitLab From 10aa2d074ec7637b1955bc9f9a879bbf1b6bd8e9 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Tue, 18 Nov 2025 07:53:29 +0100 Subject: [PATCH 03/35] fix(frontend): add .local & .cache to reset emulator options --- .../src/guis/menus/modaltasks/MenuModalEmulatorsReset.cpp | 2 ++ 1 file changed, 2 insertions(+) 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 3f2df9c26f..14f82c2730 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 -- GitLab From f8c5deaac07cc3ecd4bfeeb8c8e972bc6dc7a4a2 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Tue, 18 Nov 2025 08:45:22 +0100 Subject: [PATCH 04/35] fix(frontend): backup some files before reset factory --- projects/frontend/es-app/src/MainRunner.cpp | 4 +++ .../modaltasks/MenuModalFactoryReset.cpp | 31 +++++++++++++++++++ .../menus/modaltasks/MenuModalFactoryReset.h | 10 ++++++ 3 files changed, 45 insertions(+) diff --git a/projects/frontend/es-app/src/MainRunner.cpp b/projects/frontend/es-app/src/MainRunner.cpp index 02fe7b0d14..ff012efe6f 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 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 addeb72056..34b98d260d 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 532c504a7f..4633a6e09d 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) -- GitLab From 29fecf9f874e78ac2c7d04644154357a57231a84 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 19 Nov 2025 11:12:14 +0100 Subject: [PATCH 05/35] fix(frontend): fix indexed subset options --- projects/frontend/es-core/src/themes/ThemeOption.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/frontend/es-core/src/themes/ThemeOption.h b/projects/frontend/es-core/src/themes/ThemeOption.h index d1298478bc..ec4f015ba6 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; } -- GitLab From 1ec468d578448a572b6df9c46b7757b9a065f43f Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 19 Nov 2025 12:08:08 +0100 Subject: [PATCH 06/35] fix(frontend): fix non-working interline in multiline text component --- .../es-core/src/components/TextComponent.cpp | 6 +++--- .../frontend/es-core/src/rendering/fonts/Font.cpp | 13 +++++++------ .../frontend/es-core/src/rendering/fonts/Font.h | 6 ++++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/projects/frontend/es-core/src/components/TextComponent.cpp b/projects/frontend/es-core/src/components/TextComponent.cpp index e2c6a2b4eb..8925341e8c 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() @@ -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/rendering/fonts/Font.cpp b/projects/frontend/es-core/src/rendering/fonts/Font.cpp index da1bf792c4..9322b79dcd 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(); @@ -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 diff --git a/projects/frontend/es-core/src/rendering/fonts/Font.h b/projects/frontend/es-core/src/rendering/fonts/Font.h index 91829ec686..e866ad910a 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; } -- GitLab From 5915f5b02d2b312b6a3875258ea8a5e83ad826c7 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Thu, 20 Nov 2025 17:44:54 +0100 Subject: [PATCH 07/35] fix(frontend): fix magnified svg --- .../frontend/es-core/src/rendering/textures/TextureHolder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp b/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp index a2a8ad24e8..20f649e38e 100755 --- a/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp +++ b/projects/frontend/es-core/src/rendering/textures/TextureHolder.cpp @@ -315,7 +315,7 @@ void TextureHolder::SetTargetSize(int width, int height) if (width < mTargetWidth || height < mTargetHeight) { 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) -- GitLab From 082caa38f89ae01c19b7344a762d717e8d2b9b0e Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Thu, 20 Nov 2025 18:04:56 +0100 Subject: [PATCH 08/35] fix(frontend): fix font cleanup --- projects/frontend/es-core/src/rendering/fonts/Font.cpp | 7 ++++++- .../frontend/es-core/src/rendering/fonts/GlyphStorage.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/projects/frontend/es-core/src/rendering/fonts/Font.cpp b/projects/frontend/es-core/src/rendering/fonts/Font.cpp index 9322b79dcd..16048ffe8e 100644 --- a/projects/frontend/es-core/src/rendering/fonts/Font.cpp +++ b/projects/frontend/es-core/src/rendering/fonts/Font.cpp @@ -642,9 +642,14 @@ void NewFont::ClearCaches() { // Clear default 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/GlyphStorage.h b/projects/frontend/es-core/src/rendering/fonts/GlyphStorage.h index 7c5f76fd94..b721181dd6 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 -- GitLab From 63bef8f9d5981e586b2804fc802cea2cba358762 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 21 Nov 2025 07:30:23 +0100 Subject: [PATCH 09/35] feat(frontend): code cleanup in bios window --- projects/frontend/es-app/src/guis/GuiBiosMd5.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/projects/frontend/es-app/src/guis/GuiBiosMd5.cpp b/projects/frontend/es-app/src/guis/GuiBiosMd5.cpp index b1b1f432dd..c72de79397 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)); -- GitLab From 48fa150ec98078b3f21a53700684793691a000bc Mon Sep 17 00:00:00 2001 From: digitalLumberjack Date: Sun, 16 Nov 2025 18:51:22 +0100 Subject: [PATCH 10/35] fix(frontend): boot on game --- .../frontend/es-app/src/emulators/run/GameRunner.cpp | 3 ++- .../src/utils/cplusplus/StaticLifeCycleControler.h | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/projects/frontend/es-app/src/emulators/run/GameRunner.cpp b/projects/frontend/es-app/src/emulators/run/GameRunner.cpp index cd42d2239b..20d83ae7fd 100644 --- a/projects/frontend/es-app/src/emulators/run/GameRunner.cpp +++ b/projects/frontend/es-app/src/emulators/run/GameRunner.cpp @@ -248,7 +248,8 @@ bool GameRunner::RunGame(FileData& game, const EmulatorData& emulator, const Gam game.Metadata().SetLastPlayedNow(); // Ensure no font has been created during GL context shutdown - FontManager::Instance().ClearFontCaches(); + if (FontManager::Available()) + FontManager::Instance().ClearFontCaches(); return exitCode == 0; } diff --git a/projects/frontend/es-core/src/utils/cplusplus/StaticLifeCycleControler.h b/projects/frontend/es-core/src/utils/cplusplus/StaticLifeCycleControler.h index 1652b39b3f..ef65775c67 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; +} -- GitLab From 9f41fd8985a2a6d173bcfea91deb7a583c118665 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Thu, 27 Nov 2025 10:49:41 +0100 Subject: [PATCH 11/35] fix(frontend): fix truncated popups --- projects/frontend/es-core/src/components/TextComponent.cpp | 2 +- projects/frontend/es-core/src/guis/GuiInfoPopupBase.cpp | 2 +- projects/frontend/es-core/src/rendering/fonts/Font.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/frontend/es-core/src/components/TextComponent.cpp b/projects/frontend/es-core/src/components/TextComponent.cpp index 8925341e8c..3fdc53781b 100755 --- a/projects/frontend/es-core/src/components/TextComponent.cpp +++ b/projects/frontend/es-core/src/components/TextComponent.cpp @@ -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() * (float)mFont->Height() * mLineSpacing; else mSize.y() = (float)mFont->Height(); } diff --git a/projects/frontend/es-core/src/guis/GuiInfoPopupBase.cpp b/projects/frontend/es-core/src/guis/GuiInfoPopupBase.cpp index 9c74b6c4cc..fad655cd54 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/rendering/fonts/Font.cpp b/projects/frontend/es-core/src/rendering/fonts/Font.cpp index 16048ffe8e..4149fb5270 100644 --- a/projects/frontend/es-core/src/rendering/fonts/Font.cpp +++ b/projects/frontend/es-core/src/rendering/fonts/Font.cpp @@ -613,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() * mRealMaxHeightInPixel) * (1.f + interline); switch(AlignmentExtractVertical(alignment)) { case VerticalAlignment::Top: break; -- GitLab From 60507f7cdecd3ed66466b788b9f9b27af47db4bf Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Thu, 27 Nov 2025 11:49:00 +0100 Subject: [PATCH 12/35] fix(frontend): fix ratio component wrong tile size --- .../es-app/src/components/RatingComponent.cpp | 25 ++++++++++--------- .../es-app/src/components/RatingComponent.h | 9 ++++--- .../src/components/ScraperSearchComponent.cpp | 2 +- .../views/gamelist/DetailedGameListView.cpp | 4 +-- .../src/components/GameClipContainer.cpp | 2 +- .../es-core/src/rendering/images/IImage.cpp | 13 +++++++++- .../es-core/src/rendering/images/IImage.h | 10 +++++++- .../src/rendering/textures/Texture.cpp | 4 +-- .../es-core/src/rendering/textures/Texture.h | 3 ++- .../src/rendering/textures/TextureHolder.cpp | 4 +-- .../src/rendering/textures/TextureHolder.h | 3 ++- 11 files changed, 51 insertions(+), 28 deletions(-) diff --git a/projects/frontend/es-app/src/components/RatingComponent.cpp b/projects/frontend/es-app/src/components/RatingComponent.cpp index 8ab9c0bf7f..3618eba6d8 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(sFilledTexture + '?' + discriminent)); + mUnfilledTexture = TextureManager::Instance().Create(Path(sUnfilledTexture + '?' + discriminent)); 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 3f9ec22bbc..ac2a9852e2 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 267ae019fd..b4eba160de 100755 --- a/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp +++ b/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp @@ -92,7 +92,7 @@ ScraperSearchComponent::ScraperSearchComponent(WindowManager& window, bool lowRe mGrid.setEntry(mValuePublisher, Vector2i(3, 2), false, true, Vector2i(1, 1)); 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); diff --git a/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp b/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp index 05a65a015a..581c304bdb 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 9e760a74c9..fbe9addab6 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/rendering/images/IImage.cpp b/projects/frontend/es-core/src/rendering/images/IImage.cpp index bec7054b33..a2a0c43858 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 f47b7238d4..d0d7c051b4 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 5f53903aa8..eac1a46482 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 f26a5b6ef1..e829e07417 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 20f649e38e..6a63d98605 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,7 +312,7 @@ 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; diff --git a/projects/frontend/es-core/src/rendering/textures/TextureHolder.h b/projects/frontend/es-core/src/rendering/textures/TextureHolder.h index c20bbc2fb4..1062928cb3 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 -- GitLab From 424af9b74dab9dd6463c902fae46ef542e0277ef Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Thu, 27 Nov 2025 12:04:29 +0100 Subject: [PATCH 13/35] fix(frontend): fix popup icon size in low resolutions --- .../frontend/es-core/src/components/TextComponent.cpp | 2 +- projects/frontend/es-core/src/guis/GuiInfoPopup.cpp | 10 +++++----- projects/frontend/es-core/src/rendering/fonts/Font.cpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/projects/frontend/es-core/src/components/TextComponent.cpp b/projects/frontend/es-core/src/components/TextComponent.cpp index 3fdc53781b..2dcbf668c5 100755 --- a/projects/frontend/es-core/src/components/TextComponent.cpp +++ b/projects/frontend/es-core/src/components/TextComponent.cpp @@ -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() * mLineSpacing; + 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(); } diff --git a/projects/frontend/es-core/src/guis/GuiInfoPopup.cpp b/projects/frontend/es-core/src/guis/GuiInfoPopup.cpp index 2be10bd963..85c859908c 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/rendering/fonts/Font.cpp b/projects/frontend/es-core/src/rendering/fonts/Font.cpp index 4149fb5270..9782550f1a 100644 --- a/projects/frontend/es-core/src/rendering/fonts/Font.cpp +++ b/projects/frontend/es-core/src/rendering/fonts/Font.cpp @@ -613,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) * (1.f + interline); + float totalHeight = ((float)((strings.Chunks.Count() - 1) * mRealMaxHeightInPixel) * (1.f + interline)) + (float)mRealMaxHeightInPixel; switch(AlignmentExtractVertical(alignment)) { case VerticalAlignment::Top: break; -- GitLab From 5b6b6c62a146aaa18d65590524febc12e40c4ac2 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Thu, 27 Nov 2025 16:41:43 +0100 Subject: [PATCH 14/35] fix(frontend): remove theme old formatversion check --- projects/frontend/es-core/src/themes/ThemeData.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/projects/frontend/es-core/src/themes/ThemeData.cpp b/projects/frontend/es-core/src/themes/ThemeData.cpp index 4e7a635b3b..9913375a8a 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 @@ -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 -- GitLab From 9bab7f2f10b2857042c0d7c2cef07328c7021e8b Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Tue, 2 Dec 2025 09:23:11 +0100 Subject: [PATCH 15/35] fix(frontend): fix wrong resolution selected in multi-file themes --- projects/frontend/es-app/src/guis/GuiThemeManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/frontend/es-app/src/guis/GuiThemeManager.cpp b/projects/frontend/es-app/src/guis/GuiThemeManager.cpp index cc74966bc4..f69227efc5 100644 --- a/projects/frontend/es-app/src/guis/GuiThemeManager.cpp +++ b/projects/frontend/es-app/src/guis/GuiThemeManager.cpp @@ -458,7 +458,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 -- GitLab From 6282bacb49e909ecc51d40239313465c999c05ad Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 3 Dec 2025 10:28:31 +0100 Subject: [PATCH 16/35] fix(frontend): fix single game scraping window --- .../es-app/src/components/ScraperSearchComponent.cpp | 12 ++++++------ .../es-app/src/components/ScraperSearchComponent.h | 12 +++++++----- .../es-app/src/guis/GuiScraperSingleGameRun.h | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp b/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp index b4eba160de..e5b097842b 100755 --- a/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp +++ b/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp @@ -57,7 +57,7 @@ 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)); @@ -86,11 +86,11 @@ 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, "scraper"); mGrid.setEntry(mValueRating, Vector2i(5, 1), false, true, Vector2i(1, 1)); @@ -98,7 +98,7 @@ ScraperSearchComponent::ScraperSearchComponent(WindowManager& window, bool lowRe 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 +145,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 diff --git a/projects/frontend/es-app/src/components/ScraperSearchComponent.h b/projects/frontend/es-app/src/components/ScraperSearchComponent.h index 06c657d90a..3bed467613 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/guis/GuiScraperSingleGameRun.h b/projects/frontend/es-app/src/guis/GuiScraperSingleGameRun.h index b1af263fad..ae728d1def 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); } -- GitLab From 59444829dec549d10e17a3d5116a8e320e46a699 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 3 Dec 2025 10:41:46 +0100 Subject: [PATCH 17/35] fix(frontend): fix osd fonts in low resolutions --- projects/frontend/es-core/src/osd/ClockOsd.cpp | 4 +++- projects/frontend/es-core/src/osd/FpsOSD.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/projects/frontend/es-core/src/osd/ClockOsd.cpp b/projects/frontend/es-core/src/osd/ClockOsd.cpp index b44dece7fb..40098da593 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 dc8f9bfad5..21d1727ed2 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) -- GitLab From c77f397751c064b8f87d2edad8bba7d188e24317 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 3 Dec 2025 11:44:40 +0100 Subject: [PATCH 18/35] fix(frontend): fix ${game.*} variables in arcade view --- .../src/views/gamelist/ArcadeGameListView.cpp | 29 +++++++------------ .../src/views/gamelist/ArcadeGameListView.h | 2 +- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.cpp index 7814b124e2..9d050bf921 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()); @@ -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 diff --git a/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h b/projects/frontend/es-app/src/views/gamelist/ArcadeGameListView.h index 8badef0e09..e71fee92b4 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 -- GitLab From dec712c98ee5665fd26b477a82d06caf9c15f0a8 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Sat, 6 Dec 2025 11:43:11 +0100 Subject: [PATCH 19/35] fix(frontend): avoid using single scraper when main scraper is running --- projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp b/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp index bba8254d06..812c5bdc9f 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(); -- GitLab From 3323ce266ff958eb47adf16976cd6cf93da7b69d Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Mon, 8 Dec 2025 16:47:35 +0100 Subject: [PATCH 20/35] fix(frontend): fix crash when update check takes too long --- projects/frontend/es-app/src/Upgrade.cpp | 10 +++++++--- .../src/guis/menus/modaltasks/MenuModalUpdateCheck.cpp | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/projects/frontend/es-app/src/Upgrade.cpp b/projects/frontend/es-app/src/Upgrade.cpp index e62f604b16..171292c23b 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/guis/menus/modaltasks/MenuModalUpdateCheck.cpp b/projects/frontend/es-app/src/guis/menus/modaltasks/MenuModalUpdateCheck.cpp index 394b83745b..d094f0abc4 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(); } -- GitLab From e727fa7824991aefd5d153ef14e99c49844d9cc0 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Mon, 8 Dec 2025 17:18:58 +0100 Subject: [PATCH 21/35] fix(frontend): fix theme manager availability when no network available --- .../es-app/src/guis/GuiThemeManager.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/projects/frontend/es-app/src/guis/GuiThemeManager.cpp b/projects/frontend/es-app/src/guis/GuiThemeManager.cpp index f69227efc5..4c8dff6100 100644 --- a/projects/frontend/es-app/src/guis/GuiThemeManager.cpp +++ b/projects/frontend/es-app/src/guis/GuiThemeManager.cpp @@ -214,10 +214,6 @@ int GuiThemeManager::Execute(GuiWaitLongExecution& from, const bool& 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"); @@ -225,14 +221,21 @@ int GuiThemeManager::Execute(GuiWaitLongExecution& from, const bool& 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."; } + 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."; } + } // 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."; } + 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 !"; } } @@ -245,7 +248,7 @@ 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://")) { -- GitLab From f9f4127ddc85fd5a752fecb5c2ea1111572debb6 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Mon, 8 Dec 2025 18:42:22 +0100 Subject: [PATCH 22/35] fix(frontend): fix default luminosity value --- projects/frontend/es-app/src/games/MetadataDescriptor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/frontend/es-app/src/games/MetadataDescriptor.cpp b/projects/frontend/es-app/src/games/MetadataDescriptor.cpp index 89f5733550..50a9c72b5d 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; -- GitLab From ad9fb2c6fc18980250a3dcf19305c8582f158df3 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Tue, 9 Dec 2025 10:06:46 +0100 Subject: [PATCH 23/35] fix(frontend): fix game context in gameclip view --- projects/frontend/es-app/src/views/GameClipView.cpp | 1 + projects/frontend/es-core/src/components/GameClipContainer.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/projects/frontend/es-app/src/views/GameClipView.cpp b/projects/frontend/es-app/src/views/GameClipView.cpp index fd03c345ab..5bcb937e05 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-core/src/components/GameClipContainer.h b/projects/frontend/es-core/src/components/GameClipContainer.h index dbaba5789b..c67a1bf9c2 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 -- GitLab From b5f87a522da1d4a04633fa35c0c09230c1f6e665 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 10 Dec 2025 07:21:05 +0100 Subject: [PATCH 24/35] fix(frontend): retry remote themes when network becomes available --- .../es-app/src/guis/GuiThemeManager.cpp | 20 +++++++++++-------- .../es-app/src/guis/GuiThemeManager.h | 2 ++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/projects/frontend/es-app/src/guis/GuiThemeManager.cpp b/projects/frontend/es-app/src/guis/GuiThemeManager.cpp index 4c8dff6100..38dff3218a 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,16 +207,18 @@ 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 - // Get local themes Path folder("/recalbox/system/themes"); for(const LocalTheme& local : mLocalThemes) @@ -222,22 +226,22 @@ int GuiThemeManager::Execute(GuiWaitLongExecution& from, const bool& // Get public themes String publicHubUrl = Networks::QueryDNSRecord(sThemeHubDomain); - if (publicHubUrl.empty()) { LOG(LogError) << "[GuiThemeManager] Error getting public theme url."; } + 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."; } + else { LOG(LogError) << "[GuiThemeManager] Error loading public theme list."; sRemoteListValidated = false; } } // get patron themes if (IsPatron) { String privateHubUrl = Networks::QueryDNSRecord(sPatronThemeHubDomain); - if (privateHubUrl.empty()) { LOG(LogError) << "[GuiThemeManager] Error getting private theme url."; } + 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; @@ -253,7 +257,7 @@ int GuiThemeManager::Execute(GuiWaitLongExecution& from, const bool& 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) diff --git a/projects/frontend/es-app/src/guis/GuiThemeManager.h b/projects/frontend/es-app/src/guis/GuiThemeManager.h index bc296feaec..6b82785d42 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 -- GitLab From bab26a18da603f49fa283397b3a39b1a6f1551b6 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 10 Dec 2025 07:40:24 +0100 Subject: [PATCH 25/35] fix(frontend): fix helpbar size --- projects/frontend/es-core/src/components/HelpComponent.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/projects/frontend/es-core/src/components/HelpComponent.cpp b/projects/frontend/es-core/src/components/HelpComponent.cpp index 5a5b197608..fe5d9bc52b 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; -- GitLab From da577d0a1f97338a4cc54a938aeefe691cf1d40e Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 10 Dec 2025 08:31:54 +0100 Subject: [PATCH 26/35] fix(frontend): fix oob in inputdevice and add logs --- .../es-core/src/input/InputDevice.cpp | 140 ++++++++++-------- 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/projects/frontend/es-core/src/input/InputDevice.cpp b/projects/frontend/es-core/src/input/InputDevice.cpp index 7c667201b9..6afd307a4d 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() -- GitLab From 962816f10d03f8a70fd4d7e514a8dece3970f267 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 10 Dec 2025 10:33:02 +0100 Subject: [PATCH 27/35] fix(frontend): fix non-compatible crt systems available on crt --- projects/frontend/es-app/src/MainRunner.cpp | 2 +- .../frontend/es-app/src/emulators/EmulatorDescriptor.h | 8 ++++++++ projects/frontend/es-app/src/emulators/EmulatorList.h | 8 ++++++++ projects/frontend/es-app/src/systems/SystemManager.cpp | 6 ++++-- projects/frontend/es-app/src/systems/SystemManager.h | 4 ++-- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/projects/frontend/es-app/src/MainRunner.cpp b/projects/frontend/es-app/src/MainRunner.cpp index ff012efe6f..00b3d55103 100644 --- a/projects/frontend/es-app/src/MainRunner.cpp +++ b/projects/frontend/es-app/src/MainRunner.cpp @@ -645,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/emulators/EmulatorDescriptor.h b/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h index e09d001ca1..96aaae0286 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 f25b7d4860..4dc91435d2 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/systems/SystemManager.cpp b/projects/frontend/es-app/src/systems/SystemManager.cpp index b7b26988cf..f7a7d9aeee 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 c567c8b34c..354889b792 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 -- GitLab From 2e459bad64b7906694ca5a881b25c4370a5b1997 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Wed, 10 Dec 2025 10:49:43 +0100 Subject: [PATCH 28/35] fix(frontend): fix localized theme options --- projects/frontend/es-core/src/themes/ThemeData.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/frontend/es-core/src/themes/ThemeData.cpp b/projects/frontend/es-core/src/themes/ThemeData.cpp index 9913375a8a..e18e93455f 100755 --- a/projects/frontend/es-core/src/themes/ThemeData.cpp +++ b/projects/frontend/es-core/src/themes/ThemeData.cpp @@ -245,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)); } -- GitLab From 7ef249c4d3deeeb554c034e9911271bee49f6bd4 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Thu, 11 Dec 2025 08:16:25 +0100 Subject: [PATCH 29/35] fix(frontend): fix update window title --- projects/frontend/es-app/src/guis/GuiUpdateRecalbox.cpp | 6 +++--- projects/frontend/es-app/src/guis/GuiUpdateRecalbox.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.cpp b/projects/frontend/es-app/src/guis/GuiUpdateRecalbox.cpp index 3b63eccf42..a7f7b4620a 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 ad4b8599f9..860a97dc04 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; -- GitLab From 92ca37aa207f5ba1e38de32a206c66cac45197d7 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 12 Dec 2025 15:44:02 +0100 Subject: [PATCH 30/35] fix(frontend): fix "enable fast scrolling" option --- projects/frontend/es-core/src/components/IList.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/frontend/es-core/src/components/IList.h b/projects/frontend/es-core/src/components/IList.h index 79db0d99dc..1fdd3fac29 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; -- GitLab From 79793382fb9ca92a63ffa6a35bad0a63304c4926 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 12 Dec 2025 16:29:16 +0100 Subject: [PATCH 31/35] fix(frontend): fix display issues in scraping window --- projects/frontend/es-app/src/components/RatingComponent.cpp | 4 ++-- .../frontend/es-app/src/components/ScraperSearchComponent.cpp | 3 ++- projects/frontend/es-app/src/guis/menus/base/RatingImage.h | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/projects/frontend/es-app/src/components/RatingComponent.cpp b/projects/frontend/es-app/src/components/RatingComponent.cpp index 3618eba6d8..f6b3b9c99b 100644 --- a/projects/frontend/es-app/src/components/RatingComponent.cpp +++ b/projects/frontend/es-app/src/components/RatingComponent.cpp @@ -17,8 +17,8 @@ RatingComponent::RatingComponent(WindowManager&window, unsigned int color, float , mId(0) , mInterface(nullptr) { - mFilledTexture = TextureManager::Instance().Create(Path(sFilledTexture + '?' + discriminent)); - mUnfilledTexture = TextureManager::Instance().Create(Path(sUnfilledTexture + '?' + discriminent)); + mFilledTexture = TextureManager::Instance().Create(Path(discriminent + '|' + sFilledTexture)); + mUnfilledTexture = TextureManager::Instance().Create(Path(discriminent + '|' + sUnfilledTexture)); mSize.Set(64 * sRatingStarCount, 64); } diff --git a/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp b/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp index e5b097842b..4ad1128d30 100755 --- a/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp +++ b/projects/frontend/es-app/src/components/ScraperSearchComponent.cpp @@ -64,6 +64,7 @@ ScraperSearchComponent::ScraperSearchComponent(WindowManager& window, bool lowRe // 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 @@ -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/guis/menus/base/RatingImage.h b/projects/frontend/es-app/src/guis/menus/base/RatingImage.h index c6a68f3acd..eed862f1a2 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) -- GitLab From 1771bb2b95c9d45fbae38458ffd539c4c619dfa3 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 12 Dec 2025 16:36:55 +0100 Subject: [PATCH 32/35] fix(frontend): fix seamless scraper popupinfo size on crt --- .../src/guis/GuiInfoPopupSeamlessScraper.cpp | 18 +++++++++++++----- .../src/guis/GuiInfoPopupSeamlessScraper.h | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.cpp b/projects/frontend/es-core/src/guis/GuiInfoPopupSeamlessScraper.cpp index f3607d86a7..3b7a78c4ad 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 590a61590c..9787988e4a 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; -- GitLab From e5a39ac2e13994f8c6b226d8386b4918b8c4f008 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 12 Dec 2025 18:12:26 +0100 Subject: [PATCH 33/35] fix(frontend): fix scrollable text state whenever a new text is set --- .../frontend/es-core/src/components/TextScrollComponent.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/frontend/es-core/src/components/TextScrollComponent.cpp b/projects/frontend/es-core/src/components/TextScrollComponent.cpp index b95808b340..9d9c4ed1e8 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(); } -- GitLab From 37a1be02863377f77e2bc8132be0a3cf57770a78 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 12 Dec 2025 20:19:01 +0100 Subject: [PATCH 34/35] fix(frontend): fix default keyboard config being overwritten --- projects/frontend/es-core/src/input/InputManager.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/projects/frontend/es-core/src/input/InputManager.cpp b/projects/frontend/es-core/src/input/InputManager.cpp index f310346408..e10f1cbe18 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(); -- GitLab From 466b7c9f2f9da05aee4bdd88dc02cbd6eee9fa07 Mon Sep 17 00:00:00 2001 From: Bkg2k Date: Fri, 12 Dec 2025 21:23:29 +0100 Subject: [PATCH 35/35] fix(frontend): raise max file in emulationstation --- projects/frontend/es-app/src/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/projects/frontend/es-app/src/main.cpp b/projects/frontend/es-app/src/main.cpp index f1d6dabf38..829ef3de2d 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); -- GitLab