From e266044391be65af1c6714fa50b872259a9c3af4 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 30 Nov 2019 00:49:50 +0100 Subject: [PATCH] - update of load/save menu to latest GZDoom code. Input in save menu working. --- source/CMakeLists.txt | 1 + source/common/2d/v_draw.cpp | 1 + source/common/2d/v_draw.h | 1 + source/common/2d/v_drawtext.cpp | 22 + source/common/fonts/font.cpp | 2 +- source/common/menu/loadsavemenu.cpp | 1230 +++++++++--------------- source/common/menu/menu.cpp | 5 +- source/common/menu/menu.h | 84 +- source/common/menu/menudef.cpp | 3 - source/common/menu/menuinput.cpp | 110 ++- source/common/menu/savegamemanager.cpp | 507 ++++++++++ source/common/utility/namedef.h | 4 +- 12 files changed, 1109 insertions(+), 861 deletions(-) create mode 100644 source/common/menu/savegamemanager.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index a70e9eb18..ded88484d 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -830,6 +830,7 @@ set (PCH_SOURCES common/menu/imagescroller.cpp common/menu/joystickmenu.cpp common/menu/listmenu.cpp + common/menu/savegamemanager.cpp common/menu/loadsavemenu.cpp common/menu/menu.cpp common/menu/menudef.cpp diff --git a/source/common/2d/v_draw.cpp b/source/common/2d/v_draw.cpp index d89dcb555..3de2fbded 100644 --- a/source/common/2d/v_draw.cpp +++ b/source/common/2d/v_draw.cpp @@ -901,3 +901,4 @@ void ScaleWithAspect(int& w, int& h, int Width, int Height) h = static_cast(y); } + diff --git a/source/common/2d/v_draw.h b/source/common/2d/v_draw.h index c581cad78..a120c0cfe 100644 --- a/source/common/2d/v_draw.h +++ b/source/common/2d/v_draw.h @@ -48,6 +48,7 @@ void DrawTexture(F2DDrawer *drawer, FTexture* img, double x, double y, int tags_ void DrawChar (F2DDrawer* drawer, FFont *font, int normalcolor, double x, double y, int character, int tag_first, ...); void DrawText(F2DDrawer* drawer, FFont *font, int normalcolor, double x, double y, const char *string, int tag_first, ...); void DrawText(F2DDrawer* drawer, FFont *font, int normalcolor, double x, double y, const char32_t *string, int tag_first, ...); +void DrawFrame(F2DDrawer* twod, PalEntry color, int left, int top, int width, int height, int thickness); EXTERN_CVAR(Int, con_scaletext) // Scale notify text at high resolutions? EXTERN_CVAR(Int, con_scale) diff --git a/source/common/2d/v_drawtext.cpp b/source/common/2d/v_drawtext.cpp index 7baeb213d..46bc9a608 100644 --- a/source/common/2d/v_drawtext.cpp +++ b/source/common/2d/v_drawtext.cpp @@ -246,3 +246,25 @@ void DrawText(F2DDrawer* drawer, FFont *font, int normalcolor, double x, double DrawTextCommon(drawer, font, normalcolor, x, y, string, parms); } +//========================================================================== +// +// V_DrawFrame +// +// Draw a frame around the specified area using the view border +// frame graphics. The border is drawn outside the area, not in it. +// +//========================================================================== + +void DrawFrame(F2DDrawer* twod, PalEntry color, int left, int top, int width, int height, int thickness) +{ + // Sanity check for incomplete gameinfo + int offset = thickness == -1 ? screen->GetHeight() / 400 : thickness; + int right = left + width; + int bottom = top + height; + + // Draw top and bottom sides. + twod->AddColorOnlyQuad(left, top - offset, width, offset, color); + twod->AddColorOnlyQuad(left - offset, top - offset, offset, height + 2 * offset, color); + twod->AddColorOnlyQuad(left, bottom, width, offset, color); + twod->AddColorOnlyQuad(right, top - offset, offset, height + 2 * offset, color); +} diff --git a/source/common/fonts/font.cpp b/source/common/fonts/font.cpp index a6a0511d3..5f64a4bca 100644 --- a/source/common/fonts/font.cpp +++ b/source/common/fonts/font.cpp @@ -684,7 +684,7 @@ int FFont::StringWidth(const uint8_t *string) const } else { - w += NewSmallFont->CharWidth(chr) + GlobalKerning; + w += CharWidth(chr) + GlobalKerning; } } diff --git a/source/common/menu/loadsavemenu.cpp b/source/common/menu/loadsavemenu.cpp index f5ed87171..f68ab1d25 100644 --- a/source/common/menu/loadsavemenu.cpp +++ b/source/common/menu/loadsavemenu.cpp @@ -54,12 +54,8 @@ class DLoadSaveMenu : public DListMenu { using Super = DListMenu; - friend void ClearSaveGames(); protected: - static TArray SaveGames; - static int LastSaved; - static int LastAccessed; int Selected; int TopItem; @@ -86,650 +82,361 @@ protected: int commentHeight; int commentRight; int commentBottom; + int commentRows; + + double FontScale; + DTextEnterMenu *mInput = nullptr; + TArray BrokenSaveComment; + bool mEntering = false; - static int InsertSaveNode (FSaveGameNode *node); - static void ReadSaveStrings (); + //============================================================================= + // + // End of static savegame maintenance code + // + //============================================================================= - - FTexture *SavePic = nullptr; - TArray SaveComment; - bool mEntering; - FString savegamestring; - - DLoadSaveMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); - void Init(DMenu* parent, FListMenuDescriptor* desc) override; - void Destroy(); - - int RemoveSaveSlot (int index); - void UnloadSaveData (); - void ClearSaveStuff (); - void ExtractSaveData (int index); - void Drawer (); - bool MenuEvent (int mkey, bool fromcontroller); - bool MouseEvent(int type, int x, int y); - bool Responder(event_t *ev); - -public: - static void NotifyNewSave (const char *file, const char *title, bool okForQuicksave); - -}; - -TArray DLoadSaveMenu::SaveGames; -int DLoadSaveMenu::LastSaved = -1; -int DLoadSaveMenu::LastAccessed = -1; - -FSaveGameNode *quickSaveSlot; - -//============================================================================= -// -// Save data maintenance (stored statically) -// -//============================================================================= - -void ClearSaveGames() -{ - for(unsigned i=0;ibNoDelete) - delete DLoadSaveMenu::SaveGames[i]; + savegameManager.ReadSaveStrings(); } - DLoadSaveMenu::SaveGames.Clear(); -} -//============================================================================= -// -// Save data maintenance (stored statically) -// -//============================================================================= - -int DLoadSaveMenu::RemoveSaveSlot (int index) -{ - FSaveGameNode *file = SaveGames[index]; - - if (quickSaveSlot == SaveGames[index]) + void Init(DMenu* parent, FListMenuDescriptor* desc) { - quickSaveSlot = NULL; + Super::Init(parent, desc); + int Width43 = screen->GetHeight() * 4 / 3; + int Left43 = (screen->GetWidth() - Width43) / 2; + float wScale = Width43 / 640.; + savepicLeft = Left43 + int(20 * wScale); + savepicTop = mDesc->mYpos * screen->GetHeight() / 200 ; + savepicWidth = int(240 * wScale); + savepicHeight = int(180 * wScale); + + + FontScale = max(screen->GetHeight() / 480, 1); + rowHeight = std::max(int((NewConsoleFont->GetHeight() + 1) * FontScale), 1); + listboxLeft = savepicLeft + savepicWidth + int(20 * wScale); + listboxTop = savepicTop; + listboxWidth = Width43 + Left43 - listboxLeft - int(30 * wScale); + int listboxHeight1 = screen->GetHeight() - listboxTop - int(20*wScale); + listboxRows = (listboxHeight1 - 1) / rowHeight; + listboxHeight = listboxRows * rowHeight + 1; + listboxRight = listboxLeft + listboxWidth; + listboxBottom = listboxTop + listboxHeight; + + commentLeft = savepicLeft; + commentTop = savepicTop + savepicHeight + int(16 * wScale); + commentWidth = savepicWidth; + commentHeight = listboxHeight - savepicHeight - (16 * wScale); + commentRight = commentLeft + commentWidth; + commentBottom = commentTop + commentHeight; + commentRows = commentHeight / rowHeight; } - if (Selected == index) + + //============================================================================= + // + // + // + //============================================================================= + + void Drawer() override { - Selected = -1; - } - if (!file->bNoDelete) delete file; - SaveGames.Delete(index); - if ((unsigned)index >= SaveGames.Size()) index--; - return index; -} + Super::Drawer(); -//============================================================================= -// -// -// -//============================================================================= + int i; + unsigned j; + bool didSeeSelected = false; -int DLoadSaveMenu::InsertSaveNode (FSaveGameNode *node) -{ - if (SaveGames.Size() == 0) - { - return SaveGames.Push(node); - } - - if (node->bOldVersion) - { // Add node at bottom of list - return SaveGames.Push(node); - } - else - { // Add node at top of list - unsigned int i; - for(i = 0; i < SaveGames.Size(); i++) + // Draw picture area + /* + if (gameaction == ga_loadgame || gameaction == ga_loadgamehidecon || gameaction == ga_savegame) { - if (SaveGames[i]->bOldVersion || - stricmp (node->Title, SaveGames[i]->Title) <= 0) - { - break; - } - } - SaveGames.Insert(i, node); - return i; - } -} - - -//============================================================================= -// -// M_ReadSaveStrings -// -// Find savegames and read their titles -// -//============================================================================= - -void DLoadSaveMenu::ReadSaveStrings () -{ - if (SaveGames.Size() == 0) - { - void *filefirst; - findstate_t c_file; - FString filter; - - LastSaved = LastAccessed = -1; - quickSaveSlot = NULL; - filter = G_BuildSaveName("*"); - filefirst = I_FindFirst (filter.GetChars(), &c_file); - if (filefirst != ((void *)(-1))) - { - do - { - // I_FindName only returns the file's name and not its full path - FString filepath = "";// G_BuildSaveName(I_FindName(&c_file), -1); - - FResourceFile *savegame = FResourceFile::OpenResourceFile(filepath, true, true); - if (savegame != nullptr) - { - FResourceLump *info = savegame->FindLump("info.json"); - if (info == nullptr) - { - // savegame info not found. This is not a savegame so leave it alone. - delete savegame; - continue; - } - auto fr = info->NewReader(); - FString title; - int check = G_ValidateSavegame(fr, &title); - delete savegame; - if (check != 0) - { - FSaveGameNode *node = new FSaveGameNode; - node->Filename = filepath; - node->bOldVersion = check == -1; - node->bMissingWads = check == -2; - node->Title = title; - InsertSaveNode(node); - } - } - } while (I_FindNext (filefirst, &c_file) == 0); - I_FindClose (filefirst); - } - } -} - - -//============================================================================= -// -// -// -//============================================================================= - -void DLoadSaveMenu::NotifyNewSave (const char *file, const char *title, bool okForQuicksave) -{ - FSaveGameNode *node; - - if (file == NULL) - return; - - ReadSaveStrings (); - - // See if the file is already in our list - for (unsigned i=0; iFilename.Compare (file) == 0) -#else - if (node->Filename.CompareNoCase (file) == 0) -#endif - { - node->Title = title; - node->bOldVersion = false; - node->bMissingWads = false; - if (okForQuicksave) - { - if (quickSaveSlot == NULL) quickSaveSlot = node; - LastAccessed = LastSaved = i; - } return; } - } + */ - node = new FSaveGameNode; - node->Title = title; - node->Filename = file; - node->bOldVersion = false; - node->bMissingWads = false; - int index = InsertSaveNode (node); - - if (okForQuicksave) - { - if (quickSaveSlot == NULL) quickSaveSlot = node; - LastAccessed = LastSaved = index; - } -} - -void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave) -{ - DLoadSaveMenu::NotifyNewSave(file, title, okForQuicksave); -} - -//============================================================================= -// -// End of static savegame maintenance code -// -//============================================================================= - -DLoadSaveMenu::DLoadSaveMenu(DMenu* parent, FListMenuDescriptor* desc) - : DListMenu(parent, desc) -{ - ReadSaveStrings(); -} - -void DLoadSaveMenu::Init(DMenu* parent, FListMenuDescriptor* desc) -{ - Super::Init(parent, desc); - int Width43 = screen->GetHeight() * 4 / 3; - int Left43 = (screen->GetWidth() - Width43) / 2; - float wScale = Width43 / 640.; - savepicLeft = Left43 + int(20 * wScale); - savepicTop = mDesc->mYpos * screen->GetHeight() / 200 ; - savepicWidth = int(240 * wScale); - savepicHeight = int(180 * wScale); - - rowHeight = (NewConsoleFont->GetHeight() + 1) * CleanYfac; - listboxLeft = savepicLeft + savepicWidth + int(20 * wScale); - listboxTop = savepicTop; - listboxWidth = Width43 + Left43 - listboxLeft - int(30 * wScale); - int listboxHeight1 = screen->GetHeight() - listboxTop - 10; - listboxRows = (listboxHeight1 - 1) / rowHeight; - listboxHeight = listboxRows * rowHeight + 1; - listboxRight = listboxLeft + listboxWidth; - listboxBottom = listboxTop + listboxHeight; - - commentLeft = savepicLeft; - commentTop = savepicTop + savepicHeight + int(16 * wScale); - commentWidth = savepicWidth; - commentHeight = (51+(screen->GetHeight()>200?10:0))*CleanYfac; - commentRight = commentLeft + commentWidth; - commentBottom = commentTop + commentHeight; -} - -void DLoadSaveMenu::Destroy() -{ - ClearSaveStuff (); -} - -//============================================================================= -// -// -// -//============================================================================= - -void DLoadSaveMenu::UnloadSaveData () -{ - if (SavePic != NULL) - { - delete SavePic; - } - SaveComment.Clear(); - - SavePic = NULL; -} - -//============================================================================= -// -// -// -//============================================================================= - -void DLoadSaveMenu::ClearSaveStuff () -{ - UnloadSaveData(); - if (quickSaveSlot == (FSaveGameNode*)1) - { - quickSaveSlot = NULL; - } -} - -//============================================================================= -// -// -// -//============================================================================= - -void DLoadSaveMenu::ExtractSaveData (int index) -{ - FILE *file; - //PNGHandle *png; - FSaveGameNode *node; - - UnloadSaveData (); - - if ((unsigned)index < SaveGames.Size() && - (node = SaveGames[index]) && - !node->Filename.IsEmpty() && - !node->bOldVersion && - (file = fopen (node->Filename.GetChars(), "rb")) != NULL) - { - // Todo. - } -} - -//============================================================================= -// -// -// -//============================================================================= - -void DLoadSaveMenu::Drawer () -{ - Super::Drawer(); - - FSaveGameNode *node; - int i; - unsigned j; - bool didSeeSelected = false; - - // Draw picture area - /* - if (gameaction == ga_loadgame || gameaction == ga_loadgamehidecon || gameaction == ga_savegame) - { - return; - } - */ - - //V_DrawFrame (savepicLeft, savepicTop, savepicWidth, savepicHeight); - if (SavePic != NULL) - { - DrawTexture(&twod, SavePic, savepicLeft, savepicTop, - DTA_DestWidth, savepicWidth, - DTA_DestHeight, savepicHeight, - DTA_Masked, false, - TAG_DONE); - } - else - { - twod.AddColorOnlyQuad(savepicLeft, savepicTop, savepicWidth, savepicHeight, 0x80000000); - - if (SaveGames.Size() > 0) + PalEntry frameColor(255, 80, 80, 80); // todo: pick a proper color per game. + PalEntry fillColor(160, 0, 0, 0); + DrawFrame(&twod, frameColor, savepicLeft, savepicTop, savepicWidth, savepicHeight, -1); + if (!savegameManager.DrawSavePic(savepicLeft, savepicTop, savepicWidth, savepicHeight)) { - const char *text = - (Selected == -1 || !SaveGames[Selected]->bOldVersion) - ? GStrings("MNU_NOPICTURE") : GStrings("MNU_DIFFVERSION"); - const int textlen = NewConsoleFont->StringWidth (text)*CleanXfac; + twod.AddColorOnlyQuad(savepicLeft, savepicTop, savepicWidth, savepicHeight, fillColor); - DrawText (&twod, NewConsoleFont, CR_GOLD, savepicLeft+(savepicWidth-textlen)/2, - savepicTop+(savepicHeight-rowHeight)/2, text, - DTA_CleanNoMove, true, TAG_DONE); - } - } - - // Draw comment area - //V_DrawFrame (commentLeft, commentTop, commentWidth, commentHeight); - twod.AddColorOnlyQuad(commentLeft, commentTop, commentWidth, commentHeight, 0x80000000); - if (SaveComment.Size()) - { - // I'm not sure why SaveComment would go NULL in this loop, but I got - // a crash report where it was NULL when i reached 1, so now I check - // for that. - for (i = 0; i < SaveComment.Size() && SaveComment[i].Width >= 0 && i < 6; ++i) - { - DrawText (&twod, NewConsoleFont, CR_GOLD, commentLeft, commentTop - + NewConsoleFont->GetHeight()*i*CleanYfac, SaveComment[i].Text, - DTA_CleanNoMove, true, TAG_DONE); - } - } - - // Draw file area - //V_DrawFrame (listboxLeft, listboxTop, listboxWidth, listboxHeight); - twod.AddColorOnlyQuad(listboxLeft, listboxTop, listboxWidth, listboxHeight, 0x80000000); - - if (SaveGames.Size() == 0) - { - const char * text = GStrings("MNU_NOFILES"); - const int textlen = NewConsoleFont->StringWidth (text)*CleanXfac; - - DrawText (&twod, NewConsoleFont, CR_GOLD, listboxLeft+(listboxWidth-textlen)/2, - listboxTop+(listboxHeight-rowHeight)/2, text, - DTA_CleanNoMove, true, TAG_DONE); - return; - } - - for (i = 0, j = TopItem; i < listboxRows && j < SaveGames.Size(); i++,j++) - { - int color; - node = SaveGames[j]; - if (node->bOldVersion) - { - color = CR_BLUE; - } - else if (node->bMissingWads) - { - color = CR_ORANGE; - } - else if ((int)j == Selected) - { - color = CR_WHITE; - } - else - { - color = CR_TAN; - } - - if ((int)j == Selected) - { - twod.AddColorOnlyQuad(listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1), mEntering ? PalEntry(255,255,0,0) : PalEntry(255,0,0,255)); - didSeeSelected = true; - if (!mEntering) + if (savegameManager.SavegameCount() > 0) { - DrawText(&twod, NewConsoleFont, color, - listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, - DTA_CleanNoMove, true, TAG_DONE); + FString text = (Selected == -1 || !savegameManager.GetSavegame(Selected)->bOldVersion) ? GStrings("MNU_NOPICTURE") : GStrings("MNU_DIFFVERSION"); + int textlen = NewSmallFont->StringWidth(text) * CleanXfac; + + DrawText(&twod, NewSmallFont, CR_GOLD, savepicLeft + (savepicWidth - textlen) / 2, + savepicTop + (savepicHeight - rowHeight) / 2, text, DTA_CleanNoMove, true, TAG_DONE); + } + } + + // Draw comment area + DrawFrame(&twod, frameColor, commentLeft, commentTop, commentWidth, commentHeight, -1); + twod.AddColorOnlyQuad(commentLeft, commentTop, commentWidth, commentHeight, fillColor); + + int numlinestoprint = std::min(commentRows, (int)BrokenSaveComment.Size()); + for (int i = 0; i < numlinestoprint; i++) + { + DrawText(&twod, NewConsoleFont, CR_ORANGE, commentLeft / FontScale, (commentTop + rowHeight * i) / FontScale, BrokenSaveComment[i].Text, + DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); + } + + + // Draw file area + DrawFrame(&twod, frameColor, listboxLeft, listboxTop, listboxWidth, listboxHeight, -1); + twod.AddColorOnlyQuad(listboxLeft, listboxTop, listboxWidth, listboxHeight, fillColor); + + if (savegameManager.SavegameCount() == 0) + { + FString text = GStrings("MNU_NOFILES"); + int textlen = int(NewConsoleFont->StringWidth(text) * FontScale); + + DrawText(&twod, NewConsoleFont, CR_GOLD, (listboxLeft + (listboxWidth - textlen) / 2) / FontScale, (listboxTop + (listboxHeight - rowHeight) / 2) / FontScale, text, + DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); + return; + } + + j = TopItem; + for (i = 0; i < listboxRows && j < savegameManager.SavegameCount(); i++) + { + int colr; + auto& node = *savegameManager.GetSavegame(j); + if (node.bOldVersion) + { + colr = CR_RED; + } + else if (node.bMissingWads) + { + colr = CR_YELLOW; + } + else if (j == Selected) + { + colr = CR_WHITE; } else { - DrawText(&twod, NewConsoleFont, CR_WHITE, - listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, savegamestring, - DTA_CleanNoMove, true, TAG_DONE); - - char curs[2] = { NewConsoleFont->GetCursor(), 0 }; - DrawText(&twod, NewConsoleFont, CR_WHITE, - listboxLeft+1+NewConsoleFont->StringWidth (savegamestring)*CleanXfac, - listboxTop+rowHeight*i+CleanYfac, - curs, - DTA_CleanNoMove, true, TAG_DONE); + colr = CR_TAN; } - } - else - { - DrawText(&twod, NewConsoleFont, color, - listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, - DTA_CleanNoMove, true, TAG_DONE); - } - } -} -//============================================================================= -// -// -// -//============================================================================= + //screen->SetClipRect(listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1)); -bool DLoadSaveMenu::MenuEvent (int mkey, bool fromcontroller) -{ - switch (mkey) - { - case MKEY_Up: - if (SaveGames.Size() > 1) - { - if (Selected == -1) Selected = TopItem; - else + if ((int)j == Selected) { - if (--Selected < 0) Selected = SaveGames.Size()-1; - if (Selected < TopItem) TopItem = Selected; - else if (Selected >= TopItem + listboxRows) TopItem = std::max(0, Selected - listboxRows + 1); - } - UnloadSaveData (); - ExtractSaveData (Selected); - } - return true; - - case MKEY_Down: - if (SaveGames.Size() > 1) - { - if (Selected == -1) Selected = TopItem; - else - { - if (unsigned(++Selected) >= SaveGames.Size()) Selected = 0; - if (Selected < TopItem) TopItem = Selected; - else if (Selected >= TopItem + listboxRows) TopItem = std::max(0, Selected - listboxRows + 1); - } - UnloadSaveData (); - ExtractSaveData (Selected); - } - return true; - - case MKEY_PageDown: - if (SaveGames.Size() > 1) - { - if (TopItem >= (int)SaveGames.Size() - listboxRows) - { - TopItem = 0; - if (Selected != -1) Selected = 0; - } - else - { - TopItem = std::min(TopItem + listboxRows, SaveGames.Size() - listboxRows); - if (TopItem > Selected && Selected != -1) Selected = TopItem; - } - UnloadSaveData (); - ExtractSaveData (Selected); - } - return true; - - case MKEY_PageUp: - if (SaveGames.Size() > 1) - { - if (TopItem == 0) - { - TopItem = SaveGames.Size() - listboxRows; - if (Selected != -1) Selected = TopItem; - } - else - { - TopItem = std::max(TopItem - listboxRows, 0); - if (Selected >= TopItem + listboxRows) Selected = TopItem; - } - UnloadSaveData (); - ExtractSaveData (Selected); - } - return true; - - case MKEY_Enter: - return false; // This event will be handled by the subclasses - - case MKEY_MBYes: - { - if ((unsigned)Selected < SaveGames.Size()) - { - int listindex = SaveGames[0]->bNoDelete? Selected-1 : Selected; - remove (SaveGames[Selected]->Filename.GetChars()); - UnloadSaveData (); - Selected = RemoveSaveSlot (Selected); - ExtractSaveData (Selected); - - if (LastSaved == listindex) LastSaved = -1; - else if (LastSaved > listindex) LastSaved--; - if (LastAccessed == listindex) LastAccessed = -1; - else if (LastAccessed > listindex) LastAccessed--; - } - return true; - } - - default: - return Super::MenuEvent(mkey, fromcontroller); - } -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DLoadSaveMenu::MouseEvent(int type, int x, int y) -{ - if (x >= listboxLeft && x < listboxLeft + listboxWidth && - y >= listboxTop && y < listboxTop + listboxHeight) - { - int lineno = (y - listboxTop) / rowHeight; - - if (TopItem + lineno < (int)SaveGames.Size()) - { - Selected = TopItem + lineno; - UnloadSaveData (); - ExtractSaveData (Selected); - if (type == MOUSE_Release) - { - if (MenuEvent(MKEY_Enter, true)) + twod.AddColorOnlyQuad(listboxLeft, listboxTop + rowHeight * i, listboxWidth, rowHeight, mEntering ? PalEntry(255, 255, 0, 0) : PalEntry(255, 0, 0, 255)); + didSeeSelected = true; + if (!mEntering) { - return true; + DrawText(&twod, NewConsoleFont, colr, (listboxLeft + 1) / FontScale, (listboxTop + rowHeight * i + FontScale) / FontScale, node.SaveTitle, + DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); + } + else + { + FStringf s("%s%c", mInput->GetText(), NewConsoleFont->GetCursor()); + int length = int(NewConsoleFont->StringWidth(s) * FontScale); + int displacement = std::min(0, listboxWidth - 2 - length); + DrawText(&twod, NewConsoleFont, CR_WHITE, (listboxLeft + 1 + displacement) / FontScale, (listboxTop + rowHeight * i + FontScale) / FontScale, s, + DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); } } + else + { + DrawText(&twod, NewConsoleFont, colr, (listboxLeft + 1) / FontScale, (listboxTop + rowHeight * i + FontScale) / FontScale, node.SaveTitle, + DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); + } + //screen->ClearClipRect(); + j++; + } + } + + void UpdateSaveComment() + { + //BrokenSaveComment = NewConsoleFont.BreakLines(manager.SaveCommentString, int(commentWidth / FontScale)); + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MenuEvent(int mkey, bool fromcontroller) override + { + auto& manager = savegameManager; + switch (mkey) + { + case MKEY_Up: + if (manager.SavegameCount() > 1) + { + if (Selected == -1) Selected = TopItem; + else + { + if (--Selected < 0) Selected = manager.SavegameCount() - 1; + if (Selected < TopItem) TopItem = Selected; + else if (Selected >= TopItem + listboxRows) TopItem = std::max(0, Selected - listboxRows + 1); + } + manager.UnloadSaveData(); + manager.ExtractSaveData(Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_Down: + if (manager.SavegameCount() > 1) + { + if (Selected == -1) Selected = TopItem; + else + { + if (++Selected >= manager.SavegameCount()) Selected = 0; + if (Selected < TopItem) TopItem = Selected; + else if (Selected >= TopItem + listboxRows) TopItem = std::max(0, Selected - listboxRows + 1); + } + manager.UnloadSaveData(); + manager.ExtractSaveData(Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_PageDown: + if (manager.SavegameCount() > 1) + { + if (TopItem >= manager.SavegameCount() - listboxRows) + { + TopItem = 0; + if (Selected != -1) Selected = 0; + } + else + { + TopItem = std::min(TopItem + listboxRows, int(manager.SavegameCount()) - listboxRows); + if (TopItem > Selected&& Selected != -1) Selected = TopItem; + } + manager.UnloadSaveData(); + manager.ExtractSaveData(Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_PageUp: + if (manager.SavegameCount() > 1) + { + if (TopItem == 0) + { + TopItem = std::max(0, int(manager.SavegameCount()) - listboxRows); + if (Selected != -1) Selected = TopItem; + } + else + { + TopItem = std::max(int(TopItem - listboxRows), 0); + if (Selected >= TopItem + listboxRows) Selected = TopItem; + } + manager.UnloadSaveData(); + manager.ExtractSaveData(Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_Enter: + return false; // This event will be handled by the subclasses + + case MKEY_MBYes: + { + if (Selected < manager.SavegameCount()) + { + Selected = manager.RemoveSaveSlot(Selected); + UpdateSaveComment(); + } + return true; + } + + default: + return Super::MenuEvent(mkey, fromcontroller); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MouseEvent(int type, int x, int y) override + { + auto& manager = savegameManager; + if (x >= listboxLeft && x < listboxLeft + listboxWidth && + y >= listboxTop && y < listboxTop + listboxHeight) + { + int lineno = (y - listboxTop) / rowHeight; + + if (TopItem + lineno < manager.SavegameCount()) + { + Selected = TopItem + lineno; + manager.UnloadSaveData(); + manager.ExtractSaveData(Selected); + UpdateSaveComment(); + if (type == MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + return true; + } + } + } + else Selected = -1; } else Selected = -1; + + return Super::MouseEvent(type, x, y); } - else Selected = -1; - return Super::MouseEvent(type, x, y); -} + //============================================================================= + // + // + // + //============================================================================= -//============================================================================= -// -// -// -//============================================================================= - -bool DLoadSaveMenu::Responder (event_t *ev) -{ - if (ev->type == EV_GUI_Event) + bool Responder(event_t * ev) override { - if (ev->subtype == EV_GUI_KeyDown) + auto& manager = savegameManager; + if (ev->type == EV_GUI_Event) { - if ((unsigned)Selected < SaveGames.Size()) + if (ev->subtype == EV_GUI_KeyDown) { - switch (ev->data1) + if ((unsigned)Selected < manager.SavegameCount()) { - case GK_F1: - if (!SaveGames[Selected]->Filename.IsEmpty()) + switch (ev->data1) { - FStringf workbuf("File on disk:\n%s", SaveGames[Selected]->Filename.GetChars()); - SaveComment = V_BreakLines (NewConsoleFont, 216*screen->GetWidth()/640/CleanXfac, workbuf); - } - return true; + case GK_F1: + manager.SetFileInfo(Selected); + UpdateSaveComment(); + return true; - case GK_DEL: - case '\b': + case GK_DEL: + case '\b': { FString EndString; EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s", - GStrings("MNU_DELETESG"), SaveGames[Selected]->Title.GetChars(), GStrings("PRESSYN")); - M_StartMessage (EndString, 0); + GStrings("MNU_DELETESG"), manager.GetSavegame(Selected)->SaveTitle.GetChars(), GStrings("PRESSYN")); + M_StartMessage(EndString, 0); } return true; + } } } + else if (ev->subtype == EV_GUI_WheelUp) + { + if (TopItem > 0) TopItem--; + return true; + } + else if (ev->subtype == EV_GUI_WheelDown) + { + if (TopItem < manager.SavegameCount() - listboxRows) TopItem++; + return true; + } } - else if (ev->subtype == EV_GUI_WheelUp) - { - if (TopItem > 0) TopItem--; - return true; - } - else if (ev->subtype == EV_GUI_WheelDown) - { - if (TopItem < (int)SaveGames.Size() - listboxRows) TopItem++; - return true; - } + return Super::Responder(ev); } - return Super::Responder(ev); -} - +}; //============================================================================= // @@ -740,164 +447,140 @@ bool DLoadSaveMenu::Responder (event_t *ev) class DSaveMenu : public DLoadSaveMenu { using Super = DLoadSaveMenu; - FSaveGameNode NewSaveNode; - + FString mSaveName; public: - DSaveMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); - void Destroy(); - void DoSave (FSaveGameNode *node); - bool Responder (event_t *ev); - bool MenuEvent (int mkey, bool fromcontroller); -}; + //============================================================================= + // + // + // + //============================================================================= -//============================================================================= -// -// -// -//============================================================================= - -DSaveMenu::DSaveMenu(DMenu *parent, FListMenuDescriptor *desc) -: DLoadSaveMenu(parent, desc) -{ - NewSaveNode.Title = GStrings["NEWSAVE"]; - NewSaveNode.bNoDelete = true; - SaveGames.Insert(0, &NewSaveNode); - TopItem = 0; - if (LastSaved == -1) + DSaveMenu() { - Selected = 0; + savegameManager.InsertNewSaveNode(); + TopItem = 0; + Selected = savegameManager.ExtractSaveData (-1); + UpdateSaveComment(); } - else + + //============================================================================= + // + // + // + //============================================================================= + + void Destroy() override { - Selected = LastSaved + 1; - } - ExtractSaveData (Selected); -} - -//============================================================================= -// -// -// -//============================================================================= - -void DSaveMenu::Destroy() -{ - if (SaveGames[0] == &NewSaveNode) - { - SaveGames.Delete(0); - if (Selected == 0) Selected = -1; - else Selected--; - } -} - -//============================================================================= -// -// -// -//============================================================================= - -void DSaveMenu::DoSave (FSaveGameNode *node) -{ - if (node != &NewSaveNode) - { - //G_SaveGame (node->Filename.GetChars(), savegamestring); - } - else - { - // Find an unused filename and save as that - FString filename; - int i; - FILE *test; - - for (i = 0;; ++i) + if (savegameManager.RemoveNewSaveNode()) { - filename = "";// G_BuildSaveName("save", i); - test = fopen (filename, "rb"); - if (test == NULL) - { - break; - } - fclose (test); + Selected--; } - //G_SaveGame (filename, savegamestring); + Super::Destroy(); } - M_ClearMenus(); -} + //============================================================================= + // + // + // + //============================================================================= -//============================================================================= -// -// -// -//============================================================================= + bool MenuEvent (int mkey, bool fromcontroller) override + { + if (Super::MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (Selected == -1) + { + return false; + } -bool DSaveMenu::MenuEvent (int mkey, bool fromcontroller) -{ - if (Super::MenuEvent(mkey, fromcontroller)) - { - return true; - } - if (Selected == -1) - { + if (mkey == MKEY_Enter) + { + FString SavegameString = (Selected != 0)? savegameManager.GetSavegame(Selected)->SaveTitle : FString(); + mInput = new DTextEnterMenu(this, NewConsoleFont, SavegameString, listboxWidth, false, false); + M_ActivateMenu(mInput); + mEntering = true; + } + else if (mkey == MKEY_Input) + { + mEntering = false; + mSaveName = mInput->GetText(); + mInput = nullptr; + } + else if (mkey == MKEY_Abort) + { + mEntering = false; + mInput = nullptr; + } return false; } - if (mkey == MKEY_Enter) - { - if (Selected != 0) - { - savegamestring = SaveGames[Selected]->Title; - } - else - { - savegamestring = ""; - } - DMenu *input = new DTextEnterMenu(this, savegamestring, 1, fromcontroller); - M_ActivateMenu(input); - mEntering = true; - } - else if (mkey == MKEY_Input) - { - mEntering = false; - DoSave(SaveGames[Selected]); - } - else if (mkey == MKEY_Abort) - { - mEntering = false; - } - return false; -} + //============================================================================= + // + // + // + //============================================================================= -//============================================================================= -// -// -// -//============================================================================= - -bool DSaveMenu::Responder (event_t *ev) -{ - if (ev->subtype == EV_GUI_KeyDown) - { - if (Selected != -1) + bool MouseEvent(int type, int x, int y) override { - switch (ev->data1) + if (mSaveName.Len() > 0) { - case GK_DEL: - case '\b': - // cannot delete 'new save game' item - if (Selected == 0) return true; - break; - - case 'N': - Selected = TopItem = 0; - UnloadSaveData (); + // Do not process events when saving is in progress to avoid update of the current index, + // i.e. Selected member variable must remain unchanged return true; } + + return Super::MouseEvent(type, x, y); + } + + //============================================================================= + // + // + // + //============================================================================= + + bool Responder (event_t *ev) override + { + if (ev->subtype == EV_GUI_KeyDown) + { + if (Selected != -1) + { + switch (ev->data1) + { + case GK_DEL: + case '\b': + // cannot delete 'new save game' item + if (Selected == 0) return true; + break; + + case 'N': + Selected = TopItem = 0; + savegameManager.UnloadSaveData(); + return true; + } + } + } + return Super::Responder(ev); + } + + //============================================================================= + // + // + // + //============================================================================= + + void Ticker() override + { + if (mSaveName.Len() > 0) + { + savegameManager.DoSave(Selected, mSaveName); + mSaveName = ""; } } - return Super::Responder(ev); -} + +}; //============================================================================= // @@ -911,59 +594,44 @@ class DLoadMenu : public DLoadSaveMenu public: - DLoadMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + //============================================================================= + // + // + // + //============================================================================= - bool MenuEvent (int mkey, bool fromcontroller); -}; - -//============================================================================= -// -// -// -//============================================================================= - -DLoadMenu::DLoadMenu(DMenu *parent, FListMenuDescriptor *desc) -: DLoadSaveMenu(parent, desc) -{ - TopItem = 0; - if (LastAccessed != -1) + DLoadMenu() { - Selected = LastAccessed; + TopItem = 0; + Selected = savegameManager.ExtractSaveData(-1); + UpdateSaveComment(); } - ExtractSaveData (Selected); -} + //============================================================================= + // + // + // + //============================================================================= -//============================================================================= -// -// -// -//============================================================================= - -bool DLoadMenu::MenuEvent (int mkey, bool fromcontroller) -{ - if (Super::MenuEvent(mkey, fromcontroller)) - { - return true; - } - if (Selected == -1 || SaveGames.Size() == 0) + bool MenuEvent(int mkey, bool fromcontroller) override { + if (Super::MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (Selected == -1 || savegameManager.SavegameCount() == 0) + { + return false; + } + + if (mkey == MKEY_Enter) + { + savegameManager.LoadSavegame(Selected); + return true; + } return false; } - - if (mkey == MKEY_Enter) - { - //G_LoadGame (SaveGames[Selected]->Filename.GetChars(), true); - if (quickSaveSlot == (FSaveGameNode*)1) - { - quickSaveSlot = SaveGames[Selected]; - } - M_ClearMenus(); - LastAccessed = Selected; - return true; - } - return false; -} +}; static TMenuClassDescriptor _lm("LoadMenu"); static TMenuClassDescriptor _sm("SaveMenu"); diff --git a/source/common/menu/menu.cpp b/source/common/menu/menu.cpp index 051241b92..22328a79e 100644 --- a/source/common/menu/menu.cpp +++ b/source/common/menu/menu.cpp @@ -569,6 +569,7 @@ bool M_SetMenu(FName menu, int param, FName caller) */ } Printf("Attempting to open menu of unknown type '%s'\n", menu.GetChars()); + M_ClearMenus(); return false; } @@ -974,7 +975,7 @@ CCMD(opensavemenu) { gi->MenuOpened(); M_StartControlPanel(false); - M_SetMenu(NAME_SaveMenu); + M_SetMenu(NAME_SaveGameMenu); } } @@ -982,5 +983,5 @@ CCMD(openloadmenu) { gi->MenuOpened(); M_StartControlPanel(false); - M_SetMenu(NAME_LoadMenu); + M_SetMenu(NAME_LoadGameMenu); } diff --git a/source/common/menu/menu.h b/source/common/menu/menu.h index fdc4590f6..4fed91aff 100644 --- a/source/common/menu/menu.h +++ b/source/common/menu/menu.h @@ -138,18 +138,6 @@ enum ENativeFontValues }; extern FGameStartup GameStartupInfo; - -struct FSaveGameNode -{ - FString Title; - FString Filename; - bool bOldVersion; - bool bMissingWads; - bool bNoDelete; - - FSaveGameNode() { bNoDelete = false; } -}; - extern EMenuState menuactive; @@ -682,29 +670,31 @@ public: class DTextEnterMenu : public DMenu { using Super = DMenu; - TArray mEnterString; - FString* mOutString; - unsigned int mEnterSize; - unsigned int mEnterPos; - int mSizeMode; // 1: size is length in chars. 2: also check string width - bool mInputGridOkay; + FString mEnterString; + int mEnterSize; + int mEnterPos; + bool mInputGridOkay; int InputGridX; int InputGridY; - - // [TP] + int CursorSize; bool AllowColors; + FFont *displayFont; + + + void AppendChar(int ch); public: // [TP] Added allowcolors - DTextEnterMenu(DMenu *parent, FString &textbuffer, int sizemode, bool showgrid, bool allowcolors = false); + DTextEnterMenu(DMenu *parent, FFont *dpf, FString textbuffer, int maxlen, bool showgrid, bool allowcolors = false); void Drawer (); bool MenuEvent (int mkey, bool fromcontroller); bool Responder(event_t *ev); bool TranslateKeyboardEvents(); bool MouseEvent(int type, int x, int y); + const char* GetText() { return mEnterString.GetChars(); } }; @@ -757,4 +747,56 @@ template struct TMenuClassDescriptor : public MenuClassDescriptor } }; + +struct FSaveGameNode +{ + FString SaveTitle; + FString Filename; + bool bOldVersion = false; + bool bMissingWads = false; + bool bNoDelete = false; +}; + +struct FSavegameManager +{ +private: + TArray SaveGames; + FSaveGameNode NewSaveNode; + int LastSaved = -1; + int LastAccessed = -1; + TArray SavePicData; + FTexture *SavePic = nullptr; + +public: + int WindowSize = 0; + FString SaveCommentString; + FSaveGameNode *quickSaveSlot = nullptr; + ~FSavegameManager(); + +private: + int InsertSaveNode(FSaveGameNode *node); +public: + void NotifyNewSave(const FString &file, const FString &title, bool okForQuicksave, bool forceQuicksave); + void ClearSaveGames(); + + void ReadSaveStrings(); + void UnloadSaveData(); + + int RemoveSaveSlot(int index); + void LoadSavegame(int Selected); + void DoSave(int Selected, const char *savegamestring); + unsigned ExtractSaveData(int index); + void ClearSaveStuff(); + bool DrawSavePic(int x, int y, int w, int h); + void DrawSaveComment(FFont *font, int cr, int x, int y, int scalefactor); + void SetFileInfo(int Selected); + unsigned SavegameCount(); + FSaveGameNode *GetSavegame(int i); + void InsertNewSaveNode(); + bool RemoveNewSaveNode(); + +}; + +extern FSavegameManager savegameManager; + #endif diff --git a/source/common/menu/menudef.cpp b/source/common/menu/menudef.cpp index 5d664e12e..97a9ad20d 100644 --- a/source/common/menu/menudef.cpp +++ b/source/common/menu/menudef.cpp @@ -47,8 +47,6 @@ #include "optionmenuitems.h" -void ClearSaveGames(); - // Menu-relevant content that gets filled in by scripts. This will get processed after the game has loaded. FString gSkillNames[MAXSKILLS]; FString gVolumeNames[MAXVOLUMES]; @@ -94,7 +92,6 @@ static void DeinitMenus() OptionValues.Clear(); DMenu::CurrentMenu = NULL; DefaultListMenuSettings.mItems.Clear(); - ClearSaveGames(); } static FTexture* GetMenuTexture(const char* const name) diff --git a/source/common/menu/menuinput.cpp b/source/common/menu/menuinput.cpp index 2a5b2faff..95185c072 100644 --- a/source/common/menu/menuinput.cpp +++ b/source/common/menu/menuinput.cpp @@ -63,16 +63,13 @@ CVAR(Bool, m_showinputgrid, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) //============================================================================= // [TP] Added allowcolors -DTextEnterMenu::DTextEnterMenu(DMenu *parent, FString &textbuffer, int sizemode, bool showgrid, bool allowcolors) +DTextEnterMenu::DTextEnterMenu(DMenu *parent, FFont *dpf, FString textbuffer, int maxlen, bool showgrid, bool allowcolors) : DMenu(parent) { - mOutString = &textbuffer; - mEnterSize = 32; // this needs to calculate the size based on screen space (or scroll) - mEnterString.Resize(mEnterSize + 1); - mEnterPos = (unsigned)textbuffer.Len(); - mSizeMode = sizemode; - mInputGridOkay = showgrid || m_showinputgrid; - if (mEnterPos > 0) + mEnterString = textbuffer; + mEnterPos = maxlen; + mInputGridOkay = (showgrid && (m_showinputgrid == 0)) || (m_showinputgrid >= 1); + if (mEnterString.Len() > 0) { InputGridX = INPUTGRID_WIDTH - 1; InputGridY = INPUTGRID_HEIGHT - 1; @@ -84,6 +81,8 @@ DTextEnterMenu::DTextEnterMenu(DMenu *parent, FString &textbuffer, int sizemode, InputGridY = 0; } AllowColors = allowcolors; // [TP] + displayFont = dpf; + CursorSize = displayFont->StringWidth(displayFont->GetCursor()); } //============================================================================= @@ -111,12 +110,7 @@ bool DTextEnterMenu::Responder(event_t *ev) if (ev->subtype == EV_GUI_Char) { mInputGridOkay = false; - if (mEnterPos < mEnterSize && - (mSizeMode == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(mEnterString) < (mEnterSize-1)*8)) - { - mEnterString[mEnterPos] = (char)ev->data1; - mEnterString[++mEnterPos] = 0; - } + AppendChar(ev->data1); return true; } char ch = (char)ev->data1; @@ -124,8 +118,7 @@ bool DTextEnterMenu::Responder(event_t *ev) { if (mEnterPos > 0) { - mEnterPos--; - mEnterString[mEnterPos] = 0; + mEnterString.DeleteLastCharacter(); } } else if (ev->subtype == EV_GUI_KeyDown) @@ -133,17 +126,21 @@ bool DTextEnterMenu::Responder(event_t *ev) if (ch == GK_ESCAPE) { DMenu *parent = mParentMenu; - Close(); parent->MenuEvent(MKEY_Abort, false); + Close(); return true; } else if (ch == '\r') { - if (mEnterString[0]) + if (mEnterString.Len() > 0) { + // [TP] If we allow color codes, colorize the string now. + //if (AllowColors) + //mEnterString = mEnterString.Filter(); + DMenu *parent = mParentMenu; - Close(); parent->MenuEvent(MKEY_Input, false); + Close(); return true; } } @@ -164,8 +161,8 @@ bool DTextEnterMenu::Responder(event_t *ev) bool DTextEnterMenu::MouseEvent(int type, int x, int y) { - const int cell_width = 18 * CleanXfac; - const int cell_height = 12 * CleanYfac; + const int cell_width = 18 * CleanXfac_1; + const int cell_height = 16 * CleanYfac_1; const int screen_y = screen->GetHeight() - INPUTGRID_HEIGHT * cell_height; const int screen_x = (screen->GetWidth() - INPUTGRID_WIDTH * cell_width) / 2; @@ -177,11 +174,11 @@ bool DTextEnterMenu::MouseEvent(int type, int x, int y) { if (MenuEvent(MKEY_Enter, true)) { - //S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + //M_MenuSound(CursorSound); if (m_use_mouse == 2) InputGridX = InputGridY = -1; - return true; } } + return true; } else { @@ -190,8 +187,21 @@ bool DTextEnterMenu::MouseEvent(int type, int x, int y) return Super::MouseEvent(type, x, y); } +//============================================================================= +// +// +// +//============================================================================= + +void DTextEnterMenu::AppendChar(int ch) +{ + FStringf newstring("%s%c%c", mEnterString.GetChars(), ch, displayFont->GetCursor()); + if (mEnterSize < 0 || displayFont->StringWidth(newstring) < mEnterSize) + { + mEnterString.AppendCharacter(ch); + } +} - //============================================================================= // // @@ -237,9 +247,9 @@ bool DTextEnterMenu::MenuEvent (int key, bool fromcontroller) return true; case MKEY_Clear: - if (mEnterPos > 0) - { - mEnterString[--mEnterPos] = 0; + if (mEnterString.Len() > 0) + { + mEnterString.DeleteLastCharacter(); } return true; @@ -250,26 +260,24 @@ bool DTextEnterMenu::MenuEvent (int key, bool fromcontroller) ch = InputGridChars[InputGridX + InputGridY * INPUTGRID_WIDTH]; if (ch == 0) // end { - if (mEnterString[0] != '\0') + if (mEnterString.Len() > 0) { DMenu *parent = mParentMenu; - Close(); parent->MenuEvent(MKEY_Input, false); + Close(); return true; } } else if (ch == '\b') // bs { - if (mEnterPos > 0) + if (mEnterString.Len() > 0) { - mEnterString[--mEnterPos] = 0; + mEnterString.DeleteLastCharacter(); } } - else if (mEnterPos < mEnterSize && - (mSizeMode == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(mEnterString) < (mEnterSize-1)*8)) + else { - mEnterString[mEnterPos] = ch; - mEnterString[++mEnterPos] = 0; + AppendChar(ch); } } return true; @@ -294,7 +302,7 @@ void DTextEnterMenu::Drawer () { const int cell_width = 18 * CleanXfac; const int cell_height = 12 * CleanYfac; - const int top_padding = cell_height / 2 - SmallFont->GetHeight() * CleanYfac / 2; + const int top_padding = cell_height / 2 - displayFont->GetHeight() * CleanYfac / 2; // Darken the background behind the character grid. // Unless we frame it with a border, I think it looks better to extend the @@ -302,7 +310,7 @@ void DTextEnterMenu::Drawer () twod.AddColorOnlyQuad(0 /*screen->GetWidth()/2 - 13 * cell_width / 2*/, screen->GetHeight() - INPUTGRID_HEIGHT * cell_height, screen->GetWidth() /*13 * cell_width*/, - INPUTGRID_HEIGHT * cell_height, 0xff000000); + INPUTGRID_HEIGHT * cell_height, 0xc8000000); if (InputGridX >= 0 && InputGridY >= 0) { @@ -321,42 +329,42 @@ void DTextEnterMenu::Drawer () int width; const int xx = x * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2; const int ch = InputGridChars[y * INPUTGRID_WIDTH + x]; - FTexture *pic = SmallFont->GetChar(ch, CR_DARKGRAY, &width); + FTexture *pic = displayFont->GetChar(ch, CR_DARKGRAY, &width); EColorRange color; int remap; // The highlighted character is yellow; the rest are dark gray. color = (x == InputGridX && y == InputGridY) ? CR_YELLOW : CR_DARKGRAY; - remap = SmallFont->GetColorTranslation(color); + remap = displayFont->GetColorTranslation(color); if (pic != NULL) { // Draw a normal character. - DrawTexture(&twod, pic, xx + cell_width/2 - width*CleanXfac/2, yy + top_padding, + DrawTexture(&twod, pic, xx + cell_width/2 - width*CleanXfac_1/2, yy + top_padding, DTA_TranslationIndex, remap, - DTA_CleanNoMove, true, + DTA_CleanNoMove_1, true, TAG_DONE); } else if (ch == ' ') { // Draw the space as a box outline. We also draw it 50% wider than it really is. - const int x1 = xx + cell_width/2 - width * CleanXfac * 3 / 4; - const int x2 = x1 + width * 3 * CleanXfac / 2; + const int x1 = xx + cell_width/2 - width * CleanXfac_1 * 3 / 4; + const int x2 = x1 + width * 3 * CleanXfac_1 / 2; const int y1 = yy + top_padding; - const int y2 = y1 + SmallFont->GetHeight() * CleanYfac; + const int y2 = y1 + displayFont->GetHeight() * CleanYfac_1; auto palcolor = PalEntry(255, 160, 160, 160); - twod.AddColorOnlyQuad(x1, y1, x2, y1+CleanYfac, palcolor); // top - twod.AddColorOnlyQuad(x1, y2, x2, y2+CleanYfac, palcolor); // bottom - twod.AddColorOnlyQuad(x1, y1+CleanYfac, x1+CleanXfac, y2, palcolor); // left - twod.AddColorOnlyQuad(x2-CleanXfac, y1+CleanYfac, x2, y2, palcolor); // right + twod.AddColorOnlyQuad(x1, y1, x2 - x1, CleanYfac_1, palcolor); // top + twod.AddColorOnlyQuad(x1, y2, x2 - x1, CleanYfac_1, palcolor); // bottom + twod.AddColorOnlyQuad(x1, y1+CleanYfac_1, CleanXfac_1, y2 - y1, palcolor); // left + twod.AddColorOnlyQuad(x2-CleanXfac_1, y1+CleanYfac_1, CleanXfac_1, CleanYfac_1, palcolor); // right } else if (ch == '\b' || ch == 0) { // Draw the backspace and end "characters". const char *const str = ch == '\b' ? "BS" : "ED"; - DrawText(&twod, SmallFont, color, - xx + cell_width/2 - SmallFont->StringWidth(str)*CleanXfac/2, - yy + top_padding, str, DTA_CleanNoMove, true, TAG_DONE); + DrawText(&twod, NewSmallFont, color, + xx + cell_width/2 - displayFont->StringWidth(str)*CleanXfac_1/2, + yy + top_padding, str, DTA_CleanNoMove_1, true, TAG_DONE); } } } diff --git a/source/common/menu/savegamemanager.cpp b/source/common/menu/savegamemanager.cpp new file mode 100644 index 000000000..c7a748139 --- /dev/null +++ b/source/common/menu/savegamemanager.cpp @@ -0,0 +1,507 @@ +/* +** loadsavemenu.cpp +** The load game and save game menus +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "menu/menu.h" +#include "version.h" +#include "m_png.h" +#include "filesystem.h" +#include "v_text.h" +#include "d_event.h" +#include "gstrings.h" +#include "d_gui.h" +#include "v_draw.h" +#include "files.h" +#include "resourcefile.h" +#include "sjson.h" +#include "cmdlib.h" +#include "files.h" +#include "savegamehelp.h" +#include "i_specialpaths.h" +#include "../../platform/win32/i_findfile.h" // This is a temporary direct path. Needs to be fixed when stuff gets cleaned up. + + +FSavegameManager savegameManager; + +//============================================================================= +// +// Save data maintenance +// +//============================================================================= + +void FSavegameManager::ClearSaveGames() +{ + for (unsigned i = 0; ibNoDelete) + delete SaveGames[i]; + } + SaveGames.Clear(); +} + +FSavegameManager::~FSavegameManager() +{ + ClearSaveGames(); +} + +//============================================================================= +// +// Save data maintenance +// +//============================================================================= + +int FSavegameManager::RemoveSaveSlot(int index) +{ + int listindex = SaveGames[0]->bNoDelete ? index - 1 : index; + if (listindex < 0) return index; + + remove(SaveGames[index]->Filename.GetChars()); + UnloadSaveData(); + + FSaveGameNode *file = SaveGames[index]; + + if (quickSaveSlot == SaveGames[index]) + { + quickSaveSlot = nullptr; + } + if (!file->bNoDelete) delete file; + + if (LastSaved == listindex) LastSaved = -1; + else if (LastSaved > listindex) LastSaved--; + if (LastAccessed == listindex) LastAccessed = -1; + else if (LastAccessed > listindex) LastAccessed--; + + SaveGames.Delete(index); + if ((unsigned)index >= SaveGames.Size()) index--; + ExtractSaveData(index); + return index; +} + +//============================================================================= +// +// +// +//============================================================================= + +int FSavegameManager::InsertSaveNode(FSaveGameNode *node) +{ + if (SaveGames.Size() == 0) + { + return SaveGames.Push(node); + } + + if (node->bOldVersion) + { // Add node at bottom of list + return SaveGames.Push(node); + } + else + { // Add node at top of list + unsigned int i; + for (i = 0; i < SaveGames.Size(); i++) + { + if (SaveGames[i]->bOldVersion || node->SaveTitle.CompareNoCase(SaveGames[i]->SaveTitle) <= 0) + { + break; + } + } + SaveGames.Insert(i, node); + return i; + } +} + +//============================================================================= +// +// M_ReadSaveStrings +// +// Find savegames and read their titles +// +//============================================================================= + +void FSavegameManager::ReadSaveStrings() +{ + if (SaveGames.Size() == 0) + { + void *filefirst; + findstate_t c_file; + FString filter; + + LastSaved = LastAccessed = -1; + quickSaveSlot = nullptr; + filter = G_BuildSaveName("*"); + filefirst = I_FindFirst(filter.GetChars(), &c_file); + if (filefirst != ((void *)(-1))) + { + do + { + // I_FindName only returns the file's name and not its full path + FString filepath = G_BuildSaveName(I_FindName(&c_file)); + + FResourceFile *savegame = FResourceFile::OpenResourceFile(filepath, true, true); + if (savegame != nullptr) + { + FResourceLump *info = savegame->FindLump("info.json"); + if (info == nullptr) + { + // savegame info not found. This is not a savegame so leave it alone. + delete savegame; + continue; + } + auto fr = info->NewReader(); + FString title; + int check = G_ValidateSavegame(fr, &title); + delete savegame; + if (check != 0) + { + FSaveGameNode *node = new FSaveGameNode; + node->Filename = filepath; + node->bOldVersion = true; + node->bMissingWads = false; + node->SaveTitle = title; + InsertSaveNode(node); + } + } + } while (I_FindNext (filefirst, &c_file) == 0); + I_FindClose (filefirst); + } + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::NotifyNewSave(const FString &file, const FString &title, bool okForQuicksave, bool forceQuicksave) +{ + FSaveGameNode *node; + + if (file.IsEmpty()) + return; + + ReadSaveStrings(); + + // See if the file is already in our list + for (unsigned i = 0; iFilename.Compare(file) == 0) +#else + if (node->Filename.CompareNoCase(file) == 0) +#endif + { + node->SaveTitle = title; + node->bOldVersion = false; + node->bMissingWads = false; + if (okForQuicksave) + { + if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node; + LastAccessed = LastSaved = i; + } + return; + } + } + + node = new FSaveGameNode; + node->SaveTitle = title; + node->Filename = file; + node->bOldVersion = false; + node->bMissingWads = false; + int index = InsertSaveNode(node); + + if (okForQuicksave) + { + if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node; + LastAccessed = LastSaved = index; + } + else + { + LastAccessed = ++LastSaved; + } +} + +//============================================================================= +// +// Loads the savegame +// +//============================================================================= + +void FSavegameManager::LoadSavegame(int Selected) +{ + //G_LoadGame(SaveGames[Selected]->Filename.GetChars(), true); + if (quickSaveSlot == (FSaveGameNode*)1) + { + quickSaveSlot = SaveGames[Selected]; + } + M_ClearMenus(); + LastAccessed = Selected; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::DoSave(int Selected, const char *savegamestring) +{ + if (Selected != 0) + { + auto node = SaveGames[Selected]; + //G_SaveGame(node->Filename.GetChars(), savegamestring); + } + else + { + // Find an unused filename and save as that + FString filename; + int i; + + for (i = 0;; ++i) + { + filename = G_BuildSaveName(FStringf("save%04d", i)); + if (!FileExists(filename)) + { + break; + } + } + //G_SaveGame(filename, savegamestring); + } + M_ClearMenus(); +} + + +//============================================================================= +// +// +// +//============================================================================= + +unsigned FSavegameManager::ExtractSaveData(int index) +{ + FResourceFile *resf; + FSaveGameNode *node; + + if (index == -1) + { + if (SaveGames.Size() > 0 && SaveGames[0]->bNoDelete) + { + index = LastSaved + 1; + } + else + { + index = LastAccessed < 0? 0 : LastAccessed; + } + } + + UnloadSaveData(); + + if ((unsigned)index < SaveGames.Size() && + (node = SaveGames[index]) && + !node->Filename.IsEmpty() && + !node->bOldVersion && + (resf = FResourceFile::OpenResourceFile(node->Filename.GetChars(), true)) != nullptr) + { + FResourceLump *info = resf->FindLump("info.json"); + if (info == nullptr) + { + // this should not happen because the file has already been verified. + return index; + } + auto fr = info->NewReader(); + auto data = fr.ReadPadded(1); + sjson_context* ctx = sjson_create_context(0, 0, NULL); + if (ctx) + { + sjson_node* root = sjson_decode(ctx, (const char*)data.Data()); + + + FString comment = sjson_get_string(root, "Creation Time", ""); + FString pcomment = sjson_get_string(root, "Comment", ""); + if (comment.Len() > 0) comment += "\n"; + comment += pcomment; + SaveCommentString = comment; + + // Extract pic (todo: let the renderer write a proper PNG file instead of a raw canvas dunp of the software renderer - and make it work for all games.) + FResourceLump *pic = resf->FindLump("savepic.png"); + if (pic != nullptr) + { + FileReader picreader; + + picreader.OpenMemoryArray([=](TArray &array) + { + auto cache = pic->Lock(); + array.Resize(pic->LumpSize); + memcpy(&array[0], cache, pic->LumpSize); + pic->Unlock(); + return true; + }); + PNGHandle *png = M_VerifyPNG(picreader); + if (png != nullptr) + { + SavePic = nullptr; // not yet implemented: PNGTexture_CreateFromFile(png, node->Filename); + delete png; + if (SavePic && SavePic->GetWidth() == 1 && SavePic->GetHeight() == 1) + { + delete SavePic; + SavePic = nullptr; + SavePicData.Clear(); + } + } + } + } + delete resf; + } + return index; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::UnloadSaveData() +{ + if (SavePic != nullptr) + { + delete SavePic; + } + + SaveCommentString = ""; + SavePic = nullptr; + SavePicData.Clear(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::ClearSaveStuff() +{ + UnloadSaveData(); + if (quickSaveSlot == (FSaveGameNode*)1) + { + quickSaveSlot = nullptr; + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +bool FSavegameManager::DrawSavePic(int x, int y, int w, int h) +{ + if (SavePic == nullptr) return false; + DrawTexture(&twod, SavePic, x, y, DTA_DestWidth, w, DTA_DestHeight, h, DTA_Masked, false, TAG_DONE); + return true; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::SetFileInfo(int Selected) +{ + if (!SaveGames[Selected]->Filename.IsEmpty()) + { + SaveCommentString.Format("File on disk:\n%s", SaveGames[Selected]->Filename.GetChars()); + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +unsigned FSavegameManager::SavegameCount() +{ + return SaveGames.Size(); +} + + +//============================================================================= +// +// +// +//============================================================================= + +FSaveGameNode *FSavegameManager::GetSavegame(int i) +{ + return SaveGames[i]; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::InsertNewSaveNode() +{ + NewSaveNode.SaveTitle = GStrings["NEWSAVE"]; + NewSaveNode.bNoDelete = true; + SaveGames.Insert(0, &NewSaveNode); +} + + +//============================================================================= +// +// +// +//============================================================================= + +bool FSavegameManager::RemoveNewSaveNode() +{ + if (SaveGames[0] == &NewSaveNode) + { + SaveGames.Delete(0); + return true; + } + return false; +} + diff --git a/source/common/utility/namedef.h b/source/common/utility/namedef.h index dfb6d7eb4..a6b4ca075 100644 --- a/source/common/utility/namedef.h +++ b/source/common/utility/namedef.h @@ -20,8 +20,8 @@ xx(MainMenu) xx(IngameMenu) xx(HelpMenu) xx(CreditsMenu) -xx(SaveMenu) -xx(LoadMenu) +xx(SaveGameMenu) +xx(LoadGameMenu) xx(SoundMenu) xx(ConfirmPlayerReset) xx(EpisodeMenu)