119 void Draw(
bool* p_open)
override {
124 ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows);
129 constexpr int kRoomsPerRow = 16;
130 constexpr int kRoomsPerCol = 19;
131 constexpr int kTotalRooms = 0x128;
132 constexpr float kCellSpacing = 1.0f;
139 const ImVec2 avail = ImGui::GetContentRegionAvail();
140 const float panel_width = std::max(1.0f, avail.x);
141 const float panel_height =
142 std::max(1.0f, avail.y - ImGui::GetStyle().ItemSpacing.y);
143 const float cell_size_by_width =
144 (panel_width - kCellSpacing * (kRoomsPerRow - 1)) / kRoomsPerRow;
145 const float cell_size_by_height =
146 (panel_height - kCellSpacing * (kRoomsPerCol - 1)) / kRoomsPerCol;
147 const float cell_size = std::max(
148 1.0f, std::floor(std::min({cell_size_by_width, cell_size_by_height,
149 24.0f * ImGui::GetFontSize() / 13.0f})));
151 const float grid_width =
152 kRoomsPerRow * cell_size + kCellSpacing * (kRoomsPerRow - 1);
153 const float grid_height =
154 kRoomsPerCol * cell_size + kCellSpacing * (kRoomsPerCol - 1);
155 const float x_offset = std::max(0.0f, (panel_width - grid_width) * 0.5f);
156 const float y_offset = std::max(0.0f, (panel_height - grid_height) * 0.5f);
158 ImDrawList* draw_list = ImGui::GetWindowDrawList();
159 const ImVec2 grid_origin = ImGui::GetCursorScreenPos();
160 ImVec2 canvas_pos = grid_origin;
161 canvas_pos.x += x_offset;
162 canvas_pos.y += y_offset;
165 for (
int row = 0; row < kRoomsPerCol; row++) {
166 for (
int col = 0; col < kRoomsPerRow; col++) {
167 int room_id = room_index;
168 bool is_valid_room = (room_id < kTotalRooms);
169 const bool matches_filter =
174 ImVec2(canvas_pos.x + col * (cell_size + kCellSpacing),
175 canvas_pos.y + row * (cell_size + kCellSpacing));
177 ImVec2(cell_min.x + cell_size, cell_min.y + cell_size);
183 if (!matches_filter) {
185 bg_color, ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker),
198 if ((is_open || is_current) && matches_filter) {
199 ImVec4 color = ImGui::ColorConvertU32ToFloat4(bg_color);
201 ImGui::ColorConvertRGBtoHSV(color.x, color.y, color.z, h, s, v);
202 ImGui::ColorConvertHSVtoRGB(h, s, v + (1.0f - v) * 0.22f, color.x,
204 bg_color = ImGui::ColorConvertFloat4ToU32(color);
206 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
208 if (is_current || is_open) {
209 const ImU32 outline =
210 is_current ? ImGui::ColorConvertFloat4ToU32(
211 theme.dungeon_selection_primary)
212 : IM_COL32(255, 255, 255, matches_filter ? 130 : 45);
213 draw_list->AddRect(ImVec2(cell_min.x + 1, cell_min.y + 1),
214 ImVec2(cell_max.x - 1, cell_max.y - 1), outline,
215 0, 0, is_current ? 2.0f : 1.0f);
220 matches_filter, theme);
225 if (cell_size >= 14.0f) {
226 const std::string label = absl::StrFormat(
"%03X", room_id);
227 const ImVec2 normal_size = ImGui::CalcTextSize(label.c_str());
229 std::min(1.0f, (cell_size - 2.0f) / normal_size.x);
230 const ImVec2 text_pos(
231 cell_min.x + (cell_size - normal_size.x * scale) * 0.5f,
232 cell_min.y + (cell_size - normal_size.y * scale) * 0.5f);
233 const ImVec4 color = ImGui::ColorConvertU32ToFloat4(bg_color);
234 const float luminance =
235 color.x * 0.2126f + color.y * 0.7152f + color.z * 0.0722f;
236 const ImU32 ink = luminance > 0.55f ? IM_COL32(15, 15, 15, 255)
237 : IM_COL32(245, 245, 245, 255);
238 draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize() * scale,
239 text_pos, ink, label.c_str());
243 ImGui::SetCursorScreenPos(cell_min);
245 snprintf(btn_id,
sizeof(btn_id),
"##room%d", room_id);
246 ImGui::InvisibleButton(btn_id, ImVec2(cell_size, cell_size));
248 absl::StrFormat(
"Room 0x%03X", room_id));
250 if (ImGui::IsItemClicked()) {
251 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
264 const bool can_swap =
268 std::vector<gui::MenuItemSpec> items;
269 items.emplace_back(is_open ?
"Focus Room" :
"Open in Workbench",
288 items.back().separator_after =
true;
291 tr(
"Copy Room ID"), absl::StrFormat(
"0x%03X", room_id)));
294 items.back().separator_after =
true;
297 tr(
"Swap With Current Room"),
301 [can_swap]() {
return can_swap; }));
307 if (ImGui::IsItemHovered()) {
308 ImGui::BeginTooltip();
311 ImGui::TextDisabled(tr(
"Room 0x%02X"), room_id);
319 ImGui::TextColored(theme.dungeon_selection_primary,
321 }
else if (is_open) {
322 ImGui::TextColored(theme.status_success,
328 if (loaded_room !=
nullptr) {
330 ImGui::TextDisabled(tr(
"Palette: %d | Blockset: %d"),
331 loaded_room->palette(),
332 loaded_room->blockset());
341 if (preview_bitmap.is_active() &&
342 preview_bitmap.texture() != 0) {
345 constexpr float kThumbnailSize = 80.0f;
346 ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(),
347 ImVec2(kThumbnailSize, kThumbnailSize));
353 ImGui::TextDisabled(tr(
"Click to %s"),
354 (is_open || is_current) ?
"focus" :
"open");
355 ImGui::TextDisabled(tr(
"Double-click to open as panel"));
356 ImGui::TextDisabled(tr(
"Right-click for actions"));
361 draw_list->AddRectFilled(
363 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker));
372 ImGui::SetCursorScreenPos(grid_origin);
373 ImGui::Dummy(ImVec2(panel_width, grid_height + y_offset));
387 const float button_width = ImGui::GetFrameHeight();
388 const float spacing = ImGui::GetStyle().ItemSpacing.x;
390 ImGui::SetNextItemWidth(
391 std::max(1.0f, ImGui::GetContentRegionAvail().x -
392 (button_width + spacing) * (has_filter ? 2 : 1)));
393 ImGui::InputTextWithHint(
"##RoomSearch",
"Room ID or name...",
397 if (ImGui::Button(
ICON_MD_CLOSE "##ClearFilter", ImVec2(button_width, 0)))
399 if (ImGui::IsItemHovered())
400 ImGui::SetTooltip(tr(
"Clear room filter"));
404 ImVec2(button_width, 0)))
405 ImGui::OpenPopup(
"##MatrixOptionsPopup");
407 if (ImGui::IsItemHovered())
408 ImGui::SetTooltip(tr(
"Room colors and census"));
409 if (ImGui::BeginPopup(
"##MatrixOptionsPopup")) {
410 ImGui::TextDisabled(
"Room palette colors (same swatch as ZScream)");
411 ImGui::TextUnformatted(
"Brighter + thin outline: open room");
412 ImGui::TextUnformatted(
"Accent outline: current room");
417 const std::string caption = absl::StrFormat(
420 const ImVec2 pos = ImGui::GetCursorScreenPos();
421 const ImVec2 size(ImGui::GetContentRegionAvail().x,
422 ImGui::GetTextLineHeight());
423 auto* draw = ImGui::GetWindowDrawList();
424 draw->PushClipRect(pos, ImVec2(pos.x + size.x, pos.y + size.y),
true);
425 draw->AddText(pos, ImGui::GetColorU32(ImGuiCol_TextDisabled),
429 if (ImGui::IsItemHovered()) {
430 ImGui::BeginTooltip();
431 ImGui::TextUnformatted(caption.c_str());
432 ImGui::TextDisabled(
"%d loaded rooms",
436 ImGui::TextDisabled(
"Palette %02X / Room GFX %02X", meta->first,
439 "Click a room to open. Double-click for a separate panel.");
447 const ImVec2& cell_min,
const ImVec2& cell_max,
450 const ImU32 dim_target =
454 ImVec2(cell_min.x - 1.5f, cell_min.y - 1.5f),
455 ImVec2(cell_max.x + 1.5f, cell_max.y + 1.5f),
460 ImU32 color = *outline;
461 if (!matches_filter) {
464 draw_list->AddRect(ImVec2(cell_min.x + 1.0f, cell_min.y + 1.0f),
465 ImVec2(cell_max.x - 1.0f, cell_max.y - 1.0f), color,
469 const float corner = std::max(4.0f, (cell_max.x - cell_min.x) * 0.3f);
471 if (!matches_filter) {
474 draw_list->AddTriangleFilled(ImVec2(cell_max.x - corner, cell_min.y),
475 ImVec2(cell_max.x, cell_min.y),
476 ImVec2(cell_max.x, cell_min.y + corner),