Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add named save states widget, disable menu entries for numbered/quick… #1466

Merged
merged 2 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 51 additions & 13 deletions src/gui/gui.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/***************************************************************************

Check notice on line 1 in src/gui/gui.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

ℹ Getting worse: Lines of Code in a Single File

The lines of code increases from 1927 to 1960, improve code health by reducing it to 1000. The number of Lines of Code in a single file. More Lines of Code lowers the code health.

Check notice on line 1 in src/gui/gui.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 13.28 to 12.43, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.

Check notice on line 1 in src/gui/gui.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

ℹ Getting worse: Primitive Obsession

The ratio of primitive types in function arguments increases from 40.48% to 40.91%, threshold = 30.0%. The functions in this file have too many primitive types (e.g. int, double, float) in their function argument lists. Using many primitive types lead to the code smell Primitive Obsession. Avoid adding more primitive arguments.
* Copyright (C) 2019 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
Expand Down Expand Up @@ -1065,7 +1065,7 @@
SaveStates::ProtoFile::dumpSchema(schema);
}

static constexpr char globalSaveState[] = "global.sstate";
const auto globalSaveStateName = fmt::format(f_("global{}"), getSaveStatePostfix());

Check warning on line 1068 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1068

Added line #L1068 was not covered by tests

if (ImGui::BeginMenu(_("Save state slots"))) {
if (ImGui::MenuItem(_("Quick-save slot"), "F1")) {
Expand All @@ -1079,25 +1079,44 @@
}
}

ImGui::Separator();
ImGui::MenuItem(_("Show named save states"), nullptr, &m_namedSaveStates.m_show);

Check warning on line 1083 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1082-L1083

Added lines #L1082 - L1083 were not covered by tests

ImGui::EndMenu();
}

if (ImGui::MenuItem(_("Save global state"))) {
saveSaveState(globalSaveState);
}
if (ImGui::MenuItem(_("Save global state"))) saveSaveState(globalSaveStateName);

Check warning on line 1088 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1088

Added line #L1088 was not covered by tests

if (ImGui::BeginMenu(_("Load state slots"))) {
if (ImGui::MenuItem(_("Quick-save slot"), "F2")) loadSaveState(buildSaveStateFilename(0));
auto saveFilename = buildSaveStateFilename(0);
ImGui::BeginDisabled(!saveStateExists(saveFilename));
if (ImGui::MenuItem(_("Quick-load slot"), "F2")) loadSaveState(saveFilename);
ImGui::EndDisabled();

Check warning on line 1094 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1091-L1094

Added lines #L1091 - L1094 were not covered by tests

for (auto i = 1; i < 10; i++) {
const auto str = fmt::format(f_("Slot {}"), i);
if (ImGui::MenuItem(str.c_str())) loadSaveState(buildSaveStateFilename(i));
saveFilename = buildSaveStateFilename(i);
ImGui::BeginDisabled(!saveStateExists(saveFilename));
if (ImGui::MenuItem(str.c_str())) loadSaveState(saveFilename);
ImGui::EndDisabled();
}

Check warning on line 1102 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1098-L1102

Added lines #L1098 - L1102 were not covered by tests

auto namedSaves = m_namedSaveStates.getNamedSaveStates(this);
if (!namedSaves.empty()) {
ImGui::Separator();
for (auto filenamePair : namedSaves) {
if (ImGui::MenuItem(filenamePair.second.c_str())) {
loadSaveState(filenamePair.first);

Check warning on line 1109 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1104-L1109

Added lines #L1104 - L1109 were not covered by tests
}
}

Check warning on line 1111 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1111

Added line #L1111 was not covered by tests
}

ImGui::EndMenu();
}

if (ImGui::MenuItem(_("Load global state"))) loadSaveState(globalSaveState);
ImGui::BeginDisabled(!saveStateExists(globalSaveStateName));
if (ImGui::MenuItem(_("Load global state"))) loadSaveState(globalSaveStateName);
ImGui::EndDisabled();

Check warning on line 1119 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1117-L1119

Added lines #L1117 - L1119 were not covered by tests

ImGui::Separator();
if (ImGui::MenuItem(_("Open LID"))) {
Expand Down Expand Up @@ -1469,6 +1488,10 @@
m_breakpoints.draw(_("Breakpoints"));
}

if (m_namedSaveStates.m_show) {
m_namedSaveStates.draw(this, _("Named Save States"));

Check warning on line 1492 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L1491-L1492

Added lines #L1491 - L1492 were not covered by tests
}

Check notice on line 1494 in src/gui/gui.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

ℹ Getting worse: Complex Method

PCSX::GUI::endFrame increases in cyclomatic complexity from 188 to 192, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check notice on line 1494 in src/gui/gui.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

ℹ Getting worse: Bumpy Road Ahead

PCSX::GUI::endFrame increases from 39 to 40 logical blocks with deeply nested code, threshold is one single block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.
if (m_memoryObserver.m_show) {
m_memoryObserver.draw(_("Memory Observer"));
}
Expand Down Expand Up @@ -2431,24 +2454,31 @@
g_emulator->m_cdrom->check();
}

std::string PCSX::GUI::buildSaveStateFilename(int i) {
std::string PCSX::GUI::getSaveStatePrefix(bool includeSeparator) {

Check warning on line 2457 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2457

Added line #L2457 was not covered by tests
// the ID of the game. Every savestate is marked with the ID of the game it's from.
const auto gameID = g_emulator->m_cdrom->getCDRomID();

// Check if the game has a non-NULL ID or a game hasn't been loaded. Some stuff like PS-X
// EXEs don't have proper IDs
if (!gameID.empty()) {
// For games with an ID of SLUS00213 for example, this will generate a state named
// SLUS00213.sstate
return fmt::format("{}.sstate{}", gameID, i);
return fmt::format("{}{}", gameID, includeSeparator ? "_" : "");

Check warning on line 2465 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2465

Added line #L2465 was not covered by tests
} else {
// For games without IDs, identify them via filename
const auto& iso = PCSX::g_emulator->m_cdrom->getIso()->getIsoPath().filename();
const auto& iso = g_emulator->m_cdrom->getIso()->getIsoPath().filename();

Check warning on line 2468 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2468

Added line #L2468 was not covered by tests
const auto lastFile = iso.empty() ? "BIOS" : iso.string();
return fmt::format("{}.sstate{}", lastFile, i);
return fmt::format("{}{}", lastFile, includeSeparator ? "_" : "");

Check warning on line 2470 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2470

Added line #L2470 was not covered by tests
}
}

std::string PCSX::GUI::getSaveStatePostfix() {
return ".sstate";

Check warning on line 2475 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2474-L2475

Added lines #L2474 - L2475 were not covered by tests
}

std::string PCSX::GUI::buildSaveStateFilename(int i) {
return fmt::format("{}{}{}", getSaveStatePrefix(false), getSaveStatePostfix(), i);

Check warning on line 2479 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2478-L2479

Added lines #L2478 - L2479 were not covered by tests
}

void PCSX::GUI::saveSaveState(std::filesystem::path filename) {
if (filename.is_relative()) {
filename = g_system->getPersistentDir() / filename;
Expand Down Expand Up @@ -2484,7 +2514,15 @@
delete[] buff;

if (!error) SaveStates::load(os.str());
};
}

Check warning on line 2517 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2517

Added line #L2517 was not covered by tests

bool PCSX::GUI::saveStateExists(std::filesystem::path filename) {
if (filename.is_relative()) {
filename = g_system->getPersistentDir() / filename;

Check warning on line 2521 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2519-L2521

Added lines #L2519 - L2521 were not covered by tests
}
ZReader save(new PosixFile(filename));
return !save.failed();
}

Check warning on line 2525 in src/gui/gui.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/gui.cc#L2523-L2525

Added lines #L2523 - L2525 were not covered by tests

void PCSX::GUI::byteRateToString(float rate, std::string& str) {
if (rate >= 1000000000) {
Expand Down
13 changes: 11 additions & 2 deletions src/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "gui/widgets/luaeditor.h"
#include "gui/widgets/luainspector.h"
#include "gui/widgets/memcard_manager.h"
#include "gui/widgets/named_savestates.h"
#include "gui/widgets/pio-cart.h"
#include "gui/widgets/registers.h"
#include "gui/widgets/shader-editor.h"
Expand Down Expand Up @@ -98,6 +99,7 @@ class GUI final : public UI {
typedef Setting<bool, TYPESTRING("ShowAssembly")> ShowAssembly;
typedef Setting<bool, TYPESTRING("ShowDisassembly")> ShowDisassembly;
typedef Setting<bool, TYPESTRING("ShowBreakpoints")> ShowBreakpoints;
typedef Setting<bool, TYPESTRING("ShowNamedSaveStates")> ShowNamedSaveStates;
typedef Setting<bool, TYPESTRING("ShowEvents")> ShowEvents;
typedef Setting<bool, TYPESTRING("ShowHandlers")> ShowHandlers;
typedef Setting<bool, TYPESTRING("ShowKernelLog")> ShowKernelLog;
Expand Down Expand Up @@ -146,8 +148,8 @@ class GUI final : public UI {
Settings<Fullscreen, FullWindowRender, ShowMenu, ShowLog, WindowPosX, WindowPosY, WindowSizeX, WindowSizeY,
IdleSwapInterval, ShowLuaConsole, ShowLuaInspector, ShowLuaEditor, ShowMainVRAMViewer, ShowCLUTVRAMViewer,
ShowVRAMViewer1, ShowVRAMViewer2, ShowVRAMViewer3, ShowVRAMViewer4, ShowMemoryObserver, ShowTypedDebugger,
ShowMemcardManager, ShowRegisters, ShowAssembly, ShowDisassembly, ShowBreakpoints, ShowEvents,
ShowHandlers, ShowKernelLog, ShowCallstacks, ShowSIO1, ShowIsoBrowser, ShowGPULogger, MainFontSize,
ShowMemcardManager, ShowRegisters, ShowAssembly, ShowDisassembly, ShowBreakpoints, ShowNamedSaveStates,
ShowEvents, ShowHandlers, ShowKernelLog, ShowCallstacks, ShowSIO1, ShowIsoBrowser, ShowGPULogger, MainFontSize,
MonoFontSize, GUITheme, AllowMouseCaptureToggle, EnableRawMouseMotion, WidescreenRatio, ShowPIOCartConfig,
ShowMemoryEditor1, ShowMemoryEditor2, ShowMemoryEditor3, ShowMemoryEditor4, ShowMemoryEditor5,
ShowMemoryEditor6, ShowMemoryEditor7, ShowMemoryEditor8, ShowParallelPortEditor, ShowScratchpadEditor,
Expand Down Expand Up @@ -375,6 +377,7 @@ class GUI final : public UI {
Widgets::FileDialog<> m_openBinaryDialog = {[]() { return _("Open Binary"); }};
Widgets::FileDialog<> m_selectBiosDialog = {[]() { return _("Select BIOS"); }};
Widgets::FileDialog<> m_selectEXP1Dialog = {[]() { return _("Select EXP1"); }};
Widgets::NamedSaveStates m_namedSaveStates = {settings.get<ShowNamedSaveStates>().value};
Widgets::Breakpoints m_breakpoints = {settings.get<ShowBreakpoints>().value};
Widgets::IsoBrowser m_isoBrowser = {settings.get<ShowIsoBrowser>().value};

Expand Down Expand Up @@ -405,9 +408,15 @@ class GUI final : public UI {
EventBus::Listener m_listener;

std::string buildSaveStateFilename(int i);
bool saveStateExists(std::filesystem::path filename);

public:
void saveSaveState(std::filesystem::path filename);
void loadSaveState(std::filesystem::path filename);
std::string getSaveStatePrefix(bool includeSeparator);
static std::string getSaveStatePostfix();

private:
void applyTheme(int theme);
void cherryTheme();
void monoTheme();
Expand Down
184 changes: 184 additions & 0 deletions src/gui/widgets/named_savestates.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@

#include "gui/widgets/named_savestates.h"

#include "core/cdrom.h"
#include "core/debug.h"
#include "gui/gui.h"
#include "imgui.h"
#include "imgui_internal.h"

void PCSX::Widgets::NamedSaveStates::draw(GUI* gui, const char* title) {
ImGui::SetNextWindowPos(ImVec2(520, 30), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(title, &m_show)) {
ImGui::End();
return;

Check warning on line 15 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L10-L15

Added lines #L10 - L15 were not covered by tests
}

ImGuiStyle& style = ImGui::GetStyle();
const float heightSeparator = style.ItemSpacing.y;
const float footerHeight = (heightSeparator * 2 + ImGui::GetTextLineHeightWithSpacing()) * 4;
const float glyphWidth = ImGui::GetFontSize();

Check warning on line 21 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L18-L21

Added lines #L18 - L21 were not covered by tests

// Any ImGui::Text() preceeding a ImGui::InputText() will be incorrectly aligned, use this value to awkwardly account for this
// Luckily this value is consistent no matter what the UI main font size is set to
const float verticalAlignAdjust = 3.0f;

Check warning on line 25 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L25

Added line #L25 was not covered by tests

// Create a button for each named save state
ImGui::BeginChild("SaveStatesList", ImVec2(0, -footerHeight), true);
auto saveStates = getNamedSaveStates(gui);
for (const auto& saveStatePair : saveStates) {

Check warning on line 30 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L28-L30

Added lines #L28 - L30 were not covered by tests
// The button is invisible so we can adjust the highlight separately
if (ImGui::InvisibleButton(saveStatePair.second.c_str(),
ImVec2(-FLT_MIN, glyphWidth + style.FramePadding.y * 2),

Check warning on line 33 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L32-L33

Added lines #L32 - L33 were not covered by tests
ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick)) {
// Copy the name to the input box
strcpy(m_namedSaveNameString, saveStatePair.second.c_str());

Check warning on line 36 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L36

Added line #L36 was not covered by tests
// Load the save state if it was a double-click
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
loadSaveState(gui, saveStatePair.first);

Check warning on line 39 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L38-L39

Added lines #L38 - L39 were not covered by tests
}
}
// Add a base coloured rect - highlight it if hovering over the button or the current InputText below matches it
bool matches = StringsHelpers::strcasecmp(m_namedSaveNameString, saveStatePair.second.c_str());
bool hovered = ImGui::IsItemHovered();
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCurrentContext()->LastItemData.Rect.Min, ImGui::GetCurrentContext()->LastItemData.Rect.Max,

Check warning on line 46 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L43-L46

Added lines #L43 - L46 were not covered by tests
ImGui::ColorConvertFloat4ToU32(
ImGui::GetStyle()
.Colors[matches ? ImGuiCol_HeaderActive : (hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header)]));

Check warning on line 49 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L48-L49

Added lines #L48 - L49 were not covered by tests
// Finally the butotn text
ImGui::GetWindowDrawList()->AddText(
ImVec2(ImGui::GetCurrentContext()->LastItemData.Rect.Min.x + style.FramePadding.y,
ImGui::GetCurrentContext()->LastItemData.Rect.Min.y + style.FramePadding.y),

Check warning on line 53 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L51-L53

Added lines #L51 - L53 were not covered by tests
ImGui::GetColorU32(ImGuiCol_Text), saveStatePair.second.c_str());
}
ImGui::EndChild();

Check warning on line 56 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L56

Added line #L56 was not covered by tests

// Move the leading text down to align with the following InputText()
float posY = ImGui::GetCursorPosY();
ImGui::SetCursorPosY(posY + verticalAlignAdjust);

Check warning on line 60 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L59-L60

Added lines #L59 - L60 were not covered by tests

ImGui::Text(_("Filename: "));
ImGui::SameLine();
ImGui::Text(gui->getSaveStatePrefix(true).c_str());
ImGui::SameLine(0.0f, 0.0f);

Check warning on line 65 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L62-L65

Added lines #L62 - L65 were not covered by tests

// Restore the vertical value
ImGui::SetCursorPosY(posY);

Check warning on line 68 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L68

Added line #L68 was not covered by tests

// Ensure that we don't add invalid characters to the filename
// This also filters on pasted text
struct TextFilters {
static int FilterNonPathCharacters(ImGuiInputTextCallbackData* data) {

Check warning on line 73 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L73

Added line #L73 was not covered by tests
// Filter the core problematic characters for Windows and Linux
// Anything remaining outside of [a-zA-Z0-9._-] is also allowed
switch (data->EventChar) {
case '\\':

Check warning on line 77 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L76-L77

Added lines #L76 - L77 were not covered by tests
case '/':
case '<':
case '>':
case '|':
case '\"':
case ':':
case '?':
case '*':
case 0:
return 1;

Check warning on line 87 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L87

Added line #L87 was not covered by tests
}
return 0;

Check warning on line 89 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L89

Added line #L89 was not covered by tests
}
};

ImGui::InputTextWithHint("##SaveNameInput", "Enter the name of your save state here", m_namedSaveNameString,

Check warning on line 93 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L93

Added line #L93 was not covered by tests
NAMED_SAVE_STATE_LENGTH_MAX, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterNonPathCharacters);
ImGui::SameLine(0.0f, 0.0f);

Check warning on line 95 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L95

Added line #L95 was not covered by tests

// Trailing text alignment also needs adjusting, but in the opposite direction
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - verticalAlignAdjust);
ImGui::Text(gui->getSaveStatePostfix().c_str());

Check warning on line 99 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L98-L99

Added lines #L98 - L99 were not covered by tests

ImGui::Separator();

Check warning on line 101 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L101

Added line #L101 was not covered by tests

// Add various buttons based on whether a save state exists with that name
auto found = std::find_if(saveStates.begin(), saveStates.end(), [=](auto saveStatePair) {
return strlen(m_namedSaveNameString) > 0 &&
StringsHelpers::strcasecmp(m_namedSaveNameString, saveStatePair.second.c_str());

Check warning on line 106 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L104-L106

Added lines #L104 - L106 were not covered by tests
});
bool exists = found != saveStates.end();

Check warning on line 108 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L108

Added line #L108 was not covered by tests

float width = ImGui::GetCurrentContext()->LastItemData.Rect.GetWidth();
ImVec2 buttonDims = ImVec2(-FLT_MIN, glyphWidth + style.FramePadding.y * 2);
ImVec2 halfDims = ImVec2((width - (style.FramePadding.x * 6.0f)) * 0.5f, buttonDims.y);

Check warning on line 112 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L110-L112

Added lines #L110 - L112 were not covered by tests

if (!exists) {
if (strlen(m_namedSaveNameString) > 0) {

Check warning on line 115 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L114-L115

Added lines #L114 - L115 were not covered by tests
// The save state doesn't exist, and the name is valid
std::string pathStr = fmt::format("{}{}{}", gui->getSaveStatePrefix(true), m_namedSaveNameString, gui->getSaveStatePostfix());
std::filesystem::path newPath = pathStr;
if (ImGui::Button(_("Create save"), buttonDims)) {
saveSaveState(gui, newPath);

Check warning on line 120 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L117-L120

Added lines #L117 - L120 were not covered by tests
}
}

Check warning on line 122 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L122

Added line #L122 was not covered by tests
} else {
// The save state exists
if (ImGui::Button(_("Overwrite save"), halfDims)) {
saveSaveState(gui, found->first);

Check warning on line 126 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L125-L126

Added lines #L125 - L126 were not covered by tests
}
ImGui::SameLine();
if (ImGui::Button(_("Load save"), halfDims)) {
loadSaveState(gui, found->first);

Check warning on line 130 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L128-L130

Added lines #L128 - L130 were not covered by tests
}
// Add a deliberate spacer before the delete button
// There is no delete confirmation, and this makes a mis-click less likely to hit it
ImGui::Dummy(buttonDims);
if (ImGui::Button(_("Delete save"), buttonDims)) {
deleteSaveState(found->first);

Check warning on line 136 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L134-L136

Added lines #L134 - L136 were not covered by tests
}
}

ImGui::End();
}

Check warning on line 141 in src/gui/widgets/named_savestates.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Complex Method

PCSX::Widgets::NamedSaveStates::draw has a cyclomatic complexity of 25, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 141 in src/gui/widgets/named_savestates.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Bumpy Road Ahead

PCSX::Widgets::NamedSaveStates::draw has 3 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check warning on line 141 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L140-L141

Added lines #L140 - L141 were not covered by tests

std::vector<std::pair<std::filesystem::path, std::string>> PCSX::Widgets::NamedSaveStates::getNamedSaveStates(GUI* gui) {
std::vector<std::pair<std::filesystem::path, std::string>> names;

Check warning on line 144 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L143-L144

Added lines #L143 - L144 were not covered by tests

// Get the filename prefix to use, which follows the typical save state format, with a separator between gameID and name
std::string prefix = gui->getSaveStatePrefix(true);
std::string postfix = gui->getSaveStatePostfix();

Check warning on line 148 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L147-L148

Added lines #L147 - L148 were not covered by tests

// Loop the root directory
std::error_code ec;
if (std::filesystem::exists(std::filesystem::current_path(), ec)) {
for (const auto& entry : std::filesystem::directory_iterator(std::filesystem::current_path(), ec)) {
if (entry.is_regular_file()) {
std::string filename = entry.path().filename().string();
if (filename.find(prefix) == 0 &&
filename.rfind(postfix) == filename.length() - postfix.length()) {
std::string niceName = filename.substr(prefix.length(), filename.length() - (prefix.length() + postfix.length()));

Check warning on line 158 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L151-L158

Added lines #L151 - L158 were not covered by tests
// Only support names that fit within the character limit
if (niceName.length() < NAMED_SAVE_STATE_LENGTH_MAX) {
names.emplace_back(entry.path(), niceName);

Check warning on line 161 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L160-L161

Added lines #L160 - L161 were not covered by tests
}
}
}
}

Check warning on line 165 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L163-L165

Added lines #L163 - L165 were not covered by tests
}

return names;
}

Check warning on line 169 in src/gui/widgets/named_savestates.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Deep, Nested Complexity

PCSX::Widgets::NamedSaveStates::getNamedSaveStates has a nested complexity depth of 5, threshold = 4. This function contains deeply nested logic such as if statements and/or loops. The deeper the nesting, the lower the code health.

Check warning on line 169 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L168-L169

Added lines #L168 - L169 were not covered by tests

void PCSX::Widgets::NamedSaveStates::saveSaveState(GUI* gui, std::filesystem::path saveStatePath) {
g_system->log(LogClass::UI, "Saving named save state: %s\n", saveStatePath.filename().string().c_str());
gui->saveSaveState(saveStatePath);
}

Check warning on line 174 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L171-L174

Added lines #L171 - L174 were not covered by tests

void PCSX::Widgets::NamedSaveStates::loadSaveState(GUI* gui, std::filesystem::path saveStatePath) {
g_system->log(LogClass::UI, "Loading named save state: %s\n", saveStatePath.filename().string().c_str());
gui->loadSaveState(saveStatePath);
}

Check warning on line 179 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L176-L179

Added lines #L176 - L179 were not covered by tests

void PCSX::Widgets::NamedSaveStates::deleteSaveState(std::filesystem::path saveStatePath) {
g_system->log(LogClass::UI, "Deleting named save state: %s\n", saveStatePath.filename().string().c_str());
std::remove(saveStatePath.string().c_str());
}

Check warning on line 184 in src/gui/widgets/named_savestates.cc

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.cc#L181-L184

Added lines #L181 - L184 were not covered by tests
34 changes: 34 additions & 0 deletions src/gui/widgets/named_savestates.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

#pragma once

#include <string>
#include <vector>
#include <filesystem>

namespace PCSX {

class GUI;
namespace Widgets {

class NamedSaveStates {
public:
NamedSaveStates(bool& show) : m_show(show) {}

Check warning on line 15 in src/gui/widgets/named_savestates.h

View check run for this annotation

Codecov / codecov/patch

src/gui/widgets/named_savestates.h#L15

Added line #L15 was not covered by tests
void draw(GUI* gui, const char* title);
bool& m_show;

std::vector<std::pair<std::filesystem::path, std::string>> getNamedSaveStates(GUI* gui);

private:

static constexpr int NAMED_SAVE_STATE_LENGTH_MAX = 128;

void saveSaveState(GUI* gui, std::filesystem::path saveStatePath);
void loadSaveState(GUI* gui, std::filesystem::path saveStatePath);
void deleteSaveState(std::filesystem::path saveStatePath);

char m_namedSaveNameString[NAMED_SAVE_STATE_LENGTH_MAX] = "";
};

} // namespace Widgets

} // namespace PCSX
Loading
Loading