| e8555c89 Andy McCall |
1 |
#include "ImGuiTrainer.hh" |
| 2 |
|
| 3 |
#include "CustomFont.h" |
| 4 |
#include "ImGuiCpp.hh" |
| 5 |
#include "ImGuiManager.hh" |
| 6 |
#include "ImGuiUtils.hh" |
| 7 |
|
| 8 |
#include "StringOp.hh" |
| 9 |
#include "xrange.hh" |
| 10 |
|
| 11 |
#include <imgui_stdlib.h> |
| 12 |
|
| 13 |
#include <algorithm> |
| 14 |
#include <ranges> |
| 15 |
|
| 16 |
namespace openmsx { |
| 17 |
|
| 18 |
using namespace std::literals; |
| 19 |
|
| 20 |
void ImGuiTrainer::save(ImGuiTextBuffer& buf) |
| 21 |
{ |
| 22 |
savePersistent(buf, *this, persistentElements); |
| 23 |
} |
| 24 |
|
| 25 |
void ImGuiTrainer::loadLine(std::string_view name, zstring_view value) |
| 26 |
{ |
| 27 |
loadOnePersistent(name, value, *this, persistentElements); |
| 28 |
} |
| 29 |
|
| 30 |
void ImGuiTrainer::paint(MSXMotherBoard* ) |
| 31 |
{ |
| 32 |
if (!show) return; |
| 33 |
|
| 34 |
if (!trainers) { |
| 35 |
if (!manager.getReactor().isFullyStarted()) { |
| 36 |
|
| 37 |
return; |
| 38 |
} |
| 39 |
|
| 40 |
|
| 41 |
trainers = manager.execute(TclObject("trainer::load_trainers")).value_or(TclObject{}); |
| 42 |
|
| 43 |
gameNames = to_vector(std::views::transform(xrange(trainers->size() / 2), [&](auto i) { |
| 44 |
return std::string(trainers->getListIndexUnchecked(2 * i).getString()); |
| 45 |
})); |
| 46 |
std::ranges::sort(gameNames, StringOp::caseless{}); |
| 47 |
} |
| 48 |
auto activeGame = manager.execute(makeTclList("set", "trainer::active_trainer")).value_or(TclObject{}); |
| 49 |
auto activeList = manager.execute(makeTclList("set", "trainer::items_active")).value_or(TclObject{}); |
| 50 |
|
| 51 |
std::optional<std::string> newGame; |
| 52 |
std::optional<int> toggleItem; |
| 53 |
bool all = false; |
| 54 |
bool none = false; |
| 55 |
|
| 56 |
ImGui::SetNextWindowSize(gl::vec2{28, 26} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver); |
| 57 |
im::Window("Trainer Selector", &show, ImGuiWindowFlags_HorizontalScrollbar, [&]{ |
| 58 |
ImGui::TextUnformatted("Select Game:"sv); |
| 59 |
zstring_view displayName = activeGame.getString(); |
| 60 |
if (displayName.empty()) displayName = "none"; |
| 61 |
bool useFilter = im::TreeNode("filter", [&]{ |
| 62 |
ImGui::InputText(ICON_IGFD_FILTER, &filterString); |
| 63 |
HelpMarker("A list of substrings that must be part of the game name.\n" |
| 64 |
"\n" |
| 65 |
"For example: enter 'vamp' to search for 'Akumajyo Drakyula - Vampire Killer'."); |
| 66 |
}); |
| 67 |
auto drawGameNames = [&](size_t num, auto getName) { |
| 68 |
int selectedIdx = -1; |
| 69 |
if (ImGui::IsWindowAppearing()) { |
| 70 |
for (auto i : xrange(num)) { |
| 71 |
if (getName(i) == displayName) { |
| 72 |
selectedIdx = narrow<int>(i) + 1; |
| 73 |
} |
| 74 |
} |
| 75 |
} |
| 76 |
im::ListClipper(1 + num, selectedIdx, [&](int i) { |
| 77 |
if (i == 0) { |
| 78 |
if (ImGui::Selectable("none", displayName == "none")) { |
| 79 |
newGame = "deactivate"; |
| 80 |
} |
| 81 |
} else { |
| 82 |
auto name = getName(size_t(i - 1)); |
| 83 |
if (ImGui::Selectable(name.c_str(), name == displayName)) { |
| 84 |
newGame = name; |
| 85 |
} |
| 86 |
if (i == selectedIdx) { |
| 87 |
ImGui::SetItemDefaultFocus(); |
| 88 |
} |
| 89 |
} |
| 90 |
}); |
| 91 |
}; |
| 92 |
auto getGameName = [&](size_t i) { return gameNames[i]; }; |
| 93 |
if (useFilter) { |
| 94 |
auto indices = to_vector(xrange(gameNames.size())); |
| 95 |
filterIndices(filterString, getGameName, indices); |
| 96 |
im::ListBox("##game", {-FLT_MIN, 0.0f}, [&]{ |
| 97 |
drawGameNames(indices.size(), [&](size_t i) { return gameNames[indices[i]]; }); |
| 98 |
}); |
| 99 |
} else { |
| 100 |
ImGui::SetNextItemWidth(-FLT_MIN); |
| 101 |
im::Combo("##game", displayName.c_str(), [&]{ |
| 102 |
drawGameNames(gameNames.size(), getGameName); |
| 103 |
}); |
| 104 |
} |
| 105 |
ImGui::Separator(); |
| 106 |
|
| 107 |
im::Disabled(activeGame.getString().empty(), [&]{ |
| 108 |
ImGui::AlignTextToFramePadding(); |
| 109 |
ImGui::TextUnformatted("Select Cheats:"sv); |
| 110 |
ImGui::SameLine(); |
| 111 |
all = ImGui::Button("All"); |
| 112 |
ImGui::SameLine(); |
| 113 |
none = ImGui::Button("None"); |
| 114 |
|
| 115 |
TclObject items = trainers->getOptionalDictValue(activeGame).value_or(TclObject{}) |
| 116 |
.getOptionalDictValue(TclObject("items")).value_or(TclObject{}); |
| 117 |
auto numItems = std::min(activeList.size(), items.size() / 2); |
| 118 |
for (auto i : xrange(numItems)) { |
| 119 |
bool active = activeList.getListIndexUnchecked(i).getOptionalBool().value_or(false); |
| 120 |
auto name = items.getListIndexUnchecked(2 * i + 0).getString(); |
| 121 |
if (ImGui::Checkbox(name.c_str(), &active)) { |
| 122 |
toggleItem = i; |
| 123 |
} |
| 124 |
} |
| 125 |
}); |
| 126 |
}); |
| 127 |
|
| 128 |
if (newGame) { |
| 129 |
manager.execute(makeTclList("trainer", *newGame)); |
| 130 |
} else if (toggleItem) { |
| 131 |
manager.execute(makeTclList("trainer", activeGame, *toggleItem + 1)); |
| 132 |
} else if (all || none) { |
| 133 |
auto cmd = makeTclList("trainer", activeGame); |
| 134 |
for (auto i : xrange(activeList.size())) { |
| 135 |
if (activeList.getListIndexUnchecked(i).getOptionalBool().value_or(false) == none) { |
| 136 |
cmd.addListElement(narrow<int>(i + 1)); |
| 137 |
} |
| 138 |
} |
| 139 |
manager.execute(cmd); |
| 140 |
} |
| 141 |
} |
| 142 |
|
| 143 |
} |