openMSX
ImGuiDebugger.cc
Go to the documentation of this file.
1#include "ImGuiDebugger.hh"
2
3#include "DebuggableEditor.hh"
5#include "ImGuiBreakPoints.hh"
6#include "ImGuiCharacter.hh"
7#include "ImGuiCpp.hh"
8#include "ImGuiDisassembly.hh"
9#include "ImGuiManager.hh"
10#include "ImGuiPalette.hh"
11#include "ImGuiSpriteViewer.hh"
12#include "ImGuiSymbols.hh"
13#include "ImGuiUtils.hh"
14#include "ImGuiVdpRegs.hh"
15#include "ImGuiWatchExpr.hh"
16
17#include "CPURegs.hh"
18#include "Debuggable.hh"
19#include "Debugger.hh"
20#include "MSXCPU.hh"
21#include "MSXCPUInterface.hh"
23#include "MSXMotherBoard.hh"
24#include "MSXRom.hh"
25#include "RomBlockDebuggable.hh"
26#include "RomInfo.hh"
27
28#include "narrow.hh"
29#include "ranges.hh"
30#include "stl.hh"
31#include "strCat.hh"
32#include "StringOp.hh"
33
34#include <imgui.h>
35
36using namespace std::literals;
37
38namespace openmsx {
39
41 : ImGuiPart(manager_)
42{
43}
44
46
47// TODO quick and dirty, doesn't look right yet
48// Created from a .ttf file via:
49// binary_to_compressed_c openmsx-debugger-icons.ttf myIcons > myfont.cpp
50// https://github.com/ocornut/imgui/blob/master/misc/fonts/binary_to_compressed_c.cpp
51static const char icons_compressed_base85[1815+1] =
52 "7])#######;9u/('/###W),##.f1$#Q6>##%[n42)[KU%G5)=-<NE/1aNV=BZrPSb]->>#ICi9.o`(*HGsO2(b6O3L+lQS%,5LsCC,H3AnAMeNA*&#Gb';9Cs3BMNvSN`sr1dD4Eo3R/"
53 "w/)8eXHkG2V3dW.;h^`I@cj`W)a4&>ZW%q/o6Q<Bou@GMQuuoJH`;-*0iR/Go%fr6jMWVRReK?-qmnUCC'Hv'<J]p]`;`Y5@C=GH?o6O+j#)*Mp]G&#70lQ#;0xfLQ7wj#O?DX-@1>F%"
54 "5[)<#8JFgL6SgV-xew@'uB5X7If]8QR3O%b'kS`a)%vW-iXx6*BfW>-'lB#$1fs-$R'Mk+(lB#$ckf&,peu.:J/PQ'-MYY#jFs9)*lXJgaDe8Qw(Y:v47Y&#*r[w'WQ1x'1/V:m^U4+3"
55 "7GJcM604,NJ:-##O]-x-e:k(NX@$##]jP]4ACh^X0(^fLHl/o#g]L7#N'+&#L)Qv$t@0+*I5^+4]77<.aQ9k0$g`,3$+h,3Pqji06rs-$=uG=3tq$>MOAQY>@'.`&K8P>#g>AvL9q*<#"
56 ")EEjLr^PA#m.Us-KRb'4g)Cb4%IuD#GKRL2KkRP/N7R20t3wK#-8/$jJA5U;0viS&[5AC>uIVS%3^oh24GbM(hIL>#xP%U#WE323lpd].B[A30GK_+,sX/D=dn5q&0####GBO&#.1d7$"
57 "3g1$#prgo.-<Tv->;gF4LZ>x#<)]L(/p^I*^nr?#&Y@C#cF&@'wvPJ(?nU^#pRpQ0gF*j0EPtdVN'+9%k*l)*0qi<%>=+dVKrdx0Iu(kLgk4DWX?/YGZv9dMb/q8/+fMO#W+WBOM>#`Q"
58 "xR--3bO1F*.D,G4rFuG-hwhH-AI7q.3B3I):[(cJ[pl6&]3KI%)B1se0fv]M6T3kuGSoBJNCZY#q+er?Y,8v,5cNY5*`$s$#]YV-[@i?#=@[s$G+TC4P#l8./=]s$1Pev6jlje3&-Xf-"
59 "e](jr145d3?;Rv$ZUvC%h5%fqxC$Y@8^CY$O,@H),W'Z-W-Rh27<C[Krsf;$.wg<L9br,4%.6v,>=+dVIIx:/@JJH)Z:Nu.iUkJ2tpPm:t0(Q$@OEp%Upn;%j&5QCa)iS0%5YY#IX$_]"
60 "2^x^]Zrju5Fhf-*i+YV-6'PA#(cK+*mtq;/qwkj1?f6<.0MbI),VW/23>%&4m$ie3%&AA4l'eq7+jkA#dlfj-&$+&^S8VTd.^AN-,CeM0l,'hoodPir`IofLX=$##5C.%T=UYF%^Yk*$"
61 "/)m<-]'LS-&%.20'>uu#OYU7#euQ['GPSw8P`&:)AF14+&YF&#WW`>$Mbk-$I&l-$Bp0.$eo4wLW(I3#Ys%(#U-4&#MXN1#EIc>#ik*$M0xSfLRq)$#@>[A-$Mg;-'Spr-gN<;Ni;-##"
62 "Jer=--`/,Mv_d##tg`=-5X`=-8sG<-*fG<-+C`T.3f1$#mrG<-8X`=-/#XN0Ht*A#e5>##_RFgLX%1kLF=`T.3xL$#)6xU.R,_'#RA`T.T5$C#<rG<-M2(@-xrG<-DKx>-#EfT%8nT`3"
63 "]kU.G-i@+H=DpTCuAo6D<B?hD-rI+HXhdxFL7-AFY'auGt$EJ1m,(@'un@VHdt6L#s[DYGB0-gD.X`'/4+auGpkn+H5-xF-^%/(#B_=?-kQU@-QRt7/uJ6(#Cm[*b[^es/c:@bHdN4/#"
64 "d>?L-NNei.Fn@-#afh]%mxtLF//5UCvs.>B,(rE-i<cM:^0[eF/1u'#.mk.#[5C/#-)IqLR`4rL;wXrLX.lrL1x.qLf.K-#J#`5/%$S-#c7]nLNM-X.Q####F/LMTxS&=u<7L8#";
65
66static constexpr ImWchar DEBUGGER_ICON_MIN = 0xead1;
67static constexpr ImWchar DEBUGGER_ICON_MAX = 0xeb8f;
68static constexpr ImWchar DEBUGGER_ICON_RUN = 0xead3;
69static constexpr ImWchar DEBUGGER_ICON_BREAK = 0xead1;
70static constexpr ImWchar DEBUGGER_ICON_STEP_IN = 0xead4;
71static constexpr ImWchar DEBUGGER_ICON_STEP_OUT = 0xead5;
72static constexpr ImWchar DEBUGGER_ICON_STEP_OVER = 0xead6;
73static constexpr ImWchar DEBUGGER_ICON_STEP_BACK = 0xeb8f;
74
76{
77 auto& io = ImGui::GetIO();
78 ImFontConfig icons_config; icons_config.MergeMode = true; icons_config.PixelSnapH = true;
79 static constexpr std::array<ImWchar, 3> ranges = {DEBUGGER_ICON_MIN, DEBUGGER_ICON_MAX, 0};
80 io.Fonts->AddFontFromMemoryCompressedBase85TTF(icons_compressed_base85, 20.0f, &icons_config, ranges.data());
81}
82
84{
85 for (auto& d : disassemblyViewers) {
86 d->signalBreak();
87 }
88}
89
90template<typename T>
91static void openOrCreate(ImGuiManager& manager, std::vector<std::unique_ptr<T>>& viewers)
92{
93 // prefer to reuse a previously closed viewer
94 if (auto it = ranges::find(viewers, false, &T::show); it != viewers.end()) {
95 (*it)->show = true;
96 return;
97 }
98 // or create a new one
99 viewers.push_back(std::make_unique<T>(manager, viewers.size()));
100}
101
102void ImGuiDebugger::save(ImGuiTextBuffer& buf)
103{
104 savePersistent(buf, *this, persistentElements);
105
106 buf.appendf("disassemblyViewers=%d\n", narrow<int>(disassemblyViewers.size()));
107 buf.appendf("bitmapViewers=%d\n", narrow<int>(bitmapViewers.size()));
108 buf.appendf("tileViewers=%d\n", narrow<int>(tileViewers.size()));
109 buf.appendf("spriteViewers=%d\n", narrow<int>(spriteViewers.size()));
110
111 // TODO in the future use c++23 std::chunk_by
112 auto it = hexEditors.begin();
113 auto et = hexEditors.end();
114 while (it != et) {
115 int count = 0;
116 auto name = std::string((*it)->getDebuggableName());
117 do {
118 ++it;
119 ++count;
120 } while (it != et && (*it)->getDebuggableName() == name);
121 buf.appendf("hexEditor.%s=%d\n", name.c_str(), count);
122 }
123}
124
126{
127 disassemblyViewers.clear();
128 bitmapViewers.clear();
129 tileViewers.clear();
130 spriteViewers.clear();
131 hexEditors.clear();
132}
133
134void ImGuiDebugger::loadLine(std::string_view name, zstring_view value)
135{
136 static constexpr std::string_view hexEditorPrefix = "hexEditor.";
137
138 if (loadOnePersistent(name, value, *this, persistentElements)) {
139 // already handled
140 } else if (name == "disassemblyViewers") {
141 if (auto r = StringOp::stringTo<unsigned>(value)) {
142 repeat(*r, [&] { openOrCreate(manager, disassemblyViewers); });
143 }
144 } else if (name == "bitmapViewers") {
145 if (auto r = StringOp::stringTo<unsigned>(value)) {
146 repeat(*r, [&] { openOrCreate(manager, bitmapViewers); });
147 }
148 } else if (name == "tileViewers") {
149 if (auto r = StringOp::stringTo<unsigned>(value)) {
150 repeat(*r, [&] { openOrCreate(manager, tileViewers); });
151 }
152 } else if (name == "spriteViewers") {
153 if (auto r = StringOp::stringTo<unsigned>(value)) {
154 repeat(*r, [&] { openOrCreate(manager, spriteViewers); });
155 }
156 } else if (name.starts_with(hexEditorPrefix)) {
157 if (auto r = StringOp::stringTo<unsigned>(value)) {
158 auto debuggableName = std::string(name.substr(hexEditorPrefix.size()));
159 auto [b, e] = ranges::equal_range(hexEditors, debuggableName, {}, &DebuggableEditor::getDebuggableName);
160 auto index = std::distance(b, e); // expected to be 0, but be robust against imgui.ini changes
161 for (auto i : xrange(*r)) {
162 e = hexEditors.insert(e, std::make_unique<DebuggableEditor>(manager, debuggableName, index + i));
163 ++e;
164 }
165 }
166 }
167}
168
170{
171 auto createHexEditor = [&](const std::string& name) {
172 // prefer to reuse a previously closed editor
173 auto [b, e] = ranges::equal_range(hexEditors, name, {}, &DebuggableEditor::getDebuggableName);
174 for (auto it = b; it != e; ++it) {
175 if (!(*it)->open) {
176 (*it)->open = true;
177 return;
178 }
179 }
180 // or create a new one
181 auto index = std::distance(b, e);
182 auto it = hexEditors.insert(e, std::make_unique<DebuggableEditor>(manager, name, index));
183 (*it)->open = true;
184 };
185
186 im::Menu("Debugger", motherBoard != nullptr, [&]{
187 ImGui::MenuItem("Tool bar", nullptr, &showControl);
188 if (ImGui::MenuItem("Disassembly ...")) {
189 openOrCreate(manager, disassemblyViewers);
190 }
191 ImGui::MenuItem("CPU registers", nullptr, &showRegisters);
192 ImGui::MenuItem("CPU flags", nullptr, &showFlags);
193 ImGui::MenuItem("Slots", nullptr, &showSlots);
194 ImGui::MenuItem("Stack", nullptr, &showStack);
195 auto it = ranges::lower_bound(hexEditors, "memory", {}, &DebuggableEditor::getDebuggableName);
196 bool memoryOpen = (it != hexEditors.end()) && (*it)->open;
197 if (ImGui::MenuItem("Memory", nullptr, &memoryOpen)) {
198 if (memoryOpen) {
199 createHexEditor("memory");
200 } else {
201 assert(it != hexEditors.end());
202 (*it)->open = false;
203 }
204 }
205 ImGui::Separator();
206 ImGui::MenuItem("Breakpoints", nullptr, &manager.breakPoints->show);
207 ImGui::MenuItem("Symbol manager", nullptr, &manager.symbols->show);
208 ImGui::MenuItem("Watch expression", nullptr, &manager.watchExpr->show);
209 ImGui::Separator();
210 if (ImGui::MenuItem("VDP bitmap viewer ...")) {
211 openOrCreate(manager, bitmapViewers);
212 }
213 if (ImGui::MenuItem("VDP tile viewer ...")) {
214 openOrCreate(manager, tileViewers);
215 }
216 if (ImGui::MenuItem("VDP sprite viewer ...")) {
217 openOrCreate(manager, spriteViewers);
218 }
219 ImGui::MenuItem("VDP register viewer", nullptr, &manager.vdpRegs->show);
220 ImGui::MenuItem("Palette editor", nullptr, &manager.palette->window.open);
221 ImGui::Separator();
222 im::Menu("Add hex editor", [&]{
223 const auto& debugger = motherBoard->getDebugger();
224 auto debuggables = to_vector<std::pair<std::string, Debuggable*>>(debugger.getDebuggables());
225 ranges::sort(debuggables, StringOp::caseless{}, [](const auto& p) { return p.first; }); // sort on name
226 for (const auto& [name, debuggable] : debuggables) {
227 if (ImGui::Selectable(strCat(name, " ...").c_str())) {
228 createHexEditor(name);
229 }
230 }
231 });
232 });
233}
234
235void ImGuiDebugger::setGotoTarget(uint16_t target)
236{
237 if (disassemblyViewers.empty()) {
238 openOrCreate(manager, disassemblyViewers);
239 }
240 disassemblyViewers.front()->setGotoTarget(target);
241}
242
244{
245 if (!motherBoard) return;
246
247 auto& regs = motherBoard->getCPU().getRegisters();
248 auto& cpuInterface = motherBoard->getCPUInterface();
249 auto& debugger = motherBoard->getDebugger();
250 auto time = motherBoard->getCurrentTime();
251 drawControl(cpuInterface, *motherBoard);
252 drawSlots(cpuInterface, debugger);
253 drawStack(regs, cpuInterface, time);
254 drawRegisters(regs);
255 drawFlags(regs);
256}
257
258void ImGuiDebugger::actionBreakContinue(MSXCPUInterface& cpuInterface)
259{
261 cpuInterface.doContinue();
262 } else {
263 cpuInterface.doBreak();
264 }
265}
266void ImGuiDebugger::actionStepIn(MSXCPUInterface& cpuInterface)
267{
268 cpuInterface.doStep();
269}
270void ImGuiDebugger::actionStepOver()
271{
272 manager.executeDelayed(TclObject("step_over"));
273}
274void ImGuiDebugger::actionStepOut()
275{
276 manager.executeDelayed(TclObject("step_out"));
277}
278void ImGuiDebugger::actionStepBack()
279{
280 manager.executeDelayed(TclObject("step_back"), [&](const TclObject&) {
281 for (auto& d : disassemblyViewers) d->syncWithPC();
282 });
283}
284
286{
287 using enum Shortcuts::ID;
288 const auto& shortcuts = manager.getShortcuts();
289
290 if (shortcuts.checkShortcut(DEBUGGER_BREAK_CONTINUE)) {
291 actionBreakContinue(cpuInterface);
292 } else if (shortcuts.checkShortcut(DEBUGGER_STEP_IN)) {
293 actionStepIn(cpuInterface);
294 } else if (shortcuts.checkShortcut(DEBUGGER_STEP_OVER)) {
295 actionStepOver();
296 } else if (shortcuts.checkShortcut(DEBUGGER_STEP_OUT)) {
297 actionStepOut();
298 } else if (shortcuts.checkShortcut(DEBUGGER_STEP_BACK)) {
299 actionStepBack();
300 } else if (shortcuts.checkShortcut(DISASM_TOGGLE_BREAKPOINT)) {
302 }
303}
304
305void ImGuiDebugger::drawControl(MSXCPUInterface& cpuInterface, MSXMotherBoard& motherBoard)
306{
307 if (!showControl) return;
308 im::Window("Debugger tool bar", &showControl, [&]{
309 checkShortcuts(cpuInterface, motherBoard);
310
311 gl::vec2 maxIconSize;
312 auto* font = ImGui::GetFont();
313 for (auto c : {
314 DEBUGGER_ICON_RUN, DEBUGGER_ICON_BREAK,
315 DEBUGGER_ICON_STEP_IN, DEBUGGER_ICON_STEP_OVER,
316 DEBUGGER_ICON_STEP_OUT, DEBUGGER_ICON_STEP_BACK,
317 }) {
318 const auto* g = font->FindGlyph(c);
319 maxIconSize = max(maxIconSize, gl::vec2{g->X1 - g->X0, g->Y1 - g->Y0});
320 }
321
322 auto ButtonGlyph = [&](const char* id, ImWchar glyph, Shortcuts::ID sid) {
323 bool result = ButtonWithCenteredGlyph(glyph, maxIconSize);
324 simpleToolTip([&]() -> std::string {
325 const auto& shortcuts = manager.getShortcuts();
326 const auto& shortcut = shortcuts.getShortcut(sid);
327 if (shortcut.keyChord == ImGuiKey_None) return id;
328 return strCat(id, " (", getKeyChordName(shortcut.keyChord), ')');
329 });
330 return result;
331 };
332
333 bool breaked = MSXCPUInterface::isBreaked();
334 using enum Shortcuts::ID;
335 if (auto breakContinueIcon = breaked ? DEBUGGER_ICON_RUN : DEBUGGER_ICON_BREAK;
336 ButtonGlyph("run", breakContinueIcon, DEBUGGER_BREAK_CONTINUE)) {
337 actionBreakContinue(cpuInterface);
338 }
339 const auto& style = ImGui::GetStyle();
340 ImGui::SameLine(0.0f, 2.0f * style.ItemSpacing.x);
341
342 im::Disabled(!breaked, [&]{
343 if (ButtonGlyph("step-in", DEBUGGER_ICON_STEP_IN, DEBUGGER_STEP_IN)) {
344 actionStepIn(cpuInterface);
345 }
346 ImGui::SameLine();
347
348 if (ButtonGlyph("step-over", DEBUGGER_ICON_STEP_OVER, DEBUGGER_STEP_OVER)) {
349 actionStepOver();
350 }
351 ImGui::SameLine();
352
353 if (ButtonGlyph("step-out", DEBUGGER_ICON_STEP_OUT, DEBUGGER_STEP_OUT)) {
354 actionStepOut();
355 }
356 ImGui::SameLine();
357
358 if (ButtonGlyph("step-back", DEBUGGER_ICON_STEP_BACK, DEBUGGER_STEP_BACK)) {
359 actionStepBack();
360 }
361 });
362 });
363}
364
365void ImGuiDebugger::drawSlots(MSXCPUInterface& cpuInterface, Debugger& debugger)
366{
367 if (!showSlots) return;
368 im::Window("Slots", &showSlots, [&]{
369 int flags = ImGuiTableFlags_BordersInnerV |
370 ImGuiTableFlags_Resizable |
371 ImGuiTableFlags_Reorderable |
372 ImGuiTableFlags_Hideable |
373 ImGuiTableFlags_ContextMenuInBody;
374 im::Table("table", 4, flags, [&]{
375 ImGui::TableSetupColumn("Page");
376 ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_DefaultHide);
377 ImGui::TableSetupColumn("Slot");
378 ImGui::TableSetupColumn("Segment");
379 ImGui::TableHeadersRow();
380
381 for (auto page : xrange(uint8_t(4))) {
382 auto addr = 0x4000 * page;
383 if (ImGui::TableNextColumn()) { // page
384 ImGui::StrCat(page);
385 }
386 if (ImGui::TableNextColumn()) { // address
387 ImGui::StrCat("0x", hex_string<4>(addr));
388 }
389 if (ImGui::TableNextColumn()) { // slot
390 int ps = cpuInterface.getPrimarySlot(page);
391 if (cpuInterface.isExpanded(ps)) {
392 int ss = cpuInterface.getSecondarySlot(page);
393 ImGui::StrCat(ps, '-', ss);
394 } else {
395 ImGui::StrCat(' ', ps);
396 }
397 }
398 if (ImGui::TableNextColumn()) { // segment
399 const auto* device = cpuInterface.getVisibleMSXDevice(page);
400 if (const auto* mapper = dynamic_cast<const MSXMemoryMapperBase*>(device)) {
401 ImGui::StrCat(mapper->getSelectedSegment(page));
402 } else if (auto [rom, romBlocks] = ImGuiDisassembly::getRomBlocks(debugger, device); romBlocks) {
403 if (unsigned blockSize = RomInfo::getBlockSize(rom->getRomType())) {
404 std::string text;
405 char separator = 'R';
406 for (int offset = 0; offset < 0x4000; offset += blockSize) {
407 text += separator;
408 if (auto seg = romBlocks->readExt(addr + offset); seg != unsigned(-1)) {
409 strAppend(text, seg);
410 } else {
411 text += '-';
412 }
413 separator = '/';
414 }
416 } else {
418 }
419 } else {
421 }
422 }
423 }
424 });
425 });
426}
427
428void ImGuiDebugger::drawStack(const CPURegs& regs, const MSXCPUInterface& cpuInterface, EmuTime::param time)
429{
430 if (!showStack) return;
431
432 auto line = ImGui::GetTextLineHeightWithSpacing();
433 ImGui::SetNextWindowSize(ImVec2(0.0f, 12 * line), ImGuiCond_FirstUseEver);
434 im::Window("stack", &showStack, [&]{
435 auto sp = regs.getSP();
436
437 int flags = ImGuiTableFlags_ScrollY |
438 ImGuiTableFlags_BordersInnerV |
439 ImGuiTableFlags_Resizable |
440 ImGuiTableFlags_Reorderable |
441 ImGuiTableFlags_Hideable |
442 ImGuiTableFlags_ContextMenuInBody;
443 im::Table("table", 3, flags, [&]{
444 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
445 ImGui::TableSetupColumn("Address");
446 ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_DefaultHide);
447 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide);
448 ImGui::TableHeadersRow();
449
451 im::ListClipper(std::min(128, (0x10000 - sp) / 2), [&](int row) {
452 auto offset = 2 * row;
453 auto addr = sp + offset;
454 if (ImGui::TableNextColumn()) { // address
455 ImGui::StrCat(hex_string<4>(addr));
456 }
457 if (ImGui::TableNextColumn()) { // offset
458 ImGui::Text("SP+%X", offset);
459 }
460 if (ImGui::TableNextColumn()) { // value
461 auto l = cpuInterface.peekMem(narrow_cast<uint16_t>(addr + 0), time);
462 auto h = cpuInterface.peekMem(narrow_cast<uint16_t>(addr + 1), time);
463 auto value = narrow<uint16_t>(256 * h + l);
464 ImGui::StrCat(hex_string<4>(value));
465 }
466 });
467 });
468 });
469}
470
471void ImGuiDebugger::drawRegisters(CPURegs& regs)
472{
473 if (!showRegisters) return;
474 im::Window("CPU registers", &showRegisters, [&]{
476
477 const auto& style = ImGui::GetStyle();
478 auto padding = 2 * style.FramePadding.x;
479 auto width16 = ImGui::CalcTextSize("FFFF"sv).x + padding;
480 auto edit16 = [&](std::string_view label, std::string_view high, std::string_view low, auto getter, auto setter) {
481 uint16_t value = getter();
482 im::Group([&]{
483 ImGui::AlignTextToFramePadding();
485 ImGui::SameLine();
486 ImGui::SetNextItemWidth(width16);
487 if (ImGui::InputScalar(tmpStrCat("##", label).c_str(), ImGuiDataType_U16, &value, nullptr, nullptr, "%04X")) {
488 setter(value);
489 }
490 });
491 simpleToolTip([&]{
492 return strCat(
493 "Bin: ", bin_string<4>(value >> 12), ' ', bin_string<4>(value >> 8), ' ',
494 bin_string<4>(value >> 4), ' ', bin_string<4>(value >> 0), "\n"
495 "Dec: ", dec_string<5>(value), '\n',
496 high, ": ", dec_string<3>(value >> 8), " ", low, ": ", dec_string<3>(value & 0xff));
497 });
498 };
499 auto edit8 = [&](std::string_view label, auto getter, auto setter) {
500 uint8_t value = getter();
501 im::Group([&]{
502 ImGui::AlignTextToFramePadding();
504 ImGui::SameLine();
505 ImGui::SetNextItemWidth(width16);
506 if (ImGui::InputScalar(tmpStrCat("##", label).c_str(), ImGuiDataType_U8, &value, nullptr, nullptr, "%02X")) {
507 setter(value);
508 }
509 });
510 simpleToolTip([&]{
511 return strCat(
512 "Bin: ", bin_string<4>(value >> 4), ' ', bin_string<4>(value >> 0), "\n"
513 "Dec: ", dec_string<3>(value), '\n');
514 });
515 };
516
517 edit16("AF", "A", "F", [&]{ return regs.getAF(); }, [&](uint16_t value) { regs.setAF(value); });
518 ImGui::SameLine(0.0f, 20.0f);
519 edit16("AF'", "A'", "F'", [&]{ return regs.getAF2(); }, [&](uint16_t value) { regs.setAF2(value); });
520
521 edit16("BC", "B", "C", [&]{ return regs.getBC(); }, [&](uint16_t value) { regs.setBC(value); });
522 ImGui::SameLine(0.0f, 20.0f);
523 edit16("BC'", "B'", "C'", [&]{ return regs.getBC2(); }, [&](uint16_t value) { regs.setBC2(value); });
524
525 edit16("DE", "D", "E", [&]{ return regs.getDE(); }, [&](uint16_t value) { regs.setDE(value); });
526 ImGui::SameLine(0.0f, 20.0f);
527 edit16("DE'", "D'", "E'", [&]{ return regs.getDE2(); }, [&](uint16_t value) { regs.setDE2(value); });
528
529 edit16("HL", "H", "L", [&]{ return regs.getHL(); }, [&](uint16_t value) { regs.setHL(value); });
530 ImGui::SameLine(0.0f, 20.0f);
531 edit16("HL'", "H'", "L'", [&]{ return regs.getHL2(); }, [&](uint16_t value) { regs.setHL2(value); });
532
533 edit16("IX", "IXh", "IXl", [&]{ return regs.getIX(); }, [&](uint16_t value) { regs.setIX(value); });
534 ImGui::SameLine(0.0f, 20.0f);
535 edit16("IY ", "IYh", "IYl", [&]{ return regs.getIY(); }, [&](uint16_t value) { regs.setIY(value); });
536
537 edit16("PC", "PCh", "PCl", [&]{ return regs.getPC(); }, [&](uint16_t value) { regs.setPC(value); });
538 ImGui::SameLine(0.0f, 20.0f);
539 edit16("SP ", "SPh", "SPl", [&]{ return regs.getSP(); }, [&](uint16_t value) { regs.setSP(value); });
540
541 edit8("I ", [&]{ return regs.getI(); }, [&](uint8_t value) { regs.setI(value); });
542 ImGui::SameLine(0.0f, 20.0f);
543 edit8("R ", [&]{ return regs.getR(); }, [&](uint8_t value) { regs.setR(value); });
544
545 ImGui::AlignTextToFramePadding();
547 ImGui::SameLine();
548 ImGui::SetNextItemWidth(width16);
549 if (uint8_t im = regs.getIM();
550 ImGui::InputScalar("##IM", ImGuiDataType_U8, &im, nullptr, nullptr, "%d")) {
551 if (im <= 2) regs.setIM(im);
552 }
553
554 ImGui::SameLine(0.0f, 20.0f);
555 ImGui::AlignTextToFramePadding();
556 if (bool ei = regs.getIFF1();
557 ImGui::Selectable(ei ? "EI" : "DI", false, ImGuiSelectableFlags_AllowDoubleClick)) {
558 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
559 regs.setIFF1(!ei);
560 regs.setIFF2(!ei);
561 }
562 }
563 simpleToolTip("double-click to toggle");
564 });
565}
566
567void ImGuiDebugger::drawFlags(CPURegs& regs)
568{
569 if (!showFlags) return;
570 im::Window("CPU flags", &showFlags, [&]{
571 auto [sizeH1_, sizeH2_, sizeV_] = [&]{
573 return std::tuple{
574 ImGui::CalcTextSize("NC"sv),
575 ImGui::CalcTextSize("X:0"sv),
576 ImGui::CalcTextSize("C 0 (NC)"sv)
577 };
578 }();
579 // clang workaround
580 auto sizeH1 = sizeH1_; auto sizeH2 = sizeH2_; auto sizeV = sizeV_;
581
582 auto f = regs.getF();
583
584 auto draw = [&](const char* name, uint8_t bit, const char* val0 = nullptr, const char* val1 = nullptr) {
585 std::string s;
586 ImVec2 sz;
587 if (flagsLayout == 0) {
588 // horizontal
589 if (val0) {
590 s = (f & bit) ? val1 : val0;
591 sz = sizeH1;
592 } else {
593 s = strCat(name, ':', (f & bit) ? '1' : '0');
594 sz = sizeH2;
595 }
596 } else {
597 // vertical
598 s = strCat(name, ' ', (f & bit) ? '1' : '0');
599 if (val0) {
600 strAppend(s, " (", (f & bit) ? val1 : val0, ')');
601 }
602 sz = sizeV;
603 }
605 if (ImGui::Selectable(s.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, sz)) {
606 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
607 regs.setF(f ^ bit);
608 }
609 }
610 });
611 simpleToolTip("double-click to toggle, right-click to configure");
612 if (flagsLayout == 0) {
613 // horizontal
614 ImGui::SameLine(0.0f, sizeH1.x);
615 }
616 };
617
618 draw("S", 0x80, " P", " M");
619 draw("Z", 0x40, "NZ", " Z");
620 if (showXYFlags) draw("Y", 0x20);
621 draw("H", 0x10);
622 if (showXYFlags) draw("X", 0x08);
623 draw("P", 0x04, "PO", "PE");
624 draw("N", 0x02);
625 draw("C", 0x01, "NC", " C");
626
628 ImGui::TextUnformatted("Layout"sv);
629 ImGui::RadioButton("horizontal", &flagsLayout, 0);
630 ImGui::RadioButton("vertical", &flagsLayout, 1);
631 ImGui::Separator();
632 ImGui::Checkbox("show undocumented", &showXYFlags);
633 });
634 });
635}
636
637} // namespace openmsx
uintptr_t id
int g
std::string_view getDebuggableName() const
void loadStart() override
void showMenu(MSXMotherBoard *motherBoard) override
void checkShortcuts(MSXCPUInterface &cpuInterface, MSXMotherBoard &motherBoard)
ImGuiDebugger(ImGuiManager &manager)
void loadLine(std::string_view name, zstring_view value) override
void setGotoTarget(uint16_t target)
void paint(MSXMotherBoard *motherBoard) override
void save(ImGuiTextBuffer &buf) override
static std::pair< const MSXRom *, RomBlockDebuggableBase * > getRomBlocks(Debugger &debugger, const MSXDevice *device)
static void actionToggleBp(MSXMotherBoard &motherBoard)
std::unique_ptr< ImGuiBreakPoints > breakPoints
std::unique_ptr< ImGuiVdpRegs > vdpRegs
std::unique_ptr< ImGuiWatchExpr > watchExpr
std::unique_ptr< ImGuiPalette > palette
Shortcuts & getShortcuts()
void executeDelayed(std::function< void()> action)
std::unique_ptr< ImGuiSymbols > symbols
ImGuiManager & manager
Definition ImGuiPart.hh:30
CPURegs & getRegisters()
Definition MSXCPU.cc:343
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
MSXCPUInterface & getCPUInterface()
static unsigned getBlockSize(RomType type)
Definition RomInfo.cc:202
const Shortcut & getShortcut(ID id) const
Definition Shortcuts.cc:93
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:45
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:39
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:26
Definition ImGuiCpp.hh:60
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 Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void PopupContextWindow(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:438
bool Menu(const char *label, bool enabled, std::invocable<> auto next)
Definition ImGuiCpp.hh:359
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:506
void Group(std::invocable<> auto next)
Definition ImGuiCpp.hh:236
void Font(ImFont *font, std::invocable<> auto next)
Definition ImGuiCpp.hh:131
void ListClipper(size_t count, int forceIndex, float lineHeight, std::invocable< int > auto next)
Definition ImGuiCpp.hh:538
This file implemented 3 utility functions:
Definition Autofire.cc:11
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:79
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
std::string getKeyChordName(ImGuiKeyChord keyChord)
bool ButtonWithCenteredGlyph(ImWchar glyph, gl::vec2 maxGlyphSize)
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
auto equal_range(ForwardRange &&range, const T &value, Compare comp={})
Definition ranges.hh:135
constexpr void sort(RandomAccessRange &&range)
Definition ranges.hh:51
auto lower_bound(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
Definition ranges.hh:117
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
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147
constexpr auto xrange(T e)
Definition xrange.hh:132