diff --git a/Makefile b/Makefile index fbdf5c9..92736a5 100644 --- a/Makefile +++ b/Makefile @@ -106,7 +106,7 @@ CFILES := CPPFILES := stb_image_wrapper.cpp 3dsmain.cpp 3dsmenu.cpp 3dsopt.cpp \ 3dsgpu.cpp 3dssound.cpp 3dsui.cpp 3dsexit.cpp \ 3dsconfig.cpp 3dsfiles.cpp 3dsinput.cpp 3dsmatrix.cpp \ - 3dsimpl.cpp 3dsimpl_tilecache.cpp 3dsimpl_gpu.cpp 3dssettings.cpp \ + 3dsimpl.cpp 3dsimpl_tilecache.cpp 3dsimpl_gpu.cpp 3dsthemes.cpp 3dssettings.cpp \ gpulib.cpp \ Snes9x/bsx.cpp Snes9x/fxinst.cpp Snes9x/fxemu.cpp Snes9x/fxdbg.cpp Snes9x/c4.cpp Snes9x/c4emu.cpp \ Snes9x/soundux.cpp Snes9x/spc700.cpp Snes9x/apu.cpp Snes9x/cpuexec.cpp Snes9x/sa1cpu.cpp Snes9x/hwregisters.cpp \ diff --git a/source/3dsfiles.cpp b/source/3dsfiles.cpp index 6415c1d..57867ab 100644 --- a/source/3dsfiles.cpp +++ b/source/3dsfiles.cpp @@ -162,6 +162,27 @@ void file3dsSetRomNameMappings(const char* file) { } +// will return empty string if its root directory +std::string file3dsGetCurrentDirName() { + std::string path = std::string(currentDir); + std::string dirName = ""; + // Find the position of the last slash + size_t lastSlashPos = path.rfind('/'); + + // Check if a slash was found + if (lastSlashPos != std::string::npos) { + // Find the position of the second-to-last slash + size_t secondLastSlashPos = path.rfind('/', lastSlashPos - 1); + + if (secondLastSlashPos != std::string::npos) { + // Extract the substring between the two last slashes + dirName = path.substr(secondLastSlashPos + 1, lastSlashPos - secondLastSlashPos - 1); + } + } + + return dirName; +} + //---------------------------------------------------------------------- // Gets the current directory. //---------------------------------------------------------------------- @@ -244,7 +265,7 @@ bool IsFileExists(const char * filename) { //---------------------------------------------------------------------- void file3dsGoToChildDirectory(const char* childDir) { - strncat(currentDir, &childDir[2], _MAX_PATH); + strncat(currentDir, &childDir[0], _MAX_PATH); strncat(currentDir, "/", _MAX_PATH); } @@ -294,11 +315,15 @@ StoredFile file3dsAddFileBufferToMemory(const std::string& id, const std::string //---------------------------------------------------------------------- // Fetch all file names with any of the given extensions //---------------------------------------------------------------------- -void file3dsGetFiles(std::vector& files, const std::vector& extensions) +bool file3dsGetFiles(std::vector& files, const std::vector& extensions, const char* startDir) { files.clear(); currentDirRomCount = 0; + if (startDir != NULL && startDir[0] != 0) { + strcpy(currentDir, startDir); + } + if (currentDir[0] == '/') { char tempDir[_MAX_PATH]; @@ -308,36 +333,36 @@ void file3dsGetFiles(std::vector& files, const std::vector 1) { // Insert the parent directory. - files.emplace_back(".. (Up to Parent Directory)"s, FileEntryType::ParentDirectory); + files.emplace_back(std::string(PARENT_DIRECTORY_LABEL), FileEntryType::ParentDirectory); } - if (d) + while ((dir = readdir(d)) != NULL) { - while ((dir = readdir(d)) != NULL) + if (dir->d_name[0] == '.') + continue; + if (dir->d_type == DT_DIR) { - if (dir->d_name[0] == '.') - continue; - if (dir->d_type == DT_DIR) - { - files.emplace_back(std::string("\x01 ") + std::string(dir->d_name), FileEntryType::ChildDirectory); - } - if (dir->d_type == DT_REG) + files.emplace_back(std::string(dir->d_name), FileEntryType::ChildDirectory); + } + if (dir->d_type == DT_REG) + { + if (file3dsIsValidFilename(dir->d_name, extensions)) { - if (file3dsIsValidFilename(dir->d_name, extensions)) - { - files.emplace_back(std::string(dir->d_name), FileEntryType::File); - currentDirRomCount++; - } + files.emplace_back(std::string(dir->d_name), FileEntryType::File); + currentDirRomCount++; } } - - closedir(d); } + closedir(d); + std::sort(files.begin(), files.end(), [](const DirectoryEntry& a, const DirectoryEntry& b) { // lowercase sorting of filenames (e.g. "NHL 96" comes after "New Horizons") std::string filenameA = a.Filename; @@ -347,6 +372,8 @@ void file3dsGetFiles(std::vector& files, const std::vector& extensions) { diff --git a/source/3dsfiles.h b/source/3dsfiles.h index 60b4590..5925a05 100644 --- a/source/3dsfiles.h +++ b/source/3dsfiles.h @@ -7,6 +7,8 @@ enum class FileEntryType { ParentDirectory, ChildDirectory, File }; +#define PARENT_DIRECTORY_LABEL " ... Parent Directory" + struct DirectoryEntry { std::string Filename; FileEntryType Type; @@ -71,7 +73,7 @@ void file3dsGoToChildDirectory(const char* childDir); //---------------------------------------------------------------------- // Fetch all file names with any of the given extensions //---------------------------------------------------------------------- -void file3dsGetFiles(std::vector& files, const std::vector& extensions); +bool file3dsGetFiles(std::vector& files, const std::vector& extensions, const char* startDir); bool file3dsSetThumbnailSubDirectories(const char* type); bool file3dsthumbnailsAvailable(const char* type); void file3dsSetRomNameMappings(const char* file); @@ -82,6 +84,7 @@ bool file3dsIsValidFilename(const char* filename, const std::vector StoredFile file3dsAddFileBufferToMemory(const std::string& id, const std::string& filename); +std::string file3dsGetCurrentDirName(); std::string file3dsGetFileBasename(const char* filename, bool ext); std::string file3dsGetTrimmedFileBasename(const char* filename, bool ext); std::string file3dsGetAssociatedFilename(const char* filename, const char* ext, const char* targetDir, bool trimmed = false); diff --git a/source/3dsfont.cpp b/source/3dsfont.cpp index 29e7991..8275564 100644 --- a/source/3dsfont.cpp +++ b/source/3dsfont.cpp @@ -205,12 +205,12 @@ uint8 fontTempestaWidth[256] = { /* 201 */ 5, /* 202 */ 5, /* 203 */ 5, -/* 204 */ 2, -/* 205 */ 2, -/* 206 */ 2, -/* 207 */ 2, +/* 204 */ 12, +/* 205 */ 12, +/* 206 */ 12, +/* 207 */ 12, /* 208 */ 6, -/* 209 */ 6, +/* 209 */ 11, /* 210 */ 5, /* 211 */ 5, /* 212 */ 5, @@ -253,7 +253,7 @@ uint8 fontTempestaWidth[256] = { /* 249 */ 5, /* 250 */ 5, /* 251 */ 5, -/* 252 */ 5, +/* 252 */ 9, /* 253 */ 5, /* 254 */ 5, /* 255 */ 5 @@ -3730,102 +3730,102 @@ uint8 fontTempestaBitmap[] = { " " " " // Ì (204) -" " -"4 " -"6 " -" " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -" " -" " +" 367763 " +" 1688888861 " +" 6888888886 " +"3888 8883 " +"688 88 886 " +"788 88 887 " +"888 888 " +"788 88 887 " +"688 88 886 " +"388 88 883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " // Í (205) -" " -"48 " -"6 " -" " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -" " -" " +" 367763 " +" 1688888861 " +" 6888888886 " +"388 8883 " +"688 88 886 " +"788 88 887 " +"888 8888 " +"788 88 887 " +"688 88 886 " +"388 8883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " // Î (206) +" 367763 " +" 1688888861 " +" 6888888886 " +"388 88 883 " +"688 88 886 " +"7888 8887 " +"88888 88888 " +"7888 8887 " +"688 88 886 " +"388 88 883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " -"84 " -" 6 " " " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " " " +// Ï (207) +" 367763 " +" 1688888861 " +" 6888888886 " +"388 88 883 " +"688 88 886 " +"788 88 887 " +"8888 8888 " +"78888 88887 " +"68888 88886 " +"38888 88883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " +// Ð (208) " " -// Ï (207) " " " " -" 8 " " " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " " " +" 8 " +" 888 " +"88888 " " " +"88888 " +" 888 " +" 8 " " " " " " " -// Ð (208) " " +// Ñ (209) " " " " " " -" 8884 " -" 8 8 " -" 8 8 " -"868 8 " -" 8 8 " -" 8 8 " -" 8884 " -" " " " " " " " " " -// Ñ (209) -" " -"486 6 " -"6 684 " +" 6 67876 " +" 676 676 " +"67876 6 " " " -"8 8 " -"88 8 " -"846 8 " -"8 8 8 " -"8 648 " -"8 88 " -"8 8 " " " " " " " @@ -4550,10 +4550,11 @@ uint8 fontTempestaBitmap[] = { " " " " " " -" " -" " -" " -" " +" 888 " +" 88888 " +" 88888 " +" 88888 " +" 888 " " " " " " " @@ -4821,12 +4822,12 @@ uint8 fontRondaWidth[256] = { /* 201 */ 6, /* 202 */ 6, /* 203 */ 6, -/* 204 */ 2, -/* 205 */ 2, -/* 206 */ 2, -/* 207 */ 2, -/* 208 */ 7, -/* 209 */ 6, +/* 204 */ 12, +/* 205 */ 12, +/* 206 */ 12, +/* 207 */ 12, +/* 208 */ 6, +/* 209 */ 11, /* 210 */ 6, /* 211 */ 6, /* 212 */ 6, @@ -4869,7 +4870,7 @@ uint8 fontRondaWidth[256] = { /* 249 */ 5, /* 250 */ 5, /* 251 */ 5, -/* 252 */ 5, +/* 252 */ 9, /* 253 */ 5, /* 254 */ 5, /* 255 */ 5 @@ -8346,102 +8347,102 @@ uint8 fontRondaBitmap[] = { " " " " // Ì (204) -" " -"4 " -"6 " -" " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -" " -" " +" 367763 " +" 1688888861 " +" 6888888886 " +"3888 8883 " +"688 88 886 " +"788 88 887 " +"888 888 " +"788 88 887 " +"688 88 886 " +"388 88 883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " // Í (205) -" " -"48 " -"6 " -" " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -" " -" " +" 367763 " +" 1688888861 " +" 6888888886 " +"388 8883 " +"688 88 886 " +"788 88 887 " +"888 8888 " +"788 88 887 " +"688 88 886 " +"388 8883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " // Î (206) +" 367763 " +" 1688888861 " +" 6888888886 " +"388 88 883 " +"688 88 886 " +"7888 8887 " +"88888 88888 " +"7888 8887 " +"688 88 886 " +"388 88 883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " -"84 " -" 6 " -" " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " " " " " +// Ï (207) +" 367763 " +" 1688888861 " +" 6888888886 " +"388 88 883 " +"688 88 886 " +"788 88 887 " +"8888 8888 " +"78888 88887 " +"68888 88886 " +"38888 88883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " -// Ï (207) +// Ð (208) " " " " -" 8 " -" " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " -"8 " " " " " " " +" 8 " +" 888 " +"88888 " " " +"88888 " +" 888 " +" 8 " " " -// Ð (208) " " " " " " +// Ñ (209) " " -" 88884 " -" 8 8 " -" 8 8 " -"8686 8 " -" 8 8 " -" 8 8 " -" 88884 " " " " " " " " " " " -// Ñ (209) " " -"486 6 " -"6 684 " +" 6 67876 " +" 676 676 " +"67876 6 " " " -"8 8 " -"88 8 " -"846 8 " -"8 8 8 " -"8 648 " -"8 88 " -"8 8 " " " " " " " @@ -9166,10 +9167,11 @@ uint8 fontRondaBitmap[] = { " " " " " " -" " -" " -" " -" " +" 888 " +" 88888 " +" 88888 " +" 88888 " +" 888 " " " " " " " @@ -9438,12 +9440,12 @@ uint8 fontArialWidth[256] = { /* 201 */ 7, /* 202 */ 7, /* 203 */ 7, -/* 204 */ 3, -/* 205 */ 3, -/* 206 */ 3, -/* 207 */ 3, -/* 208 */ 7, -/* 209 */ 7, +/* 204 */ 12, +/* 205 */ 12, +/* 206 */ 12, +/* 207 */ 12, +/* 208 */ 6, +/* 209 */ 11, /* 210 */ 8, /* 211 */ 8, /* 212 */ 8, @@ -9486,7 +9488,7 @@ uint8 fontArialWidth[256] = { /* 249 */ 6, /* 250 */ 6, /* 251 */ 6, -/* 252 */ 6, +/* 252 */ 9, /* 253 */ 5, /* 254 */ 6, /* 255 */ 5 @@ -12963,101 +12965,101 @@ uint8 fontArialBitmap[] = { " " " " // Ì (204) -"37 " -" 42 " -" " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " -" " -" " -" " +" 367763 " +" 1688888861 " +" 6888888886 " +"3888 8883 " +"688 88 886 " +"788 88 887 " +"888 888 " +"788 88 887 " +"688 88 886 " +"388 88 883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " // Í (205) -"281 " -"53 " -" " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " -" " -" " -" " +" 367763 " +" 1688888861 " +" 6888888886 " +"388 8883 " +"688 88 886 " +"788 88 887 " +"888 8888 " +"788 88 887 " +"688 88 886 " +"388 8883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " // Î (206) -"185 " -"5471 " -"2 " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " -" " +" 367763 " +" 1688888861 " +" 6888888886 " +"388 88 883 " +"688 88 886 " +"7888 8887 " +"88888 88888 " +"7888 8887 " +"688 88 886 " +"388 88 883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " " " +// Ï (207) +" 367763 " +" 1688888861 " +" 6888888886 " +"388 88 883 " +"688 88 886 " +"788 88 887 " +"8888 8888 " +"78888 88887 " +"68888 88886 " +"38888 88883 " +" 6888888886 " +" 1688888861 " +" 367763 " " " " " -// Ï (207) " " -"626 " +// Ð (208) " " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " -"171 " " " " " " " " " +" 8 " +" 888 " +"88888 " " " +"88888 " +" 888 " +" 8 " " " -// Ð (208) " " " " " " -"288883 " -"26 182 " -"26 55 " -"8885 35 " -"26 53 " -"26 171 " -"288882 " +// Ñ (209) " " " " " " " " " " " " -// Ñ (209) -" 1846 " -" 3475 " +" 6 67876 " +" 676 676 " +"67876 6 " " " -"281 63 " -"286 63 " -"2653 63 " -"2617163 " -"26 3663 " -"26 683 " -"26 183 " " " " " " " @@ -13783,10 +13785,11 @@ uint8 fontArialBitmap[] = { " " " " " " -" " -" " -" " -" " +" 888 " +" 88888 " +" 88888 " +" 88888 " +" 888 " " " " " " " diff --git a/source/3dsimpl.cpp b/source/3dsimpl.cpp index 2e7a5d7..0f75b4a 100644 --- a/source/3dsimpl.cpp +++ b/source/3dsimpl.cpp @@ -761,15 +761,15 @@ void impl3dsSaveLoadMessage(bool saveMode, saveLoad_state saveLoadState) switch (saveLoadState) { case SAVELOAD_IN_PROGRESS: - dialogBackgroundColor = DIALOGCOLOR_CYAN; + dialogBackgroundColor = Themes[settings3DS.Theme].dialogColorInfo; snprintf(message, _MAX_PATH, "%s slot #%d...", saveMode ? "Saving into" : "Loading from", settings3DS.CurrentSaveSlot); break; case SAVELOAD_SUCCEEDED: - dialogBackgroundColor = DIALOGCOLOR_GREEN; + dialogBackgroundColor = Themes[settings3DS.Theme].dialogColorSuccess; snprintf(message, _MAX_PATH, "Slot %d %s.", settings3DS.CurrentSaveSlot, saveMode ? "save completed" : "loaded"); break; case SAVELOAD_FAILED: - dialogBackgroundColor = DIALOGCOLOR_RED; + dialogBackgroundColor = Themes[settings3DS.Theme].dialogColorWarn; snprintf(message, _MAX_PATH, "Unable to %s #%d!", saveMode ? "save into" : "load from", settings3DS.CurrentSaveSlot); break; } @@ -845,7 +845,7 @@ void impl3dsSelectSaveSlot(int direction) { char message[_MAX_PATH]; snprintf(message, _MAX_PATH - 1, "Current Save Slot: #%d", settings3DS.CurrentSaveSlot); - menu3dsSetSecondScreenContent(message, DIALOGCOLOR_GREEN); + menu3dsSetSecondScreenContent(message, Themes[settings3DS.Theme].dialogColorSuccess); } void impl3dsSwapJoypads() { @@ -853,7 +853,7 @@ void impl3dsSwapJoypads() { char message[_MAX_PATH]; snprintf(message, _MAX_PATH - 1, "Controllers Swapped.\nPlayer #%d active.", Settings.SwapJoypads ? 2 : 1); - menu3dsSetSecondScreenContent(message, DIALOGCOLOR_GREEN); + menu3dsSetSecondScreenContent(message, Themes[settings3DS.Theme].dialogColorSuccess); } bool impl3dsTakeScreenshot(const char*& path, bool menuOpen) { @@ -862,7 +862,7 @@ bool impl3dsTakeScreenshot(const char*& path, bool menuOpen) { snd3DS.generateSilence = true; if (!menuOpen) { - menu3dsSetSecondScreenContent("Now taking a screenshot...\nThis may take a while.", DIALOGCOLOR_CYAN); + menu3dsSetSecondScreenContent("Now taking a screenshot...\nThis may take a while.", Themes[settings3DS.Theme].dialogColorInfo); } @@ -901,7 +901,7 @@ bool impl3dsTakeScreenshot(const char*& path, bool menuOpen) { snprintf(message, _MAX_PATH - 1, "%s", "Oops. Unable to take screenshot!"); - menu3dsSetSecondScreenContent(message, (success ? DIALOGCOLOR_GREEN : DIALOGCOLOR_RED)); + menu3dsSetSecondScreenContent(message, (success ? Themes[settings3DS.Theme].dialogColorSuccess : Themes[settings3DS.Theme].dialogColorWarn)); return success; } diff --git a/source/3dsmain.cpp b/source/3dsmain.cpp index be96da8..798701c 100644 --- a/source/3dsmain.cpp +++ b/source/3dsmain.cpp @@ -63,7 +63,6 @@ int framesSkippedCount = 0; int maxFramesForDialog = 60; char romFileName[_MAX_PATH]; -char romFileNameLastSelected[_MAX_PATH]; bool slotLoaded = false; int cfgFileAvailable = 0; // 0 = none, 1 = global, 2 = game-specific, 3 = global and game-specific, -1 = deleted @@ -77,7 +76,7 @@ volatile bool thumbnailCachingInProgress = false; size_t cacheThumbnails(std::vector& romFileNames, unsigned short totalCount, const char *currentDir) { size_t currentCount = 0; - int lastRomItemIndex = menu3dsGetLastRomItemIndex(); + int lastRomItemIndex = menu3dsGetLastSelectedIndexByTab("Load Game"); // we want to load `offset` thumbnails before `lastRomItemIndex` // so roms listed before lastRomItemIndex should also get their related thumbnail sooner than without providing `offset` @@ -225,9 +224,9 @@ void initThumbnailThread() { // cache thumbnail of last selected rom instantly // we have to copy value of romFileNameLastSelected to avoid memory allocation issues - if (romFileNameLastSelected[0] != 0) { + if (settings3DS.lastSelectedFilename[0] != 0) { char lastSelectedGame[_MAX_PATH]; - strncpy(lastSelectedGame, romFileNameLastSelected, _MAX_PATH); + strncpy(lastSelectedGame, settings3DS.lastSelectedFilename, _MAX_PATH); std::string thumbnailFilename = file3dsGetAssociatedFilename(lastSelectedGame, ".png", "thumbnails", true); if (!thumbnailFilename.empty()) { @@ -360,8 +359,8 @@ namespace { items.emplace_back(callback, MenuItemType::Gauge, text, ""s, value, min, max); } - void AddMenuPicker(std::vector& items, const std::string& text, const std::string& description, const std::vector& options, int value, int backgroundColor, bool showSelectedOptionInMenu, std::function callback, int id = -1) { - items.emplace_back(callback, MenuItemType::Picker, text, ""s, value, showSelectedOptionInMenu ? 1 : 0, id, description, options, backgroundColor); + void AddMenuPicker(std::vector& items, const std::string& text, const std::string& description, const std::vector& options, int value, int dialogType, bool showSelectedOptionInMenu, std::function callback, int id = -1) { + items.emplace_back(callback, MenuItemType::Picker, text, ""s, value, showSelectedOptionInMenu ? 1 : 0, id, description, options, dialogType); } } @@ -392,13 +391,20 @@ int resetConfigOptionSelected(int val) { return cfgRemovalfailed; } -std::vector makeOptionsForNoYes() { +std::vector makePickerOptions(const std::vector& options) { std::vector items; - AddMenuDialogOption(items, 0, "No"s, ""s); - AddMenuDialogOption(items, 1, "Yes"s, ""s); + + for (int i = 0; i < options.size(); i++) { + AddMenuDialogOption(items, i, options[i], ""s); + } + return items; } +std::vector makeOptionsForNoYes() { + return makePickerOptions({ "No", "Yes" }); +} + std::vector makeOptionsForResetConfig() { std::vector items; AddMenuDialogOption(items, 0, "None"s, ""s); @@ -426,9 +432,7 @@ std::vector makeOptionsForResetConfig() { } std::vector makeOptionsForOk() { - std::vector items; - AddMenuDialogOption(items, 0, "OK"s, ""s); - return items; + return makePickerOptions({"OK"}); } std::vector makeOptionsForGameThumbnail(const std::vector& options) { @@ -450,20 +454,46 @@ std::vector makeOptionsForGameThumbnail(const std::vector makeOptionsForFont() { +std::vector makeOptionsForFileMenu(const std::vector& options) { std::vector items; - AddMenuDialogOption(items, 0, "Tempesta"s, ""s); - AddMenuDialogOption(items, 1, "Ronda"s, ""s); - AddMenuDialogOption(items, 2, "Arial"s, ""s); + + for (int i = 0; i < options.size(); i++) { + if (i == 0) { + // option "set default directory" + if (strcmp(settings3DS.defaultDir, file3dsGetCurrentDir()) != 0) { + AddMenuDialogOption(items, i, options[i], ""s); + } + } + else if (i == 1) { + // option "reset default directory" + if (strcmp(settings3DS.defaultDir, "/") != 0) { + std::string defaulDirLabel = std::string(settings3DS.defaultDir); + size_t maxChars = 28; + + if (defaulDirLabel.length() > maxChars) { + defaulDirLabel = "..." + defaulDirLabel.substr(defaulDirLabel.length() - maxChars, maxChars); + } + + AddMenuDialogOption(items, i, options[i], defaulDirLabel); + } + } + else if (i == 2) { + // option "select random game" + if (file3dsGetCurrentDirRomCount() > 1) { + AddMenuDialogOption(items, i, options[i], ""s); + } + } + } + return items; } -std::vector makeEmulatorMenu(std::vector& menuTab, int& currentMenuTab, bool& closeMenu, bool romLoaded = false) { +std::vector makeEmulatorMenu(std::vector& menuTab, int& currentMenuTab, bool& closeMenu, bool isPauseMenu) { std::vector items; - if (romLoaded) { + if (isPauseMenu) { AddMenuHeader1(items, "CURRENT GAME"s); items.emplace_back([&closeMenu](int val) { closeMenu = true; @@ -473,7 +503,7 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur items.emplace_back([&menuTab, ¤tMenuTab, &closeMenu](int val) { SMenuTab dialogTab; bool isDialog = false; - int result = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Reset Console", "This will restart the game. Are you sure?", DIALOGCOLOR_RED, makeOptionsForNoYes()); + int result = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Reset Console", "This will restart the game. Are you sure?", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForNoYes()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); if (result == 1) { @@ -482,11 +512,10 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur } }, MenuItemType::Action, " Reset"s, ""s); - items.emplace_back([&menuTab, ¤tMenuTab](int val) { SMenuTab dialogTab; bool isDialog = false; - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Screenshot", "Now taking a screenshot...\nThis may take a while.", DIALOGCOLOR_CYAN, std::vector()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Screenshot", "Now taking a screenshot...\nThis may take a while.", Themes[settings3DS.Theme].dialogColorInfo, std::vector()); const char *path; bool success = impl3dsTakeScreenshot(path, true); @@ -496,12 +525,12 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur { char text[600]; snprintf(text, 600, "Done! File saved to %s", path); - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Screenshot", text, DIALOGCOLOR_GREEN, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Screenshot", text, Themes[settings3DS.Theme].dialogColorSuccess, makeOptionsForOk()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); } else { - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Screenshot", "Oops. Unable to take screenshot!", DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Screenshot", "Oops. Unable to take screenshot!", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); } }, MenuItemType::Action, " Take Screenshot"s, ""s); @@ -530,21 +559,21 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur std::ostringstream oss; oss << "Saving into slot #" << slot << "..."; - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestates", oss.str(), DIALOGCOLOR_CYAN, std::vector()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestates", oss.str(), Themes[settings3DS.Theme].dialogColorInfo, std::vector()); result = impl3dsSaveStateSlot(slot); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); if (!result) { std::ostringstream oss; oss << "Unable to save into #" << slot << "!"; - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestate failure", oss.str(), DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestate failure", oss.str(), Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); } else { std::ostringstream oss; oss << "Slot " << slot << " save completed."; - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestate complete.", oss.str(), DIALOGCOLOR_GREEN, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestate complete.", oss.str(), Themes[settings3DS.Theme].dialogColorSuccess, makeOptionsForOk()); if (CheckAndUpdate( settings3DS.CurrentSaveSlot, slot )) { for (int i = 0; i < currentTab->MenuItems.size(); i++) { @@ -575,7 +604,7 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur bool isDialog = false; std::ostringstream oss; oss << "Unable to load slot #" << slot << "!"; - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestate failure", oss.str(), DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Savestate failure", oss.str(), Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); } else { CheckAndUpdate( settings3DS.CurrentSaveSlot, slot ); @@ -590,82 +619,79 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur AddMenuHeader1(items, "APPEARANCE"s); std::vectorthumbnailOptions = {"None", "Boxart", "Title", "Gameplay"}; - items.emplace_back([thumbnailOptions, &menuTab, ¤tMenuTab, &closeMenu](int val) { - SMenuTab *currentTab = &menuTab[currentMenuTab]; - std::string gameThumbnailMessage = "Type of thumbnails to display in \"Load Game\" tab."; - bool thumbnailsAvailable = false; - - for (const std::string& option : thumbnailOptions) { - std::string type = option; - type[0] = std::tolower(type[0]); - if (file3dsthumbnailsAvailable(type.c_str())) { - thumbnailsAvailable = true; - break; - } - } - - // display info message when user doesn't have provided any game thumbnails yet - if (!thumbnailsAvailable) { - gameThumbnailMessage += "\nNo thumbnails found. You can download them on \ngithub.com/matbo87/snes9x_3ds-assets"; - } + std::string gameThumbnailMessage = "Type of thumbnails to display in \"Load Game\" tab."; + bool thumbnailsAvailable = false; + + for (const std::string& option : thumbnailOptions) { + std::string type = option; + type[0] = std::tolower(type[0]); + if (file3dsthumbnailsAvailable(type.c_str())) { + thumbnailsAvailable = true; + break; + } + } - SMenuTab dialogTab; - bool isDialog = false; - int option = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Game Thumbnail"s, gameThumbnailMessage, DIALOGCOLOR_CYAN, makeOptionsForGameThumbnail(thumbnailOptions), settings3DS.GameThumbnailType); - - menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); + // display info message when user doesn't have provided any game thumbnails yet + if (!thumbnailsAvailable) { + gameThumbnailMessage += "\nNo thumbnails found. You can download them on \ngithub.com/matbo87/snes9x_3ds-assets"; + } - if (option < 0 || !CheckAndUpdate(settings3DS.GameThumbnailType, option)) { + AddMenuPicker(items, " Game Thumbnail"s, "Type of thumbnails to display in \"Load Game\" tab."s, makeOptionsForGameThumbnail(thumbnailOptions), settings3DS.GameThumbnailType, DIALOG_TYPE_INFO, true, + [&menuTab, ¤tMenuTab]( int val ) { + if (!CheckAndUpdate(settings3DS.GameThumbnailType, val)) { return; } + SMenuTab dialogTab; + bool isDialog = false; + if (thumbnailCachingThreadRunning) { - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Game Thumbnail", "Clean up thumbnail cache...", DIALOGCOLOR_CYAN, std::vector()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Game Thumbnail", "Clean up thumbnail cache...", Themes[settings3DS.Theme].dialogColorInfo, std::vector()); initThumbnailThread(); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); } else { initThumbnailThread(); } + }); - // because we don't use MenuItemType::Picker here, we have to do it the ugly way - for (int i = 0; i < currentTab->MenuItems.size(); i++) { - if (currentTab->MenuItems[i].Text == " Game Thumbnail") { - currentTab->MenuItems[i].Description = thumbnailOptions[option]; - break; - } - } - }, MenuItemType::Action, " Game Thumbnail"s, thumbnailOptions[settings3DS.GameThumbnailType], 99999); - - AddMenuPicker(items, " Font"s, "The font used for the user interface."s, makeOptionsForFont(), settings3DS.Font, DIALOGCOLOR_CYAN, true, - []( int val ) { if ( CheckAndUpdate( settings3DS.Font, val ) ) { ui3dsSetFont(val); } }); + std::vectorthemeNames; + + for (int i = 0; i < TOTALTHEMECOUNT; i++) { + themeNames.emplace_back(std::string(Themes[i].Name)); + } + + AddMenuPicker(items, " Theme"s, "The theme used for the user interface."s, makePickerOptions(themeNames), settings3DS.Theme, DIALOG_TYPE_INFO, true, + []( int val ) { CheckAndUpdate(settings3DS.Theme, val); }); - items.emplace_back([romLoaded, &menuTab, ¤tMenuTab, &closeMenu](int val) { - SMenuTab dialogTab; - bool isDialog = false; - int result = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Swap Screens", "Play your games on top or bottom screen"s, DIALOGCOLOR_CYAN, makeOptionsForNoYes()); - menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); - if (result == 1) { + AddMenuPicker(items, " Font"s, "The font used for the user interface."s, makePickerOptions({"Tempesta", "Ronda", "Arial"}), settings3DS.Font, DIALOG_TYPE_INFO, true, + []( int val ) { if ( CheckAndUpdate( settings3DS.Font, val ) ) { ui3dsSetFont(val); } }); + + AddMenuPicker(items, " Game Screen"s, "Play your games on top or bottom screen"s, makePickerOptions({"Top", "Bottom"}), settings3DS.GameScreen, DIALOG_TYPE_INFO, true, + [isPauseMenu, &closeMenu]( int val ) { + gfxScreen_t screen = (val == 0) ? GFX_TOP : GFX_BOTTOM; + + if (!CheckAndUpdate(settings3DS.GameScreen, screen)) { + return; + } + menu3dsDrawBlackScreen(); - settings3DS.GameScreen = screenSettings.GameScreen == GFX_TOP ? GFX_BOTTOM : GFX_TOP; ui3dsUpdateScreenSettings(settings3DS.GameScreen); menu3dsDrawBlackScreen(); gfxSetScreenFormat(screenSettings.SecondScreen, GSP_RGB565_OES); - if (!romLoaded) { + if (!isPauseMenu) { gfxSetDoubleBuffering(screenSettings.SecondScreen, true); drawStartScreen(); } else { gfxSetScreenFormat(screenSettings.GameScreen, GSP_RGBA8_OES); - closeMenu = true; } - } - }, MenuItemType::Action, " Swap Screens"s, ""s); + }); AddMenuCheckbox(items, " Disable 3D Slider"s, settings3DS.Disable3DSlider, []( int val ) { CheckAndUpdate( settings3DS.Disable3DSlider, val ); }); - int emptyLines = romLoaded ? 1 : 5; + int emptyLines = isPauseMenu ? 1 : 4; for (int i = 0; i < emptyLines; i++) { AddMenuDisabledOption(items, ""s); @@ -673,16 +699,15 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur AddMenuHeader1(items, "OTHERS"s); - // addMenuPicker doesn't work quite well here because of multiple dialogs, so we do it this way if (cfgFileAvailable > 0) { items.emplace_back([&menuTab, ¤tMenuTab, &closeMenu](int val) { std::ostringstream resetConfigDescription; std::string gameConfigDescription = " and/or remove current game config"; - resetConfigDescription << "Restore default settings" << (cfgFileAvailable == 3 ? gameConfigDescription : "") << ". Emulator will quit afterwards so that changes take effect on restart!"; + resetConfigDescription << "Restore default settings" << (cfgFileAvailable == 3 ? gameConfigDescription : "") << ". Emulator will quit afterwards so that changes take effect on restart."; SMenuTab dialogTab; bool isDialog = false; - int option = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Reset config"s, resetConfigDescription.str(), DIALOGCOLOR_RED, makeOptionsForResetConfig()); + int option = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Reset config"s, resetConfigDescription.str(), Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForResetConfig()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); // "None" selected or B pressed @@ -694,16 +719,16 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur switch (result) { case 1: - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Error", "Couldn't remove global config. If the error persists, try to delete the file manually from your sd card. Emulator will now quit.", DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Error", "Couldn't remove global config. If the error persists, try to delete the file manually from your sd card. Emulator will now quit.", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); break; case 2: - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Error", "Couldn't remove game config. If the error persists, try to delete the file manually from your sd card. Emulator will now quit.", DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Error", "Couldn't remove game config. If the error persists, try to delete the file manually from your sd card. Emulator will now quit.", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); break; case 3: - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Error", "Couldn't remove global config and game config. If the error persists, try to delete the files manually from your sd card. Emulator will now quit.", DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Error", "Couldn't remove global config and game config. If the error persists, try to delete the files manually from your sd card. Emulator will now quit.", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); break; default: - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Success", "Config removed. Emulator will now quit.", DIALOGCOLOR_GREEN, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Success", "Config removed. Emulator will now quit.", Themes[settings3DS.Theme].dialogColorSuccess, makeOptionsForOk()); break; } @@ -718,7 +743,7 @@ std::vector makeEmulatorMenu(std::vector& menuTab, int& cur }, MenuItemType::Action, " Reset Config"s, ""s); } - AddMenuPicker(items, " Quit Emulator"s, "Are you sure you want to quit?", makeOptionsForNoYes(), 0, DIALOGCOLOR_RED, false, exitEmulatorOptionSelected); + AddMenuPicker(items, " Quit Emulator"s, "Are you sure you want to quit?", makeOptionsForNoYes(), 0, DIALOG_TYPE_WARN, false, exitEmulatorOptionSelected); return items; } @@ -744,22 +769,6 @@ std::vector makeOptionsForStretch() { return items; } -std::vector makeOptionsforSecondScreen() { - std::vector items; - AddMenuDialogOption(items, 0, "None"s, ""s); - AddMenuDialogOption(items, 1, "Game Cover"s, ""s); - AddMenuDialogOption(items, 2, "ROM Information"s, ""s); - return items; -} - -std::vector makeOptionsforGameBorder() { - std::vector items; - AddMenuDialogOption(items, 0, "None"s, ""s); - AddMenuDialogOption(items, 1, "Default"s, ""s); - AddMenuDialogOption(items, 2, "Game-Specific"s, ""s); - return items; -} - std::vector makeOptionsForButtonMapping() { std::vector items; AddMenuDialogOption(items, 0, "-"s); @@ -809,24 +818,6 @@ std::vector makeOptionsFor3DSButtonMapping() { return items; } -std::vector makeOptionsForFrameskip() { - std::vector items; - AddMenuDialogOption(items, 0, "Disabled"s, ""s); - AddMenuDialogOption(items, 1, "Enabled (max 1 frame)"s, ""s); - AddMenuDialogOption(items, 2, "Enabled (max 2 frames)"s, ""s); - AddMenuDialogOption(items, 3, "Enabled (max 3 frames)"s, ""s); - AddMenuDialogOption(items, 4, "Enabled (max 4 frames)"s, ""s); - return items; -}; - -std::vector makeOptionsForCirclePad() { - std::vector items; - AddMenuDialogOption(items, 0, "Disabled"s, ""s); - AddMenuDialogOption(items, 1, "Enabled"s, ""s); - return items; -}; - - std::vector makeOptionsForFrameRate() { std::vector items; AddMenuDialogOption(items, static_cast(EmulatedFramerate::UseRomRegion), "Default based on Game region"s, ""s); @@ -858,14 +849,15 @@ std::vector makeOptionMenu(std::vector& menuTab, int& curre AddMenuHeader1(items, "GENERAL SETTINGS"s); AddMenuHeader2(items, "Video"s); - AddMenuPicker(items, " Scaling"s, "Change video scaling settings"s, makeOptionsForStretch(), settings3DS.ScreenStretch, DIALOGCOLOR_CYAN, true, + AddMenuPicker(items, " Scaling"s, "Change video scaling settings"s, makeOptionsForStretch(), settings3DS.ScreenStretch, DIALOG_TYPE_INFO, true, []( int val ) { CheckAndUpdate( settings3DS.ScreenStretch, val ); }); AddMenuDisabledOption(items, ""s); AddMenuHeader2(items, "On-Screen Display"s); int secondScreenPickerId = 1000; - AddMenuPicker(items, " Second Screen Content"s, "When selecting \"Game Cover\" make sure that image exists. If not, the default cover will be shown"s, makeOptionsforSecondScreen(), settings3DS.SecondScreenContent, DIALOGCOLOR_CYAN, true, + AddMenuPicker(items, " Second Screen Content"s, "When selecting \"Game Cover\" make sure that image exists. If not, the default cover will be shown"s, + makePickerOptions({"None", "Game Cover", "ROM Information"}), settings3DS.SecondScreenContent, DIALOG_TYPE_INFO, true, [secondScreenPickerId, &menuTab, ¤tMenuTab]( int val ) { if (CheckAndUpdate(settings3DS.SecondScreenContent, val)) { SMenuTab *currentTab = &menuTab[currentMenuTab]; @@ -879,7 +871,8 @@ std::vector makeOptionMenu(std::vector& menuTab, int& curre int gameBorderPickerId = 1500; - AddMenuPicker(items, " Game Border"s, "When selecting \"Game-specific\" make sure that image exists. If not, the border will remain black."s, makeOptionsforGameBorder(), settings3DS.GameBorder, DIALOGCOLOR_CYAN, true, + AddMenuPicker(items, " Game Border"s, "When selecting \"Game-specific\" make sure that image exists. If not, the border will remain black."s, + makePickerOptions({"None", "Default", "Game-Specific"}), settings3DS.GameBorder, DIALOG_TYPE_INFO, true, [gameBorderPickerId, &menuTab, ¤tMenuTab]( int val ) { if (CheckAndUpdate(settings3DS.GameBorder, val)) { SMenuTab *currentTab = &menuTab[currentMenuTab]; @@ -895,11 +888,12 @@ std::vector makeOptionMenu(std::vector& menuTab, int& curre AddMenuHeader1(items, "GAME-SPECIFIC SETTINGS"s); AddMenuHeader2(items, "Video"s); - AddMenuPicker(items, " Frameskip"s, "Try changing this if the game runs slow. Skipping frames helps it run faster, but less smooth."s, makeOptionsForFrameskip(), settings3DS.MaxFrameSkips, DIALOGCOLOR_CYAN, true, + AddMenuPicker(items, " Frameskip"s, "Try changing this if the game runs slow. Skipping frames helps it run faster, but less smooth."s, + makePickerOptions({"Disabled", "Enabled (max 1 frame)", "Enabled (max 2 frames)", "Enabled (max 3 frames)", "Enabled (max 4 frames)"}), settings3DS.MaxFrameSkips, DIALOG_TYPE_INFO, true, []( int val ) { CheckAndUpdate( settings3DS.MaxFrameSkips, val ); }); - AddMenuPicker(items, " Framerate"s, "Some games run at 50 or 60 FPS by default. Override if required."s, makeOptionsForFrameRate(), static_cast(settings3DS.ForceFrameRate), DIALOGCOLOR_CYAN, true, + AddMenuPicker(items, " Framerate"s, "Some games run at 50 or 60 FPS by default. Override if required."s, makeOptionsForFrameRate(), static_cast(settings3DS.ForceFrameRate), DIALOG_TYPE_INFO, true, []( int val ) { CheckAndUpdate( settings3DS.ForceFrameRate, static_cast(val) ); }); - AddMenuPicker(items, " In-Frame Palette Changes"s, "Try changing this if some colors in the game look off."s, makeOptionsForInFramePaletteChanges(), settings3DS.PaletteFix, DIALOGCOLOR_CYAN, true, + AddMenuPicker(items, " In-Frame Palette Changes"s, "Try changing this if some colors in the game look off."s, makeOptionsForInFramePaletteChanges(), settings3DS.PaletteFix, DIALOG_TYPE_INFO, true, []( int val ) { CheckAndUpdate( settings3DS.PaletteFix, val ); }); AddMenuDisabledOption(items, ""s); @@ -929,15 +923,14 @@ std::vector makeOptionMenu(std::vector& menuTab, int& curre AddMenuCheckbox(items, " Automatically save state on exit and load state on start"s, settings3DS.AutoSavestate, []( int val ) { CheckAndUpdate( settings3DS.AutoSavestate, val ); }); - AddMenuDisabledOption(items, " (creates an *.auto.frz file inside \"savestates\" directory)"s); + items.emplace_back(nullptr, MenuItemType::Textarea, " (creates an *.auto.frz file inside \"savestates\" directory)"s, ""s); - AddMenuPicker(items, " SRAM Auto-Save Delay"s, "Try 60 seconds or Disabled if the game saves SRAM to SD card too frequently."s, makeOptionsForAutoSaveSRAMDelay(), settings3DS.SRAMSaveInterval, DIALOGCOLOR_CYAN, true, + AddMenuPicker(items, " SRAM Auto-Save Delay"s, "Try 60 seconds or Disabled if the game saves SRAM to SD card too frequently."s, makeOptionsForAutoSaveSRAMDelay(), settings3DS.SRAMSaveInterval, DIALOG_TYPE_INFO, true, []( int val ) { CheckAndUpdate( settings3DS.SRAMSaveInterval, val ); }); AddMenuCheckbox(items, " Force SRAM Write on Pause"s, settings3DS.ForceSRAMWriteOnPause, []( int val ) { CheckAndUpdate( settings3DS.ForceSRAMWriteOnPause, val ); }); - AddMenuDisabledOption(items, " (some games like Yoshi's Island require this)"s); - AddMenuDisabledOption(items, ""s); + items.emplace_back(nullptr, MenuItemType::Textarea, " (some games like Yoshi's Island require this)"s, ""s); return items; }; @@ -963,7 +956,7 @@ std::vector makeControlsMenu(std::vector& menuTab, int& cur AddMenuHeader1(items, "EMULATOR INGAME FUNCTIONS"s); - AddMenuCheckbox(items, "Apply hotkey mappings to all games"s, settings3DS.UseGlobalEmuControlKeys, + AddMenuCheckbox(items, " Apply hotkey mappings to all games"s, settings3DS.UseGlobalEmuControlKeys, []( int val ) { CheckAndUpdate( settings3DS.UseGlobalEmuControlKeys, val ); @@ -982,7 +975,7 @@ std::vector makeControlsMenu(std::vector& menuTab, int& cur int hotkeyPickerGroupId = 2000; for (int i = 0; i < HOTKEYS_COUNT; ++i) { AddMenuPicker( items, hotkeysData[i][1], hotkeysData[i][2], makeOptionsFor3DSButtonMapping(), - settings3DS.UseGlobalEmuControlKeys ? settings3DS.GlobalButtonHotkeys[i].MappingBitmasks[0] : settings3DS.ButtonHotkeys[i].MappingBitmasks[0], DIALOGCOLOR_CYAN, true, + settings3DS.UseGlobalEmuControlKeys ? settings3DS.GlobalButtonHotkeys[i].MappingBitmasks[0] : settings3DS.ButtonHotkeys[i].MappingBitmasks[0], DIALOG_TYPE_INFO, true, [i]( int val ) { uint32 v = static_cast(val); if (settings3DS.UseGlobalEmuControlKeys) @@ -996,7 +989,7 @@ std::vector makeControlsMenu(std::vector& menuTab, int& cur AddMenuDisabledOption(items, ""s); AddMenuHeader1(items, "BUTTON CONFIGURATION"s); - AddMenuCheckbox(items, "Apply button mappings to all games"s, settings3DS.UseGlobalButtonMappings, + AddMenuCheckbox(items, " Apply button mappings to all games"s, settings3DS.UseGlobalButtonMappings, []( int val ) { CheckAndUpdate( settings3DS.UseGlobalButtonMappings, val ); @@ -1015,7 +1008,7 @@ std::vector makeControlsMenu(std::vector& menuTab, int& cur } }); - AddMenuCheckbox(items, "Apply rapid fire settings to all games"s, settings3DS.UseGlobalTurbo, + AddMenuCheckbox(items, " Apply rapid fire settings to all games"s, settings3DS.UseGlobalTurbo, []( int val ) { CheckAndUpdate( settings3DS.UseGlobalTurbo, val ); @@ -1033,7 +1026,7 @@ std::vector makeControlsMenu(std::vector& menuTab, int& cur AddMenuHeader2(items, ""); AddMenuHeader2(items, "Analog to Digital Type"s); AddMenuPicker(items, " Bind Circle Pad to D-Pad"s, "You might disable this option if you're only using the D-Pad for gaming. Circle Pad directions will be available for hotkeys after unbinding."s, - makeOptionsForCirclePad(), settings3DS.UseGlobalButtonMappings ? settings3DS.GlobalBindCirclePad : settings3DS.BindCirclePad, DIALOGCOLOR_CYAN, true, + makePickerOptions({"Disabled", "Enabled"}), settings3DS.UseGlobalButtonMappings ? settings3DS.GlobalBindCirclePad : settings3DS.BindCirclePad, DIALOG_TYPE_INFO, true, [hotkeyPickerGroupId, &closeMenu, &menuTab, ¤tMenuTab]( int val ) { if (CheckAndUpdate(settings3DS.UseGlobalButtonMappings ? settings3DS.GlobalBindCirclePad : settings3DS.BindCirclePad, val)) { SMenuTab *currentTab = &menuTab[currentMenuTab]; @@ -1065,7 +1058,7 @@ std::vector makeControlsMenu(std::vector& menuTab, int& cur AddMenuPicker( items, optionName.str(), ""s, makeOptionsForButtonMapping(), settings3DS.UseGlobalButtonMappings ? settings3DS.GlobalButtonMapping[i][j] : settings3DS.ButtonMapping[i][j], - DIALOGCOLOR_CYAN, true, + DIALOG_TYPE_INFO, true, [i, j]( int val ) { if (settings3DS.UseGlobalButtonMappings) CheckAndUpdate( settings3DS.GlobalButtonMapping[i][j], val ); @@ -1143,10 +1136,6 @@ bool settingsUpdateAllSettings(bool updateGameSettings = true) settings3DS.CropPixels = 0; } - // Update the screen font - // - ui3dsSetFont(settings3DS.Font); - if (updateGameSettings) { // Update frame rate @@ -1320,6 +1309,7 @@ bool settingsReadWriteFullListGlobal(bool writeMode) config3dsReadWriteInt32(stream, writeMode, "GameScreen=%d\n", &screen, 0, 1); screenSettings.GameScreen = static_cast(screen); settings3DS.GameScreen = screenSettings.GameScreen; + config3dsReadWriteInt32(stream, writeMode, "Theme=%d\n", &settings3DS.Theme, 0, TOTALTHEMECOUNT - 1); config3dsReadWriteInt32(stream, writeMode, "GameThumbnailType=%d\n", &settings3DS.GameThumbnailType, 0, 3); config3dsReadWriteInt32(stream, writeMode, "ScreenStretch=%d\n", &settings3DS.ScreenStretch, 0, 7); config3dsReadWriteInt32(stream, writeMode, "SecondScreenContent=%d\n", &settings3DS.SecondScreenContent, 0, 2); @@ -1328,11 +1318,12 @@ bool settingsReadWriteFullListGlobal(bool writeMode) config3dsReadWriteInt32(stream, writeMode, "GameBorderOpacity=%d\n", &settings3DS.GameBorderOpacity, 1, OPACITY_STEPS); config3dsReadWriteInt32(stream, writeMode, "Disable3DSlider=%d\n", &settings3DS.Disable3DSlider, 0, 1); config3dsReadWriteInt32(stream, writeMode, "Font=%d\n", &settings3DS.Font, 0, 2); - + // Fixes the bug where we have spaces in the directory name - config3dsReadWriteString(stream, writeMode, "Dir=%s\n", "Dir=%1000[^\n]\n", file3dsGetCurrentDir()); - config3dsReadWriteString(stream, writeMode, "ROM=%s\n", "ROM=%1000[^\n]\n", romFileNameLastSelected); - + config3dsReadWriteString(stream, writeMode, "DefaultDir=%s\n", "DefaultDir=%1000[^\n]\n", settings3DS.defaultDir); + config3dsReadWriteString(stream, writeMode, "LastSelectedDir=%s\n", "LastSelectedDir=%1000[^\n]\n", settings3DS.lastSelectedDir); + config3dsReadWriteString(stream, writeMode, "LastSelectedFilename=%s\n", "LastSelectedFilename=%1000[^\n]\n", settings3DS.lastSelectedFilename); + config3dsReadWriteInt32(stream, writeMode, "Vol=%d\n", &settings3DS.GlobalVolume, 0, 8); config3dsReadWriteInt32(stream, writeMode, "GlobalBindCirclePad=%d\n", &settings3DS.GlobalBindCirclePad, 0, 1); @@ -1459,10 +1450,14 @@ bool emulatorLoadRom() if(loaded) { - // always override last tab position when rom has been loaded - // because tab indices have been changed - menu3dsSetLastTabPosition(0, 1); - + // reset tab states and select first tab + menu3dsClearLastSelectedIndicesByTab(); + menu3dsSetLastSelectedTabIndex(0); + + // when rom has been loaded, store current rom directory and filename in config + strncpy(settings3DS.lastSelectedDir, file3dsGetCurrentDir(), _MAX_PATH); + strncpy(settings3DS.lastSelectedFilename, romFileName, _MAX_PATH); + snd3DS.generateSilence = true; settingsSave(false); @@ -1487,34 +1482,26 @@ bool emulatorLoadRom() return true; } - return false; - + return false; } - //---------------------------------------------------------------------- -// Load all ROM file names +// Find the ID of the last selected item in the file list. //---------------------------------------------------------------------- -void fileGetAllFiles(std::vector& romFileNames) +int findLastSelected(std::vector& romFileNames, const char* name) { - file3dsGetFiles(romFileNames, {".smc", ".sfc", ".fig"}); -} - + if (name == nullptr || name[0] == '\0') { + return -1; + } -//---------------------------------------------------------------------- -// Find the ID of the last selected file in the file list. -//---------------------------------------------------------------------- -int fileFindLastSelectedFile(std::vector& fileMenu) -{ - for (int i = 0; i < fileMenu.size() && i < 1000; i++) + for (int i = 0; i < romFileNames.size() && i < 1000; i++) { - if (strncmp(fileMenu[i].Text.c_str(), romFileNameLastSelected, _MAX_PATH) == 0) + if (strncmp(romFileNames[i].Filename.c_str(), name, _MAX_PATH) == 0) return i; } return -1; } - //---------------------------------------------------------------------- // Handle menu cheats. //---------------------------------------------------------------------- @@ -1546,7 +1533,7 @@ bool menuCopyCheats(std::vector& cheatMenu, bool copyMenuToSettings) } } - cheatMenu[i+1].Text = Cheat.c[i].name; + cheatMenu[i+1].Text = " " + std::string(Cheat.c[i].name); cheatMenu[i+1].Description = Cheat.c[i].cheat_code; cheatMenu[i+1].Type = MenuItemType::Checkbox; @@ -1576,46 +1563,32 @@ void fillFileMenuFromFileNames(std::vector& fileMenu, const std::vect for (size_t i = 0; i < romFileNames.size(); ++i) { const DirectoryEntry& entry = romFileNames[i]; - fileMenu.emplace_back( [&entry, &selectedEntry]( int val ) { - selectedEntry = &entry; - }, MenuItemType::Action, entry.Filename, ""s, 99999); - } -} + std::string prefix; -//---------------------------------------------------------------------- -// Start up menu. -//---------------------------------------------------------------------- -void setupBootupMenu(std::vector& menuTab, std::vector& romFileNames, const DirectoryEntry*& selectedDirectoryEntry, bool selectPreviousFile) { - menuTab.clear(); - menuTab.reserve(2); - - int currentMenuTab; - bool closeMenu; - - { - menu3dsAddTab(menuTab, "Emulator", makeEmulatorMenu(menuTab, currentMenuTab, closeMenu)); - menuTab.back().SubTitle.clear(); - } - - { - std::vector fileMenu; - fileGetAllFiles(romFileNames); - fillFileMenuFromFileNames(fileMenu, romFileNames, selectedDirectoryEntry); - menu3dsAddTab(menuTab, "Load Game", fileMenu); - menuTab.back().SubTitle.assign(file3dsGetCurrentDir()); - if (selectPreviousFile) { - int previousFileID = fileFindLastSelectedFile(menuTab.back().MenuItems); - menu3dsSetSelectedItemByIndex(menuTab.back(), previousFileID); + switch (entry.Type) { + case FileEntryType::ChildDirectory: + prefix = " \x01 "; + break; + case FileEntryType::ParentDirectory: + prefix = ""; + break; + default: + prefix = " "; + break; } + + fileMenu.emplace_back( [&entry, &selectedEntry]( int val ) { + selectedEntry = &entry; + }, MenuItemType::Action, prefix + entry.Filename, ""s, 99999); } } -// show saving process dialog, because writing to sd card seems to be quite slow on 3ds +// show saving process dialog, because writing to sd card tends to be slow on 3ds bool saveCurrentSettings(SMenuTab& dialogTab, bool& isDialog, int& currentMenuTab, std::vector& menuTab, bool includeGameSettings, bool includeCheatSettings = false) { double minWaitTimeInSeconds = 0.5; long startFrameTick = svcGetSystemTick(); - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Settings changed", "Saving to SD card..", DIALOGCOLOR_CYAN, std::vector()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Settings changed", "Saving to SD card..", Themes[settings3DS.Theme].dialogColorInfo, std::vector()); bool settingsSaved = settingsSave(includeGameSettings); // save cheat settings if changed @@ -1644,14 +1617,99 @@ bool saveCurrentSettings(SMenuTab& dialogTab, bool& isDialog, int& currentMenuTa return settingsSaved; } +void setupMenu(std::vector& menuTab, std::vector& romFileNames, const DirectoryEntry*& selectedDirectoryEntry, int& currentMenuTab, bool& closeMenu, bool isPauseMenu) { + menuTab.clear(); + menuTab.reserve(isPauseMenu ? 5 : 2); + menu3dsAddTab(menuTab, "Emulator", makeEmulatorMenu(menuTab, currentMenuTab, closeMenu, isPauseMenu)); + menuTab.back().SubTitle.clear(); + + if (!isPauseMenu) { + char startDir[_MAX_PATH]; + strncpy(startDir, (strcmp(settings3DS.defaultDir, "/") != 0) ? settings3DS.defaultDir : settings3DS.lastSelectedDir, _MAX_PATH); + bool success = file3dsGetFiles(romFileNames, {".smc", ".sfc", ".fig"}, startDir); + + if (success) { + int selectedItemIndex = findLastSelected(romFileNames, settings3DS.lastSelectedFilename); + menu3dsSetLastSelectedIndexByTab("Load Game", selectedItemIndex); + } else { + // if getFiles failed (e.g. stored directory has been removed), reset default directory and try again with root directory + strncpy(settings3DS.defaultDir, "/", _MAX_PATH); + file3dsGetFiles(romFileNames, {".smc", ".sfc", ".fig"}, "/"); + } + } else { + menu3dsAddTab(menuTab, "Settings", makeOptionMenu(menuTab, currentMenuTab, closeMenu)); + menuTab.back().SubTitle.clear(); + menu3dsAddTab(menuTab, "Controls", makeControlsMenu(menuTab, currentMenuTab, closeMenu)); + menuTab.back().SubTitle.clear(); + menu3dsAddTab(menuTab, "Cheats", makeCheatMenu()); + menuTab.back().SubTitle.clear(); + } + + std::vector fileMenu; + fillFileMenuFromFileNames(fileMenu, romFileNames, selectedDirectoryEntry); + menu3dsAddTab(menuTab, "Load Game", fileMenu); + menuTab.back().SubTitle.assign(file3dsGetCurrentDir()); + + for (int i = 0; i < menuTab.size(); i++) { + int lastSelectedItemIndex = menu3dsGetLastSelectedIndexByTab(menuTab[i].Title); + menu3dsSetSelectedItemByIndex(menuTab[i], lastSelectedItemIndex); + } +} + +void updateFileMenuTab(std::vector& menuTab, std::vector& romFileNames, const DirectoryEntry*& selectedDirectoryEntry, const std::string& lastSubDirectory) { + menuTab.pop_back(); + std::vector fileMenu; + + file3dsGetFiles(romFileNames, {".smc", ".sfc", ".fig"}, NULL); + fillFileMenuFromFileNames(fileMenu, romFileNames, selectedDirectoryEntry); + menu3dsAddTab(menuTab, "Load Game", fileMenu); + + SMenuTab& fileMenuTab = menuTab.back(); + fileMenuTab.SubTitle.assign(file3dsGetCurrentDir()); + + if (!lastSubDirectory.empty()) { + int selectedItemIndex = findLastSelected(romFileNames, lastSubDirectory.c_str()); + menu3dsSetSelectedItemByIndex(fileMenuTab, selectedItemIndex); + } else { + menu3dsSetSelectedItemByIndex(fileMenuTab, 0); + } +} + +int showFileMenuOptions(SMenuTab& dialogTab, bool& isDialog, int& currentMenuTab, std::vector& menuTab) { + int option = menu3dsShowDialog( + dialogTab, isDialog, currentMenuTab, menuTab, + "File Menu Options", + "If no default directory is set, the file menu will show the directory of the last selected game."s, + Themes[settings3DS.Theme].dialogColorInfo, + makeOptionsForFileMenu({"Set current directory as default", "Reset default directory", "Select random game in current directory" })); + + menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); + + if (option == 0) { + strncpy(settings3DS.defaultDir, file3dsGetCurrentDir(), _MAX_PATH); + } + + if (option == 1) { + strncpy(settings3DS.defaultDir, "/", _MAX_PATH); + } + + if (option == 2) { + menu3dsSelectRandomGame(&menuTab[currentMenuTab]); + } + + return option; +} + void menuSelectFile(void) { S9xSettings3DS prevSettings3DS = settings3DS; + std::vector menuTab; const DirectoryEntry* selectedDirectoryEntry = nullptr; - setupBootupMenu(menuTab, romFileNames, selectedDirectoryEntry, true); - int currentMenuTab = 1; + bool closeMenu = false; + setupMenu(menuTab, romFileNames, selectedDirectoryEntry, currentMenuTab, closeMenu, false); + bool isDialog = false; bool romLoaded = false; SMenuTab dialogTab; @@ -1660,26 +1718,32 @@ void menuSelectFile(void) menu3dsSetTransferGameScreen(false); while (aptMainLoop() && GPU3DS.emulatorState != EMUSTATE_END) { - menu3dsShowMenu(dialogTab, isDialog, currentMenuTab, menuTab); + int result = menu3dsShowMenu(dialogTab, isDialog, currentMenuTab, menuTab); + // user pressed X button in file menu + if (result == FILE_MENU_SHOW_OPTIONS) { + showFileMenuOptions(dialogTab, isDialog, currentMenuTab, menuTab); + } + if (selectedDirectoryEntry) { if (selectedDirectoryEntry->Type == FileEntryType::File) { strncpy(romFileName, selectedDirectoryEntry->Filename.c_str(), _MAX_PATH); - strncpy(romFileNameLastSelected, romFileName, _MAX_PATH); - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Loading Game:", file3dsGetFileBasename(romFileName, false).c_str(), DIALOGCOLOR_CYAN, std::vector()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Loading Game:", file3dsGetFileBasename(romFileName, false).c_str(), Themes[settings3DS.Theme].dialogColorInfo, std::vector()); romLoaded = emulatorLoadRom(); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); if (!romLoaded) { - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Load Game", "Oops. Unable to load Game", DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Load Game", "Oops. Unable to load Game", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); } else { break; } } else if (selectedDirectoryEntry->Type == FileEntryType::ParentDirectory || selectedDirectoryEntry->Type == FileEntryType::ChildDirectory) { + std::string lastSubDirectory = selectedDirectoryEntry->Type == FileEntryType::ParentDirectory ? file3dsGetCurrentDirName() : ""; file3dsGoUpOrDownDirectory(*selectedDirectoryEntry); - setupBootupMenu(menuTab, romFileNames, selectedDirectoryEntry, false); + updateFileMenuTab(menuTab, romFileNames, selectedDirectoryEntry, lastSubDirectory); } + selectedDirectoryEntry = nullptr; } } @@ -1699,48 +1763,6 @@ void menuSelectFile(void) } } - -//---------------------------------------------------------------------- -// Menu when the emulator is paused in-game. -//---------------------------------------------------------------------- -void setupPauseMenu(std::vector& menuTab, std::vector& romFileNames, const DirectoryEntry*& selectedDirectoryEntry, bool selectPreviousFile, int& currentMenuTab, bool& closeMenu, bool refreshFileList) { - menuTab.clear(); - menuTab.reserve(4); - - { - menu3dsAddTab(menuTab, "Emulator", makeEmulatorMenu(menuTab, currentMenuTab, closeMenu, true)); - menuTab.back().SubTitle.clear(); - } - - { - menu3dsAddTab(menuTab, "Options", makeOptionMenu(menuTab, currentMenuTab, closeMenu)); - menuTab.back().SubTitle.clear(); - } - - { - menu3dsAddTab(menuTab, "Controls", makeControlsMenu(menuTab, currentMenuTab, closeMenu)); - menuTab.back().SubTitle.clear(); - } - - { - menu3dsAddTab(menuTab, "Cheats", makeCheatMenu()); - menuTab.back().SubTitle.clear(); - } - - { - std::vector fileMenu; - if (refreshFileList) - fileGetAllFiles(romFileNames); - fillFileMenuFromFileNames(fileMenu, romFileNames, selectedDirectoryEntry); - menu3dsAddTab(menuTab, "Load Game", fileMenu); - menuTab.back().SubTitle.assign(file3dsGetCurrentDir()); - if (selectPreviousFile) { - int previousFileID = fileFindLastSelectedFile(menuTab.back().MenuItems); - menu3dsSetSelectedItemByIndex(menuTab.back(), previousFileID); - } - } -} - void menuPause() { S9xSettings3DS prevSettings3DS = settings3DS; @@ -1748,17 +1770,12 @@ void menuPause() gspWaitForVBlank(); gfxSetScreenFormat(screenSettings.SecondScreen, GSP_RGB565_OES); - int currentMenuTab; - int lastItemIndex; - menu3dsGetLastTabPosition(currentMenuTab, lastItemIndex); - + int currentMenuTab = menu3dsGetLastSelectedTabIndex(); bool closeMenu = false; std::vector menuTab; const DirectoryEntry* selectedDirectoryEntry = nullptr; - setupPauseMenu(menuTab, romFileNames, selectedDirectoryEntry, true, currentMenuTab, closeMenu, false); - - menu3dsSetSelectedItemByIndex(menuTab[currentMenuTab], lastItemIndex); + setupMenu(menuTab, romFileNames, selectedDirectoryEntry, currentMenuTab, closeMenu, true); bool isDialog = false; SMenuTab dialogTab; @@ -1773,22 +1790,29 @@ void menuPause() menu3dsSetCheatsIndicator(cheatMenu); while (aptMainLoop() && !closeMenu && GPU3DS.emulatorState != EMUSTATE_END) { - if (menu3dsShowMenu(dialogTab, isDialog, currentMenuTab, menuTab) == -1) { - // user pressed B, close menu + int result = menu3dsShowMenu(dialogTab, isDialog, currentMenuTab, menuTab); + + // user pressed START button + if (result == -1) { closeMenu = true; } + // user pressed X button in file menu + if (result == FILE_MENU_SHOW_OPTIONS) { + showFileMenuOptions(dialogTab, isDialog, currentMenuTab, menuTab); + } + if (selectedDirectoryEntry) { // Load ROM if (selectedDirectoryEntry->Type == FileEntryType::File) { bool loadRom = true; if (settings3DS.AutoSavestate) { - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Save State", "Autosaving...", DIALOGCOLOR_CYAN, std::vector()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Save State", "Autosaving...", Themes[settings3DS.Theme].dialogColorInfo, std::vector()); bool result = impl3dsSaveStateAuto(); - menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); + //menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); if (!result) { - int choice = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Autosave failure", "Automatic savestate writing failed.\nLoad chosen game anyway?", DIALOGCOLOR_RED, makeOptionsForNoYes()); + int choice = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Autosave failure", "Automatic savestate writing failed.\nLoad chosen game anyway?", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForNoYes()); if (choice != 1) { loadRom = false; } @@ -1797,15 +1821,16 @@ void menuPause() if (loadRom) { strncpy(romFileName, selectedDirectoryEntry->Filename.c_str(), _MAX_PATH); - strncpy(romFileNameLastSelected, romFileName, _MAX_PATH); - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Loading Game:", file3dsGetFileBasename(romFileName, false).c_str(), DIALOGCOLOR_CYAN, std::vector()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Loading Game:", file3dsGetFileBasename(romFileName, false).c_str(), Themes[settings3DS.Theme].dialogColorInfo, std::vector()); loadRomBeforeExit = true; break; } - } else if (selectedDirectoryEntry->Type == FileEntryType::ParentDirectory || selectedDirectoryEntry->Type == FileEntryType::ChildDirectory) { + } else if (selectedDirectoryEntry->Type == FileEntryType::ParentDirectory || selectedDirectoryEntry->Type == FileEntryType::ChildDirectory) { + std::string lastSubDirectory = selectedDirectoryEntry->Type == FileEntryType::ParentDirectory ? file3dsGetCurrentDirName() : ""; file3dsGoUpOrDownDirectory(*selectedDirectoryEntry); - setupPauseMenu(menuTab, romFileNames, selectedDirectoryEntry, false, currentMenuTab, closeMenu, true); + updateFileMenuTab(menuTab, romFileNames, selectedDirectoryEntry, lastSubDirectory); } + selectedDirectoryEntry = nullptr; } } @@ -1836,6 +1861,7 @@ void menuPause() settingsUpdateAllSettings(); menu3dsHideMenu(dialogTab, isDialog, currentMenuTab, menuTab); + // continue current game if (closeMenu && GPU3DS.emulatorState != EMUSTATE_END) { GPU3DS.emulatorState = EMUSTATE_EMULATE; @@ -1849,7 +1875,7 @@ void menuPause() menu3dsSetSecondScreenContent(NULL); if (slotLoaded) { - menu3dsSetSecondScreenContent(message); + menu3dsSetSecondScreenContent(message, Themes[settings3DS.Theme].dialogColorSuccess); gfxScreenSwapBuffers(screenSettings.SecondScreen, false); } @@ -1857,12 +1883,13 @@ void menuPause() impl3dsSetBorderImage(); } + // load new game if (loadRomBeforeExit) { bool romLoaded = emulatorLoadRom(); if (!romLoaded) { menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); - menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Load Game", "Oops. Unable to load Game", DIALOGCOLOR_RED, makeOptionsForOk()); + menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, "Load Game", "Oops. Unable to load Game", Themes[settings3DS.Theme].dialogColorWarn, makeOptionsForOk()); menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); menuPause(); } else { @@ -1884,7 +1911,7 @@ void menuSetupCheats(std::vector& cheatMenu) AddMenuHeader1(cheatMenu, ""s); for (uint32 i = 0; i < MAX_CHEATS && i < Cheat.num_cheats; i++) { - cheatMenu.emplace_back(nullptr, MenuItemType::Checkbox, std::string(Cheat.c[i].name), std::string(Cheat.c[i].cheat_code), Cheat.c[i].enabled ? 1 : 0); + cheatMenu.emplace_back(nullptr, MenuItemType::Checkbox, " " + std::string(Cheat.c[i].name), std::string(Cheat.c[i].cheat_code), Cheat.c[i].enabled ? 1 : 0); } } else { @@ -1913,7 +1940,7 @@ void menuSetupCheats(std::vector& cheatMenu) void emulatorInitialize() { file3dsInitialize(); - romFileNameLastSelected[0] = 0; + menu3dsSetHotkeysData(hotkeysData); settingsLoad(false); ui3dsUpdateScreenSettings(screenSettings.GameScreen); @@ -1939,6 +1966,7 @@ void emulatorInitialize() } ui3dsInitialize(); + ui3dsSetFont(settings3DS.Font); Result rc = romfsInit(); @@ -2041,7 +2069,7 @@ void updateSecondScreenContent() if (ui3dsGetSecondScreenDialogState() == HIDDEN) { float alpha = (float)(settings3DS.SecondScreenOpacity) / OPACITY_STEPS; gfxSetDoubleBuffering(screenSettings.SecondScreen, false); - menu3dsSetFpsInfo(framesSkippedCount ? DIALOGCOLOR_RED : 0xFFFFFF, alpha, frameCountBuffer); + menu3dsSetFpsInfo(framesSkippedCount ? Themes[settings3DS.Theme].dialogColorWarn : 0xFFFFFF, alpha, frameCountBuffer); } } diff --git a/source/3dsmenu.cpp b/source/3dsmenu.cpp index 56b2ace..70efbd0 100644 --- a/source/3dsmenu.cpp +++ b/source/3dsmenu.cpp @@ -21,16 +21,23 @@ bool transferGameScreen = false; int transferGameScreenCount = 0; bool swapBuffer = true; -int lastSelectedMenuTab = 1; -int lastSelectedItemIndex = 0; -int selectedRomItemIndex = 0; + int cheatsActive = 0; int cheatsAll = 0; int lastPercent = 0; +int lastSelectedTabIndex = 0; int currentPercent = 0; u8* tempPixelData; +std::unordered_map selectedItemIndices; + +MenuButton bottomMenuButtons[] = { + {"Select", "\x0cc", 0x800d1d}, + {"Back", "\x0cd", 0x999409}, + {"Options", "\x0ce", 0x0d5280}, + {"Page \x0d1", "\x0cf", 0x0d8014} +}; void menu3dsSetCurrentPercent(int current, int total) { // reset state @@ -82,18 +89,30 @@ int menu3dsGetCurrentPercent() { return currentPercent; } -void menu3dsSetLastTabPosition(int currentMenuTab, int index) { - lastSelectedMenuTab = currentMenuTab; - lastSelectedItemIndex = index; +int menu3dsGetLastSelectedTabIndex() { + return lastSelectedTabIndex; } -void menu3dsGetLastTabPosition(int& currentMenuTab, int& lastItemIndex) { - currentMenuTab = lastSelectedMenuTab; - lastItemIndex = lastSelectedItemIndex; +void menu3dsSetLastSelectedTabIndex(int index) { + lastSelectedTabIndex = index; } -int menu3dsGetLastRomItemIndex() { - return selectedRomItemIndex; +void menu3dsSetLastSelectedIndexByTab(const std::string& tab, int menuItemIndex) { + selectedItemIndices[tab] = menuItemIndex; +} + +int menu3dsGetLastSelectedIndexByTab(const std::string& tab) { + if (selectedItemIndices.find(tab) != selectedItemIndices.end()) { + return selectedItemIndices[tab]; + } + + return -1; +} + +void menu3dsClearLastSelectedIndicesByTab() { + for (const auto& pair : selectedItemIndices) { + selectedItemIndices[pair.first] = -1; + } } //------------------------------------------------------- @@ -182,13 +201,13 @@ void menu3dsDrawItems( SMenuTab *currentTab, int horizontalPadding, int menuStartY, int maxItems, int selectedItemBackColor, int selectedItemTextColor, - int selectedItemDescriptionTextColor, - int checkedItemTextColor, + int selectedItemDescriptionTextColor, int normalItemTextColor, int normalItemDescriptionTextColor, int disabledItemTextColor, int headerItemTextColor, - int subtitleTextColor) + int subtitleTextColor, + int offsetX = 0) { int fontHeight = 13; @@ -196,13 +215,14 @@ void menu3dsDrawItems( if (!currentTab->SubTitle.empty()) { maxItems--; - ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, 20, menuStartY, 300, menuStartY + fontHeight, + ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, horizontalPadding, menuStartY, screenSettings.SecondScreenWidth - horizontalPadding, menuStartY + fontHeight, subtitleTextColor, HALIGN_LEFT, currentTab->SubTitle.c_str()); + ui3dsDrawRect(horizontalPadding, menuStartY + fontHeight - 1, screenSettings.SecondScreenWidth - horizontalPadding, menuStartY + fontHeight, subtitleTextColor); menuStartY += fontHeight; } int line = 0; - int color = 0xffffff; + int color = Themes[settings3DS.Theme].selectedTabTextColor; // Draw all the individual items // @@ -215,7 +235,14 @@ void menu3dsDrawItems( // if (currentTab->SelectedItemIndex == i) { - ui3dsDrawRect(0, y, screenSettings.SecondScreenWidth, y + 14, selectedItemBackColor); + if (selectedItemBackColor != -1) { + ui3dsDrawRect(0, y, screenSettings.SecondScreenWidth, y + 14, selectedItemBackColor); + } + + if (settings3DS.Theme == THEME_RETROARCH && currentTab->MenuItems[i].IsHighlightable()) { + int xi = horizontalPadding - offsetX; + ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, xi, y, xi + 10, y + 14, selectedItemTextColor, HALIGN_LEFT, ">"); + } } if (currentTab->MenuItems[i].Type == MenuItemType::Header1) @@ -231,10 +258,9 @@ void menu3dsDrawItems( } else if (currentTab->MenuItems[i].Type == MenuItemType::Textarea) { - color = normalItemTextColor; - int lines = 15; // TODO: set value based on content - horizontalPadding = 10; - ui3dsDrawStringWithWrapping(screenSettings.SecondScreen, horizontalPadding, y, screenSettings.SecondScreenWidth - horizontalPadding, y + fontHeight * lines, color, HALIGN_LEFT, currentTab->MenuItems[i].Text.c_str()); + color = normalItemDescriptionTextColor; + int maxLines = 15; // TODO: set value based on content + ui3dsDrawStringWithWrapping(screenSettings.SecondScreen, horizontalPadding, y, screenSettings.SecondScreenWidth - horizontalPadding, y + fontHeight * maxLines, color, HALIGN_LEFT, currentTab->MenuItems[i].Text.c_str()); } else if (currentTab->MenuItems[i].Type == MenuItemType::Disabled) { @@ -246,13 +272,12 @@ void menu3dsDrawItems( color = normalItemTextColor; if (currentTab->SelectedItemIndex == i) color = selectedItemTextColor; + ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, horizontalPadding, y, screenSettings.SecondScreenWidth - horizontalPadding, y + fontHeight, color, HALIGN_LEFT, currentTab->MenuItems[i].Text.c_str()); - // descriptionTextColor is currently used in dialogs - // we also use it for our Game thumbnail picker variant but with a different color - color = currentTab->MenuItems[i].Text == " Game Thumbnail" ? normalItemTextColor : normalItemDescriptionTextColor; + color = normalItemDescriptionTextColor; if (currentTab->SelectedItemIndex == i) - color = currentTab->MenuItems[i].Text == " Game Thumbnail" ? selectedItemTextColor : selectedItemDescriptionTextColor; + color = selectedItemDescriptionTextColor; if (!currentTab->MenuItems[i].Description.empty()) { ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, horizontalPadding, y, screenSettings.SecondScreenWidth - horizontalPadding, y + fontHeight, color, HALIGN_RIGHT, currentTab->MenuItems[i].Description.c_str()); @@ -308,7 +333,12 @@ void menu3dsDrawItems( char gauge[max+1]; for (int j = 0; j < max; j++) - gauge[j] = (j == pos) ? '\xfa' : '\xfb'; + if (j == pos) { + gauge[j] = settings3DS.Theme == THEME_ORIGINAL ? '\xfa' : '\xfc'; + } else { + gauge[j] = '\xfb'; + } + gauge[max] = 0; ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, 245, y, screenSettings.SecondScreenWidth - horizontalPadding, y + fontHeight, color, HALIGN_RIGHT, gauge); } @@ -344,14 +374,14 @@ void menu3dsDrawItems( // Draw the "up arrow" to indicate more options available at top // - if (currentTab->FirstItemIndex != 0) + if (settings3DS.Theme == THEME_ORIGINAL && currentTab->FirstItemIndex != 0) { ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, screenSettings.SecondScreenWidth - horizontalPadding, menuStartY, screenSettings.SecondScreenWidth, menuStartY + fontHeight, disabledItemTextColor, HALIGN_CENTER, "\xf8"); } // Draw the "down arrow" to indicate more options available at bottom // - if (currentTab->FirstItemIndex + maxItems < currentTab->MenuItems.size()) + if (settings3DS.Theme == THEME_ORIGINAL && currentTab->FirstItemIndex + maxItems < currentTab->MenuItems.size()) { ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, screenSettings.SecondScreenWidth - horizontalPadding, menuStartY + (maxItems - 1) * fontHeight, screenSettings.SecondScreenWidth, menuStartY + maxItems * fontHeight, disabledItemTextColor, HALIGN_CENTER, "\xf9"); } @@ -364,18 +394,42 @@ void menu3dsDrawMenu(std::vector& menuTab, int& currentMenuTab, int me { SMenuTab *currentTab = &menuTab[currentMenuTab]; - // Draw the flat background - // - ui3dsDrawRect(0, 0, screenSettings.SecondScreenWidth, 24, 0x1976D2); - ui3dsDrawRect(0, 24, screenSettings.SecondScreenWidth, 220, 0xFFFFFF); - ui3dsDrawRect(0, 220, screenSettings.SecondScreenWidth, SCREEN_HEIGHT, 0x1976D2); + // Draw the background + if (settings3DS.Theme != THEME_RETROARCH) { + ui3dsDrawRect(0, 0, screenSettings.SecondScreenWidth, 24, Themes[settings3DS.Theme].menuTopBarColor); + ui3dsDrawRect(0, 24, screenSettings.SecondScreenWidth, 220, Themes[settings3DS.Theme].menuBackColor); + ui3dsDrawRect(0, 220, screenSettings.SecondScreenWidth, SCREEN_HEIGHT, Themes[settings3DS.Theme].menuBottomBarColor); + } else { + // draw checkerboard background for retroarch theme + int cb1 = Themes[settings3DS.Theme].menuBackColor; + int cb2 = ui3dsOverlayBlendColor(cb1, 0xededed); + ui3dsDrawCheckerboard(0, 0, screenSettings.SecondScreenWidth, SCREEN_HEIGHT, cb1, cb2); + + // draw frame + int cwidth = 4; + int cx0 = 8; + int cy0 = 20; + int cx1 = screenSettings.SecondScreenWidth - cx0; + int cy1 = 222; + + int cf1 = ui3dsOverlayBlendColor(cb1, Themes[settings3DS.Theme].accentColor); + int cf2 = ui3dsOverlayBlendColor(cb2, Themes[settings3DS.Theme].accentColor); + + // horizontal + ui3dsDrawCheckerboard(cx0, cy0, cx1, cy0 + cwidth, cf1, cf2); + ui3dsDrawCheckerboard(cx0, cy1 - cwidth, cx1, cy1, cf1, cf2); + + // vertical + ui3dsDrawCheckerboard(cx0, cy0 + cwidth, cx0 + cwidth, cy1 - cwidth, cf1, cf2); + ui3dsDrawCheckerboard(cx1 - cwidth, cy0 + cwidth, cx1, cy1 - cwidth, cf1, cf2); + } // Draw the tabs at the top // for (int i = 0; i < static_cast(menuTab.size()); i++) { - int color = i == currentMenuTab ? 0xFFFFFF : 0x90CAF9; - int accentColor = i == currentMenuTab ? 0xFF9A01 : 0x998A5E; + int color = i == currentMenuTab ? Themes[settings3DS.Theme].selectedTabTextColor : Themes[settings3DS.Theme].tabTextColor; + int accentColor = i == currentMenuTab ? Themes[settings3DS.Theme].accentColor : Themes[settings3DS.Theme].accentUnselectedColor; int offsetLeft = 10; int offsetRight = 10; @@ -394,8 +448,8 @@ void menu3dsDrawMenu(std::vector& menuTab, int& currentMenuTab, int me ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, xLeft, yTextTop, xRight, yCurrentTabBoxTop, color, HALIGN_CENTER, menuTab[i].Title.c_str()); - if (i == currentMenuTab) { - ui3dsDrawRect(xLeft, yCurrentTabBoxTop, xRight, yCurrentTabBoxBottom, 0xFFFFFF); + if (i == currentMenuTab && Themes[settings3DS.Theme].selectedTabIndicatorColor != -1) { + ui3dsDrawRect(xLeft, yCurrentTabBoxTop, xRight, yCurrentTabBoxBottom, Themes[settings3DS.Theme].selectedTabIndicatorColor); } // draw indicator when game has (active) cheats @@ -423,35 +477,22 @@ void menu3dsDrawMenu(std::vector& menuTab, int& currentMenuTab, int me battY1 + battHeadSpacing, battX2 - battFullLevelWidth - battBorderWidth, battY2 - battHeadSpacing, - 0xFFFFFF, 1.0f); + Themes[settings3DS.Theme].selectedTabTextColor, 1.0f); // battery body ui3dsDrawRect( battX2 - battFullLevelWidth - battBorderWidth, battY1 - battBorderWidth, battX2 + battBorderWidth, battY2 + battBorderWidth, - 0xFFFFFF, 1.0f); + Themes[settings3DS.Theme].selectedTabTextColor, 1.0f); // battery's empty insides ui3dsDrawRect( battX2 - battFullLevelWidth, battY1, battX2, battY2, - 0x1976D2, 1.0f); - - - bool hasRandomGameOption = currentTab->Title == "Load Game" && file3dsGetCurrentDirRomCount() > 1; - - if (!hasRandomGameOption) { - ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, 10, SCREEN_HEIGHT - 17, screenSettings.SecondScreenWidth, SCREEN_HEIGHT, 0xFFFFFF, HALIGN_LEFT, - "A: Select \x0b7 B: Cancel"); - const int rightEdge = battX2 - battFullLevelWidth - battBorderWidth - 10; - ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, 97, SCREEN_HEIGHT - 17, rightEdge, SCREEN_HEIGHT, 0xFFFFFF, HALIGN_RIGHT, getAppVersion("Snes9x for 3DS v")); - } else { - ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, 10, SCREEN_HEIGHT - 17, screenSettings.SecondScreenWidth, SCREEN_HEIGHT, 0xFFFFFF, HALIGN_LEFT, - "A: Select \x0b7 B: Cancel \x0b7 X: Page Up/Down \x0b7 Y: Random Game"); - } - + Themes[settings3DS.Theme].menuBottomBarColor, 1.0f); + ptmuInit(); u8 batteryChargeState = 0; @@ -459,7 +500,7 @@ void menu3dsDrawMenu(std::vector& menuTab, int& currentMenuTab, int me if(R_SUCCEEDED(PTMU_GetBatteryChargeState(&batteryChargeState)) && batteryChargeState) { ui3dsDrawRect( battX2-battFullLevelWidth + 1, battY1 + 1, - battX2 - 1, battY2 - 1, 0xFF9900, 1.0f); + battX2 - 1, battY2 - 1, Themes[settings3DS.Theme].accentColor, 1.0f); } else if(R_SUCCEEDED(PTMU_GetBatteryLevel(&batteryLevel))) { if (batteryLevel > 5) batteryLevel = 5; @@ -467,18 +508,40 @@ void menu3dsDrawMenu(std::vector& menuTab, int& currentMenuTab, int me { ui3dsDrawRect( battX2-battLevelWidth*(i+1), battY1 + 1, - battX2-battLevelWidth*(i) - 1, battY2 - 1, 0xFFFFFF, 1.0f); + battX2-battLevelWidth*(i) - 1, battY2 - 1, Themes[settings3DS.Theme].accentColor, 1.0f); } - } else { - //ui3dsDrawRect(battX2, battY1, battX2, battY2, 0xFFFFFF, 1.0f); } ptmuExit(); + + bool romLoaded = menuTab.size() > 2; + int buttonRightMargin = 5; + int buttonLeftMargin = 10; + int bottomMenuPosX = 10; + int buttonColor = settings3DS.Theme == THEME_ORIGINAL ? 0x529eeb : 0x555555; + + for (const auto& button : bottomMenuButtons) { + if (settings3DS.Theme == THEME_DARK_MODE) { + // multi color buttons for dark mode theme + buttonColor = button.color; + } + + if ((button.label != "Options" && button.label != "Page \x0d1") || currentTab->Title == "Load Game") { + ui3dsDrawRect(bottomMenuPosX + 2, SCREEN_HEIGHT - 13, bottomMenuPosX + 9, SCREEN_HEIGHT - 6,0xffffff); + bottomMenuPosX = ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, bottomMenuPosX, SCREEN_HEIGHT - 16, bottomMenuPosX + 12, SCREEN_HEIGHT, buttonColor, HALIGN_LEFT, button.icon) + buttonRightMargin; + bottomMenuPosX = ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, bottomMenuPosX, SCREEN_HEIGHT - 17, bottomMenuPosX + 100, SCREEN_HEIGHT, Themes[settings3DS.Theme].menuBottomBarTextColor, HALIGN_LEFT, button.label) + buttonLeftMargin; + } + } + const int rightEdge = battX2 - battFullLevelWidth - battBorderWidth - 6; + ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, 97, SCREEN_HEIGHT - 17, rightEdge, SCREEN_HEIGHT, Themes[settings3DS.Theme].menuBottomBarTextColor, HALIGN_RIGHT, getAppVersion("v")); + int line = 0; int maxItems = MENU_HEIGHT; int menuStartY = 29; - int selectedItemBackColor = menu3dsHasHighlightableItems(currentTab) ? 0x333333 : 0xFFFFFF; + + int menuBackColor = Themes[settings3DS.Theme].menuBackColor; + int selectedItemBackColor = menu3dsHasHighlightableItems(currentTab) ? Themes[settings3DS.Theme].selectedItemBackColor : -1; ui3dsSetTranslate(menuItemFrame * 3, translateY); @@ -486,30 +549,32 @@ void menu3dsDrawMenu(std::vector& menuTab, int& currentMenuTab, int me { menu3dsDrawItems( currentTab, 20, menuStartY, maxItems, - selectedItemBackColor, // selectedItemBackColor - 0xffffff, // selectedItemTextColor - 0x777777, // selectedItemDescriptionTextColor + selectedItemBackColor, + Themes[settings3DS.Theme].selectedItemTextColor, + Themes[settings3DS.Theme].selectedItemDescriptionTextColor, + Themes[settings3DS.Theme].normalItemTextColor, + Themes[settings3DS.Theme].normalItemDescriptionTextColor, + Themes[settings3DS.Theme].disabledItemTextColor, + Themes[settings3DS.Theme].headerItemTextColor, + Themes[settings3DS.Theme].subtitleTextColor); - 0x000000, // checkedItemTextColor - 0x333333, // normalItemTextColor - 0x777777, // normalItemDescriptionTextColor - 0x888888, // disabledItemTextColor - 0x1E88E5, // headerItemTextColor - 0x1E88E5); // subtitleTextColor + menu3dsSetLastSelectedIndexByTab(currentTab->Title, currentTab->SelectedItemIndex); if (currentTab->Title != "Load Game") { return; } - selectedRomItemIndex = currentTab->SelectedItemIndex; - // looking for available game thumbnail - StoredFile file = file3dsGetStoredFileById(currentTab->MenuItems[selectedRomItemIndex].Text); + std::string filename = currentTab->MenuItems[currentTab->SelectedItemIndex].Text; + size_t offs = filename.find_first_not_of(' '); + filename.assign(offs != filename.npos ? filename.substr(offs) : filename); + StoredFile file = file3dsGetStoredFileById(filename); if (!file.Buffer.empty()) { ui3dsRenderImage(screenSettings.SecondScreen, file.Filename.c_str(), file.Buffer.data(), file.Buffer.size(), IMAGE_TYPE::PREVIEW); } + } else { @@ -517,61 +582,82 @@ void menu3dsDrawMenu(std::vector& menuTab, int& currentMenuTab, int me menuItemFrame = -menuItemFrame; float alpha = (float)(ANIMATE_TAB_STEPS - menuItemFrame + 1) / (ANIMATE_TAB_STEPS + 1); - int white = ui3dsApplyAlphaToColor(0xFFFFFF, 1.0f - alpha); + int menuBackColorAlpha = ui3dsApplyAlphaToColor(menuBackColor, 1.0f - alpha); menu3dsDrawItems( currentTab, 20, menuStartY, maxItems, - ui3dsApplyAlphaToColor(selectedItemBackColor, alpha) + white, - ui3dsApplyAlphaToColor(0xffffff, alpha) + white, // selectedItemTextColor - ui3dsApplyAlphaToColor(0x777777, alpha) + white, // selectedItemDescriptionTextColor - - ui3dsApplyAlphaToColor(0x000000, alpha) + white, // checkedItemTextColor - ui3dsApplyAlphaToColor(0x333333, alpha) + white, // normalItemTextColor - ui3dsApplyAlphaToColor(0x777777, alpha) + white, // normalItemDescriptionTextColor - ui3dsApplyAlphaToColor(0x888888, alpha) + white, // disabledItemTextColor - ui3dsApplyAlphaToColor(0x1E88E5, alpha) + white, // headerItemTextColor - ui3dsApplyAlphaToColor(0x1E88E5, alpha) + white); // subtitleTextColor - } - - + selectedItemBackColor != -1 ? ui3dsApplyAlphaToColor(selectedItemBackColor, alpha) + menuBackColorAlpha : selectedItemBackColor, + ui3dsApplyAlphaToColor(Themes[settings3DS.Theme].selectedItemTextColor, alpha) + menuBackColorAlpha, + ui3dsApplyAlphaToColor(Themes[settings3DS.Theme].selectedItemDescriptionTextColor, alpha) + menuBackColorAlpha, + ui3dsApplyAlphaToColor(Themes[settings3DS.Theme].normalItemTextColor, alpha) + menuBackColorAlpha, + ui3dsApplyAlphaToColor(Themes[settings3DS.Theme].normalItemDescriptionTextColor, alpha) + menuBackColorAlpha, + ui3dsApplyAlphaToColor(Themes[settings3DS.Theme].disabledItemTextColor, alpha) + menuBackColorAlpha, + ui3dsApplyAlphaToColor(Themes[settings3DS.Theme].headerItemTextColor, alpha) + menuBackColorAlpha, + ui3dsApplyAlphaToColor(Themes[settings3DS.Theme].subtitleTextColor, alpha) + menuBackColorAlpha); + } } -int dialogBackColor = 0xEC407A; -int dialogTextColor = 0xffffff; -int dialogItemTextColor = 0xffffff; -int dialogSelectedItemTextColor = 0xffffff; -int dialogSelectedItemBackColor = 0x000000; +int dialogBackColor = 0x000000; void menu3dsDrawDialog(SMenuTab& dialogTab) { - // Dialog's Background - int dialogBackColor2 = ui3dsApplyAlphaToColor(dialogBackColor, 0.9f); - ui3dsDrawRect(0, 0, screenSettings.SecondScreenWidth, 75, dialogBackColor2); - ui3dsDrawRect(0, 75, screenSettings.SecondScreenWidth, 160, dialogBackColor); + int dialogTextColor = 0xffffff; + int selectedItemBackColor = 0x000000; + int dialogSelectedItemTextColor = Themes[settings3DS.Theme].selectedItemTextColor; + int offsetX = settings3DS.Theme == THEME_RETROARCH ? 6 : 0; + int horizontalPadding = 32; + int topHeight = 76; + int bottomHeight = 84; + + int dialogBackColorBottom = settings3DS.Theme == THEME_ORIGINAL ? dialogBackColor : Themes[settings3DS.Theme].menuBackColor; + int dialogBackColorTop = settings3DS.Theme == THEME_ORIGINAL ? ui3dsApplyAlphaToColor(dialogBackColorBottom, 0.9f) : ui3dsOverlayBlendColor(dialogBackColorBottom, 0xaaaaaa); + ui3dsDrawRect(0, 0, screenSettings.SecondScreenWidth, topHeight, dialogBackColorTop); + ui3dsDrawRect(0, topHeight, screenSettings.SecondScreenWidth, topHeight + bottomHeight, dialogBackColorBottom); - // Draw the dialog's title and descriptive text int dialogTitleTextColor = - ui3dsApplyAlphaToColor(dialogBackColor, 0.5f) + - ui3dsApplyAlphaToColor(dialogTextColor, 0.5f); - ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, 30, 10, 290, 25, dialogTitleTextColor, HALIGN_LEFT, dialogTab.Title.c_str()); - ui3dsDrawStringWithWrapping(screenSettings.SecondScreen, 30, 30, 290, 70, dialogTextColor, HALIGN_LEFT, dialogTab.DialogText.c_str()); + ui3dsApplyAlphaToColor(dialogBackColorTop, 1.0f - Themes[settings3DS.Theme].dialogTextAlpha) + + ui3dsApplyAlphaToColor(dialogTextColor, Themes[settings3DS.Theme].dialogTextAlpha); + + int dialogItemDescriptionTextColor = + ui3dsApplyAlphaToColor(dialogBackColorBottom, 1.0f - Themes[settings3DS.Theme].dialogTextAlpha) + + ui3dsApplyAlphaToColor(dialogTextColor, Themes[settings3DS.Theme].dialogTextAlpha); + + int dialogSelectedItemBackColor; + + if (settings3DS.Theme == THEME_DARK_MODE) { + ui3dsDrawRect(0, topHeight - 2, screenSettings.SecondScreenWidth, topHeight, dialogBackColor); + ui3dsDrawRect(0, topHeight, screenSettings.SecondScreenWidth, topHeight + 2, dialogBackColor); + dialogSelectedItemBackColor = Themes[settings3DS.Theme].selectedItemBackColor; + } + else if (settings3DS.Theme == THEME_RETROARCH) { + int cb1 = ui3dsOverlayBlendColor(dialogBackColorTop, dialogBackColor); + int cb2 = ui3dsOverlayBlendColor(dialogBackColorBottom, dialogBackColor); + int cb3 = ui3dsOverlayBlendColor(ui3dsApplyAlphaToColor(dialogBackColorBottom, 0.85f), dialogBackColor); + ui3dsDrawCheckerboard(0, topHeight - 2, screenSettings.SecondScreenWidth, topHeight, cb1, cb3); + ui3dsDrawCheckerboard(0, topHeight, screenSettings.SecondScreenWidth, topHeight + 2, cb1, cb3); + dialogSelectedItemBackColor = -1; + } else { + dialogSelectedItemBackColor = Themes[settings3DS.Theme].selectedItemBackColor == -1 ? -1 : + ui3dsApplyAlphaToColor(dialogBackColorBottom, 1.0f - Themes[settings3DS.Theme].dialogSelectedItemBackAlpha) + + ui3dsApplyAlphaToColor(selectedItemBackColor, Themes[settings3DS.Theme].dialogSelectedItemBackAlpha); + } - // Draw the selectable items. - int dialogItemDescriptionTextColor = dialogTitleTextColor; + ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, horizontalPadding - offsetX, 10, screenSettings.SecondScreenWidth - horizontalPadding, 25, dialogTitleTextColor, HALIGN_LEFT, dialogTab.Title.c_str()); + ui3dsDrawStringWithWrapping(screenSettings.SecondScreen, horizontalPadding - offsetX, 30, screenSettings.SecondScreenWidth - horizontalPadding, 70, dialogTextColor, HALIGN_LEFT, dialogTab.DialogText.c_str()); + + int menuStartY = settings3DS.Theme == THEME_RETROARCH ? bottomHeight + 1 : bottomHeight + 3; menu3dsDrawItems( - &dialogTab, 30, 80, DIALOG_HEIGHT, - dialogSelectedItemBackColor, // selectedItemBackColor - dialogSelectedItemTextColor, // selectedItemTextColor - dialogItemDescriptionTextColor, // selectedItemDescriptionColor - - dialogItemTextColor, // checkedItemTextColor - dialogItemTextColor, // normalItemTextColor - dialogItemDescriptionTextColor, // normalItemDescriptionTextColor - dialogItemDescriptionTextColor, // disabledItemTextColor - dialogItemTextColor, // headerItemTextColor - dialogItemTextColor // subtitleTextColor - ); + &dialogTab, horizontalPadding, menuStartY, DIALOG_HEIGHT, + dialogSelectedItemBackColor, + Themes[settings3DS.Theme].selectedItemTextColor, + dialogItemDescriptionTextColor, + dialogTextColor, + dialogItemDescriptionTextColor, + dialogItemDescriptionTextColor, + dialogTextColor, + dialogTextColor, + offsetX); } @@ -624,12 +710,12 @@ void menu3dsDrawThumbnailCacheStatus(SMenuTab& dialogTab, bool& isDialog, int& c if (currentPercent > lastPercent + 5) { lastPercent = currentPercent; menu3dsDrawMenu(menuTab, currentMenuTab, 0, 0); - ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, screenSettings.SecondScreenWidth - 120, 24, screenSettings.SecondScreenWidth - 8, 24 + 13, - 0x999999, HALIGN_LEFT, s); + ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, screenSettings.SecondScreenWidth - 130, 29, screenSettings.SecondScreenWidth - 16, 29 + FONT_HEIGHT, + Themes[settings3DS.Theme].normalItemDescriptionTextColor, HALIGN_LEFT, s); swapBuffer = true; } else { - ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, screenSettings.SecondScreenWidth - 120, 24, screenSettings.SecondScreenWidth - 8, 24 + 13, - 0x999999, HALIGN_LEFT, s); + ui3dsDrawStringWithNoWrapping(screenSettings.SecondScreen, screenSettings.SecondScreenWidth - 130, 29, screenSettings.SecondScreenWidth - 16, 29 + FONT_HEIGHT, + Themes[settings3DS.Theme].normalItemDescriptionTextColor, HALIGN_LEFT, s); } } @@ -703,9 +789,7 @@ int menu3dsMenuSelectItem(SMenuTab& dialogTab, bool& isDialog, int& currentMenuT { int framesDKeyHeld = 0; int returnResult = -1; - char menuTextBuffer[512]; - std::string currentDir = std::string(file3dsGetCurrentDir()); SMenuTab *currentTab = &menuTab[currentMenuTab]; @@ -762,33 +846,51 @@ int menu3dsMenuSelectItem(SMenuTab& dialogTab, bool& isDialog, int& currentMenuT framesDKeyHeld ++; else framesDKeyHeld = 0; - if (keysDown & KEY_B) + + // close pause menu on start button + if (keysDown & KEY_START) { returnResult = -1; break; } - if (keysDown & KEY_Y && currentTab->Title == "Load Game" && file3dsGetCurrentDirRomCount() > 1) + + if (keysDown & KEY_B) { - int randomRetry = 0; - int lastSelectedItemIndex = currentTab->SelectedItemIndex; - while (randomRetry < 10) { - int randomIndex = getRandomInt(0, (currentTab->MenuItems.size() - 1)); - - if (currentTab->MenuItems[randomIndex].Type == MenuItemType::Action && - file3dsIsValidFilename(currentTab->MenuItems[randomIndex].Text.c_str(), {".smc", ".sfc", ".fig"})) { - currentTab->SelectedItemIndex = randomIndex; - returnResult = currentTab->MenuItems[currentTab->SelectedItemIndex].Value; - currentTab->MenuItems[currentTab->SelectedItemIndex].SetValue(1); - break; + if (isDialog) { + returnResult = -1; + } + else if (currentTab->MenuItems[0].Text == PARENT_DIRECTORY_LABEL) { + // if current tab has parent directory, navigate to parent directory + currentTab->MenuItems[0].SetValue(1); + returnResult = currentTab->MenuItems[0].Value; + } + else { + // scroll to top + int lastSelectedItemIndex = currentTab->SelectedItemIndex; + for (int i = 0; i < currentTab->MenuItems.size(); i++) { + if (currentTab->MenuItems[i].IsHighlightable()) { + currentTab->SelectedItemIndex = i; + currentTab->MakeSureSelectionIsOnScreen(MENU_HEIGHT, 2); + + break; + } } - randomRetry++; + if (lastSelectedItemIndex == currentTab->SelectedItemIndex && currentTab->Title != "Emulator") { + currentTab = menu3dsAnimateTab(dialogTab, isDialog, currentMenuTab, menuTab, -1); + } + + returnResult = 0; } - if (currentTab->SelectedItemIndex != lastSelectedItemIndex) { - break; - } + break; } + if (keysDown & KEY_X && currentTab->Title == "Load Game") + { + returnResult = FILE_MENU_SHOW_OPTIONS; + break; + } + if ((keysDown & KEY_RIGHT) || (keysDown & KEY_R) || ((thisKeysHeld & KEY_RIGHT) && (framesDKeyHeld > 15) && (framesDKeyHeld % 2 == 0))) { if (!isDialog) @@ -834,7 +936,7 @@ int menu3dsMenuSelectItem(SMenuTab& dialogTab, bool& isDialog, int& currentMenuT } } } - if (keysDown & KEY_START || keysDown & KEY_A) + if (keysDown & KEY_A) { if (currentTab->MenuItems[currentTab->SelectedItemIndex].Type == MenuItemType::Action) { @@ -880,21 +982,46 @@ int menu3dsMenuSelectItem(SMenuTab& dialogTab, bool& isDialog, int& currentMenuT } if (currentTab->MenuItems[currentTab->SelectedItemIndex].Type == MenuItemType::Picker) { + int pickerDialogBackground; + + switch (currentTab->MenuItems[currentTab->SelectedItemIndex].PickerDialogType) { + case DIALOG_TYPE_SUCCESS: + pickerDialogBackground = Themes[settings3DS.Theme].dialogColorSuccess; + break; + case DIALOG_TYPE_WARN: + pickerDialogBackground = Themes[settings3DS.Theme].dialogColorWarn; + break; + default: + pickerDialogBackground = Themes[settings3DS.Theme].dialogColorInfo; + break; + } + snprintf(menuTextBuffer, 511, "%s", currentTab->MenuItems[currentTab->SelectedItemIndex].Text.c_str()); + int lastValue = currentTab->MenuItems[currentTab->SelectedItemIndex].Value; int resultValue = menu3dsShowDialog(dialogTab, isDialog, currentMenuTab, menuTab, menuTextBuffer, currentTab->MenuItems[currentTab->SelectedItemIndex].PickerDescription, - currentTab->MenuItems[currentTab->SelectedItemIndex].PickerBackColor, + pickerDialogBackground, currentTab->MenuItems[currentTab->SelectedItemIndex].PickerItems, currentTab->MenuItems[currentTab->SelectedItemIndex].Value ); + + menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); + if (resultValue != -1) { currentTab->MenuItems[currentTab->SelectedItemIndex].SetValue(resultValue); } - menu3dsDrawEverything(dialogTab, isDialog, currentMenuTab, menuTab); - menu3dsHideDialog(dialogTab, isDialog, currentMenuTab, menuTab); + menu3dsDrawEverything(dialogTab, isDialog, currentMenuTab, menuTab); + // when game screen has been swapped we want to exit the menu loop to close the menu + // TODO: provide keys for text labels (game screen menu item shouldn't be detected by text label value) + bool closeMenu = resultValue != -1 && resultValue != lastValue && currentTab->MenuItems[currentTab->SelectedItemIndex].Text == " Game Screen"; + + if (closeMenu) { + returnResult = -1; + break; + } } } if (keysDown & KEY_UP || ((thisKeysHeld & KEY_UP) && (framesDKeyHeld > 15) && (framesDKeyHeld % 2 == 0))) @@ -903,7 +1030,7 @@ int menu3dsMenuSelectItem(SMenuTab& dialogTab, bool& isDialog, int& currentMenuT do { - if (thisKeysHeld & KEY_X) + if (thisKeysHeld & KEY_Y) { currentTab->SelectedItemIndex -= 13; if (currentTab->SelectedItemIndex < 0) @@ -937,7 +1064,7 @@ int menu3dsMenuSelectItem(SMenuTab& dialogTab, bool& isDialog, int& currentMenuT int moveCursorTimes = 0; do { - if (thisKeysHeld & KEY_X) + if (thisKeysHeld & KEY_Y) { currentTab->SelectedItemIndex += 13; if (currentTab->SelectedItemIndex >= currentTab->MenuItems.size()) @@ -974,8 +1101,8 @@ int menu3dsMenuSelectItem(SMenuTab& dialogTab, bool& isDialog, int& currentMenuT menu3dsSwapBuffersAndWaitForVBlank(); } - menu3dsSetLastTabPosition(currentMenuTab, currentTab->SelectedItemIndex); - + menu3dsSetLastSelectedTabIndex(currentMenuTab); + return returnResult; } @@ -1002,6 +1129,22 @@ void menu3dsAddTab(std::vector& menuTab, char *title, const std::vecto } } +void menu3dsSelectRandomGame(SMenuTab *currentTab) { + int randomRetry = 0; + while (randomRetry < 10) { + int randomIndex = getRandomInt(0, (currentTab->MenuItems.size() - 1)); + + if (currentTab->MenuItems[randomIndex].Type == MenuItemType::Action && + file3dsIsValidFilename(currentTab->MenuItems[randomIndex].Text.c_str(), {".smc", ".sfc", ".fig"})) { + currentTab->SelectedItemIndex = randomIndex; + currentTab->MenuItems[currentTab->SelectedItemIndex].SetValue(1); + break; + } + + randomRetry++; + } +} + void menu3dsSetSelectedItemByIndex(SMenuTab& tab, int index) { if (index >= 0 && index < tab.MenuItems.size()) { @@ -1268,8 +1411,6 @@ bool menu3dsHandleDialogBackground(bool save, int x0, int y0, int x1, int y1) { unsigned char b = tempPixelData[di+2]; // Convert 8-bit color values to 16-bit color (RGB565) - uint16_t color16 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); - fb[si--] = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); } } @@ -1301,7 +1442,7 @@ void menu3dsSetSecondScreenContent(const char *dialogMessage, int dialogBackgrou // draw new dialog message if (dialogMessage) { menu3dsHandleDialogBackground(true, b.left, b.top, b.right, b.bottom); - ui3dsDrawRect(b.left, b.top, b.right, b.bottom, dialogBackgroundColor, dialogAlpha); + ui3dsDrawRect(b.left, b.top, b.right, b.bottom, ui3dsOverlayBlendColor(0x555555, dialogBackgroundColor), dialogAlpha); ui3dsDrawStringWithWrapping(screenSettings.SecondScreen, b.left + padding + 2, b.top + padding, b.right - padding + 2, b.bottom - padding, 0xffffff, HALIGN_LEFT, dialogMessage); ui3dsSetSecondScreenDialogState(VISIBLE); } diff --git a/source/3dsmenu.h b/source/3dsmenu.h index cb74674..e9ca206 100644 --- a/source/3dsmenu.h +++ b/source/3dsmenu.h @@ -5,9 +5,12 @@ #include #include -#define DIALOGCOLOR_RED 0xEC407A -#define DIALOGCOLOR_GREEN 0x4CAF50 -#define DIALOGCOLOR_CYAN 0x0097A7 +typedef struct +{ + const char* label; + const char* icon; + uint32 color; +} MenuButton; // currently used for save states typedef enum @@ -27,7 +30,7 @@ enum class MenuItemType { Checkbox, Radio, Gauge, - Picker, + Picker }; class SMenuItem { @@ -62,7 +65,7 @@ class SMenuItem { // std::string PickerDescription; std::vector PickerItems; - int PickerBackColor; + int PickerDialogType; protected: std::function ValueChangedCallback; @@ -72,10 +75,10 @@ class SMenuItem { std::function callback, MenuItemType type, const std::string& text, const std::string& description, int value = 0, int min = 0, int max = 0, - const std::string& pickerDesc = std::string(), const std::vector& pickerItems = std::vector(), int pickerColor = 0 + const std::string& pickerDesc = std::string(), const std::vector& pickerItems = std::vector(), int pickerDialogType = 0 ) : ValueChangedCallback(callback), Type(type), Text(text), Description(description), Value(value), GaugeMinValue(min), GaugeMaxValue(max), - PickerDescription(pickerDesc), PickerItems(pickerItems), PickerBackColor(pickerColor) {} + PickerDescription(pickerDesc), PickerItems(pickerItems), PickerDialogType(pickerDialogType) {} void SetValue(int value) { this->Value = value; @@ -139,8 +142,12 @@ void menu3dsHideMenu(SMenuTab& dialogTab, bool& isDialog, int& currentMenuTab, s int menu3dsShowDialog(SMenuTab& dialogTab, bool& isDialog, int& currentMenuTab, std::vector& menuTab, const std::string& title, const std::string& dialogText, int dialogBackColor, const std::vector& menuItems, int selectedID = -1); void menu3dsHideDialog(SMenuTab& dialogTab, bool& isDialog, int& currentMenuTab, std::vector& menuTab); -void menu3dsSetLastTabPosition(int currentMenuTab, int index); -void menu3dsGetLastTabPosition(int& currentMenuTab, int& lastItemIndex); +int menu3dsGetLastSelectedTabIndex(); +void menu3dsSetLastSelectedTabIndex(int index); +void menu3dsSetLastSelectedIndexByTab(const std::string& tab, int menuItemIndex); +int menu3dsGetLastSelectedIndexByTab(const std::string& tab); +void menu3dsClearLastSelectedIndicesByTab(); +void menu3dsSelectRandomGame(SMenuTab *currentTab); void menu3dsUpdateGaugeVisibility(SMenuTab *currentTab, int id, int value); bool menu3dsTakeScreenshot(const char *path); @@ -151,9 +158,8 @@ void menu3dsSetHotkeysData(char* hotkeysData[][3]); void menu3dsSetCheatsIndicator(std::vector& cheatMenu); void menu3dsSetCurrentPercent(int current, int total); int menu3dsGetCurrentPercent(); -int menu3dsGetLastRomItemIndex(); -void menu3dsSetSecondScreenContent(const char *dialogMessage, int dialogBackgroundColor = DIALOGCOLOR_GREEN, float dialogAlpha = 0.85f); +void menu3dsSetSecondScreenContent(const char *dialogMessage, int dialogBackgroundColor = 0x333333, float dialogAlpha = 0.85f); #endif diff --git a/source/3dssettings.cpp b/source/3dssettings.cpp index 42e874d..68c9d9b 100644 --- a/source/3dssettings.cpp +++ b/source/3dssettings.cpp @@ -1,7 +1,11 @@ #include "3dssettings.h" bool S9xSettings3DS::operator==(const S9xSettings3DS& other) const { - return ((GameScreen == other.GameScreen) && + return ( + (defaultDir == other.defaultDir) && + (lastSelectedDir == other.lastSelectedDir) && + (lastSelectedFilename == other.lastSelectedFilename) && + (GameScreen == other.GameScreen) && (MaxFrameSkips == other.MaxFrameSkips) && (SecondScreenContent == other.SecondScreenContent) && (SecondScreenOpacity == other.SecondScreenOpacity) && @@ -35,7 +39,8 @@ bool S9xSettings3DS::operator==(const S9xSettings3DS& other) const { (GlobalBindCirclePad == other.GlobalBindCirclePad) && (RomFsLoaded == other.RomFsLoaded) && (Disable3DSlider == other.Disable3DSlider) && - (GameThumbnailType == other.GameThumbnailType)); + (GameThumbnailType == other.GameThumbnailType) && + (Theme == other.Theme)); } bool S9xSettings3DS::operator!=(const S9xSettings3DS& other) const { diff --git a/source/3dssettings.h b/source/3dssettings.h index a37ee64..df7b3f5 100644 --- a/source/3dssettings.h +++ b/source/3dssettings.h @@ -68,13 +68,21 @@ struct ButtonMapping { #define HOTKEY_SAVE_SLOT_PREV 7 #define HOTKEYS_COUNT 8 -#define OPACITY_STEPS 20 -#define GAUGE_DISABLED_VALUE -1 +#define OPACITY_STEPS 20 +#define GAUGE_DISABLED_VALUE -1 +#define FILE_MENU_SHOW_OPTIONS -2 typedef struct S9xSettings3DS { const char *RootDir = "sdmc:/3ds/snes9x_3ds"; + // we use root directory as initial value here. If defaultDir value is empty, + // lastSelectedDir seems to be ignored in settings.cfg (not entirely sure why this is the case) + char defaultDir[_MAX_PATH] = "/"; + + char lastSelectedDir[_MAX_PATH] = ""; + char lastSelectedFilename[_MAX_PATH] = ""; + gfxScreen_t GameScreen = GFX_TOP; int GameThumbnailType = 0; // 0 - None, @@ -98,6 +106,8 @@ typedef struct S9xSettings3DS int GameBorderOpacity = OPACITY_STEPS / 2; + int Theme = 0; // current theme + int Font = 0; // 0 - Tempesta // 1 - Ronda // 2 - Arial diff --git a/source/3dsthemes.cpp b/source/3dsthemes.cpp new file mode 100644 index 0000000..d5bd2e5 --- /dev/null +++ b/source/3dsthemes.cpp @@ -0,0 +1,76 @@ +#include "3dsthemes.h" + + +Theme3ds Themes[]={ + {"Dark mode", //Name + 0x2e2e2e, //menuTopBarColor + 0xffffff, //selectedTabTextColor + 0x999999, //tabTextColor + 0x1aa1fa, //selectedTabIndicatorColor + 0x2e2e2e, //menuBottomBarColor + 0xffffff, // menuBottomBarTextColor + 0x1c1c1c, //menuBackColor + 0x2e2e2e, //selectedItemBackColor + 0xffffff, //selectedItemTextColor + 0x949494, //selectedItemDescriptionTextColor + 0xaaaaaa, //normalItemTextColor + 0x999999, //normalItemDescriptionTextColor + 0x787878, //disabledItemTextColor + 0x1aa1fa, //headerItemTextColor + 0x1aa1fa, //subtitleTextColor + 0xFF9900, //accentColor + 0x995c00, //accentUnselectedColor + 0x1aa1fa, //dialogColorInfo + 0xfa1a4f, //dialogColorWarn + 0x1afa2a, //dialogColorSuccess + 0.6f, //dialogTextAlpha + 1.0f}, //dialogSelectedItemBackAlpha + + {"Retroarch", //Name + -1, //menuTopBarColor + 0x66ff66, //selectedTabTextColor + 0xffffff, //tabTextColor + -1, //selectedTabIndicatorColor + 0x1d1d1d, //menuBottomBarColor + 0xffffff, // menuBottomBarTextColor + 0x1d1d1d, //menuBackColor + -1, //selectedItemBackColor + 0x66ff66, //selectedItemTextColor + 0x66ff66, //selectedItemDescriptionTextColor + 0xffffff, //normalItemTextColor + 0xcccccc, //normalItemDescriptionTextColor + 0xaaaaaa, //disabledItemTextColor + 0x66ff66, //headerItemTextColor + 0x66ff66, //subtitleTextColor + 0x66ff66, //accentColor + 0x66ff66, //accentUnselectedColor + 0x66ff66, //dialogColorInfo + 0xff6666, //dialogColorWarn + 0x66ff66, //dialogColorSuccess + 0.6f, //dialogTextAlpha + 0.0f}, //dialogSelectedItemBackAlpha + + { "Original", //Name + 0x1976D2, //menuTopBarColor + 0xffffff, //selectedTabTextColor + 0x90CAF9, //tabTextColor + 0xffffff, //selectedTabIndicatorColor + 0x1976D2, //menuBottomBarColor + 0xffffff, // menuBottomBarTextColor + 0xffffff, //menuBackColor + 0x333333, //selectedItemBackColor + 0xffffff, //selectedItemTextColor + 0x777777, //selectedItemDescriptionTextColor + 0x333333, //normalItemTextColor + 0x777777, //normalItemDescriptionTextColor + 0x888888, //disabledItemTextColor + 0x1E88E5, //headerItemTextColor + 0x1E88E5, //subtitleTextColor + 0xFF9900, //accentColor + 0x998A5E, //accentUnselectedColor + 0x0097A7, //dialogColorInfo + 0xEC407A, //dialogColorWarn + 0x4CAF50, //dialogColorSuccess + 0.6f, //dialogTextAlpha + 1.0f}, //dialogSelectedItemBackAlpha +}; \ No newline at end of file diff --git a/source/3dsthemes.h b/source/3dsthemes.h new file mode 100644 index 0000000..9bff492 --- /dev/null +++ b/source/3dsthemes.h @@ -0,0 +1,44 @@ +#ifndef _3DSTHEMES_H_ +#define _3DSTHEMES_H_ + +#include "snes9x.h" + +typedef struct +{ + char *Name; + uint32 menuTopBarColor; + uint32 selectedTabTextColor; + uint32 tabTextColor; + uint32 selectedTabIndicatorColor; + uint32 menuBottomBarColor; + uint32 menuBottomBarTextColor; + uint32 menuBackColor; + uint32 selectedItemBackColor; + uint32 selectedItemTextColor; + uint32 selectedItemDescriptionTextColor; + uint32 normalItemTextColor; + uint32 normalItemDescriptionTextColor; + uint32 disabledItemTextColor; + uint32 headerItemTextColor; + uint32 subtitleTextColor; + uint32 accentColor; + uint32 accentUnselectedColor; + uint32 dialogColorInfo; + uint32 dialogColorWarn; + uint32 dialogColorSuccess; + float dialogTextAlpha; + float dialogSelectedItemBackAlpha; +} Theme3ds; + +#define THEME_DARK_MODE 0 +#define THEME_RETROARCH 1 +#define THEME_ORIGINAL 2 + +#define DIALOG_TYPE_INFO 0 +#define DIALOG_TYPE_SUCCESS 1 +#define DIALOG_TYPE_WARN 2 + +#define TOTALTHEMECOUNT 3 +extern Theme3ds Themes[TOTALTHEMECOUNT]; + +#endif \ No newline at end of file diff --git a/source/3dsui.cpp b/source/3dsui.cpp index 967fb58..c11a267 100644 --- a/source/3dsui.cpp +++ b/source/3dsui.cpp @@ -438,7 +438,70 @@ void ui3dsDrawRect(int x0, int y0, int x1, int y1, int color, float alpha) } } +void ui3dsDrawCheckerboard(int x0, int y0, int x1, int y1, int color1, int color2) +{ + x0 += translateX; + x1 += translateX; + y0 += translateY; + y1 += translateY; + if (x0 < viewportX1) x0 = viewportX1; + if (x1 > viewportX2) x1 = viewportX2; + if (y0 < viewportY1) y0 = viewportY1; + if (y1 > viewportY2) y1 = viewportY2; + + uint16* fb = (uint16 *) gfxGetFramebuffer(screenSettings.SecondScreen, GFX_LEFT, NULL, NULL); + + int color1_565 = CONVERT_TO_565(color1); + int color2_565 = CONVERT_TO_565(color2); + + int tileWidth = 2; + int tileHeight = 2; + + for (int x = x0; x < x1; x += tileWidth) + { + for (int y = y0; y < y1; y += tileHeight) + { + // Determine the color for this tile based on its position + int tileColor = ((x / tileWidth) + (y / tileHeight)) % 2 == 0 ? color1_565 : color2_565; + + // Fill the current tile + for (int dx = 0; dx < tileWidth; dx++) + { + for (int dy = 0; dy < tileHeight; dy++) + { + int fbofs = (x + dx) * SCREEN_HEIGHT + (239 - (y + dy)); + fb[fbofs] = tileColor; + } + } + } + } +} + +// overlay blending mode: returns a color in RGB888 format +// may have more performance impact than simple blending mode +// but will provide more vibrant colors +// TODO: add alpha value support +int ui3dsOverlayBlendColor(int backgroundColor, int foregroundColor) { + // Extract the red, green, and blue components of the colors + float baseR = ((backgroundColor >> 16) & 0xFF) / 255.0f; + float baseG = ((backgroundColor >> 8) & 0xFF) / 255.0f; + float baseB = (backgroundColor & 0xFF) / 255.0f; + + float blendR = ((foregroundColor >> 16) & 0xFF) / 255.0f; + float blendG = ((foregroundColor >> 8) & 0xFF) / 255.0f; + float blendB = (foregroundColor & 0xFF) / 255.0f; + + float resultR = baseR <= 0.5f ? 2 * baseR * blendR : 1 - 2 * (1 - baseR) * (1 - blendR); + float resultG = baseG <= 0.5f ? 2 * baseG * blendG : 1 - 2 * (1 - baseG) * (1 - blendG); + float resultB = baseB <= 0.5f ? 2 * baseB * blendB : 1 - 2 * (1 - baseB) * (1 - blendB); + + unsigned char r = static_cast(resultR * 255); + unsigned char g = static_cast(resultG * 255); + unsigned char b = static_cast(resultB * 255); + + return (r << 16) | (g << 8) | b; +} //--------------------------------------------------------------- // Draws a rectangle with the back colour @@ -454,13 +517,13 @@ void ui3dsDrawRect(int x0, int y0, int x1, int y1) //--------------------------------------------------------------- // Draws a string at the given position without translation. //--------------------------------------------------------------- -void ui3dsDrawStringOnly(gfxScreen_t targetScreen, uint16 *fb, int absoluteX, int absoluteY, int color, const char *buffer, int startPos = 0, int endPos = 0xffff) +int ui3dsDrawStringOnly(gfxScreen_t targetScreen, uint16 *fb, int absoluteX, int absoluteY, int color, const char *buffer, int startPos = 0, int endPos = 0xffff) { int x = absoluteX; int y = absoluteY; if (color < 0) - return; + return x; if (y >= viewportY1 - 16 && y <= viewportY2) { @@ -480,6 +543,8 @@ void ui3dsDrawStringOnly(gfxScreen_t targetScreen, uint16 *fb, int absoluteX, in x += fontWidth[c]; } } + + return x; } @@ -587,12 +652,14 @@ void ui3dsDrawStringWithWrapping(gfxScreen_t targetScreen, int x0, int y0, int x //--------------------------------------------------------------- // Draws a string with the forecolor, with no wrapping //--------------------------------------------------------------- -void ui3dsDrawStringWithNoWrapping(gfxScreen_t targetScreen, int x0, int y0, int x1, int y1, int color, int horizontalAlignment, const char *buffer) +int ui3dsDrawStringWithNoWrapping(gfxScreen_t targetScreen, int x0, int y0, int x1, int y1, int color, int horizontalAlignment, const char *buffer) { x0 += translateX; x1 += translateX; y0 += translateY; y1 += translateY; + + int xEndPosition = 0; ui3dsPushViewport(x0, y0, x1, y1); @@ -610,10 +677,12 @@ void ui3dsDrawStringWithNoWrapping(gfxScreen_t targetScreen, int x0, int y0, int else // right aligned x = maxWidth - sWidth + x0; } - ui3dsDrawStringOnly(targetScreen, fb, x, y0, color, buffer); + xEndPosition = ui3dsDrawStringOnly(targetScreen, fb, x, y0, color, buffer); } ui3dsPopViewport(); + + return xEndPosition; } @@ -778,11 +847,19 @@ void ui3dsPrepareImage(gfxScreen_t targetScreen, const char *imagePath, unsigned // override properties based on image type if (type == IMAGE_TYPE::PREVIEW) { - border.width = 3; - border.color = 0xFFFFFF; position = Position::BR; - offsetX = border.width; - offsetY = border.width + 20; + + if (settings3DS.Theme != THEME_RETROARCH) { + border.width = 3; + border.color = Themes[settings3DS.Theme].menuBackColor; + offsetX = border.width; + offsetY = border.width + 20; + } else { + border.width = 0; + position = Position::BR; + offsetX = 8; + offsetY = 18; + } } if (type == IMAGE_TYPE::COVER) { diff --git a/source/3dsui.h b/source/3dsui.h index 272bd0c..94a7733 100644 --- a/source/3dsui.h +++ b/source/3dsui.h @@ -5,6 +5,7 @@ #include <3ds.h> #include #include +#include "3dsthemes.h" typedef struct { @@ -69,10 +70,12 @@ int ui3dsApplyAlphaToColor(int color, float alpha, bool rgb8 = false); void ui3dsDrawRect(int x0, int y0, int x1, int y1); void ui3dsDrawRect(int x0, int y0, int x1, int y1, int color, float alpha = 1.0f); +void ui3dsDrawCheckerboard(int x0, int y0, int x1, int y1, int color1, int color2); +int ui3dsOverlayBlendColor(int backgroundColor, int foregroundColor); void ui3dsDraw32BitRect(uint32 * fb, int x0, int y0, int x1, int y1, int color, float alpha = 1.0f); void ui3dsDrawStringWithWrapping(gfxScreen_t targetScreen, int x0, int y0, int x1, int y1, int color, int horizontalAlignment, const char *buffer); -void ui3dsDrawStringWithNoWrapping(gfxScreen_t targetScreen, int x0, int y0, int x1, int y1, int color, int horizontalAlignment, const char *buffer); +int ui3dsDrawStringWithNoWrapping(gfxScreen_t targetScreen, int x0, int y0, int x1, int y1, int color, int horizontalAlignment, const char *buffer); void ui3dsCopyFromFrameBuffer(uint16 *destBuffer); void ui3dsBlitToFrameBuffer(uint16 *srcBuffer, float alpha = 1.0f);