972 int center_room_id) {
980 ImGui::Text(tr(
"Invalid room ID: %d"), center_room_id);
984 ImGui::Text(tr(
"ROM not loaded"));
988 ImGui::TextDisabled(tr(
"Room data unavailable"));
1004 kConnectedCanvasMaxScale));
1011 const auto& connected_rooms = connected_graph.
room_mask;
1012 const int connected_room_count = connected_graph.
room_count;
1014 const ImVec2 content_size =
1015 GetConnectedCanvasSize(connected_graph.
min_col, connected_graph.
max_col,
1032 const ImVec2 window_pos = ImGui::GetWindowPos();
1033 const ImVec2 window_content_min = ImGui::GetWindowContentRegionMin();
1034 const ImVec2 window_content_max = ImGui::GetWindowContentRegionMax();
1035 const ImVec2 viewport_screen_min(window_pos.x + window_content_min.x,
1036 window_pos.y + window_content_min.y);
1037 const ImVec2 viewport_screen_max(window_pos.x + window_content_max.x,
1038 window_pos.y + window_content_max.y);
1039 const ImVec2 viewport_size(
1040 std::max(1.0f, window_content_max.x - window_content_min.x),
1041 std::max(1.0f, window_content_max.y - window_content_min.y));
1042 const bool reserve_connected_side_panel =
1044 const float desired_side_panel_width =
1045 std::max(kConnectedOverviewMaxWidth + 20.0f,
1046 kConnectedCurrentRoomPreviewWidth + 20.0f);
1047 float connected_side_panel_width = 0.0f;
1048 if (reserve_connected_side_panel) {
1049 const float raw_side_width = std::min(
1050 desired_side_panel_width, std::max(0.0f, viewport_size.x - 320.0f));
1052 ? kConnectedSidePanelApproxMinWidth -
1053 kConnectedSidePanelHideHysteresis
1054 : kConnectedSidePanelApproxMinWidth;
1055 if (raw_side_width >= min_threshold) {
1056 connected_side_panel_width = raw_side_width;
1060 const float reserved_side_width =
1061 connected_side_panel_width > 0.0f
1062 ? connected_side_panel_width + kConnectedSidePanelGap
1064 const ImVec2 map_viewport_size(
1065 std::max(1.0f, viewport_size.x - reserved_side_width), viewport_size.y);
1066 const ImVec2 map_viewport_screen_max(
1067 viewport_screen_min.x + map_viewport_size.x, viewport_screen_max.y);
1068 const ImVec2 side_panel_screen_min(
1069 map_viewport_screen_max.x + kConnectedSidePanelGap,
1070 viewport_screen_min.y);
1071 const ImVec2 side_panel_screen_max(viewport_screen_max.x,
1072 viewport_screen_max.y);
1073 const bool using_connected_side_panel = connected_side_panel_width > 0.0f;
1074 const ImVec2 mouse_pos = ImGui::GetMousePos();
1075 const bool mouse_in_map_viewport = mouse_pos.x >= viewport_screen_min.x &&
1076 mouse_pos.x <= map_viewport_screen_max.x &&
1077 mouse_pos.y >= viewport_screen_min.y &&
1078 mouse_pos.y <= viewport_screen_max.y;
1080 auto center_room_scroll = [&](
float scale_value,
int room_id) {
1081 const auto& placement =
1083 const ImVec2 room_origin = GetConnectedRoomOrigin(
1084 placement.col, placement.row, connected_graph.
min_col,
1086 const float max_scroll_x =
1087 std::max(0.0f, (content_size.x * scale_value) - map_viewport_size.x);
1088 const float max_scroll_y =
1089 std::max(0.0f, (content_size.y * scale_value) - map_viewport_size.y);
1092 ((room_origin.x + (kDungeonRoomPixelSize * 0.5f)) * scale_value) -
1093 (map_viewport_size.x * 0.5f),
1094 0.0f, max_scroll_x),
1096 ((room_origin.y + (kDungeonRoomPixelSize * 0.5f)) * scale_value) -
1097 (map_viewport_size.y * 0.5f),
1098 0.0f, max_scroll_y));
1101 auto center_content_scroll = [&](
float scale_value) {
1104 ((content_size.x * scale_value) - map_viewport_size.x) * 0.5f),
1105 std::max(0.0f, ((content_size.y * scale_value) - map_viewport_size.y) *
1109 auto set_scale_around_view_center = [&](
float new_scale) {
1111 const float clamped_scale = std::clamp(new_scale, kConnectedCanvasMinScale,
1112 kConnectedCanvasMaxScale);
1113 if (std::abs(clamped_scale - old_scale) < 0.0001f) {
1117 const float world_x =
1118 (ImGui::GetScrollX() + (map_viewport_size.x * 0.5f)) / old_scale;
1119 const float world_y =
1120 (ImGui::GetScrollY() + (map_viewport_size.y * 0.5f)) / old_scale;
1122 const float max_scroll_x =
1123 std::max(0.0f, (content_size.x * clamped_scale) - map_viewport_size.x);
1124 const float max_scroll_y =
1125 std::max(0.0f, (content_size.y * clamped_scale) - map_viewport_size.y);
1127 std::clamp((world_x * clamped_scale) - (map_viewport_size.x * 0.5f),
1128 0.0f, max_scroll_x));
1130 std::clamp((world_y * clamped_scale) - (map_viewport_size.y * 0.5f),
1131 0.0f, max_scroll_y));
1132 canvas_rt.scale = clamped_scale;
1135 auto apply_fit = [&]() {
1138 const float fit_scale = std::clamp(fit.scale, kConnectedCanvasMinScale,
1139 kConnectedCanvasMaxScale);
1141 const ImVec2 fit_scroll = center_content_scroll(fit_scale);
1142 ImGui::SetScrollX(fit_scroll.x);
1143 ImGui::SetScrollY(fit_scroll.y);
1144 canvas_rt.scale = fit_scale;
1147 auto apply_reset = [&]() {
1149 const ImVec2 reset_scroll =
1150 center_room_scroll(kConnectedCanvasDefaultScale, center_room_id);
1151 ImGui::SetScrollX(reset_scroll.x);
1152 ImGui::SetScrollY(reset_scroll.y);
1153 canvas_rt.scale = kConnectedCanvasDefaultScale;
1157 const ImGuiIO& io = ImGui::GetIO();
1158 const bool allow_connected_shortcuts = ImGui::IsWindowFocused() &&
1159 !io.WantTextInput && !io.KeyCtrl &&
1160 !io.KeyAlt && !io.KeySuper;
1161 if (allow_connected_shortcuts) {
1162 if (ImGui::IsKeyPressed(ImGuiKey_F,
false)) {
1164 }
else if (ImGui::IsKeyPressed(ImGuiKey_0,
false) ||
1165 ImGui::IsKeyPressed(ImGuiKey_Keypad0,
false)) {
1167 }
else if (ImGui::IsKeyPressed(ImGuiKey_Equal,
false) ||
1168 ImGui::IsKeyPressed(ImGuiKey_KeypadAdd,
false)) {
1170 }
else if (ImGui::IsKeyPressed(ImGuiKey_Minus,
false) ||
1171 ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract,
false)) {
1186 const ImVec2 target_scroll =
1187 center_room_scroll(canvas_rt.scale, center_room_id);
1188 ImGui::SetScrollX(target_scroll.x);
1189 ImGui::SetScrollY(target_scroll.y);
1193 const float scaled_step =
1194 (kDungeonRoomPixelSize + kConnectedRoomGap) * canvas_rt.scale;
1195 const float scroll_x = ImGui::GetScrollX();
1196 const float scroll_y = ImGui::GetScrollY();
1197 const int connected_cols =
1198 std::max(1, (connected_graph.
max_col - connected_graph.
min_col) + 1);
1199 const int connected_rows =
1200 std::max(1, (connected_graph.
max_row - connected_graph.
min_row) + 1);
1201 const int first_col =
1202 std::max(0,
static_cast<int>(std::floor(scroll_x / scaled_step)) - 1);
1203 const int first_row =
1204 std::max(0,
static_cast<int>(std::floor(scroll_y / scaled_step)) - 1);
1205 const int last_col = std::min(
1206 connected_cols - 1,
static_cast<int>(std::ceil(
1207 (scroll_x + map_viewport_size.x) / scaled_step)) +
1209 const int last_row = std::min(
1210 connected_rows - 1,
static_cast<int>(std::ceil(
1211 (scroll_y + map_viewport_size.y) / scaled_step)) +
1214 std::vector<int> visible_rooms;
1215 visible_rooms.reserve(
static_cast<size_t>(connected_room_count));
1217 if (!connected_rooms[
static_cast<size_t>(room_id)]) {
1221 const auto& placement =
1223 if (!placement.placed) {
1227 const int local_col = placement.col - connected_graph.
min_col;
1228 const int local_row = placement.row - connected_graph.
min_row;
1229 if (local_col < first_col || local_col > last_col ||
1230 local_row < first_row || local_row > last_row) {
1234 visible_rooms.push_back(room_id);
1243 const ImU32 outline_color = ImGui::GetColorU32(
1246 const ImU32 active_outline = ImGui::GetColorU32(theme.selection_primary);
1247 const ImU32 hover_outline = ImGui::GetColorU32(theme.status_warning);
1248 const ImU32 label_bg = ImGui::GetColorU32(ImVec4(0.05f, 0.08f, 0.06f, 0.88f));
1249 const ImU32 placeholder_fill = ImGui::GetColorU32(
1250 ImVec4(theme.editor_background.x, theme.editor_background.y,
1251 theme.editor_background.z, 0.95f));
1252 const ImU32 link_shadow = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.32f));
1253 const float link_thickness =
1254 std::clamp(1.1f + (canvas_rt.scale * 3.4f), 1.35f, 3.0f);
1255 const float transport_node_radius =
1256 std::clamp(2.4f + (canvas_rt.scale * 7.0f), 3.0f, 6.0f);
1258 if (using_connected_side_panel) {
1259 canvas_rt.draw_list->AddRectFilled(side_panel_screen_min,
1260 side_panel_screen_max,
1261 IM_COL32(10, 14, 12, 232), 6.0f);
1262 canvas_rt.draw_list->AddLine(
1263 ImVec2(side_panel_screen_min.x - (kConnectedSidePanelGap * 0.5f),
1264 side_panel_screen_min.y),
1265 ImVec2(side_panel_screen_min.x - (kConnectedSidePanelGap * 0.5f),
1266 side_panel_screen_max.y),
1273 for (
const auto& link : connected_graph.
links) {
1274 const auto& from_placement =
1275 connected_graph.
room_positions[
static_cast<size_t>(link.from_room_id)];
1276 const auto& to_placement =
1277 connected_graph.
room_positions[
static_cast<size_t>(link.to_room_id)];
1278 if (!from_placement.placed || !to_placement.placed) {
1281 const auto [canvas_start, canvas_end] = GetConnectedLinkEndpoints(
1282 link, from_placement.col, from_placement.row, to_placement.col,
1283 to_placement.row, connected_graph.
min_col, connected_graph.
min_row);
1284 const ImVec2 screen_start =
1285 ToConnectedCanvasScreenPoint(canvas_rt, canvas_start);
1286 const ImVec2 screen_end =
1287 ToConnectedCanvasScreenPoint(canvas_rt, canvas_end);
1288 const ImU32 link_color = GetConnectedLinkColor(theme, link.type);
1289 canvas_rt.draw_list->AddLine(screen_start, screen_end, link_shadow,
1290 link_thickness + 2.0f);
1291 canvas_rt.draw_list->AddLine(screen_start, screen_end, link_color,
1295 int hovered_room_id = -1;
1296 for (
int room_id : visible_rooms) {
1297 const auto& placement =
1299 const ImVec2 room_origin = GetConnectedRoomOrigin(
1300 placement.col, placement.row, connected_graph.
min_col,
1302 const ImVec2 screen_min(
1303 canvas_rt.canvas_p0.x + room_origin.x * canvas_rt.scale,
1304 canvas_rt.canvas_p0.y + room_origin.y * canvas_rt.scale);
1305 const ImVec2 screen_max(
1306 screen_min.x + (kDungeonRoomPixelSize * canvas_rt.scale),
1307 screen_min.y + (kDungeonRoomPixelSize * canvas_rt.scale));
1310 composite && composite->
texture()) {
1311 canvas_rt.draw_list->AddImage(
1312 (ImTextureID)(intptr_t)composite->texture(), screen_min, screen_max,
1313 ImVec2(0, 0), ImVec2(1, 1),
1314 ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)));
1316 canvas_rt.draw_list->AddRectFilled(screen_min, screen_max,
1317 placeholder_fill, 6.0f);
1320 if (!placement.connected_to_start) {
1321 canvas_rt.draw_list->AddRectFilled(
1322 screen_min, screen_max,
1323 ImGui::GetColorU32(WithAlpha(theme.status_warning, 0.12f)), 6.0f);
1326 const std::string& floor_label =
1328 if (!floor_label.empty()) {
1329 const float strip_width = std::clamp(6.0f * canvas_rt.scale, 3.0f, 8.0f);
1330 canvas_rt.draw_list->AddRectFilled(
1331 screen_min, ImVec2(screen_min.x + strip_width, screen_max.y),
1332 GetConnectedFloorColor(theme, connected_graph, room_id, 0.82f), 6.0f,
1333 ImDrawFlags_RoundCornersLeft);
1336 const bool hovered =
1337 mouse_in_map_viewport &&
1338 ImGui::IsMouseHoveringRect(screen_min, screen_max,
false) &&
1339 ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
1341 hovered_room_id = room_id;
1344 const ImU32 border_color = room_id == center_room_id ? active_outline
1345 : hovered ? hover_outline
1347 const float border_thickness = room_id == center_room_id
1348 ? kConnectedRoomHighlightThickness
1349 : kConnectedRoomOutlineThickness;
1350 canvas_rt.draw_list->AddRect(screen_min, screen_max, border_color, 6.0f, 0,
1353 char room_label[192];
1354 if (floor_label.empty()) {
1356 room_label,
sizeof(room_label),
"[%03X] %s", room_id,
1360 room_label,
sizeof(room_label),
"[%03X] %s - %s", room_id,
1361 floor_label.c_str(),
1364 const ImVec2 label_size = ImGui::CalcTextSize(room_label);
1365 const ImVec2 label_min(screen_min.x + kConnectedRoomLabelPadding,
1366 screen_min.y + kConnectedRoomLabelPadding);
1367 const ImVec2 label_max(label_min.x + label_size.x + 12.0f,
1368 label_min.y + kConnectedRoomLabelHeight);
1369 canvas_rt.draw_list->AddRectFilled(label_min, label_max, label_bg, 5.0f);
1370 canvas_rt.draw_list->AddText(ImVec2(label_min.x + 6.0f, label_min.y + 6.0f),
1371 ImGui::GetColorU32(theme.text_primary),
1375 for (
const auto& link : connected_graph.
links) {
1380 const auto& from_placement =
1381 connected_graph.
room_positions[
static_cast<size_t>(link.from_room_id)];
1382 const auto& to_placement =
1383 connected_graph.
room_positions[
static_cast<size_t>(link.to_room_id)];
1384 if (!from_placement.placed || !to_placement.placed) {
1388 const auto [canvas_start, canvas_end] = GetConnectedLinkEndpoints(
1389 link, from_placement.col, from_placement.row, to_placement.col,
1390 to_placement.row, connected_graph.
min_col, connected_graph.
min_row);
1391 const ImVec2 screen_start =
1392 ToConnectedCanvasScreenPoint(canvas_rt, canvas_start);
1393 const ImVec2 screen_end =
1394 ToConnectedCanvasScreenPoint(canvas_rt, canvas_end);
1395 const ImU32 link_color = GetConnectedLinkColor(theme, link.type);
1396 canvas_rt.draw_list->AddCircleFilled(screen_start, transport_node_radius,
1398 canvas_rt.draw_list->AddCircleFilled(screen_end, transport_node_radius,
1400 canvas_rt.draw_list->AddCircle(screen_start, transport_node_radius + 1.5f,
1401 link_shadow, 12, 1.25f);
1402 canvas_rt.draw_list->AddCircle(screen_end, transport_node_radius + 1.5f,
1403 link_shadow, 12, 1.25f);
1406 ConnectedCanvasControlActions control_actions;
1408 const ImVec2 connected_controls_pos =
1409 using_connected_side_panel ? side_panel_screen_min
1412 : ImVec2(viewport_screen_max.x - 12.0f,
1413 viewport_screen_min.y + 12.0f);
1414 const ImVec2 connected_controls_pivot =
1416 ? ImVec2(0.0f, 0.0f)
1417 : ImVec2(1.0f, 0.0f);
1418 control_actions = DrawConnectedCanvasControls(
1419 connected_controls_pos, connected_controls_pivot,
1422 if (!using_connected_side_panel &&
1423 control_actions.moved_window_pos.has_value()) {
1426 *control_actions.moved_window_pos;
1428 if (control_actions.reset_position) {
1434 if (control_actions.fit) {
1437 if (control_actions.reset) {
1442 ConnectedCanvasOverviewActions overview_actions;
1444 overview_actions = DrawConnectedCanvasOverview(
1445 connected_graph, content_size, map_viewport_size,
1447 ImGui::GetScrollY(), center_room_id, hovered_room_id,
1448 using_connected_side_panel
1449 ? ImVec2(side_panel_screen_min.x,
1450 side_panel_screen_min.y + kConnectedControlsApproxHeight +
1453 viewport_screen_max.x - kConnectedOverviewMaxWidth - 12.0f,
1454 viewport_screen_max.y - kConnectedOverviewMaxHeight - 12.0f));
1456 if (overview_actions.target_scroll.has_value()) {
1457 ImGui::SetScrollX(overview_actions.target_scroll->x);
1458 ImGui::SetScrollY(overview_actions.target_scroll->y);
1460 if (overview_actions.activated_room_id.has_value()) {
1461 if (*overview_actions.activated_room_id != center_room_id) {
1465 return *overview_actions.activated_room_id;
1468 if (!control_actions.hovered && !overview_actions.hovered &&
1469 hovered_room_id >= 0) {
1471 std::string tooltip = absl::StrFormat(
1472 "[%03X] %s", hovered_room_id,
1475 const auto& hovered_placement =
1476 connected_graph.
room_positions[
static_cast<size_t>(hovered_room_id)];
1477 const std::string& floor_label =
1480 if (!floor_label.empty()) {
1481 absl::StrAppend(&tooltip,
"\nFloor: ", floor_label);
1483 if (!hovered_placement.connected_to_start) {
1486 "\nUnlinked: no resolved ROM path from the current room.");
1491 const auto& diagnostics = it->second;
1492 if (!diagnostics.links.empty()) {
1493 absl::StrAppend(&tooltip,
"\nConnections:");
1494 for (
const auto& link : diagnostics.links) {
1495 absl::StrAppend(&tooltip,
"\n ",
1499 if (!diagnostics.staircase_issues.empty()) {
1500 absl::StrAppend(&tooltip,
"\nStaircase issues:");
1501 for (
const auto& issue : diagnostics.staircase_issues) {
1502 absl::StrAppend(&tooltip,
"\n ",
1507 absl::StrAppend(&tooltip,
"\nClick to open room view");
1508 ImGui::SetTooltip(
"%s", tooltip.c_str());
1510 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
1511 if (hovered_room_id != center_room_id) {
1515 return hovered_room_id;
1522 current_room && current_room->
texture()) {
1523 const float preview_scale =
1524 kConnectedCurrentRoomPreviewWidth /
1525 std::max(1.0f,
static_cast<float>(current_room->width()));
1526 const ImVec2 preview_size(current_room->width() * preview_scale,
1527 current_room->height() * preview_scale);
1528 const ImVec2 preview_pos(
1529 using_connected_side_panel ? side_panel_screen_min.x
1530 : viewport_screen_max.x - preview_size.x -
1531 kConnectedCurrentRoomPreviewPadding,
1532 using_connected_side_panel
1533 ? side_panel_screen_max.y - kConnectedPreviewApproxHeight
1534 : viewport_screen_min.y + kConnectedCurrentRoomPreviewPadding);
1536 kConnectedCurrentRoomHudId, preview_pos, ImVec2(0, 0), [&]() {
1537 ImGui::Text(tr(
"Current Room"));
1538 ImGui::TextDisabled(
1539 "[%03X] %s", center_room_id,
1542 ImGui::Image((ImTextureID)(intptr_t)current_room->texture(),
1548 const bool side_panel_hovered = using_connected_side_panel &&
1549 mouse_pos.x >= side_panel_screen_min.x &&
1550 mouse_pos.x <= side_panel_screen_max.x &&
1551 mouse_pos.y >= side_panel_screen_min.y &&
1552 mouse_pos.y <= side_panel_screen_max.y;
1553 HandleConnectedCanvasPan((control_actions.hovered ||
1554 overview_actions.hovered || side_panel_hovered)
1559 return std::nullopt;