845 int center_room_id) {
848 ImGui::Text(tr(
"Invalid room ID: %d"), center_room_id);
852 ImGui::Text(tr(
"ROM not loaded"));
856 ImGui::TextDisabled(tr(
"Room data unavailable"));
872 kConnectedCanvasMaxScale));
879 const auto& connected_rooms = connected_graph.
room_mask;
880 const int connected_room_count = connected_graph.
room_count;
882 const ImVec2 content_size =
883 GetConnectedCanvasSize(connected_graph.
min_col, connected_graph.
max_col,
900 const ImVec2 window_pos = ImGui::GetWindowPos();
901 const ImVec2 window_content_min = ImGui::GetWindowContentRegionMin();
902 const ImVec2 window_content_max = ImGui::GetWindowContentRegionMax();
903 const ImVec2 viewport_screen_min(window_pos.x + window_content_min.x,
904 window_pos.y + window_content_min.y);
905 const ImVec2 viewport_screen_max(window_pos.x + window_content_max.x,
906 window_pos.y + window_content_max.y);
907 const ImVec2 viewport_size(
908 std::max(1.0f, window_content_max.x - window_content_min.x),
909 std::max(1.0f, window_content_max.y - window_content_min.y));
910 const bool reserve_connected_side_panel =
912 const float desired_side_panel_width =
913 std::max(kConnectedOverviewMaxWidth + 20.0f,
914 kConnectedCurrentRoomPreviewWidth + 20.0f);
915 float connected_side_panel_width = 0.0f;
916 if (reserve_connected_side_panel) {
917 const float raw_side_width = std::min(
918 desired_side_panel_width, std::max(0.0f, viewport_size.x - 320.0f));
920 ? kConnectedSidePanelApproxMinWidth -
921 kConnectedSidePanelHideHysteresis
922 : kConnectedSidePanelApproxMinWidth;
923 if (raw_side_width >= min_threshold) {
924 connected_side_panel_width = raw_side_width;
928 const float reserved_side_width =
929 connected_side_panel_width > 0.0f
930 ? connected_side_panel_width + kConnectedSidePanelGap
932 const ImVec2 map_viewport_size(
933 std::max(1.0f, viewport_size.x - reserved_side_width), viewport_size.y);
934 const ImVec2 map_viewport_screen_max(
935 viewport_screen_min.x + map_viewport_size.x, viewport_screen_max.y);
936 const ImVec2 side_panel_screen_min(
937 map_viewport_screen_max.x + kConnectedSidePanelGap,
938 viewport_screen_min.y);
939 const ImVec2 side_panel_screen_max(viewport_screen_max.x,
940 viewport_screen_max.y);
941 const bool using_connected_side_panel = connected_side_panel_width > 0.0f;
942 const ImVec2 mouse_pos = ImGui::GetMousePos();
943 const bool mouse_in_map_viewport = mouse_pos.x >= viewport_screen_min.x &&
944 mouse_pos.x <= map_viewport_screen_max.x &&
945 mouse_pos.y >= viewport_screen_min.y &&
946 mouse_pos.y <= viewport_screen_max.y;
948 auto center_room_scroll = [&](
float scale_value,
int room_id) {
949 const auto& placement =
951 const ImVec2 room_origin = GetConnectedRoomOrigin(
952 placement.col, placement.row, connected_graph.
min_col,
954 const float max_scroll_x =
955 std::max(0.0f, (content_size.x * scale_value) - map_viewport_size.x);
956 const float max_scroll_y =
957 std::max(0.0f, (content_size.y * scale_value) - map_viewport_size.y);
960 ((room_origin.x + (kDungeonRoomPixelSize * 0.5f)) * scale_value) -
961 (map_viewport_size.x * 0.5f),
964 ((room_origin.y + (kDungeonRoomPixelSize * 0.5f)) * scale_value) -
965 (map_viewport_size.y * 0.5f),
966 0.0f, max_scroll_y));
969 auto center_content_scroll = [&](
float scale_value) {
972 ((content_size.x * scale_value) - map_viewport_size.x) * 0.5f),
973 std::max(0.0f, ((content_size.y * scale_value) - map_viewport_size.y) *
977 auto set_scale_around_view_center = [&](
float new_scale) {
979 const float clamped_scale = std::clamp(new_scale, kConnectedCanvasMinScale,
980 kConnectedCanvasMaxScale);
981 if (std::abs(clamped_scale - old_scale) < 0.0001f) {
985 const float world_x =
986 (ImGui::GetScrollX() + (map_viewport_size.x * 0.5f)) / old_scale;
987 const float world_y =
988 (ImGui::GetScrollY() + (map_viewport_size.y * 0.5f)) / old_scale;
990 const float max_scroll_x =
991 std::max(0.0f, (content_size.x * clamped_scale) - map_viewport_size.x);
992 const float max_scroll_y =
993 std::max(0.0f, (content_size.y * clamped_scale) - map_viewport_size.y);
995 std::clamp((world_x * clamped_scale) - (map_viewport_size.x * 0.5f),
996 0.0f, max_scroll_x));
998 std::clamp((world_y * clamped_scale) - (map_viewport_size.y * 0.5f),
999 0.0f, max_scroll_y));
1000 canvas_rt.scale = clamped_scale;
1003 auto apply_fit = [&]() {
1006 const float fit_scale = std::clamp(fit.scale, kConnectedCanvasMinScale,
1007 kConnectedCanvasMaxScale);
1009 const ImVec2 fit_scroll = center_content_scroll(fit_scale);
1010 ImGui::SetScrollX(fit_scroll.x);
1011 ImGui::SetScrollY(fit_scroll.y);
1012 canvas_rt.scale = fit_scale;
1015 auto apply_reset = [&]() {
1017 const ImVec2 reset_scroll =
1018 center_room_scroll(kConnectedCanvasDefaultScale, center_room_id);
1019 ImGui::SetScrollX(reset_scroll.x);
1020 ImGui::SetScrollY(reset_scroll.y);
1021 canvas_rt.scale = kConnectedCanvasDefaultScale;
1025 const ImGuiIO& io = ImGui::GetIO();
1026 const bool allow_connected_shortcuts = ImGui::IsWindowFocused() &&
1027 !io.WantTextInput && !io.KeyCtrl &&
1028 !io.KeyAlt && !io.KeySuper;
1029 if (allow_connected_shortcuts) {
1030 if (ImGui::IsKeyPressed(ImGuiKey_F,
false)) {
1032 }
else if (ImGui::IsKeyPressed(ImGuiKey_0,
false) ||
1033 ImGui::IsKeyPressed(ImGuiKey_Keypad0,
false)) {
1035 }
else if (ImGui::IsKeyPressed(ImGuiKey_Equal,
false) ||
1036 ImGui::IsKeyPressed(ImGuiKey_KeypadAdd,
false)) {
1038 }
else if (ImGui::IsKeyPressed(ImGuiKey_Minus,
false) ||
1039 ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract,
false)) {
1054 const ImVec2 target_scroll =
1055 center_room_scroll(canvas_rt.scale, center_room_id);
1056 ImGui::SetScrollX(target_scroll.x);
1057 ImGui::SetScrollY(target_scroll.y);
1061 const float scaled_step =
1062 (kDungeonRoomPixelSize + kConnectedRoomGap) * canvas_rt.scale;
1063 const float scroll_x = ImGui::GetScrollX();
1064 const float scroll_y = ImGui::GetScrollY();
1065 const int connected_cols =
1066 std::max(1, (connected_graph.
max_col - connected_graph.
min_col) + 1);
1067 const int connected_rows =
1068 std::max(1, (connected_graph.
max_row - connected_graph.
min_row) + 1);
1069 const int first_col =
1070 std::max(0,
static_cast<int>(std::floor(scroll_x / scaled_step)) - 1);
1071 const int first_row =
1072 std::max(0,
static_cast<int>(std::floor(scroll_y / scaled_step)) - 1);
1073 const int last_col = std::min(
1074 connected_cols - 1,
static_cast<int>(std::ceil(
1075 (scroll_x + map_viewport_size.x) / scaled_step)) +
1077 const int last_row = std::min(
1078 connected_rows - 1,
static_cast<int>(std::ceil(
1079 (scroll_y + map_viewport_size.y) / scaled_step)) +
1082 std::vector<int> visible_rooms;
1083 visible_rooms.reserve(
static_cast<size_t>(connected_room_count));
1085 if (!connected_rooms[
static_cast<size_t>(room_id)]) {
1089 const auto& placement =
1091 if (!placement.placed) {
1095 const int local_col = placement.col - connected_graph.
min_col;
1096 const int local_row = placement.row - connected_graph.
min_row;
1097 if (local_col < first_col || local_col > last_col ||
1098 local_row < first_row || local_row > last_row) {
1102 visible_rooms.push_back(room_id);
1111 const ImU32 outline_color = ImGui::GetColorU32(
1114 const ImU32 active_outline = ImGui::GetColorU32(theme.selection_primary);
1115 const ImU32 hover_outline = ImGui::GetColorU32(theme.status_warning);
1116 const ImU32 label_bg = ImGui::GetColorU32(ImVec4(0.05f, 0.08f, 0.06f, 0.88f));
1117 const ImU32 placeholder_fill = ImGui::GetColorU32(
1118 ImVec4(theme.editor_background.x, theme.editor_background.y,
1119 theme.editor_background.z, 0.95f));
1120 const ImU32 link_shadow = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.32f));
1121 const float link_thickness =
1122 std::clamp(1.1f + (canvas_rt.scale * 3.4f), 1.35f, 3.0f);
1123 const float transport_node_radius =
1124 std::clamp(2.4f + (canvas_rt.scale * 7.0f), 3.0f, 6.0f);
1126 if (using_connected_side_panel) {
1127 canvas_rt.draw_list->AddRectFilled(side_panel_screen_min,
1128 side_panel_screen_max,
1129 IM_COL32(10, 14, 12, 232), 6.0f);
1130 canvas_rt.draw_list->AddLine(
1131 ImVec2(side_panel_screen_min.x - (kConnectedSidePanelGap * 0.5f),
1132 side_panel_screen_min.y),
1133 ImVec2(side_panel_screen_min.x - (kConnectedSidePanelGap * 0.5f),
1134 side_panel_screen_max.y),
1141 for (
const auto& link : connected_graph.
links) {
1142 const auto& from_placement =
1143 connected_graph.
room_positions[
static_cast<size_t>(link.from_room_id)];
1144 const auto& to_placement =
1145 connected_graph.
room_positions[
static_cast<size_t>(link.to_room_id)];
1146 if (!from_placement.placed || !to_placement.placed) {
1149 const auto [canvas_start, canvas_end] = GetConnectedLinkEndpoints(
1150 link, from_placement.col, from_placement.row, to_placement.col,
1151 to_placement.row, connected_graph.
min_col, connected_graph.
min_row);
1152 const ImVec2 screen_start =
1153 ToConnectedCanvasScreenPoint(canvas_rt, canvas_start);
1154 const ImVec2 screen_end =
1155 ToConnectedCanvasScreenPoint(canvas_rt, canvas_end);
1156 const ImU32 link_color = GetConnectedLinkColor(theme, link.type);
1157 canvas_rt.draw_list->AddLine(screen_start, screen_end, link_shadow,
1158 link_thickness + 2.0f);
1159 canvas_rt.draw_list->AddLine(screen_start, screen_end, link_color,
1163 int hovered_room_id = -1;
1164 for (
int room_id : visible_rooms) {
1165 const auto& placement =
1167 const ImVec2 room_origin = GetConnectedRoomOrigin(
1168 placement.col, placement.row, connected_graph.
min_col,
1170 const ImVec2 screen_min(
1171 canvas_rt.canvas_p0.x + room_origin.x * canvas_rt.scale,
1172 canvas_rt.canvas_p0.y + room_origin.y * canvas_rt.scale);
1173 const ImVec2 screen_max(
1174 screen_min.x + (kDungeonRoomPixelSize * canvas_rt.scale),
1175 screen_min.y + (kDungeonRoomPixelSize * canvas_rt.scale));
1178 composite && composite->
texture()) {
1179 canvas_rt.draw_list->AddImage((ImTextureID)(intptr_t)composite->texture(),
1180 screen_min, screen_max, ImVec2(0, 0),
1181 ImVec2(1, 1), IM_COL32(255, 255, 255, 255));
1183 canvas_rt.draw_list->AddRectFilled(screen_min, screen_max,
1184 placeholder_fill, 6.0f);
1187 if (!placement.connected_to_start) {
1188 canvas_rt.draw_list->AddRectFilled(
1189 screen_min, screen_max,
1190 ImGui::GetColorU32(WithAlpha(theme.status_warning, 0.12f)), 6.0f);
1193 const std::string& floor_label =
1195 if (!floor_label.empty()) {
1196 const float strip_width = std::clamp(6.0f * canvas_rt.scale, 3.0f, 8.0f);
1197 canvas_rt.draw_list->AddRectFilled(
1198 screen_min, ImVec2(screen_min.x + strip_width, screen_max.y),
1199 GetConnectedFloorColor(theme, connected_graph, room_id, 0.82f), 6.0f,
1200 ImDrawFlags_RoundCornersLeft);
1203 const bool hovered =
1204 mouse_in_map_viewport &&
1205 ImGui::IsMouseHoveringRect(screen_min, screen_max,
false) &&
1206 ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
1208 hovered_room_id = room_id;
1211 const ImU32 border_color = room_id == center_room_id ? active_outline
1212 : hovered ? hover_outline
1214 const float border_thickness = room_id == center_room_id
1215 ? kConnectedRoomHighlightThickness
1216 : kConnectedRoomOutlineThickness;
1217 canvas_rt.draw_list->AddRect(screen_min, screen_max, border_color, 6.0f, 0,
1220 char room_label[192];
1221 if (floor_label.empty()) {
1223 room_label,
sizeof(room_label),
"[%03X] %s", room_id,
1227 room_label,
sizeof(room_label),
"[%03X] %s - %s", room_id,
1228 floor_label.c_str(),
1231 const ImVec2 label_size = ImGui::CalcTextSize(room_label);
1232 const ImVec2 label_min(screen_min.x + kConnectedRoomLabelPadding,
1233 screen_min.y + kConnectedRoomLabelPadding);
1234 const ImVec2 label_max(label_min.x + label_size.x + 12.0f,
1235 label_min.y + kConnectedRoomLabelHeight);
1236 canvas_rt.draw_list->AddRectFilled(label_min, label_max, label_bg, 5.0f);
1237 canvas_rt.draw_list->AddText(ImVec2(label_min.x + 6.0f, label_min.y + 6.0f),
1238 ImGui::GetColorU32(theme.text_primary),
1242 for (
const auto& link : connected_graph.
links) {
1247 const auto& from_placement =
1248 connected_graph.
room_positions[
static_cast<size_t>(link.from_room_id)];
1249 const auto& to_placement =
1250 connected_graph.
room_positions[
static_cast<size_t>(link.to_room_id)];
1251 if (!from_placement.placed || !to_placement.placed) {
1255 const auto [canvas_start, canvas_end] = GetConnectedLinkEndpoints(
1256 link, from_placement.col, from_placement.row, to_placement.col,
1257 to_placement.row, connected_graph.
min_col, connected_graph.
min_row);
1258 const ImVec2 screen_start =
1259 ToConnectedCanvasScreenPoint(canvas_rt, canvas_start);
1260 const ImVec2 screen_end =
1261 ToConnectedCanvasScreenPoint(canvas_rt, canvas_end);
1262 const ImU32 link_color = GetConnectedLinkColor(theme, link.type);
1263 canvas_rt.draw_list->AddCircleFilled(screen_start, transport_node_radius,
1265 canvas_rt.draw_list->AddCircleFilled(screen_end, transport_node_radius,
1267 canvas_rt.draw_list->AddCircle(screen_start, transport_node_radius + 1.5f,
1268 link_shadow, 12, 1.25f);
1269 canvas_rt.draw_list->AddCircle(screen_end, transport_node_radius + 1.5f,
1270 link_shadow, 12, 1.25f);
1273 ConnectedCanvasControlActions control_actions;
1275 const ImVec2 connected_controls_pos =
1276 using_connected_side_panel ? side_panel_screen_min
1279 : ImVec2(viewport_screen_max.x - 12.0f,
1280 viewport_screen_min.y + 12.0f);
1281 const ImVec2 connected_controls_pivot =
1283 ? ImVec2(0.0f, 0.0f)
1284 : ImVec2(1.0f, 0.0f);
1285 control_actions = DrawConnectedCanvasControls(
1286 connected_controls_pos, connected_controls_pivot,
1289 if (!using_connected_side_panel &&
1290 control_actions.moved_window_pos.has_value()) {
1293 *control_actions.moved_window_pos;
1295 if (control_actions.reset_position) {
1301 if (control_actions.fit) {
1304 if (control_actions.reset) {
1309 ConnectedCanvasOverviewActions overview_actions;
1311 overview_actions = DrawConnectedCanvasOverview(
1312 connected_graph, content_size, map_viewport_size,
1314 ImGui::GetScrollY(), center_room_id, hovered_room_id,
1315 using_connected_side_panel
1316 ? ImVec2(side_panel_screen_min.x,
1317 side_panel_screen_min.y + kConnectedControlsApproxHeight +
1320 viewport_screen_max.x - kConnectedOverviewMaxWidth - 12.0f,
1321 viewport_screen_max.y - kConnectedOverviewMaxHeight - 12.0f));
1323 if (overview_actions.target_scroll.has_value()) {
1324 ImGui::SetScrollX(overview_actions.target_scroll->x);
1325 ImGui::SetScrollY(overview_actions.target_scroll->y);
1327 if (overview_actions.activated_room_id.has_value()) {
1328 if (*overview_actions.activated_room_id != center_room_id) {
1332 return *overview_actions.activated_room_id;
1335 if (!control_actions.hovered && !overview_actions.hovered &&
1336 hovered_room_id >= 0) {
1338 std::string tooltip = absl::StrFormat(
1339 "[%03X] %s", hovered_room_id,
1342 const auto& hovered_placement =
1343 connected_graph.
room_positions[
static_cast<size_t>(hovered_room_id)];
1344 const std::string& floor_label =
1347 if (!floor_label.empty()) {
1348 absl::StrAppend(&tooltip,
"\nFloor: ", floor_label);
1350 if (!hovered_placement.connected_to_start) {
1353 "\nUnlinked: no resolved ROM path from the current room.");
1358 hovered_room_id, *hovered_room,
1362 if (!diagnostics.links.empty()) {
1363 absl::StrAppend(&tooltip,
"\nConnections:");
1364 for (
const auto& link : diagnostics.links) {
1365 absl::StrAppend(&tooltip,
"\n ",
1369 if (!diagnostics.staircase_issues.empty()) {
1370 absl::StrAppend(&tooltip,
"\nStaircase issues:");
1371 for (
const auto& issue : diagnostics.staircase_issues) {
1372 absl::StrAppend(&tooltip,
"\n ",
1377 absl::StrAppend(&tooltip,
"\nClick to open room view");
1378 ImGui::SetTooltip(
"%s", tooltip.c_str());
1380 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
1381 if (hovered_room_id != center_room_id) {
1385 return hovered_room_id;
1392 current_room && current_room->
texture()) {
1393 const float preview_scale =
1394 kConnectedCurrentRoomPreviewWidth /
1395 std::max(1.0f,
static_cast<float>(current_room->width()));
1396 const ImVec2 preview_size(current_room->width() * preview_scale,
1397 current_room->height() * preview_scale);
1398 const ImVec2 preview_pos(
1399 using_connected_side_panel ? side_panel_screen_min.x
1400 : viewport_screen_max.x - preview_size.x -
1401 kConnectedCurrentRoomPreviewPadding,
1402 using_connected_side_panel
1403 ? side_panel_screen_max.y - kConnectedPreviewApproxHeight
1404 : viewport_screen_min.y + kConnectedCurrentRoomPreviewPadding);
1406 kConnectedCurrentRoomHudId, preview_pos, ImVec2(0, 0), [&]() {
1407 ImGui::Text(tr(
"Current Room"));
1408 ImGui::TextDisabled(
1409 "[%03X] %s", center_room_id,
1412 ImGui::Image((ImTextureID)(intptr_t)current_room->texture(),
1418 const bool side_panel_hovered = using_connected_side_panel &&
1419 mouse_pos.x >= side_panel_screen_min.x &&
1420 mouse_pos.x <= side_panel_screen_max.x &&
1421 mouse_pos.y >= side_panel_screen_min.y &&
1422 mouse_pos.y <= side_panel_screen_max.y;
1423 HandleConnectedCanvasPan((control_actions.hovered ||
1424 overview_actions.hovered || side_panel_hovered)
1429 return std::nullopt;