diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 4014bee22f911b8c9441c90b3e696d009a79ef04..6e39fc48a94facdf385c3f80ccc94c16f233a644 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -27,9 +27,8 @@ as release notes for end user on a Recalbox upgrade.
- Add Tamagotchi (tamalibretro core)
- Add NetPlay support for cores libretro-gpsp and libretro-dosbox-pure
- Add original xbox emulator (xemu)
-- Add new aliases and licences
-- Add new search aliases or licences
-- Add new sort groupy by alias
+- Add new search by aliases or licences
+- Add new One Game One Rom filter
- Add new Atari core, HatariB set as new default emulator for Atari ST(E)/Mega ST(E)/TT/Falcon
- Add AppleWin libretro core in Apple II system
- Add libretro dolphin
diff --git a/projects/frontend/data/resources/menu.xml b/projects/frontend/data/resources/menu.xml
index f8f2cda0dba594ad4cd3abd047043a098811f335..ec6e1129c9f55e0e4887e151ae37127a7846c54c 100644
--- a/projects/frontend/data/resources/menu.xml
+++ b/projects/frontend/data/resources/menu.xml
@@ -247,6 +247,10 @@
+
+
+
+
diff --git a/projects/frontend/es-app/src/games/FileSorts.cpp b/projects/frontend/es-app/src/games/FileSorts.cpp
index 5d16b4c39ed1ec23530bdb0d58c278da40eb05ca..0274ec2213edfedcb9e7f6d83bdd072d9f9c4292 100644
--- a/projects/frontend/es-app/src/games/FileSorts.cpp
+++ b/projects/frontend/es-app/src/games/FileSorts.cpp
@@ -78,8 +78,8 @@ int FileSorts::compareFoldersAndGames(const FileData& fd1, const FileData& fd2)
{
if (sUseTopFavorites && f1 == ItemType::Game)
{
- bool fv1 = fd1.Metadata().Favorite();
- bool fv2 = fd2.Metadata().Favorite();
+ bool fv1 = fd1.Metadata().Favorite() || fd1.Metadata().HasFavoriteChild();
+ bool fv2 = fd2.Metadata().Favorite() || fd2.Metadata().HasFavoriteChild();
if (fv1 == fv2) return 0; // Both are favorites or not
if (fv1) return -1; // fv1 is favorite, fv2 is not
return 1; // fv2 is favorite, fv1 is not
@@ -168,7 +168,7 @@ ImplementSortMethod(compareDevelopper)
CheckFoldersAndGames(file1, file2)
int result = unicodeCompareUppercase(file1.Metadata().Developer().Trim(), file2.Metadata().Developer().Trim());
if (result != 0) return result;
- return compareFileName(file1, file2);
+ return compareFileName(file1, file2);
}
ImplementSortMethod(comparePublisher)
diff --git a/projects/frontend/es-app/src/games/MetadataDescriptor.h b/projects/frontend/es-app/src/games/MetadataDescriptor.h
index 3c729af7b7e4e46a8a511e7c477fc65a3954a694..38744bb1459e0383fd29f8277ce9cf5c37b31e4a 100755
--- a/projects/frontend/es-app/src/games/MetadataDescriptor.h
+++ b/projects/frontend/es-app/src/games/MetadataDescriptor.h
@@ -101,6 +101,7 @@ class MetadataDescriptor
bool mPreinstalled:1; //!< Preinstalled game?
bool mNoGame:1; //!< Nor a game (application, tools, ...)
bool mDirty:1; //!< Dirty flag (modified data flag)
+ bool mHasFavoriteChild:1;
int mLightgunLuminosity:4; //!< LightGunLuminosity. First bit is to check if default, last 3 bits is the value : 0000 = default 0, 1000 = 0 forced
bool mPathValidated:1;//!< File path has been validated during loading
@@ -237,6 +238,7 @@ class MetadataDescriptor
, mPreinstalled(false)
, mNoGame(false)
, mDirty(false)
+ , mHasFavoriteChild(false)
, mLightgunLuminosity(0)
, mPathValidated(false)
, mDirtyEnabled(false)
@@ -290,6 +292,8 @@ class MetadataDescriptor
mType(source.mType),
mRotation(source.mRotation),
mTimePlayed(source.mTimePlayed),
+ mHasFavoriteChild(false)
+
{
LivingClasses++;
if (_Type == ItemType::Game) LivingGames++;
@@ -506,6 +510,8 @@ class MetadataDescriptor
[[nodiscard]] int SupportIndex() const { return mSupportIndex; }
[[nodiscard]] int SupportTotal() const { return mSupportTotal; }
[[nodiscard]] SupportSides SupportSide() const { return mSupportSide; }
+ [[nodiscard]] bool HasFavoriteChild()const { return mHasFavoriteChild; }
+
[[nodiscard]] int LightgunLuminosity() const { return mLightgunLuminosity & 0x3; }
[[nodiscard]] SupportTypes SupportType() const { return mSupportType; }
@@ -618,6 +624,8 @@ class MetadataDescriptor
void SetSupportIndex(int index) { mSupportIndex = index; }
void SetSupportTotal(int total) { mSupportTotal = total; }
void SetSupportSide(SupportSides side) { mSupportSide = side; }
+ void SetHasFavoriteChild(bool hasFavoriteChild) { mHasFavoriteChild = hasFavoriteChild; }
+
void SetSupportType(SupportTypes type) { mSupportType = type; }
//! Disable dirtiness until EnableDirtiness is called
diff --git a/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp b/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp
index 9fc03704d9f3b5ce783c1c926321e9c6a3be5e9a..0b4dd5770fdbf2b54145483b2cd88ce3c257d286 100644
--- a/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp
+++ b/projects/frontend/es-app/src/guis/menus/MenuBuilder.cpp
@@ -553,7 +553,8 @@ void MenuBuilder::AddItem(const InheritableContext& context, const ItemDefinitio
case MenuItemType::ScrapeOptionVideo: { AddList(context, item, MenuDataProvider::GetScraperVideosEntries(), mConf.GetScreenScraperVideo(), ScreenScraperEnums::ScreenScraperVideoType::None, defaultGrayed); break; }
case MenuItemType::ScrapeOptionThumb: { AddList(context, item, MenuDataProvider::GetScraperThumbnailsEntries(), mConf.GetScreenScraperThumbnail(), ScreenScraperEnums::ScreenScraperImageType::None, defaultGrayed); break; }
case MenuItemType::ScrapeOptionRegionPriority: { AddList(context, item, MenuDataProvider::GetScraperRegionOptionsEntries(), mConf.GetScreenScraperRegionPriority(), ScreenScraperEnums::ScreenScraperRegionPriority::DetectedRegion, defaultGrayed); break; }
- case MenuItemType::ScrapeOptionRegionFavorite: { AddList(context, item, MenuDataProvider::GetScraperRegionsEntries(), mConf.GetScreenScraperRegion(), Regions::GameRegions::WOR, defaultGrayed); break; }
+ case MenuItemType::ScrapeOptionRegionFavorite: { AddList(context, item,
+ MenuDataProvider::GetRegionEntries(), mConf.GetScreenScraperRegion(), Regions::GameRegions::WOR, defaultGrayed); break; }
case MenuItemType::ScrapeOptionLanguageFavorite: { AddList(context, item, MenuDataProvider::GetScraperLanguagesEntries(), LanguagesTools::GetScrapingLanguage(), Languages::EN, defaultGrayed); break; }
case MenuItemType::ScrapeOptionManual: { AddSwitch(context, item, mConf.GetScreenScraperWantManual(), defaultGrayed); break; }
case MenuItemType::ScrapeOptionMap: { AddSwitch(context, item, mConf.GetScreenScraperWantMaps(), defaultGrayed); break; }
@@ -643,6 +644,9 @@ void MenuBuilder::AddItem(const InheritableContext& context, const ItemDefinitio
case MenuItemType::OnScreenHelp: { AddSwitch(context, item, mConf.GetShowHelp(), defaultGrayed); break; }
case MenuItemType::SwapValidateCancel: { AddSwitch(context, item, mConf.GetSwapValidateAndCancel(), defaultGrayed); break; }
case MenuItemType::OSDClock: { AddSwitch(context, item, mConf.GetClock(), defaultGrayed); break; }
+ case MenuItemType::OneGameOneRom: { AddSwitch(context, item, mConf.GetOneGameOneRom(), defaultGrayed); break; }
+ case MenuItemType::RomPreferredRegion: { AddList(context, item, MenuDataProvider::GetRegionEntries(), Regions::Clamp(mConf.GetRomPreferredRegion()), Regions::GameRegions::EU, defaultGrayed); break; }
+ case MenuItemType::RomSecondPreferredRegion: { AddList(context, item, MenuDataProvider::GetRegionEntries(), Regions::Clamp(mConf.GetRomSecondPreferredRegion()), Regions::GameRegions::EU, defaultGrayed); break; }
case MenuItemType::DisplayByFilename: { AddSwitch(context, item, mConf.GetDisplayByFileName(), defaultGrayed); break; }
case MenuItemType::UpdateGamelists: { AddAction(context, item, true, defaultGrayed); break; }
case MenuItemType::ShowSystems: { AddMultiList(context, item, mDataProvider.GetSystemShown(), defaultGrayed); break; }
diff --git a/projects/frontend/es-app/src/guis/menus/MenuDataProvider.cpp b/projects/frontend/es-app/src/guis/menus/MenuDataProvider.cpp
index 8395fd3ac96e7c0873dcb9f568970383b175790d..1e24c8030d9157101eb13d1fa444a620b991b86d 100644
--- a/projects/frontend/es-app/src/guis/menus/MenuDataProvider.cpp
+++ b/projects/frontend/es-app/src/guis/menus/MenuDataProvider.cpp
@@ -201,7 +201,7 @@ SelectorEntry::List MenuDataPro
return list;
}
-SelectorEntry::List MenuDataProvider::GetScraperRegionsEntries()
+SelectorEntry::List MenuDataProvider::GetRegionEntries()
{
SelectorEntry::List list;
for(Regions::GameRegions region : Regions::AvailableRegions())
diff --git a/projects/frontend/es-app/src/guis/menus/MenuDataProvider.h b/projects/frontend/es-app/src/guis/menus/MenuDataProvider.h
index 722defacb50145b8162d22d2faa4962013c228ff..bf64f690dc43296411808b8b50be19dd5e65d300 100644
--- a/projects/frontend/es-app/src/guis/menus/MenuDataProvider.h
+++ b/projects/frontend/es-app/src/guis/menus/MenuDataProvider.h
@@ -59,7 +59,7 @@ class MenuDataProvider
//! Get scraped region priority
static SelectorEntry::List GetScraperRegionOptionsEntries();
//! Get favorite region when scrping
- static SelectorEntry::List GetScraperRegionsEntries();
+ static SelectorEntry::List GetRegionEntries();
//! Get available scraped languages
static SelectorEntry::List GetScraperLanguagesEntries();
//! Get Softpatching option list
diff --git a/projects/frontend/es-app/src/guis/menus/MenuItemType.cpp b/projects/frontend/es-app/src/guis/menus/MenuItemType.cpp
index b4fb4e044358ee67a537b61083b065b8e7baf01a..4566d724e286a9a72bac24352746b3331093816c 100644
--- a/projects/frontend/es-app/src/guis/menus/MenuItemType.cpp
+++ b/projects/frontend/es-app/src/guis/menus/MenuItemType.cpp
@@ -163,6 +163,9 @@ static const HashMap sStringToItemType
{ "OnScreenHelp", MenuItemType::OnScreenHelp },
{ "SwapValidateCancel", MenuItemType::SwapValidateCancel },
{ "OSDClock", MenuItemType::OSDClock },
+ { "OneGameOneRom", MenuItemType::OneGameOneRom },
+ { "RomPreferredRegion", MenuItemType::RomPreferredRegion },
+ { "RomSecondPreferredRegion", MenuItemType::RomSecondPreferredRegion },
{ "DisplayByFilename", MenuItemType::DisplayByFilename },
{ "UpdateGamelists", MenuItemType::UpdateGamelists },
{ "ShowSystems", MenuItemType::ShowSystems },
diff --git a/projects/frontend/es-app/src/guis/menus/MenuItemType.h b/projects/frontend/es-app/src/guis/menus/MenuItemType.h
index 1d4f62362cb2849fbfa6df8db006628362801ad5..f16acf4ca5b07165d642e9e30290f2fd8a36d2ed 100644
--- a/projects/frontend/es-app/src/guis/menus/MenuItemType.h
+++ b/projects/frontend/es-app/src/guis/menus/MenuItemType.h
@@ -163,6 +163,9 @@ enum class MenuItemType
OnScreenHelp,
SwapValidateCancel,
OSDClock,
+ OneGameOneRom,
+ RomPreferredRegion,
+ RomSecondPreferredRegion,
DisplayByFilename,
UpdateGamelists,
ShowSystems,
diff --git a/projects/frontend/es-app/src/guis/menus/MenuProvider.cpp b/projects/frontend/es-app/src/guis/menus/MenuProvider.cpp
index df2f14b2c3b3d6b015b109ca10c3374e44a2c6e9..5cfe463be3bf2da039b959a861cf2c4d7ae2c81f 100644
--- a/projects/frontend/es-app/src/guis/menus/MenuProvider.cpp
+++ b/projects/frontend/es-app/src/guis/menus/MenuProvider.cpp
@@ -666,6 +666,13 @@ void MenuProvider::MenuSwitchChanged(const ItemSwitch& item, bool& value, int id
case MenuItemType::OnScreenHelp: mConf.SetShowHelp(value).Save(); break;
case MenuItemType::SwapValidateCancel: mConf.SetSwapValidateAndCancel(value).Save(); mWindow.UpdateHelpSystem(); break;
case MenuItemType::OSDClock: mConf.SetClock(value).Save(); break;
+ case MenuItemType::OneGameOneRom:
+ {
+ mConf.SetOneGameOneRom(value).Save();
+ ViewController::Instance().GetOrCreateGamelistView(ViewController::Instance().CurrentSystem())->refreshList();
+ ViewController::Instance().InvalidateAllGamelistsExcept(nullptr);
+ break;
+ }
case MenuItemType::DisplayByFilename:
{
mConf.SetDisplayByFileName(value).Save();
@@ -942,6 +949,8 @@ void MenuProvider::MenuSingleChanged(ItemSelectorBase& ite
switch((MenuItemType)id)
{
case MenuItemType::ScrapeOptionRegionFavorite: mConf.SetScreenScraperRegion(value).Save(); break;
+ case MenuItemType::RomPreferredRegion: mConf.SetRomPreferredRegion(value).Save(); break;
+ case MenuItemType::RomSecondPreferredRegion: mConf.SetRomSecondPreferredRegion(value).Save(); break;
case MenuItemType::HighlightRegion: assert(item.Context().HasSystem() && "No system context"); mConf.SetSystemRegionFilter(*item.Context().System(), value).Save(); break;
default:
mWindow.displayMessage("[MenuProcessor] Unprocessed id " + MenuConverters::ItemToString((MenuItemType)id) + " in MenuSingleChanged !");
diff --git a/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp b/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp
index e88daa2a9713834fc0c3ff6f4f57f7ec0325fcd1..5e1d10c8074bb15b0df2ded59171b9ef013119ed 100755
--- a/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp
+++ b/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.cpp
@@ -1179,6 +1179,21 @@ void DetailedGameListView::populateList(const FolderData& folder)
FolderData::Sort(items, sorts.ComparerFromSort(sort), sorts.IsAscending(sort));
mSort = sort;
+ // loop twice for set HasOtherVersions flag
+ // use for sort by alias. if alone sort by name, otherwise sort by alias
+ Regions::GameRegions firstPreferredRegion = Regions::Clamp(RecalboxConf::Instance().GetRomPreferredRegion());
+ Regions::GameRegions secondPreferredRegion = Regions::Clamp(RecalboxConf::Instance().GetRomSecondPreferredRegion());
+
+ bool isOneGameOneRomMode = !IsFavoriteSystem() && RecalboxConf::Instance().GetOneGameOneRom();
+ if (isOneGameOneRomMode)
+ {
+ // compute one game on rom structs parent and children and filter only parent items
+ items = GeneratePreferredOneGameOneRoms(items, firstPreferredRegion, secondPreferredRegion, sorts);
+
+ }
+ // sort them
+ FolderData::Sort(items, sorts.ComparerFromSort(sort), sorts.IsAscending(sort));
+
// Region filtering?
Regions::GameRegions currentRegion = Regions::Clamp(RecalboxConf::Instance().GetSystemRegionFilter(mSystem));
bool activeRegionFiltering = false;
@@ -1212,17 +1227,17 @@ void DetailedGameListView::populateList(const FolderData& folder)
{
// Has any favorite?
for(int t = items.Count() - 1, i = (items.Count() + 1) / 2; --i >= 0; )
- if (items[i]->Metadata().Favorite() || items[t - i]->Metadata().Favorite()) { hasTopFavorites = true; break; }
+ if (ItemIsFavorite(items[i], isOneGameOneRomMode) || ItemIsFavorite(items[t - i], isOneGameOneRomMode)) { hasTopFavorites = true; break; }
// If we have favorite + favorite first + descending order, move favorites on top
if (hasTopFavorites && !sorts.IsAscending(sort))
{
int favoriteCount = 0;
int favoriteBase = 0;
for(int i = items.Count(); --i >= 0; )
- if (items[i]->Metadata().Favorite())
+ if (ItemIsFavorite(items[i], isOneGameOneRomMode))
{
for(int j = i + 1; --j >= 0; )
- if (items[j]->Metadata().Favorite()) { favoriteCount++; favoriteBase = j; }
+ if (ItemIsFavorite(items[j], isOneGameOneRomMode)) { favoriteCount++; favoriteBase = j; }
else break;
break;
}
@@ -1232,6 +1247,7 @@ void DetailedGameListView::populateList(const FolderData& folder)
for (FileData* item : items)
{
+ String name = item->Name();
// Region filtering?
int colorIndexOffset = BaseColor;
if (activeRegionFiltering)
@@ -1243,14 +1259,33 @@ void DetailedGameListView::populateList(const FolderData& folder)
if (onlyYoko && RotationUtils::IsTate(item->Metadata().Rotation())) continue;
// Header?
if (item->IsGame())
- if (HeaderData* header = NeedHeader(previous, item, hasTopFavorites); header != nullptr)
+ if (HeaderData* header = NeedHeader(previous, item, hasTopFavorites, isOneGameOneRomMode); header != nullptr)
{
mList.add(header->Name(leftIcon), header, GameColor, HeaderColor, headerAlignment);
lastHeader = header;
}
// Store
if (lastHeader == nullptr || !lastHeader->IsFolded())
- mList.add(GetDisplayName(*item), item, colorIndexOffset + (item->IsFolder() ? FolderColor : GameColor), false);
+ {
+ if (isOneGameOneRomMode)
+ {
+ // get children if exist and display them if unfolded
+ OneGameOneRom* oneGameOneRom = mOneGameOnRomByAlias[item->Metadata().MainAliasOrName()];
+ bool isFolded = oneGameOneRom->mFolded;
+ String itemName = isFolded ? "▶ " + GetDisplayName(*item) : "▼ " +GetDisplayName(*item);
+
+ mList.add(oneGameOneRom->mChildren.Count() > 0 ? itemName : GetDisplayName(*item), item, colorIndexOffset + (item->IsFolder() ? FolderColor : GameColor), false);
+
+ if (!isFolded)
+ for (auto& child: oneGameOneRom->mChildren)
+ {
+ mList.add(" \u2022 " + GetDisplayName(*child), child, colorIndexOffset + GameColor, false);
+ }
+ }
+ else
+ mList.add(GetDisplayName(*item), item, colorIndexOffset + (item->IsFolder() ? FolderColor : GameColor), false);
+ }
+
if (item->IsGame())
previous = item;
}
@@ -1293,6 +1328,16 @@ void DetailedGameListView::setCursor(FileData* cursor)
{
if(!mList.setCursor(cursor, 0))
{
+ if (cursor->IsGame() && !IsFavoriteSystem() && RecalboxConf::Instance().GetOneGameOneRom())
+ {
+ String alias = cursor->Metadata().MainAliasOrName();
+ OneGameOneRom* oneGameOneRom = mOneGameOnRomByAlias[alias];
+ if (cursor != oneGameOneRom->mParent)
+ {
+ oneGameOneRom->mFolded = false;
+ }
+ }
+
populateList(mSystem.MasterRoot());
mList.setCursor(cursor);
@@ -1445,19 +1490,19 @@ void DetailedGameListView::BuildVideoLinks(const ThemeData& theme)
}
}
-HeaderData* DetailedGameListView::NeedHeader(FileData* previous, FileData* next, bool hasTopFavorites)
+HeaderData* DetailedGameListView::NeedHeader(FileData* previous, FileData* next, bool hasTopFavorites, bool isOneGameOneRom)
{
// Favorites
if (hasTopFavorites)
{
- if (next->Metadata().Favorite())
+ if (ItemIsFavorite(next, isOneGameOneRom))
{
- if (previous == nullptr || (!previous->Metadata().Favorite() && next->Metadata().Favorite()))
+ if (previous == nullptr || (!ItemIsFavorite(previous, isOneGameOneRom) && ItemIsFavorite(next, isOneGameOneRom)))
return GetHeader(_("Favorites"));
return nullptr;
}
// Leaving favorite zone: reset prevous has if it is the first encountered game
- if (previous != nullptr && previous->Metadata().Favorite())
+ if (previous != nullptr && ItemIsFavorite(previous, isOneGameOneRom))
previous = nullptr;
}
@@ -1470,7 +1515,7 @@ HeaderData* DetailedGameListView::NeedHeader(FileData* previous, FileData* next,
if (hasTopFavorites)
{
// Leavinf favorite area
- if ((previous != nullptr && previous->Metadata().Favorite() && !next->Metadata().Favorite()) ||
+ if ((previous != nullptr && ItemIsFavorite(previous, isOneGameOneRom) && !ItemIsFavorite(next, isOneGameOneRom)) ||
// List has favorites but not at top
(previous == nullptr))
return GetHeader(_("In alphabetical order"));
@@ -1680,6 +1725,48 @@ bool DetailedGameListView::ProcessInput(const InputCompactEvent& event)
{
if (event.HotkeyL2Released()) { ChangeSort(false); return true; }
if (event.HotkeyR2Released()) { ChangeSort(true); return true; }
+
+ FileData* item = getCursor();
+ bool vertical = event.HotkeyDownReleased() || event.HotkeyUpReleased();
+ if (item->IsGame() && !IsFavoriteSystem() && RecalboxConf::Instance().GetOneGameOneRom())
+ {
+ if (event.HotkeyLeftReleased() && !vertical)
+ {
+ OneGameOneRom* oneGameOneRom = mOneGameOnRomByAlias[item->Metadata().MainAliasOrName()];
+ oneGameOneRom->mFolded = true;
+ populateList(*mPopulatedFolder);
+ setCursor(oneGameOneRom->mParent);
+ return true;
+ }
+ if (event.HotkeyRightReleased() && !vertical)
+ {
+ OneGameOneRom* oneGameOneRom = mOneGameOnRomByAlias[item->Metadata().MainAliasOrName()];
+ oneGameOneRom->mFolded = false;
+ populateList(*mPopulatedFolder);
+ setCursor(item);
+ return true;
+ }
+ }
+ if (event.HotkeyUpReleased() && !IsFavoriteSystem() && RecalboxConf::Instance().GetOneGameOneRom())
+ {
+ for (auto& alias : mAliases)
+ {
+ mOneGameOnRomByAlias[alias]->mFolded = true;
+ }
+ populateList(*mPopulatedFolder);
+ setCursor(item);
+ return true;
+ }
+ if (event.HotkeyDownReleased() && !IsFavoriteSystem() && RecalboxConf::Instance().GetOneGameOneRom())
+ {
+ for (auto& alias : mAliases)
+ {
+ mOneGameOnRomByAlias[alias]->mFolded = false;
+ }
+ populateList(*mPopulatedFolder);
+ setCursor(item);
+ return true;
+ }
}
return ISimpleGameListView::ProcessInput(event);
@@ -1874,3 +1961,164 @@ void DetailedGameListView::RemoveGame(FileData& game)
if (mList.remove(&game))
ListRefreshRequired();
}
+
+FileData::List DetailedGameListView::GeneratePreferredOneGameOneRoms(const FileData::List& items, Regions::GameRegions firstPreferredRegion, Regions::GameRegions secondPreferredRegion, FileSorts& sorts)
+{
+ FileData::List filteredGames;
+
+ FileData::List parents;
+ HashMap gamesByAlias;
+ mAliases.clear();
+ for (FileData* item: items)
+ {
+ const String alias = item->Metadata().MainAliasOrName();
+ gamesByAlias[alias].Add(item);
+ mAliases.insert(alias);
+ }
+
+ for (auto& alias : mAliases)
+ {
+ FileData::List byAlias = gamesByAlias.get_or_return_default(alias);
+ bool found = false;
+ FileData* parent;
+
+ // choose with region 1rst favorite region and is latestversion
+ for (auto& game: byAlias)
+ {
+ if (Regions::IsIn4Regions(game->Metadata().Region().Pack, firstPreferredRegion) && !found)
+ {
+ parent = game;
+ parents.Add(parent);
+ found = true;
+ break;
+ }
+ }
+
+ // choose with region 1rst favorite region if not latest version
+ for (auto& game: byAlias)
+ {
+ if (Regions::IsIn4Regions(game->Metadata().Region().Pack, firstPreferredRegion) && game->Metadata().LatestVersion() && !found)
+ {
+ parent = game;
+ found = true;
+ break;
+ }
+ }
+
+ // choose with region 2nd favorite region and if latest version
+ for (auto& game: byAlias)
+ {
+ if (Regions::IsIn4Regions(game->Metadata().Region().Pack, secondPreferredRegion) && game->Metadata().LatestVersion() && !found)
+ {
+ parent = game;
+ parents.Add(parent);
+ found = true;
+ break;
+ }
+ }
+
+ // choose with region 2nd favorite region if not latest version
+ for (auto& game: byAlias)
+ {
+ if (Regions::IsIn4Regions(game->Metadata().Region().Pack, secondPreferredRegion) && !found)
+ {
+ parent = game;
+ parents.Add(parent);
+ found = true;
+ break;
+ }
+ }
+
+ // choose with region WOR
+ for (auto& game: byAlias)
+ {
+ if (Regions::IsIn4Regions(game->Metadata().Region().Pack, Regions::GameRegions::WOR) && !found)
+ {
+ parent = game;
+ parents.Add(parent);
+ found = true;
+ break;
+ }
+ }
+
+ // choose with region US
+ for (auto& game: byAlias)
+ {
+ if (Regions::IsIn4Regions(game->Metadata().Region().Pack, Regions::GameRegions::US) && !found)
+ {
+ parent = game;
+ parents.Add(parent);
+ found = true;
+ break;
+ }
+ }
+
+ // choose with region JP
+ for (auto& game: byAlias)
+ {
+ if (Regions::IsIn4Regions(game->Metadata().Region().Pack, Regions::GameRegions::JP) && !found)
+ {
+ parent = game;
+ parents.Add(parent);
+ found = true;
+ break;
+ }
+
+ // choose fisrt one..
+ if (!found)
+ {
+ parent = byAlias[0];
+ parents.Add(parent);
+ found = true;
+ }
+
+ }
+
+ // change with another way to pick game
+ if (!found) parent = byAlias[0];
+
+ FileData::List children;
+ for (auto& game : gamesByAlias[alias])
+ {
+ if (game == parent)
+ continue;
+
+ children.Add(game);
+ }
+
+ bool hasFavoriteChild = false;
+ for (auto& child : children)
+ if (child->Metadata().Favorite())
+ {
+ hasFavoriteChild = true;
+ break;
+ }
+ parent->Metadata().SetHasFavoriteChild(hasFavoriteChild);
+
+
+ FileSorts::Sorts sort = FileSorts::Sorts::RegionAscending;
+ FolderData::Sort(children, sorts.ComparerFromSort(sort), sorts.IsAscending(sort));
+
+ OneGameOneRom* oneGameOneRom = mOneGameOnRomByAlias.get_or_return_default(alias);
+ if (oneGameOneRom == nullptr)
+ {
+ oneGameOneRom = new OneGameOneRom(parent, children);
+ mOneGameOnRomByAlias[alias] = oneGameOneRom;
+ }
+ else
+ {
+ oneGameOneRom->mParent = parent;
+ oneGameOneRom->mChildren = children;
+ }
+ }
+
+ return parents;
+}
+
+bool DetailedGameListView::ItemIsFavorite(FileData* game, bool isOneGameOneRom)
+{
+ if (!isOneGameOneRom) return game->Metadata().Favorite();
+
+ OneGameOneRom* oneGameOneRom = mOneGameOnRomByAlias.get_or_return_default(game->Metadata().MainAliasOrName());
+ return oneGameOneRom->HasFavorite();
+}
diff --git a/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.h b/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.h
index baa01903b85959634c8cfd299306e713f2c12b0a..2ad0fb727edf59d8d74c424dd73b3f333da0eb49 100644
--- a/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.h
+++ b/projects/frontend/es-app/src/views/gamelist/DetailedGameListView.h
@@ -13,6 +13,36 @@
#include
#include
+#include
+
+struct OneGameOneRom
+{
+ FileData* mParent;
+ FileData::List mChildren;
+ bool mFolded; //!< True if the parent is folded
+
+ OneGameOneRom(FileData* parent, FileData::List children) :
+ mParent(parent),
+ mChildren(std::move(children)),
+ mFolded(true)
+ {}
+
+ bool HasFavorite()
+ {
+ if (mParent->Metadata().Favorite())
+ return true;
+
+ for (auto& child : mChildren)
+ {
+ if (child->Metadata().Favorite())
+ return true;
+ }
+
+ return false;
+ }
+
+};
+
class DetailedGameListView : public ISimpleGameListView
, public ITextListComponentOverlay
, private IScraperEngineStage
@@ -20,6 +50,8 @@ class DetailedGameListView : public ISimpleGameListView
, private RecalboxConf::ISystemGamelistDecorationNotification
, private RecalboxConf::ISystemSortNotification
, private RecalboxConf::ISystemRegionFilterNotification
+ , private RecalboxConf::IRomPreferredRegionNotification
+ , private RecalboxConf::IRomSecondPreferredRegionNotification
, private RecalboxConf::ISystemFlatFoldersNotification
, private RecalboxConf::IShowOnlyLatestVersionNotification
, private RecalboxConf::IFavoritesOnlyNotification
@@ -63,6 +95,13 @@ class DetailedGameListView : public ISimpleGameListView
int index = getCursorIndex();
// Refresh list
populateList(*mPopulatedFolder);
+
+ // if 1G1R
+ if (!IsFavoriteSystem() && RecalboxConf::Instance().GetOneGameOneRom())
+ {
+ setCursor(currentItem);
+ }
+
// Try to set the old data. If it does not exist anymore, ajust index
if (!mList.setCursor(currentItem))
setCursorIndex(index);
@@ -236,6 +275,10 @@ class DetailedGameListView : public ISimpleGameListView
//! Current sort
FileSorts::Sorts mSort;
+ HashMap mGamesByAlias;
+ HashSet mAliases;
+ HashMap mOneGameOnRomByAlias;
+
//! Current gamelist decorations
RecalboxConf::GamelistDecoration mDecorations;
@@ -422,7 +465,7 @@ class DetailedGameListView : public ISimpleGameListView
* @param hasTopFavorites instruct the method that the list has at least one favorite on top or bottom
* @return No null header pointer if a header is required, nullptr otherwise
*/
- HeaderData* NeedHeader(FileData* previous, FileData* next, bool hasTopFavorites);
+ HeaderData* NeedHeader(FileData* previous, FileData* next, bool hasTopFavorites, bool isOneGameOneRom);
/*!
* @brief Create or get a named header
@@ -564,6 +607,19 @@ class DetailedGameListView : public ISimpleGameListView
void ConfigurationSystemRegionFilterChanged(const SystemData& system, const Regions::GameRegions& state) final { (void)state; RefreshRequiredIfSelf(system); }
+ /*
+ * RecalboxConf::RomPreferredRegion implementation
+ */
+
+ void ConfigurationRomPreferredRegionChanged(const Regions::GameRegions& state) final { (void)state; ListRefreshRequired(); }
+
+ /*
+ * RecalboxConf::RomSecondPreferredRegion implementation
+ */
+
+ void ConfigurationRomSecondPreferredRegionChanged(const Regions::GameRegions& state) final { (void)state; ListRefreshRequired(); }
+
+
/*
* RecalboxConf::SystemFlatFoldersNotify implementation
*/
@@ -635,4 +691,8 @@ class DetailedGameListView : public ISimpleGameListView
*/
void ConfigurationHideNoGamesChanged(const bool& value) final { (void)value; ListRefreshRequired(); }
+
+ FileData::List GeneratePreferredOneGameOneRoms(const FileData::List& items, Regions::GameRegions firstPreferredRegion, Regions::GameRegions secondPreferredRegion, FileSorts& sorts);
+
+ bool ItemIsFavorite(FileData* game, bool isOneGameOneRom);
};
diff --git a/projects/frontend/es-core/src/RecalboxConf.h b/projects/frontend/es-core/src/RecalboxConf.h
index 54b6897befd8ae65f8b1e465a0c99bec4a1c3931..bb997a836a273d325bad3fb94dfa65a297b0b555 100644
--- a/projects/frontend/es-core/src/RecalboxConf.h
+++ b/projects/frontend/es-core/src/RecalboxConf.h
@@ -255,6 +255,8 @@ class RecalboxConf: public IniFile, public StaticLifeCycleControler