yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_room_selector.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <map>
6
7#include "absl/strings/str_format.h"
16#include "app/gui/core/icons.h"
17#include "app/gui/core/input.h"
20#include "imgui/imgui.h"
21#include "util/hex.h"
23#include "zelda3/dungeon/room.h"
27
28namespace yaze::editor {
29
30using ImGui::BeginChild;
31using ImGui::EndChild;
32using ImGui::SameLine;
33
35 // Legacy combined view - prefer using DrawRoomSelector() and
36 // DrawEntranceSelector() separately via their own EditorPanels
38}
39
41 RoomSelectionIntent single_click_intent) {
42 DrawRoomSelectorInternal(single_click_intent, true);
43}
44
46 RoomSelectionIntent single_click_intent) {
47 DrawRoomSelectorInternal(single_click_intent, false);
48}
49
53
54 for (int i = 0; i < zelda3::kNumberOfRooms; ++i) {
55 std::string display_name =
57 if (room_filter_.PassFilter(display_name.c_str()) &&
59 filtered_room_indices_.push_back(i);
60 }
61 }
62}
63
66 return true;
67 if (!rooms_ || room_id < 0 || room_id >= static_cast<int>(rooms_->size())) {
68 return true;
69 }
70 const auto* room = rooms_->GetIfLoaded(room_id);
71 if (room == nullptr) {
72 return false;
73 }
74 switch (entity_type_filter_) {
76 return !room->GetSprites().empty();
77 case kFilterHasItems:
78 return !room->GetPotItems().empty();
80 return !room->GetTileObjects().empty();
81 default:
82 return true;
83 }
84}
85
87 RoomSelectionIntent single_click_intent, bool show_room_id_input) {
88 if (!rom_ || !rom_->is_loaded()) {
89 ImGui::Text(tr("ROM not loaded"));
90 return;
91 }
92
93 if (show_room_id_input) {
94 if (gui::InputHexWord("Room ID", &current_room_id_, 50.f, true)) {
96 current_room_id_ = static_cast<uint16_t>(zelda3::kNumberOfRooms - 1);
97 }
98
99 // Publish selection changed event
100 if (auto* bus = ContentRegistry::Context::event_bus()) {
101 bus->Publish(SelectionChangedEvent::CreateSingle("dungeon_room",
103 }
104
105 // Callback support
108 }
109 }
110 ImGui::Separator();
111 }
112
113 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
114 room_filter_.DrawWithHint("Filter", tr("room name"));
115
116 // View mode + entity-type filter row
117 {
118 // View mode toggle
119 if (ImGui::SmallButton(view_mode_ == kViewList ? ICON_MD_LIST " List"
121 " Grouped")) {
123 }
124 if (ImGui::IsItemHovered()) {
125 ImGui::SetTooltip(
126 tr("Toggle between flat list and dungeon-grouped view"));
127 }
128 ImGui::SameLine(0, 12);
129
130 // Entity-type filter chips (compact, touch-friendly)
131 const char* labels[] = {"All", "Sprites", "Items", "Objects"};
134 for (int idx = 0; idx < 4; ++idx) {
135 if (idx > 0)
136 ImGui::SameLine();
137 bool selected = (entity_type_filter_ == values[idx]);
138 std::optional<gui::StyleColorGuard> selected_guard;
139 if (selected) {
140 selected_guard.emplace(ImGuiCol_Button,
141 ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));
142 }
143 if (ImGui::SmallButton(labels[idx])) {
144 entity_type_filter_ = values[idx];
145 }
146 }
147 }
148
149 // Rebuild every frame so renamed room labels appear immediately.
150 std::string current_filter(room_filter_.InputBuf);
151 if (current_filter != last_room_filter_) {
152 last_room_filter_ = current_filter;
153 }
155
156 // Increase row height on touch devices for easier tapping
157 const bool is_touch = gui::LayoutHelpers::IsTouchDevice();
158 std::optional<gui::StyleVarGuard> touch_pad_guard;
159 if (is_touch) {
160 float touch_pad = std::max(
161 6.0f, (gui::LayoutHelpers::GetMinTouchTarget() - ImGui::GetFontSize()) *
162 0.5f);
163 touch_pad_guard.emplace(ImGuiStyleVar_CellPadding,
164 ImVec2(ImGui::GetStyle().CellPadding.x, touch_pad));
165 }
166
167 // Dispatch to appropriate view mode
168 if (view_mode_ == kViewGrouped) {
169 DrawGroupedRoomList(single_click_intent);
170 return;
171 }
172
173 // === Flat list view (original) ===
174 if (ImGui::BeginTable("RoomList", 2,
175 ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders |
176 ImGuiTableFlags_RowBg |
177 ImGuiTableFlags_Resizable)) {
178 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 40.0f);
179 ImGui::TableSetupColumn("Name");
180 ImGui::TableHeadersRow();
181
182 // Use ImGuiListClipper for virtualized rendering
183 ImGuiListClipper clipper;
184 clipper.Begin(static_cast<int>(filtered_room_indices_.size()));
185
186 while (clipper.Step()) {
187 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
188 int room_id = filtered_room_indices_[row];
189 std::string display_name =
191
192 ImGui::TableNextRow();
193 ImGui::TableNextColumn();
194
195 char label[32];
196 snprintf(label, sizeof(label), "%03X", room_id);
197 if (ImGui::Selectable(label, current_room_id_ == room_id,
198 ImGuiSelectableFlags_SpanAllColumns |
199 ImGuiSelectableFlags_AllowDoubleClick)) {
200 current_room_id_ = room_id;
201
202 // Publish selection changed event
203 if (auto* bus = ContentRegistry::Context::event_bus()) {
204 bus->Publish(
205 SelectionChangedEvent::CreateSingle("dungeon_room", room_id));
206 }
207
208 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
210 room_intent_callback_(room_id,
212 } else if (room_selected_callback_) {
214 }
215 } else {
217 room_intent_callback_(room_id, single_click_intent);
218 } else if (room_selected_callback_) {
220 }
221 }
222 }
223
224 // Context menu
225 gui::ItemContextMenu(nullptr, [&, room_id]() {
226 std::vector<gui::MenuItemSpec> items;
227 items.emplace_back(tr("Open in Workbench"), ICON_MD_ARROW_FORWARD,
228 [this, room_id]() {
229 current_room_id_ = room_id;
232 room_id,
234 } else if (room_selected_callback_) {
236 }
237 });
238 items.emplace_back(
239 tr("Open as Panel"), ICON_MD_OPEN_IN_NEW, [this, room_id]() {
240 current_room_id_ = room_id;
242 room_intent_callback_(room_id,
244 } else if (room_selected_callback_) {
246 }
247 });
248 items.back().separator_after = true;
249 items.push_back(gui::CopyToClipboardItem(
250 tr("Copy Room ID"), absl::StrFormat("0x%03X", room_id)));
251 return items;
252 });
253
254 // Tooltip with room name and thumbnail
255 if (ImGui::IsItemHovered()) {
256 ImGui::BeginTooltip();
257 ImGui::Text("%s", display_name.c_str());
258 if (rooms_) {
259 auto* loaded_room = rooms_->GetIfLoaded(room_id);
260 if (loaded_room != nullptr) {
261 ImGui::TextDisabled(tr("Blockset: %d | Palette: %d"),
262 loaded_room->blockset(),
263 loaded_room->palette());
265 *loaded_room, room_tooltip_composite_);
268 if (bmp.is_active() && bmp.texture() != 0) {
269 ImGui::Image((ImTextureID)(intptr_t)bmp.texture(),
270 ImVec2(64, 64));
271 }
272 }
273 }
274 ImGui::EndTooltip();
275 }
276
277 ImGui::TableNextColumn();
278 ImGui::TextUnformatted(display_name.c_str());
279 }
280 }
281
282 ImGui::EndTable();
283 }
284}
285
287 constexpr int kNumSpawnPoints = zelda3::kNumDungeonSpawnPoints;
288 constexpr int kNumEntrances = zelda3::kNumRegularDungeonEntrances;
289 constexpr int kTotalEntries = zelda3::kNumDungeonEntranceSlots;
290
292 filtered_entrance_indices_.reserve(kTotalEntries);
293
294 for (int i = 0; i < kTotalEntries; i++) {
295 std::string display_name;
296
297 if (i < kNumSpawnPoints) {
298 display_name = absl::StrFormat("Spawn Point %d", i);
299 } else {
300 int entrance_id = i - kNumSpawnPoints;
301 if (entrance_id < kNumEntrances) {
303 project_, entrance_id, entrances_ ? (*entrances_)[i].room_ : -1);
304 } else {
305 display_name = absl::StrFormat("Unknown Entrance %d", i);
306 }
307 }
308
309 int room_id = 0;
310 if (i < kNumSpawnPoints && spawn_points_) {
311 room_id = (*spawn_points_)[i].room_id;
312 } else if (entrances_ && i < static_cast<int>(entrances_->size())) {
313 room_id = (*entrances_)[i].room_;
314 }
315
316 char filter_text[256];
317 snprintf(filter_text, sizeof(filter_text), "%s %03X", display_name.c_str(),
318 room_id);
319
320 if (entrance_filter_.PassFilter(filter_text)) {
321 filtered_entrance_indices_.push_back(i);
322 }
323 }
324}
325
329
333
335 if (!rom_ || !rom_->is_loaded()) {
336 ImGui::Text(tr("ROM not loaded"));
337 return;
338 }
339
340 if (!entrances_) {
341 ImGui::Text(tr("Entrances not loaded"));
342 return;
343 }
344
345 if (show_properties) {
346 if (current_entrance_id_ < 0 ||
347 current_entrance_id_ >= static_cast<int>(entrances_->size())) {
349 }
350 auto& current_entrance = (*entrances_)[current_entrance_id_];
351 const bool properties_editable =
353 bool changed = false;
354
355 // Keep the full property editor in the standalone entrance panel.
356 if (!properties_editable) {
357 ImGui::TextWrapped(tr(kDungeonSpawnReadOnlyReason));
358 }
359 ImGui::BeginDisabled(!properties_editable);
360 if (ImGui::BeginTable("EntranceProps", 4, ImGuiTableFlags_Borders)) {
361 ImGui::TableSetupColumn("Core", ImGuiTableColumnFlags_WidthStretch);
362 ImGui::TableSetupColumn("Position", ImGuiTableColumnFlags_WidthStretch);
363 ImGui::TableSetupColumn("Camera", ImGuiTableColumnFlags_WidthStretch);
364 ImGui::TableSetupColumn("Scroll", ImGuiTableColumnFlags_WidthStretch);
365 ImGui::TableHeadersRow();
366
367 ImGui::TableNextRow();
368 ImGui::TableNextColumn();
369 ImGui::Text(tr("Entr ID: %04X"), current_entrance.entrance_id_);
370 changed |= gui::InputHexWord("Room ID", &current_entrance.room_);
371 changed |= gui::InputHexByte("Dungeon", &current_entrance.dungeon_id_);
372 changed |= gui::InputHexByte("Music", &current_entrance.music_);
373
374 ImGui::TableNextColumn();
375 changed |= gui::InputHexWord("Player X", &current_entrance.x_position_);
376 changed |= gui::InputHexWord("Player Y", &current_entrance.y_position_);
377 changed |= gui::InputHexByte("Blockset", &current_entrance.blockset_);
378 changed |= gui::InputHexByte("Floor", &current_entrance.floor_);
379
380 ImGui::TableNextColumn();
381 changed |=
382 gui::InputHexWord("Cam Trg X", &current_entrance.camera_trigger_x_);
383 changed |=
384 gui::InputHexWord("Cam Trg Y", &current_entrance.camera_trigger_y_);
385 changed |= gui::InputHexWord("Exit", &current_entrance.exit_);
386
387 ImGui::TableNextColumn();
388 changed |= gui::InputHexWord("Scroll X", &current_entrance.camera_x_);
389 changed |= gui::InputHexWord("Scroll Y", &current_entrance.camera_y_);
390
391 ImGui::EndTable();
392 }
393 ImGui::EndDisabled();
394
395 ImGui::Separator();
396 if (ImGui::CollapsingHeader(tr("Camera Boundaries"))) {
397 ImGui::Text(tr(" North East South West"));
398 ImGui::BeginDisabled(!properties_editable);
399 ImGui::Text(tr("Quadrant "));
400 SameLine();
401 changed |= gui::InputHexByte("##QN",
402 &current_entrance.camera_boundary_qn_, 40.f);
403 SameLine();
404 changed |= gui::InputHexByte("##QE",
405 &current_entrance.camera_boundary_qe_, 40.f);
406 SameLine();
407 changed |= gui::InputHexByte("##QS",
408 &current_entrance.camera_boundary_qs_, 40.f);
409 SameLine();
410 changed |= gui::InputHexByte("##QW",
411 &current_entrance.camera_boundary_qw_, 40.f);
412
413 ImGui::Text(tr("Full Room "));
414 SameLine();
415 changed |= gui::InputHexByte("##FN",
416 &current_entrance.camera_boundary_fn_, 40.f);
417 SameLine();
418 changed |= gui::InputHexByte("##FE",
419 &current_entrance.camera_boundary_fe_, 40.f);
420 SameLine();
421 changed |= gui::InputHexByte("##FS",
422 &current_entrance.camera_boundary_fs_, 40.f);
423 SameLine();
424 changed |= gui::InputHexByte("##FW",
425 &current_entrance.camera_boundary_fw_, 40.f);
426 ImGui::EndDisabled();
427 }
429 changed);
430 ImGui::Separator();
431 }
432
433 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
434 entrance_filter_.DrawWithHint("Filter", tr("entrance name or room ID"));
435
436 // Target-room ownership can change while the filter text stays the same.
437 std::string current_filter(entrance_filter_.InputBuf);
438 last_entrance_filter_ = current_filter;
440
441 constexpr int kNumSpawnPoints = zelda3::kNumDungeonSpawnPoints;
442
443 if (ImGui::BeginTable("EntranceList", 3,
444 ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders |
445 ImGuiTableFlags_RowBg |
446 ImGuiTableFlags_Resizable)) {
447 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 40.0f);
448 ImGui::TableSetupColumn("Room", ImGuiTableColumnFlags_WidthFixed, 50.0f);
449 ImGui::TableSetupColumn("Name");
450 ImGui::TableHeadersRow();
451
452 // Use ImGuiListClipper for virtualized rendering
453 ImGuiListClipper clipper;
454 clipper.Begin(static_cast<int>(filtered_entrance_indices_.size()));
455
456 while (clipper.Step()) {
457 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
458 int i = filtered_entrance_indices_[row];
459 std::string display_name;
460
461 if (i < kNumSpawnPoints) {
462 display_name = absl::StrFormat("Spawn Point %d", i);
463 } else {
464 int entrance_id = i - kNumSpawnPoints;
466 project_, entrance_id, (*entrances_)[i].room_);
467 }
468
469 int room_id = 0;
470 if (i < kNumSpawnPoints && spawn_points_) {
471 room_id = (*spawn_points_)[i].room_id;
472 } else if (i < static_cast<int>(entrances_->size())) {
473 room_id = (*entrances_)[i].room_;
474 }
475
476 ImGui::TableNextRow();
477 ImGui::TableNextColumn();
478
479 char label[32];
480 snprintf(label, sizeof(label), "%c%02X",
481 i < kNumSpawnPoints ? 'S' : 'E',
482 i < kNumSpawnPoints ? i : i - kNumSpawnPoints);
483 if (ImGui::Selectable(label, current_entrance_id_ == i,
484 ImGuiSelectableFlags_SpanAllColumns)) {
486 if (i < static_cast<int>(entrances_->size())) {
487 // Publish selection changed event
488 if (auto* bus = ContentRegistry::Context::event_bus()) {
489 bus->Publish(
490 SelectionChangedEvent::CreateSingle("dungeon_entrance", i));
491 }
492
493 // Legacy callback support
496 } else if (room_selected_callback_) {
498 }
499 }
500 }
501
502 ImGui::TableNextColumn();
503 ImGui::Text("%03X", room_id);
504
505 ImGui::TableNextColumn();
506 ImGui::TextUnformatted(display_name.c_str());
507 }
508 }
509
510 ImGui::EndTable();
511 }
512}
513
514} // namespace yaze::editor
515
516// ============================================================================
517// Grouped view + Blockset group name helper (appended)
518// ============================================================================
519
520namespace yaze::editor {
521
522const char* DungeonRoomSelector::GetBlocksetGroupName(uint8_t blockset) {
523 // ALttP blockset -> dungeon name mapping (vanilla ROM)
524 switch (blockset) {
525 case 0:
526 return "Hyrule Castle";
527 case 1:
528 return "Eastern Palace";
529 case 2:
530 return "Desert Palace";
531 case 3:
532 return "Tower of Hera";
533 case 4:
534 return "Agahnim's Tower";
535 case 5:
536 return "Palace of Darkness";
537 case 6:
538 return "Swamp Palace";
539 case 7:
540 return "Skull Woods";
541 case 8:
542 return "Thieves' Town";
543 case 9:
544 return "Ice Palace";
545 case 10:
546 return "Misery Mire";
547 case 11:
548 return "Turtle Rock";
549 case 12:
550 return "Ganon's Tower";
551 case 13:
552 return "Cave";
553 case 14:
554 return "Sanctuary / Church";
555 case 15:
556 return "Houses / Interior";
557 case 16:
558 return "Shops";
559 case 17:
560 return "Fairy Fountain";
561 case 18:
562 return "Underground";
563 case 19:
564 return "Master Sword";
565 case 20:
566 return "Fortune Teller";
567 case 21:
568 return "Tower (Ganon)";
569 case 22:
570 return "Chris Houlihan";
571 case 23:
572 return "Links House";
573 case 24:
574 return "Tomb";
575 default:
576 return "Unknown";
577 }
578}
579
581 RoomSelectionIntent single_click_intent) {
582 // Build groups from filtered rooms
583 // Group key = blockset (if rooms loaded), else "Unloaded"
584 struct GroupInfo {
585 std::string name;
586 std::vector<int> room_ids;
587 };
588 std::map<int, GroupInfo> groups;
589
590 for (int room_id : filtered_room_indices_) {
591 int key = 255; // Unknown/unloaded
592 std::string group_name = "Unloaded";
593 if (rooms_ && room_id >= 0 && room_id < static_cast<int>(rooms_->size())) {
594 size_t dungeon_index = 0;
595 if (const auto* dungeon = dungeon_project_labels::FindDungeonForRoom(
596 project_, room_id, &dungeon_index)) {
597 key = -static_cast<int>(dungeon_index) - 1;
598 group_name = dungeon_project_labels::FormatDungeonName(*dungeon);
599 } else if (auto* loaded_room = rooms_->GetIfLoaded(room_id)) {
600 key = loaded_room->blockset();
601 group_name = GetBlocksetGroupName(static_cast<uint8_t>(key));
602 }
603 }
604 auto& g = groups[key];
605 g.name = group_name;
606 g.room_ids.push_back(room_id);
607 }
608
609 // Draw as scrollable child with collapsible tree nodes
610 if (ImGui::BeginChild("##GroupedRoomList", ImVec2(0, 0), false,
611 ImGuiWindowFlags_None)) {
612 for (auto& [key, group] : groups) {
613 char header[64];
614 snprintf(header, sizeof(header), "%s (%zu rooms)##grp%d",
615 group.name.c_str(), group.room_ids.size(), key);
616
617 // Auto-open the group that contains the currently selected room
618 bool has_current =
619 std::find(group.room_ids.begin(), group.room_ids.end(),
620 static_cast<int>(current_room_id_)) != group.room_ids.end();
621 if (has_current) {
622 ImGui::SetNextItemOpen(
623 true, pending_scroll_room_id_ == static_cast<int>(current_room_id_)
624 ? ImGuiCond_Always
625 : ImGuiCond_Once);
626 }
627
628 if (ImGui::CollapsingHeader(header)) {
629 for (int room_id : group.room_ids) {
630 std::string display_name =
632 char label[64];
633 snprintf(label, sizeof(label), "%03X %s##r%d", room_id,
634 display_name.c_str(), room_id);
635
636 const bool is_current = current_room_id_ == room_id;
637 if (ImGui::Selectable(label, is_current,
638 ImGuiSelectableFlags_AllowDoubleClick)) {
639 current_room_id_ = room_id;
640
641 if (auto* bus = ContentRegistry::Context::event_bus()) {
642 bus->Publish(
643 SelectionChangedEvent::CreateSingle("dungeon_room", room_id));
644 }
645
646 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
648 room_intent_callback_(room_id,
650 } else if (room_selected_callback_) {
652 }
653 } else {
655 room_intent_callback_(room_id, single_click_intent);
656 } else if (room_selected_callback_) {
658 }
659 }
660 }
661 if (is_current && pending_scroll_room_id_ == room_id) {
662 ImGui::SetScrollHereY(0.5f);
664 }
665
666 // Tooltip with thumbnail
667 if (ImGui::IsItemHovered() && rooms_) {
668 auto* loaded_room = rooms_->GetIfLoaded(room_id);
669 if (loaded_room != nullptr) {
670 ImGui::BeginTooltip();
671 ImGui::Text("%s", display_name.c_str());
672 ImGui::TextDisabled(tr("Blockset: %d | Palette: %d"),
673 loaded_room->blockset(),
674 loaded_room->palette());
676 *loaded_room, room_tooltip_composite_);
679 if (bmp.is_active() && bmp.texture() != 0) {
680 ImGui::Image((ImTextureID)(intptr_t)bmp.texture(),
681 ImVec2(64, 64));
682 }
683 ImGui::EndTooltip();
684 }
685 }
686 }
687 }
688 }
689 }
690 ImGui::EndChild();
691}
692
693} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:199
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > * spawn_points_
void DrawEntranceSelectorInternal(bool show_properties)
void DrawGroupedRoomList(RoomSelectionIntent single_click_intent)
void DrawRoomSelectorInternal(RoomSelectionIntent single_click_intent, bool show_room_id_input)
void DrawRoomSelector(RoomSelectionIntent single_click_intent=RoomSelectionIntent::kFocusInWorkbench)
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > * entrances_
void DrawRoomBrowser(RoomSelectionIntent single_click_intent=RoomSelectionIntent::kFocusInWorkbench)
bool PassesEntityTypeFilter(int room_id) const
std::function< void(int)> room_selected_callback_
std::function< void(int, RoomSelectionIntent)> room_intent_callback_
const project::YazeProject * project_
static const char * GetBlocksetGroupName(uint8_t blockset)
std::function< void(int)> entrance_selected_callback_
zelda3::Room * GetIfLoaded(int room_id)
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:211
static Arena & Get()
Definition arena.cc:24
static float GetMinTouchTarget()
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:316
#define ICON_MD_ARROW_FORWARD
Definition icons.h:184
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
::yaze::EventBus * event_bus()
Get the current EventBus instance.
std::string GetEntranceLabel(const project::YazeProject *project, int entrance_id, int room_id)
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
const core::DungeonEntry * FindDungeonForRoom(const project::YazeProject *project, int room_id, size_t *dungeon_index=nullptr)
std::string FormatDungeonName(const core::DungeonEntry &dungeon)
Editors are the view controllers for the application.
bool CanEditDungeonEntrance(int slot_index, const zelda3::RoomEntrance &entrance)
constexpr char kDungeonSpawnReadOnlyReason[]
bool MarkDungeonEntranceDirtyIfEditable(int slot_index, zelda3::RoomEntrance &entrance, bool properties_changed)
gfx::Bitmap & PrepareCanonicalRoomComposite(zelda3::Room &room, RoomCompositeOutput &output)
RoomSelectionIntent
Intent for room selection in the dungeon editor.
void EnsureCompositeBitmapTextureQueued(Bitmap &composite)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:462
bool ItemContextMenu(const char *id, const std::function< std::vector< MenuItemSpec >()> &build, ImGuiPopupFlags flags)
Right-click menu for the last submitted ImGui item.
MenuItemSpec CopyToClipboardItem(const std::string &label, const std::string &text)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:483
constexpr int kNumRegularDungeonEntrances
constexpr int kNumDungeonSpawnPoints
constexpr int kNumberOfRooms
constexpr int kNumDungeonEntranceSlots
static SelectionChangedEvent CreateSingle(const std::string &src, int id, size_t session=0)