316 ImGui::TextDisabled(
"This project has no custom rail assets.");
320 ImGui::GetContentRegionAvail().x, 64.0f, ImGui::GetStyle().ItemSpacing.x);
322 ImGui::PushID(
"MinecartRailPalette");
325 static constexpr std::array<std::array<int, 7>, 2> kFamilies = {
326 {{7, 6, 8, 9, 10, 11, 12}, {0, 1, 2, 3, 4, 5, 14}}};
327 for (
size_t family = 0; family < kFamilies.size(); ++family) {
328 ImGui::TextDisabled(
"%s",
329 family == 0 ?
"Floor-style rails" :
"Pit-style rails");
331 for (
int subtype : kFamilies[family]) {
336 const auto label = absl::StrFormat(
"##Rail%d", subtype);
337 ImGui::BeginDisabled(!status.ok());
338 if (ImGui::Selectable(label.c_str(),
341 0, ImVec2(layout.item_size, layout.item_size))) {
346 absl::StrFormat(
"Rail%d", subtype));
347 ImGui::EndDisabled();
348 const auto top = ImGui::GetItemRectMin();
351 const bool rendered =
352 status.ok() && ImGui::IsItemVisible() &&
354 ImVec2(layout.item_size - 6, layout.item_size - 6));
356 ImGui::GetWindowDrawList()->AddText(
357 top, ImGui::GetColorU32(ImGuiCol_TextDisabled),
358 status.ok() ?
"Preview..." :
"Missing");
360 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
361 ImGui::BeginTooltip();
362 ImGui::TextUnformatted(name.c_str());
363 ImGui::TextUnformatted(status.ok()
364 ?
"Click, then place on the room canvas."
365 : std::string(status.message()).c_str());
366 if (status.ok() && family == 0) {
367 ImGui::TextUnformatted(
368 "Uses fixed floor tiles; check the room's graphics and palette.");
372 column = (column + 1) % layout.columns;
392 static const ObjectRange ranges[] = {
393 {0x00, 0xF7,
"Type 1"},
394 {0x100, 0x13F,
"Type 2"},
395 {0xF80, 0xFFF,
"Type 3"},
400 int custom_count = 0;
402 custom_count += std::min(obj_manager.GetSubtypeCount(object_id),
403 kPersistedCustomSubtypeSlots);
406 const std::string custom_mode_label =
407 absl::StrFormat(
"Custom Assets (%d)", custom_count);
408 if (ImGui::BeginTable(
409 "##ObjectBrowserMode", 2,
410 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX)) {
411 ImGui::TableNextColumn();
413 0, ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) {
416 ImGui::TableNextColumn();
417 if (ImGui::Selectable(custom_mode_label.c_str(),
419 ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) {
431 const ImGuiStyle& style = ImGui::GetStyle();
432 const float control_spacing = std::max(2.0f, style.ItemSpacing.x * 0.5f);
435 ImGuiStyleVar_ItemSpacing,
436 ImVec2(control_spacing, std::max(2.0f, style.ItemSpacing.y * 0.5f)));
438 const float toolbar_width =
439 std::max(ImGui::GetContentRegionAvail().x, 1.0f);
440 ImGui::SetNextItemWidth(toolbar_width);
441 ImGui::InputTextWithHint(
"##ObjectSearch",
"Search by name or hex ID...",
445 static const char* kFilterLabels[] = {
446 "All categories",
"Walls",
"Floors",
"Chests",
447 "Doorways",
"Decor",
"Stairs"};
448 static const char* kStreamLabels[] = {
"All streams",
"Type 1",
"Type 2",
450 constexpr const char* kMoreLabel =
"More##ObjectSelectorMore";
451 const float more_width = ImGui::CalcTextSize(kMoreLabel,
nullptr,
true).x +
452 style.FramePadding.x * 2.0f;
453 const bool stack_filters = toolbar_width < 230.0f;
454 const float filter_row_width =
455 std::max(1.0f, toolbar_width - more_width - control_spacing);
456 const float used_width = ImGui::GetFrameHeight() +
457 ImGui::CalcTextSize(
"Used here").x +
458 style.ItemInnerSpacing.x;
459 const float category_width =
462 : std::max(1.0f, filter_row_width - used_width - control_spacing);
464 ImGui::SetNextItemWidth(category_width);
466 IM_ARRAYSIZE(kFilterLabels));
469 tr(
"Doorways are tile objects. Use the Door Editor "
470 "to place or change room doors."));
472 if (!stack_filters) {
473 ImGui::SameLine(0.0f, control_spacing);
476 ImGui::SameLine(0.0f, control_spacing);
477 if (ImGui::Button(kMoreLabel, ImVec2(more_width, 0.0f))) {
478 ImGui::OpenPopup(
"##ObjectSelectorOptionsPopup");
480 if (ImGui::BeginPopup(
"##ObjectSelectorOptionsPopup")) {
484 if (ImGui::MenuItem(tr(
"Clear filters"),
nullptr,
false, has_filters)) {
491 ImGui::SetNextItemWidth(130.0f);
493 IM_ARRAYSIZE(kStreamLabels));
495 ImGui::SeparatorText(tr(
"Display"));
497 if (ImGui::IsItemHovered()) {
499 tr(
"Show rendered object thumbnails in the selector.\n"
500 "Requires a room to be loaded and may cost some performance."));
502 ImGui::SeparatorText(tr(
"Card size"));
503 for (
int density = 0; density < 3; ++density) {
504 if (ImGui::MenuItem(kObjectGridDensityLabels[density],
nullptr,
516 const float child_height = std::max(ImGui::GetContentRegionAvail().y,
517 std::max(minimum_grid_height, 1.0f));
518 if (ImGui::BeginChild(
"##ObjectGrid", ImVec2(0, child_height),
false)) {
519 const float item_spacing = control_spacing;
524 ImGui::GetContentRegionAvail().x,
526 const float item_size = grid_layout.item_size;
527 const int columns = grid_layout.columns;
529 ImGuiStyleVar_ItemSpacing,
530 ImVec2(item_spacing, std::max(item_spacing, style.ItemSpacing.y)));
533 for (
const auto& range : ranges) {
540 ImGui::TextDisabled(
"%s 0x%03X-0x%03X", range.label, range.start,
544 int current_column = 0;
546 for (
int obj_id = range.start; obj_id <= range.end; ++obj_id) {
568 if (current_column > 0) {
569 ImGui::SameLine(0.0f, item_spacing);
571 ImGui::SetCursorPosX(ImGui::GetCursorPosX() +
572 grid_layout.leading_inset);
575 ImGui::PushID(obj_id);
579 ImVec2 button_size(item_size, item_size);
581 if (ImGui::Selectable(
"", is_selected, 0, button_size)) {
584 const bool item_visible = ImGui::IsItemVisible();
591 ImVec2 button_pos = ImGui::GetItemRectMin();
596 ImDrawList* draw_list = ImGui::GetWindowDrawList();
597 const bool show_id = item_size >= 32.0f;
598 const float footer_height =
599 show_id ? std::min(item_size * 0.32f, ImGui::GetFontSize() + 3.0f)
601 const float card_padding = std::min(3.0f, item_size * 0.08f);
602 const ImVec2 preview_pos(button_pos.x + card_padding,
603 button_pos.y + card_padding);
604 const ImVec2 preview_box(
605 std::max(1.0f, item_size - card_padding * 2.0f),
606 std::max(1.0f, item_size - footer_height - card_padding * 2.0f));
609 bool rendered =
false;
615 if (item_visible && !rendered) {
617 DrawFallbackPreviewTile(
618 draw_list, preview_pos, preview_box,
623 const bool item_hovered = ImGui::IsItemHovered();
624 if (item_visible && (is_selected || item_hovered)) {
625 const ImU32 border_color =
626 ImGui::GetColorU32(is_selected ? theme.dungeon_selection_primary
627 : theme.panel_border_color);
630 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
631 border_color, 2.0f, 0, is_selected ? 2.0f : 1.0f);
638 if (item_visible && !sym.
badge.empty() && sym.
badge !=
"?") {
639 const ImVec2 badge_text_size =
640 ImGui::CalcTextSize(sym.
badge.c_str(),
nullptr,
true);
641 const float badge_pad = 2.0f;
642 const float badge_w = badge_text_size.x + badge_pad * 2.0f;
643 const float badge_h = badge_text_size.y + badge_pad;
644 const ImVec2 badge_tl(button_pos.x + item_size - badge_w,
646 const ImVec2 badge_br(button_pos.x + item_size,
647 button_pos.y + badge_h);
648 draw_list->AddRectFilled(
650 ImGui::GetColorU32(WithAlpha(theme.panel_bg_darker, 0.82f)),
655 ? theme.dungeon_object_wall
656 : theme.text_secondary_gray;
658 ImVec2(badge_tl.x + badge_pad, badge_tl.y + badge_pad * 0.5f),
659 ImGui::GetColorU32(badge_color), sym.
badge.c_str());
662 if (item_visible && show_id) {
663 const ImVec2 card_bottom(button_pos.x + item_size,
664 button_pos.y + item_size);
665 const float footer_y = card_bottom.y - footer_height;
666 draw_list->AddRectFilled(
667 ImVec2(button_pos.x, footer_y), card_bottom,
668 ImGui::GetColorU32(WithAlpha(theme.panel_bg_darker, 0.88f)),
670 std::string id_text = absl::StrFormat(
"%03X", obj_id);
671 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
672 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
673 footer_y + (footer_height - id_size.y) * 0.5f);
674 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
681 {{ImGuiCol_PopupBg, theme.panel_bg_color},
682 {ImGuiCol_Border, theme.panel_border_color}});
684 if (ImGui::BeginTooltip()) {
685 ImGui::TextColored(theme.selection_primary, tr(
"Object 0x%03X"),
687 ImGui::Text(
"%s", full_name.c_str());
689 ImGui::TextColored(theme.text_secondary_gray, tr(
"Subtype %d"),
692 rendered ? theme.status_success : theme.status_warning,
694 rendered ?
"rendered tile layout"
696 :
"thumbnails off"));
699 const uint8_t preview_size =
701 const bool can_capture_layout =
706 const uint32_t layout_key =
708 if (can_capture_layout &&
711 auto& room_ref = *layout_room;
714 if (layout_or.ok()) {
721 ImGui::TextColored(theme.status_success, tr(
"Tiles: %zu"),
722 layout.cells.size());
724 if (can_capture_layout) {
727 room_ref.get_gfx_buffer().
data());
729 ImGui::TextColored(theme.status_active, tr(
"Draw Routine: %d"),
733 ImGui::Text(tr(
"Layout:"));
734 ImDrawList* tooltip_draw_list = ImGui::GetWindowDrawList();
735 ImVec2 grid_start = ImGui::GetCursorScreenPos();
736 float cell_size = 4.0f;
737 for (
const auto& cell : layout.cells) {
738 ImVec2 p1(grid_start.x + cell.rel_x * cell_size,
739 grid_start.y + cell.rel_y * cell_size);
740 ImVec2 p2(p1.x + cell_size, p1.y + cell_size);
741 tooltip_draw_list->AddRectFilled(
742 p1, p2, ThemeColor(theme.dungeon_grid_cell_highlight));
743 tooltip_draw_list->AddRect(
744 p1, p2, ThemeColor(theme.panel_border_color));
746 ImGui::Dummy(ImVec2(layout.bounds_width * cell_size,
747 layout.bounds_height * cell_size));
754 if (!sym.
family.empty()) {
758 const bool is_named_special =
761 if (is_named_special) {
762 ImGui::TextColored(theme.item_color, tr(
"Routine: %s"),
765 ImGui::TextColored(theme.text_secondary_gray, tr(
"Routine: %s"),
769 ImGui::TextColored(theme.dungeon_object_wall,
770 tr(
" BothBG: writes to BG1 and BG2"));
775 ImGui::TextColored(theme.text_secondary_gray,
776 tr(
"Click to select for placement"));
783 current_column = (current_column + 1) % columns;
1248 const std::string custom_base_path = obj_manager.GetBasePath();
1250 ImGui::TextColored(theme.text_info,
1252 ImGui::PushTextWrapPos(0.0f);
1254 theme.text_secondary_gray,
1255 tr(
"Manage the 21 fixed runtime assets used by Oracle custom objects."));
1256 ImGui::TextDisabled(
1257 "%s", tr(
"New subtypes require an ASM dispatch-table change; this "
1258 "browser edits or places existing slots only."));
1259 ImGui::PopTextWrapPos();
1262 if (ImGui::BeginTable(
1263 "##CustomObjectToolbar", 2,
1264 ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoPadOuterX)) {
1265 ImGui::TableSetupColumn(
"Status", ImGuiTableColumnFlags_WidthStretch, 2.0f);
1266 ImGui::TableSetupColumn(
"Actions", ImGuiTableColumnFlags_WidthFixed,
1268 ImGui::TableNextRow();
1270 ImGui::TableNextColumn();
1271 if (custom_base_path.empty()) {
1273 " Custom object folder is not configured.");
1275 ImGui::TextColored(theme.text_secondary_gray,
1277 if (ImGui::IsItemHovered()) {
1278 ImGui::SetTooltip(
"%s", custom_base_path.c_str());
1282 ImGui::TableNextColumn();
1284 obj_manager.ReloadAll();
1288 if (ImGui::IsItemHovered()) {
1290 tr(
"Refresh custom object and external sprite previews from disk. "
1291 "Unsaved room and tile edits are kept."));
1299 " Custom Objects is disabled for this project. Enable the feature "
1300 "before editing or placing runtime assets.");
1304 ImGui::TextDisabled(
"%s", tr(
"Card size"));
1306 ImGui::SetNextItemWidth(-1.0f);
1308 kObjectGridDensityLabels,
1309 IM_ARRAYSIZE(kObjectGridDensityLabels));
1311 ImGui::PushTextWrapPos(0.0f);
1314 " Track tile layouts are assets here; routes and collision are managed "
1315 "in Minecart Tracks.");
1316 ImGui::PopTextWrapPos();
1319 struct CustomAssetFamily {
1322 const char* description;
1324 static constexpr std::array<CustomAssetFamily, 3> kFamilies = {{
1325 {0x31,
"Tracks + Props (16)",
1326 "Minecart tile layouts, the Sword House wall object, and a small "
1328 {0x32,
"Ice Props (3)",
"Furnace, firewood, and chair assets"},
1329 {0x54,
"Boss Bodies (2)",
"Kydreeok and Manhandla body tilemaps"},
1331 const CustomAssetFamily* selected_family = &kFamilies.front();
1332 for (
const auto& family : kFamilies) {
1334 selected_family = &family;
1338 ImGui::TextDisabled(
"%s", tr(
"Asset family"));
1339 ImGui::SetNextItemWidth(-1.0f);
1340 if (ImGui::BeginCombo(
"##CustomAssetFamily", selected_family->label)) {
1341 for (
const auto& family : kFamilies) {
1343 if (ImGui::Selectable(family.label, selected)) {
1346 selected_family = &family;
1350 ImGui::SetItemDefaultFocus();
1355 ImGui::PushTextWrapPos(0.0f);
1356 ImGui::TextDisabled(
"%s", tr(selected_family->description));
1357 ImGui::PopTextWrapPos();
1360 const ImGuiStyle& style = ImGui::GetStyle();
1361 const float item_spacing = std::max(2.0f, style.ItemSpacing.x * 0.5f);
1363 ImGui::GetContentRegionAvail().x,
1365 const int columns = grid_layout.columns;
1366 const float item_size = grid_layout.item_size;
1368 ImGuiStyleVar_ItemSpacing,
1369 ImVec2(item_spacing, std::max(item_spacing, style.ItemSpacing.y)));
1375 const int subtype_count = std::min(obj_manager.GetSubtypeCount(obj_id),
1376 kPersistedCustomSubtypeSlots);
1377 for (
int subtype = 0; subtype < subtype_count; ++subtype) {
1378 const std::string subtype_name =
1381 if (custom_col > 0) {
1382 ImGui::SameLine(0.0f, item_spacing);
1384 ImGui::SetCursorPosX(ImGui::GetCursorPosX() +
1385 grid_layout.leading_inset);
1388 ImGui::PushID(obj_id * 1000 + subtype);
1390 const absl::Status asset_status =
1392 const bool asset_ready = asset_status.ok();
1396 ImVec2 button_size(item_size, item_size);
1398 if (ImGui::Selectable(
"", is_selected, 0, button_size)) {
1403 const bool item_visible = ImGui::IsItemVisible();
1404 ImVec2 button_pos = ImGui::GetItemRectMin();
1406 if (asset_ready &&
rooms_ !=
nullptr &&
1411 static_cast<uint8_t
>(subtype)));
1413 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1414 const bool show_id = item_size >= 44.0f;
1415 const float footer_height =
1416 show_id ? std::min(item_size * 0.32f, ImGui::GetFontSize() + 3.0f)
1418 const float card_padding = std::min(3.0f, item_size * 0.08f);
1419 const ImVec2 preview_pos(button_pos.x + card_padding,
1420 button_pos.y + card_padding);
1421 const ImVec2 preview_box(
1422 std::max(1.0f, item_size - card_padding * 2.0f),
1423 std::max(1.0f, item_size - footer_height - card_padding * 2.0f));
1425 bool rendered =
false;
1429 obj_id,
static_cast<uint8_t
>(subtype));
1433 if (item_visible && !rendered) {
1434 std::string sub_text = absl::StrFormat(
"%02X", subtype);
1435 DrawFallbackPreviewTile(
1436 draw_list, preview_pos, preview_box,
1437 asset_ready ? theme.status_success : theme.status_error,
1441 const bool item_hovered = ImGui::IsItemHovered();
1442 if (item_visible && (is_selected || item_hovered)) {
1443 const ImU32 border_color =
1444 ImGui::GetColorU32(is_selected ? theme.dungeon_selection_primary
1445 : theme.panel_border_color);
1448 ImVec2(button_pos.x + item_size, button_pos.y + item_size),
1449 border_color, 2.0f, 0, is_selected ? 2.0f : 1.0f);
1452 if (item_visible && show_id) {
1453 const ImVec2 card_bottom(button_pos.x + item_size,
1454 button_pos.y + item_size);
1455 const float footer_y = card_bottom.y - footer_height;
1456 draw_list->AddRectFilled(
1457 ImVec2(button_pos.x, footer_y), card_bottom,
1458 ImGui::GetColorU32(WithAlpha(theme.panel_bg_darker, 0.88f)),
1460 std::string id_text = absl::StrFormat(
"%02X:%02X", obj_id, subtype);
1461 ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
1462 ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
1463 footer_y + (footer_height - id_size.y) * 0.5f);
1464 draw_list->AddText(id_pos, ImGui::GetColorU32(theme.text_primary),
1470 {{ImGuiCol_PopupBg, theme.panel_bg_color},
1471 {ImGuiCol_Border, theme.panel_border_color}});
1472 if (ImGui::BeginTooltip()) {
1473 const auto binding_or =
1474 obj_manager.ResolveSlotBinding(obj_id, subtype);
1475 const std::string filename =
1476 binding_or.ok() ? binding_or->filename :
"";
1478 ImGui::TextColored(theme.selection_primary,
1479 tr(
"Custom 0x%02X:%02X"), obj_id, subtype);
1480 ImGui::Text(
"%s", subtype_name.c_str());
1482 rendered ? theme.status_success : theme.status_warning,
1484 rendered ?
"rendered custom layout"
1486 :
"thumbnails off"));
1488 ImGui::Text(tr(
"File: %s"),
1489 filename.empty() ?
"(unmapped)" : filename.c_str());
1490 if (binding_or.ok()) {
1491 const ImVec4 mapping_color =
1492 binding_or->origin ==
1494 ? theme.status_active
1495 : (binding_or->origin ==
1496 zelda3::CustomObjectMappingOrigin::
1497 kConfiguredSlotUnmapped
1498 ? theme.status_error
1499 : theme.text_secondary_gray);
1501 mapping_color, tr(
"Mapping: %s"),
1502 tr(CustomObjectMappingOriginLabel(binding_or->origin)));
1505 ImGui::TextColored(theme.status_success,
1506 tr(
"Asset decoded and ready"));
1508 ImGui::TextColored(theme.status_error,
"%s",
1509 asset_status.message().data());
1512 ImGui::EndTooltip();
1517 custom_col = (custom_col + 1) % columns;
1524 const auto selected_binding_or = obj_manager.ResolveSlotBinding(
1526 const std::string selected_filename =
1527 selected_binding_or.ok() ? selected_binding_or->filename :
"";
1530 const auto selected_path_or =
1532 const bool selected_asset_ready = selected_asset_status.ok();
1533 ImGui::TextColored(theme.selection_primary,
"0x%02X:%02X %s",
1535 selected_name.c_str());
1537 ImGui::TextDisabled(
"%s", selected_filename.empty()
1539 : selected_filename.c_str());
1540 if (selected_binding_or.ok()) {
1541 const ImVec4 mapping_color =
1542 selected_binding_or->origin ==
1544 ? theme.status_active
1545 : (selected_binding_or->origin ==
1546 zelda3::CustomObjectMappingOrigin::
1547 kConfiguredSlotUnmapped
1548 ? theme.status_error
1549 : theme.text_secondary_gray);
1551 mapping_color, tr(
"Mapping: %s"),
1552 tr(CustomObjectMappingOriginLabel(selected_binding_or->origin)));
1554 if (selected_asset_ready) {
1555 ImGui::TextColored(theme.status_success,
1557 if (selected_path_or.ok() && ImGui::IsItemHovered()) {
1558 ImGui::SetTooltip(
"%s", selected_path_or->string().c_str());
1562 selected_asset_status.message().data());
1564 ImGui::PushTextWrapPos(0.0f);
1566 ImGui::TextDisabled(
1567 "%s", tr(
"Custom 0x31 wall object; separate from vanilla wall and "
1568 "corner tile tables"));
1571 ImGui::TextDisabled(
"%s", tr(
"Tile layout only; behavior is separate"));
1575 " Tilemap editing is available; external boss pixels "
1576 "are not loaded into the room preview yet.");
1577 if (ImGui::IsItemHovered()) {
1580 tr(
"Oracle forces nonzero body tiles into graphics page 0x300 and "
1581 "DMA-loads separate Kydreeok or Manhandla graphics at runtime. "
1582 "Yaze currently preserves the correct tilemap geometry and raw "
1583 "source words, but the displayed pixels are not visual-parity "
1587 ImGui::PopTextWrapPos();
1593 const bool has_room_context =
1595#if defined(__EMSCRIPTEN__)
1596 constexpr bool kCanPublishCustomAssets =
false;
1598 constexpr bool kCanPublishCustomAssets =
true;
1600 const bool can_edit = selected_asset_ready && has_room_context &&
1602 kCanPublishCustomAssets;
1603 const bool can_use_in_room = selected_asset_ready && has_room_context;
1604 if (ImGui::BeginTable(
1605 "##CustomAssetActions", 2,
1606 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX)) {
1607 ImGui::TableNextColumn();
1609 ImGui::BeginDisabled();
1611 if (ImGui::Button(
ICON_MD_EDIT " Edit Tile Layout", ImVec2(-1.0f, 0.0f))) {
1622 ImGui::EndDisabled();
1624 if (!kCanPublishCustomAssets &&
1625 ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1627 "%s", tr(
"Custom asset publishing is desktop-only until browser "
1628 "persistence can be verified."));
1631 ImGui::TableNextColumn();
1632 if (!can_use_in_room) {
1633 ImGui::BeginDisabled();
1635 if (ImGui::Button(
ICON_MD_ADD " Place in Room", ImVec2(-1.0f, 0.0f))) {
1639 if (!can_use_in_room) {
1640 ImGui::EndDisabled();
1647 const bool can_open_minecart =
1649 if (!can_open_minecart) {
1650 ImGui::BeginDisabled();
1652 if (ImGui::Button(
ICON_MD_TRAIN " Minecart Routes & Collision",
1653 ImVec2(-1.0f, 0.0f))) {
1658 "Minecart Tracks window is unavailable in this session";
1661 if (!can_open_minecart) {
1662 ImGui::EndDisabled();