yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_matrix_content.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_DUNGEON_WORKSPACE_ROOM_MATRIX_CONTENT_H_
2#define YAZE_APP_EDITOR_DUNGEON_WORKSPACE_ROOM_MATRIX_CONTENT_H_
3
4#include <array>
5#include <cctype>
6#include <cmath>
7#include <cstdint>
8#include <functional>
9#include <optional>
10#include <string>
11#include <unordered_map>
12#include <vector>
13
18#include "app/gui/core/icons.h"
19#include "imgui/imgui.h"
20#include "zelda3/dungeon/room.h"
23
24namespace yaze {
25namespace editor {
26
42 public:
50 RoomMatrixContent(int* current_room_id, ImVector<int>* active_rooms,
51 std::function<void(int)> on_room_selected,
52 std::function<void(int, int)> on_room_swap = nullptr,
53 DungeonRoomStore* rooms = nullptr)
54 : current_room_id_(current_room_id),
55 active_rooms_(active_rooms),
56 rooms_(rooms),
57 on_room_selected_(std::move(on_room_selected)),
58 on_room_swap_(std::move(on_room_swap)) {}
59
60 // ==========================================================================
61 // WindowContent Identity
62 // ==========================================================================
63
64 std::string GetId() const override { return "dungeon.room_matrix"; }
65 std::string GetDisplayName() const override { return "Room Matrix"; }
66 std::string GetIcon() const override { return ICON_MD_GRID_VIEW; }
67 std::string GetEditorCategory() const override { return "Dungeon"; }
68 int GetPriority() const override { return 30; }
69 float GetPreferredWidth() const override { return 440.0f; }
70
72 std::function<void(int, RoomSelectionIntent)> callback) {
73 on_room_intent_ = std::move(callback);
74 }
75
76 // ==========================================================================
77 // WindowContent Drawing
78 // ==========================================================================
79
80 void Draw(bool* p_open) override {
82 return;
83
84 const auto& theme = AgentUI::GetTheme();
85
86 // 16 wide x 19 tall = 304 cells (296 rooms + 8 empty)
87 constexpr int kRoomsPerRow = 16;
88 constexpr int kRoomsPerCol = 19;
89 constexpr int kTotalRooms = 0x128; // 296 rooms (0x00-0x127)
90 constexpr float kCellSpacing = 1.0f;
91
92 DrawMatrixSummary(theme, kTotalRooms);
93
94 ImGui::PushID("RoomMatrixFilter");
95 ImGui::SetNextItemWidth(
96 std::max(140.0f, ImGui::GetContentRegionAvail().x - 82.0f));
97 ImGui::InputTextWithHint("##Search", ICON_MD_SEARCH " Filter room id/name",
98 search_filter_, IM_ARRAYSIZE(search_filter_));
99 ImGui::SameLine();
100 const bool has_filter = search_filter_[0] != '\0';
101 if (!has_filter) {
102 ImGui::BeginDisabled();
103 }
104 if (ImGui::Button(ICON_MD_CLOSE "##ClearFilter")) {
105 search_filter_[0] = '\0';
106 }
107 if (!has_filter) {
108 ImGui::EndDisabled();
109 }
110 if (ImGui::IsItemHovered()) {
111 ImGui::SetTooltip("Clear filter");
112 }
113 ImGui::PopID();
114
115 DrawMatrixLegend(theme);
116 ImGui::Spacing();
117
118 const ImVec2 avail = ImGui::GetContentRegionAvail();
119 const float panel_width = std::max(1.0f, avail.x);
120 const float panel_height = std::max(1.0f, avail.y);
121 const float cell_size_by_width =
122 (panel_width - kCellSpacing * (kRoomsPerRow - 1)) / kRoomsPerRow;
123 const float cell_size_by_height =
124 (panel_height - kCellSpacing * (kRoomsPerCol - 1)) / kRoomsPerCol;
125 const float cell_size = std::clamp(
126 std::min(cell_size_by_width, cell_size_by_height), 12.0f, 24.0f);
127
128 const float grid_width =
129 kRoomsPerRow * cell_size + kCellSpacing * (kRoomsPerRow - 1);
130 const float grid_height =
131 kRoomsPerCol * cell_size + kCellSpacing * (kRoomsPerCol - 1);
132 const float x_offset = std::max(0.0f, (panel_width - grid_width) * 0.5f);
133 const float y_offset = std::max(0.0f, (panel_height - grid_height) * 0.5f);
134
135 ImDrawList* draw_list = ImGui::GetWindowDrawList();
136 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
137 canvas_pos.x += x_offset;
138 canvas_pos.y += y_offset;
139
140 int room_index = 0;
141 for (int row = 0; row < kRoomsPerCol; row++) {
142 for (int col = 0; col < kRoomsPerRow; col++) {
143 int room_id = room_index;
144 bool is_valid_room = (room_id < kTotalRooms);
145 const bool matches_filter = MatchesSearchFilter(room_id);
146
147 ImVec2 cell_min =
148 ImVec2(canvas_pos.x + col * (cell_size + kCellSpacing),
149 canvas_pos.y + row * (cell_size + kCellSpacing));
150 ImVec2 cell_max =
151 ImVec2(cell_min.x + cell_size, cell_min.y + cell_size);
152
153 if (is_valid_room) {
154 // Get color based on room palette if available, else use algorithmic
155 ImU32 bg_color = GetRoomColor(room_id, theme);
156 if (!matches_filter) {
157 bg_color = BlendRoomColor(
158 bg_color, ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker),
159 0.72f);
160 }
161
162 bool is_current = (*current_room_id_ == room_id);
163 bool is_open = false;
164 for (int i = 0; i < active_rooms_->Size; i++) {
165 if ((*active_rooms_)[i] == room_id) {
166 is_open = true;
167 break;
168 }
169 }
170
171 // Draw cell background
172 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
173
174 // Draw outline based on state using theme colors
175 if (is_current) {
176 // Add glow effect for current room (outer glow layers)
177 ImVec4 glow_color = theme.dungeon_selection_primary;
178 glow_color.w = matches_filter ? 0.3f : 0.18f;
179 ImVec2 glow_min(cell_min.x - 2, cell_min.y - 2);
180 ImVec2 glow_max(cell_max.x + 2, cell_max.y + 2);
181 draw_list->AddRect(glow_min, glow_max,
182 ImGui::ColorConvertFloat4ToU32(glow_color), 0.0f,
183 0, 3.0f);
184
185 // Inner bright border
186 ImU32 sel_color =
187 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
188 draw_list->AddRect(cell_min, cell_max, sel_color, 0.0f, 0, 2.5f);
189 } else if (is_open) {
190 ImU32 open_color = ImGui::ColorConvertFloat4ToU32(
191 theme.dungeon_grid_cell_selected);
192 draw_list->AddRect(cell_min, cell_max, open_color, 0.0f, 0, 2.0f);
193 } else {
194 ImU32 border_color =
195 ImGui::ColorConvertFloat4ToU32(theme.dungeon_grid_cell_border);
196 if (!matches_filter) {
197 border_color = BlendRoomColor(
198 border_color,
199 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker), 0.5f);
200 }
201 draw_list->AddRect(cell_min, cell_max, border_color, 0.0f, 0, 1.0f);
202 }
203
204 // Draw room ID (only if cell is large enough)
205 if (cell_size >= 18.0f) {
206 char label[8];
207 snprintf(label, sizeof(label), "%02X", room_id);
208 ImVec2 text_size = ImGui::CalcTextSize(label);
209 ImVec2 text_pos =
210 ImVec2(cell_min.x + (cell_size - text_size.x) * 0.5f,
211 cell_min.y + (cell_size - text_size.y) * 0.5f);
212 ImVec4 text_color_vec = theme.dungeon_grid_text;
213 if (!matches_filter) {
214 text_color_vec.w *= 0.55f;
215 }
216 ImU32 text_color = ImGui::ColorConvertFloat4ToU32(text_color_vec);
217 draw_list->AddText(text_pos, text_color, label);
218 }
219
220 // Handle clicks
221 ImGui::SetCursorScreenPos(cell_min);
222 char btn_id[32];
223 snprintf(btn_id, sizeof(btn_id), "##room%d", room_id);
224 ImGui::InvisibleButton(btn_id, ImVec2(cell_size, cell_size));
225
226 if (ImGui::IsItemClicked()) {
227 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
228 // Double-click: open as standalone panel
229 if (on_room_intent_) {
231 } else if (on_room_selected_) {
232 on_room_selected_(room_id);
233 }
234 } else if (on_room_selected_) {
235 on_room_selected_(room_id);
236 }
237 }
238
239 if (ImGui::BeginPopupContextItem()) {
240 const bool can_swap =
242 *current_room_id_ < kTotalRooms && *current_room_id_ != room_id;
243
244 std::string open_label =
245 is_open ? "Focus Room" : "Open in Workbench";
246 if (ImGui::MenuItem(open_label.c_str())) {
247 if (on_room_intent_) {
248 on_room_intent_(room_id,
250 } else if (on_room_selected_) {
251 on_room_selected_(room_id);
252 }
253 }
254
255 if (ImGui::MenuItem("Open as Panel")) {
256 if (on_room_intent_) {
258 } else if (on_room_selected_) {
259 on_room_selected_(room_id);
260 }
261 }
262
263 if (ImGui::MenuItem("Swap With Current Room", nullptr, false,
264 can_swap)) {
266 }
267
268 ImGui::Separator();
269
270 char id_buf[16];
271 snprintf(id_buf, sizeof(id_buf), "0x%02X", room_id);
272 if (ImGui::MenuItem("Copy Room ID")) {
273 ImGui::SetClipboardText(id_buf);
274 }
275
276 const std::string& room_label = zelda3::GetRoomLabel(room_id);
277 if (ImGui::MenuItem("Copy Room Name")) {
278 ImGui::SetClipboardText(room_label.c_str());
279 }
280
281 ImGui::EndPopup();
282 }
283
284 // Tooltip with room info and thumbnail preview
285 if (ImGui::IsItemHovered()) {
286 ImGui::BeginTooltip();
287 // Use unified ResourceLabelProvider for room names
288 ImGui::Text("%s", zelda3::GetRoomLabel(room_id).c_str());
289 ImGui::TextDisabled("Room 0x%02X", room_id);
290
291 if (is_current) {
292 ImGui::TextColored(theme.dungeon_selection_primary,
293 ICON_MD_MY_LOCATION " Current workbench room");
294 } else if (is_open) {
295 ImGui::TextColored(theme.status_success,
296 ICON_MD_TAB " Open in editor");
297 }
298
299 if (rooms_) {
300 auto* loaded_room = rooms_->GetIfLoaded(room_id);
301 if (loaded_room != nullptr) {
302 // Show palette info
303 ImGui::TextDisabled("Palette: %d | Blockset: %d",
304 loaded_room->palette(),
305 loaded_room->blockset());
306
307 // Show thumbnail preview of the room
308 auto& room = *loaded_room;
309 zelda3::RoomLayerManager layer_mgr;
310 layer_mgr.ApplyLayerMerging(room.layer_merging());
311 auto& preview_bitmap = room.GetCompositeBitmap(layer_mgr);
312 if (preview_bitmap.is_active() &&
313 preview_bitmap.texture() != 0) {
314 ImGui::Separator();
315 // Render at thumbnail size (80x80 from 512x512)
316 constexpr float kThumbnailSize = 80.0f;
317 ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(),
318 ImVec2(kThumbnailSize, kThumbnailSize));
319 }
320 }
321 }
322
323 ImGui::Separator();
324 ImGui::TextDisabled("Click to %s", is_open ? "focus" : "open");
325 ImGui::TextDisabled("Double-click to open as panel");
326 ImGui::TextDisabled("Right-click for actions");
327 ImGui::EndTooltip();
328 }
329 } else {
330 // Empty cell
331 draw_list->AddRectFilled(
332 cell_min, cell_max,
333 ImGui::ColorConvertFloat4ToU32(theme.panel_bg_darker));
334 }
335
336 room_index++;
337 }
338 }
339
340 // Advance cursor past the grid
341 ImGui::Dummy(
342 ImVec2(panel_width, std::max(grid_height + y_offset, panel_height)));
343 }
344
345 void SetRooms(DungeonRoomStore* rooms) { rooms_ = rooms; }
346
347 private:
348 void DrawMatrixSummary(const AgentUITheme& theme, int total_rooms) const {
349 const int open_rooms = active_rooms_ ? active_rooms_->Size : 0;
350 const int current_room = current_room_id_ ? *current_room_id_ : -1;
351 const std::string current_label = current_room >= 0
352 ? zelda3::GetRoomLabel(current_room)
353 : "No room selected";
354
355 ImGui::SeparatorText("Navigator");
356 ImGui::Text("%s", current_label.c_str());
357 ImGui::TextDisabled("Current: 0x%02X", std::max(0, current_room));
358 ImGui::SameLine();
359 ImGui::TextDisabled("%d open", open_rooms);
360
361 if (current_room >= 0) {
362 if (auto room_meta = GetRoomMetadata(current_room);
363 room_meta.has_value()) {
364 ImGui::SameLine();
365 ImGui::TextColored(theme.status_success, "Pal %d / Blockset %d",
366 room_meta->first, room_meta->second);
367 }
368 }
369 }
370
371 void DrawMatrixLegend(const AgentUITheme& theme) const {
372 ImGui::SeparatorText("Legend");
374 ICON_MD_MY_LOCATION " Current");
375 ImGui::SameLine();
377 ImGui::SameLine();
379 ICON_MD_GRID_VIEW " Other");
380 }
381
382 void DrawLegendSwatch(const ImVec4& color, const char* label) const {
383 ImDrawList* draw_list = ImGui::GetWindowDrawList();
384 const ImVec2 pos = ImGui::GetCursorScreenPos();
385 const float size = ImGui::GetFrameHeight() - 6.0f;
386 const ImVec2 min = ImVec2(pos.x, pos.y + 3.0f);
387 const ImVec2 max = ImVec2(pos.x + size, pos.y + size + 3.0f);
388 draw_list->AddRectFilled(min, max, ImGui::ColorConvertFloat4ToU32(color),
389 3.0f);
390 draw_list->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border), 3.0f);
391 ImGui::Dummy(ImVec2(size + 6.0f, size + 6.0f));
392 ImGui::SameLine(0.0f, 6.0f);
393 ImGui::TextUnformatted(label);
394 }
395
396 bool MatchesSearchFilter(int room_id) const {
397 if (search_filter_[0] == '\0') {
398 return true;
399 }
400
401 const std::string filter = NormalizeForSearch(search_filter_);
402 char id_buf[16];
403 snprintf(id_buf, sizeof(id_buf), "%02X", room_id);
404 char hex_buf[16];
405 snprintf(hex_buf, sizeof(hex_buf), "0x%02X", room_id);
406
407 const std::string room_label =
409 const std::string id_text = NormalizeForSearch(id_buf);
410 const std::string hex_text = NormalizeForSearch(hex_buf);
411 return room_label.find(filter) != std::string::npos ||
412 id_text.find(filter) != std::string::npos ||
413 hex_text.find(filter) != std::string::npos;
414 }
415
416 static std::string NormalizeForSearch(const std::string& value) {
417 std::string lowered;
418 lowered.reserve(value.size());
419 for (unsigned char ch : value) {
420 lowered.push_back(static_cast<char>(std::tolower(ch)));
421 }
422 return lowered;
423 }
424
425 static ImU32 BlendRoomColor(ImU32 source, ImU32 target, float blend) {
426 ImVec4 src = ImGui::ColorConvertU32ToFloat4(source);
427 ImVec4 dst = ImGui::ColorConvertU32ToFloat4(target);
428 src.x = (src.x * (1.0f - blend)) + (dst.x * blend);
429 src.y = (src.y * (1.0f - blend)) + (dst.y * blend);
430 src.z = (src.z * (1.0f - blend)) + (dst.z * blend);
431 src.w = 1.0f;
432 return ImGui::ColorConvertFloat4ToU32(src);
433 }
434
435 std::optional<std::pair<int, int>> GetRoomMetadata(int room_id) const {
436 if (!rooms_ || room_id < 0 ||
437 room_id >= static_cast<int>(DungeonRoomStore::kRoomCount)) {
438 return std::nullopt;
439 }
440
441 if (const auto* room = rooms_->GetIfMaterialized(room_id);
442 room != nullptr) {
443 return std::make_pair(static_cast<int>(room->palette()),
444 static_cast<int>(room->blockset()));
445 }
446
447 Rom* rom = rooms_->rom();
448 if (rom == nullptr || !rom->is_loaded()) {
449 return std::nullopt;
450 }
451
452 zelda3::Room header_room = zelda3::LoadRoomHeaderFromRom(rom, room_id);
453 return std::make_pair(static_cast<int>(header_room.palette()),
454 static_cast<int>(header_room.blockset()));
455 }
456
460 ImU32 GetRoomColor(int room_id, const AgentUITheme& theme) {
461 auto sample_dominant_color =
462 [](const gfx::Bitmap& bitmap) -> std::optional<ImU32> {
463 if (!bitmap.is_active() || bitmap.width() <= 0 || bitmap.height() <= 0 ||
464 bitmap.data() == nullptr || bitmap.surface() == nullptr ||
465 bitmap.surface()->format == nullptr ||
466 bitmap.surface()->format->palette == nullptr) {
467 return std::nullopt;
468 }
469
470 constexpr int kSampleStep = 16;
471 std::array<uint32_t, 256> histogram{};
472 SDL_Palette* palette = bitmap.surface()->format->palette;
473 const uint8_t* pixels = bitmap.data();
474 const int width = bitmap.width();
475 const int height = bitmap.height();
476
477 for (int y = 0; y < height; y += kSampleStep) {
478 for (int x = 0; x < width; x += kSampleStep) {
479 const uint8_t idx = pixels[(y * width) + x];
480 if (idx == 255) {
481 continue;
482 }
483 histogram[idx]++;
484 }
485 }
486
487 uint32_t best_count = 0;
488 uint8_t best_index = 0;
489 for (int i = 0; i < palette->ncolors && i < 256; ++i) {
490 if (histogram[static_cast<size_t>(i)] > best_count) {
491 best_count = histogram[static_cast<size_t>(i)];
492 best_index = static_cast<uint8_t>(i);
493 }
494 }
495
496 if (best_count == 0) {
497 return std::nullopt;
498 }
499 const SDL_Color& c = palette->colors[best_index];
500 return IM_COL32(c.r, c.g, c.b, 255);
501 };
502
503 auto soften_color = [&](ImU32 color, float blend = 0.32f) -> ImU32 {
504 ImVec4 src = ImGui::ColorConvertU32ToFloat4(color);
505 const ImVec4 bg = theme.panel_bg_darker;
506 src.x = (src.x * (1.0f - blend)) + (bg.x * blend);
507 src.y = (src.y * (1.0f - blend)) + (bg.y * blend);
508 src.z = (src.z * (1.0f - blend)) + (bg.z * blend);
509 src.w = 1.0f;
510 return ImGui::ColorConvertFloat4ToU32(src);
511 };
512
513 auto palette_fallback_color = [&](int palette_id,
514 int blockset_id) -> ImU32 {
515 const ImVec4 bg = theme.panel_bg_darker;
516 const float palette_mix =
517 0.26f + (static_cast<float>(palette_id & 0x07) * 0.055f);
518 const float blockset_mix =
519 0.08f + (static_cast<float>(blockset_id & 0x0F) * 0.018f);
520 ImVec4 tint = theme.dungeon_selection_primary;
521 tint.x = std::clamp(tint.x + blockset_mix, 0.0f, 1.0f);
522 tint.y = std::clamp(tint.y + (palette_mix * 0.35f), 0.0f, 1.0f);
523 tint.z = std::clamp(tint.z - (blockset_mix * 0.2f), 0.0f, 1.0f);
524
525 ImVec4 mixed;
526 mixed.x = std::clamp(
527 (bg.x * (1.0f - palette_mix)) + (tint.x * palette_mix), 0.0f, 1.0f);
528 mixed.y = std::clamp(
529 (bg.y * (1.0f - palette_mix)) + (tint.y * palette_mix), 0.0f, 1.0f);
530 mixed.z = std::clamp(
531 (bg.z * (1.0f - palette_mix)) + (tint.z * palette_mix), 0.0f, 1.0f);
532 mixed.w = 1.0f;
533 return ImGui::ColorConvertFloat4ToU32(mixed);
534 };
535
536 // If room data is available and loaded, sample the actual room bitmap and
537 // choose its most frequent indexed color.
538 if (rooms_) {
539 auto* room = rooms_->GetIfLoaded(room_id);
540 if (room != nullptr) {
541 zelda3::RoomLayerManager layer_mgr;
542 layer_mgr.ApplyLayerMerging(room->layer_merging());
543 auto& composite = room->GetCompositeBitmap(layer_mgr);
544 if (auto composite_color = sample_dominant_color(composite);
545 composite_color.has_value()) {
546 return soften_color(composite_color.value());
547 }
548
549 if (auto bg1_color = sample_dominant_color(room->bg1_buffer().bitmap());
550 bg1_color.has_value()) {
551 return soften_color(bg1_color.value());
552 }
553
554 return palette_fallback_color(room->palette(), room->blockset());
555 }
556
557 if (auto room_meta = GetRoomMetadata(room_id); room_meta.has_value()) {
558 return palette_fallback_color(room_meta->first, room_meta->second);
559 }
560 }
561
562 // Fallback: neutral deterministic color buckets (no rainbow hue wheel).
563 const auto clamp01 = [](float v) {
564 return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v);
565 };
566
567 const ImVec4 dark = theme.panel_bg_darker;
568 const ImVec4 mid = theme.panel_bg_color;
569 const float group_mix =
570 0.16f + (static_cast<float>((room_id >> 4) & 0x03) * 0.08f);
571 const float step = static_cast<float>(room_id & 0x07) * 0.0125f;
572
573 ImVec4 fallback;
574 fallback.x = clamp01(dark.x + (mid.x - dark.x) * group_mix + step);
575 fallback.y = clamp01(dark.y + (mid.y - dark.y) * group_mix + step);
576 fallback.z = clamp01(dark.z + (mid.z - dark.z) * group_mix + step);
577 fallback.w = 1.0f;
578 return ImGui::ColorConvertFloat4ToU32(fallback);
579 }
580
581 int* current_room_id_ = nullptr;
582 ImVector<int>* active_rooms_ = nullptr;
584 std::function<void(int)> on_room_selected_;
585 std::function<void(int, int)> on_room_swap_;
586 std::function<void(int, RoomSelectionIntent)> on_room_intent_;
587 char search_filter_[64] = "";
588};
589
590} // namespace editor
591} // namespace yaze
592
593#endif // YAZE_APP_EDITOR_DUNGEON_WORKSPACE_ROOM_MATRIX_CONTENT_H_
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
bool is_loaded() const
Definition rom.h:132
zelda3::Room * GetIfMaterialized(int room_id)
static constexpr size_t kRoomCount
zelda3::Room * GetIfLoaded(int room_id)
WindowContent for displaying a visual 16x19 grid of all dungeon rooms.
std::function< void(int)> on_room_selected_
void DrawMatrixLegend(const AgentUITheme &theme) const
static std::string NormalizeForSearch(const std::string &value)
void DrawMatrixSummary(const AgentUITheme &theme, int total_rooms) const
std::function< void(int, RoomSelectionIntent)> on_room_intent_
static ImU32 BlendRoomColor(ImU32 source, ImU32 target, float blend)
std::string GetIcon() const override
Material Design icon for this panel.
std::optional< std::pair< int, int > > GetRoomMetadata(int room_id) const
void SetRooms(DungeonRoomStore *rooms)
std::string GetEditorCategory() const override
Editor category this panel belongs to.
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
int GetPriority() const override
Get display priority for menu ordering.
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
RoomMatrixContent(int *current_room_id, ImVector< int > *active_rooms, std::function< void(int)> on_room_selected, std::function< void(int, int)> on_room_swap=nullptr, DungeonRoomStore *rooms=nullptr)
Construct a room matrix panel.
void Draw(bool *p_open) override
Draw the panel content.
bool MatchesSearchFilter(int room_id) const
std::string GetId() const override
Unique identifier for this panel.
std::function< void(int, int)> on_room_swap_
void DrawLegendSwatch(const ImVec4 &color, const char *label) const
void SetRoomIntentCallback(std::function< void(int, RoomSelectionIntent)> callback)
ImU32 GetRoomColor(int room_id, const AgentUITheme &theme)
Get color for a room from dominant preview color, with fallback.
Base interface for all logical window content components.
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
RoomLayerManager - Manages layer visibility and compositing.
void ApplyLayerMerging(const LayerMergeType &merge_type)
uint8_t blockset() const
Definition room.h:602
uint8_t palette() const
Definition room.h:604
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_MY_LOCATION
Definition icons.h:1270
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_TAB
Definition icons.h:1930
#define ICON_MD_CLOSE
Definition icons.h:418
const AgentUITheme & GetTheme()
RoomSelectionIntent
Intent for room selection in the dungeon editor.
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Definition room.cc:346
std::string GetRoomLabel(int id)
Convenience function to get a room label.
Centralized theme colors for Agent UI components.
Definition agent_theme.h:19