From 69f94323c0cdccc81dff7e40a94d8b7f6176cd88 Mon Sep 17 00:00:00 2001 From: scawful Date: Thu, 16 Oct 2025 11:41:51 -0400 Subject: [PATCH] feat(editor): add entity operations for overworld editing - Introduced new `entity_operations.cc` and `entity_operations.h` files to handle the insertion of entrances, exits, sprites, and items in the overworld editor. - Updated `map_properties.cc` to include a context menu for entity insertion, allowing users to add various entities directly from the canvas. - Enhanced `overworld_editor.cc` to manage entity insertion callbacks and streamline the editing process. Benefits: - Improves the functionality of the overworld editor by enabling direct manipulation of entities. - Provides a more intuitive user experience with context-sensitive menus for entity operations. --- src/app/editor/editor_library.cmake | 1 + src/app/editor/overworld/entity_operations.cc | 235 ++++++++++++++++++ src/app/editor/overworld/entity_operations.h | 136 ++++++++++ src/app/editor/overworld/map_properties.cc | 109 ++++++-- src/app/editor/overworld/map_properties.h | 11 +- src/app/editor/overworld/overworld_editor.cc | 115 ++++++++- src/app/editor/overworld/overworld_editor.h | 10 + src/app/gui/canvas/canvas.cc | 105 ++++---- src/app/gui/canvas/canvas.h | 23 +- src/app/gui/canvas/canvas_context_menu.cc | 14 +- src/app/gui/canvas/canvas_context_menu.h | 5 +- .../gui/canvas/canvas_interaction_handler.cc | 2 - .../gui/canvas/canvas_interaction_handler.h | 2 - src/app/gui/canvas/canvas_modals.cc | 35 ++- src/app/gui/canvas/canvas_modals.h | 29 +-- .../canvas/canvas_performance_integration.cc | 2 - .../canvas/canvas_performance_integration.h | 2 - src/app/gui/canvas/canvas_usage_tracker.cc | 4 +- src/app/gui/canvas/canvas_usage_tracker.h | 3 +- src/app/gui/canvas/canvas_utils.h | 18 +- 20 files changed, 694 insertions(+), 167 deletions(-) create mode 100644 src/app/editor/overworld/entity_operations.cc create mode 100644 src/app/editor/overworld/entity_operations.h diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index 106339b2..7f95cf5c 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -23,6 +23,7 @@ set( app/editor/message/message_preview.cc app/editor/music/music_editor.cc app/editor/overworld/entity.cc + app/editor/overworld/entity_operations.cc app/editor/overworld/map_properties.cc app/editor/overworld/overworld_editor.cc app/editor/overworld/overworld_entity_renderer.cc diff --git a/src/app/editor/overworld/entity_operations.cc b/src/app/editor/overworld/entity_operations.cc new file mode 100644 index 00000000..c7dc2cc1 --- /dev/null +++ b/src/app/editor/overworld/entity_operations.cc @@ -0,0 +1,235 @@ +#include "entity_operations.h" + +#include "absl/strings/str_format.h" +#include "util/log.h" + +namespace yaze { +namespace editor { + +absl::StatusOr InsertEntrance( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + bool is_hole) { + + if (!overworld || !overworld->is_loaded()) { + return absl::FailedPreconditionError("Overworld not loaded"); + } + + // Snap to 16x16 grid and clamp to bounds (ZScream: EntranceMode.cs:86-87) + ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); + + // Get parent map ID (ZScream: EntranceMode.cs:78-82) + auto* current_ow_map = overworld->overworld_map(current_map); + uint8_t map_id = GetParentMapId(current_ow_map, current_map); + + if (is_hole) { + // Search for first deleted hole slot (ZScream: EntranceMode.cs:74-100) + auto& holes = overworld->holes(); + for (size_t i = 0; i < holes.size(); ++i) { + if (holes[i].deleted) { + // Reuse deleted slot + holes[i].deleted = false; + holes[i].map_id_ = map_id; + holes[i].x_ = static_cast(snapped_pos.x); + holes[i].y_ = static_cast(snapped_pos.y); + holes[i].entrance_id_ = 0; // Default, user configures in popup + holes[i].is_hole_ = true; + + // Update map properties (ZScream: EntranceMode.cs:90) + holes[i].UpdateMapProperties(map_id); + + LOG_DEBUG("EntityOps", "Inserted hole at slot %zu: pos=(%d,%d) map=0x%02X", + i, holes[i].x_, holes[i].y_, map_id); + + return &holes[i]; + } + } + return absl::ResourceExhaustedError( + "No space available for new hole. Delete one first."); + + } else { + // Search for first deleted entrance slot (ZScream: EntranceMode.cs:104-130) + auto* entrances = overworld->mutable_entrances(); + for (size_t i = 0; i < entrances->size(); ++i) { + if (entrances->at(i).deleted) { + // Reuse deleted slot + entrances->at(i).deleted = false; + entrances->at(i).map_id_ = map_id; + entrances->at(i).x_ = static_cast(snapped_pos.x); + entrances->at(i).y_ = static_cast(snapped_pos.y); + entrances->at(i).entrance_id_ = 0; // Default, user configures in popup + entrances->at(i).is_hole_ = false; + + // Update map properties (ZScream: EntranceMode.cs:120) + entrances->at(i).UpdateMapProperties(map_id); + + LOG_DEBUG("EntityOps", "Inserted entrance at slot %zu: pos=(%d,%d) map=0x%02X", + i, entrances->at(i).x_, entrances->at(i).y_, map_id); + + return &entrances->at(i); + } + } + return absl::ResourceExhaustedError( + "No space available for new entrance. Delete one first."); + } +} + +absl::StatusOr InsertExit( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map) { + + if (!overworld || !overworld->is_loaded()) { + return absl::FailedPreconditionError("Overworld not loaded"); + } + + // Snap to 16x16 grid and clamp to bounds (ZScream: ExitMode.cs:71-72) + ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); + + // Get parent map ID (ZScream: ExitMode.cs:63-67) + auto* current_ow_map = overworld->overworld_map(current_map); + uint8_t map_id = GetParentMapId(current_ow_map, current_map); + + // Search for first deleted exit slot (ZScream: ExitMode.cs:59-124) + auto& exits = *overworld->mutable_exits(); + for (size_t i = 0; i < exits.size(); ++i) { + if (exits[i].deleted_) { + // Reuse deleted slot + exits[i].deleted_ = false; + exits[i].map_id_ = map_id; + exits[i].x_ = static_cast(snapped_pos.x); + exits[i].y_ = static_cast(snapped_pos.y); + + // Initialize with default values (ZScream: ExitMode.cs:95-112) + // User will configure room_id, scroll, camera in popup + exits[i].room_id_ = 0; + exits[i].x_scroll_ = 0; + exits[i].y_scroll_ = 0; + exits[i].x_camera_ = 0; + exits[i].y_camera_ = 0; + exits[i].x_player_ = static_cast(snapped_pos.x); + exits[i].y_player_ = static_cast(snapped_pos.y); + exits[i].scroll_mod_x_ = 0; + exits[i].scroll_mod_y_ = 0; + exits[i].door_type_1_ = 0; + exits[i].door_type_2_ = 0; + + // Update map properties + exits[i].UpdateMapProperties(map_id); + + LOG_DEBUG("EntityOps", "Inserted exit at slot %zu: pos=(%d,%d) map=0x%02X", + i, exits[i].x_, exits[i].y_, map_id); + + return &exits[i]; + } + } + + return absl::ResourceExhaustedError( + "No space available for new exit. Delete one first."); +} + +absl::StatusOr InsertSprite( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + int game_state, uint8_t sprite_id) { + + if (!overworld || !overworld->is_loaded()) { + return absl::FailedPreconditionError("Overworld not loaded"); + } + + if (game_state < 0 || game_state > 2) { + return absl::InvalidArgumentError("Invalid game state (must be 0-2)"); + } + + // Snap to 16x16 grid and clamp to bounds (ZScream: SpriteMode.cs similar logic) + ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); + + // Get parent map ID (ZScream: SpriteMode.cs:90-95) + auto* current_ow_map = overworld->overworld_map(current_map); + uint8_t map_id = GetParentMapId(current_ow_map, current_map); + + // Calculate map position (ZScream uses mapHover for parent tracking) + // For sprites, we need the actual map coordinates within the 512x512 map + int map_local_x = static_cast(snapped_pos.x) % 512; + int map_local_y = static_cast(snapped_pos.y) % 512; + + // Convert to game coordinates (0-63 for X/Y within map) + uint8_t game_x = static_cast(map_local_x / 16); + uint8_t game_y = static_cast(map_local_y / 16); + + // Add new sprite to the game state array (ZScream: SpriteMode.cs:34-35) + auto& sprites = *overworld->mutable_sprites(game_state); + + // Create new sprite + zelda3::Sprite new_sprite( + current_ow_map->current_graphics(), + static_cast(map_id), + sprite_id, // Sprite ID (user will configure in popup) + game_x, // X position in map coordinates + game_y, // Y position in map coordinates + static_cast(snapped_pos.x), // Real X (world coordinates) + static_cast(snapped_pos.y) // Real Y (world coordinates) + ); + + sprites.push_back(new_sprite); + + // Return pointer to the newly added sprite + zelda3::Sprite* inserted_sprite = &sprites.back(); + + LOG_DEBUG("EntityOps", "Inserted sprite at game_state=%d: pos=(%d,%d) map=0x%02X id=0x%02X", + game_state, inserted_sprite->x_, inserted_sprite->y_, map_id, sprite_id); + + return inserted_sprite; +} + +absl::StatusOr InsertItem( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + uint8_t item_id) { + + if (!overworld || !overworld->is_loaded()) { + return absl::FailedPreconditionError("Overworld not loaded"); + } + + // Snap to 16x16 grid and clamp to bounds (ZScream: ItemMode.cs similar logic) + ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); + + // Get parent map ID (ZScream: ItemMode.cs:60-64) + auto* current_ow_map = overworld->overworld_map(current_map); + uint8_t map_id = GetParentMapId(current_ow_map, current_map); + + // Calculate game coordinates (0-63 for X/Y within map) + // Following LoadItems logic in overworld.cc:840-854 + int fake_id = current_map % 0x40; + int sy = fake_id / 8; + int sx = fake_id - (sy * 8); + + // Calculate map-local coordinates + int map_local_x = static_cast(snapped_pos.x) % 512; + int map_local_y = static_cast(snapped_pos.y) % 512; + + // Game coordinates (0-63 range) + uint8_t game_x = static_cast(map_local_x / 16); + uint8_t game_y = static_cast(map_local_y / 16); + + // Add new item to the all_items array (ZScream: ItemMode.cs:92-108) + auto& items = *overworld->mutable_all_items(); + + // Create new item with calculated coordinates + items.emplace_back( + item_id, // Item ID + static_cast(map_id), // Room map ID + static_cast(snapped_pos.x), // X (world coordinates) + static_cast(snapped_pos.y), // Y (world coordinates) + false // Not deleted + ); + + // Set game coordinates + zelda3::OverworldItem* inserted_item = &items.back(); + inserted_item->game_x_ = game_x; + inserted_item->game_y_ = game_y; + + LOG_DEBUG("EntityOps", "Inserted item: pos=(%d,%d) game=(%d,%d) map=0x%02X id=0x%02X", + inserted_item->x_, inserted_item->y_, game_x, game_y, map_id, item_id); + + return inserted_item; +} + +} // namespace editor +} // namespace yaze + diff --git a/src/app/editor/overworld/entity_operations.h b/src/app/editor/overworld/entity_operations.h new file mode 100644 index 00000000..39f821a2 --- /dev/null +++ b/src/app/editor/overworld/entity_operations.h @@ -0,0 +1,136 @@ +#ifndef YAZE_APP_EDITOR_OVERWORLD_ENTITY_OPERATIONS_H +#define YAZE_APP_EDITOR_OVERWORLD_ENTITY_OPERATIONS_H + +#include "absl/status/statusor.h" +#include "zelda3/overworld/overworld.h" +#include "zelda3/overworld/overworld_entrance.h" +#include "zelda3/overworld/overworld_exit.h" +#include "zelda3/overworld/overworld_item.h" +#include "zelda3/sprite/sprite.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace editor { + +/** + * @brief Flat helper functions for entity insertion/manipulation + * + * Following ZScream's entity management pattern (EntranceMode.cs, ExitMode.cs, etc.) + * but implemented as free functions to minimize state management. + * + * Key concepts from ZScream: + * - Find first deleted slot for insertion + * - Calculate map position from mouse coordinates + * - Use parent map ID for multi-area maps + * - Call UpdateMapProperties to sync position data + */ + +/** + * @brief Insert a new entrance at the specified position + * + * Follows ZScream's EntranceMode.AddEntrance() logic (EntranceMode.cs:53-148): + * - Finds first deleted entrance slot + * - Snaps position to 16x16 grid + * - Uses parent map ID for multi-area maps + * - Calls UpdateMapProperties to calculate game coordinates + * + * @param overworld Overworld data containing entrance arrays + * @param mouse_pos Mouse position in canvas coordinates (world space) + * @param current_map Current map index being edited + * @param is_hole True to insert a hole instead of entrance + * @return Pointer to newly inserted entrance, or error if no slots available + */ +absl::StatusOr InsertEntrance( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + bool is_hole = false); + +/** + * @brief Insert a new exit at the specified position + * + * Follows ZScream's ExitMode.AddExit() logic (ExitMode.cs:59-124): + * - Finds first deleted exit slot + * - Snaps position to 16x16 grid + * - Initializes exit with default scroll/camera values + * - Sets room ID to 0 (needs to be configured by user) + * + * @param overworld Overworld data containing exit arrays + * @param mouse_pos Mouse position in canvas coordinates + * @param current_map Current map index being edited + * @return Pointer to newly inserted exit, or error if no slots available + */ +absl::StatusOr InsertExit( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map); + +/** + * @brief Insert a new sprite at the specified position + * + * Follows ZScream's SpriteMode sprite insertion (SpriteMode.cs:27-100): + * - Adds new sprite to game state array + * - Calculates map position and game coordinates + * - Sets sprite ID (default 0, user configures in popup) + * + * @param overworld Overworld data containing sprite arrays + * @param mouse_pos Mouse position in canvas coordinates + * @param current_map Current map index being edited + * @param game_state Current game state (0=beginning, 1=zelda, 2=agahnim) + * @param sprite_id Sprite ID to insert (default 0) + * @return Pointer to newly inserted sprite + */ +absl::StatusOr InsertSprite( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + int game_state, uint8_t sprite_id = 0); + +/** + * @brief Insert a new item at the specified position + * + * Follows ZScream's ItemMode item insertion (ItemMode.cs:54-113): + * - Adds new item to all_items array + * - Calculates map position and game coordinates + * - Sets item ID (default 0, user configures in popup) + * + * @param overworld Overworld data containing item arrays + * @param mouse_pos Mouse position in canvas coordinates + * @param current_map Current map index being edited + * @param item_id Item ID to insert (default 0x00 - Nothing) + * @return Pointer to newly inserted item + */ +absl::StatusOr InsertItem( + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + uint8_t item_id = 0); + +/** + * @brief Helper to get parent map ID for multi-area maps + * + * Returns the parent map ID, handling the case where a map is its own parent. + * Matches ZScream logic where ParentID == 255 means use current map. + */ +inline uint8_t GetParentMapId(const zelda3::OverworldMap* map, int current_map) { + uint8_t parent = map->parent(); + return (parent == 0xFF) ? static_cast(current_map) : parent; +} + +/** + * @brief Snap position to 16x16 grid (standard entity positioning) + */ +inline ImVec2 SnapToEntityGrid(ImVec2 pos) { + return ImVec2( + static_cast(static_cast(pos.x / 16) * 16), + static_cast(static_cast(pos.y / 16) * 16) + ); +} + +/** + * @brief Clamp position to valid overworld bounds + */ +inline ImVec2 ClampToOverworldBounds(ImVec2 pos) { + return ImVec2( + std::clamp(pos.x, 0.0f, 4080.0f), // 4096 - 16 + std::clamp(pos.y, 0.0f, 4080.0f) + ); +} + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_OPERATIONS_H + diff --git a/src/app/editor/overworld/map_properties.cc b/src/app/editor/overworld/map_properties.cc index 1a502a7a..15dbacec 100644 --- a/src/app/editor/overworld/map_properties.cc +++ b/src/app/editor/overworld/map_properties.cc @@ -7,6 +7,7 @@ #include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" +#include "app/gui/core/layout_helpers.h" #include "zelda3/overworld/overworld_map.h" #include "imgui/imgui.h" @@ -410,10 +411,68 @@ void MapPropertiesSystem::DrawOverlayEditor(int current_map, void MapPropertiesSystem::SetupCanvasContextMenu( gui::Canvas& canvas, int current_map, bool current_map_lock, bool& show_map_properties_panel, bool& show_custom_bg_color_editor, - bool& show_overlay_editor) { + bool& show_overlay_editor, int current_mode) { (void)current_map; // Used for future context-sensitive menu items // Clear any existing context menu items canvas.ClearContextMenuItems(); + + // Add entity insertion submenu (only in MOUSE mode) + if (current_mode == 0 && entity_insert_callback_) { // 0 = EditingMode::MOUSE + gui::Canvas::ContextMenuItem entity_menu; + entity_menu.label = ICON_MD_ADD_LOCATION " Insert Entity"; + + // Entrance submenu item + gui::Canvas::ContextMenuItem entrance_item; + entrance_item.label = ICON_MD_DOOR_FRONT " Entrance"; + entrance_item.callback = [this]() { + if (entity_insert_callback_) { + entity_insert_callback_("entrance"); + } + }; + entity_menu.subitems.push_back(entrance_item); + + // Hole submenu item + gui::Canvas::ContextMenuItem hole_item; + hole_item.label = ICON_MD_CYCLONE " Hole"; + hole_item.callback = [this]() { + if (entity_insert_callback_) { + entity_insert_callback_("hole"); + } + }; + entity_menu.subitems.push_back(hole_item); + + // Exit submenu item + gui::Canvas::ContextMenuItem exit_item; + exit_item.label = ICON_MD_DOOR_BACK " Exit"; + exit_item.callback = [this]() { + if (entity_insert_callback_) { + entity_insert_callback_("exit"); + } + }; + entity_menu.subitems.push_back(exit_item); + + // Item submenu item + gui::Canvas::ContextMenuItem item_item; + item_item.label = ICON_MD_GRASS " Item"; + item_item.callback = [this]() { + if (entity_insert_callback_) { + entity_insert_callback_("item"); + } + }; + entity_menu.subitems.push_back(item_item); + + // Sprite submenu item + gui::Canvas::ContextMenuItem sprite_item; + sprite_item.label = ICON_MD_PEST_CONTROL_RODENT " Sprite"; + sprite_item.callback = [this]() { + if (entity_insert_callback_) { + entity_insert_callback_("sprite"); + } + }; + entity_menu.subitems.push_back(sprite_item); + + canvas.AddContextMenuItem(entity_menu); + } // Add overworld-specific context menu items gui::Canvas::ContextMenuItem lock_item; @@ -476,18 +535,16 @@ void MapPropertiesSystem::SetupCanvasContextMenu( canvas.set_global_scale(scale); }; canvas.AddContextMenuItem(zoom_out_item); - - // Entity Operations submenu will be added in future iteration - // For now, users can use keyboard shortcuts (3-8) to activate entity editing } // Private method implementations void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { if (ImGui::BeginPopup("GraphicsPopup")) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); + // Use theme-aware spacing instead of hardcoded constants + float spacing = gui::LayoutHelpers::GetStandardSpacing(); + float padding = gui::LayoutHelpers::GetButtonPadding(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(padding, padding)); ImGui::Text("Graphics Settings"); ImGui::Separator(); @@ -589,10 +646,11 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state, bool& show_custom_bg_color_editor) { if (ImGui::BeginPopup("PalettesPopup")) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); + // Use theme-aware spacing instead of hardcoded constants + float spacing = gui::LayoutHelpers::GetStandardSpacing(); + float padding = gui::LayoutHelpers::GetButtonPadding(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(padding, padding)); ImGui::Text("Palette Settings"); ImGui::Separator(); @@ -651,10 +709,11 @@ void MapPropertiesSystem::DrawPropertiesPopup(int current_map, bool& show_overlay_preview, int& game_state) { if (ImGui::BeginPopup("ConfigPopup")) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); + // Use theme-aware spacing instead of hardcoded constants + float spacing = gui::LayoutHelpers::GetStandardSpacing(); + float padding = gui::LayoutHelpers::GetButtonPadding(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(padding, padding)); ImGui::Text(ICON_MD_TUNE " Area Configuration"); ImGui::Separator(); @@ -1590,10 +1649,11 @@ void MapPropertiesSystem::DrawOverlayPreviewOnMap(int current_map, void MapPropertiesSystem::DrawViewPopup() { if (ImGui::BeginPopup("ViewPopup")) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); + // Use theme-aware spacing instead of hardcoded constants + float spacing = gui::LayoutHelpers::GetStandardSpacing(); + float padding = gui::LayoutHelpers::GetButtonPadding(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(padding, padding)); ImGui::Text("View Controls"); ImGui::Separator(); @@ -1624,10 +1684,11 @@ void MapPropertiesSystem::DrawViewPopup() { void MapPropertiesSystem::DrawQuickAccessPopup() { if (ImGui::BeginPopup("QuickPopup")) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2(kCompactItemSpacing, kCompactFramePadding)); + // Use theme-aware spacing instead of hardcoded constants + float spacing = gui::LayoutHelpers::GetStandardSpacing(); + float padding = gui::LayoutHelpers::GetButtonPadding(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(padding, padding)); ImGui::Text("Quick Access"); ImGui::Separator(); diff --git a/src/app/editor/overworld/map_properties.h b/src/app/editor/overworld/map_properties.h index 7fba9d65..5b508e57 100644 --- a/src/app/editor/overworld/map_properties.h +++ b/src/app/editor/overworld/map_properties.h @@ -41,6 +41,12 @@ class MapPropertiesSystem { refresh_tile16_blockset_ = std::move(refresh_tile16_blockset); force_refresh_graphics_ = std::move(force_refresh_graphics); } + + // Set callbacks for entity operations + void SetEntityCallbacks( + std::function insert_callback) { + entity_insert_callback_ = std::move(insert_callback); + } // Main interface methods void DrawSimplifiedMapSettings(int& current_world, int& current_map, @@ -60,7 +66,7 @@ class MapPropertiesSystem { // Context menu integration void SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, bool current_map_lock, bool& show_map_properties_panel, bool& show_custom_bg_color_editor, - bool& show_overlay_editor); + bool& show_overlay_editor, int current_mode = 0); private: // Property category drawers @@ -108,6 +114,9 @@ class MapPropertiesSystem { RefreshPaletteCallback refresh_tile16_blockset_; ForceRefreshGraphicsCallback force_refresh_graphics_; + // Callback for entity insertion (generic, editor handles entity types) + std::function entity_insert_callback_; + // Using centralized UI constants from ui_constants.h }; diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 4662ae49..be6d6d6a 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -18,6 +18,7 @@ #include "core/features.h" #include "app/editor/overworld/map_properties.h" #include "app/editor/overworld/entity.h" +#include "app/editor/overworld/entity_operations.h" #include "app/editor/overworld/tile16_editor.h" #include "app/gfx/resource/arena.h" #include "app/gfx/core/bitmap.h" @@ -189,6 +190,14 @@ absl::Status OverworldEditor::Load() { LOG_DEBUG("OverworldEditor", "Overworld editor refreshed after Tile16 changes"); return absl::OkStatus(); }); + + // Set up entity insertion callback for MapPropertiesSystem + if (map_properties_system_) { + map_properties_system_->SetEntityCallbacks( + [this](const std::string& entity_type) { + HandleEntityInsertion(entity_type); + }); + } ASSIGN_OR_RETURN(entrance_tiletypes_, zelda3::LoadEntranceTileTypes(rom_)); all_gfx_loaded_ = true; @@ -476,11 +485,17 @@ void OverworldEditor::DrawToolset() { toolbar.BeginModeGroup(); if (toolbar.ModeButton(ICON_MD_MOUSE, current_mode == EditingMode::MOUSE, "Mouse Mode (1)\nNavigate, pan, and manage entities")) { - current_mode = EditingMode::MOUSE; + if (current_mode != EditingMode::MOUSE) { + current_mode = EditingMode::MOUSE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); + } } if (toolbar.ModeButton(ICON_MD_DRAW, current_mode == EditingMode::DRAW_TILE, "Tile Paint Mode (2)\nDraw tiles on the map")) { - current_mode = EditingMode::DRAW_TILE; + if (current_mode != EditingMode::DRAW_TILE) { + current_mode = EditingMode::DRAW_TILE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kTilePainting); + } } toolbar.EndModeGroup(); @@ -647,6 +662,8 @@ void OverworldEditor::DrawToolset() { // Keyboard shortcuts for the Overworld Editor if (!ImGui::IsAnyItemActive()) { using enum EditingMode; + + EditingMode old_mode = current_mode; // Tool shortcuts (simplified) if (ImGui::IsKeyDown(ImGuiKey_1)) { @@ -655,25 +672,40 @@ void OverworldEditor::DrawToolset() { current_mode = EditingMode::DRAW_TILE; } + // Update canvas usage mode when mode changes + if (old_mode != current_mode) { + if (current_mode == EditingMode::MOUSE) { + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); + } else if (current_mode == EditingMode::DRAW_TILE) { + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kTilePainting); + } + } + // Entity editing shortcuts (3-8) if (ImGui::IsKeyDown(ImGuiKey_3)) { entity_edit_mode_ = EntityEditMode::ENTRANCES; current_mode = EditingMode::MOUSE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); } else if (ImGui::IsKeyDown(ImGuiKey_4)) { entity_edit_mode_ = EntityEditMode::EXITS; current_mode = EditingMode::MOUSE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); } else if (ImGui::IsKeyDown(ImGuiKey_5)) { entity_edit_mode_ = EntityEditMode::ITEMS; current_mode = EditingMode::MOUSE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); } else if (ImGui::IsKeyDown(ImGuiKey_6)) { entity_edit_mode_ = EntityEditMode::SPRITES; current_mode = EditingMode::MOUSE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); } else if (ImGui::IsKeyDown(ImGuiKey_7)) { entity_edit_mode_ = EntityEditMode::TRANSPORTS; current_mode = EditingMode::MOUSE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); } else if (ImGui::IsKeyDown(ImGuiKey_8)) { entity_edit_mode_ = EntityEditMode::MUSIC; current_mode = EditingMode::MOUSE; + ow_map_canvas_.SetUsageMode(gui::CanvasUsage::kEntityManipulation); } // View shortcuts @@ -1455,7 +1487,7 @@ void OverworldEditor::DrawOverworldCanvas() { map_properties_system_->SetupCanvasContextMenu( ow_map_canvas_, current_map_, current_map_lock_, show_map_properties_panel_, show_custom_bg_color_editor_, - show_overlay_editor_); + show_overlay_editor_, static_cast(current_mode)); } // Handle pan and zoom (works in all modes) @@ -2971,4 +3003,81 @@ int OverworldEditor::AutomationGetTile(int x, int y) { return overworld_.GetTile(x, y); } +void OverworldEditor::HandleEntityInsertion(const std::string& entity_type) { + if (!overworld_.is_loaded()) { + LOG_ERROR("OverworldEditor", "Cannot insert entity: overworld not loaded"); + return; + } + + // Get mouse position from canvas (in world coordinates) + ImVec2 mouse_pos = ow_map_canvas_.hover_mouse_pos(); + + LOG_DEBUG("OverworldEditor", "Inserting entity type='%s' at pos=(%f,%f) map=%d", + entity_type.c_str(), mouse_pos.x, mouse_pos.y, current_map_); + + if (entity_type == "entrance") { + auto result = InsertEntrance(&overworld_, mouse_pos, current_map_, false); + if (result.ok()) { + current_entrance_ = **result; + current_entity_ = *result; + ImGui::OpenPopup("Entrance Editor"); + rom_->set_dirty(true); + } else { + LOG_ERROR("OverworldEditor", "Failed to insert entrance: %s", + result.status().message().data()); + } + + } else if (entity_type == "hole") { + auto result = InsertEntrance(&overworld_, mouse_pos, current_map_, true); + if (result.ok()) { + current_entrance_ = **result; + current_entity_ = *result; + ImGui::OpenPopup("Entrance Editor"); + rom_->set_dirty(true); + } else { + LOG_ERROR("OverworldEditor", "Failed to insert hole: %s", + result.status().message().data()); + } + + } else if (entity_type == "exit") { + auto result = InsertExit(&overworld_, mouse_pos, current_map_); + if (result.ok()) { + current_exit_ = **result; + current_entity_ = *result; + ImGui::OpenPopup("Exit editor"); + rom_->set_dirty(true); + } else { + LOG_ERROR("OverworldEditor", "Failed to insert exit: %s", + result.status().message().data()); + } + + } else if (entity_type == "item") { + auto result = InsertItem(&overworld_, mouse_pos, current_map_, 0x00); + if (result.ok()) { + current_item_ = **result; + current_entity_ = *result; + ImGui::OpenPopup("Item editor"); + rom_->set_dirty(true); + } else { + LOG_ERROR("OverworldEditor", "Failed to insert item: %s", + result.status().message().data()); + } + + } else if (entity_type == "sprite") { + auto result = InsertSprite(&overworld_, mouse_pos, current_map_, game_state_, 0x00); + if (result.ok()) { + current_sprite_ = **result; + current_entity_ = *result; + ImGui::OpenPopup("Sprite editor"); + rom_->set_dirty(true); + } else { + LOG_ERROR("OverworldEditor", "Failed to insert sprite: %s", + result.status().message().data()); + } + + } else { + LOG_WARN("OverworldEditor", "Unknown entity type: %s", entity_type.c_str()); + } +} + } // namespace yaze::editor \ No newline at end of file diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index 49ef6ab0..353d90ae 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -126,6 +126,16 @@ class OverworldEditor : public Editor, public gfx::GfxContext { * assembling the OverworldMap Bitmap objects. */ absl::Status LoadGraphics(); + + /** + * @brief Handle entity insertion from context menu + * + * Delegates to flat helper functions in entity_operations.cc + * following ZScream's pattern for entity management. + * + * @param entity_type Type of entity to insert ("entrance", "hole", "exit", "item", "sprite") + */ + void HandleEntityInsertion(const std::string& entity_type); private: void DrawFullscreenCanvas(); diff --git a/src/app/gui/canvas/canvas.cc b/src/app/gui/canvas/canvas.cc index 60507745..d3e08049 100644 --- a/src/app/gui/canvas/canvas.cc +++ b/src/app/gui/canvas/canvas.cc @@ -163,44 +163,35 @@ void Canvas::Cleanup() { void Canvas::InitializeEnhancedComponents() { // Initialize modals system - modals_ = std::make_unique(); + modals_ = std::make_unique(); // Initialize context menu system - context_menu_ = std::make_unique(); + context_menu_ = std::make_unique(); context_menu_->Initialize(canvas_id_); - // Initialize usage tracker - usage_tracker_ = std::make_shared(); - usage_tracker_->Initialize(canvas_id_); - canvas::CanvasUsageManager::Get().RegisterTracker(canvas_id_, usage_tracker_); + // Initialize usage tracker (optional, controlled by config.enable_metrics) + if (config_.enable_metrics) { + usage_tracker_ = std::make_shared(); + usage_tracker_->Initialize(canvas_id_); + usage_tracker_->StartSession(); - // Initialize performance integration - performance_integration_ = - std::make_shared(); - performance_integration_->Initialize(canvas_id_); - performance_integration_->SetUsageTracker(usage_tracker_); - canvas::CanvasPerformanceManager::Get().RegisterIntegration( - canvas_id_, performance_integration_); - - // Start performance monitoring - performance_integration_->StartMonitoring(); - usage_tracker_->StartSession(); + // Initialize performance integration + performance_integration_ = + std::make_shared(); + performance_integration_->Initialize(canvas_id_); + performance_integration_->SetUsageTracker(usage_tracker_); + performance_integration_->StartMonitoring(); + } } -void Canvas::SetUsageMode(canvas::CanvasUsage usage) { +void Canvas::SetUsageMode(CanvasUsage usage) { if (usage_tracker_) { usage_tracker_->SetUsageMode(usage); } if (context_menu_) { context_menu_->SetUsageMode(usage); } -} - -canvas::CanvasUsage Canvas::GetUsageMode() const { - if (usage_tracker_) { - return usage_tracker_->GetCurrentStats().usage_mode; - } - return canvas::CanvasUsage::kUnknown; + config_.usage_mode = usage; } void Canvas::RecordCanvasOperation(const std::string& operation_name, @@ -210,7 +201,7 @@ void Canvas::RecordCanvasOperation(const std::string& operation_name, } if (performance_integration_) { performance_integration_->RecordOperation(operation_name, time_ms, - GetUsageMode()); + usage_mode()); } } @@ -455,7 +446,7 @@ void Canvas::DrawContextMenu() { // Use enhanced context menu if available if (context_menu_) { - canvas::CanvasConfig snapshot; + CanvasConfig snapshot; snapshot.canvas_size = canvas_sz_; snapshot.content_size = config_.content_size; snapshot.global_scale = global_scale_; @@ -476,78 +467,78 @@ void Canvas::DrawContextMenu() { context_menu_->Render( context_id_, mouse_pos, rom_, bitmap_, bitmap_ ? bitmap_->mutable_palette() : nullptr, - [this](canvas::CanvasContextMenu::Command command, - const canvas::CanvasConfig& updated_config) { + [this](CanvasContextMenu::Command command, + const CanvasConfig& updated_config) { switch (command) { - case canvas::CanvasContextMenu::Command::kResetView: + case CanvasContextMenu::Command::kResetView: ResetView(); break; - case canvas::CanvasContextMenu::Command::kZoomToFit: + case CanvasContextMenu::Command::kZoomToFit: if (bitmap_) { SetZoomToFit(*bitmap_); } break; - case canvas::CanvasContextMenu::Command::kZoomIn: + case CanvasContextMenu::Command::kZoomIn: SetGlobalScale(config_.global_scale * 1.25f); break; - case canvas::CanvasContextMenu::Command::kZoomOut: + case CanvasContextMenu::Command::kZoomOut: SetGlobalScale(config_.global_scale * 0.8f); break; - case canvas::CanvasContextMenu::Command::kToggleGrid: + case CanvasContextMenu::Command::kToggleGrid: config_.enable_grid = !config_.enable_grid; enable_grid_ = config_.enable_grid; break; - case canvas::CanvasContextMenu::Command::kToggleHexLabels: + case CanvasContextMenu::Command::kToggleHexLabels: config_.enable_hex_labels = !config_.enable_hex_labels; enable_hex_tile_labels_ = config_.enable_hex_labels; break; - case canvas::CanvasContextMenu::Command::kToggleCustomLabels: + case CanvasContextMenu::Command::kToggleCustomLabels: config_.enable_custom_labels = !config_.enable_custom_labels; enable_custom_labels_ = config_.enable_custom_labels; break; - case canvas::CanvasContextMenu::Command::kToggleContextMenu: + case CanvasContextMenu::Command::kToggleContextMenu: config_.enable_context_menu = !config_.enable_context_menu; enable_context_menu_ = config_.enable_context_menu; break; - case canvas::CanvasContextMenu::Command::kToggleAutoResize: + case CanvasContextMenu::Command::kToggleAutoResize: config_.auto_resize = !config_.auto_resize; break; - case canvas::CanvasContextMenu::Command::kToggleDraggable: + case CanvasContextMenu::Command::kToggleDraggable: config_.is_draggable = !config_.is_draggable; draggable_ = config_.is_draggable; break; - case canvas::CanvasContextMenu::Command::kSetGridStep: + case CanvasContextMenu::Command::kSetGridStep: config_.grid_step = updated_config.grid_step; custom_step_ = config_.grid_step; break; - case canvas::CanvasContextMenu::Command::kSetScale: + case CanvasContextMenu::Command::kSetScale: config_.global_scale = updated_config.global_scale; global_scale_ = config_.global_scale; break; - case canvas::CanvasContextMenu::Command::kOpenAdvancedProperties: + case CanvasContextMenu::Command::kOpenAdvancedProperties: if (modals_) { - canvas::CanvasConfig modal_config = updated_config; + CanvasConfig modal_config = updated_config; modal_config.on_config_changed = - [this](const canvas::CanvasConfig& cfg) { + [this](const CanvasConfig& cfg) { ApplyConfigSnapshot(cfg); }; modal_config.on_scale_changed = - [this](const canvas::CanvasConfig& cfg) { + [this](const CanvasConfig& cfg) { ApplyScaleSnapshot(cfg); }; modals_->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_); } break; - case canvas::CanvasContextMenu::Command::kOpenScalingControls: + case CanvasContextMenu::Command::kOpenScalingControls: if (modals_) { - canvas::CanvasConfig modal_config = updated_config; + CanvasConfig modal_config = updated_config; modal_config.on_config_changed = - [this](const canvas::CanvasConfig& cfg) { + [this](const CanvasConfig& cfg) { ApplyConfigSnapshot(cfg); }; modal_config.on_scale_changed = - [this](const canvas::CanvasConfig& cfg) { + [this](const CanvasConfig& cfg) { ApplyScaleSnapshot(cfg); }; modals_->ShowScalingControls(canvas_id_, modal_config, bitmap_); @@ -695,7 +686,7 @@ void Canvas::ResetView() { scrolling_ = ImVec2(0, 0); } -void Canvas::ApplyConfigSnapshot(const canvas::CanvasConfig& snapshot) { +void Canvas::ApplyConfigSnapshot(const CanvasConfig& snapshot) { config_.enable_grid = snapshot.enable_grid; config_.enable_hex_labels = snapshot.enable_hex_labels; config_.enable_custom_labels = snapshot.enable_custom_labels; @@ -719,7 +710,7 @@ void Canvas::ApplyConfigSnapshot(const canvas::CanvasConfig& snapshot) { scrolling_ = snapshot.scrolling; } -void Canvas::ApplyScaleSnapshot(const canvas::CanvasConfig& snapshot) { +void Canvas::ApplyScaleSnapshot(const CanvasConfig& snapshot) { config_.global_scale = snapshot.global_scale; global_scale_ = config_.global_scale; scrolling_ = snapshot.scrolling; @@ -1586,7 +1577,7 @@ void TableCanvasPipeline(gui::Canvas& canvas, gfx::Bitmap& bitmap, void Canvas::ShowAdvancedCanvasProperties() { // Use the new modal system if available if (modals_) { - canvas::CanvasConfig modal_config; + CanvasConfig modal_config; modal_config.canvas_size = canvas_sz_; modal_config.content_size = config_.content_size; modal_config.global_scale = global_scale_; @@ -1599,14 +1590,14 @@ void Canvas::ShowAdvancedCanvasProperties() { modal_config.auto_resize = config_.auto_resize; modal_config.scrolling = scrolling_; modal_config.on_config_changed = - [this](const canvas::CanvasConfig& updated_config) { + [this](const CanvasConfig& updated_config) { // Update legacy variables when config changes enable_grid_ = updated_config.enable_grid; enable_hex_tile_labels_ = updated_config.enable_hex_labels; enable_custom_labels_ = updated_config.enable_custom_labels; }; modal_config.on_scale_changed = - [this](const canvas::CanvasConfig& updated_config) { + [this](const CanvasConfig& updated_config) { global_scale_ = updated_config.global_scale; scrolling_ = updated_config.scrolling; }; @@ -1709,7 +1700,7 @@ void Canvas::ShowAdvancedCanvasProperties() { void Canvas::ShowScalingControls() { // Use the new modal system if available if (modals_) { - canvas::CanvasConfig modal_config; + CanvasConfig modal_config; modal_config.canvas_size = canvas_sz_; modal_config.content_size = config_.content_size; modal_config.global_scale = global_scale_; @@ -1722,7 +1713,7 @@ void Canvas::ShowScalingControls() { modal_config.auto_resize = config_.auto_resize; modal_config.scrolling = scrolling_; modal_config.on_config_changed = - [this](const canvas::CanvasConfig& updated_config) { + [this](const CanvasConfig& updated_config) { // Update legacy variables when config changes enable_grid_ = updated_config.enable_grid; enable_hex_tile_labels_ = updated_config.enable_hex_labels; @@ -1730,7 +1721,7 @@ void Canvas::ShowScalingControls() { enable_context_menu_ = updated_config.enable_context_menu; }; modal_config.on_scale_changed = - [this](const canvas::CanvasConfig& updated_config) { + [this](const CanvasConfig& updated_config) { draggable_ = updated_config.is_draggable; custom_step_ = updated_config.grid_step; global_scale_ = updated_config.global_scale; diff --git a/src/app/gui/canvas/canvas.h b/src/app/gui/canvas/canvas.h index cb7eb9f6..0b83820e 100644 --- a/src/app/gui/canvas/canvas.h +++ b/src/app/gui/canvas/canvas.h @@ -187,11 +187,11 @@ class Canvas { std::unique_ptr bpp_comparison_tool_; // Enhanced canvas components - std::unique_ptr modals_; - std::unique_ptr context_menu_; - std::shared_ptr usage_tracker_; - std::shared_ptr performance_integration_; - canvas::CanvasInteractionHandler interaction_handler_; + std::unique_ptr modals_; + std::unique_ptr context_menu_; + std::shared_ptr usage_tracker_; + std::shared_ptr performance_integration_; + CanvasInteractionHandler interaction_handler_; void AddContextMenuItem(const ContextMenuItem& item); void ClearContextMenuItems(); @@ -207,8 +207,8 @@ class Canvas { void ShowScalingControls(); void SetZoomToFit(const gfx::Bitmap& bitmap); void ResetView(); - void ApplyConfigSnapshot(const canvas::CanvasConfig& snapshot); - void ApplyScaleSnapshot(const canvas::CanvasConfig& snapshot); + void ApplyConfigSnapshot(const CanvasConfig& snapshot); + void ApplyScaleSnapshot(const CanvasConfig& snapshot); // Modular component access CanvasConfig& GetConfig() { return config_; } @@ -231,15 +231,16 @@ class Canvas { // Enhanced canvas management void InitializeEnhancedComponents(); - void SetUsageMode(canvas::CanvasUsage usage); - canvas::CanvasUsage GetUsageMode() const; + void SetUsageMode(CanvasUsage usage); + auto usage_mode() const { return config_.usage_mode; } + void RecordCanvasOperation(const std::string& operation_name, double time_ms); void ShowPerformanceUI(); void ShowUsageReport(); // Interaction handler access - canvas::CanvasInteractionHandler& GetInteractionHandler() { return interaction_handler_; } - const canvas::CanvasInteractionHandler& GetInteractionHandler() const { return interaction_handler_; } + CanvasInteractionHandler& GetInteractionHandler() { return interaction_handler_; } + const CanvasInteractionHandler& GetInteractionHandler() const { return interaction_handler_; } // Automation API access (Phase 4A) CanvasAutomationAPI* GetAutomationAPI(); diff --git a/src/app/gui/canvas/canvas_context_menu.cc b/src/app/gui/canvas/canvas_context_menu.cc index b2efd914..1492b047 100644 --- a/src/app/gui/canvas/canvas_context_menu.cc +++ b/src/app/gui/canvas/canvas_context_menu.cc @@ -12,7 +12,6 @@ namespace yaze { namespace gui { -namespace canvas { namespace { inline void Dispatch( @@ -506,6 +505,7 @@ std::string CanvasContextMenu::GetUsageModeName(CanvasUsage usage) const { case CanvasUsage::kPaletteEditing: return "Palette Editing"; case CanvasUsage::kBppConversion: return "BPP Conversion"; case CanvasUsage::kPerformanceMode: return "Performance Mode"; + case CanvasUsage::kEntityManipulation: return "Entity Manipulation"; case CanvasUsage::kUnknown: return "Unknown"; default: return "Unknown"; } @@ -521,6 +521,7 @@ ImVec4 CanvasContextMenu::GetUsageModeColor(CanvasUsage usage) const { case CanvasUsage::kPaletteEditing: return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple case CanvasUsage::kBppConversion: return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan case CanvasUsage::kPerformanceMode: return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red + case CanvasUsage::kEntityManipulation: return ImVec4(0.4F, 0.8F, 1.0F, 1.0F); // Light Blue case CanvasUsage::kUnknown: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray default: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray } @@ -578,36 +579,35 @@ void CanvasContextMenu::CreateDefaultMenuItems() { usage_specific_items_[CanvasUsage::kPerformanceMode].push_back(perf_item); } -canvas::CanvasContextMenu::ContextMenuItem canvas::CanvasContextMenu::CreateViewMenuItem(const std::string& label, +CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateViewMenuItem(const std::string& label, const std::string& icon, std::function callback) { return ContextMenuItem(label, icon, callback); } -canvas::CanvasContextMenu::ContextMenuItem canvas::CanvasContextMenu::CreateBitmapMenuItem(const std::string& label, +CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateBitmapMenuItem(const std::string& label, const std::string& icon, std::function callback) { return ContextMenuItem(label, icon, callback); } -canvas::CanvasContextMenu::ContextMenuItem canvas::CanvasContextMenu::CreatePaletteMenuItem(const std::string& label, +CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreatePaletteMenuItem(const std::string& label, const std::string& icon, std::function callback) { return ContextMenuItem(label, icon, callback); } -canvas::CanvasContextMenu::ContextMenuItem canvas::CanvasContextMenu::CreateBppMenuItem(const std::string& label, +CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateBppMenuItem(const std::string& label, const std::string& icon, std::function callback) { return ContextMenuItem(label, icon, callback); } -canvas::CanvasContextMenu::ContextMenuItem canvas::CanvasContextMenu::CreatePerformanceMenuItem(const std::string& label, +CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreatePerformanceMenuItem(const std::string& label, const std::string& icon, std::function callback) { return ContextMenuItem(label, icon, callback); } -} // namespace canvas } // namespace gui } // namespace yaze \ No newline at end of file diff --git a/src/app/gui/canvas/canvas_context_menu.h b/src/app/gui/canvas/canvas_context_menu.h index 96e6d7a8..6f19b93d 100644 --- a/src/app/gui/canvas/canvas_context_menu.h +++ b/src/app/gui/canvas/canvas_context_menu.h @@ -19,8 +19,6 @@ namespace gui { // Forward declarations class PaletteEditorWidget; -namespace canvas { - class CanvasContextMenu { public: enum class Command { @@ -71,7 +69,7 @@ class CanvasContextMenu { Rom* rom, const gfx::Bitmap* bitmap, const gfx::SnesPalette* palette, - const std::function& command_handler, + const std::function& command_handler, CanvasConfig current_config); bool ShouldShowContextMenu() const; @@ -159,7 +157,6 @@ class CanvasContextMenu { std::function callback); }; -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_interaction_handler.cc b/src/app/gui/canvas/canvas_interaction_handler.cc index cbdb979a..d9c5c07f 100644 --- a/src/app/gui/canvas/canvas_interaction_handler.cc +++ b/src/app/gui/canvas/canvas_interaction_handler.cc @@ -6,7 +6,6 @@ namespace yaze { namespace gui { -namespace canvas { namespace { @@ -364,6 +363,5 @@ bool CanvasInteractionHandler::IsMouseReleased(ImGuiMouseButton button) { return ImGui::IsMouseReleased(button); } -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_interaction_handler.h b/src/app/gui/canvas/canvas_interaction_handler.h index 584d19d3..4c6f3d17 100644 --- a/src/app/gui/canvas/canvas_interaction_handler.h +++ b/src/app/gui/canvas/canvas_interaction_handler.h @@ -8,7 +8,6 @@ namespace yaze { namespace gui { -namespace canvas { /** * @brief Tile interaction mode for canvas @@ -201,7 +200,6 @@ class CanvasInteractionHandler { bool IsMouseReleased(ImGuiMouseButton button); }; -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_modals.cc b/src/app/gui/canvas/canvas_modals.cc index 6b15f08f..52b5a5e5 100644 --- a/src/app/gui/canvas/canvas_modals.cc +++ b/src/app/gui/canvas/canvas_modals.cc @@ -13,21 +13,17 @@ namespace yaze { namespace gui { -namespace canvas { +// Helper functions for dispatching config callbacks namespace { -void DispatchConfigCallback(const std::function& callback, - const CanvasConfig& config) { - if (callback) { - callback(config); - } +inline void DispatchConfig(const std::function& callback, + const CanvasConfig& config) { + if (callback) callback(config); } -void DispatchScaleCallback(const std::function& callback, - const CanvasConfig& config) { - if (callback) { - callback(config); - } +inline void DispatchScale(const std::function& callback, + const CanvasConfig& config) { + if (callback) callback(config); } } // namespace @@ -214,7 +210,7 @@ void CanvasModals::RenderAdvancedPropertiesModal(const std::string& canvas_id, if (i > 0) ImGui::SameLine(); if (ImGui::Button(preset_labels[i])) { config.global_scale = preset_values[i]; - DispatchConfigCallback(config.on_config_changed, config); + DispatchConfig(config.on_config_changed, config); } } } @@ -225,14 +221,14 @@ void CanvasModals::RenderAdvancedPropertiesModal(const std::string& canvas_id, if (ImGui::Button("Reset Scroll")) { config.scrolling = ImVec2(0, 0); - DispatchConfigCallback(config.on_config_changed, config); + DispatchConfig(config.on_config_changed, config); } ImGui::SameLine(); if (ImGui::Button("Center View") && bitmap) { config.scrolling = ImVec2(-(bitmap->width() * config.global_scale - config.canvas_size.x) / 2.0f, -(bitmap->height() * config.global_scale - config.canvas_size.y) / 2.0f); - DispatchConfigCallback(config.on_config_changed, config); + DispatchConfig(config.on_config_changed, config); } } @@ -262,7 +258,7 @@ void CanvasModals::RenderAdvancedPropertiesModal(const std::string& canvas_id, ImGui::Spacing(); if (ImGui::Button("Apply Changes", ImVec2(120, 0))) { - DispatchConfigCallback(config.on_config_changed, config); + DispatchConfig(config.on_config_changed, config); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); @@ -282,7 +278,7 @@ void CanvasModals::RenderAdvancedPropertiesModal(const std::string& canvas_id, config.is_draggable = false; config.auto_resize = false; config.scrolling = ImVec2(0, 0); - DispatchConfigCallback(config.on_config_changed, config); + DispatchConfig(config.on_config_changed, config); } ImGui::EndPopup(); @@ -316,7 +312,7 @@ void CanvasModals::RenderScalingControlsModal(const std::string& canvas_id, if (i > 0) ImGui::SameLine(); if (ImGui::Button(preset_labels[i])) { config.global_scale = preset_values[i]; - DispatchScaleCallback(config.on_scale_changed, config); + DispatchScale(config.on_scale_changed, config); } } @@ -335,7 +331,7 @@ void CanvasModals::RenderScalingControlsModal(const std::string& canvas_id, if (i > 0) ImGui::SameLine(); if (ImGui::Button(grid_labels[i])) { config.grid_step = grid_values[i]; - DispatchScaleCallback(config.on_scale_changed, config); + DispatchScale(config.on_scale_changed, config); } } @@ -360,7 +356,7 @@ void CanvasModals::RenderScalingControlsModal(const std::string& canvas_id, ImGui::Spacing(); if (ImGui::Button("Apply", ImVec2(120, 0))) { - DispatchScaleCallback(config.on_scale_changed, config); + DispatchScale(config.on_scale_changed, config); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); @@ -586,6 +582,5 @@ void CanvasModals::RenderSliderWithIcon(const std::string& label, const std::str ImGui::SliderFloat(("##" + label).c_str(), value, min_val, max_val, format); } -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_modals.h b/src/app/gui/canvas/canvas_modals.h index c5ac15c6..ebb8c16b 100644 --- a/src/app/gui/canvas/canvas_modals.h +++ b/src/app/gui/canvas/canvas_modals.h @@ -12,33 +12,9 @@ namespace yaze { namespace gui { -namespace canvas { -void DispatchConfigCallback(const std::function& callback, - const CanvasConfig& config); -void DispatchScaleCallback(const std::function& callback, - const CanvasConfig& config); - -/** - * @brief Canvas configuration options for modals - */ -struct CanvasConfig { - ImVec2 canvas_size = ImVec2(0, 0); - ImVec2 content_size = ImVec2(0, 0); - float global_scale = 1.0f; - float grid_step = 32.0f; - bool enable_grid = true; - bool enable_hex_labels = false; - bool enable_custom_labels = false; - bool enable_context_menu = true; - bool is_draggable = false; - bool auto_resize = false; - ImVec2 scrolling = ImVec2(0, 0); - - // Callbacks provide updated configuration state - std::function on_config_changed; - std::function on_scale_changed; -}; +// Note: DispatchConfigCallback and DispatchScaleCallback are internal helpers +// defined in canvas_modals.cc (not part of public API) /** * @brief BPP conversion options @@ -175,7 +151,6 @@ class CanvasModals { const char* format = "%.2f"); }; -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_performance_integration.cc b/src/app/gui/canvas/canvas_performance_integration.cc index c76aee11..1a916073 100644 --- a/src/app/gui/canvas/canvas_performance_integration.cc +++ b/src/app/gui/canvas/canvas_performance_integration.cc @@ -12,7 +12,6 @@ namespace yaze { namespace gui { -namespace canvas { void CanvasPerformanceIntegration::Initialize(const std::string& canvas_id) { canvas_id_ = canvas_id; @@ -607,6 +606,5 @@ void CanvasPerformanceManager::ClearAllIntegrations() { LOG_DEBUG("CanvasPerformance", "Cleared all canvas performance integrations"); } -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_performance_integration.h b/src/app/gui/canvas/canvas_performance_integration.h index 2aa1b876..216e808b 100644 --- a/src/app/gui/canvas/canvas_performance_integration.h +++ b/src/app/gui/canvas/canvas_performance_integration.h @@ -13,7 +13,6 @@ namespace yaze { namespace gui { -namespace canvas { /** * @brief Canvas performance metrics @@ -262,7 +261,6 @@ class CanvasPerformanceManager { std::unordered_map> integrations_; }; -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_usage_tracker.cc b/src/app/gui/canvas/canvas_usage_tracker.cc index 4633877a..955ac6b6 100644 --- a/src/app/gui/canvas/canvas_usage_tracker.cc +++ b/src/app/gui/canvas/canvas_usage_tracker.cc @@ -9,7 +9,6 @@ namespace yaze { namespace gui { -namespace canvas { void CanvasUsageTracker::Initialize(const std::string& canvas_id) { canvas_id_ = canvas_id; @@ -127,6 +126,7 @@ std::string CanvasUsageTracker::GetUsageModeName(CanvasUsage usage) const { case CanvasUsage::kPaletteEditing: return "Palette Editing"; case CanvasUsage::kBppConversion: return "BPP Conversion"; case CanvasUsage::kPerformanceMode: return "Performance Mode"; + case CanvasUsage::kEntityManipulation: return "Entity Manipulation"; case CanvasUsage::kUnknown: return "Unknown"; default: return "Unknown"; } @@ -142,6 +142,7 @@ ImVec4 CanvasUsageTracker::GetUsageModeColor(CanvasUsage usage) const { case CanvasUsage::kPaletteEditing: return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple case CanvasUsage::kBppConversion: return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan case CanvasUsage::kPerformanceMode: return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red + case CanvasUsage::kEntityManipulation: return ImVec4(0.4F, 0.8F, 1.0F, 1.0F); // Light Blue case CanvasUsage::kUnknown: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray default: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray } @@ -424,6 +425,5 @@ void CanvasUsageManager::ClearAllTrackers() { LOG_DEBUG("CanvasUsage", "Cleared all canvas usage trackers"); } -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_usage_tracker.h b/src/app/gui/canvas/canvas_usage_tracker.h index 1a77768a..3a198e50 100644 --- a/src/app/gui/canvas/canvas_usage_tracker.h +++ b/src/app/gui/canvas/canvas_usage_tracker.h @@ -10,7 +10,6 @@ namespace yaze { namespace gui { -namespace canvas { /** * @brief Canvas usage patterns and tracking @@ -24,6 +23,7 @@ enum class CanvasUsage { kPaletteEditing, // Palette editing mode kBppConversion, // BPP format conversion kPerformanceMode, // Performance monitoring mode + kEntityManipulation, // Generic entity manipulation (insertion/editing/deletion) kUnknown // Unknown or mixed usage }; @@ -242,7 +242,6 @@ class CanvasUsageManager { std::unordered_map> trackers_; }; -} // namespace canvas } // namespace gui } // namespace yaze diff --git a/src/app/gui/canvas/canvas_utils.h b/src/app/gui/canvas/canvas_utils.h index e118bc72..6caae038 100644 --- a/src/app/gui/canvas/canvas_utils.h +++ b/src/app/gui/canvas/canvas_utils.h @@ -6,15 +6,20 @@ #include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" #include "app/rom.h" +#include "app/gui/canvas/canvas_usage_tracker.h" #include "imgui/imgui.h" namespace yaze { namespace gui { /** - * @brief Configuration for canvas display and interaction + * @brief Unified configuration for canvas display and interaction + * + * Consolidates all canvas configuration into a single struct, including + * display settings, interaction state, and optional callbacks for updates. */ struct CanvasConfig { + // Display settings bool enable_grid = true; bool enable_hex_labels = false; bool enable_custom_labels = false; @@ -23,11 +28,22 @@ struct CanvasConfig { bool auto_resize = false; bool clamp_rect_to_local_maps = true; // Prevent rectangle wrap across 512x512 boundaries bool use_theme_sizing = true; // Use theme-aware sizing instead of fixed sizes + bool enable_metrics = false; // Enable performance/usage tracking + + // Sizing and scale float grid_step = 32.0f; float global_scale = 1.0f; ImVec2 canvas_size = ImVec2(0, 0); ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.) + ImVec2 scrolling = ImVec2(0, 0); bool custom_canvas_size = false; + + // Usage tracking + CanvasUsage usage_mode = CanvasUsage::kUnknown; + + // Callbacks for configuration changes (used by modals) + std::function on_config_changed; + std::function on_scale_changed; // Get theme-aware canvas toolbar height (when use_theme_sizing is true) float GetToolbarHeight() const;