24#include <imgui_stdlib.h>
29using namespace std::literals;
44 if (
auto* firmwareSwitch =
dynamic_cast<BooleanSetting*
>(controller.findSetting(
"firmwareswitch"))) {
45 Checkbox(hotKey,
"Firmware switch", *firmwareSwitch);
49 auto& pauseSetting = reactor.getGlobalSettings().getPauseSetting();
50 bool pause = pauseSetting.getBoolean();
52 ImGui::MenuItem(
"Pause", shortCut.c_str(), &pause)) {
53 pauseSetting.setBoolean(pause);
57 ImGui::MenuItem(
"Reset", shortCut.c_str(),
nullptr, motherBoard !=
nullptr)) {
61 auto& powerSetting = reactor.getGlobalSettings().getPowerSetting();
62 bool power = powerSetting.getBoolean();
64 ImGui::MenuItem(
"Power", shortCut.c_str(), &power)) {
65 powerSetting.setBoolean(power);
76 paintSelectMachine(motherBoard);
84void ImGuiMachine::paintSelectMachine(
const MSXMotherBoard* motherBoard)
86 ImGui::SetNextWindowSize(
gl::vec2{29, 26} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
90 auto currentInstance = reactor.getMachineID();
91 if (instances.size() > 1 || currentInstance.empty()) {
92 ImGui::TextUnformatted(
"Instances:"sv);
93 HelpMarker(
"Switch between different machine instances. Right-click to delete an instance.");
95 float height = (std::min(4.0f, float(instances.size())) + 0.25f) * ImGui::GetTextLineHeightWithSpacing();
96 im::ListBox(
"##empty", {-FLT_MIN, height}, [&]{
97 im::ID_for_range(instances.size(), [&](int i) {
98 const auto& name = instances[i];
99 bool isCurrent = name == currentInstance;
100 auto board = reactor.getMachine(name);
101 std::string display = [&]{
103 auto configName = board->getMachineName();
104 auto* info = findMachineInfo(configName);
106 auto time = (board->getCurrentTime() - EmuTime::zero()).toDouble();
107 return strCat(info->displayName,
" (", formatTime(time),
')');
109 return std::string(name);
112 if (ImGui::Selectable(display.c_str(), isCurrent)) {
113 manager.executeDelayed(makeTclList(
"activate_machine", name));
115 im::PopupContextItem(
"instance context menu", [&]{
116 if (ImGui::Selectable(
"Delete instance")) {
117 manager.executeDelayed(makeTclList(
"delete_machine", name));
128 auto* info = findMachineInfo(configName);
130 std::string display =
strCat(
"Current machine: ", info->displayName);
132 printConfigInfo(*info);
134 if (newMachineConfig.empty()) newMachineConfig = configName;
135 if (
auto& defaultMachine = reactor.getMachineSetting();
136 defaultMachine.getString() != configName) {
137 if (ImGui::Button(
"Make this the default machine")) {
138 defaultMachine.setValue(TclObject(configName));
140 simpleToolTip(
"Use this as the default MSX machine when openMSX starts.");
146 auto& allMachines = getAllMachines();
147 std::string filterDisplay =
"filter";
148 if (!filterType.empty() || !filterRegion.empty() || !filterString.empty())
strAppend(filterDisplay,
':');
149 if (!filterType.empty())
strAppend(filterDisplay,
' ', filterType);
150 if (!filterRegion.empty())
strAppend(filterDisplay,
' ', filterRegion);
151 if (!filterString.empty())
strAppend(filterDisplay,
' ', filterString);
153 im::TreeNode(filterDisplay.c_str(), ImGuiTreeNodeFlags_DefaultOpen, [&]{
154 displayFilterCombo(filterType,
"Type", allMachines);
155 displayFilterCombo(filterRegion,
"Region", allMachines);
156 if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
157 ImGui::InputText(ICON_IGFD_FILTER, &filterString);
158 simpleToolTip(
"A list of substrings that must be part of the machine name.\n"
160 "For example: enter 'pa' to search for 'Panasonic' machines. "
161 "Then refine the search by appending '<space>st' to find the 'Panasonic FS-A1ST' machine.");
169 auto it =
ranges::find(filteredMachines, newMachineConfig,
170 [&](
auto idx) {
return allMachines[idx].configName; });
171 bool inFilteredList = it != filteredMachines.end();
172 int selectedIdx = inFilteredList ? narrow<int>(*it) : -1;
174 im::ListBox(
"##list", {-FLT_MIN, -ImGui::GetFrameHeightWithSpacing()}, [&]{
176 auto idx = filteredMachines[i];
177 auto& info = allMachines[idx];
178 bool ok = getTestResult(info).empty();
179 im::StyleColor(!ok, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
180 bool selected = info.configName == newMachineConfig;
181 if (ImGui::Selectable(info.displayName.c_str(), selected, ImGuiSelectableFlags_AllowDoubleClick)) {
182 newMachineConfig = info.configName;
183 if (ok && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
184 showSelectMachine = false;
185 manager.executeDelayed(makeTclList(
"machine", newMachineConfig));
189 if (ImGui::IsWindowAppearing()) ImGui::SetScrollHereY();
191 if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_Stationary)) {
193 printConfigInfo(info);
201 if (!inFilteredList)
return false;
202 auto* info = findMachineInfo(newMachineConfig);
203 if (!info)
return false;
204 const auto&
test = getTestResult(*info);
208 if (ImGui::Button(
"Replace current machine")) {
209 manager.executeDelayed(
makeTclList(
"machine", newMachineConfig));
211 simpleToolTip(
"Replace the current machine with the selected machine. "
212 "Alternatively you can also double click in the list above (in addition that also closes this window).");
213 ImGui::SameLine(0.0f, 10.0f);
214 if (ImGui::Button(
"New machine instance")) {
215 std::string script =
strCat(
216 "set id [create_machine]\n"
217 "set err [catch {${id}::load_machine ", newMachineConfig,
"} error_result]\n"
219 " delete_machine $id\n"
220 " error \"Error activating new machine: $error_result\"\n"
222 " activate_machine $id\n"
224 manager.executeDelayed(TclObject(script));
226 simpleToolTip(
"Create a new machine instance (next to the current machine). "
227 "Later you can switch between the different instances (like different tabs in a web browser).");
232void ImGuiMachine::paintTestHardware()
234 ImGui::SetNextWindowSize(
gl::vec2{41.0f, 32.5f} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
235 im::Window(
"Test MSX hardware", &showTestHardware, [&]{
236 auto formatNum = [](
size_t num, std::string_view text) {
238 return strCat(
"No ", text,
"###", text);
240 return strCat(num,
' ', text,
"###", text);
243 auto printList = [](
const auto& indices,
const auto& infos) {
244 auto n = narrow<int>(indices.size());
245 auto clamped = std::clamp(narrow_cast<float>(n), 2.5f, 7.5f);
246 gl::vec2 listSize{0.0f, clamped * ImGui::GetTextLineHeightWithSpacing()};
248 ImGui::SetNextItemWidth(-FLT_MIN);
251 auto& info = infos[indices[i]];
253 assert(info.testResult);
261 auto& allMachines = getAllMachines();
262 std::vector<size_t> workingMachines, nonWorkingMachines;
263 for (
auto [idx, info] :
enumerate(allMachines)) {
264 if (!info.testResult.has_value()) {
265 if (!doTest)
continue;
268 const auto& result = getTestResult(info);
269 (result.empty() ? workingMachines : nonWorkingMachines).push_back(idx);
271 bool allMachinesTested = allMachines.size() == (workingMachines.size() + nonWorkingMachines.size());
274 im::TreeNode(
"Machines", ImGuiTreeNodeFlags_DefaultOpen, [&]{
275 auto workingText = formatNum(workingMachines.size(),
"working machines");
277 printList(workingMachines, allMachines);
279 auto nonWorkingText = formatNum(nonWorkingMachines.size(),
"non-working machines");
280 int flags = nonWorkingMachines.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen;
282 printList(nonWorkingMachines, allMachines);
287 auto& allExtensions = manager.media->getAllExtensions();
288 std::vector<size_t> workingExtensions, nonWorkingExtensions;
289 for (
auto [idx, info] :
enumerate(allExtensions)) {
290 if (!info.testResult.has_value()) {
291 if (!doTest)
continue;
294 const auto& result = manager.media->getTestResult(info);
295 (result.empty() ? workingExtensions : nonWorkingExtensions).push_back(idx);
297 bool allExtensionsTested = allExtensions.size() == (workingExtensions.size() + nonWorkingExtensions.size());
300 im::TreeNode(
"Extensions", ImGuiTreeNodeFlags_DefaultOpen, [&]{
301 auto workingText = formatNum(workingExtensions.size(),
"working extensions");
303 printList(workingExtensions, allExtensions);
305 auto nonWorkingText = formatNum(nonWorkingExtensions.size(),
"non-working extensions");
306 int flags = nonWorkingExtensions.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen;
308 printList(nonWorkingExtensions, allExtensions);
314 if (!nonWorkingMachines.empty() || !nonWorkingExtensions.empty()) {
315 im::Disabled(!allMachinesTested || !allExtensionsTested, [&]{
316 if (ImGui::Button(
"Copy list of non-working hardware to clipboard")) {
318 auto print = [&](std::string_view label,
const auto& indices,
const auto& infos) {
319 if (!indices.empty()) {
320 strAppend(result,
"Non-working ", label,
":\n");
321 for (
auto idx : indices) {
322 const auto& info = infos[idx];
323 strAppend(result,
'\t', info.displayName,
" (", info.configName,
")\n",
324 "\t\t", *info.testResult,
'\n');
329 print(
"machines", nonWorkingMachines, allMachines);
330 print(
"extensions", nonWorkingExtensions, allExtensions);
331 ImGui::SetClipboardText(result.c_str());
336 im::Disabled(!allMachinesTested || !allExtensionsTested, [&]{
337 if (ImGui::Button(
"Rerun test")) {
338 manager.media->resetExtensionInfo();
347 if (ImGui::Button(
"Open user system ROMs folder...")) {
348 SDL_OpenURL(
strCat(
"file://", FileOperations::getUserDataDir(),
"/systemroms").c_str());
350 if (ImGui::Button(
"Open system wide system ROMs folder...")) {
351 SDL_OpenURL(
strCat(
"file://", FileOperations::getSystemDataDir(),
"/systemroms").c_str());
356std::vector<ImGuiMachine::MachineInfo>& ImGuiMachine::getAllMachines()
358 if (machineInfo.empty()) {
359 machineInfo = parseAllConfigFiles<MachineInfo>(manager,
"machines", {
"Manufacturer"sv,
"Product code"sv});
364static void amendConfigInfo(MSXMotherBoard& mb, ImGuiMachine::MachineInfo& info)
366 auto& configInfo = info.configInfo;
368 const auto& debugger = mb.getDebugger();
369 unsigned ramSize = 0;
370 for (
const auto& [name, debuggable] : debugger.getDebuggables()) {
371 if (debuggable->getDescription() ==
one_of(
"memory mapper",
"ram")) {
372 ramSize += debuggable->getSize();
375 configInfo.emplace_back(
"RAM size",
strCat(ramSize / 1024,
"kB"));
377 if (
auto* vdp =
dynamic_cast<VDP*
>(mb.findDevice(
"VDP"))) {
378 configInfo.emplace_back(
"VRAM size",
strCat(vdp->getVRAM().getSize() / 1024,
"kB"));
379 configInfo.emplace_back(
"VDP version", vdp->getVersionString());
382 if (
auto drives = RealDrive::getDrivesInUse(mb)) {
383 configInfo.emplace_back(
"Disk drives",
strCat(narrow<int>(drives->count())));
386 const auto& carts = mb.getSlotManager();
387 configInfo.emplace_back(
"Cartridge slots",
strCat(carts.getNumberOfSlots()));
390const std::string& ImGuiMachine::getTestResult(MachineInfo& info)
392 if (!info.testResult) {
393 info.testResult.emplace();
395 auto& reactor = manager.getReactor();
396 manager.executeDelayed([&reactor, &info]()
mutable {
399 MSXMotherBoard mb(reactor);
400 mb.getMSXCliComm().setSuppressMessages(
true);
401 mb.loadMachine(info.configName);
402 assert(info.testResult->empty());
403 amendConfigInfo(mb, info);
404 }
catch (MSXException& e) {
405 info.testResult =
e.getMessage();
409 return info.testResult.value();
412bool ImGuiMachine::printConfigInfo(MachineInfo& info)
414 const auto&
test = getTestResult(info);
415 bool ok =
test.empty();
417 im::Table(
"##machine-info", 2, ImGuiTableFlags_SizingFixedFit, [&]{
418 for (
const auto& [desc, value_] : info.configInfo) {
419 const auto& value = value_;
420 if (ImGui::TableNextColumn()) {
423 if (ImGui::TableNextColumn()) {
440ImGuiMachine::MachineInfo* ImGuiMachine::findMachineInfo(std::string_view config)
442 auto& allMachines = getAllMachines();
443 auto it =
ranges::find(allMachines, config, &MachineInfo::configName);
444 return (it != allMachines.end()) ? std::to_address(it) : nullptr;
void test(const IterableBitSet< N > &s, std::initializer_list< size_t > list)
void showMenu(MSXMotherBoard *motherBoard) override
void paint(MSXMotherBoard *motherBoard) override
void executeDelayed(std::function< void()> action)
std::string_view getMachineName() const
MSXCommandController & getMSXCommandController()
const HotKey & getHotKey() const
auto getMachineIDs() const
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
void TextUnformatted(const std::string &str)
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
void VisuallyDisabled(bool b, std::invocable<> auto next)
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
void ListBox(const char *label, const ImVec2 &size, std::invocable<> auto next)
void StyleColor(bool active, Args &&...args)
void TextWrapPos(float wrap_local_pos_x, std::invocable<> auto next)
bool Menu(const char *label, bool enabled, std::invocable<> auto next)
void Disabled(bool b, std::invocable<> auto next)
void ListClipper(size_t count, int forceIndex, float lineHeight, std::invocable< int > auto next)
This file implemented 3 utility functions:
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
void applyComboFilter(std::string_view key, std::string_view value, const std::vector< T > &items, std::vector< size_t > &indices)
void simpleToolTip(std::string_view desc)
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
ImU32 getColor(imColor col)
void applyDisplayNameFilter(std::string_view filterString, const std::vector< T > &items, std::vector< size_t > &indices)
TclObject makeTclList(Args &&... args)
auto find(InputRange &&range, const T &value)
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)