diff --git a/docs/z3ed/E6-z3ed-implementation-plan.md b/docs/z3ed/E6-z3ed-implementation-plan.md index 5b66a983..e1a89756 100644 --- a/docs/z3ed/E6-z3ed-implementation-plan.md +++ b/docs/z3ed/E6-z3ed-implementation-plan.md @@ -220,6 +220,12 @@ _Status Legend: ๐Ÿ”„ Active ยท ๐Ÿ“‹ Planned ยท โœ… Done_ - Test Reject and Delete actions - Validate filtering and refresh functionality +2. **Widget ID Refactoring** (Started Oct 2, 2025) ๐ŸŽฏ NEW + - โœ… Added widget_id_registry to build system + - โœ… Registered 13 Overworld toolset buttons with hierarchical IDs + - ๐Ÿ“‹ Next: Test widget discovery and update test harness + - See: [WIDGET_ID_REFACTORING_PROGRESS.md](WIDGET_ID_REFACTORING_PROGRESS.md) + ### Priority 1: ImGuiTestHarness Foundation (IT-01) โœ… PHASE 2 COMPLETE **Rationale**: Required for automated GUI testing and remote control of YAZE for AI workflows **Decision**: โœ… **Use gRPC** - Production-grade, cross-platform, type-safe (see `IT-01-grpc-evaluation.md`) diff --git a/scripts/test_remote_control.sh b/scripts/test_remote_control.sh new file mode 100755 index 00000000..35836f2e --- /dev/null +++ b/scripts/test_remote_control.sh @@ -0,0 +1,276 @@ +#!/bin/bash + +# Test Remote Control - Practical Agent Workflows +# +# This script demonstrates the agent's ability to remotely control YAZE +# and perform real editing tasks like drawing tiles, moving entities, etc. +# +# Usage: ./scripts/test_remote_control.sh + +set -e # Exit on error + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +PROTO_DIR="$PROJECT_ROOT/src/app/core/proto" +PROTO_FILE="imgui_test_harness.proto" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Test harness connection +HOST="127.0.0.1" +PORT="50052" + +echo -e "${BLUE}=== YAZE Remote Control Test ===${NC}\n" + +# Check if grpcurl is available +if ! command -v grpcurl &> /dev/null; then + echo -e "${RED}Error: grpcurl not found${NC}" + echo "Install with: brew install grpcurl" + exit 1 +fi + +# Helper function to make gRPC calls +grpc_call() { + local method=$1 + local data=$2 + grpcurl -plaintext \ + -import-path "$PROTO_DIR" \ + -proto "$PROTO_FILE" \ + -d "$data" \ + "$HOST:$PORT" \ + "yaze.test.ImGuiTestHarness/$method" +} + +# Helper function to print test status +print_test() { + local test_num=$1 + local test_name=$2 + echo -e "\n${BLUE}Test $test_num: $test_name${NC}" +} + +print_success() { + echo -e "${GREEN}โœ“ PASSED${NC}" +} + +print_failure() { + echo -e "${RED}โœ— FAILED: $1${NC}" +} + +# Test 0: Check server connection +print_test "0" "Server Connection" +if grpc_call "Ping" '{"message":"hello"}' &> /dev/null; then + print_success +else + print_failure "Server not responding" + echo -e "${YELLOW}Start the test harness:${NC}" + echo "./build-grpc-test/bin/yaze.app/Contents/MacOS/yaze \\" + echo " --enable_test_harness \\" + echo " --test_harness_port=50052 \\" + echo " --rom_file=assets/zelda3.sfc" + exit 1 +fi + +echo -e "\n${BLUE}=== Practical Agent Workflows ===${NC}\n" + +# Workflow 1: Activate Draw Tile Mode +print_test "1" "Activate Draw Tile Mode" +echo "Action: Click DrawTile button in Overworld toolset" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:DrawTile", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Agent can now paint tiles on the overworld" +else + print_failure "Could not activate draw tile mode" + echo "Response: $response" +fi + +sleep 1 + +# Workflow 2: Select Pan Mode +print_test "2" "Select Pan Mode" +echo "Action: Click Pan button to enable map navigation" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:Pan", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Agent can now pan the overworld map" +else + print_failure "Could not activate pan mode" + echo "Response: $response" +fi + +sleep 1 + +# Workflow 3: Open Tile16 Editor +print_test "3" "Open Tile16 Editor" +echo "Action: Click Tile16Editor button to open editor" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:Tile16Editor", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Tile16 Editor window should now be open" + echo "Agent can select tiles for drawing" +else + print_failure "Could not open Tile16 Editor" + echo "Response: $response" +fi + +sleep 1 + +# Workflow 4: Test Entrances Mode +print_test "4" "Switch to Entrances Mode" +echo "Action: Click Entrances button" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:Entrances", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Agent can now edit overworld entrances" +else + print_failure "Could not activate entrances mode" + echo "Response: $response" +fi + +sleep 1 + +# Workflow 5: Test Exits Mode +print_test "5" "Switch to Exits Mode" +echo "Action: Click Exits button" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:Exits", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Agent can now edit overworld exits" +else + print_failure "Could not activate exits mode" + echo "Response: $response" +fi + +sleep 1 + +# Workflow 6: Test Sprites Mode +print_test "6" "Switch to Sprites Mode" +echo "Action: Click Sprites button" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:Sprites", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Agent can now edit sprite placements" +else + print_failure "Could not activate sprites mode" + echo "Response: $response" +fi + +sleep 1 + +# Workflow 7: Test Items Mode +print_test "7" "Switch to Items Mode" +echo "Action: Click Items button" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:Items", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Agent can now place items on the overworld" +else + print_failure "Could not activate items mode" + echo "Response: $response" +fi + +sleep 1 + +# Workflow 8: Test Zoom Controls +print_test "8" "Test Zoom Controls" +echo "Action: Zoom in on the map" + +response=$(grpc_call "Click" '{ + "target": "Overworld/Toolset/button:ZoomIn", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Zoom level increased" + + # Zoom back out + sleep 0.5 + grpc_call "Click" '{ + "target": "Overworld/Toolset/button:ZoomOut", + "type": "LEFT" + }' &> /dev/null + echo "Zoom level restored" +else + print_failure "Could not zoom" + echo "Response: $response" +fi + +# Workflow 9: Legacy Format Fallback Test +print_test "9" "Legacy Format Fallback" +echo "Action: Test old-style widget reference" + +response=$(grpc_call "Click" '{ + "target": "button:Overworld", + "type": "LEFT" +}' 2>&1) + +if echo "$response" | grep -q '"success": true'; then + print_success + echo "Legacy format still works (backwards compatible)" +else + # This is expected if Overworld Editor isn't in main window + echo -e "${YELLOW}Legacy format may not work (expected)${NC}" +fi + +# Summary +echo -e "\n${BLUE}=== Test Summary ===${NC}\n" +echo "Remote control capabilities verified:" +echo " โœ“ Mode switching (Draw, Pan, Entrances, Exits, Sprites, Items)" +echo " โœ“ Tool opening (Tile16 Editor)" +echo " โœ“ Zoom controls" +echo " โœ“ Widget registry integration" +echo "" +echo "Agent can now:" +echo " โ€ข Switch between editing modes" +echo " โ€ข Open auxiliary editors" +echo " โ€ข Control view settings" +echo " โ€ข Prepare for complex editing operations" +echo "" +echo "Next steps for full automation:" +echo " 1. Add canvas click support (x,y coordinates)" +echo " 2. Add tile selection in Tile16 Editor" +echo " 3. Add entity dragging support" +echo " 4. Implement workflow chaining (mode + select + draw)" +echo "" +echo -e "${GREEN}Remote control system functional!${NC}" diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 8f8f4148..3aa1fdfc 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -25,6 +25,7 @@ #include "app/gui/icons.h" #include "app/gui/input.h" #include "app/gui/style.h" +#include "app/gui/widget_id_registry.h" #include "app/rom.h" #include "app/zelda3/common.h" #include "app/zelda3/overworld/overworld.h" @@ -54,6 +55,12 @@ void OverworldEditor::Initialize() { // Setup overworld canvas context menu SetupOverworldCanvasContextMenu(); + // Setup widget ID scope for Overworld editor + // This enables test automation to reference widgets like: + // "Overworld/Toolset/button:Pan" or "Overworld/Toolset/button:DrawTile" + gui::WidgetIdScope overworld_scope("Overworld"); + gui::WidgetIdScope toolset_scope("Toolset"); + // Core editing tools gui::AddTableColumn(toolset_table_, "##Pan", [&]() { if (Selectable(ICON_MD_PAN_TOOL_ALT, current_mode == EditingMode::PAN)) { @@ -61,44 +68,69 @@ void OverworldEditor::Initialize() { ow_map_canvas_.set_draggable(true); } HOVER_HINT("Pan (1) - Middle click and drag"); + // Register this widget for test automation + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Pan", "button", ImGui::GetItemID(), + "Pan tool - Middle click and drag to move the overworld canvas"); }); gui::AddTableColumn(toolset_table_, "##DrawTile", [&]() { if (Selectable(ICON_MD_DRAW, current_mode == EditingMode::DRAW_TILE)) { current_mode = EditingMode::DRAW_TILE; } HOVER_HINT("Draw Tile (2)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:DrawTile", "button", ImGui::GetItemID(), + "Draw Tile tool - Paint tiles on the overworld map"); }); gui::AddTableColumn(toolset_table_, "##Entrances", [&]() { if (Selectable(ICON_MD_DOOR_FRONT, current_mode == EditingMode::ENTRANCES)) current_mode = EditingMode::ENTRANCES; HOVER_HINT("Entrances (3)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Entrances", "button", ImGui::GetItemID(), + "Entrances tool - Edit overworld entrances"); }); gui::AddTableColumn(toolset_table_, "##Exits", [&]() { if (Selectable(ICON_MD_DOOR_BACK, current_mode == EditingMode::EXITS)) current_mode = EditingMode::EXITS; HOVER_HINT("Exits (4)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Exits", "button", ImGui::GetItemID(), + "Exits tool - Edit overworld exits"); }); gui::AddTableColumn(toolset_table_, "##Items", [&]() { if (Selectable(ICON_MD_GRASS, current_mode == EditingMode::ITEMS)) current_mode = EditingMode::ITEMS; HOVER_HINT("Items (5)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Items", "button", ImGui::GetItemID(), + "Items tool - Place items on the overworld"); }); gui::AddTableColumn(toolset_table_, "##Sprites", [&]() { if (Selectable(ICON_MD_PEST_CONTROL_RODENT, current_mode == EditingMode::SPRITES)) current_mode = EditingMode::SPRITES; HOVER_HINT("Sprites (6)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Sprites", "button", ImGui::GetItemID(), + "Sprites tool - Edit overworld sprites"); }); gui::AddTableColumn(toolset_table_, "##Transports", [&]() { if (Selectable(ICON_MD_ADD_LOCATION, current_mode == EditingMode::TRANSPORTS)) current_mode = EditingMode::TRANSPORTS; HOVER_HINT("Transports (7)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Transports", "button", ImGui::GetItemID(), + "Transports tool - Configure transport locations"); }); gui::AddTableColumn(toolset_table_, "##Music", [&]() { if (Selectable(ICON_MD_MUSIC_NOTE, current_mode == EditingMode::MUSIC)) current_mode = EditingMode::MUSIC; HOVER_HINT("Music (8)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Music", "button", ImGui::GetItemID(), + "Music tool - Configure overworld music"); }); // View controls @@ -106,16 +138,25 @@ void OverworldEditor::Initialize() { if (Button(ICON_MD_ZOOM_OUT)) ow_map_canvas_.ZoomOut(); HOVER_HINT("Zoom Out"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:ZoomOut", "button", ImGui::GetItemID(), + "Zoom Out - Decrease canvas zoom level"); }); gui::AddTableColumn(toolset_table_, "##ZoomIn", [&]() { if (Button(ICON_MD_ZOOM_IN)) ow_map_canvas_.ZoomIn(); HOVER_HINT("Zoom In"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:ZoomIn", "button", ImGui::GetItemID(), + "Zoom In - Increase canvas zoom level"); }); gui::AddTableColumn(toolset_table_, "##Fullscreen", [&]() { if (Button(ICON_MD_OPEN_IN_FULL)) overworld_canvas_fullscreen_ = !overworld_canvas_fullscreen_; HOVER_HINT("Fullscreen Canvas (F11)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Fullscreen", "button", ImGui::GetItemID(), + "Fullscreen - Toggle fullscreen canvas view (F11)"); }); // Quick access tools @@ -123,12 +164,18 @@ void OverworldEditor::Initialize() { if (Button(ICON_MD_GRID_VIEW)) show_tile16_editor_ = !show_tile16_editor_; HOVER_HINT("Tile16 Editor (Ctrl+T)"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:Tile16Editor", "button", ImGui::GetItemID(), + "Tile16 Editor - Open the Tile16 editor window (Ctrl+T)"); }); gui::AddTableColumn(toolset_table_, "##CopyMap", [&]() { if (Button(ICON_MD_CONTENT_COPY)) { status_ = absl::UnimplementedError("PNG export functionality removed"); } HOVER_HINT("Copy Map to Clipboard"); + gui::WidgetIdRegistry::Instance().RegisterWidget( + "Overworld/Toolset/button:CopyMap", "button", ImGui::GetItemID(), + "Copy Map - Copy map to clipboard"); }); } diff --git a/src/app/gui/gui.cmake b/src/app/gui/gui.cmake index 1dfea38c..3c6d1504 100644 --- a/src/app/gui/gui.cmake +++ b/src/app/gui/gui.cmake @@ -11,6 +11,7 @@ set( app/gui/theme_manager.cc app/gui/background_renderer.cc app/gui/bpp_format_ui.cc + app/gui/widget_id_registry.cc # Canvas system components app/gui/canvas/canvas_modals.cc app/gui/canvas/canvas_context_menu.cc