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