| 1 |
#include "ImGuiSlotMap.hh" |
| 2 |
|
| 3 |
#include "ImGuiCpp.hh" |
| 4 |
#include "ImGuiManager.hh" |
| 5 |
#include "ImGuiMedia.hh" |
| 6 |
#include "ImGuiUtils.hh" |
| 7 |
|
| 8 |
#include "CartridgeSlotManager.hh" |
| 9 |
#include "Debuggable.hh" |
| 10 |
#include "Debugger.hh" |
| 11 |
#include "DummyDevice.hh" |
| 12 |
#include "MSXCPUInterface.hh" |
| 13 |
#include "MSXDevice.hh" |
| 14 |
#include "MSXMotherBoard.hh" |
| 15 |
#include "MSXMultiMemDevice.hh" |
| 16 |
#include "TclObject.hh" |
| 17 |
|
| 18 |
#include "gl_vec.hh" |
| 19 |
#include "static_vector.hh" |
| 20 |
#include "strCat.hh" |
| 21 |
#include "xrange.hh" |
| 22 |
|
| 23 |
#include <imgui.h> |
| 24 |
|
| 25 |
#include <algorithm> |
| 26 |
#include <optional> |
| 27 |
#include <ranges> |
| 28 |
#include <string_view> |
| 29 |
#include <utility> |
| 30 |
#include <vector> |
| 31 |
|
| 32 |
namespace openmsx { |
| 33 |
|
| 34 |
static constexpr unsigned PAGE_SIZE = 0x4000; |
| 35 |
static constexpr unsigned ADDRESS_SPACE = 4 * PAGE_SIZE; |
| 36 |
|
| 37 |
void ImGuiSlotMap::save(ImGuiTextBuffer& buf) |
| 38 |
{ |
| 39 |
savePersistent(buf, *this, persistentElements); |
| 40 |
} |
| 41 |
|
| 42 |
void ImGuiSlotMap::loadLine(std::string_view name, zstring_view value) |
| 43 |
{ |
| 44 |
loadOnePersistent(name, value, *this, persistentElements); |
| 45 |
} |
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
struct Block { |
| 50 |
unsigned begin; |
| 51 |
unsigned end; |
| 52 |
const MSXDevice* device; |
| 53 |
}; |
| 54 |
|
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
static void drawText(ImDrawList* drawList, std::string_view text, |
| 60 |
gl::vec2 min, gl::vec2 max, ImU32 color) |
| 61 |
{ |
| 62 |
const auto& style = ImGui::GetStyle(); |
| 63 |
auto [width, height] = (max - min) - 2.0f * gl::vec2(style.CellPadding); |
| 64 |
auto lineHeight = ImGui::GetTextLineHeight(); |
| 65 |
if ((height < lineHeight) || (width < ImGui::CalcTextSize("..."sv).x)) return; |
| 66 |
|
| 67 |
auto draw = [&](std::string_view line, float y) { |
| 68 |
auto lineWidth = ImGui::CalcTextSize(line).x; |
| 69 |
drawList->AddText(gl::vec2(min.x + 0.5f * ((max.x - min.x) - lineWidth), y), |
| 70 |
color, line.data(), line.data() + line.size()); |
| 71 |
}; |
| 72 |
|
| 73 |
|
| 74 |
|
| 75 |
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
auto* font = ImGui::GetFont(); |
| 80 |
auto fontSize = ImGui::GetFontSize(); |
| 81 |
static_vector<std::string_view, 16> lines; |
| 82 |
auto maxLines = std::min(size_t(height / lineHeight), lines.max_size()); |
| 83 |
std::string_view rest; |
| 84 |
const char* pos = text.data(); |
| 85 |
const char* end = pos + text.size(); |
| 86 |
while (pos < end) { |
| 87 |
auto remainder = std::string_view(pos, size_t(end - pos)); |
| 88 |
if ((lines.size() + 1) == maxLines) { rest = remainder; break; } |
| 89 |
const char* stop = font->CalcWordWrapPosition(fontSize, pos, end, width); |
| 90 |
if ((stop == pos) || |
| 91 |
((stop != end) && (*stop != ' '))) { |
| 92 |
rest = remainder; |
| 93 |
break; |
| 94 |
} |
| 95 |
lines.push_back(std::string_view(pos, size_t(stop - pos))); |
| 96 |
pos = stop; |
| 97 |
while ((pos < end) && (*pos == ' ')) ++pos; |
| 98 |
} |
| 99 |
|
| 100 |
auto numLines = lines.size() + (rest.empty() ? 0 : 1); |
| 101 |
auto y = min.y + 0.5f * ((max.y - min.y) - float(numLines) * lineHeight); |
| 102 |
for (auto line : lines) { |
| 103 |
draw(line, y); |
| 104 |
y += lineHeight; |
| 105 |
} |
| 106 |
|
| 107 |
if (!rest.empty()) draw(ImGui::rightClip(rest, width), y); |
| 108 |
} |
| 109 |
|
| 110 |
namespace { |
| 111 |
|
| 112 |
|
| 113 |
struct HoveredBlock { |
| 114 |
Block block{0, 0, nullptr}; |
| 115 |
std::optional<unsigned> cartridgeSlot; |
| 116 |
float height = 0.0f; |
| 117 |
bool valid = false; |
| 118 |
}; |
| 119 |
|
| 120 |
|
| 121 |
|
| 122 |
struct TinyBlock { |
| 123 |
gl::vec2 min; |
| 124 |
gl::vec2 max; |
| 125 |
ImU32 color; |
| 126 |
}; |
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
struct Metrics { |
| 131 |
float rowHeight; |
| 132 |
float headerHeight; |
| 133 |
float labelWidth; |
| 134 |
float minSlotWidth; |
| 135 |
float minMapHeight; |
| 136 |
gl::vec2 gap; |
| 137 |
float overhang; |
| 138 |
}; |
| 139 |
|
| 140 |
struct DrawContext { |
| 141 |
ImGuiManager& manager; |
| 142 |
MSXCPUInterface& cpuInterface; |
| 143 |
const CartridgeSlotManager& slotManager; |
| 144 |
Debugger& debugger; |
| 145 |
const MSXDevice* dummyDevice; |
| 146 |
Metrics metrics; |
| 147 |
ImDrawList* drawList; |
| 148 |
ImU32 emptyColor; |
| 149 |
ImU32 occupiedColor; |
| 150 |
ImU32 externalColor; |
| 151 |
ImU32 outlineColor; |
| 152 |
ImU32 textColor; |
| 153 |
ImU32 dimTextColor; |
| 154 |
gl::vec2 mouse; |
| 155 |
HoveredBlock hovered; |
| 156 |
std::vector<std::pair<gl::vec2, gl::vec2>> outlines; |
| 157 |
std::vector<std::pair<gl::vec2, gl::vec2>> separators; |
| 158 |
std::vector<TinyBlock> tinyBlocks; |
| 159 |
std::vector<Block> blocks; |
| 160 |
std::vector<Block> scratch; |
| 161 |
}; |
| 162 |
} |
| 163 |
|
| 164 |
|
| 165 |
|
| 166 |
|
| 167 |
|
| 168 |
|
| 169 |
|
| 170 |
|
| 171 |
|
| 172 |
static void getBlocks(DrawContext& ctx, int ps, int ss) |
| 173 |
{ |
| 174 |
auto& scratch = ctx.scratch; |
| 175 |
scratch.clear(); |
| 176 |
for (auto page : xrange(4)) { |
| 177 |
auto pageBegin = PAGE_SIZE * unsigned(page); |
| 178 |
const auto* device = ctx.cpuInterface.getMSXDevice(ps, ss, page); |
| 179 |
if (device == ctx.dummyDevice) continue; |
| 180 |
if (const auto* multi = dynamic_cast<const MSXMultiMemDevice*>(device)) { |
| 181 |
|
| 182 |
|
| 183 |
auto first = scratch.size(); |
| 184 |
for (const auto& range : multi->getRanges()) { |
| 185 |
auto begin = std::max(range.base, pageBegin); |
| 186 |
auto end = std::min(range.base + range.size, pageBegin + PAGE_SIZE); |
| 187 |
if (begin < end) scratch.emplace_back(begin, end, range.device); |
| 188 |
} |
| 189 |
std::ranges::sort(scratch.begin() + first, scratch.end(), {}, &Block::begin); |
| 190 |
} else { |
| 191 |
scratch.emplace_back(pageBegin, pageBegin + PAGE_SIZE, device); |
| 192 |
} |
| 193 |
} |
| 194 |
|
| 195 |
|
| 196 |
|
| 197 |
auto& result = ctx.blocks; |
| 198 |
result.clear(); |
| 199 |
unsigned pos = 0; |
| 200 |
for (const auto& block : scratch) { |
| 201 |
if (!result.empty() && (result.back().device == block.device) && |
| 202 |
(result.back().end == block.begin)) { |
| 203 |
result.back().end = block.end; |
| 204 |
} else { |
| 205 |
if (pos < block.begin) result.emplace_back(pos, block.begin, nullptr); |
| 206 |
result.push_back(block); |
| 207 |
} |
| 208 |
pos = block.end; |
| 209 |
} |
| 210 |
if (pos < ADDRESS_SPACE) result.emplace_back(pos, ADDRESS_SPACE, nullptr); |
| 211 |
} |
| 212 |
|
| 213 |
static Metrics getMetrics() |
| 214 |
{ |
| 215 |
const auto& style = ImGui::GetStyle(); |
| 216 |
auto lineHeight = ImGui::GetTextLineHeight(); |
| 217 |
auto rowHeight = lineHeight + 2.0f * style.CellPadding.y; |
| 218 |
return { |
| 219 |
.rowHeight = rowHeight, |
| 220 |
.headerHeight = 2.0f * rowHeight, |
| 221 |
|
| 222 |
|
| 223 |
.labelWidth = 2.0f * style.CellPadding.x + |
| 224 |
std::max({ImGui::CalcTextSize("0000"sv).x, |
| 225 |
ImGui::CalcTextSize("C000"sv).x, |
| 226 |
ImGui::CalcTextSize("FFFF"sv).x}), |
| 227 |
|
| 228 |
|
| 229 |
.minSlotWidth = 4.0f * 3.0f * lineHeight, |
| 230 |
.minMapHeight = 10.0f * rowHeight, |
| 231 |
.gap = 2.0f * gl::vec2(style.ItemSpacing), |
| 232 |
|
| 233 |
.overhang = 0.5f * rowHeight, |
| 234 |
}; |
| 235 |
} |
| 236 |
|
| 237 |
|
| 238 |
|
| 239 |
static constexpr float MIN_BLOCK_HEIGHT = 4.0f; |
| 240 |
|
| 241 |
|
| 242 |
|
| 243 |
static void drawSlot(DrawContext& ctx, int ps, int ss, bool expanded, |
| 244 |
float x0, float x1, float headerTop, float top, float bottom) |
| 245 |
{ |
| 246 |
|
| 247 |
|
| 248 |
auto cartridge = ctx.slotManager.findSlot(ps, ss, true); |
| 249 |
auto labelHeight = 0.5f * (top - headerTop); |
| 250 |
if (cartridge) { |
| 251 |
drawText(ctx.drawList, tmpStrCat("Slot ", char('A' + *cartridge)), |
| 252 |
gl::vec2(x0, headerTop), gl::vec2(x1, headerTop + labelHeight), |
| 253 |
ctx.dimTextColor); |
| 254 |
} |
| 255 |
drawText(ctx.drawList, expanded ? strCat(ps, '-', ss) : strCat(ps), |
| 256 |
gl::vec2(x0, headerTop + labelHeight), gl::vec2(x1, top), ctx.textColor); |
| 257 |
|
| 258 |
auto height = bottom - top; |
| 259 |
auto addressToY = [&](unsigned address) { |
| 260 |
return bottom - height * (float(address) * (1.0f / float(ADDRESS_SPACE))); |
| 261 |
}; |
| 262 |
|
| 263 |
getBlocks(ctx, ps, ss); |
| 264 |
|
| 265 |
|
| 266 |
auto lowestEnlarged = bottom; |
| 267 |
for (const auto& block : ctx.blocks) { |
| 268 |
auto blockTop = addressToY(block.end); |
| 269 |
auto blockBottom = addressToY(block.begin); |
| 270 |
auto blockHeight = blockBottom - blockTop; |
| 271 |
auto color = !block.device ? ctx.emptyColor |
| 272 |
: (cartridge ? ctx.externalColor : ctx.occupiedColor); |
| 273 |
|
| 274 |
if (auto grow = 0.5f * (MIN_BLOCK_HEIGHT - blockHeight); grow > 0.0f) { |
| 275 |
|
| 276 |
|
| 277 |
|
| 278 |
|
| 279 |
blockTop -= grow; |
| 280 |
blockBottom += grow; |
| 281 |
if (blockBottom > lowestEnlarged) { |
| 282 |
blockTop -= blockBottom - lowestEnlarged; |
| 283 |
blockBottom = lowestEnlarged; |
| 284 |
} |
| 285 |
lowestEnlarged = blockTop; |
| 286 |
ctx.tinyBlocks.emplace_back(gl::vec2(x0, blockTop), gl::vec2(x1, blockBottom), |
| 287 |
color); |
| 288 |
} else { |
| 289 |
gl::vec2 min{x0, blockTop}; |
| 290 |
gl::vec2 max{x1, blockBottom}; |
| 291 |
ctx.drawList->AddRectFilled(min, max, color); |
| 292 |
|
| 293 |
|
| 294 |
|
| 295 |
|
| 296 |
if (block.end != ADDRESS_SPACE) { |
| 297 |
ctx.separators.emplace_back(min, gl::vec2(x1 - 1.0f, blockTop)); |
| 298 |
} |
| 299 |
drawText(ctx.drawList, block.device ? std::string_view(block.device->getName()) |
| 300 |
: "empty"sv, |
| 301 |
min, max, ctx.textColor); |
| 302 |
} |
| 303 |
|
| 304 |
|
| 305 |
|
| 306 |
if ((x0 <= ctx.mouse.x) && (ctx.mouse.x < x1) && |
| 307 |
(blockTop <= ctx.mouse.y) && (ctx.mouse.y < blockBottom) && |
| 308 |
(!ctx.hovered.valid || (blockHeight < ctx.hovered.height))) { |
| 309 |
ctx.hovered = HoveredBlock{block, cartridge, blockHeight, true}; |
| 310 |
} |
| 311 |
} |
| 312 |
} |
| 313 |
|
| 314 |
static void drawDeviceToolTip(DrawContext& ctx) |
| 315 |
{ |
| 316 |
const auto& block = ctx.hovered.block; |
| 317 |
im::Tooltip([&]{ |
| 318 |
im::TextWrapPos(ImGui::GetFontSize() * 35.0f, [&]{ |
| 319 |
bool header = false; |
| 320 |
|
| 321 |
|
| 322 |
|
| 323 |
if (auto slot = ctx.hovered.cartridgeSlot) { |
| 324 |
auto content = ctx.manager.media->displayNameForSlotContent( |
| 325 |
ctx.slotManager, *slot); |
| 326 |
if (block.device && (content == block.device->getName())) { |
| 327 |
ImGui::StrCat("Slot ", char('A' + *slot)); |
| 328 |
} else { |
| 329 |
ImGui::StrCat("Slot ", char('A' + *slot), ": ", content); |
| 330 |
} |
| 331 |
header = true; |
| 332 |
} |
| 333 |
if (block.device) { |
| 334 |
ImGui::TextUnformatted(block.device->getName()); |
| 335 |
header = true; |
| 336 |
} |
| 337 |
if (header) ImGui::Separator(); |
| 338 |
|
| 339 |
auto printSize = [](std::string_view label, unsigned bytes) { |
| 340 |
auto unit = (bytes == 1) ? " byte"sv : " bytes"sv; |
| 341 |
ImGui::StrCat(label, ": ", (bytes < 1024) ? strCat(bytes, unit) |
| 342 |
: strCat(bytes / 1024, "kB")); |
| 343 |
}; |
| 344 |
ImGui::StrCat("address: 0x", hex_string<4, HexCase::upper>(block.begin), |
| 345 |
" - 0x", hex_string<4, HexCase::upper>(block.end - 1)); |
| 346 |
printSize("size", block.end - block.begin); |
| 347 |
if (!block.device) return; |
| 348 |
|
| 349 |
|
| 350 |
|
| 351 |
|
| 352 |
|
| 353 |
TclObject info; |
| 354 |
block.device->getDeviceInfo(info); |
| 355 |
for (size_t i = 0; (i + 1) < info.size(); i += 2) { |
| 356 |
auto key = info.getListIndexUnchecked(i).getString(); |
| 357 |
auto value = info.getListIndexUnchecked(i + 1).getString(); |
| 358 |
if (value.empty()) continue; |
| 359 |
ImGui::StrCat(key, ": ", value); |
| 360 |
} |
| 361 |
|
| 362 |
|
| 363 |
if (const auto* debuggable = ctx.debugger.findDebuggable(block.device->getName())) { |
| 364 |
if (auto bytes = debuggable->getSize(); bytes != 0) { |
| 365 |
printSize("total memory", bytes); |
| 366 |
} |
| 367 |
} |
| 368 |
}); |
| 369 |
}); |
| 370 |
} |
| 371 |
|
| 372 |
static void drawSlotMap(DrawContext& ctx) |
| 373 |
{ |
| 374 |
auto [rowHeight, headerHeight, labelWidth, minSlotWidth, minMapHeight, gap, overhang] = |
| 375 |
ctx.metrics; |
| 376 |
auto avail = gl::vec2(ImGui::GetContentRegionAvail()); |
| 377 |
|
| 378 |
|
| 379 |
auto widthFor = [&](int perRow) { |
| 380 |
return labelWidth + float(perRow) * minSlotWidth + float(perRow - 1) * gap.x; |
| 381 |
}; |
| 382 |
int perRow = 4; |
| 383 |
if (widthFor(perRow) > avail.x) perRow = 2; |
| 384 |
if ((perRow == 2) && (widthFor(perRow) > avail.x)) perRow = 1; |
| 385 |
int numRows = 4 / perRow; |
| 386 |
|
| 387 |
auto slotWidth = std::max(minSlotWidth, |
| 388 |
(avail.x - labelWidth - float(perRow - 1) * gap.x) / float(perRow)); |
| 389 |
auto mapHeight = std::max(minMapHeight, |
| 390 |
(avail.y - overhang - float(numRows) * headerHeight - float(numRows - 1) * gap.y) / |
| 391 |
float(numRows)); |
| 392 |
|
| 393 |
auto origin = gl::vec2(ImGui::GetCursorScreenPos()); |
| 394 |
gl::vec2 size{labelWidth + float(perRow) * slotWidth + float(perRow - 1) * gap.x, |
| 395 |
float(numRows) * (headerHeight + mapHeight) + float(numRows - 1) * gap.y + |
| 396 |
overhang}; |
| 397 |
ImGui::Dummy(size); |
| 398 |
|
| 399 |
for (auto row : xrange(numRows)) { |
| 400 |
auto headerTop = origin.y + float(row) * (headerHeight + mapHeight + gap.y); |
| 401 |
auto top = headerTop + headerHeight; |
| 402 |
auto bottom = top + mapHeight; |
| 403 |
auto addressToY = [&](unsigned address) { |
| 404 |
return bottom - mapHeight * (float(address) * (1.0f / float(ADDRESS_SPACE))); |
| 405 |
}; |
| 406 |
|
| 407 |
|
| 408 |
|
| 409 |
auto drawTick = [&](unsigned address, std::string_view label) { |
| 410 |
auto y = addressToY(address); |
| 411 |
drawText(ctx.drawList, label, |
| 412 |
gl::vec2(origin.x, y - 0.5f * rowHeight), |
| 413 |
gl::vec2(origin.x + labelWidth, y + 0.5f * rowHeight), |
| 414 |
ctx.textColor); |
| 415 |
}; |
| 416 |
for (auto page : xrange(4)) { |
| 417 |
auto address = PAGE_SIZE * unsigned(page); |
| 418 |
drawTick(address, tmpStrCat(hex_string<4, HexCase::upper>(address))); |
| 419 |
} |
| 420 |
drawTick(ADDRESS_SPACE, "FFFF"sv); |
| 421 |
|
| 422 |
auto x = origin.x + labelWidth; |
| 423 |
for (auto ps : xrange(row * perRow, std::min((row + 1) * perRow, 4))) { |
| 424 |
if (ctx.cpuInterface.isExpanded(ps)) { |
| 425 |
auto width = 0.25f * slotWidth; |
| 426 |
for (auto ss : xrange(4)) { |
| 427 |
auto x0 = x + float(ss) * width; |
| 428 |
drawSlot(ctx, ps, ss, true, x0, x0 + width, headerTop, top, bottom); |
| 429 |
|
| 430 |
|
| 431 |
if (ss != 0) { |
| 432 |
ctx.separators.emplace_back(gl::vec2(x0, top), |
| 433 |
gl::vec2(x0, bottom - 1.0f)); |
| 434 |
} |
| 435 |
} |
| 436 |
} else { |
| 437 |
drawSlot(ctx, ps, 0, false, x, x + slotWidth, headerTop, top, bottom); |
| 438 |
} |
| 439 |
ctx.outlines.emplace_back(gl::vec2(x, top), gl::vec2(x + slotWidth, bottom)); |
| 440 |
x += slotWidth + gap.x; |
| 441 |
} |
| 442 |
} |
| 443 |
|
| 444 |
|
| 445 |
|
| 446 |
|
| 447 |
|
| 448 |
|
| 449 |
|
| 450 |
|
| 451 |
|
| 452 |
|
| 453 |
for (const auto& [p1, p2] : ctx.separators) { |
| 454 |
ctx.drawList->AddLine(p1, p2, ctx.outlineColor); |
| 455 |
} |
| 456 |
ctx.separators.clear(); |
| 457 |
for (const auto& [min, max] : ctx.outlines) { |
| 458 |
ctx.drawList->AddRect(min, max, ctx.outlineColor); |
| 459 |
} |
| 460 |
ctx.outlines.clear(); |
| 461 |
|
| 462 |
|
| 463 |
|
| 464 |
for (const auto& [min, max, color] : ctx.tinyBlocks) { |
| 465 |
ctx.drawList->AddRectFilled(min, max, color); |
| 466 |
ctx.drawList->AddRect(min, max, ctx.outlineColor); |
| 467 |
} |
| 468 |
ctx.tinyBlocks.clear(); |
| 469 |
} |
| 470 |
|
| 471 |
|
| 472 |
|
| 473 |
static gl::vec2 defaultWindowSize(const Metrics& m) |
| 474 |
{ |
| 475 |
const auto& style = ImGui::GetStyle(); |
| 476 |
gl::vec2 content{m.labelWidth + 3.0f * (m.minSlotWidth + m.gap.x), |
| 477 |
2.0f * (m.headerHeight + m.minMapHeight) + m.gap.y + m.overhang}; |
| 478 |
return content + 2.0f * gl::vec2(style.WindowPadding) + |
| 479 |
gl::vec2(0.0f, ImGui::GetFrameHeight()); |
| 480 |
} |
| 481 |
|
| 482 |
void ImGuiSlotMap::paint(MSXMotherBoard* motherBoard) |
| 483 |
{ |
| 484 |
if (!show) return; |
| 485 |
if (!motherBoard) return; |
| 486 |
|
| 487 |
auto& cpuInterface = motherBoard->getCPUInterface(); |
| 488 |
DrawContext ctx{ |
| 489 |
.manager = manager, |
| 490 |
.cpuInterface = cpuInterface, |
| 491 |
.slotManager = motherBoard->getSlotManager(), |
| 492 |
.debugger = motherBoard->getDebugger(), |
| 493 |
.dummyDevice = &cpuInterface.getDummyDevice(), |
| 494 |
.metrics = getMetrics(), |
| 495 |
.drawList = nullptr, |
| 496 |
.emptyColor = ImGui::GetColorU32(getColor(imColor::GRAY), 0.4f), |
| 497 |
.occupiedColor = ImGui::GetColorU32(ImGuiCol_Header), |
| 498 |
.externalColor = getColor(imColor::YELLOW_BG), |
| 499 |
.outlineColor = ImGui::GetColorU32(ImGuiCol_Text), |
| 500 |
.textColor = ImGui::GetColorU32(ImGuiCol_Text), |
| 501 |
.dimTextColor = ImGui::GetColorU32(ImGuiCol_TextDisabled), |
| 502 |
.mouse = gl::vec2(ImGui::GetIO().MousePos), |
| 503 |
|
| 504 |
|
| 505 |
.hovered = {}, |
| 506 |
.outlines = {}, |
| 507 |
.separators = {}, |
| 508 |
.tinyBlocks = {}, |
| 509 |
.blocks = {}, |
| 510 |
.scratch = {}, |
| 511 |
}; |
| 512 |
ctx.outlines.reserve(4); |
| 513 |
ctx.separators.reserve(64); |
| 514 |
|
| 515 |
ImGui::SetNextWindowSize(defaultWindowSize(ctx.metrics), ImGuiCond_FirstUseEver); |
| 516 |
im::Window("Slot map", &show, [&]{ |
| 517 |
ctx.drawList = ImGui::GetWindowDrawList(); |
| 518 |
drawSlotMap(ctx); |
| 519 |
|
| 520 |
if (ctx.hovered.valid && ImGui::IsWindowHovered()) { |
| 521 |
drawDeviceToolTip(ctx); |
| 522 |
} |
| 523 |
}); |
| 524 |
} |
| 525 |
|
| 526 |
} |