7#include "absl/strings/str_format.h"
11#include "imgui/imgui.h"
34 insert_menu.separator_after =
true;
55 auto enabled_if = [](
bool enabled) {
62 insert_menu.
label =
"Insert";
73 insert_menu.
subitems.push_back(std::move(insert_object_item));
82 insert_menu.
subitems.push_back(std::move(insert_sprite_item));
91 insert_menu.
subitems.push_back(std::move(insert_item_item));
104std::optional<zelda3::RoomObject>
110 const ImGuiIO& io = ImGui::GetIO();
113 const auto [canvas_x, canvas_y] =
121 if (!hovered_index.has_value()) {
125 const auto& objects = (*rooms_)[room_id].GetTileObjects();
126 if (*hovered_index >= objects.size()) {
129 return objects[*hovered_index];
132std::vector<gui::CanvasMenuItem>
136 const bool has_selection = !selected.empty();
137 const bool single_selection = selected.size() == 1;
138 const bool group_selection = selected.size() > 1;
139 const bool has_clipboard = interaction.HasClipboardData();
140 const bool placing_object = interaction.IsObjectLoaded();
141 const bool valid_room =
143 const bool room_has_objects =
144 valid_room && !(*rooms_)[room_id].GetTileObjects().empty();
145 bool has_room_stream_object =
false;
146 bool has_special_layer_object =
false;
147 if (has_selection && valid_room) {
148 const auto& objects = (*rooms_)[room_id].GetTileObjects();
149 for (
const size_t index : selected) {
150 if (index >= objects.size()) {
154 has_room_stream_object =
true;
156 has_special_layer_object =
true;
161 std::vector<gui::CanvasMenuItem> items;
163 bool can_edit_selected_graphics =
false;
164 std::function<void()> edit_selected_graphics;
165 if (single_selection &&
rooms_) {
166 auto& room = (*rooms_)[room_id];
167 const auto& objects = room.GetTileObjects();
168 if (selected[0] < objects.size()) {
169 const auto object = objects[selected[0]];
172 edit_selected_graphics = [
this, room_id, object]() {
182 auto enabled_if = [](
bool enabled) {
187 auto paste_item = [&]() {
190 [&interaction]() { interaction.HandlePasteObjects(); },
"Ctrl+V");
194 auto disabled_item = [](
const char* label,
const char* icon,
195 const char* shortcut =
nullptr) {
199 if (shortcut !=
nullptr) {
207 auto layer_item = [&interaction](
const char* label,
int layer,
208 const char* shortcut) {
211 [&interaction, layer]() { interaction.SendSelectedToLayer(layer); },
216 std::optional<size_t> sample_item_index;
217 if (context_sample_object.has_value()) {
218 sample_item_index = items.size();
220 [
this,
object = *context_sample_object]() {
229 interaction.HandleCopySelected();
230 interaction.HandleDeleteSelected();
235 [&interaction]() { interaction.HandleCopySelected(); },
"Ctrl+C");
236 items.push_back(paste_item());
239 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
243 [&interaction]() { interaction.SendSelectedToFront(); },
247 [&interaction]() { interaction.SendSelectedToBack(); },
"Ctrl+Shift+[");
250 z_order.
label =
"Z Order";
254 [&interaction]() { interaction.BringSelectedForward(); },
"Ctrl+]");
257 [&interaction]() { interaction.SendSelectedBackward(); },
"Ctrl+[");
258 items.push_back(std::move(z_order));
260 if (has_room_stream_object && !has_special_layer_object) {
261 items.push_back(layer_item(
"Object Stream: Primary", 0,
"1"));
262 items.push_back(layer_item(
"Object Stream: BG2 Overlay", 1,
"2"));
263 items.push_back(layer_item(
"Object Stream: BG1 Overlay", 2,
"3"));
264 }
else if (has_special_layer_object && !has_room_stream_object) {
265 items.push_back(layer_item(
"Upper Layer (BG1)", 0,
"1"));
266 items.push_back(layer_item(
"Lower Layer (BG2)", 1,
"2"));
269 layer_item(
"Placement 0: Primary / Upper Layer (BG1)", 0,
"1"));
271 layer_item(
"Placement 1: BG2 Overlay / Lower Layer (BG2)", 1,
"2"));
275 yaze_more.
label =
"More";
280 interaction.HandleCopySelected();
281 interaction.HandlePasteObjects();
284 if (group_selection) {
288 items.push_back(std::move(yaze_more));
291 if (can_edit_selected_graphics) {
293 std::move(edit_selected_graphics));
296 const bool has_entity_selection = interaction.HasEntitySelection();
297 if (has_entity_selection && !has_selection) {
300 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
303 if (!has_selection && !has_entity_selection) {
304 items.push_back(paste_item());
308 [&interaction]() { interaction.HandleDeleteAllObjects(); });
310 items.push_back(std::move(delete_all_item));
313 if (placing_object) {
316 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
317 if (!items.empty()) {
320 items.push_back(std::move(cancel_item));
323 if (sample_item_index.has_value() && items.size() > *sample_item_index + 1) {
324 items[*sample_item_index].separator_after =
true;
327 if (!items.empty()) {
328 items.back().separator_after =
true;
335 room_menu.
label =
"Room";
346 (*rooms_)[room_id].RenderRoomGraphics();
350 if (
rooms_ && !(*
rooms_)[room_id].GetTileObjects().empty()) {
360 report_menu.
label =
"Report";
368 absl::StrFormat(
"Report Room 0x%03X Render Issue", room_id),
369 absl::StrFormat(
"Room 0x%03X render mismatch", room_id),
370 "Dungeon Render Issue Report",
381 absl::StrFormat(
"Report Room 0x%03X Palette Issue", room_id),
382 absl::StrFormat(
"Room 0x%03X palette mismatch", room_id),
383 "Dungeon Palette Issue Report",
395 absl::StrFormat(
"Report Selection Issue for Room 0x%03X",
397 absl::StrFormat(
"Room 0x%03X selection or entity mismatch",
399 "Dungeon Selection Issue Report",
410 open_menu.
label =
"Open";
439 copy_export_menu.
label =
"Copy / Export";
441 copy_export_menu.
subitems.emplace_back(
446 ImGui::SetClipboardText(
449 copy_export_menu.
subitems.emplace_back(
451 ImGui::SetClipboardText(absl::StrFormat(
"0x%03X", room_id).c_str());
455 copy_export_menu.
subitems.emplace_back(
457 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); });
459 copy_export_menu.
subitems.emplace_back(
464 auto& room = (*rooms_)[room_id];
467 ImGui::SetClipboardText(result.value().c_str());
470 return copy_export_menu;
476 layers_menu.
label =
"Layers";
488 layers_menu.
subitems.push_back(std::move(bg1_layout_item));
499 layers_menu.
subitems.push_back(std::move(bg1_objects_item));
510 layers_menu.
subitems.push_back(std::move(bg2_layout_item));
521 layers_menu.
subitems.push_back(std::move(bg2_objects_item));
529 layers_menu.
subitems.push_back(std::move(sprites_item));
537 layers_menu.
subitems.push_back(std::move(pot_items_item));
544 overlays_menu.
label =
"Overlays";
553 overlays_menu.
subitems.push_back(std::move(coordinates_item));
561 minecart_toggle.checked_condition = [
this]() {
564 overlays_menu.
subitems.push_back(std::move(minecart_toggle));
573 overlays_menu.
subitems.push_back(std::move(custom_collision_item));
582 overlays_menu.
subitems.push_back(std::move(track_collision_item));
591 overlays_menu.
subitems.push_back(std::move(camera_quadrants_item));
600 overlays_menu.
subitems.push_back(std::move(minecart_sprites_item));
610 overlays_menu.
subitems.push_back(std::move(collision_legend_item));
619 overlays_menu.
subitems.push_back(std::move(object_bounds_item));
621 return overlays_menu;
626 debug_menu.
label =
"Debug";
632 debug_menu.
subitems.back().checked_condition = [
this]() {
639 debug_menu.
subitems.back().checked_condition = [
this]() {
644 object_bounds_menu.
label =
"Object Bounds Filter";
647 object_bounds_menu.
subitems.push_back(
652 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
655 object_bounds_menu.
subitems.push_back(
660 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
663 object_bounds_menu.
subitems.push_back(
668 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
677 object_bounds_menu.
subitems.push_back(std::move(sep));
679 object_bounds_menu.
subitems.push_back(
684 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
687 object_bounds_menu.
subitems.push_back(
692 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
695 object_bounds_menu.
subitems.push_back(
700 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
703 debug_menu.
subitems.push_back(std::move(object_bounds_menu));
708 debug_menu.
subitems.back().checked_condition = [
this]() {
724 const auto& room = (*rooms_)[room_id];
725 LOG_DEBUG(
"DungeonDebug",
"=== Room %03X Debug ===", room_id);
726 LOG_DEBUG(
"DungeonDebug",
"Blockset: %d, Palette: %d, Layout: %d",
727 room.blockset(), room.palette(), room.layout_id());
728 LOG_DEBUG(
"DungeonDebug",
"Objects: %zu, Sprites: %zu",
729 room.GetTileObjects().size(), room.GetSprites().size());
730 LOG_DEBUG(
"DungeonDebug",
"BG1: %dx%d, BG2: %dx%d",
731 room.bg1_buffer().bitmap().width(),
732 room.bg1_buffer().bitmap().height(),
733 room.bg2_buffer().bitmap().width(),
734 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)
gui::CanvasMenuItem BuildInsertContextMenu()
bool show_track_collision_legend_
gui::CanvasMenuItem BuildRoomContextMenu(int room_id)
gui::CanvasMenuItem BuildDebugContextMenu(int room_id)
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_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)
std::string BuildRoomMetadataSummary(const zelda3::Room &room, int room_id) const
gui::CanvasMenuItem BuildReportContextMenu(int room_id)
void SetPreviewObject(const zelda3::RoomObject &object)
std::function< void()> show_object_panel_callback_
bool show_minecart_sprite_overlay_
void AddLoadedRoomContextMenuItems(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
uint8_t current_entrance_blockset_
void AddInteractionContextMenuItems(int room_id)
DungeonRoomStore * rooms_
std::string BuildDrawIssueReport(const zelda3::Room &room, int room_id) 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)
bool IsLayerVisible(LayerType layer) const
#define ICON_MD_GRID_VIEW
#define ICON_MD_MY_LOCATION
#define ICON_MD_CONTENT_CUT
#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_INVENTORY
#define ICON_MD_DOOR_FRONT
#define ICON_MD_CROP_SQUARE
#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
@ 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