7#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
36 insert_menu.separator_after =
true;
50 display.
label =
"Display";
68 display.
subitems.back().separator_after =
true;
74 advanced.
label =
"Advanced";
83 auto enabled_if = [](
bool enabled) {
90 insert_menu.
label =
"Insert";
101 insert_menu.
subitems.push_back(std::move(insert_object_item));
110 insert_menu.
subitems.push_back(std::move(insert_sprite_item));
119 insert_menu.
subitems.push_back(std::move(insert_item_item));
124 [
this, door_mode]() {
132std::optional<zelda3::RoomObject>
138 const ImGuiIO& io = ImGui::GetIO();
139 ImVec2 screen_position = io.MousePos;
140 if (!ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
142 popup_anchor.has_value()) {
143 screen_position = *popup_anchor;
148 const auto [canvas_x, canvas_y] =
156 if (!hovered_index.has_value()) {
160 const auto& objects = (*rooms_)[room_id].GetTileObjects();
161 if (*hovered_index >= objects.size()) {
164 return objects[*hovered_index];
167std::vector<gui::CanvasMenuItem>
173std::vector<gui::CanvasMenuItem>
175 int room_id, std::optional<zelda3::RoomObject> context_object) {
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 =
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()) {
194 has_room_stream_object =
true;
196 has_special_layer_object =
true;
201 std::vector<gui::CanvasMenuItem> items;
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]];
215 edit_selected_graphics = [
this, room_id, object]() {
222 can_edit_selected_tiles =
225 edit_selected_tiles = [
this, room_id, object]() {
231 check_selected_coverage = [
this, room_id, object]() {
238 auto enabled_if = [](
bool enabled) {
243 auto paste_item = [&]() {
246 [&interaction]() { (void)interaction.HandlePasteObjects(); },
251 auto disabled_item = [](
const char* label,
const char* icon,
252 const char* shortcut =
nullptr) {
256 if (shortcut !=
nullptr) {
264 auto layer_item = [&interaction](
const char* label,
int layer,
265 const char* shortcut) {
268 [&interaction, layer]() { interaction.SendSelectedToLayer(layer); },
273 [
this, context_object]() {
274 if (context_object.has_value()) {
275 SetPreviewObject(*context_object);
279 sample_item.separator_after =
true;
285 if (interaction.HandleCopySelected().ok()) {
286 (void)interaction.HandleDeleteSelected();
292 [&interaction]() { (void)interaction.HandleCopySelected(); },
294 items.push_back(paste_item());
296 (void)interaction.HandleDuplicateSelected();
300 [&interaction]() { (void)interaction.HandleDeleteSelected(); },
303 items.back().separator_after =
true;
304 if (!selected.empty()) {
306 arrange.
label =
"Arrange";
310 [&interaction]() { interaction.SendSelectedToFront(); },
314 [&interaction]() { interaction.SendSelectedToBack(); },
319 [&interaction]() { interaction.BringSelectedForward(); },
323 [&interaction]() { interaction.SendSelectedBackward(); },
325 items.push_back(std::move(arrange));
328 placement.
label =
"Placement";
331 if (has_room_stream_object && !has_special_layer_object) {
333 layer_item(
"Object Stream: Primary", 0,
"1"));
335 layer_item(
"Object Stream: BG2 Overlay", 1,
"2"));
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"));
343 layer_item(
"Placement 0: Primary / Upper Layer (BG1)", 0,
"1"));
345 layer_item(
"Placement 1: BG2 Overlay / Lower Layer (BG2)", 1,
"2"));
347 items.push_back(std::move(placement));
351 if (can_edit_selected_graphics) {
353 std::move(edit_selected_graphics));
355 if (can_edit_selected_tiles) {
357 std::move(edit_selected_tiles));
359 if (check_selected_coverage) {
361 std::move(check_selected_coverage));
364 if (!has_selection && !has_entity_selection) {
365 items.push_back(paste_item());
366 items.push_back(disabled_item(
"Delete",
ICON_MD_DELETE,
"Delete"));
369 items.push_back(std::move(sample_item));
371 if (placing_object) {
374 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
375 if (!items.empty()) {
378 items.push_back(std::move(cancel_item));
386 room_menu.
label =
"Room";
393 room_menu.
subitems.push_back(std::move(transfer_item));
402 (*rooms_)[room_id].RenderRoomGraphics();
405 if (
rooms_ && !(*
rooms_)[room_id].GetTileObjects().empty()) {
415 report_menu.
label =
"Capture Issue";
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",
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",
450 absl::StrFormat(
"Capture Selection Issue for Room 0x%03X",
452 absl::StrFormat(
"Room 0x%03X selection or entity mismatch",
454 "Dungeon Selection Issue Report",
465 open_menu.
label =
"Open";
494 copy_export_menu.
label =
"Copy / Export";
496 copy_export_menu.
subitems.emplace_back(
501 ImGui::SetClipboardText(
504 copy_export_menu.
subitems.emplace_back(
506 ImGui::SetClipboardText(absl::StrFormat(
"0x%03X", room_id).c_str());
510 copy_export_menu.
subitems.emplace_back(
512 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); });
514 copy_export_menu.
subitems.emplace_back(
519 auto& room = (*rooms_)[room_id];
522 ImGui::SetClipboardText(result.value().c_str());
525 return copy_export_menu;
531 layers_menu.
label =
"Layers";
543 layers_menu.
subitems.push_back(std::move(bg1_layout_item));
554 layers_menu.
subitems.push_back(std::move(bg1_objects_item));
565 layers_menu.
subitems.push_back(std::move(bg2_layout_item));
576 layers_menu.
subitems.push_back(std::move(bg2_objects_item));
583 mgr.SetShowHiddenLayers(!mgr.ShowHiddenLayers());
588 hidden_layers_item.checked_condition = [
this, room_id]() {
591 layers_menu.
subitems.push_back(std::move(hidden_layers_item));
599 layers_menu.
subitems.push_back(std::move(sprites_item));
607 layers_menu.
subitems.push_back(std::move(pot_items_item));
614 overlays_menu.
label =
"Overlays";
623 overlays_menu.
subitems.push_back(std::move(chest_contents_item));
631 overlays_menu.
subitems.push_back(std::move(coordinates_item));
639 minecart_toggle.checked_condition = [
this]() {
642 overlays_menu.
subitems.push_back(std::move(minecart_toggle));
651 overlays_menu.
subitems.push_back(std::move(custom_collision_item));
660 overlays_menu.
subitems.push_back(std::move(track_collision_item));
669 overlays_menu.
subitems.push_back(std::move(camera_quadrants_item));
678 overlays_menu.
subitems.push_back(std::move(minecart_sprites_item));
688 overlays_menu.
subitems.push_back(std::move(collision_legend_item));
697 overlays_menu.
subitems.push_back(std::move(object_bounds_item));
699 return overlays_menu;
704 debug_menu.
label =
"Debug";
710 debug_menu.
subitems.back().checked_condition = [
this]() {
717 debug_menu.
subitems.back().checked_condition = [
this]() {
722 object_bounds_menu.
label =
"Object Bounds Filter";
725 object_bounds_menu.
subitems.push_back(
730 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
733 object_bounds_menu.
subitems.push_back(
738 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
741 object_bounds_menu.
subitems.push_back(
746 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
755 object_bounds_menu.
subitems.push_back(std::move(sep));
757 object_bounds_menu.
subitems.push_back(
762 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
765 object_bounds_menu.
subitems.push_back(
770 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
773 object_bounds_menu.
subitems.push_back(
778 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
781 debug_menu.
subitems.push_back(std::move(object_bounds_menu));
786 debug_menu.
subitems.back().checked_condition = [
this]() {
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());
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_
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_
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_)
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
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)
gfx::Bitmap * PrepareRoomCompositeBitmap(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
uint8_t current_entrance_blockset_
void AddInteractionContextMenuItems(int room_id)
DungeonRoomStore * rooms_
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)
TileObjectHandler & tile_handler()
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
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
#define ICON_MD_GRID_VIEW
#define ICON_MD_MY_LOCATION
#define ICON_MD_CONTENT_CUT
#define ICON_MD_FIT_SCREEN
#define ICON_MD_FILE_DOWNLOAD
#define ICON_MD_FLIP_TO_FRONT
#define ICON_MD_ARROW_DOWNWARD
#define ICON_MD_VISIBILITY
#define ICON_MD_BUG_REPORT
#define ICON_MD_MORE_HORIZ
#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::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