openMSX
ImGuiManager.cc
Go to the documentation of this file.
1#include "ImGuiManager.hh"
2
4#include "ImGuiBreakPoints.hh"
5#include "ImGuiCharacter.hh"
6#include "ImGuiCheatFinder.hh"
7#include "ImGuiConnector.hh"
8#include "ImGuiConsole.hh"
9#include "ImGuiCpp.hh"
10#include "ImGuiDebugger.hh"
12#include "ImGuiHelp.hh"
13#include "ImGuiKeyboard.hh"
14#include "ImGuiMachine.hh"
15#include "ImGuiMedia.hh"
16#include "ImGuiMessages.hh"
17#include "ImGuiOpenFile.hh"
18#include "ImGuiOsdIcons.hh"
19#include "ImGuiPalette.hh"
20#include "ImGuiReverseBar.hh"
21#include "ImGuiSCCViewer.hh"
22#include "ImGuiSettings.hh"
23#include "ImGuiSoundChip.hh"
24#include "ImGuiSpriteViewer.hh"
25#include "ImGuiSymbols.hh"
26#include "ImGuiTools.hh"
27#include "ImGuiTrainer.hh"
28#include "ImGuiUtils.hh"
29#include "ImGuiVdpRegs.hh"
30#include "ImGuiWatchExpr.hh"
31#include "ImGuiWaveViewer.hh"
32
34#include "CommandException.hh"
35#include "Display.hh"
36#include "Event.hh"
37#include "EventDistributor.hh"
38#include "FileContext.hh"
39#include "FileOperations.hh"
40#include "FilePool.hh"
41#include "HardwareConfig.hh"
42#include "Keyboard.hh"
43#include "Reactor.hh"
44#include "RealDrive.hh"
45#include "RomDatabase.hh"
46#include "RomInfo.hh"
47#include "SettingsConfig.hh"
48#include "VDP.hh"
49
50#include "stl.hh"
51#include "strCat.hh"
52
53#include <imgui.h>
54#include <imgui_impl_opengl3.h>
55#include <imgui_impl_sdl2.h>
56#include <imgui_internal.h>
57#include <CustomFont.ii> // icons for ImGuiFileDialog
58
59#include <SDL.h>
60
61namespace openmsx {
62
63using namespace std::literals;
64
65ImFont* ImGuiManager::addFont(zstring_view filename, int fontSize)
66{
67 auto& io = ImGui::GetIO();
68 if (!filename.empty()) {
69 try {
70 const auto& context = systemFileContext();
71
72 File file(context.resolve(FileOperations::join("skins", filename)));
73 auto fileSize = file.getSize();
74 auto ttfData = std::span(
75 static_cast<uint8_t*>(ImGui::MemAlloc(fileSize)), fileSize);
76 file.read(ttfData);
77
78 static const std::array<ImWchar, 2*6 + 1> ranges = {
79 0x0020, 0x00FF, // Basic Latin + Latin Supplement
80 0x0370, 0x03FF, // Greek and Coptic
81 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement
82 //0x0E00, 0x0E7F, // Thai
83 //0x2000, 0x206F, // General Punctuation
84 //0x2DE0, 0x2DFF, // Cyrillic Extended-A
85 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana
86 0x3131, 0x3163, // Korean alphabets
87 0x31F0, 0x31FF, // Katakana Phonetic Extensions
88 //0x4e00, 0x9FAF, // CJK Ideograms
89 //0xA640, 0xA69F, // Cyrillic Extended-B
90 //0xAC00, 0xD7A3, // Korean characters
91 //0xFF00, 0xFFEF, // Half-width characters
92 0
93 };
94 return io.Fonts->AddFontFromMemoryTTF(
95 ttfData.data(), // transfer ownership of 'ttfData' buffer
96 narrow<int>(ttfData.size()), narrow<float>(fontSize),
97 nullptr, ranges.data());
98 } catch (MSXException& e) {
99 getCliComm().printWarning("Couldn't load font: ", filename, ": ", e.getMessage(),
100 ". Reverted to builtin font");
101 }
102 }
103 return io.Fonts->AddFontDefault(); // embedded "ProggyClean.ttf", size 13
104}
105
106void ImGuiManager::loadFont()
107{
108 ImGuiIO& io = ImGui::GetIO();
109
110 assert(fontProp == nullptr);
112
114 static constexpr std::array<ImWchar, 3> icons_ranges = {ICON_MIN_IGFD, ICON_MAX_IGFD, 0};
115 ImFontConfig icons_config; icons_config.MergeMode = true; icons_config.PixelSnapH = true;
116 io.Fonts->AddFontFromMemoryCompressedBase85TTF(FONT_ICON_BUFFER_NAME_IGFD, 15.0f, &icons_config, icons_ranges.data());
117 // load debugger icons, also only in default font
119
120 assert(fontMono == nullptr);
122}
123
124void ImGuiManager::reloadFont()
125{
126 fontProp = fontMono = nullptr;
127
129
130 ImGuiIO& io = ImGui::GetIO();
131 io.Fonts->Clear();
132 loadFont();
133 io.Fonts->Build();
134
136}
137
138void ImGuiManager::initializeImGui()
139{
140 // Setup Dear ImGui context
141 IMGUI_CHECKVERSION();
142 ImGui::CreateContext();
143 ImGuiIO& io = ImGui::GetIO();
144 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard |
145 //ImGuiConfigFlags_NavEnableGamepad | // TODO revisit this later
146 ImGuiConfigFlags_DockingEnable |
147 ImGuiConfigFlags_ViewportsEnable;
148 static auto iniFilename = systemFileContext().resolveCreate("imgui.ini");
149 io.IniFilename = iniFilename.c_str();
150
151 loadFont();
152}
153
154static void cleanupImGui()
155{
156 ImGui::DestroyContext();
157}
158
159
161 : reactor(reactor_)
162 , fontPropFilename(reactor.getCommandController(), "gui_font_default_filename", "TTF font filename for the default GUI font", "DejaVuSans.ttf.gz")
163 , fontMonoFilename(reactor.getCommandController(), "gui_font_mono_filename", "TTF font filename for the monospaced GUI font", "DejaVuSansMono.ttf.gz")
164 , fontPropSize(reactor.getCommandController(), "gui_font_default_size", "size for the default GUI font", 13, 9, 72)
165 , fontMonoSize(reactor.getCommandController(), "gui_font_mono_size", "size for the monospaced GUI font", 13, 9, 72)
166 , windowPos{SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED}
167{
168 parts.push_back(this);
169
170 // In order that they appear in the menubar
171 machine = std::make_unique<ImGuiMachine>(*this);
172 media = std::make_unique<ImGuiMedia>(*this);
173 connector = std::make_unique<ImGuiConnector>(*this);
174 reverseBar = std::make_unique<ImGuiReverseBar>(*this);
175 tools = std::make_unique<ImGuiTools>(*this);
176 settings = std::make_unique<ImGuiSettings>(*this);
177 debugger = std::make_unique<ImGuiDebugger>(*this);
178 help = std::make_unique<ImGuiHelp>(*this);
179
180 breakPoints = std::make_unique<ImGuiBreakPoints>(*this);
181 symbols = std::make_unique<ImGuiSymbols>(*this);
182 watchExpr = std::make_unique<ImGuiWatchExpr>(*this);
183 bitmap = std::make_unique<ImGuiBitmapViewer>(*this);
184 character = std::make_unique<ImGuiCharacter>(*this);
185 sprite = std::make_unique<ImGuiSpriteViewer>(*this);
186 vdpRegs = std::make_unique<ImGuiVdpRegs>(*this);
187 palette = std::make_unique<ImGuiPalette>(*this);
188 osdIcons = std::make_unique<ImGuiOsdIcons>(*this);
189 openFile = std::make_unique<ImGuiOpenFile>(*this);
190 trainer = std::make_unique<ImGuiTrainer>(*this);
191 cheatFinder = std::make_unique<ImGuiCheatFinder>(*this);
192 sccViewer = std::make_unique<ImGuiSCCViewer>(*this);
193 waveViewer = std::make_unique<ImGuiWaveViewer>(*this);
194 diskManipulator = std::make_unique<ImGuiDiskManipulator>(*this);
195 soundChip = std::make_unique<ImGuiSoundChip>(*this);
196 keyboard = std::make_unique<ImGuiKeyboard>(*this);
197 console = std::make_unique<ImGuiConsole>(*this);
198 messages = std::make_unique<ImGuiMessages>(*this);
199 initializeImGui();
200
201 ImGuiSettingsHandler ini_handler;
202 ini_handler.TypeName = "openmsx";
203 ini_handler.TypeHash = ImHashStr("openmsx");
204 ini_handler.UserData = this;
205 //ini_handler.ClearAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler) { // optional
206 // // Clear all settings data
207 // static_cast<ImGuiManager*>(handler->UserData)->iniClearAll();
208 //};
209 ini_handler.ReadInitFn = [](ImGuiContext*, ImGuiSettingsHandler* handler) { // optional
210 // Read: Called before reading (in registration order)
211 static_cast<ImGuiManager*>(handler->UserData)->iniReadInit();
212 };
213 ini_handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, const char* name) -> void* { // required
214 // Read: Called when entering into a new ini entry e.g. "[Window][Name]"
215 return static_cast<ImGuiManager*>(handler->UserData)->iniReadOpen(name);
216 };
217 ini_handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, void* entry, const char* line) { // required
218 // Read: Called for every line of text within an ini entry
219 static_cast<ImGuiManager*>(handler->UserData)->loadLine(entry, line);
220 };
221 ini_handler.ApplyAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler) { // optional
222 // Read: Called after reading (in registration order)
223 static_cast<ImGuiManager*>(handler->UserData)->iniApplyAll();
224 };
225 ini_handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf) { // required
226 // Write: Output every entries into 'out_buf'
227 static_cast<ImGuiManager*>(handler->UserData)->iniWriteAll(*out_buf);
228 };
229 ImGui::AddSettingsHandler(&ini_handler);
230
231 auto& eventDistributor = reactor.getEventDistributor();
232 using enum EventType;
236 eventDistributor.registerEventListener(type, *this, EventDistributor::Priority::IMGUI);
237 }
238
241 fontPropSize.attach(*this);
242 fontMonoSize.attach(*this);
243}
244
246{
247 fontMonoSize.detach(*this);
248 fontPropSize.detach(*this);
251
252 auto& eventDistributor = reactor.getEventDistributor();
253 using enum EventType;
257 eventDistributor.unregisterEventListener(type, *this);
258 }
259
260 cleanupImGui();
261}
262
264{
265 assert(!contains(parts, part));
266 assert(!contains(toBeAddedParts, part));
267 toBeAddedParts.push_back(part);
268}
269
271{
272 if (auto it1 = ranges::find(parts, part); it1 != parts.end()) {
273 *it1 = nullptr;
274 removeParts = true; // filter nullptr later
275 } else if (auto it2 = ranges::find(toBeAddedParts, part); it2 != toBeAddedParts.end()) {
276 toBeAddedParts.erase(it2); // fine to remove now
277 }
278}
279
280void ImGuiManager::updateParts()
281{
282 if (removeParts) {
283 removeParts = false;
284 parts.erase(ranges::remove(parts, nullptr), parts.end());
285 }
286
287 append(parts, toBeAddedParts);
288 toBeAddedParts.clear();
289}
290
291void ImGuiManager::save(ImGuiTextBuffer& buf)
292{
293 // We cannot query "reactor.getDisplay().getWindowPosition()" here
294 // because display may already be destroyed. Instead Display pushes
295 // window position to here
296 savePersistent(buf, *this, persistentElements);
297}
298
299void ImGuiManager::loadLine(std::string_view name, zstring_view value)
300{
301 loadOnePersistent(name, value, *this, persistentElements);
302}
303
304static gl::ivec2 ensureVisible(gl::ivec2 windowPos, gl::ivec2 windowSize)
305{
306 auto windowTL = windowPos;
307 auto windowBR = windowTL + windowSize;
308 auto overlaps = [&](const ImGuiPlatformMonitor& monitor) {
309 auto monitorTL = trunc(gl::vec2(monitor.MainPos));
310 auto monitorBR = monitorTL + trunc(gl::vec2(monitor.MainSize));
311 return windowTL.x < monitorBR.x &&
312 windowBR.x > monitorTL.x &&
313 windowTL.y < monitorBR.y &&
314 windowBR.y > monitorTL.y;
315 };
316
317 const auto& monitors = ImGui::GetPlatformIO().Monitors;
318 if (!monitors.empty() && ranges::none_of(monitors, overlaps)) {
319 // window isn't visible in any of the monitors
320 // -> place centered on primary monitor
321 return gl::ivec2(SDL_WINDOWPOS_CENTERED);
322 }
323 return windowPos; // current placement is fine
324}
325
326void ImGuiManager::loadEnd()
327{
328 auto& display = reactor.getDisplay();
329 windowPos = ensureVisible(windowPos, display.getWindowSize());
330 display.setWindowPosition(windowPos);
331}
332
334{
335 return reactor.getInterpreter();
336}
337
339{
340 return reactor.getCliComm();
341}
342
343std::optional<TclObject> ImGuiManager::execute(TclObject command)
344{
345 try {
346 return command.executeCommand(getInterpreter());
347 } catch (CommandException&) {
348 // ignore
349 return {};
350 }
351}
352
353void ImGuiManager::executeDelayed(std::function<void()> action)
354{
355 delayedActionQueue.push_back(std::move(action));
357}
358
360 const std::function<void(const TclObject&)>& ok,
361 const std::function<void(const std::string&)>& error)
362{
363 executeDelayed([this, command, ok, error]() mutable {
364 try {
365 auto result = command.executeCommand(getInterpreter());
366 if (ok) ok(result);
367 } catch (CommandException& e) {
368 if (error) error(e.getMessage());
369 }
370 });
371}
372
374 const std::function<void(const TclObject&)>& ok)
375{
376 executeDelayed(std::move(command), ok,
377 [this](const std::string& message) { this->printError(message); });
378}
379
380void ImGuiManager::printError(std::string_view message)
381{
382 getCliComm().printError(message);
383}
384
385bool ImGuiManager::signalEvent(const Event& event)
386{
387 if (auto* evt = get_event_if<SdlEvent>(event)) {
388 const ImGuiIO& io = ImGui::GetIO();
389 if (!io.BackendPlatformUserData) {
390 // ImGui backend not (yet) initialized (e.g. after 'set renderer none')
391 return false;
392 }
393 const SDL_Event& sdlEvent = evt->getSdlEvent();
395 if ((io.WantCaptureMouse &&
396 sdlEvent.type == one_of(SDL_MOUSEMOTION, SDL_MOUSEWHEEL,
397 SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP)) ||
398 (io.WantCaptureKeyboard &&
399 sdlEvent.type == one_of(SDL_KEYDOWN, SDL_KEYUP, SDL_TEXTINPUT))) {
400 return true; // block event for lower priority listeners
401 }
402 } else {
403 switch (getType(event)) {
405 for (auto& action : delayedActionQueue) {
406 std::invoke(action);
407 }
408 delayedActionQueue.clear();
409 break;
410 }
412 const auto& fde = get_event<FileDropEvent>(event);
413 droppedFile = fde.getFileName();
414 handleDropped = true;
415 break;
416 }
418 // Triggers when a new machine gets activated, e.g.:
419 // * after a 'step_back' (or any click in the reverse bar).
420 // * after a machine instance switch
421 // (For now) this triggers the same behavior as BREAK: scroll debugger to PC
422 [[fallthrough]];
423 case EventType::BREAK:
424 debugger->signalBreak();
425 break;
426 default:
428 }
429 }
430 return false;
431}
432
433void ImGuiManager::update(const Setting& /*setting*/) noexcept
434{
435 needReloadFont = true;
436}
437
438// TODO share code with ImGuiMedia
439static std::vector<std::string> getDrives(MSXMotherBoard* motherBoard)
440{
441 std::vector<std::string> result;
442 if (!motherBoard) return result;
443
444 std::string driveName = "diskX";
445 auto drivesInUse = RealDrive::getDrivesInUse(*motherBoard);
446 for (auto i : xrange(RealDrive::MAX_DRIVES)) {
447 if (!(*drivesInUse)[i]) continue;
448 driveName[4] = char('a' + i);
449 result.push_back(driveName);
450 }
451 return result;
452}
453
454static std::vector<std::string> getSlots(MSXMotherBoard* motherBoard)
455{
456 std::vector<std::string> result;
457 if (!motherBoard) return result;
458
459 const auto& slotManager = motherBoard->getSlotManager();
460 std::string cartName = "cartX";
461 for (auto slot : xrange(CartridgeSlotManager::MAX_SLOTS)) {
462 if (!slotManager.slotExists(slot)) continue;
463 cartName[4] = char('a' + slot);
464 result.push_back(cartName);
465 }
466 return result;
467}
468
470{
471 if (!loadIniFile.empty()) {
472 ImGui::LoadIniSettingsFromDisk(loadIniFile.c_str());
473 loadIniFile.clear();
474 }
475 if (needReloadFont) {
476 needReloadFont = false;
477 reloadFont();
478 }
479}
480
481static bool hoverMenuBar()
482{
483 const auto* viewport = ImGui::GetMainViewport();
484 gl::vec2 topLeft = viewport->Pos;
485 gl::vec2 bottomRight = topLeft + gl::vec2(viewport->Size.x, ImGui::GetFrameHeight());
486 gl::vec2 mouse = ImGui::GetMousePos();
487 return mouse.x >= topLeft.x && mouse.x <= bottomRight.x &&
488 mouse.y >= topLeft.y && mouse.y <= bottomRight.y;
489}
490
492{
493 // Apply added/removed parts. Avoids iterating over a changing vector.
494 updateParts();
495
496 auto* motherBoard = reactor.getMotherBoard();
497 if (motherBoard) {
498 if (auto* keyb = motherBoard->getKeyboard()) {
499 auto time = motherBoard->getCurrentTime();
500 keyb->setFocus(!ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow), time);
501 }
502 }
503
504 for (auto* part : parts) {
505 part->paint(motherBoard);
506 }
508 openFile->doPaint();
509 }
510
511 auto drawMenu = [&]{
512 for (auto* part : parts) {
513 part->showMenu(motherBoard);
514 }
515 };
516 if (mainMenuBarUndocked) {
517 im::Window("openMSX main menu", &mainMenuBarUndocked, ImGuiWindowFlags_MenuBar, [&]{
518 im::MenuBar([&]{
519 if (ImGui::ArrowButton("re-dock-button", ImGuiDir_Down)) {
520 mainMenuBarUndocked = false;
521 }
522 simpleToolTip("Dock the menu bar in the main openMSX window.");
523 drawMenu();
524 });
525 });
526 } else {
527 bool active = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) ||
528 ImGui::IsWindowFocused(ImGuiHoveredFlags_AnyWindow) ||
529 hoverMenuBar();
530 if (active != guiActive) {
531 guiActive = active;
532 auto& eventDistributor = reactor.getEventDistributor();
533 eventDistributor.distributeEvent(ImGuiActiveEvent(active));
534 }
535 menuAlpha = [&] {
536 if (!menuFade) return 1.0f;
537 auto target = active ? 1.0f : 0.0f;
538 auto period = active ? 0.5f : 5.0f;
539 return calculateFade(menuAlpha, target, period);
540 }();
541 im::StyleVar(ImGuiStyleVar_Alpha, menuAlpha, [&]{
542 im::MainMenuBar([&]{
543 if (ImGui::ArrowButton("undock-button", ImGuiDir_Up)) {
544 mainMenuBarUndocked = true;
545 }
546 simpleToolTip("Undock the menu bar from the main openMSX window.");
547 drawMenu();
548 });
549 });
550 }
551
552 if (statusBarVisible) drawStatusBar(motherBoard);
553
554 // drag and drop (move this to ImGuiMedia ?)
555 auto insert2 = [&](std::string_view displayName, TclObject cmd) {
556 auto message = strCat("Inserted ", droppedFile, " in ", displayName);
557 executeDelayed(cmd, [this, message, cmd](const TclObject&){
558 insertedInfo = message;
559 openInsertedInfo = true;
560 media->addRecent(cmd);
561 });
562 };
563 auto insert = [&](std::string_view displayName, std::string_view cmd) {
564 insert2(displayName, makeTclList(cmd, "insert", droppedFile));
565 };
566 if (handleDropped) {
567 handleDropped = false;
568 insertedInfo.clear();
569
570 auto category = execute(makeTclList("openmsx_info", "file_type_category", droppedFile))->getString();
571 if (category == "unknown" && FileOperations::isDirectory(droppedFile)) {
572 category = "disk";
573 }
574
575 auto error = [&](auto&& ...message) {
576 executeDelayed(makeTclList("error", strCat(message...)));
577 };
578 auto cantHandle = [&](auto&& ...message) {
579 error("Can't handle dropped file ", droppedFile, ": ", message...);
580 };
581 auto notPresent = [&](const auto& mediaType) {
582 cantHandle("no ", mediaType, " present.");
583 };
584
585 auto testMedia = [&](std::string_view displayName, std::string_view cmd) {
586 if (auto cmdResult = execute(TclObject(cmd))) {
587 insert(displayName, cmd);
588 } else {
589 notPresent(displayName);
590 }
591 };
592
593 if (category == "disk") {
594 auto list = getDrives(motherBoard);
595 if (list.empty()) {
596 notPresent("disk drive");
597 } else if (list.size() == 1) {
598 const auto& drive = list.front();
599 insert(strCat("disk drive ", char(drive.back() - 'a' + 'A')), drive);
600 } else {
601 selectList = std::move(list);
602 ImGui::OpenPopup("select-drive");
603 }
604 } else if (category == "rom") {
605 auto list = getSlots(motherBoard);
606 if (list.empty()) {
607 notPresent("cartridge slot");
608 return;
609 }
610 selectedMedia = list.front();
611 selectList = std::move(list);
612 if (auto sha1 = reactor.getFilePool().getSha1Sum(droppedFile)) {
613 romInfo = reactor.getSoftwareDatabase().fetchRomInfo(*sha1);
614 } else {
615 romInfo = nullptr;
616 }
617 selectedRomType = romInfo ? romInfo->getRomType()
618 : RomType::UNKNOWN; // auto-detect
619 ImGui::OpenPopup("select-cart");
620 } else if (category == "cassette") {
621 testMedia("casette port", "cassetteplayer");
622 } else if (category == "laserdisc") {
623 testMedia("laser disc player", "laserdiscplayer");
624 } else if (category == "savestate") {
625 executeDelayed(makeTclList("loadstate", droppedFile));
626 } else if (category == "replay") {
627 executeDelayed(makeTclList("reverse", "loadreplay", droppedFile));
628 } else if (category == "script") {
629 executeDelayed(makeTclList("source", droppedFile));
630 } else if (FileOperations::getExtension(droppedFile) == ".txt") {
631 executeDelayed(makeTclList("type_from_file", droppedFile));
632 } else {
633 cantHandle("unknown file type");
634 }
635 }
636 im::Popup("select-drive", [&]{
637 ImGui::TextUnformatted(tmpStrCat("Select disk drive for ", droppedFile));
638 auto n = std::min(3.5f, narrow<float>(selectList.size()));
639 auto height = n * ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y;
640 im::ListBox("##select-media", {-FLT_MIN, height}, [&]{
641 for (const auto& item : selectList) {
642 auto drive = item.back() - 'a';
643 auto display = strCat(char('A' + drive), ": ", media->displayNameForDriveContent(drive, true));
644 if (ImGui::Selectable(display.c_str())) {
645 insert(strCat("disk drive ", char(drive + 'A')), item);
646 ImGui::CloseCurrentPopup();
647 }
648 }
649 });
650 });
651 im::Popup("select-cart", [&]{
652 ImGui::TextUnformatted(strCat("Filename: ", droppedFile));
653 ImGui::Separator();
654
655 if (!romInfo) {
656 ImGui::TextUnformatted("ROM not present in software database"sv);
657 }
658 im::Table("##extension-info", 2, [&]{
659 const char* buf = reactor.getSoftwareDatabase().getBufferStart();
660 ImGui::TableSetupColumn("description", ImGuiTableColumnFlags_WidthFixed);
661 ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthStretch);
662
663 if (romInfo) {
664 ImGuiMedia::printDatabase(*romInfo, buf);
665 }
666 if (ImGui::TableNextColumn()) {
667 ImGui::AlignTextToFramePadding();
668 ImGui::TextUnformatted("Mapper"sv);
669 }
670 if (ImGui::TableNextColumn()) {
671 ImGuiMedia::selectMapperType("##mapper-type", selectedRomType);
672 }
673 });
674 ImGui::Separator();
675
676 if (selectList.size() > 1) {
677 const auto& slotManager = motherBoard->getSlotManager();
678 ImGui::TextUnformatted("Select cartridge slot"sv);
679 auto n = std::min(3.5f, narrow<float>(selectList.size()));
680 auto height = n * ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y;
681 im::ListBox("##select-media", {-FLT_MIN, height}, [&]{
682 for (const auto& item : selectList) {
683 auto slot = item.back() - 'a';
684 auto display = strCat(
685 char('A' + slot),
686 " (", slotManager.getPsSsString(slot), "): ",
687 media->displayNameForSlotContent(slotManager, slot, true));
688
689 if (ImGui::Selectable(display.c_str(), item == selectedMedia)) {
690 selectedMedia = item;
691 }
692 }
693 });
694 }
695
696 ImGui::Checkbox("Reset MSX on inserting ROM", &media->resetOnInsertRom);
697
698 if (ImGui::Button("Insert ROM")) {
699 auto cmd = makeTclList(selectedMedia, "insert", droppedFile);
700 if (selectedRomType != RomType::UNKNOWN) {
701 cmd.addListElement("-romtype", RomInfo::romTypeToName(selectedRomType));
702 }
703 insert2(strCat("cartridge slot ", char(selectedMedia.back() - 'a' + 'A')), cmd);
704 if (media->resetOnInsertRom) {
705 executeDelayed(TclObject("reset"));
706 }
707 ImGui::CloseCurrentPopup();
708 }
709 ImGui::SameLine();
710 if (ImGui::Button("Cancel")) {
711 ImGui::CloseCurrentPopup();
712 }
713 });
714 if (openInsertedInfo) {
715 openInsertedInfo = false;
716 insertedInfoTimeout = 3.0f;
717 ImGui::OpenPopup("inserted-info");
718 }
719 im::Popup("inserted-info", [&]{
720 insertedInfoTimeout -= ImGui::GetIO().DeltaTime;
721 if (insertedInfoTimeout <= 0.0f || insertedInfo.empty()) {
722 ImGui::CloseCurrentPopup();
723 }
724 im::TextWrapPos(ImGui::GetFontSize() * 35.0f, [&]{
725 ImGui::TextUnformatted(insertedInfo);
726 });
727 });
728}
729
730void ImGuiManager::drawStatusBar(MSXMotherBoard* motherBoard)
731{
732 if (ImGui::BeginViewportSideBar("##MainStatusBar", nullptr, ImGuiDir_Down, ImGui::GetFrameHeight(),
733 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar)) {
734 im::MenuBar([&]{
735 auto frameTime = ImGui::GetIO().DeltaTime;
736
737 // limit updating to at most 10Hz
738 fpsDrawTimeOut -= frameTime;
739 if (fpsDrawTimeOut < 0.0f) {
740 fpsDrawTimeOut = 0.1f;
741 fps = reactor.getDisplay().getFps();
742 }
743 std::stringstream ssFps;
744 ssFps << std::fixed << std::setprecision(1) << fps << " fps";
745 ImGui::RightAlignText(ssFps.str(), "999.9 fps");
746 simpleToolTip("refresh rate");
747 ImGui::Separator();
748
749 auto [modeStr, extendedStr] = [&] { // TODO: remove duplication with VDP debugger code
750 if (!motherBoard) return std::pair{"-", ""};
751 const auto* vdp = dynamic_cast<const VDP*>(motherBoard->findDevice("VDP"));
752 if (!vdp) return std::pair{"-", ""};
753
754 auto mode = vdp->getDisplayMode();
755 auto base = mode.getBase();
756 if (base == DisplayMode::TEXT1) return std::pair{"0 (40)", "TEXT 1"};
757 if (base == DisplayMode::TEXT2) return std::pair{"0 (80)", "TEXT 2"};
758 if (base == DisplayMode::GRAPHIC1) return std::pair{"1", "GRAPHIC 1"};
759 if (base == DisplayMode::GRAPHIC2) return std::pair{"2", "GRAPHIC 2"};
760 if (base == DisplayMode::GRAPHIC3) return std::pair{"4", "GRAPHIC 3"};
761 if (base == DisplayMode::MULTICOLOR) return std::pair{"3", "MULTICOLOR"};
762 if (base == DisplayMode::GRAPHIC4) return std::pair{"5", "GRAPHIC 4"};
763 if (base == DisplayMode::GRAPHIC5) return std::pair{"6", "GRAPHIC 5"};
764 if (base == DisplayMode::GRAPHIC6) return std::pair{"7", "GRAPHIC 6"};
765 if (base != DisplayMode::GRAPHIC7) return std::pair{"?", ""};
766 return (mode.getByte() & DisplayMode::YJK)
767 ? (mode.getByte() & DisplayMode::YAE) ? std::pair{"11", "GRAPHIC 7 (YJK/YAE mode)"} : std::pair{"12", "GRAPHIC 7 (YJK mode)"}
768 : std::pair{"8", "GRAPHIC 7"};
769 }();
770 ImGui::RightAlignText(modeStr, "0 (80)");
771 simpleToolTip([&]{
772 std::string result = "screen mode as used in MSX-BASIC";
773 if (extendedStr[0]) {
774 strAppend(result, ", corresponds to VDP mode ", extendedStr);
775 }
776 return result;
777 });
778 ImGui::Separator();
779
780 auto timeStr = motherBoard
781 ? formatTime((motherBoard->getCurrentTime() - EmuTime::zero()).toDouble())
782 : formatTime(std::nullopt);
784 simpleToolTip("time since MSX power on");
785 ImGui::Separator();
786
787 if (motherBoard) {
788 // limit updating to at most 1Hz
789 speedDrawTimeOut -= frameTime;
790 if (speedDrawTimeOut < 0.0f) {
791 auto realTimePassed = 1.0f - speedDrawTimeOut;
792 speedDrawTimeOut = 1.0f;
793
794 auto boardTime = motherBoard->getCurrentTime();
795 auto boardTimePassed = (boardTime < prevBoardTime)
796 ? 0.0 // due to reverse for instance
797 : (boardTime - prevBoardTime).toDouble();
798 prevBoardTime = boardTime;
799
800 speed = 100.0f * boardTimePassed / realTimePassed;
801 }
802 } else {
803 speed = 0.0f;
804 prevBoardTime = EmuTime::zero();
805 }
806 ImGui::RightAlignText(strCat(std::round(speed), '%'), "10000%");
807 simpleToolTip("emulation speed");
808 ImGui::Separator();
809
810 if (motherBoard) {
811 if (const HardwareConfig* machineConfig = motherBoard->getMachineConfig()) {
812 if (const auto* info = machineConfig->getConfig().findChild("info")) {
813 auto manuf = info->getChildData("manufacturer", "?");
814 auto code = info->getChildData("code", "?");
815 ImGui::StrCat(manuf, ' ', code);
816 simpleToolTip([&]{
817 auto type = info->getChildData("type", "");
818 auto desc = info->getChildData("description", "");
819 return strCat((type.empty() ? "" : strCat("Machine type: ", type, '\n')), desc);
820 });
821 }
822 }
823 }
824 ImGui::Separator();
825
826 if (auto result = execute(TclObject("guess_title"))) {
827 ImGui::TextUnformatted(result->getString());
828 simpleToolTip("the (probably) currently running software");
829 }
830
831 });
832 }
833 ImGui::End();
834}
835
836void ImGuiManager::iniReadInit()
837{
838 updateParts();
839 for (auto* part : parts) {
840 if (part) { // loadStart() could call unregisterPart()
841 part->loadStart();
842 }
843 }
844}
845
846void* ImGuiManager::iniReadOpen(std::string_view name)
847{
848 updateParts();
849 for (auto* part : parts) {
850 if (part->iniName() == name) return part;
851 }
852 return nullptr;
853}
854
855void ImGuiManager::loadLine(void* entry, const char* line_) const
856{
857 zstring_view line = line_;
858 auto pos = line.find('=');
859 if (pos == zstring_view::npos) return;
860 std::string_view name = line.substr(0, pos);
861 zstring_view value = line.substr(pos + 1);
862
863 assert(entry);
864 static_cast<ImGuiPartInterface*>(entry)->loadLine(name, value);
865}
866
867void ImGuiManager::iniApplyAll()
868{
869 updateParts();
870 for (auto* part : parts) {
871 part->loadEnd();
872 }
873}
874
875void ImGuiManager::iniWriteAll(ImGuiTextBuffer& buf)
876{
877 updateParts();
878 for (auto* part : parts) {
879 if (auto name = part->iniName(); !name.empty()) {
880 buf.appendf("[openmsx][%s]\n", name.c_str());
881 part->save(buf);
882 buf.append("\n");
883 }
884 }
885}
886
887} // namespace openmsx
void printError(std::string_view message)
Definition CliComm.cc:17
void printWarning(std::string_view message)
Definition CliComm.cc:12
static constexpr uint8_t GRAPHIC3
static constexpr uint8_t MULTICOLOR
static constexpr uint8_t GRAPHIC4
static constexpr uint8_t GRAPHIC5
static constexpr uint8_t GRAPHIC1
static constexpr uint8_t GRAPHIC7
static constexpr uint8_t TEXT2
static constexpr byte YAE
Encoding of YAE flag.
static constexpr uint8_t GRAPHIC6
static constexpr byte YJK
Encoding of YJK flag.
static constexpr uint8_t GRAPHIC2
static constexpr uint8_t TEXT1
float getFps() const
Definition Display.cc:252
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void distributeEvent(Event &&event)
Schedule the given event for delivery.
void registerEventListener(EventType type, EventListener &listener, Priority priority=Priority::OTHER)
Registers a given object to receive certain events.
std::string resolveCreate(std::string_view filename) const
Sha1Sum getSha1Sum(File &file)
Calculate sha1sum for the given File object.
Definition FilePool.cc:58
zstring_view getString() const noexcept
std::unique_ptr< ImGuiMachine > machine
void registerPart(ImGuiPartInterface *part)
std::unique_ptr< ImGuiBreakPoints > breakPoints
void printError(std::string_view message)
std::unique_ptr< ImGuiVdpRegs > vdpRegs
std::unique_ptr< ImGuiCheatFinder > cheatFinder
std::unique_ptr< ImGuiTrainer > trainer
std::unique_ptr< ImGuiDiskManipulator > diskManipulator
IntegerSetting fontMonoSize
std::unique_ptr< ImGuiWatchExpr > watchExpr
std::unique_ptr< ImGuiPalette > palette
std::unique_ptr< ImGuiWaveViewer > waveViewer
std::unique_ptr< ImGuiConnector > connector
std::optional< TclObject > execute(TclObject command)
std::unique_ptr< ImGuiKeyboard > keyboard
std::unique_ptr< ImGuiHelp > help
IntegerSetting fontPropSize
std::unique_ptr< ImGuiSpriteViewer > sprite
Interpreter & getInterpreter()
std::unique_ptr< ImGuiConsole > console
std::unique_ptr< ImGuiSoundChip > soundChip
ImGuiManager(Reactor &reactor_)
std::unique_ptr< ImGuiReverseBar > reverseBar
std::unique_ptr< ImGuiMedia > media
std::unique_ptr< ImGuiMessages > messages
std::unique_ptr< ImGuiOpenFile > openFile
std::unique_ptr< ImGuiOsdIcons > osdIcons
FilenameSetting fontPropFilename
std::unique_ptr< ImGuiBitmapViewer > bitmap
std::unique_ptr< ImGuiSettings > settings
std::unique_ptr< ImGuiDebugger > debugger
std::unique_ptr< ImGuiSCCViewer > sccViewer
std::unique_ptr< ImGuiCharacter > character
void unregisterPart(ImGuiPartInterface *part)
std::unique_ptr< ImGuiTools > tools
void executeDelayed(std::function< void()> action)
FilenameSetting fontMonoFilename
std::unique_ptr< ImGuiSymbols > symbols
static void printDatabase(const RomInfo &romInfo, const char *buf)
static bool selectMapperType(const char *label, RomType &item)
int getInt() const noexcept
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
const HardwareConfig * getMachineConfig() const
MSXDevice * findDevice(std::string_view name)
Find a MSXDevice by name.
Contains the main loop of openMSX.
Definition Reactor.hh:75
MSXMotherBoard * getMotherBoard() const
Definition Reactor.cc:409
Display & getDisplay()
Definition Reactor.hh:93
CliComm & getCliComm()
Definition Reactor.cc:323
Interpreter & getInterpreter()
Definition Reactor.cc:328
EventDistributor & getEventDistributor()
Definition Reactor.hh:89
RomDatabase & getSoftwareDatabase()
Definition Reactor.cc:315
FilePool & getFilePool()
Definition Reactor.hh:98
static std::shared_ptr< DrivesInUse > getDrivesInUse(MSXMotherBoard &motherBoard)
Definition RealDrive.cc:21
const RomInfo * fetchRomInfo(const Sha1Sum &sha1sum) const
Lookup an entry in the database by sha1sum.
static std::string_view romTypeToName(RomType type)
Definition RomInfo.cc:191
RomType getRomType() const
Definition RomInfo.hh:64
void detach(Observer< T > &observer)
Definition Subject.hh:60
void attach(Observer< T > &observer)
Definition Subject.hh:54
TclObject executeCommand(Interpreter &interp, bool compile=false)
Interpret this TclObject as a command and execute it.
Definition TclObject.cc:248
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
static constexpr auto npos
constexpr auto find(char c, size_type pos=0) const
constexpr zstring_view substr(size_type pos) const
constexpr auto empty() const
ImGuiID ImHashStr(const char *data_p, size_t data_size, ImGuiID seed)
Definition imgui.cc:2246
bool ImGui_ImplOpenGL3_CreateFontsTexture()
void ImGui_ImplOpenGL3_DestroyFontsTexture()
bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event *event)
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:44
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:25
void RightAlignText(std::string_view text, std::string_view maxWidthText)
Definition ImGuiUtils.hh:50
constexpr double e
Definition Math.hh:21
vecN< 2, int > ivec2
Definition gl_vec.hh:181
constexpr vecN< N, int > trunc(const vecN< N, T > &x)
Definition gl_vec.hh:411
vecN< 2, float > vec2
Definition gl_vec.hh:178
std::optional< Context > context
Definition GLContext.cc:10
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
Definition ImGuiCpp.hh:455
void MainMenuBar(std::invocable<> auto next)
Definition ImGuiCpp.hh:350
void MenuBar(std::invocable<> auto next)
Definition ImGuiCpp.hh:341
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void StyleVar(ImGuiStyleVar idx, float val, std::invocable<> auto next)
Definition ImGuiCpp.hh:190
void ListBox(const char *label, const ImVec2 &size, std::invocable<> auto next)
Definition ImGuiCpp.hh:328
void TextWrapPos(float wrap_local_pos_x, std::invocable<> auto next)
Definition ImGuiCpp.hh:212
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:391
string_view getExtension(string_view path)
Returns the extension portion of a path.
bool isDirectory(const Stat &st)
string join(string_view part1, string_view part2)
Join two paths.
This file implemented 3 utility functions:
Definition Autofire.cc:11
const FileContext & systemFileContext()
EventType
Definition Event.hh:454
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:78
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
EventType getType(const Event &event)
Definition Event.hh:517
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:445
std::string formatTime(std::optional< double > time)
float calculateFade(float current, float target, float period)
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:293
auto remove(ForwardRange &&range, const T &value)
Definition ranges.hh:291
constexpr bool none_of(InputRange &&range, UnaryPredicate pred)
Definition ranges.hh:212
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
STL namespace.
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition stl.hh:32
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132