7#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
25 ImVec2 screen_position = ImGui::GetIO().MousePos;
26 if (!ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
28 popup_anchor.has_value()) {
29 screen_position = *popup_anchor;
54 insert_menu.separator_after =
true;
92 view.
subitems.back().separator_after =
true;
98 debug.subitems.back().separator_after =
true;
104 auto enabled_if = [](
bool enabled) {
111 insert_menu.
label =
"Insert";
122 insert_menu.
subitems.push_back(std::move(insert_object_item));
131 insert_menu.
subitems.push_back(std::move(insert_sprite_item));
140 insert_menu.
subitems.push_back(std::move(insert_item_item));
145 [
this, door_mode]() {
153std::optional<zelda3::RoomObject>
156 return index ? std::optional((*
rooms_)[room_id].GetTileObjects()[*index])
166 const auto [canvas_x, canvas_y] = ContextMenuRoomPosition(
canvas_);
173 if (!hovered_index.has_value()) {
177 const auto& objects = (*rooms_)[room_id].GetTileObjects();
178 if (*hovered_index >= objects.size()) {
181 return hovered_index;
184std::vector<gui::CanvasMenuItem>
190std::vector<gui::CanvasMenuItem>
192 int room_id, std::optional<zelda3::RoomObject> context_object) {
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 =
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()) {
211 has_room_stream_object =
true;
213 has_special_layer_object =
true;
218 std::vector<gui::CanvasMenuItem> items;
221 auto& room = (*rooms_)[room_id];
222 std::optional<size_t> chest_index;
223 if (context_object &&
230 if (!chest_index && single_selection) {
237 [
this, room_id, index = *chest_index]() {
240 status.ok() ?
"" : std::string(status.message());
246 index < room->GetChests().size();
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));
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]];
266 edit_selected_graphics = [
this, room_id, object]() {
273 can_edit_selected_tiles =
276 edit_selected_tiles = [
this, room_id, object]() {
282 check_selected_coverage = [
this, room_id, object]() {
289 auto enabled_if = [](
bool enabled) {
294 auto paste_item = [&]() {
295 const auto [room_x, room_y] =
296 has_clipboard ? ContextMenuRoomPosition(
canvas_) : std::pair{0, 0};
299 [&interaction, room_x = room_x, room_y = room_y]() {
300 (void)interaction.HandlePasteObjectsAt(room_x, room_y);
306 auto disabled_item = [](
const char* label,
const char* icon,
307 const char* shortcut =
nullptr) {
311 if (shortcut !=
nullptr) {
319 auto layer_item = [&interaction](
const char* label,
int layer,
320 const char* shortcut) {
323 [&interaction, layer]() { interaction.SendSelectedToLayer(layer); },
328 [
this, context_object]() {
329 if (context_object.has_value()) {
330 SetPreviewObject(*context_object);
334 sample_item.separator_after =
true;
340 if (interaction.HandleCopySelected().ok()) {
341 (void)interaction.HandleDeleteSelected();
347 [&interaction]() { (void)interaction.HandleCopySelected(); },
349 items.push_back(paste_item());
351 (void)interaction.HandleDuplicateSelected();
355 [&interaction]() { (void)interaction.HandleDeleteSelected(); },
358 items.back().separator_after =
true;
359 if (!selected.empty()) {
361 arrange.
label =
"Arrange";
365 [&interaction]() { interaction.SendSelectedToFront(); },
369 [&interaction]() { interaction.SendSelectedToBack(); },
374 [&interaction]() { interaction.BringSelectedForward(); },
378 [&interaction]() { interaction.SendSelectedBackward(); },
380 items.push_back(std::move(arrange));
383 placement.
label =
"Placement";
386 if (has_room_stream_object && !has_special_layer_object) {
388 layer_item(
"Object Stream: Primary", 0,
"1"));
390 layer_item(
"Object Stream: BG2 Overlay", 1,
"2"));
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"));
398 layer_item(
"Placement 0: Primary / Upper Layer (BG1)", 0,
"1"));
400 layer_item(
"Placement 1: BG2 Overlay / Lower Layer (BG2)", 1,
"2"));
402 items.push_back(std::move(placement));
406 if (can_edit_selected_graphics) {
408 std::move(edit_selected_graphics));
410 if (can_edit_selected_tiles) {
412 std::move(edit_selected_tiles));
414 if (check_selected_coverage) {
416 std::move(check_selected_coverage));
419 if (!has_selection && !has_entity_selection) {
420 items.push_back(paste_item());
421 items.push_back(disabled_item(
"Delete",
ICON_MD_DELETE,
"Delete"));
424 items.push_back(std::move(sample_item));
426 if (placing_object) {
429 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
430 if (!items.empty()) {
433 items.push_back(std::move(cancel_item));
441 room_menu.
label =
"Room";
448 room_menu.
subitems.push_back(std::move(transfer_item));
457 (*rooms_)[room_id].RenderRoomGraphics();
460 if (
rooms_ && !(*
rooms_)[room_id].GetTileObjects().empty()) {
470 report_menu.
label =
"Capture Issue";
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",
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",
505 absl::StrFormat(
"Capture Selection Issue for Room 0x%03X",
507 absl::StrFormat(
"Room 0x%03X selection or entity mismatch",
509 "Dungeon Selection Issue Report",
520 open_menu.
label =
"Open";
553 copy_export_menu.
label =
"Export";
555 copy_export_menu.
subitems.emplace_back(
560 ImGui::SetClipboardText(
563 copy_export_menu.
subitems.emplace_back(
565 ImGui::SetClipboardText(absl::StrFormat(
"0x%03X", room_id).c_str());
569 copy_export_menu.
subitems.emplace_back(
571 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); });
573 copy_export_menu.
subitems.emplace_back(
578 auto& room = (*rooms_)[room_id];
581 ImGui::SetClipboardText(result.value().c_str());
584 return copy_export_menu;
590 layers_menu.
label =
"Layers";
602 layers_menu.
subitems.push_back(std::move(bg1_layout_item));
613 layers_menu.
subitems.push_back(std::move(bg1_objects_item));
624 layers_menu.
subitems.push_back(std::move(bg2_layout_item));
635 layers_menu.
subitems.push_back(std::move(bg2_objects_item));
642 mgr.SetShowHiddenLayers(!mgr.ShowHiddenLayers());
647 hidden_layers_item.checked_condition = [
this, room_id]() {
650 layers_menu.
subitems.push_back(std::move(hidden_layers_item));
658 layers_menu.
subitems.push_back(std::move(sprites_item));
666 layers_menu.
subitems.push_back(std::move(pot_items_item));
673 overlays_menu.
label =
"Overlays";
682 overlays_menu.
subitems.push_back(std::move(chest_contents_item));
690 overlays_menu.
subitems.push_back(std::move(coordinates_item));
698 minecart_toggle.checked_condition = [
this]() {
701 overlays_menu.
subitems.push_back(std::move(minecart_toggle));
710 overlays_menu.
subitems.push_back(std::move(custom_collision_item));
719 overlays_menu.
subitems.push_back(std::move(track_collision_item));
728 overlays_menu.
subitems.push_back(std::move(camera_quadrants_item));
737 overlays_menu.
subitems.push_back(std::move(minecart_sprites_item));
747 overlays_menu.
subitems.push_back(std::move(collision_legend_item));
756 overlays_menu.
subitems.push_back(std::move(object_bounds_item));
758 return overlays_menu;
763 debug_menu.
label =
"Debug";
771 debug_menu.
subitems.back().separator_after =
true;
776 debug_menu.
subitems.back().checked_condition = [
this]() {
783 debug_menu.
subitems.back().checked_condition = [
this]() {
788 object_bounds_menu.
label =
"Object Bounds Filter";
791 object_bounds_menu.
subitems.push_back(
796 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
799 object_bounds_menu.
subitems.push_back(
804 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
807 object_bounds_menu.
subitems.push_back(
812 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
821 object_bounds_menu.
subitems.push_back(std::move(sep));
823 object_bounds_menu.
subitems.push_back(
828 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
831 object_bounds_menu.
subitems.push_back(
836 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
839 object_bounds_menu.
subitems.push_back(
844 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
847 debug_menu.
subitems.push_back(std::move(object_bounds_menu));
852 debug_menu.
subitems.back().checked_condition = [
this]() {
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());
bool show_coordinate_overlay_
std::optional< zelda3::RoomObject > GetObjectUnderContextCursor(int room_id)
gui::CanvasMenuItem BuildLayerVisibilityContextMenu(int room_id)
void PopulateCanvasContextMenu(int room_id)
float room_viewport_last_scale_
gui::CanvasMenuItem BuildInsertContextMenu()
DungeonRoomTransferEditorState room_transfer_state_
bool show_track_collision_legend_
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_)
ObjectOutlineToggles object_outline_toggles_
std::vector< gui::CanvasMenuItem > BuildSelectionContextMenuItems(int room_id)
bool show_camera_quadrant_overlay_
bool show_room_debug_info_
absl::Status OpenChestEditor(int room_id, size_t index)
ChestEditCallback chest_edit_callback_
std::optional< size_t > GetObjectIndexUnderContextCursor(int room_id)
std::function< void()> show_door_editor_callback_
gui::CanvasMenuItem BuildOverlayContextMenu()
std::function< void(int, const zelda3::RoomObject &) edit_object_tiles_callback_)
std::function< void(int, const zelda3::RoomObject &) edit_graphics_callback_)
DungeonChestEditorState chest_editor_state_
std::function< void()> show_entrance_list_callback_
bool object_interaction_enabled_
bool show_custom_collision_overlay_
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)
bool room_viewport_auto_fit_
std::string BuildRoomMetadataSummary(const zelda3::Room &room, int room_id) const
std::function< void()> show_room_list_callback_
void SetChestContentsVisible(bool visible)
gui::CanvasMenuItem BuildReportContextMenu(int room_id)
std::function< void()> show_object_panel_callback_
bool show_minecart_sprite_overlay_
void AddLoadedRoomContextMenuItems(int room_id)
bool IsObjectInteractionEnabled() const
gfx::Bitmap * PrepareRoomCompositeBitmap(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
uint8_t current_entrance_blockset_
void AddInteractionContextMenuItems(int room_id)
DungeonRoomStore * rooms_
std::function< void()> show_dungeon_settings_callback_
bool AreChestContentsVisible() const
std::function< void()> show_room_matrix_callback_
EntityVisibility entity_visibility_
std::function< void()> show_room_graphics_callback_
std::function< void()> show_item_panel_callback_
bool show_minecart_tracks_
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_
bool show_track_collision_overlay_
void HandleDeleteAllObjects()
std::vector< size_t > GetSelectedObjectIndices() const
bool IsDoorPlacementActive() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
size_t GetSelectionCount() const
bool HasEntitySelection() const
void SetDoorPlacementMode(bool enabled, zelda3::DoorType type=zelda3::DoorType::NormalDoor)
zelda3::Room * GetIfLoaded(int room_id)
TileObjectHandler & tile_handler()
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.
auto global_scale() const
void ClearContextMenuItems()
void AddContextMenuItem(const gui::CanvasMenuItem &item)
void SetZoomToFit(const gfx::Bitmap &bitmap)
std::optional< ImVec2 > context_menu_open_screen_position() const
static bool IsEditableStandardObject(int16_t object_id)
bool GameHidesLowerTilemap() const
bool ShowHiddenLayers() const
bool IsLayerVisible(LayerType layer) const
bool AreChestsLoaded() const
#define ICON_MD_GRID_VIEW
#define ICON_MD_MY_LOCATION
#define ICON_MD_CONTENT_CUT
#define ICON_MD_FIT_SCREEN
#define ICON_MD_FLIP_TO_FRONT
#define ICON_MD_ARROW_DOWNWARD
#define ICON_MD_VISIBILITY
#define ICON_MD_BUG_REPORT
#define ICON_MD_CONTENT_PASTE
#define ICON_MD_FACT_CHECK
#define ICON_MD_VISIBILITY_OFF
#define ICON_MD_INVENTORY
#define ICON_MD_DOOR_FRONT
#define ICON_MD_CROP_SQUARE
#define ICON_MD_FILE_COPY
#define ICON_MD_ARROW_UPWARD
#define ICON_MD_ASSIGNMENT
#define ICON_MD_OPEN_IN_NEW
#define ICON_MD_CONTENT_COPY
#define ICON_MD_DELETE_FOREVER
#define ICON_MD_FLIP_TO_BACK
#define ICON_MD_ADD_CIRCLE
#define LOG_DEBUG(category, format,...)
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)
@ GeneralRoomRenderMismatch
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)
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)