OXYGENHUB
◆ main openmsx / src / imgui / ImGuiTrainer.cc
4.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
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* /*motherBoard*/)
31
{
32
	if (!show) return;
33
34
	if (!trainers) {
35
		if (!manager.getReactor().isFullyStarted()) {
36
			// trainer.tcl script (actually lazy.tcl) has not yet been executed
37
			return;
38
		}
39
		// Only query/sort this list once instead of on every frame.
40
		// If loading fails, use an empty dict (don't keep on retying).
41
		trainers = manager.execute(TclObject("trainer::load_trainers")).value_or(TclObject{});
42
		// TODO use c++23 stride_view
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()) { // The ComboBox popup window
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
} // namespace openmsx