From 60ddf76331cc1c5ea16cd2b6b04a26b0b3e584dd Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 23:59:48 -0400 Subject: [PATCH] refactor: Integrate Canvas Automation API and Simplify Overworld Editor Controls - Implemented Canvas Automation API integration in OverworldEditor, allowing for automated tile operations and enhanced control. - Simplified editing modes in the toolbar, consolidating functionality for a more intuitive user experience. - Updated entity editing shortcuts and context menu interactions to streamline entity management. - Adjusted drawing methods to ensure consistent rendering without scale application, improving performance and clarity. - Added comments for future enhancements regarding entity operations submenu. --- src/app/editor/overworld/map_properties.cc | 3 + src/app/editor/overworld/overworld_editor.cc | 222 ++++++++++++------- src/app/editor/overworld/overworld_editor.h | 27 ++- 3 files changed, 168 insertions(+), 84 deletions(-) diff --git a/src/app/editor/overworld/map_properties.cc b/src/app/editor/overworld/map_properties.cc index 1f7b3064..fbe0d200 100644 --- a/src/app/editor/overworld/map_properties.cc +++ b/src/app/editor/overworld/map_properties.cc @@ -476,6 +476,9 @@ 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 diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 393cdd34..bef4e829 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -25,6 +25,7 @@ #include "app/gfx/snes_palette.h" #include "app/gfx/tilemap.h" #include "app/gui/canvas.h" +#include "app/gui/canvas/canvas_automation_api.h" #include "app/gui/editor_layout.h" #include "app/gui/icons.h" #include "app/gui/style.h" @@ -65,6 +66,9 @@ void OverworldEditor::Initialize() { entity_renderer_ = std::make_unique( &overworld_, &ow_map_canvas_, &sprite_previews_); + // Setup Canvas Automation API callbacks (Phase 4) + SetupCanvasAutomation(); + // Note: Context menu is now setup dynamically in DrawOverworldCanvas() // for context-aware menu items based on current map state @@ -283,44 +287,36 @@ void OverworldEditor::DrawToolset() { toolbar.Begin(); - // Mode buttons (editing tools) - compact inline row + // Mode buttons - simplified to 2 modes toolbar.BeginModeGroup(); - if (toolbar.ModeButton(ICON_MD_PAN_TOOL_ALT, current_mode == EditingMode::PAN, "Pan (1)")) { - current_mode = EditingMode::PAN; - ow_map_canvas_.set_draggable(true); + if (toolbar.ModeButton(ICON_MD_MOUSE, current_mode == EditingMode::MOUSE, "Mouse Mode (1)\nNavigate, pan, and manage entities")) { + current_mode = EditingMode::MOUSE; } - if (toolbar.ModeButton(ICON_MD_DRAW, current_mode == EditingMode::DRAW_TILE, "Draw (2)")) { + 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 (toolbar.ModeButton(ICON_MD_DOOR_FRONT, current_mode == EditingMode::ENTRANCES, "Entrances (3)")) { - current_mode = EditingMode::ENTRANCES; - } - - if (toolbar.ModeButton(ICON_MD_DOOR_BACK, current_mode == EditingMode::EXITS, "Exits (4)")) { - current_mode = EditingMode::EXITS; - } - - if (toolbar.ModeButton(ICON_MD_GRASS, current_mode == EditingMode::ITEMS, "Items (5)")) { - current_mode = EditingMode::ITEMS; - } - - if (toolbar.ModeButton(ICON_MD_PEST_CONTROL_RODENT, current_mode == EditingMode::SPRITES, "Sprites (6)")) { - current_mode = EditingMode::SPRITES; - } - - if (toolbar.ModeButton(ICON_MD_ADD_LOCATION, current_mode == EditingMode::TRANSPORTS, "Transports (7)")) { - current_mode = EditingMode::TRANSPORTS; - } - - if (toolbar.ModeButton(ICON_MD_MUSIC_NOTE, current_mode == EditingMode::MUSIC, "Music (8)")) { - current_mode = EditingMode::MUSIC; - } - toolbar.EndModeGroup(); + // Entity editing indicator (shows current entity mode if active) + if (entity_edit_mode_ != EntityEditMode::NONE) { + toolbar.AddSeparator(); + const char* entity_label = ""; + const char* entity_icon = ""; + switch (entity_edit_mode_) { + case EntityEditMode::ENTRANCES: entity_icon = ICON_MD_DOOR_FRONT; entity_label = "Entrances"; break; + case EntityEditMode::EXITS: entity_icon = ICON_MD_DOOR_BACK; entity_label = "Exits"; break; + case EntityEditMode::ITEMS: entity_icon = ICON_MD_GRASS; entity_label = "Items"; break; + case EntityEditMode::SPRITES: entity_icon = ICON_MD_PEST_CONTROL_RODENT; entity_label = "Sprites"; break; + case EntityEditMode::TRANSPORTS: entity_icon = ICON_MD_ADD_LOCATION; entity_label = "Transports"; break; + case EntityEditMode::MUSIC: entity_icon = ICON_MD_MUSIC_NOTE; entity_label = "Music"; break; + default: break; + } + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s Editing: %s", entity_icon, entity_label); + } + // ROM version badge (already read above) toolbar.AddRomBadge(asm_version, [this]() { ImGui::OpenPopup("UpgradeROMVersion"); @@ -467,23 +463,32 @@ void OverworldEditor::DrawToolset() { if (!ImGui::IsAnyItemActive()) { using enum EditingMode; - // Tool shortcuts + // Tool shortcuts (simplified) if (ImGui::IsKeyDown(ImGuiKey_1)) { - current_mode = PAN; + current_mode = EditingMode::MOUSE; } else if (ImGui::IsKeyDown(ImGuiKey_2)) { - current_mode = DRAW_TILE; - } else if (ImGui::IsKeyDown(ImGuiKey_3)) { - current_mode = ENTRANCES; + current_mode = EditingMode::DRAW_TILE; + } + + // Entity editing shortcuts (3-8) + if (ImGui::IsKeyDown(ImGuiKey_3)) { + entity_edit_mode_ = EntityEditMode::ENTRANCES; + current_mode = EditingMode::MOUSE; } else if (ImGui::IsKeyDown(ImGuiKey_4)) { - current_mode = EXITS; + entity_edit_mode_ = EntityEditMode::EXITS; + current_mode = EditingMode::MOUSE; } else if (ImGui::IsKeyDown(ImGuiKey_5)) { - current_mode = ITEMS; + entity_edit_mode_ = EntityEditMode::ITEMS; + current_mode = EditingMode::MOUSE; } else if (ImGui::IsKeyDown(ImGuiKey_6)) { - current_mode = SPRITES; + entity_edit_mode_ = EntityEditMode::SPRITES; + current_mode = EditingMode::MOUSE; } else if (ImGui::IsKeyDown(ImGuiKey_7)) { - current_mode = TRANSPORTS; + entity_edit_mode_ = EntityEditMode::TRANSPORTS; + current_mode = EditingMode::MOUSE; } else if (ImGui::IsKeyDown(ImGuiKey_8)) { - current_mode = MUSIC; + entity_edit_mode_ = EntityEditMode::MUSIC; + current_mode = EditingMode::MOUSE; } // View shortcuts @@ -514,9 +519,9 @@ void OverworldEditor::DrawOverworldMaps() { continue; // Skip invalid map index } - int scale = static_cast(ow_map_canvas_.global_scale()); - int map_x = (xx * kOverworldMapSize * scale); - int map_y = (yy * kOverworldMapSize * scale); + // Don't apply scale to coordinates - scale is applied to canvas rendering + int map_x = xx * kOverworldMapSize; + int map_y = yy * kOverworldMapSize; // Check if the map has a texture, if not, ensure it gets loaded if (!maps_bmp_[world_index].texture() && @@ -526,8 +531,8 @@ void OverworldEditor::DrawOverworldMaps() { // Only draw if the map has a texture or is the currently selected map if (maps_bmp_[world_index].texture() || world_index == current_map_) { - ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y, - ow_map_canvas_.global_scale()); + // Draw without applying scale here - canvas handles zoom uniformly + ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y, 1.0f); } else { // Draw a placeholder for maps that haven't loaded yet ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -535,7 +540,7 @@ void OverworldEditor::DrawOverworldMaps() { ImVec2 placeholder_pos = ImVec2(canvas_pos.x + map_x, canvas_pos.y + map_y); ImVec2 placeholder_size = - ImVec2(kOverworldMapSize * scale, kOverworldMapSize * scale); + ImVec2(kOverworldMapSize, kOverworldMapSize); // Modern loading indicator with theme colors draw_list->AddRectFilled( @@ -1120,13 +1125,9 @@ ImVec2 ClampScrollPosition(ImVec2 scroll, ImVec2 content_size, ImVec2 visible_si } // namespace void OverworldEditor::HandleOverworldPan() { - // Middle mouse button panning - if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle)) { - if (!middle_mouse_dragging_) { - previous_mode = current_mode; - current_mode = EditingMode::PAN; - middle_mouse_dragging_ = true; - } + // Middle mouse button panning (works in all modes) + if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle) && ImGui::IsItemHovered()) { + middle_mouse_dragging_ = true; // Get mouse delta and apply to scroll ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; @@ -1145,10 +1146,26 @@ void OverworldEditor::HandleOverworldPan() { } if (ImGui::IsMouseReleased(ImGuiMouseButton_Middle) && middle_mouse_dragging_) { - current_mode = previous_mode; middle_mouse_dragging_ = false; } + // In MOUSE mode, left-click drag also pans + if (current_mode == EditingMode::MOUSE && + ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsItemHovered()) { + ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; + ImVec2 current_scroll = ow_map_canvas_.scrolling(); + ImVec2 new_scroll = ImVec2( + current_scroll.x + mouse_delta.x, + current_scroll.y + mouse_delta.y + ); + + // Clamp scroll to boundaries + ImVec2 content_size = CalculateOverworldContentSize(ow_map_canvas_.global_scale()); + ImVec2 visible_size = ow_map_canvas_.canvas_size(); + new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size); + + ow_map_canvas_.set_scrolling(new_scroll); + } } void OverworldEditor::HandleOverworldZoom() { @@ -1247,30 +1264,15 @@ void OverworldEditor::DrawOverworldCanvas() { show_overlay_editor_); } - // Handle pan and zoom + // Handle pan and zoom (works in all modes) HandleOverworldPan(); HandleOverworldZoom(); - if (current_mode == EditingMode::PAN) { - // In PAN mode, allow right-click drag for panning - if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { - ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; - ImVec2 current_scroll = ow_map_canvas_.scrolling(); - ImVec2 new_scroll = ImVec2( - current_scroll.x + mouse_delta.x, - current_scroll.y + mouse_delta.y - ); - - // Clamp scroll to boundaries - ImVec2 content_size = CalculateOverworldContentSize(ow_map_canvas_.global_scale()); - ImVec2 visible_size = ow_map_canvas_.canvas_size(); - new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size); - - ow_map_canvas_.set_scrolling(new_scroll); - } + // Context menu only in MOUSE mode + if (current_mode == EditingMode::MOUSE) { ow_map_canvas_.DrawContextMenu(); - } else { - // Handle map interaction (tile painting, etc.) + } else if (current_mode == EditingMode::DRAW_TILE) { + // Tile painting mode - handle tile edits and right-click tile picking HandleMapInteraction(); } @@ -1281,13 +1283,14 @@ void OverworldEditor::DrawOverworldCanvas() { entity_renderer_->set_current_map(current_map_); // Draw all entities using the entity renderer - int mode_int = static_cast(current_mode); + // Convert entity_edit_mode_ to legacy mode int for entity renderer + int entity_mode_int = static_cast(entity_edit_mode_); entity_renderer_->DrawExits(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), - current_world_, mode_int); + current_world_, entity_mode_int); entity_renderer_->DrawEntrances(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), - current_world_, mode_int); - entity_renderer_->DrawItems(current_world_, mode_int); - entity_renderer_->DrawSprites(current_world_, game_state_, mode_int); + current_world_, entity_mode_int); + entity_renderer_->DrawItems(current_world_, entity_mode_int); + entity_renderer_->DrawSprites(current_world_, game_state_, entity_mode_int); // Check if entity renderer wants to jump to a tab (e.g., double-clicked entrance/exit) if (entity_renderer_->jump_to_tab() != -1) { @@ -2645,4 +2648,67 @@ void OverworldEditor::UpdateBlocksetSelectorState() { blockset_selector_->SetSelectedTile(current_tile16_); } +// ============================================================================ +// Canvas Automation API Integration (Phase 4) +// ============================================================================ + +void OverworldEditor::SetupCanvasAutomation() { + auto* api = ow_map_canvas_.GetAutomationAPI(); + + // Set tile paint callback + api->SetTilePaintCallback([this](int x, int y, int tile_id) { + return AutomationSetTile(x, y, tile_id); + }); + + // Set tile query callback + api->SetTileQueryCallback([this](int x, int y) { + return AutomationGetTile(x, y); + }); +} + +bool OverworldEditor::AutomationSetTile(int x, int y, int tile_id) { + if (!overworld_.is_loaded()) { + return false; + } + + // Bounds check + if (x < 0 || y < 0 || x >= 512 || y >= 512) { + return false; + } + + // Set current world based on current_map_ + overworld_.set_current_world(current_world_); + overworld_.set_current_map(current_map_); + + // Set the tile in the overworld data structure + overworld_.SetTile(x, y, static_cast(tile_id)); + + // Update the bitmap + auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id); + if (!tile_data.empty()) { + RenderUpdatedMapBitmap(ImVec2(static_cast(x * 16), + static_cast(y * 16)), tile_data); + return true; + } + + return false; +} + +int OverworldEditor::AutomationGetTile(int x, int y) { + if (!overworld_.is_loaded()) { + return -1; + } + + // Bounds check + if (x < 0 || y < 0 || x >= 512 || y >= 512) { + return -1; + } + + // Set current world + overworld_.set_current_world(current_world_); + overworld_.set_current_map(current_map_); + + return overworld_.GetTile(x, y); +} + } // 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 2b512055..c02b5f5b 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -192,6 +192,14 @@ class OverworldEditor : public Editor, public gfx::GfxContext { void ResetOverworldView(); void CenterOverworldView(); + // Canvas Automation API integration (Phase 4) + void SetupCanvasAutomation(); + gui::Canvas* GetOverworldCanvas() { return &ow_map_canvas_; } + + // Tile operations for automation callbacks + bool AutomationSetTile(int x, int y, int tile_id); + int AutomationGetTile(int x, int y); + /** * @brief Scroll the blockset canvas to show the current selected tile16 */ @@ -212,18 +220,25 @@ class OverworldEditor : public Editor, public gfx::GfxContext { void DrawDebugWindow(); enum class EditingMode { - DRAW_TILE, + MOUSE, // Navigation, selection, entity management via context menu + DRAW_TILE // Tile painting mode + }; + + EditingMode current_mode = EditingMode::DRAW_TILE; + EditingMode previous_mode = EditingMode::DRAW_TILE; + + // Entity editing state (managed via context menu now) + enum class EntityEditMode { + NONE, ENTRANCES, EXITS, ITEMS, SPRITES, TRANSPORTS, - MUSIC, - PAN + MUSIC }; - - EditingMode current_mode = EditingMode::DRAW_TILE; - EditingMode previous_mode = EditingMode::DRAW_TILE; + + EntityEditMode entity_edit_mode_ = EntityEditMode::NONE; enum OverworldProperty { LW_AREA_GFX,