7#include "absl/strings/str_format.h"
11#include "imgui/imgui.h"
33 insert_menu.separator_after =
true;
40 selection_menu.
label =
"Selection";
43 return selection_menu;
63 auto enabled_if = [](
bool enabled) {
70 insert_menu.
label =
"Insert";
81 insert_menu.
subitems.push_back(std::move(insert_object_item));
90 insert_menu.
subitems.push_back(std::move(insert_sprite_item));
99 insert_menu.
subitems.push_back(std::move(insert_item_item));
104 [
this, door_mode]() {
112std::optional<zelda3::RoomObject>
118 const ImGuiIO& io = ImGui::GetIO();
119 ImVec2 screen_position = io.MousePos;
120 if (!ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
122 popup_anchor.has_value()) {
123 screen_position = *popup_anchor;
128 const auto [canvas_x, canvas_y] =
136 if (!hovered_index.has_value()) {
140 const auto& objects = (*rooms_)[room_id].GetTileObjects();
141 if (*hovered_index >= objects.size()) {
144 return objects[*hovered_index];
147std::vector<gui::CanvasMenuItem>
153std::vector<gui::CanvasMenuItem>
155 int room_id, std::optional<zelda3::RoomObject> context_object) {
158 const bool has_selection = !selected.empty();
159 const bool single_selection = selected.size() == 1;
160 const bool group_selection = selected.size() > 1;
161 const bool has_clipboard = interaction.HasClipboardData();
162 const bool placing_object = interaction.IsObjectLoaded();
163 const bool valid_room =
165 const bool room_has_objects =
166 valid_room && !(*rooms_)[room_id].GetTileObjects().empty();
167 bool has_room_stream_object =
false;
168 bool has_special_layer_object =
false;
169 if (has_selection && valid_room) {
170 const auto& objects = (*rooms_)[room_id].GetTileObjects();
171 for (
const size_t index : selected) {
172 if (index >= objects.size()) {
176 has_room_stream_object =
true;
178 has_special_layer_object =
true;
183 std::vector<gui::CanvasMenuItem> items;
185 bool can_edit_selected_graphics =
false;
186 std::function<void()> edit_selected_graphics;
187 bool can_edit_selected_tiles =
false;
188 std::function<void()> edit_selected_tiles;
189 if (single_selection && valid_room) {
190 auto& room = (*rooms_)[room_id];
191 const auto& objects = room.GetTileObjects();
192 if (selected[0] < objects.size()) {
193 const auto object = objects[selected[0]];
196 edit_selected_graphics = [
this, room_id, object]() {
203 can_edit_selected_tiles =
206 edit_selected_tiles = [
this, room_id, object]() {
214 auto enabled_if = [](
bool enabled) {
219 auto paste_item = [&]() {
222 [&interaction]() { interaction.HandlePasteObjects(); },
"Ctrl+V");
226 auto disabled_item = [](
const char* label,
const char* icon,
227 const char* shortcut =
nullptr) {
231 if (shortcut !=
nullptr) {
239 auto layer_item = [&interaction](
const char* label,
int layer,
240 const char* shortcut) {
243 [&interaction, layer]() { interaction.SendSelectedToLayer(layer); },
248 [
this, context_object]() {
249 if (context_object.has_value()) {
250 SetPreviewObject(*context_object);
254 sample_item.separator_after =
true;
255 items.push_back(std::move(sample_item));
261 interaction.HandleCopySelected();
262 interaction.HandleDeleteSelected();
267 [&interaction]() { interaction.HandleCopySelected(); },
"Ctrl+C");
268 items.push_back(paste_item());
271 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
275 [&interaction]() { interaction.SendSelectedToFront(); },
279 [&interaction]() { interaction.SendSelectedToBack(); },
"Ctrl+Shift+[");
282 z_order.
label =
"Z Order";
286 [&interaction]() { interaction.BringSelectedForward(); },
"Ctrl+]");
289 [&interaction]() { interaction.SendSelectedBackward(); },
"Ctrl+[");
290 items.push_back(std::move(z_order));
292 if (has_room_stream_object && !has_special_layer_object) {
293 items.push_back(layer_item(
"Object Stream: Primary", 0,
"1"));
294 items.push_back(layer_item(
"Object Stream: BG2 Overlay", 1,
"2"));
295 items.push_back(layer_item(
"Object Stream: BG1 Overlay", 2,
"3"));
296 }
else if (has_special_layer_object && !has_room_stream_object) {
297 items.push_back(layer_item(
"Upper Layer (BG1)", 0,
"1"));
298 items.push_back(layer_item(
"Lower Layer (BG2)", 1,
"2"));
301 layer_item(
"Placement 0: Primary / Upper Layer (BG1)", 0,
"1"));
303 layer_item(
"Placement 1: BG2 Overlay / Lower Layer (BG2)", 1,
"2"));
307 yaze_more.
label =
"More";
312 interaction.HandleCopySelected();
313 interaction.HandlePasteObjects();
316 if (group_selection) {
320 items.push_back(std::move(yaze_more));
323 if (can_edit_selected_graphics) {
325 std::move(edit_selected_graphics));
327 if (can_edit_selected_tiles) {
329 std::move(edit_selected_tiles));
332 const bool has_entity_selection = interaction.HasEntitySelection();
333 if (has_entity_selection && !has_selection) {
336 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
339 if (!has_selection && !has_entity_selection) {
340 items.push_back(paste_item());
344 [&interaction]() { interaction.HandleDeleteAllObjects(); });
346 items.push_back(std::move(delete_all_item));
349 if (placing_object) {
352 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
353 if (!items.empty()) {
356 items.push_back(std::move(cancel_item));
364 room_menu.
label =
"Room";
375 (*rooms_)[room_id].RenderRoomGraphics();
379 if (
rooms_ && !(*
rooms_)[room_id].GetTileObjects().empty()) {
389 report_menu.
label =
"Capture Issue";
397 absl::StrFormat(
"Capture Room 0x%03X Rendering Issue", room_id),
398 absl::StrFormat(
"Room 0x%03X render mismatch", room_id),
399 "Dungeon Render Issue Report",
410 absl::StrFormat(
"Capture Room 0x%03X Palette Issue", room_id),
411 absl::StrFormat(
"Room 0x%03X palette mismatch", room_id),
412 "Dungeon Palette Issue Report",
424 absl::StrFormat(
"Capture Selection Issue for Room 0x%03X",
426 absl::StrFormat(
"Room 0x%03X selection or entity mismatch",
428 "Dungeon Selection Issue Report",
439 open_menu.
label =
"Open";
468 copy_export_menu.
label =
"Copy / Export";
470 copy_export_menu.
subitems.emplace_back(
475 ImGui::SetClipboardText(
478 copy_export_menu.
subitems.emplace_back(
480 ImGui::SetClipboardText(absl::StrFormat(
"0x%03X", room_id).c_str());
484 copy_export_menu.
subitems.emplace_back(
486 [room_label]() { ImGui::SetClipboardText(room_label.c_str()); });
488 copy_export_menu.
subitems.emplace_back(
493 auto& room = (*rooms_)[room_id];
496 ImGui::SetClipboardText(result.value().c_str());
499 return copy_export_menu;
505 layers_menu.
label =
"Layers";
517 layers_menu.
subitems.push_back(std::move(bg1_layout_item));
528 layers_menu.
subitems.push_back(std::move(bg1_objects_item));
539 layers_menu.
subitems.push_back(std::move(bg2_layout_item));
550 layers_menu.
subitems.push_back(std::move(bg2_objects_item));
558 layers_menu.
subitems.push_back(std::move(sprites_item));
566 layers_menu.
subitems.push_back(std::move(pot_items_item));
573 overlays_menu.
label =
"Overlays";
582 overlays_menu.
subitems.push_back(std::move(coordinates_item));
590 minecart_toggle.checked_condition = [
this]() {
593 overlays_menu.
subitems.push_back(std::move(minecart_toggle));
602 overlays_menu.
subitems.push_back(std::move(custom_collision_item));
611 overlays_menu.
subitems.push_back(std::move(track_collision_item));
620 overlays_menu.
subitems.push_back(std::move(camera_quadrants_item));
629 overlays_menu.
subitems.push_back(std::move(minecart_sprites_item));
639 overlays_menu.
subitems.push_back(std::move(collision_legend_item));
648 overlays_menu.
subitems.push_back(std::move(object_bounds_item));
650 return overlays_menu;
655 debug_menu.
label =
"Debug";
661 debug_menu.
subitems.back().checked_condition = [
this]() {
668 debug_menu.
subitems.back().checked_condition = [
this]() {
673 object_bounds_menu.
label =
"Object Bounds Filter";
676 object_bounds_menu.
subitems.push_back(
681 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
684 object_bounds_menu.
subitems.push_back(
689 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
692 object_bounds_menu.
subitems.push_back(
697 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
706 object_bounds_menu.
subitems.push_back(std::move(sep));
708 object_bounds_menu.
subitems.push_back(
713 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
716 object_bounds_menu.
subitems.push_back(
721 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
724 object_bounds_menu.
subitems.push_back(
729 object_bounds_menu.
subitems.back().checked_condition = [
this]() {
732 debug_menu.
subitems.push_back(std::move(object_bounds_menu));
737 debug_menu.
subitems.back().checked_condition = [
this]() {
753 const auto& room = (*rooms_)[room_id];
754 LOG_DEBUG(
"DungeonDebug",
"=== Room %03X Debug ===", room_id);
755 LOG_DEBUG(
"DungeonDebug",
"Blockset: %d, Palette: %d, Layout: %d",
756 room.blockset(), room.palette(), room.layout_id());
757 LOG_DEBUG(
"DungeonDebug",
"Objects: %zu, Sprites: %zu",
758 room.GetTileObjects().size(), room.GetSprites().size());
759 LOG_DEBUG(
"DungeonDebug",
"BG1: %dx%d, BG2: %dx%d",
760 room.bg1_buffer().bitmap().width(),
761 room.bg1_buffer().bitmap().height(),
762 room.bg2_buffer().bitmap().width(),
763 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)
std::string BuildDrawIssueReport(const zelda3::Room &room, 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_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)
gui::CanvasMenuItem BuildSelectionContextMenu(int room_id)
std::string BuildRoomMetadataSummary(const zelda3::Room &room, int room_id) const
gui::CanvasMenuItem BuildReportContextMenu(int room_id)
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::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)
std::optional< ImVec2 > context_menu_open_screen_position() const
static bool IsEditableStandardObject(int16_t object_id)
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_SELECT_ALL
#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