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"
19
20namespace yaze::editor {
21
22namespace {
23
24std::pair<int, int> ContextMenuRoomPosition(const gui::Canvas& canvas) {
25 ImVec2 screen_position = ImGui::GetIO().MousePos;
26 if (!ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
27 if (const auto popup_anchor = canvas.context_menu_open_screen_position();
28 popup_anchor.has_value()) {
29 screen_position = *popup_anchor;
30 }
31 }
32 return DungeonCanvasTransform(canvas.zero_point(), canvas.scrolling(),
33 canvas.global_scale())
34 .ScreenToRoomPixelCoordinates(screen_position);
35}
36
37} // namespace
38
44
47 return;
48 }
49
50 for (auto& item : BuildSelectionContextMenuItems(room_id)) {
51 canvas_.AddContextMenuItem(std::move(item));
52 }
53 auto insert_menu = BuildInsertContextMenu();
54 insert_menu.separator_after = true;
55 canvas_.AddContextMenuItem(insert_menu);
56}
57
59 if (!rooms_ || !rom_ || !rom_->is_loaded()) {
60 return;
61 }
62
64 gui::CanvasMenuItem inspector("Inspector...", ICON_MD_TUNE, [this]() {
66 });
67 inspector.separator_after = true;
68 canvas_.AddContextMenuItem(std::move(inspector));
69 }
71 if (auto open_menu = BuildOpenContextMenu(); open_menu.has_value()) {
72 canvas_.AddContextMenuItem(std::move(*open_menu));
73 }
75 view.label = "View";
77 view.subitems.emplace_back("Fit room", ICON_MD_FIT_SCREEN, [this, room_id] {
78 if (auto* bitmap = PrepareRoomCompositeBitmap(room_id)) {
79 canvas_.SetZoomToFit(*bitmap);
82 }
83 });
84 view.subitems.emplace_back("Zoom in", ICON_MD_ZOOM_IN, [this] {
87 });
88 view.subitems.emplace_back("Zoom out", ICON_MD_ZOOM_OUT, [this] {
91 });
92 view.subitems.back().separator_after = true;
93 view.subitems.push_back(BuildLayerVisibilityContextMenu(room_id));
94 view.subitems.push_back(BuildOverlayContextMenu());
95 canvas_.AddContextMenuItem(std::move(view));
97 auto debug = BuildDebugContextMenu(room_id);
98 debug.subitems.back().separator_after = true;
99 debug.subitems.push_back(BuildReportContextMenu(room_id));
100 canvas_.AddContextMenuItem(std::move(debug));
101}
102
104 auto enabled_if = [](bool enabled) {
105 return [enabled]() {
106 return enabled;
107 };
108 };
109
110 gui::CanvasMenuItem insert_menu;
111 insert_menu.label = "Insert";
112 insert_menu.icon = ICON_MD_ADD_CIRCLE;
113
114 gui::CanvasMenuItem insert_object_item("Object...", ICON_MD_WIDGETS,
115 [this]() {
118 }
119 });
120 insert_object_item.enabled_condition =
121 enabled_if(show_object_panel_callback_ != nullptr);
122 insert_menu.subitems.push_back(std::move(insert_object_item));
123
124 gui::CanvasMenuItem insert_sprite_item("Sprite...", ICON_MD_PERSON, [this]() {
127 }
128 });
129 insert_sprite_item.enabled_condition =
130 enabled_if(show_sprite_panel_callback_ != nullptr);
131 insert_menu.subitems.push_back(std::move(insert_sprite_item));
132
133 gui::CanvasMenuItem insert_item_item("Item...", ICON_MD_INVENTORY, [this]() {
136 }
137 });
138 insert_item_item.enabled_condition =
139 enabled_if(show_item_panel_callback_ != nullptr);
140 insert_menu.subitems.push_back(std::move(insert_item_item));
141
142 const bool door_mode = object_interaction_.IsDoorPlacementActive();
143 insert_menu.subitems.emplace_back(
144 door_mode ? "Cancel Door Placement" : "Door (Normal)", ICON_MD_DOOR_FRONT,
145 [this, door_mode]() {
148 });
149
150 return insert_menu;
151}
152
153std::optional<zelda3::RoomObject>
155 const auto index = GetObjectIndexUnderContextCursor(room_id);
156 return index ? std::optional((*rooms_)[room_id].GetTileObjects()[*index])
157 : std::nullopt;
158}
159
161 int room_id) {
162 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
163 return std::nullopt;
164 }
165
166 const auto [canvas_x, canvas_y] = ContextMenuRoomPosition(canvas_);
167 if (!dungeon_coords::IsWithinBounds(canvas_x, canvas_y)) {
168 return std::nullopt;
169 }
170 const auto hovered_index = object_interaction_.entity_coordinator()
171 .tile_handler()
172 .GetEntityAtPosition(canvas_x, canvas_y);
173 if (!hovered_index.has_value()) {
174 return std::nullopt;
175 }
176
177 const auto& objects = (*rooms_)[room_id].GetTileObjects();
178 if (*hovered_index >= objects.size()) {
179 return std::nullopt;
180 }
181 return hovered_index;
182}
183
184std::vector<gui::CanvasMenuItem>
189
190std::vector<gui::CanvasMenuItem>
192 int room_id, std::optional<zelda3::RoomObject> context_object) {
193 auto& interaction = object_interaction_;
194 const auto selected = interaction.GetSelectedObjectIndices();
195 const bool has_entity_selection = interaction.HasEntitySelection();
196 const bool has_selection = !selected.empty() || has_entity_selection;
197 const bool single_selection = selected.size() == 1 && !has_entity_selection;
198 const bool has_clipboard = interaction.HasClipboardData();
199 const bool placing_object = interaction.IsObjectLoaded();
200 const bool valid_room =
201 rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms;
202 bool has_room_stream_object = false;
203 bool has_special_layer_object = false;
204 if (has_selection && valid_room) {
205 const auto& objects = (*rooms_)[room_id].GetTileObjects();
206 for (const size_t index : selected) {
207 if (index >= objects.size()) {
208 continue;
209 }
210 if (zelda3::UsesRoomObjectStream(objects[index])) {
211 has_room_stream_object = true;
212 } else {
213 has_special_layer_object = true;
214 }
215 }
216 }
217
218 std::vector<gui::CanvasMenuItem> items;
219
220 if (valid_room) {
221 auto& room = (*rooms_)[room_id];
222 std::optional<size_t> chest_index;
223 if (context_object &&
224 zelda3::IsStatefulChestObjectId(context_object->id_)) {
225 if (const auto index = GetObjectIndexUnderContextCursor(room_id)) {
226 chest_index =
227 zelda3::ChestIndexForObject(room.GetTileObjects(), *index);
228 }
229 }
230 if (!chest_index && single_selection) {
231 chest_index =
232 zelda3::ChestIndexForObject(room.GetTileObjects(), selected.front());
233 }
234 if (chest_index) {
235 gui::CanvasMenuItem edit_chest(
236 "Edit Chest...", ICON_MD_EDIT,
237 [this, room_id, index = *chest_index]() {
238 const auto status = OpenChestEditor(room_id, index);
240 status.ok() ? "" : std::string(status.message());
241 });
242 edit_chest.enabled_condition = [this, room_id, index = *chest_index]() {
243 const auto* room = rooms_ ? rooms_->GetIfLoaded(room_id) : nullptr;
245 chest_edit_callback_ && room && room->AreChestsLoaded() &&
246 index < room->GetChests().size();
247 };
248 edit_chest.tooltip = "Edit this chest's reward and type.";
249 edit_chest.separator_after = true;
250 items.push_back(std::move(edit_chest));
251 }
252 }
253
254 bool can_edit_selected_graphics = false;
255 std::function<void()> edit_selected_graphics;
256 bool can_edit_selected_tiles = false;
257 std::function<void()> edit_selected_tiles;
258 std::function<void()> check_selected_coverage;
259 if (single_selection && valid_room) {
260 auto& room = (*rooms_)[room_id];
261 const auto& objects = room.GetTileObjects();
262 if (selected[0] < objects.size()) {
263 const auto object = objects[selected[0]];
264 can_edit_selected_graphics = edit_graphics_callback_ != nullptr ||
266 edit_selected_graphics = [this, room_id, object]() {
268 edit_graphics_callback_(room_id, object);
269 } else if (show_room_graphics_callback_) {
271 }
272 };
273 can_edit_selected_tiles =
274 edit_object_tiles_callback_ != nullptr &&
276 edit_selected_tiles = [this, room_id, object]() {
278 edit_object_tiles_callback_(room_id, object);
279 }
280 };
282 check_selected_coverage = [this, room_id, object]() {
283 check_object_coverage_callback_(room_id, object);
284 };
285 }
286 }
287 }
288
289 auto enabled_if = [](bool enabled) {
290 return [enabled]() {
291 return enabled;
292 };
293 };
294 auto paste_item = [&]() {
295 const auto [room_x, room_y] =
296 has_clipboard ? ContextMenuRoomPosition(canvas_) : std::pair{0, 0};
298 "Paste", ICON_MD_CONTENT_PASTE,
299 [&interaction, room_x = room_x, room_y = room_y]() {
300 (void)interaction.HandlePasteObjectsAt(room_x, room_y);
301 },
302 gui::FormatCtrlShortcut(ImGuiKey_V));
303 item.enabled_condition = enabled_if(has_clipboard);
304 return item;
305 };
306 auto disabled_item = [](const char* label, const char* icon,
307 const char* shortcut = nullptr) {
309 item.label = label;
310 item.icon = icon;
311 if (shortcut != nullptr) {
312 item.shortcut = shortcut;
313 }
314 item.enabled_condition = []() {
315 return false;
316 };
317 return item;
318 };
319 auto layer_item = [&interaction](const char* label, int layer,
320 const char* shortcut) {
321 return gui::CanvasMenuItem(
322 label, ICON_MD_LAYERS,
323 [&interaction, layer]() { interaction.SendSelectedToLayer(layer); },
324 shortcut);
325 };
326
327 gui::CanvasMenuItem sample_item("Use Object as Brush", ICON_MD_COLORIZE,
328 [this, context_object]() {
329 if (context_object.has_value()) {
330 SetPreviewObject(*context_object);
331 }
332 });
333 sample_item.enabled_condition = enabled_if(context_object.has_value());
334 sample_item.separator_after = true;
335
336 if (has_selection) {
337 items.emplace_back(
338 "Cut", ICON_MD_CONTENT_CUT,
339 [&interaction]() {
340 if (interaction.HandleCopySelected().ok()) {
341 (void)interaction.HandleDeleteSelected();
342 }
343 },
344 gui::FormatCtrlShortcut(ImGuiKey_X));
345 items.emplace_back(
346 "Copy", ICON_MD_CONTENT_COPY,
347 [&interaction]() { (void)interaction.HandleCopySelected(); },
348 gui::FormatCtrlShortcut(ImGuiKey_C));
349 items.push_back(paste_item());
350 items.emplace_back("Duplicate", ICON_MD_FILE_COPY, [&interaction]() {
351 (void)interaction.HandleDuplicateSelected();
352 });
353 items.push_back(gui::CanvasMenuItem::Destructive(
354 "Delete", ICON_MD_DELETE,
355 [&interaction]() { (void)interaction.HandleDeleteSelected(); },
356 /*confirm=*/false, "Delete"));
357
358 items.back().separator_after = true;
359 if (!selected.empty()) {
360 gui::CanvasMenuItem arrange;
361 arrange.label = "Arrange";
362 arrange.icon = ICON_MD_LAYERS;
363 arrange.subitems.emplace_back(
364 "Bring to Front", ICON_MD_FLIP_TO_FRONT,
365 [&interaction]() { interaction.SendSelectedToFront(); },
366 gui::FormatCtrlShiftShortcut(ImGuiKey_RightBracket));
367 arrange.subitems.emplace_back(
368 "Send to Back", ICON_MD_FLIP_TO_BACK,
369 [&interaction]() { interaction.SendSelectedToBack(); },
370 gui::FormatCtrlShiftShortcut(ImGuiKey_LeftBracket));
371
372 arrange.subitems.emplace_back(
373 "Bring Forward", ICON_MD_ARROW_UPWARD,
374 [&interaction]() { interaction.BringSelectedForward(); },
375 gui::FormatCtrlShortcut(ImGuiKey_RightBracket));
376 arrange.subitems.emplace_back(
377 "Send Backward", ICON_MD_ARROW_DOWNWARD,
378 [&interaction]() { interaction.SendSelectedBackward(); },
379 gui::FormatCtrlShortcut(ImGuiKey_LeftBracket));
380 items.push_back(std::move(arrange));
381
382 gui::CanvasMenuItem placement;
383 placement.label = "Placement";
384 placement.icon = ICON_MD_LAYERS;
385
386 if (has_room_stream_object && !has_special_layer_object) {
387 placement.subitems.push_back(
388 layer_item("Object Stream: Primary", 0, "1"));
389 placement.subitems.push_back(
390 layer_item("Object Stream: BG2 Overlay", 1, "2"));
391 placement.subitems.push_back(
392 layer_item("Object Stream: BG1 Overlay", 2, "3"));
393 } else if (has_special_layer_object && !has_room_stream_object) {
394 placement.subitems.push_back(layer_item("Upper Layer (BG1)", 0, "1"));
395 placement.subitems.push_back(layer_item("Lower Layer (BG2)", 1, "2"));
396 } else {
397 placement.subitems.push_back(
398 layer_item("Placement 0: Primary / Upper Layer (BG1)", 0, "1"));
399 placement.subitems.push_back(
400 layer_item("Placement 1: BG2 Overlay / Lower Layer (BG2)", 1, "2"));
401 }
402 items.push_back(std::move(placement));
403 }
404 }
405
406 if (can_edit_selected_graphics) {
407 items.emplace_back("Edit Graphics...", ICON_MD_IMAGE,
408 std::move(edit_selected_graphics));
409 }
410 if (can_edit_selected_tiles) {
411 items.emplace_back("Edit Object Tiles...", ICON_MD_GRID_ON,
412 std::move(edit_selected_tiles));
413 }
414 if (check_selected_coverage) {
415 items.emplace_back("Check in Object Coverage", ICON_MD_FACT_CHECK,
416 std::move(check_selected_coverage));
417 }
418
419 if (!has_selection && !has_entity_selection) {
420 items.push_back(paste_item());
421 items.push_back(disabled_item("Delete", ICON_MD_DELETE, "Delete"));
422 }
423
424 items.push_back(std::move(sample_item));
425
426 if (placing_object) {
427 gui::CanvasMenuItem cancel_item(
428 "Cancel Placement", ICON_MD_CANCEL,
429 [&interaction]() { interaction.CancelPlacement(); }, "Esc");
430 if (!items.empty()) {
431 items.back().separator_after = true;
432 }
433 items.push_back(std::move(cancel_item));
434 }
435
436 return items;
437}
438
440 gui::CanvasMenuItem room_menu;
441 room_menu.label = "Room";
442 room_menu.icon = ICON_MD_HOME;
443 gui::CanvasMenuItem transfer_item(
444 "Clone / Import Room...", ICON_MD_CONTENT_COPY, [this, room_id]() {
447 });
448 room_menu.subitems.push_back(std::move(transfer_item));
450 room_menu.subitems.emplace_back(
451 "Apply Room to ROM", ICON_MD_SAVE,
452 [this, room_id]() { save_room_callback_(room_id); });
453 }
454 room_menu.subitems.emplace_back(
455 "Re-render Room", ICON_MD_REFRESH, [this, room_id]() {
456 if (rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms) {
457 (*rooms_)[room_id].RenderRoomGraphics();
458 }
459 });
460 if (rooms_ && !(*rooms_)[room_id].GetTileObjects().empty()) {
461 room_menu.subitems.push_back(gui::CanvasMenuItem::Destructive(
462 "Delete All Objects...", ICON_MD_DELETE_FOREVER,
464 }
465 return room_menu;
466}
467
469 gui::CanvasMenuItem report_menu;
470 report_menu.label = "Capture Issue";
471 report_menu.icon = ICON_MD_BUG_REPORT;
472 report_menu.subitems.emplace_back(
473 "Room Rendering...", ICON_MD_BUG_REPORT, [this, room_id]() {
474 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
475 return;
476 }
478 absl::StrFormat("Capture Room 0x%03X Rendering Issue", room_id),
479 absl::StrFormat("Room 0x%03X render mismatch", room_id),
480 "Dungeon Render Issue Report",
481 BuildDrawIssueReport((*rooms_)[room_id], room_id), room_id,
484 });
485 report_menu.subitems.emplace_back(
486 "Room Palette...", ICON_MD_PALETTE, [this, room_id]() {
487 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
488 return;
489 }
491 absl::StrFormat("Capture Room 0x%03X Palette Issue", room_id),
492 absl::StrFormat("Room 0x%03X palette mismatch", room_id),
493 "Dungeon Palette Issue Report",
494 BuildDrawIssueReport((*rooms_)[room_id], room_id), room_id,
496 });
499 report_menu.subitems.emplace_back(
500 "Current Selection...", ICON_MD_FACT_CHECK, [this, room_id]() {
501 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
502 return;
503 }
505 absl::StrFormat("Capture Selection Issue for Room 0x%03X",
506 room_id),
507 absl::StrFormat("Room 0x%03X selection or entity mismatch",
508 room_id),
509 "Dungeon Selection Issue Report",
510 BuildSelectionIssueReport((*rooms_)[room_id], room_id), room_id,
513 });
514 }
515 return report_menu;
516}
517
518std::optional<gui::CanvasMenuItem> DungeonCanvasViewer::BuildOpenContextMenu() {
519 gui::CanvasMenuItem open_menu;
520 open_menu.label = "Open";
521 open_menu.icon = ICON_MD_OPEN_IN_NEW;
523 open_menu.subitems.emplace_back("Room List", ICON_MD_LIST,
524 [this]() { show_room_list_callback_(); });
525 }
527 open_menu.subitems.emplace_back("Room Matrix", ICON_MD_GRID_VIEW,
528 [this]() { show_room_matrix_callback_(); });
529 }
531 open_menu.subitems.emplace_back("Room Graphics", ICON_MD_IMAGE, [this]() {
533 });
534 }
536 open_menu.subitems.emplace_back("Door Editor", ICON_MD_DOOR_FRONT,
537 [this]() { show_door_editor_callback_(); });
538 }
540 open_menu.subitems.emplace_back("Entrance List", ICON_MD_LOGIN, [this]() {
542 });
543 }
544 if (open_menu.subitems.empty()) {
545 return std::nullopt;
546 }
547 return open_menu;
548}
549
551 int room_id) {
552 gui::CanvasMenuItem copy_export_menu;
553 copy_export_menu.label = "Export";
554 copy_export_menu.icon = ICON_MD_CONTENT_COPY;
555 copy_export_menu.subitems.emplace_back(
556 "Copy Room Summary", ICON_MD_ASSIGNMENT, [this, room_id]() {
557 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
558 return;
559 }
560 ImGui::SetClipboardText(
561 BuildRoomMetadataSummary((*rooms_)[room_id], room_id).c_str());
562 });
563 copy_export_menu.subitems.emplace_back(
564 "Copy Room ID", ICON_MD_CONTENT_COPY, [room_id]() {
565 ImGui::SetClipboardText(absl::StrFormat("0x%03X", room_id).c_str());
566 });
567 if (rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms) {
568 const std::string room_label = zelda3::GetRoomLabel(room_id);
569 copy_export_menu.subitems.emplace_back(
570 "Copy Room Name", ICON_MD_CONTENT_COPY,
571 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); });
572 }
573 copy_export_menu.subitems.emplace_back(
574 "Copy Layout Template", ICON_MD_CONTENT_COPY, [this, room_id]() {
575 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
576 return;
577 }
578 auto& room = (*rooms_)[room_id];
579 auto result = zelda3::ExportRoomLayoutTemplate(room);
580 if (result.ok()) {
581 ImGui::SetClipboardText(result.value().c_str());
582 }
583 });
584 return copy_export_menu;
585}
586
588 int room_id) {
589 gui::CanvasMenuItem layers_menu;
590 layers_menu.label = "Layers";
591 layers_menu.icon = ICON_MD_VISIBILITY;
592
593 gui::CanvasMenuItem bg1_layout_item("BG1 Layout", [this, room_id]() {
594 auto& mgr = GetRoomLayerManager(room_id);
595 mgr.SetLayerVisible(zelda3::LayerType::BG1_Layout,
596 !mgr.IsLayerVisible(zelda3::LayerType::BG1_Layout));
597 });
598 bg1_layout_item.checked_condition = [this, room_id]() {
599 return GetRoomLayerManager(room_id).IsLayerVisible(
601 };
602 layers_menu.subitems.push_back(std::move(bg1_layout_item));
603
604 gui::CanvasMenuItem bg1_objects_item("BG1 Objects", [this, room_id]() {
605 auto& mgr = GetRoomLayerManager(room_id);
606 mgr.SetLayerVisible(zelda3::LayerType::BG1_Objects,
607 !mgr.IsLayerVisible(zelda3::LayerType::BG1_Objects));
608 });
609 bg1_objects_item.checked_condition = [this, room_id]() {
610 return GetRoomLayerManager(room_id).IsLayerVisible(
612 };
613 layers_menu.subitems.push_back(std::move(bg1_objects_item));
614
615 gui::CanvasMenuItem bg2_layout_item("BG2 Layout", [this, room_id]() {
616 auto& mgr = GetRoomLayerManager(room_id);
617 mgr.SetLayerVisible(zelda3::LayerType::BG2_Layout,
618 !mgr.IsLayerVisible(zelda3::LayerType::BG2_Layout));
619 });
620 bg2_layout_item.checked_condition = [this, room_id]() {
621 return GetRoomLayerManager(room_id).IsLayerVisible(
623 };
624 layers_menu.subitems.push_back(std::move(bg2_layout_item));
625
626 gui::CanvasMenuItem bg2_objects_item("BG2 Objects", [this, room_id]() {
627 auto& mgr = GetRoomLayerManager(room_id);
628 mgr.SetLayerVisible(zelda3::LayerType::BG2_Objects,
629 !mgr.IsLayerVisible(zelda3::LayerType::BG2_Objects));
630 });
631 bg2_objects_item.checked_condition = [this, room_id]() {
632 return GetRoomLayerManager(room_id).IsLayerVisible(
634 };
635 layers_menu.subitems.push_back(std::move(bg2_objects_item));
636
637 // The game hides the lower tilemap (BG2 here) in rooms whose layer settings
638 // put it on neither the main nor the sub screen; show it anyway for editing.
639 gui::CanvasMenuItem hidden_layers_item(
640 "Show Layers the Game Hides", ICON_MD_VISIBILITY_OFF, [this, room_id]() {
641 auto& mgr = GetRoomLayerManager(room_id);
642 mgr.SetShowHiddenLayers(!mgr.ShowHiddenLayers());
643 });
644 hidden_layers_item.enabled_condition = [this, room_id]() {
646 };
647 hidden_layers_item.checked_condition = [this, room_id]() {
648 return GetRoomLayerManager(room_id).ShowHiddenLayers();
649 };
650 layers_menu.subitems.push_back(std::move(hidden_layers_item));
651
652 gui::CanvasMenuItem sprites_item("Sprites", ICON_MD_PERSON, [this]() {
654 });
655 sprites_item.checked_condition = [this]() {
657 };
658 layers_menu.subitems.push_back(std::move(sprites_item));
659
660 gui::CanvasMenuItem pot_items_item("Pot Items", ICON_MD_INVENTORY, [this]() {
662 });
663 pot_items_item.checked_condition = [this]() {
665 };
666 layers_menu.subitems.push_back(std::move(pot_items_item));
667
668 return layers_menu;
669}
670
672 gui::CanvasMenuItem overlays_menu;
673 overlays_menu.label = "Overlays";
674 overlays_menu.icon = ICON_MD_LAYERS;
675
676 gui::CanvasMenuItem chest_contents_item("Chest contents", [this]() {
678 });
679 chest_contents_item.checked_condition = [this]() {
681 };
682 overlays_menu.subitems.push_back(std::move(chest_contents_item));
683
684 gui::CanvasMenuItem coordinates_item(
685 "Coordinates", ICON_MD_MY_LOCATION,
687 coordinates_item.checked_condition = [this]() {
689 };
690 overlays_menu.subitems.push_back(std::move(coordinates_item));
691
692 gui::CanvasMenuItem minecart_toggle(
693 "Minecart Tracks", ICON_MD_TRAIN,
695 minecart_toggle.enabled_condition = [this]() {
696 return minecart_track_panel_ != nullptr;
697 };
698 minecart_toggle.checked_condition = [this]() {
700 };
701 overlays_menu.subitems.push_back(std::move(minecart_toggle));
702
703 gui::CanvasMenuItem custom_collision_item(
704 "Custom Collision", ICON_MD_GRID_ON, [this]() {
706 });
707 custom_collision_item.checked_condition = [this]() {
709 };
710 overlays_menu.subitems.push_back(std::move(custom_collision_item));
711
712 gui::CanvasMenuItem track_collision_item(
713 "Track Collision", ICON_MD_LAYERS, [this]() {
715 });
716 track_collision_item.checked_condition = [this]() {
718 };
719 overlays_menu.subitems.push_back(std::move(track_collision_item));
720
721 gui::CanvasMenuItem camera_quadrants_item(
722 "Camera Quadrants", ICON_MD_GRID_VIEW, [this]() {
724 });
725 camera_quadrants_item.checked_condition = [this]() {
727 };
728 overlays_menu.subitems.push_back(std::move(camera_quadrants_item));
729
730 gui::CanvasMenuItem minecart_sprites_item(
731 "Minecart Sprites", ICON_MD_TRAIN, [this]() {
733 });
734 minecart_sprites_item.checked_condition = [this]() {
736 };
737 overlays_menu.subitems.push_back(std::move(minecart_sprites_item));
738
740 gui::CanvasMenuItem collision_legend_item(
741 "Collision Legend", ICON_MD_INFO, [this]() {
743 });
744 collision_legend_item.checked_condition = [this]() {
746 };
747 overlays_menu.subitems.push_back(std::move(collision_legend_item));
748 }
749
750 gui::CanvasMenuItem object_bounds_item(
751 "Object Bounds", ICON_MD_CROP_SQUARE,
753 object_bounds_item.checked_condition = [this]() {
754 return show_object_bounds_;
755 };
756 overlays_menu.subitems.push_back(std::move(object_bounds_item));
757
758 return overlays_menu;
759}
760
762 gui::CanvasMenuItem debug_menu;
763 debug_menu.label = "Debug";
764 debug_menu.icon = ICON_MD_BUG_REPORT;
765
766 debug_menu.subitems.emplace_back(
767 "Chest Records...", ICON_MD_INVENTORY, [this, room_id]() {
770 });
771 debug_menu.subitems.back().separator_after = true;
772
773 debug_menu.subitems.push_back(gui::CanvasMenuItem(
774 "Room Info", ICON_MD_INFO,
776 debug_menu.subitems.back().checked_condition = [this]() {
778 };
779
780 debug_menu.subitems.push_back(gui::CanvasMenuItem(
781 "Texture Debug", ICON_MD_IMAGE,
782 [this]() { show_texture_debug_ = !show_texture_debug_; }));
783 debug_menu.subitems.back().checked_condition = [this]() {
784 return show_texture_debug_;
785 };
786
787 gui::CanvasMenuItem object_bounds_menu;
788 object_bounds_menu.label = "Object Bounds Filter";
789 object_bounds_menu.icon = ICON_MD_CROP_SQUARE;
790
791 object_bounds_menu.subitems.push_back(
792 gui::CanvasMenuItem("Type 1 (0x00-0xFF)", [this]() {
795 }));
796 object_bounds_menu.subitems.back().checked_condition = [this]() {
798 };
799 object_bounds_menu.subitems.push_back(
800 gui::CanvasMenuItem("Type 2 (0x100-0x1FF)", [this]() {
803 }));
804 object_bounds_menu.subitems.back().checked_condition = [this]() {
806 };
807 object_bounds_menu.subitems.push_back(
808 gui::CanvasMenuItem("Type 3 (0xF00-0xFFF)", [this]() {
811 }));
812 object_bounds_menu.subitems.back().checked_condition = [this]() {
814 };
815
817 sep.label = "---";
818 sep.enabled_condition = []() {
819 return false;
820 };
821 object_bounds_menu.subitems.push_back(std::move(sep));
822
823 object_bounds_menu.subitems.push_back(
824 gui::CanvasMenuItem("Primary / Upper layer (BG1)", [this]() {
827 }));
828 object_bounds_menu.subitems.back().checked_condition = [this]() {
830 };
831 object_bounds_menu.subitems.push_back(
832 gui::CanvasMenuItem("BG2 overlay / Lower layer (BG2)", [this]() {
835 }));
836 object_bounds_menu.subitems.back().checked_condition = [this]() {
838 };
839 object_bounds_menu.subitems.push_back(
840 gui::CanvasMenuItem("BG1 overlay (room stream only)", [this]() {
843 }));
844 object_bounds_menu.subitems.back().checked_condition = [this]() {
846 };
847 debug_menu.subitems.push_back(std::move(object_bounds_menu));
848
849 debug_menu.subitems.push_back(
851 [this]() { show_layer_info_ = !show_layer_info_; }));
852 debug_menu.subitems.back().checked_condition = [this]() {
853 return show_layer_info_;
854 };
855
856 debug_menu.subitems.push_back(
857 gui::CanvasMenuItem("Force Reload", ICON_MD_REFRESH, [this, room_id]() {
858 if (rooms_ && room_id >= 0 && room_id < zelda3::kNumberOfRooms) {
859 (*rooms_)[room_id].ReloadGraphics(current_entrance_blockset_);
860 }
861 }));
862
863 debug_menu.subitems.push_back(
864 gui::CanvasMenuItem("Log Room State", ICON_MD_PRINT, [this, room_id]() {
865 if (!rooms_ || room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
866 return;
867 }
868 const auto& room = (*rooms_)[room_id];
869 LOG_DEBUG("DungeonDebug", "=== Room %03X Debug ===", room_id);
870 LOG_DEBUG("DungeonDebug", "Blockset: %d, Palette: %d, Layout: %d",
871 room.blockset(), room.palette(), room.layout_id());
872 LOG_DEBUG("DungeonDebug", "Objects: %zu, Sprites: %zu",
873 room.GetTileObjects().size(), room.GetSprites().size());
874 LOG_DEBUG("DungeonDebug", "BG1: %dx%d, BG2: %dx%d",
875 room.bg1_buffer().bitmap().width(),
876 room.bg1_buffer().bitmap().height(),
877 room.bg2_buffer().bitmap().width(),
878 room.bg2_buffer().bitmap().height());
879 }));
880
881 return debug_menu;
882}
883
884} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:199
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_
DungeonChestEditDialogState chest_edit_dialog_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)
absl::Status OpenChestEditor(int room_id, size_t index)
std::optional< size_t > GetObjectIndexUnderContextCursor(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
std::function< void()> show_room_list_callback_
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_dungeon_settings_callback_
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)
zelda3::Room * GetIfLoaded(int room_id)
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:64
auto global_scale() const
Definition canvas.h:413
void ClearContextMenuItems()
Definition canvas.cc:995
void AddContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:972
void SetZoomToFit(const gfx::Bitmap &bitmap)
Definition canvas.cc:1015
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
bool AreChestsLoaded() const
Definition room.h:974
#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_TUNE
Definition icons.h:2022
#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_EDIT
Definition icons.h:645
#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_LIST
Definition icons.h:1094
#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
std::pair< int, int > ContextMenuRoomPosition(const gui::Canvas &canvas)
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::optional< size_t > ChestIndexForObject(const std::vector< RoomObject > &objects, size_t object_index)
Definition chest_edit.cc:63
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
constexpr bool IsStatefulChestObjectId(int object_id)
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="")