yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_context_menu.cc
Go to the documentation of this file.
2
3#include <optional>
4#include <string>
5#include <utility>
6
7#include "absl/strings/str_format.h"
10#include "app/gui/core/icons.h"
12#include "imgui/imgui.h"
13#include "util/log.h"
18
19namespace yaze::editor {
20
26
29 return;
30 }
31
32 for (auto& item : BuildSelectionContextMenuItems(room_id)) {
33 canvas_.AddContextMenuItem(std::move(item));
34 }
35 auto insert_menu = BuildInsertContextMenu();
36 insert_menu.separator_after = true;
37 canvas_.AddContextMenuItem(insert_menu);
38}
39
41 if (!rooms_ || !rom_ || !rom_->is_loaded()) {
42 return;
43 }
44
46 if (auto open_menu = BuildOpenContextMenu(); open_menu.has_value()) {
47 canvas_.AddContextMenuItem(std::move(*open_menu));
48 }
49 gui::CanvasMenuItem display;
50 display.label = "Display";
51 display.icon = ICON_MD_VISIBILITY;
52 display.subitems.emplace_back(
53 "Fit room", ICON_MD_FIT_SCREEN, [this, room_id] {
54 if (auto* bitmap = PrepareRoomCompositeBitmap(room_id)) {
55 canvas_.SetZoomToFit(*bitmap);
58 }
59 });
60 display.subitems.emplace_back("Zoom in", ICON_MD_ZOOM_IN, [this] {
63 });
64 display.subitems.emplace_back("Zoom out", ICON_MD_ZOOM_OUT, [this] {
67 });
68 display.subitems.back().separator_after = true;
69 display.subitems.push_back(BuildLayerVisibilityContextMenu(room_id));
70 display.subitems.push_back(BuildOverlayContextMenu());
71 canvas_.AddContextMenuItem(std::move(display));
72
73 gui::CanvasMenuItem advanced;
74 advanced.label = "Advanced";
75 advanced.icon = ICON_MD_MORE_HORIZ;
76 advanced.subitems.push_back(BuildCopyExportContextMenu(room_id));
77 advanced.subitems.push_back(BuildReportContextMenu(room_id));
78 advanced.subitems.push_back(BuildDebugContextMenu(room_id));
79 canvas_.AddContextMenuItem(std::move(advanced));
80}
81
83 auto enabled_if = [](bool enabled) {
84 return [enabled]() {
85 return enabled;
86 };
87 };
88
89 gui::CanvasMenuItem insert_menu;
90 insert_menu.label = "Insert";
91 insert_menu.icon = ICON_MD_ADD_CIRCLE;
92
93 gui::CanvasMenuItem insert_object_item("Object...", ICON_MD_WIDGETS,
94 [this]() {
97 }
98 });
99 insert_object_item.enabled_condition =
100 enabled_if(show_object_panel_callback_ != nullptr);
101 insert_menu.subitems.push_back(std::move(insert_object_item));
102
103 gui::CanvasMenuItem insert_sprite_item("Sprite...", ICON_MD_PERSON, [this]() {
106 }
107 });
108 insert_sprite_item.enabled_condition =
109 enabled_if(show_sprite_panel_callback_ != nullptr);
110 insert_menu.subitems.push_back(std::move(insert_sprite_item));
111
112 gui::CanvasMenuItem insert_item_item("Item...", ICON_MD_INVENTORY, [this]() {
115 }
116 });
117 insert_item_item.enabled_condition =
118 enabled_if(show_item_panel_callback_ != nullptr);
119 insert_menu.subitems.push_back(std::move(insert_item_item));
120
121 const bool door_mode = object_interaction_.IsDoorPlacementActive();
122 insert_menu.subitems.emplace_back(
123 door_mode ? "Cancel Door Placement" : "Door (Normal)", ICON_MD_DOOR_FRONT,
124 [this, door_mode]() {
127 });
128
129 return insert_menu;
130}
131
132std::optional<zelda3::RoomObject>
134 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
135 return std::nullopt;
136 }
137
138 const ImGuiIO& io = ImGui::GetIO();
139 ImVec2 screen_position = io.MousePos;
140 if (!ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
141 if (const auto popup_anchor = canvas_.context_menu_open_screen_position();
142 popup_anchor.has_value()) {
143 screen_position = *popup_anchor;
144 }
145 }
146 const DungeonCanvasTransform transform(
148 const auto [canvas_x, canvas_y] =
149 transform.ScreenToRoomPixelCoordinates(screen_position);
150 if (!dungeon_coords::IsWithinBounds(canvas_x, canvas_y)) {
151 return std::nullopt;
152 }
153 const auto hovered_index = object_interaction_.entity_coordinator()
154 .tile_handler()
155 .GetEntityAtPosition(canvas_x, canvas_y);
156 if (!hovered_index.has_value()) {
157 return std::nullopt;
158 }
159
160 const auto& objects = (*rooms_)[room_id].GetTileObjects();
161 if (*hovered_index >= objects.size()) {
162 return std::nullopt;
163 }
164 return objects[*hovered_index];
165}
166
167std::vector<gui::CanvasMenuItem>
172
173std::vector<gui::CanvasMenuItem>
175 int room_id, std::optional<zelda3::RoomObject> context_object) {
176 auto& interaction = object_interaction_;
177 const auto selected = interaction.GetSelectedObjectIndices();
178 const bool has_entity_selection = interaction.HasEntitySelection();
179 const bool has_selection = !selected.empty() || has_entity_selection;
180 const bool single_selection = selected.size() == 1 && !has_entity_selection;
181 const bool has_clipboard = interaction.HasClipboardData();
182 const bool placing_object = interaction.IsObjectLoaded();
183 const bool valid_room =
184 rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms;
185 bool has_room_stream_object = false;
186 bool has_special_layer_object = false;
187 if (has_selection && valid_room) {
188 const auto& objects = (*rooms_)[room_id].GetTileObjects();
189 for (const size_t index : selected) {
190 if (index >= objects.size()) {
191 continue;
192 }
193 if (zelda3::UsesRoomObjectStream(objects[index])) {
194 has_room_stream_object = true;
195 } else {
196 has_special_layer_object = true;
197 }
198 }
199 }
200
201 std::vector<gui::CanvasMenuItem> items;
202
203 bool can_edit_selected_graphics = false;
204 std::function<void()> edit_selected_graphics;
205 bool can_edit_selected_tiles = false;
206 std::function<void()> edit_selected_tiles;
207 std::function<void()> check_selected_coverage;
208 if (single_selection && valid_room) {
209 auto& room = (*rooms_)[room_id];
210 const auto& objects = room.GetTileObjects();
211 if (selected[0] < objects.size()) {
212 const auto object = objects[selected[0]];
213 can_edit_selected_graphics = edit_graphics_callback_ != nullptr ||
215 edit_selected_graphics = [this, room_id, object]() {
217 edit_graphics_callback_(room_id, object);
218 } else if (show_room_graphics_callback_) {
220 }
221 };
222 can_edit_selected_tiles =
223 edit_object_tiles_callback_ != nullptr &&
225 edit_selected_tiles = [this, room_id, object]() {
227 edit_object_tiles_callback_(room_id, object);
228 }
229 };
231 check_selected_coverage = [this, room_id, object]() {
232 check_object_coverage_callback_(room_id, object);
233 };
234 }
235 }
236 }
237
238 auto enabled_if = [](bool enabled) {
239 return [enabled]() {
240 return enabled;
241 };
242 };
243 auto paste_item = [&]() {
245 "Paste", ICON_MD_CONTENT_PASTE,
246 [&interaction]() { (void)interaction.HandlePasteObjects(); },
247 gui::FormatCtrlShortcut(ImGuiKey_V));
248 item.enabled_condition = enabled_if(has_clipboard);
249 return item;
250 };
251 auto disabled_item = [](const char* label, const char* icon,
252 const char* shortcut = nullptr) {
254 item.label = label;
255 item.icon = icon;
256 if (shortcut != nullptr) {
257 item.shortcut = shortcut;
258 }
259 item.enabled_condition = []() {
260 return false;
261 };
262 return item;
263 };
264 auto layer_item = [&interaction](const char* label, int layer,
265 const char* shortcut) {
266 return gui::CanvasMenuItem(
267 label, ICON_MD_LAYERS,
268 [&interaction, layer]() { interaction.SendSelectedToLayer(layer); },
269 shortcut);
270 };
271
272 gui::CanvasMenuItem sample_item("Use Object as Brush", ICON_MD_COLORIZE,
273 [this, context_object]() {
274 if (context_object.has_value()) {
275 SetPreviewObject(*context_object);
276 }
277 });
278 sample_item.enabled_condition = enabled_if(context_object.has_value());
279 sample_item.separator_after = true;
280
281 if (has_selection) {
282 items.emplace_back(
283 "Cut", ICON_MD_CONTENT_CUT,
284 [&interaction]() {
285 if (interaction.HandleCopySelected().ok()) {
286 (void)interaction.HandleDeleteSelected();
287 }
288 },
289 gui::FormatCtrlShortcut(ImGuiKey_X));
290 items.emplace_back(
291 "Copy", ICON_MD_CONTENT_COPY,
292 [&interaction]() { (void)interaction.HandleCopySelected(); },
293 gui::FormatCtrlShortcut(ImGuiKey_C));
294 items.push_back(paste_item());
295 items.emplace_back("Duplicate", ICON_MD_FILE_COPY, [&interaction]() {
296 (void)interaction.HandleDuplicateSelected();
297 });
298 items.push_back(gui::CanvasMenuItem::Destructive(
299 "Delete", ICON_MD_DELETE,
300 [&interaction]() { (void)interaction.HandleDeleteSelected(); },
301 /*confirm=*/false, "Delete"));
302
303 items.back().separator_after = true;
304 if (!selected.empty()) {
305 gui::CanvasMenuItem arrange;
306 arrange.label = "Arrange";
307 arrange.icon = ICON_MD_LAYERS;
308 arrange.subitems.emplace_back(
309 "Bring to Front", ICON_MD_FLIP_TO_FRONT,
310 [&interaction]() { interaction.SendSelectedToFront(); },
311 gui::FormatCtrlShiftShortcut(ImGuiKey_RightBracket));
312 arrange.subitems.emplace_back(
313 "Send to Back", ICON_MD_FLIP_TO_BACK,
314 [&interaction]() { interaction.SendSelectedToBack(); },
315 gui::FormatCtrlShiftShortcut(ImGuiKey_LeftBracket));
316
317 arrange.subitems.emplace_back(
318 "Bring Forward", ICON_MD_ARROW_UPWARD,
319 [&interaction]() { interaction.BringSelectedForward(); },
320 gui::FormatCtrlShortcut(ImGuiKey_RightBracket));
321 arrange.subitems.emplace_back(
322 "Send Backward", ICON_MD_ARROW_DOWNWARD,
323 [&interaction]() { interaction.SendSelectedBackward(); },
324 gui::FormatCtrlShortcut(ImGuiKey_LeftBracket));
325 items.push_back(std::move(arrange));
326
327 gui::CanvasMenuItem placement;
328 placement.label = "Placement";
329 placement.icon = ICON_MD_LAYERS;
330
331 if (has_room_stream_object && !has_special_layer_object) {
332 placement.subitems.push_back(
333 layer_item("Object Stream: Primary", 0, "1"));
334 placement.subitems.push_back(
335 layer_item("Object Stream: BG2 Overlay", 1, "2"));
336 placement.subitems.push_back(
337 layer_item("Object Stream: BG1 Overlay", 2, "3"));
338 } else if (has_special_layer_object && !has_room_stream_object) {
339 placement.subitems.push_back(layer_item("Upper Layer (BG1)", 0, "1"));
340 placement.subitems.push_back(layer_item("Lower Layer (BG2)", 1, "2"));
341 } else {
342 placement.subitems.push_back(
343 layer_item("Placement 0: Primary / Upper Layer (BG1)", 0, "1"));
344 placement.subitems.push_back(
345 layer_item("Placement 1: BG2 Overlay / Lower Layer (BG2)", 1, "2"));
346 }
347 items.push_back(std::move(placement));
348 }
349 }
350
351 if (can_edit_selected_graphics) {
352 items.emplace_back("Edit Graphics...", ICON_MD_IMAGE,
353 std::move(edit_selected_graphics));
354 }
355 if (can_edit_selected_tiles) {
356 items.emplace_back("Edit Object Tiles...", ICON_MD_GRID_ON,
357 std::move(edit_selected_tiles));
358 }
359 if (check_selected_coverage) {
360 items.emplace_back("Check in Object Coverage", ICON_MD_FACT_CHECK,
361 std::move(check_selected_coverage));
362 }
363
364 if (!has_selection && !has_entity_selection) {
365 items.push_back(paste_item());
366 items.push_back(disabled_item("Delete", ICON_MD_DELETE, "Delete"));
367 }
368
369 items.push_back(std::move(sample_item));
370
371 if (placing_object) {
372 gui::CanvasMenuItem cancel_item(
373 "Cancel Placement", ICON_MD_CANCEL,
374 [&interaction]() { interaction.CancelPlacement(); }, "Esc");
375 if (!items.empty()) {
376 items.back().separator_after = true;
377 }
378 items.push_back(std::move(cancel_item));
379 }
380
381 return items;
382}
383
385 gui::CanvasMenuItem room_menu;
386 room_menu.label = "Room";
387 room_menu.icon = ICON_MD_HOME;
388 gui::CanvasMenuItem transfer_item(
389 "Clone / Import Room...", ICON_MD_CONTENT_COPY, [this, room_id]() {
392 });
393 room_menu.subitems.push_back(std::move(transfer_item));
395 room_menu.subitems.emplace_back(
396 "Apply Room to ROM", ICON_MD_SAVE,
397 [this, room_id]() { save_room_callback_(room_id); });
398 }
399 room_menu.subitems.emplace_back(
400 "Re-render Room", ICON_MD_REFRESH, [this, room_id]() {
401 if (rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms) {
402 (*rooms_)[room_id].RenderRoomGraphics();
403 }
404 });
405 if (rooms_ && !(*rooms_)[room_id].GetTileObjects().empty()) {
406 room_menu.subitems.push_back(gui::CanvasMenuItem::Destructive(
407 "Delete All Objects...", ICON_MD_DELETE_FOREVER,
409 }
410 return room_menu;
411}
412
414 gui::CanvasMenuItem report_menu;
415 report_menu.label = "Capture Issue";
416 report_menu.icon = ICON_MD_BUG_REPORT;
417 report_menu.subitems.emplace_back(
418 "Room Rendering...", ICON_MD_BUG_REPORT, [this, room_id]() {
419 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
420 return;
421 }
423 absl::StrFormat("Capture Room 0x%03X Rendering Issue", room_id),
424 absl::StrFormat("Room 0x%03X render mismatch", room_id),
425 "Dungeon Render Issue Report",
426 BuildDrawIssueReport((*rooms_)[room_id], room_id), room_id,
429 });
430 report_menu.subitems.emplace_back(
431 "Room Palette...", ICON_MD_PALETTE, [this, room_id]() {
432 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
433 return;
434 }
436 absl::StrFormat("Capture Room 0x%03X Palette Issue", room_id),
437 absl::StrFormat("Room 0x%03X palette mismatch", room_id),
438 "Dungeon Palette Issue Report",
439 BuildDrawIssueReport((*rooms_)[room_id], room_id), room_id,
441 });
444 report_menu.subitems.emplace_back(
445 "Current Selection...", ICON_MD_FACT_CHECK, [this, room_id]() {
446 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
447 return;
448 }
450 absl::StrFormat("Capture Selection Issue for Room 0x%03X",
451 room_id),
452 absl::StrFormat("Room 0x%03X selection or entity mismatch",
453 room_id),
454 "Dungeon Selection Issue Report",
455 BuildSelectionIssueReport((*rooms_)[room_id], room_id), room_id,
458 });
459 }
460 return report_menu;
461}
462
463std::optional<gui::CanvasMenuItem> DungeonCanvasViewer::BuildOpenContextMenu() {
464 gui::CanvasMenuItem open_menu;
465 open_menu.label = "Open";
466 open_menu.icon = ICON_MD_OPEN_IN_NEW;
468 open_menu.subitems.emplace_back("Room Matrix", ICON_MD_GRID_VIEW,
469 [this]() { show_room_matrix_callback_(); });
470 }
472 open_menu.subitems.emplace_back("Room Graphics", ICON_MD_IMAGE, [this]() {
474 });
475 }
477 open_menu.subitems.emplace_back("Door Editor", ICON_MD_DOOR_FRONT,
478 [this]() { show_door_editor_callback_(); });
479 }
481 open_menu.subitems.emplace_back("Entrance List", ICON_MD_LOGIN, [this]() {
483 });
484 }
485 if (open_menu.subitems.empty()) {
486 return std::nullopt;
487 }
488 return open_menu;
489}
490
492 int room_id) {
493 gui::CanvasMenuItem copy_export_menu;
494 copy_export_menu.label = "Copy / Export";
495 copy_export_menu.icon = ICON_MD_CONTENT_COPY;
496 copy_export_menu.subitems.emplace_back(
497 "Copy Room Summary", ICON_MD_ASSIGNMENT, [this, room_id]() {
498 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
499 return;
500 }
501 ImGui::SetClipboardText(
502 BuildRoomMetadataSummary((*rooms_)[room_id], room_id).c_str());
503 });
504 copy_export_menu.subitems.emplace_back(
505 "Copy Room ID", ICON_MD_CONTENT_COPY, [room_id]() {
506 ImGui::SetClipboardText(absl::StrFormat("0x%03X", room_id).c_str());
507 });
508 if (rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms) {
509 const std::string room_label = zelda3::GetRoomLabel(room_id);
510 copy_export_menu.subitems.emplace_back(
511 "Copy Room Name", ICON_MD_CONTENT_COPY,
512 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); });
513 }
514 copy_export_menu.subitems.emplace_back(
515 "Export Layout Template...", ICON_MD_FILE_DOWNLOAD, [this, room_id]() {
516 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
517 return;
518 }
519 auto& room = (*rooms_)[room_id];
520 auto result = zelda3::ExportRoomLayoutTemplate(room);
521 if (result.ok()) {
522 ImGui::SetClipboardText(result.value().c_str());
523 }
524 });
525 return copy_export_menu;
526}
527
529 int room_id) {
530 gui::CanvasMenuItem layers_menu;
531 layers_menu.label = "Layers";
532 layers_menu.icon = ICON_MD_VISIBILITY;
533
534 gui::CanvasMenuItem bg1_layout_item("BG1 Layout", [this, room_id]() {
535 auto& mgr = GetRoomLayerManager(room_id);
536 mgr.SetLayerVisible(zelda3::LayerType::BG1_Layout,
537 !mgr.IsLayerVisible(zelda3::LayerType::BG1_Layout));
538 });
539 bg1_layout_item.checked_condition = [this, room_id]() {
540 return GetRoomLayerManager(room_id).IsLayerVisible(
542 };
543 layers_menu.subitems.push_back(std::move(bg1_layout_item));
544
545 gui::CanvasMenuItem bg1_objects_item("BG1 Objects", [this, room_id]() {
546 auto& mgr = GetRoomLayerManager(room_id);
547 mgr.SetLayerVisible(zelda3::LayerType::BG1_Objects,
548 !mgr.IsLayerVisible(zelda3::LayerType::BG1_Objects));
549 });
550 bg1_objects_item.checked_condition = [this, room_id]() {
551 return GetRoomLayerManager(room_id).IsLayerVisible(
553 };
554 layers_menu.subitems.push_back(std::move(bg1_objects_item));
555
556 gui::CanvasMenuItem bg2_layout_item("BG2 Layout", [this, room_id]() {
557 auto& mgr = GetRoomLayerManager(room_id);
558 mgr.SetLayerVisible(zelda3::LayerType::BG2_Layout,
559 !mgr.IsLayerVisible(zelda3::LayerType::BG2_Layout));
560 });
561 bg2_layout_item.checked_condition = [this, room_id]() {
562 return GetRoomLayerManager(room_id).IsLayerVisible(
564 };
565 layers_menu.subitems.push_back(std::move(bg2_layout_item));
566
567 gui::CanvasMenuItem bg2_objects_item("BG2 Objects", [this, room_id]() {
568 auto& mgr = GetRoomLayerManager(room_id);
569 mgr.SetLayerVisible(zelda3::LayerType::BG2_Objects,
570 !mgr.IsLayerVisible(zelda3::LayerType::BG2_Objects));
571 });
572 bg2_objects_item.checked_condition = [this, room_id]() {
573 return GetRoomLayerManager(room_id).IsLayerVisible(
575 };
576 layers_menu.subitems.push_back(std::move(bg2_objects_item));
577
578 // The game hides the lower tilemap (BG2 here) in rooms whose layer settings
579 // put it on neither the main nor the sub screen; show it anyway for editing.
580 gui::CanvasMenuItem hidden_layers_item(
581 "Show Layers the Game Hides", ICON_MD_VISIBILITY_OFF, [this, room_id]() {
582 auto& mgr = GetRoomLayerManager(room_id);
583 mgr.SetShowHiddenLayers(!mgr.ShowHiddenLayers());
584 });
585 hidden_layers_item.enabled_condition = [this, room_id]() {
587 };
588 hidden_layers_item.checked_condition = [this, room_id]() {
589 return GetRoomLayerManager(room_id).ShowHiddenLayers();
590 };
591 layers_menu.subitems.push_back(std::move(hidden_layers_item));
592
593 gui::CanvasMenuItem sprites_item("Sprites", ICON_MD_PERSON, [this]() {
595 });
596 sprites_item.checked_condition = [this]() {
598 };
599 layers_menu.subitems.push_back(std::move(sprites_item));
600
601 gui::CanvasMenuItem pot_items_item("Pot Items", ICON_MD_INVENTORY, [this]() {
603 });
604 pot_items_item.checked_condition = [this]() {
606 };
607 layers_menu.subitems.push_back(std::move(pot_items_item));
608
609 return layers_menu;
610}
611
613 gui::CanvasMenuItem overlays_menu;
614 overlays_menu.label = "Overlays";
615 overlays_menu.icon = ICON_MD_LAYERS;
616
617 gui::CanvasMenuItem chest_contents_item("Chest contents", [this]() {
619 });
620 chest_contents_item.checked_condition = [this]() {
622 };
623 overlays_menu.subitems.push_back(std::move(chest_contents_item));
624
625 gui::CanvasMenuItem coordinates_item(
626 "Coordinates", ICON_MD_MY_LOCATION,
628 coordinates_item.checked_condition = [this]() {
630 };
631 overlays_menu.subitems.push_back(std::move(coordinates_item));
632
633 gui::CanvasMenuItem minecart_toggle(
634 "Minecart Tracks", ICON_MD_TRAIN,
636 minecart_toggle.enabled_condition = [this]() {
637 return minecart_track_panel_ != nullptr;
638 };
639 minecart_toggle.checked_condition = [this]() {
641 };
642 overlays_menu.subitems.push_back(std::move(minecart_toggle));
643
644 gui::CanvasMenuItem custom_collision_item(
645 "Custom Collision", ICON_MD_GRID_ON, [this]() {
647 });
648 custom_collision_item.checked_condition = [this]() {
650 };
651 overlays_menu.subitems.push_back(std::move(custom_collision_item));
652
653 gui::CanvasMenuItem track_collision_item(
654 "Track Collision", ICON_MD_LAYERS, [this]() {
656 });
657 track_collision_item.checked_condition = [this]() {
659 };
660 overlays_menu.subitems.push_back(std::move(track_collision_item));
661
662 gui::CanvasMenuItem camera_quadrants_item(
663 "Camera Quadrants", ICON_MD_GRID_VIEW, [this]() {
665 });
666 camera_quadrants_item.checked_condition = [this]() {
668 };
669 overlays_menu.subitems.push_back(std::move(camera_quadrants_item));
670
671 gui::CanvasMenuItem minecart_sprites_item(
672 "Minecart Sprites", ICON_MD_TRAIN, [this]() {
674 });
675 minecart_sprites_item.checked_condition = [this]() {
677 };
678 overlays_menu.subitems.push_back(std::move(minecart_sprites_item));
679
681 gui::CanvasMenuItem collision_legend_item(
682 "Collision Legend", ICON_MD_INFO, [this]() {
684 });
685 collision_legend_item.checked_condition = [this]() {
687 };
688 overlays_menu.subitems.push_back(std::move(collision_legend_item));
689 }
690
691 gui::CanvasMenuItem object_bounds_item(
692 "Object Bounds", ICON_MD_CROP_SQUARE,
694 object_bounds_item.checked_condition = [this]() {
695 return show_object_bounds_;
696 };
697 overlays_menu.subitems.push_back(std::move(object_bounds_item));
698
699 return overlays_menu;
700}
701
703 gui::CanvasMenuItem debug_menu;
704 debug_menu.label = "Debug";
705 debug_menu.icon = ICON_MD_BUG_REPORT;
706
707 debug_menu.subitems.push_back(gui::CanvasMenuItem(
708 "Room Info", ICON_MD_INFO,
710 debug_menu.subitems.back().checked_condition = [this]() {
712 };
713
714 debug_menu.subitems.push_back(gui::CanvasMenuItem(
715 "Texture Debug", ICON_MD_IMAGE,
716 [this]() { show_texture_debug_ = !show_texture_debug_; }));
717 debug_menu.subitems.back().checked_condition = [this]() {
718 return show_texture_debug_;
719 };
720
721 gui::CanvasMenuItem object_bounds_menu;
722 object_bounds_menu.label = "Object Bounds Filter";
723 object_bounds_menu.icon = ICON_MD_CROP_SQUARE;
724
725 object_bounds_menu.subitems.push_back(
726 gui::CanvasMenuItem("Type 1 (0x00-0xFF)", [this]() {
729 }));
730 object_bounds_menu.subitems.back().checked_condition = [this]() {
732 };
733 object_bounds_menu.subitems.push_back(
734 gui::CanvasMenuItem("Type 2 (0x100-0x1FF)", [this]() {
737 }));
738 object_bounds_menu.subitems.back().checked_condition = [this]() {
740 };
741 object_bounds_menu.subitems.push_back(
742 gui::CanvasMenuItem("Type 3 (0xF00-0xFFF)", [this]() {
745 }));
746 object_bounds_menu.subitems.back().checked_condition = [this]() {
748 };
749
751 sep.label = "---";
752 sep.enabled_condition = []() {
753 return false;
754 };
755 object_bounds_menu.subitems.push_back(std::move(sep));
756
757 object_bounds_menu.subitems.push_back(
758 gui::CanvasMenuItem("Primary / Upper layer (BG1)", [this]() {
761 }));
762 object_bounds_menu.subitems.back().checked_condition = [this]() {
764 };
765 object_bounds_menu.subitems.push_back(
766 gui::CanvasMenuItem("BG2 overlay / Lower layer (BG2)", [this]() {
769 }));
770 object_bounds_menu.subitems.back().checked_condition = [this]() {
772 };
773 object_bounds_menu.subitems.push_back(
774 gui::CanvasMenuItem("BG1 overlay (room stream only)", [this]() {
777 }));
778 object_bounds_menu.subitems.back().checked_condition = [this]() {
780 };
781 debug_menu.subitems.push_back(std::move(object_bounds_menu));
782
783 debug_menu.subitems.push_back(
785 [this]() { show_layer_info_ = !show_layer_info_; }));
786 debug_menu.subitems.back().checked_condition = [this]() {
787 return show_layer_info_;
788 };
789
790 debug_menu.subitems.push_back(
791 gui::CanvasMenuItem("Force Reload", ICON_MD_REFRESH, [this, room_id]() {
792 if (rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms) {
793 (*rooms_)[room_id].ReloadGraphics(current_entrance_blockset_);
794 }
795 }));
796
797 debug_menu.subitems.push_back(
798 gui::CanvasMenuItem("Log Room State", ICON_MD_PRINT, [this, room_id]() {
799 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
800 return;
801 }
802 const auto& room = (*rooms_)[room_id];
803 LOG_DEBUG("DungeonDebug", "=== Room %03X Debug ===", room_id);
804 LOG_DEBUG("DungeonDebug", "Blockset: %d, Palette: %d, Layout: %d",
805 room.blockset(), room.palette(), room.layout_id());
806 LOG_DEBUG("DungeonDebug", "Objects: %zu, Sprites: %zu",
807 room.GetTileObjects().size(), room.GetSprites().size());
808 LOG_DEBUG("DungeonDebug", "BG1: %dx%d, BG2: %dx%d",
809 room.bg1_buffer().bitmap().width(),
810 room.bg1_buffer().bitmap().height(),
811 room.bg2_buffer().bitmap().width(),
812 room.bg2_buffer().bitmap().height());
813 }));
814
815 return debug_menu;
816}
817
818} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:187
std::pair< int, int > ScreenToRoomPixelCoordinates(ImVec2 screen) const
std::optional< zelda3::RoomObject > GetObjectUnderContextCursor(int room_id)
gui::CanvasMenuItem BuildLayerVisibilityContextMenu(int room_id)
DungeonRoomTransferEditorState room_transfer_state_
gui::CanvasMenuItem BuildRoomContextMenu(int room_id)
gui::CanvasMenuItem BuildDebugContextMenu(int room_id)
std::string BuildDrawIssueReport(const zelda3::Room &room, int room_id)
std::function< void(int, const zelda3::RoomObject &) check_object_coverage_callback_)
std::vector< gui::CanvasMenuItem > BuildSelectionContextMenuItems(int room_id)
std::function< void()> show_door_editor_callback_
std::function< void(int, const zelda3::RoomObject &) edit_object_tiles_callback_)
std::function< void(int, const zelda3::RoomObject &) edit_graphics_callback_)
std::function< void()> show_entrance_list_callback_
DungeonObjectInteraction object_interaction_
void OpenIssueReportPopup(const std::string &title, const std::string &summary, const std::string &kind_label, const std::string &diagnostics, int room_id, int default_category_index)
std::string BuildRoomMetadataSummary(const zelda3::Room &room, int room_id) const
gui::CanvasMenuItem BuildReportContextMenu(int room_id)
std::function< void()> show_object_panel_callback_
gfx::Bitmap * PrepareRoomCompositeBitmap(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
std::function< void()> show_room_matrix_callback_
std::function< void()> show_room_graphics_callback_
std::function< void()> show_item_panel_callback_
std::function< void()> show_sprite_panel_callback_
std::optional< gui::CanvasMenuItem > BuildOpenContextMenu()
MinecartTrackEditorPanel * minecart_track_panel_
std::string BuildSelectionIssueReport(const zelda3::Room &room, int room_id) const
gui::CanvasMenuItem BuildCopyExportContextMenu(int room_id)
std::function< void(int)> save_room_callback_
std::vector< size_t > GetSelectedObjectIndices() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
void SetDoorPlacementMode(bool enabled, zelda3::DoorType type=zelda3::DoorType::NormalDoor)
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
auto global_scale() const
Definition canvas.h:413
void ClearContextMenuItems()
Definition canvas.cc:994
void AddContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:971
void SetZoomToFit(const gfx::Bitmap &bitmap)
Definition canvas.cc:1014
auto zero_point() const
Definition canvas.h:364
auto scrolling() const
Definition canvas.h:366
std::optional< ImVec2 > context_menu_open_screen_position() const
Definition canvas.h:211
static bool IsEditableStandardObject(int16_t object_id)
bool IsLayerVisible(LayerType layer) const
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_COLORIZE
Definition icons.h:441
#define ICON_MD_MY_LOCATION
Definition icons.h:1270
#define ICON_MD_CONTENT_CUT
Definition icons.h:466
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_FIT_SCREEN
Definition icons.h:781
#define ICON_MD_TRAIN
Definition icons.h:2005
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:744
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_ZOOM_OUT
Definition icons.h:2196
#define ICON_MD_FLIP_TO_FRONT
Definition icons.h:802
#define ICON_MD_ARROW_DOWNWARD
Definition icons.h:180
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_WIDGETS
Definition icons.h:2156
#define ICON_MD_MORE_HORIZ
Definition icons.h:1241
#define ICON_MD_LOGIN
Definition icons.h:1146
#define ICON_MD_CONTENT_PASTE
Definition icons.h:467
#define ICON_MD_HOME
Definition icons.h:953
#define ICON_MD_GRID_ON
Definition icons.h:896
#define ICON_MD_FACT_CHECK
Definition icons.h:721
#define ICON_MD_VISIBILITY_OFF
Definition icons.h:2102
#define ICON_MD_LAYERS
Definition icons.h:1068
#define ICON_MD_INVENTORY
Definition icons.h:1011
#define ICON_MD_DOOR_FRONT
Definition icons.h:613
#define ICON_MD_CROP_SQUARE
Definition icons.h:500
#define ICON_MD_FILE_COPY
Definition icons.h:743
#define ICON_MD_ARROW_UPWARD
Definition icons.h:189
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_PERSON
Definition icons.h:1415
#define ICON_MD_ASSIGNMENT
Definition icons.h:194
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_DELETE_FOREVER
Definition icons.h:531
#define ICON_MD_PRINT
Definition icons.h:1515
#define ICON_MD_FLIP_TO_BACK
Definition icons.h:801
#define ICON_MD_ADD_CIRCLE
Definition icons.h:95
#define LOG_DEBUG(category, format,...)
Definition log.h:133
bool IsWithinBounds(int canvas_x, int canvas_y, int margin=0)
Check if coordinates are within room bounds.
Editors are the view controllers for the application.
DungeonIssueCategory GetDefaultSelectionIssueCategory(const DungeonObjectInteraction &object_interaction)
constexpr int ToIssueCategoryIndex(DungeonIssueCategory category)
std::string FormatCtrlShiftShortcut(ImGuiKey key)
Convenience function for Ctrl+Shift+key shortcuts.
std::string FormatCtrlShortcut(ImGuiKey key)
Convenience function for Ctrl+key shortcuts.
@ NormalDoor
Normal door (upper layer)
std::string GetRoomLabel(int id)
Convenience function to get a room label.
absl::StatusOr< std::string > ExportRoomLayoutTemplate(const Room &room)
Export a room's layout as a JSON template string.
bool UsesRoomObjectStream(const RoomObject &object)
constexpr int kNumberOfRooms
Declarative menu item definition.
Definition canvas_menu.h:64
std::function< bool()> checked_condition
Definition canvas_menu.h:95
std::vector< CanvasMenuItem > subitems
Definition canvas_menu.h:98
std::function< bool()> enabled_condition
Definition canvas_menu.h:85
static CanvasMenuItem Destructive(const std::string &lbl, const std::string &ico, std::function< void()> cb, bool confirm=true, const std::string &sc="")