diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 576ed9c1..3faa1c80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -227,7 +227,10 @@ jobs: -DCMAKE_CXX_FLAGS="/Dgoogle_protobuf_undef_DWORD=1" ` -DYAZE_BUILD_TESTS=ON ` -DYAZE_BUILD_EMU=OFF ` + -DYAZE_MINIMAL_BUILD=ON ` -DYAZE_ENABLE_ROM_TESTS=OFF 2>&1 | Tee-Object -FilePath cmake_config.log + # Note: YAZE_BUILD_EMU=OFF disables standalone emulator executable + # but yaze_emulator library is still built for main app/tests if ($LASTEXITCODE -ne 0) { Write-Host "::error::CMake configuration failed with exit code $LASTEXITCODE" @@ -249,8 +252,11 @@ jobs: -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \ -DYAZE_BUILD_TESTS=ON \ -DYAZE_BUILD_EMU=OFF \ + -DYAZE_MINIMAL_BUILD=ON \ -DYAZE_ENABLE_ROM_TESTS=OFF 2>&1 | tee cmake_config.log echo "::endgroup::" + # Note: YAZE_BUILD_EMU=OFF disables standalone emulator executable + # but yaze_emulator library is still built for main app/tests - name: Report Configure Failure if: failure() && (steps.configure_windows.outcome == 'failure' || steps.configure_unix.outcome == 'failure') diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 376d981f..4a04d15d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: shell: bash run: | set -euo pipefail - + if [[ "${{ github.event_name }}" == "push" ]]; then TAG="${{ github.ref_name }}" else @@ -81,7 +81,7 @@ jobs: run: | set -euo pipefail VERSION="${VALIDATED_TAG#v}" - + if [[ -f scripts/extract_changelog.py ]]; then echo "Attempting to extract changelog for version ${VERSION}..." python3 scripts/extract_changelog.py "${VERSION}" > release_notes.md @@ -91,7 +91,7 @@ jobs: echo "" >> release_notes.md echo "For a detailed list of changes, please see the project's commit history or changelog file." >> release_notes.md fi - + echo "Generated release notes:" cat release_notes.md @@ -111,6 +111,7 @@ jobs: build-and-package: name: "๐Ÿ“ฆ Build (${{ matrix.name }})" needs: prepare-release + continue-on-error: true # PRE-1.0: Don't block release on platform failure strategy: fail-fast: false matrix: @@ -147,7 +148,7 @@ jobs: vcpkgDirectory: '${{ github.workspace }}/vcpkg' vcpkgGitCommitId: '01f602195983451bc83e72f4214af2cbc495aa94' # 2024.10.21 release runVcpkgInstall: true - + - name: "Retry vcpkg setup (Windows only)" if: runner.os == 'Windows' && steps.vcpkg.outcome == 'failure' uses: lukka/run-vcpkg@v11 @@ -174,7 +175,7 @@ jobs: elif [[ "${{ runner.os }}" == "macOS" ]]; then brew install ninja cmake pkg-config fi - + - name: "Retry Dependencies (if failed)" if: steps.deps.outcome == 'failure' shell: bash @@ -200,25 +201,26 @@ jobs: shell: pwsh run: | Write-Host "::group::CMake Configuration (Windows)" -ForegroundColor Cyan - + $vcpkgToolchain = "${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" -replace '\\', '/' Write-Host "Using vcpkg toolchain: $vcpkgToolchain" - + cmake -B build -G "Visual Studio 17 2022" -A x64 ` -DCMAKE_BUILD_TYPE=$env:BUILD_TYPE ` -DCMAKE_TOOLCHAIN_FILE="$vcpkgToolchain" ` -DVCPKG_TARGET_TRIPLET=x64-windows-static ` -DVCPKG_MANIFEST_MODE=ON ` -DCMAKE_CXX_FLAGS="/Dgoogle_protobuf_undef_DWORD=1" ` + -DYAZE_MINIMAL_BUILD=OFF ` -DYAZE_BUILD_TESTS=OFF 2>&1 | Tee-Object -FilePath cmake_config.log - + if ($LASTEXITCODE -ne 0) { Write-Host "::error::CMake configuration failed with exit code $LASTEXITCODE" exit $LASTEXITCODE } - + Write-Host "::endgroup::" - + - name: "Configure (macOS)" if: runner.os == 'macOS' id: configure_macos @@ -229,11 +231,13 @@ jobs: cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ -DCMAKE_OSX_ARCHITECTURES=${{ matrix.mac_arch }} \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ -DYAZE_BUILD_EMU=OFF \ + -DYAZE_MINIMAL_BUILD=OFF \ -DYAZE_BUILD_TESTS=OFF 2>&1 | tee cmake_config.log echo "::endgroup::" - + # Note: YAZE_BUILD_EMU=OFF disables standalone emulator executable + # but yaze_emulator library is still built for main app + - name: "Configure (Linux)" if: runner.os == 'Linux' id: configure_linux @@ -244,14 +248,17 @@ jobs: cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ -DYAZE_BUILD_EMU=OFF \ + -DYAZE_MINIMAL_BUILD=OFF \ -DYAZE_BUILD_TESTS=OFF 2>&1 | tee cmake_config.log echo "::endgroup::" - + # Note: YAZE_BUILD_EMU=OFF disables standalone emulator executable + # but yaze_emulator library is still built for main app + - name: "Report Configure Failure" if: | - failure() && - (steps.configure_windows.outcome == 'failure' || - steps.configure_macos.outcome == 'failure' || + failure() && + (steps.configure_windows.outcome == 'failure' || + steps.configure_macos.outcome == 'failure' || steps.configure_linux.outcome == 'failure') shell: bash run: | @@ -270,7 +277,7 @@ jobs: - name: "Build" id: build run: cmake --build build --config ${{ env.BUILD_TYPE }} --parallel 2>&1 | tee build.log - + - name: "Report Build Failure" if: failure() && steps.build.outcome == 'failure' shell: bash @@ -280,12 +287,12 @@ jobs: echo "::group::Build Log (last 100 lines)" tail -100 build.log echo "::endgroup::" - + echo "::group::Build Errors" grep -i "error" build.log | head -20 || true echo "::endgroup::" fi - + - name: "Upload Build Logs on Failure" if: failure() uses: actions/upload-artifact@v4 @@ -304,10 +311,10 @@ jobs: run: | set -euo pipefail ARTIFACT_NAME="${{ matrix.artifact_name }}" - + # Create staging directory mkdir -p stage - + if [[ "${{ runner.os }}" == "Windows" ]]; then # Windows uses Visual Studio generator: build/bin/Release/ echo "Packaging Windows artifacts..." @@ -321,7 +328,7 @@ jobs: cp LICENSE README.md stage/ (cd stage && powershell -Command "Compress-Archive -Path * -DestinationPath ../${ARTIFACT_NAME}.zip") echo "Created ${ARTIFACT_NAME}.zip" - + elif [[ "${{ runner.os }}" == "macOS" ]]; then # macOS creates app bundle: build/bin/yaze.app echo "Packaging macOS artifacts..." @@ -332,7 +339,7 @@ jobs: fi cp -R build/bin/yaze.app stage/yaze.app echo "Staged yaze.app slice for ${ARTIFACT_NAME}" - + else # Linux # Linux uses Ninja generator: build/bin/yaze (no subdirectory) echo "Packaging Linux artifacts..." @@ -344,7 +351,7 @@ jobs: ls -la build/bin/ || true exit 1 fi - + # Copy assets from source tree if [[ -d assets ]]; then cp -r assets/ stage/assets/ @@ -352,7 +359,7 @@ jobs: echo "::error::Assets directory not found" exit 1 fi - + cp LICENSE README.md stage/ tar -czf "${ARTIFACT_NAME}.tar.gz" -C stage . echo "Created ${ARTIFACT_NAME}.tar.gz" @@ -384,20 +391,20 @@ jobs: path: ${{ matrix.artifact_name }}.tar.gz if-no-files-found: error retention-days: 5 - + - name: "Generate Build Summary" if: always() shell: bash run: | echo "## Release Build Summary - ${{ matrix.name }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - + echo "### Configuration" >> $GITHUB_STEP_SUMMARY echo "- **Platform**: ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY echo "- **Artifact**: ${{ matrix.artifact_name }}" >> $GITHUB_STEP_SUMMARY echo "- **Build Type**: ${{ env.BUILD_TYPE }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - + echo "### Build Status" >> $GITHUB_STEP_SUMMARY CONFIGURE_OUTCOME="${{ steps.configure_windows.outcome || steps.configure_macos.outcome || steps.configure_linux.outcome }}" if [[ "$CONFIGURE_OUTCOME" == "success" ]]; then @@ -405,13 +412,13 @@ jobs: else echo "- โŒ Configure: Failed" >> $GITHUB_STEP_SUMMARY fi - + if [[ "${{ steps.build.outcome }}" == "success" ]]; then echo "- โœ… Build: Success" >> $GITHUB_STEP_SUMMARY else echo "- โŒ Build: Failed" >> $GITHUB_STEP_SUMMARY fi - + # Show package size if build succeeded if [[ "${{ steps.build.outcome }}" == "success" ]]; then if [[ "${{ runner.os }}" == "Windows" && -f "${{ matrix.artifact_name }}.zip" ]]; then @@ -432,16 +439,22 @@ jobs: merge-macos-universal: name: "๐ŸŽ Merge macOS Universal" needs: build-and-package + if: always() # PRE-1.0: Run even if some platform builds failed + continue-on-error: true # PRE-1.0: Don't block release if universal merge fails runs-on: macos-14 steps: - name: "Download arm64 Slice" + id: download_arm64 uses: actions/download-artifact@v4 + continue-on-error: true with: name: yaze-macos-arm64-slice path: arm64-slice - name: "Download x86_64 Slice" + id: download_x64 uses: actions/download-artifact@v4 + continue-on-error: true with: name: yaze-macos-x86_64-slice path: x86_64-slice @@ -450,26 +463,52 @@ jobs: shell: bash run: | set -euo pipefail - + + ARM_AVAILABLE=${{ steps.download_arm64.outcome == 'success' }} + X64_AVAILABLE=${{ steps.download_x64.outcome == 'success' }} + + echo "ARM64 slice available: $ARM_AVAILABLE" + echo "x86_64 slice available: $X64_AVAILABLE" + + if [[ "$ARM_AVAILABLE" == "false" && "$X64_AVAILABLE" == "false" ]]; then + echo "::error::No macOS slices available - cannot create macOS release" + exit 1 + fi + # The artifacts are downloaded as yaze.app directly ARM_APP="arm64-slice/yaze.app" X64_APP="x86_64-slice/yaze.app" - - echo "Checking downloaded artifacts..." - ls -la arm64-slice/ - ls -la x86_64-slice/ - - # Use the arm64 bundle as the base - cp -R "${ARM_APP}" ./yaze.app - - echo "Merging executables..." - lipo -create "${ARM_APP}/Contents/MacOS/yaze" "${X64_APP}/Contents/MacOS/yaze" -output "./yaze.app/Contents/MacOS/yaze" - - echo "Verifying universal binary..." - lipo -info "./yaze.app/Contents/MacOS/yaze" - - echo "Creating DMG..." - hdiutil create -fs HFS+ -srcfolder ./yaze.app -volname "yaze ${{ needs.prepare-release.outputs.tag_name }}" yaze-macos-universal.dmg + + if [[ "$ARM_AVAILABLE" == "true" && "$X64_AVAILABLE" == "true" ]]; then + echo "Creating universal binary from both architectures..." + echo "Checking downloaded artifacts..." + ls -la arm64-slice/ + ls -la x86_64-slice/ + + # Use the arm64 bundle as the base + cp -R "${ARM_APP}" ./yaze.app + + echo "Merging executables..." + lipo -create "${ARM_APP}/Contents/MacOS/yaze" "${X64_APP}/Contents/MacOS/yaze" -output "./yaze.app/Contents/MacOS/yaze" + + echo "Verifying universal binary..." + lipo -info "./yaze.app/Contents/MacOS/yaze" + + echo "Creating DMG..." + hdiutil create -fs HFS+ -srcfolder ./yaze.app -volname "yaze ${{ needs.prepare-release.outputs.tag_name }} (Universal)" yaze-macos-universal.dmg + + elif [[ "$ARM_AVAILABLE" == "true" ]]; then + echo "::warning::Only ARM64 slice available - creating ARM64-only DMG" + cp -R "${ARM_APP}" ./yaze.app + hdiutil create -fs HFS+ -srcfolder ./yaze.app -volname "yaze ${{ needs.prepare-release.outputs.tag_name }} (ARM64)" yaze-macos-arm64.dmg + mv yaze-macos-arm64.dmg yaze-macos-universal.dmg # Use same name for upload + + else + echo "::warning::Only x86_64 slice available - creating x86_64-only DMG" + cp -R "${X64_APP}" ./yaze.app + hdiutil create -fs HFS+ -srcfolder ./yaze.app -volname "yaze ${{ needs.prepare-release.outputs.tag_name }} (Intel)" yaze-macos-x86_64.dmg + mv yaze-macos-x86_64.dmg yaze-macos-universal.dmg # Use same name for upload + fi - name: "Upload Universal macOS Artifact" uses: actions/upload-artifact@v4 @@ -485,37 +524,113 @@ jobs: create-github-release: name: "๐Ÿš€ Create GitHub Release" needs: [prepare-release, build-and-package, merge-macos-universal] + if: always() # PRE-1.0: Always run to create release even if some platforms failed runs-on: ubuntu-latest steps: - name: "Download Windows Artifact" + id: download_windows uses: actions/download-artifact@v4 + continue-on-error: true with: name: yaze-windows-x64 path: release-artifacts - name: "Download Linux Artifact" + id: download_linux uses: actions/download-artifact@v4 + continue-on-error: true with: name: yaze-linux-x64 path: release-artifacts - name: "Download macOS Universal Artifact" + id: download_macos uses: actions/download-artifact@v4 + continue-on-error: true with: name: yaze-macos-universal path: release-artifacts - name: "List Release Artifacts" - run: ls -lR release-artifacts + run: ls -lR release-artifacts || echo "No artifacts directory" + + - name: "Generate Platform Availability Report" + id: platform_report + shell: bash + run: | + echo "Checking which platforms succeeded..." + + WINDOWS_OK=${{ steps.download_windows.outcome == 'success' }} + LINUX_OK=${{ steps.download_linux.outcome == 'success' }} + MACOS_OK=${{ steps.download_macos.outcome == 'success' }} + + # Build availability message + AVAILABILITY="## Platform Availability\n\n" + + if [[ "$WINDOWS_OK" == "true" ]]; then + AVAILABILITY+="- โœ… **Windows x64**: Available\n" + else + AVAILABILITY+="- โŒ **Windows x64**: Build failed (will be added in subsequent release)\n" + fi + + if [[ "$LINUX_OK" == "true" ]]; then + AVAILABILITY+="- โœ… **Linux x64**: Available\n" + else + AVAILABILITY+="- โŒ **Linux x64**: Build failed (will be added in subsequent release)\n" + fi + + if [[ "$MACOS_OK" == "true" ]]; then + AVAILABILITY+="- โœ… **macOS Universal**: Available\n" + else + AVAILABILITY+="- โŒ **macOS**: Build failed (will be added in subsequent release)\n" + fi + + AVAILABILITY+="\n---\n\n" + + # Save to output + { + echo 'availability<> "$GITHUB_OUTPUT" + + # Check if we have at least one successful build + if [[ "$WINDOWS_OK" == "false" && "$LINUX_OK" == "false" && "$MACOS_OK" == "false" ]]; then + echo "::error::All platform builds failed - cannot create release" + exit 1 + fi + + echo "โœ… At least one platform succeeded - release can proceed" - name: "Create/Update Release" uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.prepare-release.outputs.tag_name }} name: "yaze ${{ needs.prepare-release.outputs.tag_name }}" - body: ${{ needs.prepare-release.outputs.release_notes }} + body: | + ${{ steps.platform_report.outputs.availability }} + ${{ needs.prepare-release.outputs.release_notes }} + + --- + + **Note**: This is a pre-1.0 release. Platforms may be added incrementally. + You can re-run failed platform jobs to add missing binaries to this release. draft: false prerelease: ${{ needs.prepare-release.outputs.is_prerelease }} files: release-artifacts/* + fail_on_unmatched_files: false # Don't fail if some platforms missing env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "Generate Release Summary" + if: always() + shell: bash + run: | + echo "## ๐Ÿš€ Release Created: ${{ needs.prepare-release.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.platform_report.outputs.availability }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next Steps" >> $GITHUB_STEP_SUMMARY + echo "- If platforms failed, you can re-run those specific jobs" >> $GITHUB_STEP_SUMMARY + echo "- Failed builds will be added to this same release when re-run succeeds" >> $GITHUB_STEP_SUMMARY + echo "- The release tag remains the same (${{ needs.prepare-release.outputs.tag_name }})" >> $GITHUB_STEP_SUMMARY diff --git a/CMakeLists.txt b/CMakeLists.txt index 1985a71f..a47bfa61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,53 +2,36 @@ # by scawful cmake_minimum_required(VERSION 3.16) -# Set policy version to handle compatibility issues -if(POLICY CMP0091) - cmake_policy(SET CMP0091 NEW) -endif() - -# Set additional policies to handle submodule compatibility -if(POLICY CMP0048) - cmake_policy(SET CMP0048 NEW) -endif() -if(POLICY CMP0077) - cmake_policy(SET CMP0077 NEW) -endif() -if(POLICY CMP0091) - cmake_policy(SET CMP0091 NEW) -endif() - -# Suppress deprecation warnings from submodules -set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "Suppress deprecation warnings") - -# Handle pthread issues on Windows -if(WIN32) - set(THREADS_PREFER_PTHREAD_FLAG OFF) -endif() +# Set policies for compatibility +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0048 NEW) +cmake_policy(SET CMP0077 NEW) project(yaze VERSION 0.3.2 DESCRIPTION "Yet Another Zelda3 Editor" - LANGUAGES CXX C) + LANGUAGES CXX C OBJC OBJCXX) + +# Enable ccache for faster rebuilds if available +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + message(STATUS "โœ“ ccache found, enabling for faster builds") + set(CMAKE_CXX_COMPILER_LAUNCHER ccache) + set(CMAKE_C_COMPILER_LAUNCHER ccache) +endif() # Set project metadata set(YAZE_VERSION_MAJOR 0) set(YAZE_VERSION_MINOR 3) set(YAZE_VERSION_PATCH 2) -# Add an option to enable Unity builds for faster compilation -option(YAZE_UNITY_BUILD "Enable Unity (Jumbo) builds" OFF) +# Suppress deprecation warnings from submodules +set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "Suppress deprecation warnings") -if(YAZE_UNITY_BUILD) - message(STATUS "Unity builds enabled") - set(CMAKE_UNITY_BUILD ON) - set(CMAKE_UNITY_BUILD_BATCH_SIZE 8) -endif() +# Add cmake directory to module path +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/src/yaze_config.h.in - ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h - @ONLY -) +# Include utility functions +include(cmake/utils.cmake) # Build Flags set(YAZE_BUILD_APP ON) @@ -64,52 +47,27 @@ option(YAZE_ENABLE_EXPERIMENTAL_TESTS "Enable experimental/unstable tests" ON) option(YAZE_ENABLE_UI_TESTS "Enable ImGui Test Engine UI testing" ON) option(YAZE_MINIMAL_BUILD "Minimal build for CI (disable optional features)" OFF) option(YAZE_USE_MODULAR_BUILD "Use modularized library build system for faster builds" ON) +option(YAZE_UNITY_BUILD "Enable Unity (Jumbo) builds" OFF) -# ============================================================================ -# Feature Flags - Always Enable All Features -# ============================================================================ -# CI and Release now use the same configuration for consistency -# This avoids runtime library mismatches and ensures CI tests what we ship -set(Z3ED_AI ON) -set(YAZE_WITH_JSON ON) -set(YAZE_WITH_GRPC ON) -message(STATUS "โœ“ All features enabled: JSON, gRPC, AI Agent") +# Feature Flags +if(YAZE_MINIMAL_BUILD) + # Minimal build for CI: disable features that are slow to build or not essential + set(Z3ED_AI OFF) + set(YAZE_WITH_JSON ON) # JSON is header-only, low impact + set(YAZE_WITH_GRPC OFF) + set(YAZE_ENABLE_UI_TESTS OFF) + message(STATUS "โœ“ Minimal build enabled for CI") +else() + # Full build for development/release + set(Z3ED_AI ON) + set(YAZE_WITH_JSON ON) + set(YAZE_WITH_GRPC ON) + message(STATUS "โœ“ All features enabled: JSON, gRPC, AI Agent") +endif() -# YAZE_SUPPRESS_WARNINGS: Suppress compiler warnings for cleaner build output option(YAZE_SUPPRESS_WARNINGS "Suppress compiler warnings (use -v preset suffix for verbose)" ON) set(YAZE_TEST_ROM_PATH "${CMAKE_BINARY_DIR}/bin/zelda3.sfc" CACHE STRING "Path to test ROM file") -# PNG support removed - no longer needed - -# Modern CMake standards -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -# Apply warning suppression if requested -if(YAZE_SUPPRESS_WARNINGS) - if(MSVC) - add_compile_options(/w) - else() - add_compile_options(-w) - endif() - message(STATUS "โœ“ Warnings suppressed (use -v preset suffix for verbose builds)") -else() - message(STATUS "โ—‹ Verbose warnings enabled") -endif() -set(CMAKE_C_STANDARD 99) -set(CMAKE_C_STANDARD_REQUIRED ON) - -# Output directories -include(GNUInstallDirs) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) -set(BUILD_SHARED_LIBS OFF) -set(CMAKE_FIND_FRAMEWORK LAST) - # Platform detection if(CMAKE_SYSTEM_NAME MATCHES "Darwin") set(YAZE_PLATFORM_MACOS ON) @@ -117,248 +75,33 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") set(YAZE_PLATFORM_LINUX ON) elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") set(YAZE_PLATFORM_WINDOWS ON) - # Enable vcpkg integration for Windows builds - # Check if CMAKE_TOOLCHAIN_FILE is set but the file doesn't exist - if(DEFINED CMAKE_TOOLCHAIN_FILE AND NOT EXISTS "${CMAKE_TOOLCHAIN_FILE}") - message(WARNING "vcpkg toolchain file specified but not found: ${CMAKE_TOOLCHAIN_FILE}") - message(WARNING "Disabling vcpkg integration. Install vcpkg or set VCPKG_ROOT environment variable.") - unset(CMAKE_TOOLCHAIN_FILE CACHE) - endif() - - # Set vcpkg toolchain file if not already set or if the previous one was invalid - if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) - if(DEFINED ENV{VCPKG_ROOT}) - set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") - elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake") - set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") - endif() - endif() - # Setup yaze_config include directories - endif() -# Abseil provider selection: default to bundled libraries on macOS to avoid -# deployment target mismatches with system packages, but let other platforms -# use their package managers unless overridden. -# CRITICAL: When gRPC is enabled, always use bundled Abseil to avoid version conflicts -set(_yaze_default_force_absl OFF) -if(YAZE_PLATFORM_MACOS OR YAZE_WITH_GRPC) - set(_yaze_default_force_absl ON) -endif() -option(YAZE_FORCE_BUNDLED_ABSL - "Force building the bundled Abseil FetchContent dependency instead of finding a system package" - ${_yaze_default_force_absl}) +# Setup compiler flags and common interface target +yaze_add_compiler_flags() -# Create a common interface target for shared settings -add_library(yaze_common INTERFACE) -target_compile_features(yaze_common INTERFACE cxx_std_23) +# Configure yaze_config.h +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/yaze_config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h + @ONLY +) -# Configure httplib before including it (header-only, minimal dependencies) -# Disable optional compression features to avoid Windows build issues -if (WIN32) -set(HTTPLIB_COMPILE OFF CACHE BOOL "Use httplib as header-only library" FORCE) -set(HTTPLIB_USE_ZLIB_IF_AVAILABLE OFF CACHE BOOL "Disable zlib to avoid Windows issues" FORCE) -set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF CACHE BOOL "Disable brotli" FORCE) -set(HTTPLIB_USE_ZSTD_IF_AVAILABLE OFF CACHE BOOL "Disable zstd" FORCE) -set(HTTPLIB_REQUIRE_OPENSSL OFF CACHE BOOL "Don't require OpenSSL" FORCE) -set(HTTPLIB_REQUIRE_ZLIB OFF CACHE BOOL "Don't require zlib" FORCE) -set(HTTPLIB_REQUIRE_BROTLI OFF CACHE BOOL "Don't require brotli" FORCE) -set(HTTPLIB_REQUIRE_ZSTD OFF CACHE BOOL "Don't require zstd" FORCE) -endif() +# Output directories +include(GNUInstallDirs) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(BUILD_SHARED_LIBS OFF) -target_include_directories(yaze_common INTERFACE ${CMAKE_SOURCE_DIR}/third_party/httplib) - -if(YAZE_WITH_JSON) - set(JSON_BuildTests OFF CACHE INTERNAL "Disable nlohmann_json tests") - - # Verify JSON library exists - if(NOT EXISTS "${CMAKE_SOURCE_DIR}/third_party/json/CMakeLists.txt") - message(FATAL_ERROR - "JSON library not found in third_party/json/\n" - "This is required when YAZE_WITH_JSON=ON.\n" - "Please ensure the repository is fully cloned with submodules." - ) - endif() - - add_subdirectory(${CMAKE_SOURCE_DIR}/third_party/json ${CMAKE_BINARY_DIR}/third_party/json EXCLUDE_FROM_ALL) - - # Verify the target was created - if(NOT TARGET nlohmann_json::nlohmann_json) - message(FATAL_ERROR - "Failed to create nlohmann_json::nlohmann_json target.\n" - "The JSON library subdirectory was added but the target is missing.\n" - "Please check third_party/json/CMakeLists.txt for errors." - ) - endif() - - message(STATUS "โœ“ JSON support enabled (nlohmann/json)") - message(STATUS " - Include directory: ${CMAKE_SOURCE_DIR}/third_party/json/include") - message(STATUS " - Target available: nlohmann_json::nlohmann_json") -endif() - -# Platform-specific configurations -if(YAZE_PLATFORM_LINUX) - target_compile_definitions(yaze_common INTERFACE linux stricmp=strcasecmp) -elseif(YAZE_PLATFORM_MACOS) - set(CMAKE_INSTALL_PREFIX /usr/local) - target_compile_definitions(yaze_common INTERFACE MACOS) -elseif(YAZE_PLATFORM_WINDOWS) - # vcpkg configuration must be done early (in CMakeLists.txt or via command line) - # The vcpkg.cmake file contains post-toolchain configuration and reporting - if(DEFINED CMAKE_TOOLCHAIN_FILE AND EXISTS "${CMAKE_TOOLCHAIN_FILE}") - # vcpkg toolchain is active - # Note: VCPKG_TARGET_TRIPLET and other settings should be set via command line - # or will be auto-detected by our cmake/vcpkg.cmake include - message(STATUS "โœ“ Using vcpkg integration") - message(STATUS " โ”œโ”€ Toolchain: ${CMAKE_TOOLCHAIN_FILE}") - if(DEFINED VCPKG_TARGET_TRIPLET) - message(STATUS " โ””โ”€ Triplet: ${VCPKG_TARGET_TRIPLET}") - endif() - - # Include our vcpkg configuration for additional settings - include(cmake/vcpkg.cmake) - else() - message(STATUS "โ—‹ vcpkg not available - using system packages or bundled dependencies") - message(STATUS " Tip: Set VCPKG_ROOT environment variable or CMAKE_TOOLCHAIN_FILE to enable vcpkg") - endif() - target_compile_definitions(yaze_common INTERFACE WINDOWS) - - # Windows-specific build guidance - message(STATUS "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”") - message(STATUS "Windows Build Configuration:") - message(STATUS " โ”œโ”€ Generator: ${CMAKE_GENERATOR}") - message(STATUS " โ”œโ”€ Architecture: ${CMAKE_GENERATOR_PLATFORM}") - message(STATUS " โ”œโ”€ Build Type: ${CMAKE_BUILD_TYPE}") - message(STATUS " โ”œโ”€ Minimal Build: ${YAZE_MINIMAL_BUILD}") - message(STATUS " โ”œโ”€ JSON Support: ${YAZE_WITH_JSON}") - message(STATUS " โ”œโ”€ AI Features: ${Z3ED_AI}") - message(STATUS " โ””โ”€ gRPC Support: ${YAZE_WITH_GRPC}") - message(STATUS "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”") - - # Helpful messages about build configuration - if(YAZE_MINIMAL_BUILD) - message(STATUS "โ„น๏ธ Minimal Build Mode:") - message(STATUS " โ€ข gRPC disabled for faster CI builds") - message(STATUS " โ€ข UI tests disabled") - message(STATUS " โ€ข JSON support enabled (header-only, lightweight)") - endif() - - if(NOT Z3ED_AI AND NOT YAZE_MINIMAL_BUILD) - message(STATUS "Note: AI agent features disabled") - message(STATUS " To enable: cmake -DYAZE_WITH_GRPC=ON -DZ3ED_AI=ON ...") - endif() - - if(NOT YAZE_WITH_JSON) - message(WARNING "JSON disabled - some features may not compile") - message(WARNING "Recommend keeping JSON enabled (it's header-only)") - endif() - - # Note about httplib - message(STATUS "httplib: Header-only mode (compression features disabled)") - if(Z3ED_AI) - message(STATUS " For Gemini API: curl.exe must be in PATH") - message(STATUS " (Usually included with Windows 10/11 or Git)") - endif() - - # Windows-specific architecture detection and configuration - if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64") - target_compile_definitions(yaze_common INTERFACE YAZE_ARCH_ARM64) - message(STATUS "Building for Windows ARM64") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64") - target_compile_definitions(yaze_common INTERFACE YAZE_ARCH_X64) - message(STATUS "Building for Windows x64") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i686|x86") - target_compile_definitions(yaze_common INTERFACE YAZE_ARCH_X86) - message(STATUS "Building for Windows x86") - else() - message(WARNING "Unknown Windows architecture: ${CMAKE_SYSTEM_PROCESSOR}") - endif() -endif() - -# Compiler-specific settings -if(MSVC) - target_compile_options(yaze_common INTERFACE - /EHsc - /W4 /permissive- - /bigobj # Support large object files - /utf-8 # Use UTF-8 encoding - ) - target_compile_definitions(yaze_common INTERFACE - _CRT_SECURE_NO_WARNINGS - _CRT_NONSTDC_NO_WARNINGS - SILENCE_CXX23_DEPRECATIONS - _SILENCE_CXX23_DEPRECATION_WARNING - _SILENCE_ALL_CXX23_DEPRECATION_WARNINGS - NOMINMAX # Disable min/max macros - WIN32_LEAN_AND_MEAN # Reduce Windows header bloat - strncasecmp=_strnicmp - strcasecmp=_stricmp - ) -else() - target_compile_options(yaze_common INTERFACE - -Wall -Wextra -Wpedantic - -Wno-deprecated-declarations # Silence deprecation warnings - -Wno-c++23-compat # Silence C++23 compatibility warnings - ) - # Add C++23 deprecation silencing for GCC/Clang - target_compile_definitions(yaze_common INTERFACE - _SILENCE_CXX23_DEPRECATION_WARNING - _SILENCE_ALL_CXX23_DEPRECATION_WARNINGS - ) -endif() - -# Abseil Standard Specifications -include(cmake/absl.cmake) - -# Optional gRPC support -if(YAZE_WITH_GRPC) - message(STATUS "โœ“ gRPC support enabled (FetchContent will download and build from source)") - message(STATUS " Note: First build takes 15-20 minutes to compile gRPC + Protobuf") - message(STATUS " Versions: gRPC v1.62.0, Protobuf (bundled), Abseil (bundled)") - - # Include existing gRPC infrastructure - include(cmake/grpc.cmake) - - # Pass to source code - add_compile_definitions(YAZE_WITH_GRPC) - - set(YAZE_HAS_GRPC TRUE) -else() - message(STATUS "โ—‹ gRPC support disabled (set -DYAZE_WITH_GRPC=ON to enable)") - set(YAZE_HAS_GRPC FALSE) -endif() - -# SDL2 and PNG -include(cmake/sdl2.cmake) - -# Asar -include(cmake/asar.cmake) - -# Google Test (if needed for main app integration) -if (YAZE_BUILD_TESTS) -include(cmake/gtest.cmake) -endif() - -# ImGui (after minimal build flags are set) -include(cmake/imgui.cmake) - -# Project Files -# Copy theme files to build directory (for development) -file(GLOB THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme") -file(COPY ${THEME_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/themes/") - -# Copy agent resource files to build directory (for AI features) -file(GLOB AGENT_FILES "${CMAKE_SOURCE_DIR}/assets/agent/*") -file(COPY ${AGENT_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/agent/") - -# IMPORTANT: Also ensure themes are included in macOS bundles -# This is handled in src/CMakeLists.txt via YAZE_RESOURCE_FILES +# Handle dependencies +include(cmake/dependencies.cmake) +# Project Files add_subdirectory(src) -# Tools (development utilities - only for local development) +# Tools option(YAZE_BUILD_TOOLS "Build development utility tools" OFF) if(YAZE_BUILD_TOOLS) message(STATUS "Building development tools") @@ -366,24 +109,24 @@ if(YAZE_BUILD_TOOLS) endif() # Tests -if (YAZE_BUILD_TESTS) -add_subdirectory(test) +if(YAZE_BUILD_TESTS) + add_subdirectory(test) endif() # Code quality targets find_program(CLANG_FORMAT NAMES clang-format clang-format-14 clang-format-15 clang-format-16 clang-format-17 clang-format-18) if(CLANG_FORMAT) - file(GLOB_RECURSE ALL_SOURCE_FILES - "${CMAKE_SOURCE_DIR}/src/*.cc" + file(GLOB_RECURSE ALL_SOURCE_FILES + "${CMAKE_SOURCE_DIR}/src/*.cc" "${CMAKE_SOURCE_DIR}/src/*.h" "${CMAKE_SOURCE_DIR}/test/*.cc" "${CMAKE_SOURCE_DIR}/test/*.h") - + add_custom_target(format COMMAND ${CLANG_FORMAT} -i --style=Google ${ALL_SOURCE_FILES} COMMENT "Running clang-format on source files" ) - + add_custom_target(format-check COMMAND ${CLANG_FORMAT} --dry-run --Werror --style=Google ${ALL_SOURCE_FILES} COMMENT "Checking code format" diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake new file mode 100644 index 00000000..2ab5e11d --- /dev/null +++ b/cmake/dependencies.cmake @@ -0,0 +1,118 @@ +# This file centralizes the management of all third-party dependencies. +# It provides functions to find or fetch dependencies and creates alias targets +# for consistent usage throughout the project. + +include(FetchContent) + +# ============================================================================ +# Helper function to add a dependency +# ============================================================================ +function(yaze_add_dependency name) + set(options) + set(oneValueArgs GIT_REPOSITORY GIT_TAG URL) + set(multiValueArgs) + cmake_parse_arguments(DEP "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(TARGET yaze::${name}) + return() + endif() + + # Try to find the package via find_package first + find_package(${name} QUIET) + + if(${name}_FOUND) + message(STATUS "Found ${name} via find_package") + if(TARGET ${name}::${name}) + add_library(yaze::${name} ALIAS ${name}::${name}) + else() + # Handle cases where find_package doesn't create an imported target + # This is a simplified approach; more logic may be needed for specific packages + add_library(yaze::${name} INTERFACE IMPORTED) + target_include_directories(yaze::${name} INTERFACE ${${name}_INCLUDE_DIRS}) + target_link_libraries(yaze::${name} INTERFACE ${${name}_LIBRARIES}) + endif() + return() + endif() + + # If not found, use FetchContent + message(STATUS "Could not find ${name}, fetching from source.") + FetchContent_Declare( + ${name} + GIT_REPOSITORY ${DEP_GIT_REPOSITORY} + GIT_TAG ${DEP_GIT_TAG} + ) + + FetchContent_GetProperties(${name}) + if(NOT ${name}_POPULATED) + FetchContent_Populate(${name}) + add_subdirectory(${${name}_SOURCE_DIR} ${${name}_BINARY_DIR}) + endif() + + if(TARGET ${name}) + add_library(yaze::${name} ALIAS ${name}) + elseif(TARGET ${name}::${name}) + add_library(yaze::${name} ALIAS ${name}::${name}) + else() + message(FATAL_ERROR "Failed to create target for ${name}") + endif() +endfunction() + +# ============================================================================ +# Dependency Declarations +# ============================================================================ + +# gRPC (must come before Abseil - provides its own compatible Abseil) +if(YAZE_WITH_GRPC) + include(cmake/grpc.cmake) +endif() + +# Abseil (only if gRPC didn't provide it) +if(NOT YAZE_WITH_GRPC) + include(cmake/absl.cmake) +endif() + +# SDL2 +include(cmake/sdl2.cmake) + +# Asar +include(cmake/asar.cmake) + +# Google Test +if(YAZE_BUILD_TESTS) + include(cmake/gtest.cmake) +endif() + +# ImGui +include(cmake/imgui.cmake) + +# FTXUI (for z3ed) +if(YAZE_BUILD_Z3ED) + FetchContent_Declare(ftxui + GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui + GIT_TAG v5.0.0 + ) + FetchContent_MakeAvailable(ftxui) +endif() + +# yaml-cpp (always available for configuration files) +set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Disable yaml-cpp tests" FORCE) +set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "Disable yaml-cpp contrib" FORCE) +set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "Disable yaml-cpp tools" FORCE) +set(YAML_CPP_INSTALL OFF CACHE BOOL "Disable yaml-cpp install" FORCE) +set(YAML_CPP_FORMAT_SOURCE OFF CACHE BOOL "Disable yaml-cpp format target" FORCE) + +FetchContent_Declare(yaml-cpp + GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git + GIT_TAG 0.8.0 +) +FetchContent_MakeAvailable(yaml-cpp) + +# nlohmann_json (header only) +if(YAZE_WITH_JSON) + set(JSON_BuildTests OFF CACHE INTERNAL "Disable nlohmann_json tests") + add_subdirectory(${CMAKE_SOURCE_DIR}/third_party/json ${CMAKE_BINARY_DIR}/third_party/json EXCLUDE_FROM_ALL) +endif() + +# httplib (header only) +# No action needed here as it's included directly. + diff --git a/cmake/grpc.cmake b/cmake/grpc.cmake index 1b47e525..15fd47d9 100644 --- a/cmake/grpc.cmake +++ b/cmake/grpc.cmake @@ -98,15 +98,22 @@ set(ABSL_PROPAGATE_CXX_STD ON CACHE BOOL "" FORCE) set(ABSL_ENABLE_INSTALL ON CACHE BOOL "" FORCE) set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE) -# Declare gRPC - use v1.67.1 which fixes MSVC template issues and is compatible with modern compilers -# v1.67.1 includes: -# - MSVC/Visual Studio compatibility fixes (template instantiation errors) +# Disable x86-specific optimizations for ARM64 macOS builds +if(APPLE AND CMAKE_OSX_ARCHITECTURES STREQUAL "arm64") + set(ABSL_USE_EXTERNAL_GOOGLETEST OFF CACHE BOOL "" FORCE) + set(ABSL_BUILD_TEST_HELPERS OFF CACHE BOOL "" FORCE) +endif() + +# Declare gRPC - use v1.75.1 which includes ARM64 macOS fixes and is compatible with modern compilers +# v1.75.1 includes: +# - ARM64 macOS compilation fixes (Abseil randen_hwaes) +# - MSVC/Visual Studio compatibility fixes # - Clang 18+ compatibility -# - Abseil compatibility updates +# - Updated Abseil and Protobuf dependencies FetchContent_Declare( grpc GIT_REPOSITORY https://github.com/grpc/grpc.git - GIT_TAG v1.67.1 + GIT_TAG v1.75.1 GIT_PROGRESS TRUE GIT_SHALLOW TRUE USES_TERMINAL_DOWNLOAD TRUE @@ -142,7 +149,76 @@ file(MAKE_DIRECTORY ${_gRPC_PROTO_GENS_DIR}) get_target_property(_PROTOBUF_INCLUDE_DIRS libprotobuf INTERFACE_INCLUDE_DIRECTORIES) list(GET _PROTOBUF_INCLUDE_DIRS 0 _gRPC_PROTOBUF_WELLKNOWN_INCLUDE_DIR) -message(STATUS "gRPC setup complete") +# Export Abseil targets from gRPC's bundled abseil for use by the rest of the project +# This ensures version compatibility between gRPC and our project +set( + ABSL_TARGETS + absl::strings + absl::str_format + absl::flags + absl::flags_parse + absl::flags_usage + absl::flags_commandlineflag + absl::flags_marshalling + absl::flags_private_handle_accessor + absl::flags_program_name + absl::flags_config + absl::flags_reflection + absl::status + absl::statusor + absl::examine_stack + absl::stacktrace + absl::base + absl::config + absl::core_headers + absl::failure_signal_handler + absl::flat_hash_map + absl::cord + absl::hash + absl::synchronization + absl::time + absl::symbolize + absl::container_memory + absl::memory + absl::utility + PARENT_SCOPE +) + +# Only expose absl::int128 when it's supported without warnings +if(NOT WIN32) + list(APPEND ABSL_TARGETS absl::int128) + set(ABSL_TARGETS ${ABSL_TARGETS} PARENT_SCOPE) +endif() + +# Fix Abseil ARM64 macOS compile flags (remove x86-specific flags) +if(APPLE AND DEFINED CMAKE_OSX_ARCHITECTURES AND CMAKE_OSX_ARCHITECTURES STREQUAL "arm64") + foreach(_absl_target IN ITEMS absl_random_internal_randen_hwaes absl_random_internal_randen_hwaes_impl) + if(TARGET ${_absl_target}) + get_target_property(_absl_opts ${_absl_target} COMPILE_OPTIONS) + if(_absl_opts AND NOT _absl_opts STREQUAL "NOTFOUND") + set(_absl_filtered_opts) + set(_absl_skip_next FALSE) + foreach(_absl_opt IN LISTS _absl_opts) + if(_absl_skip_next) + set(_absl_skip_next FALSE) + continue() + endif() + if(_absl_opt STREQUAL "-Xarch_x86_64") + set(_absl_skip_next TRUE) + continue() + endif() + if(_absl_opt STREQUAL "-maes" OR _absl_opt STREQUAL "-msse4.1") + continue() + endif() + list(APPEND _absl_filtered_opts ${_absl_opt}) + endforeach() + set_property(TARGET ${_absl_target} PROPERTY COMPILE_OPTIONS ${_absl_filtered_opts}) + endif() + endif() + endforeach() +endif() + +message(STATUS "gRPC setup complete (includes bundled Abseil)") function(target_add_protobuf target) if(NOT TARGET ${target}) diff --git a/cmake/utils.cmake b/cmake/utils.cmake new file mode 100644 index 00000000..b958a04c --- /dev/null +++ b/cmake/utils.cmake @@ -0,0 +1,70 @@ +# This file contains utility functions for the yaze build system. + +# ============================================================================ +# yaze_add_compiler_flags +# +# Sets standard compiler flags for C++ and C. +# Also handles platform-specific and compiler-specific flags. +# ============================================================================ +function(yaze_add_compiler_flags) + set(CMAKE_CXX_STANDARD 23) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_C_STANDARD 99) + set(CMAKE_C_STANDARD_REQUIRED ON) + + if(YAZE_SUPPRESS_WARNINGS) + if(MSVC) + add_compile_options(/w) + else() + add_compile_options(-w) + endif() + message(STATUS "โœ“ Warnings suppressed (use -v preset suffix for verbose builds)") + else() + message(STATUS "โ—‹ Verbose warnings enabled") + endif() + + # Common interface target for shared settings + add_library(yaze_common INTERFACE) + target_compile_features(yaze_common INTERFACE cxx_std_23) + + # Platform-specific definitions + if(YAZE_PLATFORM_LINUX) + target_compile_definitions(yaze_common INTERFACE linux stricmp=strcasecmp) + elseif(YAZE_PLATFORM_MACOS) + target_compile_definitions(yaze_common INTERFACE MACOS) + elseif(YAZE_PLATFORM_WINDOWS) + target_compile_definitions(yaze_common INTERFACE WINDOWS) + endif() + + # Compiler-specific settings + if(MSVC) + target_compile_options(yaze_common INTERFACE + /EHsc + /W4 /permissive- + /bigobj + /utf-8 + ) + target_compile_definitions(yaze_common INTERFACE + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_WARNINGS + SILENCE_CXX23_DEPRECATIONS + _SILENCE_CXX23_DEPRECATION_WARNING + _SILENCE_ALL_CXX23_DEPRECATION_WARNINGS + NOMINMAX + WIN32_LEAN_AND_MEAN + strncasecmp=_strnicmp + strcasecmp=_stricmp + ) + else() + target_compile_options(yaze_common INTERFACE + -Wall -Wextra -Wpedantic + -Wno-deprecated-declarations + -Wno-c++23-compat + ) + target_compile_definitions(yaze_common INTERFACE + _SILENCE_CXX23_DEPRECATION_WARNING + _SILENCE_ALL_CXX23_DEPRECATION_WARNINGS + ) + endif() +endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b8dc9ed0..4b1158c1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,29 +1,38 @@ +# This file orchestrates the build of the yaze application and its libraries. +# It includes component-specific .cmake files to keep the build system modular. + +# Define emulator source files +# This list is auto-maintained by scripts/build_cleaner.py set( YAZE_APP_EMU_SRC app/emu/audio/apu.cc app/emu/audio/audio_backend.cc - app/emu/audio/spc700.cc app/emu/audio/dsp.cc app/emu/audio/internal/addressing.cc app/emu/audio/internal/instructions.cc + app/emu/audio/spc700.cc + app/emu/cpu/cpu.cc app/emu/cpu/internal/addressing.cc app/emu/cpu/internal/instructions.cc + app/emu/cpu/internal/old_cpu.cc app/emu/debug/apu_debugger.cc - app/emu/debug/disassembly_viewer.cc app/emu/debug/breakpoint_manager.cc + app/emu/debug/disassembly_viewer.cc app/emu/debug/watchpoint_manager.cc + app/emu/emu.cc + app/emu/emulator.cc app/emu/input/input_backend.cc app/emu/input/input_manager.cc - app/emu/ui/input_handler.cc - app/emu/ui/emulator_ui.cc - app/emu/ui/debugger_ui.cc - app/emu/cpu/cpu.cc - app/emu/video/ppu.cc app/emu/memory/dma.cc app/emu/memory/memory.cc app/emu/snes.cc + app/emu/ui/debugger_ui.cc + app/emu/ui/emulator_ui.cc + app/emu/ui/input_handler.cc + app/emu/video/ppu.cc ) +# Define resource files for bundling set(YAZE_RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf ${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf @@ -33,8 +42,6 @@ set(YAZE_RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/font/IBMPlexSansJP-Bold.ttf ${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf ) - -# Add theme files for macOS bundle (replacing the glob pattern with explicit files) file(GLOB YAZE_THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme") list(APPEND YAZE_RESOURCE_FILES ${YAZE_THEME_FILES}) @@ -44,1160 +51,58 @@ foreach (FILE ${YAZE_RESOURCE_FILES}) get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) if (APPLE) - # macOS: Set bundle location - set_source_files_properties(${FILE} - PROPERTIES - MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}" - ) + set_source_files_properties(${FILE} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") else() - # Windows/Linux: Copy to output directory - set_source_files_properties(${FILE} - PROPERTIES - VS_DEPLOYMENT_CONTENT 1 - VS_DEPLOYMENT_LOCATION "assets/${NEW_FILE_PATH}" - ) + set_source_files_properties(${FILE} PROPERTIES VS_DEPLOYMENT_CONTENT 1 VS_DEPLOYMENT_LOCATION "assets/${NEW_FILE_PATH}") endif() endforeach() -# Conditionally add native file dialog (optional for CI builds) -if(NOT YAZE_MINIMAL_BUILD) - # Check if we can build NFD before adding it - find_package(PkgConfig QUIET) - if(PKG_CONFIG_FOUND AND UNIX AND NOT APPLE) - pkg_check_modules(GTK3 QUIET gtk+-3.0) - if(GTK3_FOUND) - add_subdirectory(lib/nativefiledialog-extended) - set(YAZE_HAS_NFD ON) - message(STATUS "NFD enabled with GTK3 support") - else() - set(YAZE_HAS_NFD OFF) - message(STATUS "NFD disabled - GTK3 not found") - endif() - elseif(WIN32 OR APPLE) - add_subdirectory(lib/nativefiledialog-extended) - set(YAZE_HAS_NFD ON) - message(STATUS "NFD enabled for Windows/macOS") - else() - set(YAZE_HAS_NFD OFF) - message(STATUS "NFD disabled - no platform support") - endif() -else() - set(YAZE_HAS_NFD OFF) - message(STATUS "NFD disabled for minimal build") -endif() +# Include modular libraries +include(util/util.cmake) +include(app/gfx/gfx_library.cmake) +include(app/net/net_library.cmake) +include(app/gui/gui_library.cmake) +include(app/zelda3/zelda3_library.cmake) +include(app/core/core_library.cmake) +include(app/editor/editor_library.cmake) +include(app/emu/emu_library.cmake) -if(YAZE_BUILD_APP OR YAZE_BUILD_Z3ED) +# Include agent/CLI components BEFORE tests so test.cmake can link against yaze_agent +if(YAZE_BUILD_APP OR YAZE_BUILD_Z3ED OR YAZE_BUILD_TESTS) include(cli/agent.cmake) endif() - message(STATUS "Using modular build system") - - if(YAZE_BUILD_LIB OR YAZE_BUILD_APP OR YAZE_BUILD_Z3ED) - include(util/util.cmake) - include(app/core/core_library.cmake) - include(app/gfx/gfx_library.cmake) - include(app/gui/gui_library.cmake) - include(app/net/net_library.cmake) - include(app/zelda3/zelda3_library.cmake) - include(app/editor/editor_library.cmake) - include(app/emu/emu_library.cmake) - - if(YAZE_BUILD_TESTS AND NOT YAZE_MINIMAL_BUILD) - include(app/test/test.cmake) - endif() - endif() - -if (YAZE_BUILD_APP) - if (APPLE) - add_executable( - yaze - MACOSX_BUNDLE - app/main.cc - # Bundled Resources - ${YAZE_RESOURCE_FILES} - ) - - set(ICON_FILE "${CMAKE_SOURCE_DIR}/assets/yaze.icns") - target_sources(yaze PRIVATE ${ICON_FILE}) - set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - - set_target_properties(yaze PROPERTIES - MACOSX_BUNDLE_ICON_FILE "yaze.icns" - MACOSX_BUNDLE_BUNDLE_NAME "Yaze" - MACOSX_BUNDLE_EXECUTABLE_NAME "yaze" - MACOSX_BUNDLE_GUI_IDENTIFIER "com.scawful.yaze" - MACOSX_BUNDLE_INFO_STRING "Yet Another Zelda3 Editor" - MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_VERSION}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}" - MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}" - MACOSX_BUNDLE_COPYRIGHT "Copyright ยฉ 2024 scawful. All rights reserved." - ) - else() - add_executable( - yaze - app/main.cc - ) - - if(WIN32 OR UNIX) - target_sources(yaze PRIVATE ${YAZE_RESOURCE_FILES}) - - if(WIN32) - foreach(ASSET_FILE ${YAZE_RESOURCE_FILES}) - file(RELATIVE_PATH ASSET_REL_PATH "${CMAKE_SOURCE_DIR}/assets" ${ASSET_FILE}) - get_filename_component(ASSET_DIR ${ASSET_REL_PATH} DIRECTORY) - - set_source_files_properties(${ASSET_FILE} - PROPERTIES - VS_DEPLOYMENT_CONTENT 1 - VS_DEPLOYMENT_LOCATION "assets/${ASSET_DIR}" - ) - endforeach() - endif() - endif() - endif() - - target_include_directories( - yaze PUBLIC - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${CMAKE_SOURCE_DIR}/third_party/httplib - ${SDL2_INCLUDE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${PROJECT_BINARY_DIR} - ) - - target_sources(yaze PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h) - - set_source_files_properties( - ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h - PROPERTIES GENERATED TRUE - ) - - source_group(TREE ${CMAKE_CURRENT_BINARY_DIR} - FILES ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h) - - if(PNG_FOUND) - target_include_directories(yaze PUBLIC ${PNG_INCLUDE_DIRS}) - endif() - - if(YAZE_HAS_NFD) - target_link_libraries(yaze PRIVATE nfd) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=1) - else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=0) - endif() - - if(YAZE_USE_MODULAR_BUILD) - set(_yaze_modular_links yaze_editor yaze_emulator) - - if(TARGET yaze_agent) - list(APPEND _yaze_modular_links yaze_agent) - endif() - - if(YAZE_BUILD_TESTS AND TARGET yaze_test_support) - list(APPEND _yaze_modular_links yaze_test_support) - endif() - - target_link_libraries(yaze PRIVATE ${_yaze_modular_links}) - else() - target_link_libraries(yaze PRIVATE yaze_core) - endif() - - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_POLICY_FRAMEWORK=1) - - if(WIN32) - if(MSVC) - target_link_options(yaze PRIVATE - /STACK:8388608 - /SUBSYSTEM:WINDOWS - /ENTRY:mainCRTStartup - ) - message(STATUS "Configuring yaze as Windows GUI application with main() entry point") - elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_link_options(yaze PRIVATE - -Wl,--stack,8388608 - -Wl,--subsystem,windows - -Wl,-emain - ) - message(STATUS "Configuring yaze as Windows GUI application with main() entry point (MinGW)") - endif() - endif() - - if(YAZE_ENABLE_UI_TESTS) - if(TARGET ImGuiTestEngine) - target_include_directories(yaze PUBLIC ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine) - target_link_libraries(yaze PUBLIC ImGuiTestEngine) - target_compile_definitions(yaze PRIVATE - YAZE_ENABLE_IMGUI_TEST_ENGINE=1 - ${IMGUI_TEST_ENGINE_DEFINITIONS}) - else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0) - endif() - else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0) - endif() - - if(YAZE_BUILD_TESTS AND TARGET gtest) - target_link_libraries(yaze PRIVATE gtest) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=1) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=1) - else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=0) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=0) - endif() - - if(PNG_FOUND) - target_link_libraries(yaze PUBLIC ${PNG_LIBRARIES}) - endif() - - if (APPLE) - target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY}) - endif() - - if(NOT APPLE) - add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/font - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/font - $/assets/font - COMMENT "Copying font assets" - ) - - add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/themes - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/themes - $/assets/themes - COMMENT "Copying theme assets" - ) - - if(EXISTS ${CMAKE_SOURCE_DIR}/assets/layouts) - add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/layouts - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/layouts - $/assets/layouts - COMMENT "Copying layout assets" - ) - endif() - - if(EXISTS ${CMAKE_SOURCE_DIR}/assets/lib) - add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/lib - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/lib - $/assets/lib - COMMENT "Copying library assets" - ) - endif() - endif() - - if(YAZE_WITH_GRPC) - message(STATUS "Adding gRPC ImGuiTestHarness to yaze target") - - target_include_directories(yaze PRIVATE - ${CMAKE_SOURCE_DIR}/third_party/json/include) - target_compile_definitions(yaze PRIVATE YAZE_WITH_JSON) - - if(NOT YAZE_USE_MODULAR_BUILD) - target_add_protobuf(yaze - ${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto - ${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto) - - target_sources(yaze PRIVATE - ${CMAKE_SOURCE_DIR}/src/app/core/service/imgui_test_harness_service.cc - ${CMAKE_SOURCE_DIR}/src/app/core/service/imgui_test_harness_service.h - ${CMAKE_SOURCE_DIR}/src/app/core/service/screenshot_utils.cc - ${CMAKE_SOURCE_DIR}/src/app/core/service/screenshot_utils.h - ${CMAKE_SOURCE_DIR}/src/app/core/service/widget_discovery_service.cc - ${CMAKE_SOURCE_DIR}/src/app/core/service/widget_discovery_service.h - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_recorder.cc - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_recorder.h - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.cc - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.h) - endif() - - target_link_libraries(yaze PRIVATE - grpc++ - grpc++_reflection - libprotobuf) - - message(STATUS "โœ“ gRPC ImGuiTestHarness integrated") - message(STATUS "โœ“ AI Agent services integrated into yaze GUI") - endif() +if(YAZE_BUILD_TESTS AND NOT YAZE_MINIMAL_BUILD) + include(app/test/test.cmake) endif() +# Build main application +if(YAZE_BUILD_APP) + include(app/app.cmake) +endif() + +# Build standalone emulator if(YAZE_BUILD_EMU) - if (NOT YAZE_MINIMAL_BUILD AND APPLE) - add_executable( - yaze_emu - MACOSX_BUNDLE - app/emu/emu.cc - app/rom.cc - app/platform/app_delegate.mm - ${YAZE_APP_EMU_SRC} - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_UTIL_SRC} - ${YAZE_GUI_SRC} - ${IMGUI_SRC} - cli/service/planning/proposal_registry.cc - cli/service/rom/rom_sandbox_manager.cc - ) - target_link_libraries(yaze_emu PUBLIC ${COCOA_LIBRARY}) - target_compile_definitions(yaze_emu PRIVATE IMGUI_DEFINE_MATH_OPERATORS) - elseif(NOT YAZE_MINIMAL_BUILD) - add_executable( - yaze_emu - app/rom.cc - app/emu/emu.cc - ${YAZE_APP_EMU_SRC} - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_UTIL_SRC} - ${YAZE_GUI_SRC} - ${IMGUI_SRC} - cli/service/planning/proposal_registry.cc - cli/service/rom/rom_sandbox_manager.cc - ) - endif() - - if(NOT YAZE_MINIMAL_BUILD) - target_include_directories( - yaze_emu PUBLIC - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${CMAKE_SOURCE_DIR}/third_party/httplib - ${PNG_INCLUDE_DIRS} - ${SDL2_INCLUDE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${PROJECT_BINARY_DIR} - ) - -target_link_libraries(yaze_emu PUBLIC - ${ABSL_TARGETS} - ${SDL_TARGETS} - ${PNG_LIBRARIES} - ${CMAKE_DL_LIBS} - ImGui - asar-static - nlohmann_json::nlohmann_json - yaze_agent - yaze_editor - yaze_zelda3 - yaze_gui - yaze_gfx - yaze_core_lib - yaze_util - yaze_test_support - ) - - if(YAZE_WITH_GRPC) - target_compile_definitions(yaze_emu PRIVATE YAZE_WITH_JSON) - target_include_directories(yaze_emu PRIVATE ${CMAKE_SOURCE_DIR}/third_party/json/include) - - target_add_protobuf(yaze_emu - ${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto - ${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto) - - target_link_libraries(yaze_emu PRIVATE - grpc++ - grpc++_reflection - libprotobuf) - endif() - endif() - - # Headless Emulator Test Harness (minimal dependencies, fast compile) - if(NOT YAZE_MINIMAL_BUILD) - add_executable(yaze_emu_test emu_test.cc) - - target_include_directories( - yaze_emu_test PRIVATE - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/ - ${SDL2_INCLUDE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${PROJECT_BINARY_DIR} - ) - - target_link_libraries(yaze_emu_test PRIVATE - ${ABSL_TARGETS} - ${SDL_TARGETS} - ${CMAKE_DL_LIBS} - yaze_emulator - yaze_util - ) - - message(STATUS "โœ“ yaze_emu_test: Headless emulator test harness configured") - endif() + include(app/emu/emu.cmake) endif() -if (YAZE_BUILD_Z3ED) + +# Build z3ed CLI tool +if(YAZE_BUILD_Z3ED) include(cli/z3ed.cmake) endif() -if(MACOS) - set_target_properties(yaze - PROPERTIES - BUNDLE True - OUTPUT_NAME "yaze" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in - RESOURCE ${YAZE_RESOURCE_FILES} - ) -elseif(UNIX) - set_target_properties(yaze - PROPERTIES - BUNDLE True - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" - ) - target_compile_definitions(yaze PRIVATE "linux") - target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp") -else() - if(YAZE_MINIMAL_BUILD) - # Skip Windows resource file in CI/minimal builds to avoid architecture conflicts - set_target_properties(yaze - PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" - ) - else() - # Windows resource file - only for x64 architecture - set_target_properties(yaze - PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" - ) - - # Only use resource file for x64 Windows builds - if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8) - # Generate yaze.res from yaze.rc and yaze.ico for Windows x64 builds - set(YAZE_RC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/app/platform/win32/yaze.rc") - set(YAZE_ICO_FILE "${CMAKE_SOURCE_DIR}/assets/yaze.ico") - set(YAZE_RES_FILE "${CMAKE_CURRENT_BINARY_DIR}/yaze.res") - - # Add a custom command to generate the .res file from the .rc and .ico - # /I adds include directory so RC compiler can find yaze.ico - add_custom_command( - OUTPUT ${YAZE_RES_FILE} - COMMAND ${CMAKE_RC_COMPILER} /fo ${YAZE_RES_FILE} /I "${CMAKE_SOURCE_DIR}/assets" ${YAZE_RC_FILE} - DEPENDS ${YAZE_RC_FILE} ${YAZE_ICO_FILE} - COMMENT "Generating yaze.res from yaze.rc and yaze.ico" - VERBATIM - ) - - # Make sure the resource file is built before linking - add_custom_target(yaze_res ALL DEPENDS ${YAZE_RES_FILE}) - - # Link the generated .res file to the yaze target - target_sources(yaze PRIVATE ${YAZE_RES_FILE}) - endif() - endif() -endif() - # Yaze Core Library (for testing and C API) if (YAZE_BUILD_LIB) - # Sources shared by the C API library (minimal dependencies) - set(YAZE_C_SOURCES - ./yaze.cc - cli/service/gui/gui_automation_client.cc + add_library(yaze_core INTERFACE) + target_link_libraries(yaze_core INTERFACE + yaze_util + yaze_gfx + yaze_gui + yaze_zelda3 + yaze_core_lib + yaze_editor + yaze_emulator + yaze_agent + ImGui ) - - if(YAZE_USE_MODULAR_BUILD) - # Aggregate modular libraries into an interface target for backward compatibility - if(NOT TARGET yaze_core) - add_library(yaze_core INTERFACE) - endif() - - target_include_directories( - yaze_core INTERFACE - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${SDL2_INCLUDE_DIR} - ${PROJECT_BINARY_DIR} - ) - - target_link_libraries( - yaze_core INTERFACE - yaze_util - yaze_gfx - yaze_gui - yaze_zelda3 - yaze_core_lib - yaze_editor - ImGui - ) - - if(TARGET yaze_agent) - target_link_libraries(yaze_core INTERFACE yaze_agent) - endif() - - if(YAZE_BUILD_EMU AND TARGET yaze_emulator) - target_link_libraries(yaze_core INTERFACE yaze_emulator) - endif() - - if(YAZE_ENABLE_UI_TESTS AND TARGET ImGuiTestEngine) - target_link_libraries(yaze_core INTERFACE ImGuiTestEngine) - endif() - - if(YAZE_WITH_GRPC) - target_link_libraries(yaze_core INTERFACE - grpc++ - grpc++_reflection - libprotobuf) - endif() - else() - # Create core library for testing (includes editor and zelda3 components needed by tests) - include(util/util.cmake) - set(YAZE_CORE_SOURCES - app/rom.cc - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_APP_EMU_SRC} - ${YAZE_GUI_SRC} - # cli/service/gui_automation_client.cc # Moved to yaze_c - cli/service/testing/test_workflow_generator.cc - ) - - add_library(yaze_core STATIC ${YAZE_CORE_SOURCES}) - - target_include_directories( - yaze_core PUBLIC - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${SDL2_INCLUDE_DIR} - ${PROJECT_BINARY_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ) - - target_link_libraries( - yaze_core PUBLIC - asar-static - yaze_agent - yaze_util - ${ABSL_TARGETS} - ${SDL_TARGETS} - ${CMAKE_DL_LIBS} - ImGui - ) - - if(YAZE_WITH_GRPC) - target_add_protobuf(yaze_core - ${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto - ${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto) - - target_link_libraries(yaze_core PRIVATE - grpc++ - grpc++_reflection - libprotobuf) - endif() - endif() - - # Create the full C API library (static for CI, shared for release) - if(YAZE_MINIMAL_BUILD) - add_library(yaze_c STATIC ${YAZE_C_SOURCES}) - else() - add_library(yaze_c SHARED ${YAZE_C_SOURCES}) - endif() - - target_include_directories( - yaze_c PUBLIC - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${SDL2_INCLUDE_DIR} - ${PROJECT_BINARY_DIR} - ) - - if(PNG_FOUND) - target_include_directories(yaze_c PUBLIC ${PNG_INCLUDE_DIRS}) - if(NOT YAZE_USE_MODULAR_BUILD) - target_include_directories(yaze_core PUBLIC ${PNG_INCLUDE_DIRS}) - endif() - endif() - - if(YAZE_USE_MODULAR_BUILD) - target_link_libraries( - yaze_c PRIVATE - yaze_util - yaze_gfx - yaze_gui - yaze_zelda3 - yaze_core_lib - yaze_editor - ImGui - ) - - if(TARGET yaze_agent) - target_link_libraries(yaze_c PRIVATE yaze_agent) - endif() - - if(YAZE_BUILD_EMU AND TARGET yaze_emulator) - target_link_libraries(yaze_c PRIVATE yaze_emulator) - endif() - else() - target_link_libraries( - yaze_c PRIVATE - yaze_core - ImGui - ) - endif() - - if(YAZE_WITH_GRPC) - target_add_protobuf(yaze_c - ${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto - ${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto) - - target_link_libraries(yaze_c PRIVATE - grpc++ - grpc++_reflection - libprotobuf) - endif() - - if(YAZE_ENABLE_UI_TESTS AND TARGET ImGuiTestEngine) - target_link_libraries(yaze_c PRIVATE ImGuiTestEngine) - target_compile_definitions(yaze_c PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=1) - else() - target_compile_definitions(yaze_c PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0) - endif() - - # Link with test support library if available (required by editor) - if(TARGET yaze_test_support) - target_link_libraries(yaze_c PRIVATE yaze_test_support) - endif() - - if(PNG_FOUND) - target_link_libraries(yaze_c PRIVATE ${PNG_LIBRARIES}) - if(NOT YAZE_USE_MODULAR_BUILD) - target_link_libraries(yaze_core PRIVATE ${PNG_LIBRARIES}) - endif() - endif() - - if (YAZE_INSTALL_LIB) - install(TARGETS yaze_c - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib/static) - - install( - FILES - incl/yaze.h - incl/zelda.h - DESTINATION - include - ) - endif() endif() - -# ============================================================================= -# Visual Studio Source Groups Organization -# ============================================================================= -# These source groups will organize files in Visual Studio Solution Explorer -# into logical, hierarchical folders for better navigation and development. - -# Core Application Structure -source_group("Application\\Core" FILES - app/main.cc - app/rom.cc - app/rom.h - app/snes.h - app/transaction.h - yaze.cc -) - -# App Core Components -source_group("Application\\Core\\Controller" FILES - app/core/controller.cc - app/core/controller.h - app/core/window.cc - app/core/window.h -) - -source_group("Application\\Core\\Project" FILES - app/core/project.cc - app/core/project.h - app/core/features.h -) - -source_group("Application\\Core\\Asar" FILES - app/core/asar_wrapper.cc - app/core/asar_wrapper.h -) - -# Platform-specific files -source_group("Application\\Core\\Platform" FILES - app/platform/app_delegate.h - app/platform/app_delegate.mm - app/platform/clipboard.cc - app/platform/clipboard.h - app/platform/clipboard.mm - app/platform/file_dialog.cc - app/platform/file_dialog.h - app/platform/file_dialog.mm - app/platform/font_loader.cc - app/platform/font_loader.h - app/platform/font_loader.mm - app/platform/view_controller.h -) - -# Editor System -source_group("Application\\Editor" FILES - app/editor/editor_manager.cc - app/editor/editor_manager.h - app/editor/editor.h - app/editor/editor_safeguards.h -) - -# Code Editor -source_group("Application\\Editor\\Code" FILES - app/editor/code/assembly_editor.cc - app/editor/code/assembly_editor.h - app/editor/code/memory_editor.h -) - -# Dungeon Editor -source_group("Application\\Editor\\Dungeon" FILES - app/editor/dungeon/dungeon_editor_v2.cc - app/editor/dungeon/dungeon_editor_v2.h - app/editor/dungeon/dungeon_map_editor.cc - app/editor/dungeon/dungeon_map_editor.h - app/editor/dungeon/dungeon_room_editor.cc - app/editor/dungeon/dungeon_room_editor.h - app/editor/dungeon/dungeon_sprite_editor.cc - app/editor/dungeon/dungeon_sprite_editor.h - app/editor/dungeon/dungeon_tile_editor.cc - app/editor/dungeon/dungeon_tile_editor.h - app/editor/dungeon/room_data_editor.cc - app/editor/dungeon/room_data_editor.h - app/editor/dungeon/room_properties_editor.cc - app/editor/dungeon/room_properties_editor.h - app/editor/dungeon/room_sprite_editor.cc - app/editor/dungeon/room_sprite_editor.h - app/editor/dungeon/room_tile_editor.cc - app/editor/dungeon/room_tile_editor.h -) - -# Graphics Editor -source_group("Application\\Editor\\Graphics" FILES - app/editor/graphics/graphics_editor.cc - app/editor/graphics/graphics_editor.h - app/editor/graphics/palette_editor.cc - app/editor/graphics/palette_editor.h - app/editor/graphics/sprite_editor.cc - app/editor/graphics/sprite_editor.h - app/editor/graphics/tile_editor.cc - app/editor/graphics/tile_editor.h -) - -# Message Editor -source_group("Application\\Editor\\Message" FILES - app/editor/message/message_editor.cc - app/editor/message/message_editor.h - app/editor/message/text_editor.cc - app/editor/message/text_editor.h - app/editor/message/translation_editor.cc - app/editor/message/translation_editor.h -) - -# Music Editor -source_group("Application\\Editor\\Music" FILES - app/editor/music/music_editor.cc - app/editor/music/music_editor.h -) - -# Overworld Editor -source_group("Application\\Editor\\Overworld" FILES - app/editor/overworld/overworld_editor.cc - app/editor/overworld/overworld_editor.h - app/editor/overworld/overworld_map_editor.cc - app/editor/overworld/overworld_map_editor.h - app/editor/overworld/overworld_sprite_editor.cc - app/editor/overworld/overworld_sprite_editor.h - app/editor/overworld/overworld_tile_editor.cc - app/editor/overworld/overworld_tile_editor.h - app/editor/overworld/overworld_transport_editor.cc - app/editor/overworld/overworld_transport_editor.h - app/editor/overworld/overworld_entrance_editor.cc - app/editor/overworld/overworld_entrance_editor.h -) - -# Sprite Editor -source_group("Application\\Editor\\Sprite" FILES - app/editor/sprite/sprite_editor.cc - app/editor/sprite/sprite_editor.h - app/editor/sprite/sprite_properties_editor.cc - app/editor/sprite/sprite_properties_editor.h -) - -# System Editor -source_group("Application\\Editor\\System" FILES - app/editor/system/asm_editor.cc - app/editor/system/asm_editor.h - app/editor/system/config_editor.cc - app/editor/system/config_editor.h - app/editor/system/debug_console.cc - app/editor/system/debug_console.h - app/editor/system/hex_editor.cc - app/editor/system/hex_editor.h - app/editor/system/log_viewer.cc - app/editor/system/log_viewer.h - app/editor/system/memory_editor.cc - app/editor/system/memory_editor.h - app/editor/system/proposal_drawer.cc - app/editor/system/proposal_drawer.h - app/editor/system/rom_analyzer.cc - app/editor/system/rom_analyzer.h -) - -# Emulator -source_group("Application\\Emulator" FILES - app/emu/emu.cc - app/emu/emulator.cc - app/emu/emulator.h - app/emu/snes.cc - app/emu/snes.h -) - -# Audio System -source_group("Application\\Emulator\\Audio" FILES - app/emu/audio/apu.cc - app/emu/audio/apu.h - app/emu/audio/audio_backend.cc - app/emu/audio/audio_backend.h - app/emu/audio/spc700.cc - app/emu/audio/spc700.h - app/emu/audio/dsp.cc - app/emu/audio/dsp.h - app/emu/audio/internal/addressing.cc - app/emu/audio/internal/addressing.h - app/emu/audio/internal/instructions.cc - app/emu/audio/internal/instructions.h -) - -# CPU System -source_group("Application\\Emulator\\CPU" FILES - app/emu/cpu/cpu.cc - app/emu/cpu/cpu.h - app/emu/cpu/internal/addressing.cc - app/emu/cpu/internal/addressing.h - app/emu/cpu/internal/instructions.cc - app/emu/cpu/internal/instructions.h -) - -# Memory System -source_group("Application\\Emulator\\Memory" FILES - app/emu/memory/dma.cc - app/emu/memory/dma.h - app/emu/memory/memory.cc - app/emu/memory/memory.h - app/emu/memory/mock_memory.h -) - -# Video System -source_group("Application\\Emulator\\Video" FILES - app/emu/video/ppu.cc - app/emu/video/ppu.h - app/emu/video/ppu_registers.h -) - -# Input System (abstracted for SDL2/SDL3) -source_group("Application\\Emulator\\Input" FILES - app/emu/input/input_backend.cc - app/emu/input/input_backend.h - app/emu/input/input_manager.cc - app/emu/input/input_manager.h -) - -# Emulator UI System -source_group("Application\\Emulator\\UI" FILES - app/emu/ui/input_handler.cc - app/emu/ui/input_handler.h - app/emu/ui/emulator_ui.cc - app/emu/ui/emulator_ui.h - app/emu/ui/debugger_ui.cc - app/emu/ui/debugger_ui.h -) - -# Debug System -source_group("Application\\Emulator\\Debug" FILES - app/emu/debug/apu_debugger.cc - app/emu/debug/apu_debugger.h - app/emu/debug/breakpoint_manager.cc - app/emu/debug/breakpoint_manager.h - app/emu/debug/disassembly_viewer.cc - app/emu/debug/disassembly_viewer.h - app/emu/debug/watchpoint_manager.cc - app/emu/debug/watchpoint_manager.h -) - -# Graphics System -source_group("Application\\Graphics" FILES - app/gfx/arena.cc - app/gfx/arena.h - app/gfx/background_buffer.cc - app/gfx/background_buffer.h - app/gfx/bitmap.cc - app/gfx/bitmap.h - app/gfx/compression.cc - app/gfx/compression.h - app/gfx/performance/performance_profiler.cc - app/gfx/performance/performance_profiler.h - app/gfx/scad_format.cc - app/gfx/scad_format.h - app/gfx/snes_color.cc - app/gfx/snes_color.h - app/gfx/snes_palette.cc - app/gfx/snes_palette.h - app/gfx/snes_tile.cc - app/gfx/snes_tile.h - app/gfx/tilemap.cc - app/gfx/tilemap.h -) - -# GUI System -source_group("Application\\GUI" FILES - app/gui/canvas/canvas_utils.cc - app/gui/canvas/canvas_utils.h - app/gui/canvas.cc - app/gui/canvas.h - app/gui/color.cc - app/gui/color.h - app/gui/enhanced_palette_editor.cc - app/gui/widgets/palette_widget.h - app/gui/icons.h - app/gui/input.cc - app/gui/input.h - app/gui/style.cc - app/gui/style.h - app/gui/theme_manager.cc - app/gui/theme_manager.h - app/gui/zeml.cc - app/gui/zeml.h -) - -# GUI Modules -source_group("Application\\GUI\\Modules" FILES - app/gui/modules/about_dialog.cc - app/gui/modules/about_dialog.h - app/gui/modules/preferences_dialog.cc - app/gui/modules/preferences_dialog.h - app/gui/modules/project_dialog.cc - app/gui/modules/project_dialog.h -) - -# Zelda3 Specific -source_group("Application\\Zelda3" FILES - app/zelda3/common.h - app/zelda3/hyrule_magic.cc - app/zelda3/hyrule_magic.h -) - -# Zelda3 Dungeon -source_group("Application\\Zelda3\\Dungeon" FILES - app/zelda3/dungeon/dungeon_data.cc - app/zelda3/dungeon/dungeon_data.h - app/zelda3/dungeon/dungeon_loader.cc - app/zelda3/dungeon/dungeon_loader.h - app/zelda3/dungeon/dungeon_room.cc - app/zelda3/dungeon/dungeon_room.h - app/zelda3/dungeon/dungeon_sprite.cc - app/zelda3/dungeon/dungeon_sprite.h - app/zelda3/dungeon/dungeon_tile.cc - app/zelda3/dungeon/dungeon_tile.h - app/zelda3/dungeon/room_data.cc - app/zelda3/dungeon/room_data.h - app/zelda3/dungeon/room_properties.cc - app/zelda3/dungeon/room_properties.h - app/zelda3/dungeon/room_sprite.cc - app/zelda3/dungeon/room_sprite.h - app/zelda3/dungeon/room_tile.cc - app/zelda3/dungeon/room_tile.h -) - -# Zelda3 Music -source_group("Application\\Zelda3\\Music" FILES - app/zelda3/music/music_data.cc - app/zelda3/music/music_data.h -) - -# Zelda3 Overworld -source_group("Application\\Zelda3\\Overworld" FILES - app/zelda3/overworld/overworld_data.cc - app/zelda3/overworld/overworld_data.h - app/zelda3/overworld/overworld_loader.cc - app/zelda3/overworld/overworld_loader.h - app/zelda3/overworld/overworld_sprite.cc - app/zelda3/overworld/overworld_sprite.h - app/zelda3/overworld/overworld_tile.cc - app/zelda3/overworld/overworld_tile.h - app/zelda3/overworld/overworld_transport.cc - app/zelda3/overworld/overworld_transport.h -) - -# Zelda3 Screen -source_group("Application\\Zelda3\\Screen" FILES - app/zelda3/screen/dungeon_map.cc - app/zelda3/screen/dungeon_map.h - app/zelda3/screen/inventory.cc - app/zelda3/screen/inventory.h - app/zelda3/screen/title_screen.cc - app/zelda3/screen/title_screen.h -) - -# Zelda3 Sprite -source_group("Application\\Zelda3\\Sprite" FILES - app/zelda3/sprite/sprite_data.cc - app/zelda3/sprite/sprite_data.h - app/zelda3/sprite/sprite_loader.cc - app/zelda3/sprite/sprite_loader.h - app/zelda3/sprite/sprite.cc - app/zelda3/sprite/sprite.h -) - -# Testing -source_group("Application\\Testing" FILES - app/test/test_manager.cc - app/test/test_manager.h - app/test/e2e_test_suite.h - app/test/integrated_test_suite.h - app/test/rom_dependent_test_suite.h - app/test/unit_test_suite.h - app/test/zscustomoverworld_test_suite.h -) - -# CLI Tools -source_group("CLI" FILES - cli/cli_main.cc - cli/tui/tui.cc - cli/tui/tui.h - cli/tui/unified_layout.cc - cli/tui/unified_layout.h - cli/tui/enhanced_chat_component.cc - cli/tui/enhanced_chat_component.h - cli/tui/enhanced_status_panel.cc - cli/tui/enhanced_status_panel.h - cli/cli.cc - cli/cli.h -) - -source_group("CLI\\Handlers" FILES - cli/handlers/compress.cc - cli/handlers/patch.cc - cli/handlers/tile16_transfer.cc -) - -# Utilities -source_group("Utilities" FILES - util/bps.cc - util/bps.h - util/flag.cc - util/flag.h - util/hex.cc - util/hex.h - util/log.cc - util/log.h - util/macro.h - util/notify.h -) - -# API -source_group("API" FILES - api/service_handler.cc - api/yaze.proto -) - -source_group("API\\Python" FILES - api/python/yaze_py.cc -) - -# Platform-specific Resources -source_group("Platform\\Windows" FILES - app/platform/win32/yaze.rc -) - -source_group("Platform\\iOS" FILES - ios/main.mm - ios/iOS/Info-iOS.plist - ios/iOS/LaunchScreen.storyboard - ios/macOS/Info-macOS.plist - ios/macOS/MainMenu.storyboard -) - -source_group("Platform\\iOS\\Assets" FILES - ios/Media.xcassets/Contents.json -) - -# Configuration -source_group("Configuration" FILES - yaze_config.h.in -) - -# Assets -source_group("Assets\\Fonts" FILES - ${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf - ${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf - ${CMAKE_SOURCE_DIR}/assets/font/Cousine-Regular.ttf - ${CMAKE_SOURCE_DIR}/assets/font/DroidSans.ttf - ${CMAKE_SOURCE_DIR}/assets/font/NotoSansJP.ttf - ${CMAKE_SOURCE_DIR}/assets/font/IBMPlexSansJP-Bold.ttf - ${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf -) - -source_group("Assets\\Themes" FILES - ${YAZE_THEME_FILES} -) - -source_group("Assets\\Layouts" FILES - ${CMAKE_SOURCE_DIR}/assets/layouts/ow_toolset.zeml -) - -source_group("Assets\\Library" FILES - ${CMAKE_SOURCE_DIR}/assets/lib/libasar.dll -) diff --git a/src/app/app.cmake b/src/app/app.cmake index 1fed388c..e8c93bd5 100644 --- a/src/app/app.cmake +++ b/src/app/app.cmake @@ -1,288 +1,77 @@ -include(app/core/core_library.cmake) -include(app/editor/editor_library.cmake) -include(app/gfx/gfx_library.cmake) -include(app/gui/gui_library.cmake) -include(app/zelda3/zelda3_library.cmake) +# This file defines the main `yaze` application executable. if (APPLE) - add_executable( - yaze - MACOSX_BUNDLE - app/main.cc - # Bundled Resources - ${YAZE_RESOURCE_FILES} - ) + add_executable(yaze MACOSX_BUNDLE app/main.cc ${YAZE_RESOURCE_FILES}) - # Add the app icon to the macOS bundle set(ICON_FILE "${CMAKE_SOURCE_DIR}/assets/yaze.icns") target_sources(yaze PRIVATE ${ICON_FILE}) set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - # Set macOS bundle properties set_target_properties(yaze PROPERTIES MACOSX_BUNDLE_ICON_FILE "yaze.icns" MACOSX_BUNDLE_BUNDLE_NAME "Yaze" - MACOSX_BUNDLE_EXECUTABLE_NAME "yaze" MACOSX_BUNDLE_GUI_IDENTIFIER "com.scawful.yaze" MACOSX_BUNDLE_INFO_STRING "Yet Another Zelda3 Editor" MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_VERSION}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}" - MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}" - MACOSX_BUNDLE_COPYRIGHT "Copyright ยฉ 2024 scawful. All rights reserved." ) else() - # Windows/Linux builds - add_executable( - yaze - app/main.cc - ) - - # Add asset files for Windows/Linux builds + add_executable(yaze app/main.cc) if(WIN32 OR UNIX) target_sources(yaze PRIVATE ${YAZE_RESOURCE_FILES}) - - # Set up asset deployment for Visual Studio - if(WIN32) - foreach(ASSET_FILE ${YAZE_RESOURCE_FILES}) - file(RELATIVE_PATH ASSET_REL_PATH "${CMAKE_SOURCE_DIR}/assets" ${ASSET_FILE}) - get_filename_component(ASSET_DIR ${ASSET_REL_PATH} DIRECTORY) - - set_source_files_properties(${ASSET_FILE} - PROPERTIES - VS_DEPLOYMENT_CONTENT 1 - VS_DEPLOYMENT_LOCATION "assets/${ASSET_DIR}" - ) - endforeach() - endif() endif() endif() -target_include_directories( - yaze PUBLIC - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${CMAKE_SOURCE_DIR}/third_party/httplib - ${SDL2_INCLUDE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} +target_include_directories(yaze PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/incl ${PROJECT_BINARY_DIR} ) target_sources(yaze PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h) +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h PROPERTIES GENERATED TRUE) -# 4) Tell the IDE itโ€™s generated -set_source_files_properties( - ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h - PROPERTIES GENERATED TRUE +# Link modular libraries +target_link_libraries(yaze PRIVATE + yaze_editor + yaze_emulator + yaze_agent + absl::failure_signal_handler + absl::flags + absl::flags_parse ) -# (Optional) put it under a neat filter in VS Solution Explorer -source_group(TREE ${CMAKE_CURRENT_BINARY_DIR} - FILES ${CMAKE_CURRENT_BINARY_DIR}/yaze_config.h) - -# Conditionally add PNG include dirs if available -if(PNG_FOUND) - target_include_directories(yaze PUBLIC ${PNG_INCLUDE_DIRS}) +if(YAZE_BUILD_TESTS AND TARGET yaze_test_support) + target_link_libraries(yaze PRIVATE yaze_test_support) endif() -# Conditionally link nfd if available -if(YAZE_HAS_NFD) - target_link_libraries(yaze PRIVATE nfd) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=1) -else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=0) -endif() - -if(YAZE_USE_MODULAR_BUILD) - set(_yaze_modular_links yaze_editor) - - if(TARGET yaze_agent) - list(APPEND _yaze_modular_links yaze_agent) - endif() - - if(YAZE_BUILD_EMU AND TARGET yaze_emulator) - list(APPEND _yaze_modular_links yaze_emulator) - endif() - - # Link once against the editor library and allow its PUBLIC dependencies - # (core, gfx, util, absl, etc.) to propagate transitively. This avoids - # duplicate static archives on the link line while keeping absl symbols - # available for main and other entry points. - target_link_libraries(yaze PRIVATE ${_yaze_modular_links}) -else() - target_link_libraries(yaze PRIVATE yaze_core) -endif() - -# Enable policy framework in main yaze target -target_compile_definitions(yaze PRIVATE YAZE_ENABLE_POLICY_FRAMEWORK=1) - -# Increase stack size on Windows to prevent stack overflow during asset loading -# Windows default is 1MB, macOS/Linux is typically 8MB -# LoadAssets() loads 223 graphics sheets and initializes multiple editors +# Platform-specific settings if(WIN32) if(MSVC) - # Set Windows subsystem to WINDOWS (GUI app, no console) - # But keep main() as entry point (SDL_MAIN_HANDLED is set globally) - target_link_options(yaze PRIVATE - /STACK:8388608 # 8MB stack - /SUBSYSTEM:WINDOWS # Windows GUI subsystem - /ENTRY:mainCRTStartup # Use main() instead of WinMain() - ) - message(STATUS "Configuring yaze as Windows GUI application with main() entry point") + target_link_options(yaze PRIVATE /STACK:8388608 /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup) elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_link_options(yaze PRIVATE - -Wl,--stack,8388608 # 8MB stack - -Wl,--subsystem,windows # Windows GUI subsystem - -Wl,-emain # Use main() as entry point - ) - message(STATUS "Configuring yaze as Windows GUI application with main() entry point (MinGW)") + target_link_options(yaze PRIVATE -Wl,--stack,8388608 -Wl,--subsystem,windows -Wl,-emain) endif() endif() -# Conditionally link ImGui Test Engine -if(YAZE_ENABLE_UI_TESTS) - if(TARGET ImGuiTestEngine) - target_include_directories(yaze PUBLIC ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine) - target_link_libraries(yaze PUBLIC ImGuiTestEngine) - target_compile_definitions(yaze PRIVATE - YAZE_ENABLE_IMGUI_TEST_ENGINE=1 - ${IMGUI_TEST_ENGINE_DEFINITIONS}) - else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0) - endif() -else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0) -endif() - -# Link Google Test if available for integrated testing (but NOT gtest_main to avoid main() conflicts) -if(YAZE_BUILD_TESTS AND TARGET gtest) - target_link_libraries(yaze PRIVATE gtest) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=1) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=1) -else() - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=0) - target_compile_definitions(yaze PRIVATE YAZE_ENABLE_TESTING=0) -endif() - -# Conditionally link PNG if available -if(PNG_FOUND) - target_link_libraries(yaze PUBLIC ${PNG_LIBRARIES}) -endif() - -if (APPLE) - target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY}) -endif() - -# Post-build step to copy assets to output directory if(APPLE) - # macOS: Copy to bundle Resources + target_link_libraries(yaze PUBLIC "-framework Cocoa") +endif() + +# Post-build asset copying for non-macOS platforms +if(NOT APPLE) add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/../Resources/agent - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/agent - $/../Resources/agent - COMMENT "Copying agent assets to macOS bundle" - ) -elseif(NOT APPLE) - # Add post-build commands directly to the yaze target - # Copy fonts - add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/font - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/font - $/assets/font + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/font $/assets/font COMMENT "Copying font assets" ) - - # Copy themes add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/themes - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/themes - $/assets/themes + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/themes $/assets/themes COMMENT "Copying theme assets" ) - - # Copy agent assets (system prompts, etc.) if(EXISTS ${CMAKE_SOURCE_DIR}/assets/agent) add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/agent - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/agent - $/assets/agent - COMMENT "Copying agent assets (prompts, schemas)" - ) - endif() - - # Copy other assets if they exist - if(EXISTS ${CMAKE_SOURCE_DIR}/assets/layouts) - add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/layouts - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/layouts - $/assets/layouts - COMMENT "Copying layout assets" - ) - endif() - - if(EXISTS ${CMAKE_SOURCE_DIR}/assets/lib) - add_custom_command(TARGET yaze POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory - $/assets/lib - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/lib - $/assets/lib - COMMENT "Copying library assets" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/agent $/assets/agent + COMMENT "Copying agent assets" ) endif() endif() - -# ============================================================================ -# Optional gRPC Support for ImGuiTestHarness -# ============================================================================ -if(YAZE_WITH_GRPC) - message(STATUS "Adding gRPC ImGuiTestHarness to yaze target") - - target_include_directories(yaze PRIVATE - ${CMAKE_SOURCE_DIR}/third_party/json/include) - target_compile_definitions(yaze PRIVATE YAZE_WITH_JSON) - - if(NOT YAZE_USE_MODULAR_BUILD) - # Generate C++ code from .proto using the helper function from cmake/grpc.cmake - target_add_protobuf(yaze - ${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto - ${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto) - - # Add service implementation sources - target_sources(yaze PRIVATE - ${CMAKE_SOURCE_DIR}/src/app/core/service/imgui_test_harness_service.cc - ${CMAKE_SOURCE_DIR}/src/app/core/service/imgui_test_harness_service.h - ${CMAKE_SOURCE_DIR}/src/app/core/service/screenshot_utils.cc - ${CMAKE_SOURCE_DIR}/src/app/core/service/screenshot_utils.h - ${CMAKE_SOURCE_DIR}/src/app/core/service/widget_discovery_service.cc - ${CMAKE_SOURCE_DIR}/src/app/core/service/widget_discovery_service.h - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_recorder.cc - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_recorder.h - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.cc - ${CMAKE_SOURCE_DIR}/src/app/core/testing/test_script_parser.h) - endif() - - # Link gRPC libraries - target_link_libraries(yaze PRIVATE - grpc++ - grpc++_reflection - libprotobuf) - - message(STATUS "โœ“ gRPC ImGuiTestHarness integrated") - message(STATUS "โœ“ AI Agent services integrated into yaze GUI") -endif() diff --git a/src/app/core/core_library.cmake b/src/app/core/core_library.cmake index cff7f1c1..3ec626fa 100644 --- a/src/app/core/core_library.cmake +++ b/src/app/core/core_library.cmake @@ -1,10 +1,9 @@ set( YAZE_APP_CORE_SRC + app/core/asar_wrapper.cc app/core/controller.cc - app/emu/emulator.cc app/core/project.cc app/core/window.cc - app/core/asar_wrapper.cc ) if (WIN32 OR MINGW OR (UNIX AND NOT APPLE)) @@ -16,13 +15,37 @@ endif() if(APPLE) list(APPEND YAZE_APP_CORE_SRC - app/platform/file_dialog.mm - app/platform/app_delegate.mm app/platform/font_loader.cc - app/platform/font_loader.mm app/platform/asset_loader.cc ) + set(YAZE_APPLE_OBJCXX_SRC + app/platform/file_dialog.mm + app/platform/app_delegate.mm + app/platform/font_loader.mm + ) + + add_library(yaze_core_objcxx OBJECT ${YAZE_APPLE_OBJCXX_SRC}) + set_target_properties(yaze_core_objcxx PROPERTIES + OBJCXX_STANDARD 20 + OBJCXX_STANDARD_REQUIRED ON + ) + + target_include_directories(yaze_core_objcxx PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/app + ${CMAKE_SOURCE_DIR}/src/lib + ${CMAKE_SOURCE_DIR}/src/lib/imgui + ${CMAKE_SOURCE_DIR}/src/lib/asar/src + ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar + ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c + ${CMAKE_SOURCE_DIR}/incl + ${SDL2_INCLUDE_DIR} + ${PROJECT_BINARY_DIR} + ) + target_link_libraries(yaze_core_objcxx PUBLIC ${ABSL_TARGETS} yaze_util) + target_compile_definitions(yaze_core_objcxx PUBLIC MACOS) + find_library(COCOA_LIBRARY Cocoa) if(NOT COCOA_LIBRARY) message(FATAL_ERROR "Cocoa not found") @@ -48,6 +71,11 @@ endif() add_library(yaze_core_lib STATIC app/rom.cc ${YAZE_APP_CORE_SRC} + $<$:$> +) + +target_precompile_headers(yaze_core_lib PRIVATE + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" ) target_include_directories(yaze_core_lib PUBLIC diff --git a/src/app/core/testing/test_recorder.cc b/src/app/core/testing/test_recorder.cc index cfd9d544..062d2f39 100644 --- a/src/app/core/testing/test_recorder.cc +++ b/src/app/core/testing/test_recorder.cc @@ -17,7 +17,8 @@ namespace { constexpr absl::Duration kTestCompletionTimeout = absl::Seconds(10); constexpr absl::Duration kPollInterval = absl::Milliseconds(50); -const char* HarnessStatusToString(HarnessTestStatus status) { +#if defined(YAZE_WITH_GRPC) +const char* HarnessStatusToString(test::HarnessTestStatus status) { switch (status) { case HarnessTestStatus::kQueued: return "queued"; @@ -31,9 +32,10 @@ const char* HarnessStatusToString(HarnessTestStatus status) { return "timeout"; case HarnessTestStatus::kUnspecified: default: - return "unknown"; + return "unspecified"; } } +#endif // defined(YAZE_WITH_GRPC) } // namespace @@ -152,7 +154,11 @@ absl::StatusOr TestRecorder::StopLocked( script_step.region = step.region; script_step.format = step.format; script_step.expect_success = step.success; +#if defined(YAZE_WITH_GRPC) script_step.expect_status = ::yaze::test::HarnessStatusToString(step.final_status); +#else + script_step.expect_status.clear(); +#endif if (!step.final_error_message.empty()) { script_step.expect_message = step.final_error_message; } else { @@ -194,6 +200,9 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() { return absl::FailedPreconditionError("TestManager unavailable"); } +#if !defined(YAZE_WITH_GRPC) + return absl::OkStatus(); +#else for (auto& step : steps_) { if (step.test_id.empty()) { continue; @@ -201,7 +210,7 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() { const absl::Time deadline = absl::Now() + kTestCompletionTimeout; while (absl::Now() < deadline) { - absl::StatusOr execution = + absl::StatusOr execution = test_manager_->GetHarnessTestExecution(step.test_id); if (!execution.ok()) { absl::SleepFor(kPollInterval); @@ -213,8 +222,8 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() { step.assertion_failures = execution->assertion_failures; step.metrics = execution->metrics; - if (execution->status == HarnessTestStatus::kQueued || - execution->status == HarnessTestStatus::kRunning) { + if (execution->status == test::HarnessTestStatus::kQueued || + execution->status == test::HarnessTestStatus::kRunning) { absl::SleepFor(kPollInterval); continue; } @@ -223,6 +232,7 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() { } return absl::OkStatus(); +#endif // defined(YAZE_WITH_GRPC) } std::string TestRecorder::GenerateRecordingId() { diff --git a/src/app/core/testing/test_recorder.h b/src/app/core/testing/test_recorder.h index f5c073f6..2218433d 100644 --- a/src/app/core/testing/test_recorder.h +++ b/src/app/core/testing/test_recorder.h @@ -52,7 +52,9 @@ class TestRecorder { std::vector assertion_failures; std::string expected_value; std::string actual_value; +#if defined(YAZE_WITH_GRPC) HarnessTestStatus final_status = HarnessTestStatus::kUnspecified; +#endif std::string final_error_message; std::map metrics; absl::Time captured_at = absl::InfinitePast(); diff --git a/src/app/editor/agent/agent_ui_theme.cc b/src/app/editor/agent/agent_ui_theme.cc index db1f8bf5..0001a379 100644 --- a/src/app/editor/agent/agent_ui_theme.cc +++ b/src/app/editor/agent/agent_ui_theme.cc @@ -38,9 +38,9 @@ AgentUITheme AgentUITheme::FromCurrentTheme() { ); // Content colors - theme.json_text_color = ImVec4(0.78f, 0.83f, 0.90f, 1.0f); - theme.command_text_color = ImVec4(1.0f, 0.647f, 0.0f, 1.0f); - theme.code_bg_color = ImVec4(0.08f, 0.08f, 0.10f, 0.95f); + theme.json_text_color = ConvertColorToImVec4(current.text_secondary); + theme.command_text_color = ConvertColorToImVec4(current.accent); + theme.code_bg_color = ConvertColorToImVec4(current.code_background); theme.text_secondary_color = ConvertColorToImVec4(current.text_secondary); diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index 022bdb62..73e83617 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -1,46 +1,55 @@ set( YAZE_APP_EDITOR_SRC - app/editor/editor_manager.cc - app/editor/ui/menu_builder.cc - app/editor/ui/editor_selection_dialog.cc - app/editor/ui/welcome_screen.cc - app/editor/ui/workspace_manager.cc - app/editor/system/user_settings.cc - app/editor/ui/background_renderer.cc - app/editor/dungeon/dungeon_editor_v2.cc - app/editor/dungeon/dungeon_room_selector.cc - app/editor/dungeon/dungeon_canvas_viewer.cc - app/editor/dungeon/dungeon_object_selector.cc - app/editor/dungeon/dungeon_toolset.cc - app/editor/dungeon/dungeon_object_interaction.cc - app/editor/dungeon/dungeon_room_loader.cc - app/editor/dungeon/dungeon_usage_tracker.cc - app/editor/dungeon/object_editor_card.cc - app/editor/overworld/overworld_editor.cc - app/editor/overworld/scratch_space.cc - app/editor/sprite/sprite_editor.cc - app/editor/music/music_editor.cc - app/editor/message/message_editor.cc - app/editor/message/message_data.cc - app/editor/message/message_preview.cc + app/editor/agent/agent_chat_history_codec.cc + app/editor/agent/agent_chat_history_popup.cc + app/editor/agent/agent_chat_widget.cc + app/editor/agent/agent_collaboration_coordinator.cc + app/editor/agent/agent_editor.cc + app/editor/agent/agent_ui_theme.cc + app/editor/agent/automation_bridge.cc + app/editor/agent/network_collaboration_coordinator.cc app/editor/code/assembly_editor.cc app/editor/code/memory_editor.cc app/editor/code/project_file_editor.cc - app/editor/graphics/screen_editor.cc + app/editor/dungeon/dungeon_canvas_viewer.cc + app/editor/dungeon/dungeon_editor_v2.cc + app/editor/dungeon/dungeon_object_interaction.cc + app/editor/dungeon/dungeon_object_selector.cc + app/editor/dungeon/dungeon_room_loader.cc + app/editor/dungeon/dungeon_room_selector.cc + app/editor/dungeon/dungeon_toolset.cc + app/editor/dungeon/dungeon_usage_tracker.cc + app/editor/dungeon/object_editor_card.cc + app/editor/editor_manager.cc + app/editor/graphics/gfx_group_editor.cc app/editor/graphics/graphics_editor.cc app/editor/graphics/palette_editor.cc - app/editor/overworld/tile16_editor.cc - app/editor/overworld/map_properties.cc - app/editor/graphics/gfx_group_editor.cc + app/editor/graphics/screen_editor.cc + app/editor/message/message_data.cc + app/editor/message/message_editor.cc + app/editor/message/message_preview.cc + app/editor/music/music_editor.cc app/editor/overworld/entity.cc + app/editor/overworld/map_properties.cc + app/editor/overworld/overworld_editor.cc app/editor/overworld/overworld_entity_renderer.cc - app/editor/system/settings_editor.cc + app/editor/overworld/scratch_space.cc + app/editor/overworld/tile16_editor.cc + app/editor/sprite/sprite_editor.cc app/editor/system/command_manager.cc + app/editor/system/command_palette.cc app/editor/system/extension_manager.cc - app/editor/system/shortcut_manager.cc app/editor/system/popup_manager.cc app/editor/system/proposal_drawer.cc - app/editor/agent/agent_chat_history_codec.cc + app/editor/system/settings_editor.cc + app/editor/system/shortcut_manager.cc + app/editor/system/user_settings.cc + app/editor/ui/background_renderer.cc + app/editor/ui/editor_selection_dialog.cc + app/editor/ui/menu_builder.cc + app/editor/ui/menu_manager.cc + app/editor/ui/welcome_screen.cc + app/editor/ui/workspace_manager.cc ) if(YAZE_WITH_GRPC) @@ -76,12 +85,7 @@ endif() add_library(yaze_editor STATIC ${YAZE_APP_EDITOR_SRC}) target_precompile_headers(yaze_editor PRIVATE - - - - - - + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" ) target_include_directories(yaze_editor PUBLIC diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 12c1ea77..932dbef5 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -1337,9 +1337,71 @@ void EditorManager::BuildModernMenu() { menu_builder_.Draw(); } +void EditorManager::DrawMenuBarExtras() { + auto* current_rom = GetCurrentRom(); + std::string version_text = absl::StrFormat("v%s", version_.c_str()); + float version_width = ImGui::CalcTextSize(version_text.c_str()).x; + float session_rom_area_width = 280.0f; + + SameLine(ImGui::GetWindowWidth() - version_width - 10 - session_rom_area_width); + + if (GetActiveSessionCount() > 1) { + if (ImGui::SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, GetActiveSessionCount()).c_str())) { + ShowSessionSwitcher(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Sessions: %zu active\nClick to switch", GetActiveSessionCount()); + } + ImGui::SameLine(); + } + + if (current_rom && current_rom->is_loaded()) { + if (ImGui::SmallButton(ICON_MD_APPS)) { + ShowEditorSelection(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)"); + } + ImGui::SameLine(); + if (ImGui::SmallButton(ICON_MD_DISPLAY_SETTINGS)) { + ShowDisplaySettings(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(ICON_MD_TUNE " Display Settings"); + } + ImGui::SameLine(); + ImGui::Separator(); + ImGui::SameLine(); + } + + if (current_rom && current_rom->is_loaded()) { + std::string rom_display = current_rom->title(); + if (rom_display.length() > 22) { + rom_display = rom_display.substr(0, 19) + "..."; + } + if (ImGui::SmallButton(absl::StrFormat("%s%s", rom_display.c_str(), current_rom->dirty() ? "*" : "").c_str())) { + ImGui::OpenPopup("ROM Details"); + } + } else { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM"); + ImGui::SameLine(); + } + + SameLine(ImGui::GetWindowWidth() - version_width - 10); + ImGui::Text("%s", version_text.c_str()); +} + +void EditorManager::ShowSessionSwitcher() { show_session_switcher_ = true; } + +void EditorManager::ShowEditorSelection() { show_editor_selection_ = true; } + +void EditorManager::ShowDisplaySettings() { if (popup_manager_) popup_manager_->Show("Display Settings"); } + void EditorManager::DrawMenuBar() { static bool show_display_settings = false; static bool save_as_menu = false; + std::string version_text = absl::StrFormat("v%s", version_.c_str()); + float version_width = ImGui::CalcTextSize(version_text.c_str()).x; if (BeginMenuBar()) { BuildModernMenu(); @@ -1347,173 +1409,7 @@ void EditorManager::DrawMenuBar() { // Inline ROM selector and status status_ = DrawRomSelector(); - // Calculate proper right-side positioning - std::string version_text = absl::StrFormat("v%s", version_.c_str()); - float version_width = CalcTextSize(version_text.c_str()).x; - - // Allocate space for ROM status and sessions (wider for better ROM title display) - float session_rom_area_width = 280.0f; // Increased for wider ROM title - SameLine(GetWindowWidth() - version_width - 10 - session_rom_area_width); - - // Multi-session indicator - if (sessions_.size() > 1) { - if (SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, sessions_.size()) - .c_str())) { - show_session_switcher_ = true; - } - if (IsItemHovered()) { - SetTooltip("Sessions: %zu active\nClick to switch", sessions_.size()); - } - SameLine(); - } - - // Editor selection and display settings quick buttons (when ROM loaded) - if (current_rom_ && current_rom_->is_loaded()) { - // Editor selection button - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 1.0f)); - if (SmallButton(ICON_MD_APPS)) { - show_editor_selection_ = true; - } - ImGui::PopStyleColor(2); - if (IsItemHovered()) { - SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)"); - } - SameLine(); - - // Display settings button - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 1.0f)); - if (SmallButton(ICON_MD_DISPLAY_SETTINGS)) { - show_display_settings = !show_display_settings; - } - ImGui::PopStyleColor(2); - if (IsItemHovered()) { - SetTooltip(ICON_MD_TUNE " Display Settings"); - } - SameLine(); - - ImGui::Separator(); - SameLine(); - } - - // Compact ROM status with metadata popup - if (current_rom_ && current_rom_->is_loaded()) { - // Truncate long ROM titles for wider display - std::string rom_display = current_rom_->title(); - if (rom_display.length() > 22) { - rom_display = rom_display.substr(0, 19) + "..."; - } - - ImVec4 status_color = - current_rom_->dirty() ? ImVec4(1.0f, 0.5f, 0.0f, 1.0f) - : // Orange for modified - ImVec4(0.0f, 0.8f, 0.0f, 1.0f); // Green for clean - - // Compact ROM status button - if (SmallButton(absl::StrFormat("%s%s", rom_display.c_str(), - current_rom_->dirty() ? "*" : "") - .c_str())) { - ImGui::OpenPopup("ROM Details"); - } - - // Enhanced tooltip on hover - if (IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s ROM Information", ICON_MD_INFO); - ImGui::Separator(); - ImGui::Text("%s Title: %s", ICON_MD_TITLE, - current_rom_->title().c_str()); - ImGui::Text("%s File: %s", ICON_MD_FOLDER_OPEN, - current_rom_->filename().c_str()); - ImGui::Text("%s Size: %.1f MB (%zu bytes)", ICON_MD_STORAGE, - current_rom_->size() / 1048576.0f, current_rom_->size()); - ImGui::Text("%s Status: %s", - current_rom_->dirty() ? ICON_MD_EDIT : ICON_MD_CHECK_CIRCLE, - current_rom_->dirty() ? "Modified" : "Clean"); - ImGui::Text("%s Click for detailed view", ICON_MD_LAUNCH); - ImGui::EndTooltip(); - } - - // Detailed ROM popup - if (ImGui::BeginPopup("ROM Details")) { - ImGui::Text("%s ROM Detailed Information", ICON_MD_INFO); - ImGui::Separator(); - ImGui::Spacing(); - - // Basic info with icons - if (ImGui::BeginTable("ROMDetailsTable", 2, - ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, - 120); - ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s Title", ICON_MD_TITLE); - ImGui::TableNextColumn(); - ImGui::Text("%s", current_rom_->title().c_str()); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s File", ICON_MD_FOLDER_OPEN); - ImGui::TableNextColumn(); - ImGui::Text("%s", current_rom_->filename().c_str()); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s Size", ICON_MD_STORAGE); - ImGui::TableNextColumn(); - ImGui::Text("%.1f MB (%zu bytes)", current_rom_->size() / 1048576.0f, - current_rom_->size()); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s Status", current_rom_->dirty() - ? ICON_MD_EDIT - : ICON_MD_CHECK_CIRCLE); - ImGui::TableNextColumn(); - ImGui::TextColored(status_color, "%s", - current_rom_->dirty() ? "Modified" : "Clean"); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s Session", ICON_MD_TAB); - ImGui::TableNextColumn(); - size_t current_session_idx = GetCurrentSessionIndex(); - ImGui::Text("Session %zu of %zu", current_session_idx + 1, - sessions_.size()); - - ImGui::EndTable(); - } - - ImGui::Spacing(); - ImGui::Separator(); - - // Quick actions - ImGui::Text("%s Quick Actions", ICON_MD_FLASH_ON); - if (ImGui::Button(absl::StrFormat("%s Save ROM", ICON_MD_SAVE).c_str(), - ImVec2(120, 0))) { - status_ = SaveRom(); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button( - absl::StrFormat("%s Switch Session", ICON_MD_SWITCH_ACCOUNT) - .c_str(), - ImVec2(120, 0))) { - show_session_switcher_ = true; - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - SameLine(); - } else { - TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM"); - SameLine(); - } + DrawMenuBarExtras(); // Version display on far right SameLine(GetWindowWidth() - version_width - 10); diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index 5f668146..8c72b787 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -122,6 +122,11 @@ class EditorManager { auto emulator() -> emu::Emulator& { return emulator_; } auto quit() const { return quit_; } auto version() const { return version_; } + void DrawMenuBarExtras(); + MenuBuilder& menu_builder() { return menu_builder_; } + void ShowSessionSwitcher(); + void ShowEditorSelection(); + void ShowDisplaySettings(); absl::Status SetCurrentRom(Rom* rom); auto GetCurrentRom() -> Rom* { return current_rom_; } diff --git a/src/app/editor/ui/menu_manager.cc b/src/app/editor/ui/menu_manager.cc index 07d19e45..5bdc8ef9 100644 --- a/src/app/editor/ui/menu_manager.cc +++ b/src/app/editor/ui/menu_manager.cc @@ -11,63 +11,13 @@ MenuManager::MenuManager(EditorManager* editor_manager) : editor_manager_(editor_manager) {} void MenuManager::BuildAndDraw() { + if (!editor_manager_) { + return; + } + if (ImGui::BeginMenuBar()) { - editor_manager_->BuildModernMenu(); // This contains the menu_builder_ logic - editor_manager_->menu_builder_.Draw(); - - // This is the logic from the second half of DrawMenuBar - auto* current_rom = editor_manager_->GetCurrentRom(); - std::string version_text = absl::StrFormat("v%s", editor_manager_->version().c_str()); - float version_width = ImGui::CalcTextSize(version_text.c_str()).x; - float session_rom_area_width = 280.0f; - ImGui::SameLine(ImGui::GetWindowWidth() - version_width - 10 - session_rom_area_width); - - if (editor_manager_->GetActiveSessionCount() > 1) { - if (ImGui::SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, editor_manager_->GetActiveSessionCount()).c_str())) { - editor_manager_->show_session_switcher_ = true; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Sessions: %zu active\nClick to switch", editor_manager_->GetActiveSessionCount()); - } - ImGui::SameLine(); - } - - if (current_rom && current_rom->is_loaded()) { - if (ImGui::SmallButton(ICON_MD_APPS)) { - editor_manager_->show_editor_selection_ = true; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)"); - } - ImGui::SameLine(); - if (ImGui::SmallButton(ICON_MD_DISPLAY_SETTINGS)) { - editor_manager_->popup_manager_->Show("Display Settings"); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(ICON_MD_TUNE " Display Settings"); - } - ImGui::SameLine(); - ImGui::Separator(); - ImGui::SameLine(); - } - - if (current_rom && current_rom->is_loaded()) { - std::string rom_display = current_rom->title(); - if (rom_display.length() > 22) { - rom_display = rom_display.substr(0, 19) + "..."; - } - ImVec4 status_color = current_rom->dirty() ? ImVec4(1.0f, 0.5f, 0.0f, 1.0f) : ImVec4(0.0f, 0.8f, 0.0f, 1.0f); - if (ImGui::SmallButton(absl::StrFormat("%s%s", rom_display.c_str(), current_rom->dirty() ? "*" : "").c_str())) { - ImGui::OpenPopup("ROM Details"); - } - // ... (rest of the popup logic from DrawMenuBar) - } else { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM"); - ImGui::SameLine(); - } - - ImGui::SameLine(ImGui::GetWindowWidth() - version_width - 10); - ImGui::Text("%s", version_text.c_str()); + editor_manager_->BuildModernMenu(); + editor_manager_->DrawMenuBarExtras(); ImGui::EndMenuBar(); } } diff --git a/src/app/editor/ui/menu_manager.h b/src/app/editor/ui/menu_manager.h index fa09a1a1..4aba172c 100644 --- a/src/app/editor/ui/menu_manager.h +++ b/src/app/editor/ui/menu_manager.h @@ -16,7 +16,6 @@ class MenuManager { private: EditorManager* editor_manager_; - MenuBuilder menu_builder_; }; } // namespace editor diff --git a/src/app/emu/emu.cmake b/src/app/emu/emu.cmake index d15dc6ea..50ba7d88 100644 --- a/src/app/emu/emu.cmake +++ b/src/app/emu/emu.cmake @@ -1,76 +1,55 @@ -# Yaze Emulator Standalone Application (skip in minimal builds) -if (NOT YAZE_MINIMAL_BUILD AND APPLE) - add_executable( - yaze_emu - MACOSX_BUNDLE - app/main.cc - app/rom.cc - app/platform/app_delegate.mm - ${YAZE_APP_EMU_SRC} - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_UTIL_SRC} - ${YAZE_GUI_SRC} - ${IMGUI_SRC} - # CLI service sources (needed for ProposalDrawer) - cli/service/planning/proposal_registry.cc - cli/service/rom/rom_sandbox_manager.cc - ) - target_link_libraries(yaze_emu PUBLIC ${COCOA_LIBRARY}) -elseif(NOT YAZE_MINIMAL_BUILD) - add_executable( - yaze_emu - app/rom.cc - app/emu/emu.cc - ${YAZE_APP_EMU_SRC} - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_UTIL_SRC} - ${YAZE_GUI_SRC} - ${IMGUI_SRC} - # CLI service sources (needed for ProposalDrawer) - cli/service/planning/proposal_registry.cc - cli/service/rom/rom_sandbox_manager.cc - ) -endif() +# This file defines the yaze_emu standalone executable. +# Note: The yaze_emulator library is ALWAYS built (via emu_library.cmake) +# because it's used by the main yaze app and test suites. +# This file only controls the STANDALONE emulator executable. -# Only configure emulator target if it was created -if(NOT YAZE_MINIMAL_BUILD) - target_include_directories( - yaze_emu PUBLIC - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${PNG_INCLUDE_DIRS} - ${SDL2_INCLUDE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} +if(YAZE_BUILD_EMU AND NOT YAZE_MINIMAL_BUILD) + if(APPLE) + add_executable(yaze_emu MACOSX_BUNDLE app/emu/emu.cc app/platform/app_delegate.mm) + target_link_libraries(yaze_emu PUBLIC "-framework Cocoa") + else() + add_executable(yaze_emu app/emu/emu.cc) + endif() + + target_include_directories(yaze_emu PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/incl ${PROJECT_BINARY_DIR} ) - target_link_libraries( - yaze_emu PUBLIC - ${ABSL_TARGETS} - ${SDL_TARGETS} - ${PNG_LIBRARIES} - ${CMAKE_DL_LIBS} - ImGui - asar-static + target_link_libraries(yaze_emu PRIVATE + yaze_editor + yaze_emulator + yaze_agent + yaze_test_support + absl::flags + absl::flags_parse + absl::failure_signal_handler ) - # Conditionally link ImGui Test Engine if(YAZE_ENABLE_UI_TESTS) - target_link_libraries(yaze_emu PUBLIC ImGuiTestEngine) target_compile_definitions(yaze_emu PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=1) - else() - target_compile_definitions(yaze_emu PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0) endif() + + # Headless Emulator Test Harness + add_executable(yaze_emu_test emu_test.cc) + target_include_directories(yaze_emu_test PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/incl + ${PROJECT_BINARY_DIR} + ) + target_link_libraries(yaze_emu_test PRIVATE + yaze_emulator + yaze_util + absl::flags + absl::flags_parse + absl::status + absl::strings + absl::str_format + ) + message(STATUS "โœ“ yaze_emu_test: Headless emulator test harness configured") + message(STATUS "โœ“ yaze_emu: Standalone emulator executable configured") +else() + message(STATUS "โ—‹ Standalone emulator builds disabled (YAZE_BUILD_EMU=OFF or YAZE_MINIMAL_BUILD=ON)") + message(STATUS " Note: yaze_emulator library still available for main app and tests") endif() diff --git a/src/app/emu/emu_library.cmake b/src/app/emu/emu_library.cmake index 57f15f8e..92abf42d 100644 --- a/src/app/emu/emu_library.cmake +++ b/src/app/emu/emu_library.cmake @@ -13,6 +13,10 @@ add_library(yaze_emulator STATIC ${YAZE_APP_EMU_SRC}) +target_precompile_headers(yaze_emulator PRIVATE + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" +) + target_include_directories(yaze_emulator PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/app diff --git a/src/app/gfx/gfx_library.cmake b/src/app/gfx/gfx_library.cmake index 8837f34c..d178eaaf 100644 --- a/src/app/gfx/gfx_library.cmake +++ b/src/app/gfx/gfx_library.cmake @@ -36,9 +36,7 @@ set( add_library(yaze_gfx STATIC ${YAZE_APP_GFX_SRC}) target_precompile_headers(yaze_gfx PRIVATE - - - + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" ) target_include_directories(yaze_gfx PUBLIC diff --git a/src/app/gui/gui_library.cmake b/src/app/gui/gui_library.cmake index 31319328..51dd91f1 100644 --- a/src/app/gui/gui_library.cmake +++ b/src/app/gui/gui_library.cmake @@ -1,33 +1,34 @@ set( YAZE_GUI_SRC + app/gui/canvas.cc + app/gui/canvas/bpp_format_ui.cc + app/gui/canvas/canvas_automation_api.cc + app/gui/canvas/canvas_context_menu.cc + app/gui/canvas/canvas_interaction_handler.cc + app/gui/canvas/canvas_modals.cc + app/gui/canvas/canvas_performance_integration.cc + app/gui/canvas/canvas_usage_tracker.cc + app/gui/canvas/canvas_utils.cc + app/gui/color.cc + app/gui/editor_card_manager.cc + app/gui/editor_layout.cc + app/gui/input.cc app/gui/modules/asset_browser.cc app/gui/modules/text_editor.cc - app/gui/widgets/agent_chat_widget.cc - app/gui/widgets/dungeon_object_emulator_preview.cc - app/gui/widgets/collaboration_panel.cc - app/gui/canvas.cc - app/gui/canvas/canvas_utils.cc - app/gui/widgets/palette_widget.cc - app/gui/widgets/palette_editor_widget.cc - app/gui/input.cc app/gui/style.cc - app/gui/color.cc app/gui/theme_manager.cc - app/gui/canvas/bpp_format_ui.cc - app/gui/widgets/widget_id_registry.cc - app/gui/widgets/widget_auto_register.cc - app/gui/widgets/widget_state_capture.cc app/gui/ui_helpers.cc - app/gui/editor_layout.cc - app/gui/editor_card_manager.cc - # Canvas system components - app/gui/canvas/canvas_modals.cc - app/gui/canvas/canvas_context_menu.cc - app/gui/canvas/canvas_usage_tracker.cc - app/gui/canvas/canvas_performance_integration.cc - app/gui/canvas/canvas_interaction_handler.cc - app/gui/canvas/canvas_automation_api.cc + app/gui/widgets/agent_chat_widget.cc + app/gui/widgets/collaboration_panel.cc + app/gui/widgets/dungeon_object_emulator_preview.cc + app/gui/widgets/palette_editor_widget.cc + app/gui/widgets/palette_widget.cc app/gui/widgets/tile_selector_widget.cc + app/gui/widgets/widget_auto_register.cc + app/gui/widgets/widget_id_registry.cc + app/gui/widgets/widget_measurement.cc + app/gui/widgets/widget_state_capture.cc + # Canvas system components ) # ============================================================================== @@ -47,12 +48,7 @@ set( add_library(yaze_gui STATIC ${YAZE_GUI_SRC}) target_precompile_headers(yaze_gui PRIVATE - - - - - - + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" ) target_include_directories(yaze_gui PUBLIC diff --git a/src/app/gui/theme_manager.cc b/src/app/gui/theme_manager.cc index 80d5c14d..140f43a4 100644 --- a/src/app/gui/theme_manager.cc +++ b/src/app/gui/theme_manager.cc @@ -67,6 +67,8 @@ void EnhancedTheme::ApplyToImGui() const { colors[ImGuiCol_Tab] = ConvertColorToImVec4(tab); colors[ImGuiCol_TabHovered] = ConvertColorToImVec4(tab_hovered); colors[ImGuiCol_TabSelected] = ConvertColorToImVec4(tab_active); + colors[ImGuiCol_TabUnfocused] = ConvertColorToImVec4(tab_unfocused); + colors[ImGuiCol_TabUnfocusedActive] = ConvertColorToImVec4(tab_unfocused_active); colors[ImGuiCol_DockingPreview] = ConvertColorToImVec4(docking_preview); colors[ImGuiCol_DockingEmptyBg] = ConvertColorToImVec4(docking_empty_bg); @@ -170,6 +172,8 @@ void ThemeManager::CreateFallbackYazeClassic() { theme.tab = RGBA(46, 66, 46); // alttpDarkGreen theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen theme.tab_active = RGBA(89, 119, 89); // TabActive + theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab + theme.tab_unfocused_active = RGBA(62, 83, 62); // Darker version of tab_active // Complete all remaining ImGui colors from original ColorsYaze() function theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen @@ -875,6 +879,8 @@ void ThemeManager::ApplyClassicYazeTheme() { classic_theme.tab = RGBA(46, 66, 46); // alttpDarkGreen classic_theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen classic_theme.tab_active = RGBA(89, 119, 89); // TabActive + classic_theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab + classic_theme.tab_unfocused_active = RGBA(62, 83, 62); // Darker version of tab_active // Complete all remaining ImGui colors from original ColorsYaze() function classic_theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen @@ -1432,6 +1438,8 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { {"Tab", &edit_theme.tab}, {"Tab Hovered", &edit_theme.tab_hovered}, {"Tab Active", &edit_theme.tab_active}, + {"Tab Unfocused", &edit_theme.tab_unfocused}, + {"Tab Unfocused Active", &edit_theme.tab_unfocused_active}, {"Tab Dimmed", &edit_theme.tab_dimmed}, {"Tab Dimmed Selected", &edit_theme.tab_dimmed_selected}, {"Title Background", &edit_theme.title_bg}, diff --git a/src/app/gui/theme_manager.h b/src/app/gui/theme_manager.h index 45172f32..80ce4ce1 100644 --- a/src/app/gui/theme_manager.h +++ b/src/app/gui/theme_manager.h @@ -108,6 +108,8 @@ struct EnhancedTheme { Color tree_lines; // Additional ImGui colors for complete coverage + Color tab_unfocused; + Color tab_unfocused_active; Color tab_dimmed; Color tab_dimmed_selected; Color tab_dimmed_selected_overline; diff --git a/src/app/net/net_library.cmake b/src/app/net/net_library.cmake index f2d51aa2..4f37d610 100644 --- a/src/app/net/net_library.cmake +++ b/src/app/net/net_library.cmake @@ -23,6 +23,10 @@ endif() add_library(yaze_net STATIC ${YAZE_NET_SRC}) +target_precompile_headers(yaze_net PRIVATE + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" +) + target_include_directories(yaze_net PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/lib diff --git a/src/app/platform/app_delegate.mm b/src/app/platform/app_delegate.mm index bf644fcd..05afdfc6 100644 --- a/src/app/platform/app_delegate.mm +++ b/src/app/platform/app_delegate.mm @@ -4,6 +4,10 @@ #import "util/file_util.h" #import "app/editor/editor.h" #import "app/rom.h" +#include +#include + +using std::span; #if defined(__APPLE__) && defined(__MACH__) /* Apple OSX and iOS (Darwin). */ diff --git a/src/app/test/test.cmake b/src/app/test/test.cmake index 1a32d05e..03934ca2 100644 --- a/src/app/test/test.cmake +++ b/src/app/test/test.cmake @@ -18,16 +18,13 @@ set(YAZE_TEST_SOURCES add_library(yaze_test_support STATIC ${YAZE_TEST_SOURCES}) target_precompile_headers(yaze_test_support PRIVATE - - - - - + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" ) target_include_directories(yaze_test_support PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/incl + ${CMAKE_SOURCE_DIR}/src/lib ${PROJECT_BINARY_DIR} ) @@ -41,11 +38,17 @@ target_link_libraries(yaze_test_support PUBLIC yaze_common ) -# Link agent library if gRPC is enabled (for z3ed test suites) +# Link agent library if available (for z3ed test suites) # yaze_agent contains all the CLI service code (tile16_proposal_generator, gui_automation_client, etc.) -if(YAZE_WITH_GRPC) +if(TARGET yaze_agent) target_link_libraries(yaze_test_support PUBLIC yaze_agent) - message(STATUS "โœ“ z3ed test suites enabled (YAZE_WITH_GRPC=ON)") + if(YAZE_WITH_GRPC) + message(STATUS "โœ“ z3ed test suites enabled with gRPC support") + else() + message(STATUS "โœ“ z3ed test suites enabled (without gRPC)") + endif() +else() + message(STATUS "โ—‹ z3ed test suites disabled (yaze_agent not built)") endif() message(STATUS "โœ“ yaze_test_support library configured") \ No newline at end of file diff --git a/src/app/test/test_manager.cc b/src/app/test/test_manager.cc index b3b9e4f8..b54f92b3 100644 --- a/src/app/test/test_manager.cc +++ b/src/app/test/test_manager.cc @@ -1675,9 +1675,9 @@ absl::Status TestManager::TestRomDataIntegrity(Rom* rom) { }); } +#if defined(YAZE_WITH_GRPC) std::string TestManager::RegisterHarnessTest(const std::string& name, const std::string& category) { -#if defined(YAZE_WITH_GRPC) absl::MutexLock lock(&harness_history_mutex_); std::string test_id = absl::StrCat("harness_", absl::ToUnixMicros(absl::Now()), "_", harness_history_.size()); HarnessTestExecution execution; @@ -1693,12 +1693,15 @@ std::string TestManager::RegisterHarnessTest(const std::string& name, aggregate.latest_execution = execution; harness_history_order_.push_back(test_id); return test_id; +} #else +std::string TestManager::RegisterHarnessTest(const std::string& name, + const std::string& category) { (void)name; (void)category; return {}; -#endif } +#endif #if defined(YAZE_WITH_GRPC) void TestManager::MarkHarnessTestRunning(const std::string& test_id) { @@ -1852,10 +1855,12 @@ void TestManager::CaptureFailureContext(const std::string& test_id) { if (harness_listener_) { harness_listener_->OnHarnessTestUpdated(execution); } -#else - (void)test_id; -#endif } +#else +void TestManager::CaptureFailureContext(const std::string& test_id) { + (void)test_id; +} +#endif #if defined(YAZE_WITH_GRPC) void TestManager::TrimHarnessHistoryLocked() { @@ -1870,14 +1875,6 @@ absl::Status TestManager::ReplayLastPlan() { return absl::FailedPreconditionError("Harness plan replay not available"); } -absl::Status TestManager::ShowHarnessDashboard() { - return absl::OkStatus(); -} - -absl::Status TestManager::ShowHarnessActiveTests() { - return absl::OkStatus(); -} - void TestManager::SetHarnessListener(HarnessListener* listener) { absl::MutexLock lock(&mutex_); harness_listener_ = listener; @@ -1886,15 +1883,24 @@ void TestManager::SetHarnessListener(HarnessListener* listener) { absl::Status TestManager::ReplayLastPlan() { return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC"); } +#endif absl::Status TestManager::ShowHarnessDashboard() { + // These methods are always available, but may return unimplemented without GRPC +#if defined(YAZE_WITH_GRPC) + return absl::OkStatus(); +#else return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC"); +#endif } absl::Status TestManager::ShowHarnessActiveTests() { +#if defined(YAZE_WITH_GRPC) + return absl::OkStatus(); +#else return absl::UnimplementedError("Harness features require YAZE_WITH_GRPC"); -} #endif +} void TestManager::RecordPlanSummary(const std::string& summary) { (void)summary; diff --git a/src/app/test/test_manager.h b/src/app/test/test_manager.h index acf74aa4..5c973d86 100644 --- a/src/app/test/test_manager.h +++ b/src/app/test/test_manager.h @@ -299,8 +299,16 @@ class TestManager { void SetHarnessListener(HarnessListener* listener); + absl::Status ReplayLastPlan(); +#else + // Stub implementations when GRPC is not available + std::string RegisterHarnessTest(const std::string& name, + const std::string& category); + void CaptureFailureContext(const std::string& test_id); absl::Status ReplayLastPlan(); #endif + + // These methods are always available absl::Status ShowHarnessDashboard(); absl::Status ShowHarnessActiveTests(); void RecordPlanSummary(const std::string& summary); diff --git a/src/app/zelda3/zelda3_library.cmake b/src/app/zelda3/zelda3_library.cmake index c20f487c..4326a00a 100644 --- a/src/app/zelda3/zelda3_library.cmake +++ b/src/app/zelda3/zelda3_library.cmake @@ -1,21 +1,22 @@ set( YAZE_APP_ZELDA3_SRC - app/zelda3/hyrule_magic.cc - app/zelda3/zelda3_labels.cc - app/zelda3/overworld/overworld_map.cc - app/zelda3/overworld/overworld.cc - app/zelda3/screen/inventory.cc - app/zelda3/screen/title_screen.cc - app/zelda3/screen/dungeon_map.cc - app/zelda3/sprite/sprite.cc - app/zelda3/sprite/sprite_builder.cc - app/zelda3/music/tracker.cc - app/zelda3/dungeon/room.cc - app/zelda3/dungeon/room_object.cc - app/zelda3/dungeon/object_parser.cc - app/zelda3/dungeon/object_drawer.cc app/zelda3/dungeon/dungeon_editor_system.cc app/zelda3/dungeon/dungeon_object_editor.cc + app/zelda3/dungeon/object_drawer.cc + app/zelda3/dungeon/object_parser.cc + app/zelda3/dungeon/room.cc + app/zelda3/dungeon/room_layout.cc + app/zelda3/dungeon/room_object.cc + app/zelda3/hyrule_magic.cc + app/zelda3/music/tracker.cc + app/zelda3/overworld/overworld.cc + app/zelda3/overworld/overworld_map.cc + app/zelda3/screen/dungeon_map.cc + app/zelda3/screen/inventory.cc + app/zelda3/screen/title_screen.cc + app/zelda3/sprite/sprite.cc + app/zelda3/sprite/sprite_builder.cc + app/zelda3/zelda3_labels.cc ) # ============================================================================== @@ -34,12 +35,7 @@ set( add_library(yaze_zelda3 STATIC ${YAZE_APP_ZELDA3_SRC}) target_precompile_headers(yaze_zelda3 PRIVATE - - - - - - + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" ) target_include_directories(yaze_zelda3 PUBLIC diff --git a/src/cli/agent.cmake b/src/cli/agent.cmake index 853e9784..3a5e29c0 100644 --- a/src/cli/agent.cmake +++ b/src/cli/agent.cmake @@ -1,69 +1,3 @@ -include(FetchContent) - -function(_yaze_ensure_yaml_cpp _out_target) - if(TARGET yaml-cpp::yaml-cpp) - set(${_out_target} yaml-cpp::yaml-cpp PARENT_SCOPE) - return() - endif() - - if(TARGET yaml-cpp) - set(${_out_target} yaml-cpp PARENT_SCOPE) - return() - endif() - - find_package(yaml-cpp CONFIG QUIET) - - if(TARGET yaml-cpp::yaml-cpp) - set(${_out_target} yaml-cpp::yaml-cpp PARENT_SCOPE) - return() - elseif(TARGET yaml-cpp) - set(${_out_target} yaml-cpp PARENT_SCOPE) - return() - endif() - - message(STATUS "yaml-cpp not found via package config, fetching from source") - - FetchContent_Declare(yaml-cpp - GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git - GIT_TAG 0.8.0 - ) - - FetchContent_GetProperties(yaml-cpp) - if(NOT yaml-cpp_POPULATED) - FetchContent_Populate(yaml-cpp) - - set(_yaml_cpp_cmakelists "${yaml-cpp_SOURCE_DIR}/CMakeLists.txt") - if(EXISTS "${_yaml_cpp_cmakelists}") - file(READ "${_yaml_cpp_cmakelists}" _yaml_cpp_cmake_contents) - if(_yaml_cpp_cmake_contents MATCHES "cmake_minimum_required\\(VERSION 3\\.4\\)") - string(REPLACE "cmake_minimum_required(VERSION 3.4)" - "cmake_minimum_required(VERSION 3.5)" - _yaml_cpp_cmake_contents "${_yaml_cpp_cmake_contents}") - file(WRITE "${_yaml_cpp_cmakelists}" "${_yaml_cpp_cmake_contents}") - endif() - endif() - - set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Disable yaml-cpp tests" FORCE) - set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "Disable yaml-cpp contrib" FORCE) - set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "Disable yaml-cpp tools" FORCE) - set(YAML_CPP_INSTALL OFF CACHE BOOL "Disable yaml-cpp install" FORCE) - set(YAML_CPP_FORMAT_SOURCE OFF CACHE BOOL "Disable yaml-cpp format target" FORCE) - - add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR} EXCLUDE_FROM_ALL) - - if(NOT TARGET yaml-cpp) - message(FATAL_ERROR "yaml-cpp target was not created after fetching") - endif() - - # Ensure the fetched target exposes its public headers - target_include_directories(yaml-cpp PUBLIC ${yaml-cpp_SOURCE_DIR}/include) - endif() - - set(${_out_target} yaml-cpp PARENT_SCOPE) -endfunction() - -_yaze_ensure_yaml_cpp(YAZE_YAML_CPP_TARGET) - set(YAZE_AGENT_SOURCES cli/service/agent/proposal_executor.cc cli/handlers/agent/todo_commands.cc @@ -73,6 +7,7 @@ set(YAZE_AGENT_SOURCES cli/service/agent/tool_dispatcher.cc cli/service/agent/learned_knowledge_service.cc cli/service/agent/todo_manager.cc + cli/service/agent/vim_mode.cc cli/service/ai/ai_service.cc cli/service/ai/ai_action_parser.cc cli/service/ai/vision_action_refiner.cc @@ -91,8 +26,9 @@ set(YAZE_AGENT_SOURCES cli/service/resources/resource_context_builder.cc cli/service/resources/command_context.cc cli/service/resources/command_handler.cc - cli/handlers/command_wrappers.cc cli/handlers/agent.cc + cli/handlers/command_handlers.cc + cli/handlers/agent/simple_chat_command.cc cli/handlers/game/overworld_inspect.cc cli/handlers/game/message.cc cli/handlers/rom/mock_rom.cc @@ -107,6 +43,7 @@ set(YAZE_AGENT_SOURCES cli/handlers/graphics/palette_commands.cc cli/handlers/tools/emulator_commands.cc cli/handlers/game/message_commands.cc + cli/handlers/graphics/sprite_commands.cc # ROM commands cli/handlers/rom/rom_commands.cc cli/handlers/rom/project_commands.cc @@ -122,8 +59,14 @@ add_library(yaze_agent STATIC ${YAZE_AGENT_SOURCES}) set(_yaze_agent_link_targets yaze_common + yaze_util + yaze_gfx + yaze_gui + yaze_core_lib + yaze_zelda3 + yaze_emulator ${ABSL_TARGETS} - ${YAZE_YAML_CPP_TARGET} + yaml-cpp ) target_link_libraries(yaze_agent PUBLIC ${_yaze_agent_link_targets}) @@ -135,15 +78,9 @@ target_include_directories(yaze_agent ${CMAKE_SOURCE_DIR}/third_party/httplib ${CMAKE_SOURCE_DIR}/third_party/json/include ${CMAKE_SOURCE_DIR}/src/lib + ${CMAKE_SOURCE_DIR}/src/cli/handlers ) -if(YAZE_YAML_CPP_TARGET) - get_target_property(_yaze_yaml_include_dirs ${YAZE_YAML_CPP_TARGET} INTERFACE_INCLUDE_DIRECTORIES) - if(_yaze_yaml_include_dirs) - target_include_directories(yaze_agent PUBLIC ${_yaze_yaml_include_dirs}) - endif() -endif() - if(SDL2_INCLUDE_DIR) target_include_directories(yaze_agent PUBLIC ${SDL2_INCLUDE_DIR}) endif() diff --git a/src/cli/cli.cc b/src/cli/cli.cc index b5cc9550..db9b7891 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -1,34 +1,10 @@ #include "cli/cli.h" - -#include -#include - -#include "absl/flags/flag.h" -#include "absl/flags/declare.h" -#include "absl/strings/ascii.h" -#include "absl/strings/match.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_join.h" - -#include "app/core/asar_wrapper.h" -#include "app/rom.h" +#include "cli/handlers/command_handlers.h" #include "cli/z3ed_ascii_logo.h" -#include "cli/tui/tui.h" -#include "app/snes.h" -#include "util/macro.h" -#include "cli/handlers/commands.h" -#include "cli/service/resources/command_context.h" - -// Define additional z3ed-specific flags -ABSL_DECLARE_FLAG(std::string, rom); -ABSL_DECLARE_FLAG(bool, quiet); -ABSL_DECLARE_FLAG(std::string, ai_provider); -ABSL_DECLARE_FLAG(std::string, ai_model); -ABSL_DECLARE_FLAG(std::string, gemini_api_key); -ABSL_DECLARE_FLAG(std::string, ollama_host); -ABSL_DECLARE_FLAG(std::string, prompt_version); -ABSL_DECLARE_FLAG(bool, use_function_calling); +#include "absl/strings/str_join.h" +#include "absl/strings/str_cat.h" +#include "ftxui/dom/elements.hpp" +#include "ftxui/dom/table.hpp" namespace yaze { namespace cli { @@ -37,736 +13,11 @@ ModernCLI::ModernCLI() { SetupCommands(); } - void ModernCLI::SetupCommands() { - commands_["patch apply-asar"] = { - .name = "patch apply-asar", - .description = "Apply Asar 65816 assembly patch to ROM", - .usage = "z3ed patch apply-asar [--rom=] [--output=]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleAsarPatchCommand(args); - } - }; - - commands_["patch apply-bps"] = { - .name = "patch apply-bps", - .description = "Apply BPS patch to ROM", - .usage = "z3ed patch apply-bps [--rom=] [--output=]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleBpsPatchCommand(args); - } - }; - - commands_["patch extract-symbols"] = { - .name = "patch extract-symbols", - .description = "Extract symbols from assembly file", - .usage = "z3ed patch extract-symbols ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleExtractSymbolsCommand(args); - } - }; - - commands_["rom info"] = { - .name = "rom info", - .description = "Show ROM information", - .usage = "z3ed rom info [--rom=]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleRomInfoCommand(args); - } - }; - - commands_["agent"] = { - .name = "agent", - .description = "๐Ÿค– AI-Powered Conversational Agent for ROM Inspection\n" - " Interact naturally with Zelda3 ROM data using embedded labels\n" - " โœจ Features: Natural language queries, automatic label lookup, context-aware responses", - .usage = "\n" - "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" - "โ•‘ ๐Ÿค– AI AGENT COMMANDS โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" - "\n" - "๐Ÿ’ฌ CONVERSATIONAL MODE (Recommended for testing):\n" - " z3ed agent test-conversation [--rom=] [--verbose] [--file=]\n" - " โ†’ Interactive AI testing with all embedded Zelda3 labels\n" - " โ†’ Ask about rooms, sprites, entrances, items naturally\n" - " โ†’ Example: 'What sprites are in room 5?' or 'List all dungeons'\n" - "\n" - "๐Ÿ’ก SIMPLE CHAT MODE (Multiple input methods):\n" - " # Single message\n" - " z3ed agent simple-chat \"\" --rom=\n" - " \n" - " # Interactive session\n" - " z3ed agent simple-chat --rom=\n" - " \n" - " # Piped input\n" - " echo \"What is room 5?\" | z3ed agent simple-chat --rom=\n" - " \n" - " # Batch file (one question per line)\n" - " z3ed agent simple-chat --file=questions.txt --rom=\n" - "\n" - "๐ŸŽฏ ADVANCED CHAT MODE:\n" - " z3ed agent chat \"\" [--host=] [--port=]\n" - " โ†’ Full conversational AI with Ollama/Gemini\n" - " โ†’ Supports multi-turn conversations\n" - "\n" - "๐Ÿ“Š TEST MANAGEMENT:\n" - " z3ed agent test run --prompt \"\" [--timeout=]\n" - " z3ed agent test status --test-id= [--follow]\n" - " z3ed agent test list [--category=] [--status=]\n" - " z3ed agent test results --test-id= [--format=yaml|json]\n" - " z3ed agent test suite \n" - "\n" - "๐Ÿ” OTHER COMMANDS:\n" - " z3ed agent gui discover [--window=] [--type=]\n" - " z3ed agent describe [--resource=] [--format=json|yaml]\n" - "\n" - "๐Ÿฐ EMBEDDED LABELS (Always Available):\n" - " โ€ข 296+ room names โ€ข 256 sprite names โ€ข 133 entrance names\n" - " โ€ข 100 item names โ€ข 160 overworld maps โ€ข 48 music tracks\n" - " โ€ข 60 tile types โ€ข 26 overlord names โ€ข 32 graphics sheets\n" - "\n" - "๐Ÿ“š No separate labels file needed - all names built into z3ed!\n", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleAgentCommand(args); - } - }; - - commands_["chat"] = { - .name = "chat", - .description = - "Unified chat entrypoint with text, markdown, or JSON output", - .usage = - "z3ed chat [--mode=simple|gui|test] [--format=text|markdown|json|compact]" - " [--prompt \"\"] [--file=] [--quiet]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleChatEntryCommand(args); - } - }; - - commands_["proposal"] = { - .name = "proposal", - .description = "Review and manage AI-generated change proposals", - .usage = - "z3ed proposal [options]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleProposalCommand(args); - } - }; - - commands_["collab"] = { - .name = "collab", - .description = "๐ŸŒ Collaboration Server Management\n" - " Launch and manage the WebSocket collaboration server for networked sessions", - .usage = "\n" - "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" - "โ•‘ ๐ŸŒ COLLABORATION SERVER COMMANDS โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n" - "\n" - "๐Ÿš€ SERVER MANAGEMENT:\n" - " z3ed collab start [--port=]\n" - " โ†’ Start the WebSocket collaboration server\n" - " โ†’ Default port: 8765\n" - " โ†’ Server will be accessible at ws://localhost:\n" - "\n" - "๐Ÿ“Š SERVER STATUS:\n" - " z3ed collab status\n" - " โ†’ Check if collaboration server is running\n" - " โ†’ Show active sessions and participants\n" - "\n" - "๐Ÿ’ก USAGE:\n" - " 1. Start server: z3ed collab start\n" - " 2. In YAZE GUI: Debug โ†’ Agent Chat\n" - " 3. Select 'Network' mode and connect to ws://localhost:8765\n" - " 4. Host or join a session to collaborate!\n", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleCollabCommand(args); - } - }; - - commands_["widget"] = { - .name = "widget", - .description = "Discover GUI widgets exposed through automation APIs", - .usage = "z3ed widget discover [--window=] [--type=]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleWidgetCommand(args); - } - }; - - commands_["widget discover"] = { - .name = "widget discover", - .description = "Inspect UI widgets using the automation service", - .usage = - "z3ed widget discover [--window=] [--type=]" - " [--format=text|json]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleWidgetCommand(args); - } - }; - - commands_["project build"] = { - .name = "project build", - .description = "Build the project and create a new ROM file", - .usage = "z3ed project build", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleProjectBuildCommand(args); - } - }; - - commands_["project init"] = { - .name = "project init", - .description = "Initialize a new z3ed project", - .usage = "z3ed project init ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleProjectInitCommand(args); - } - }; - - commands_["rom generate-golden"] = { - .name = "rom generate-golden", - .description = "Generate a golden file from a ROM for regression testing", - .usage = "z3ed rom generate-golden ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleRomGenerateGoldenCommand(args); - } - }; - - commands_["rom diff"] = { - .name = "rom diff", - .description = "Compare two ROM files and show the differences", - .usage = "z3ed rom diff ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleRomDiffCommand(args); - } - }; - - commands_["rom validate"] = { - .name = "rom validate", - .description = "Validate the ROM file", - .usage = "z3ed rom validate [--rom=]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleRomValidateCommand(args); - } - }; - - commands_["dungeon export"] = { - .name = "dungeon export", - .description = "Export dungeon data to a file", - .usage = "z3ed dungeon export --to ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleDungeonExportCommand(args); - } - }; - - commands_["dungeon list-objects"] = { - .name = "dungeon list-objects", - .description = "List all objects in a dungeon room", - .usage = "z3ed dungeon list-objects ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleDungeonListObjectsCommand(args); - } - }; - - commands_["gfx export-sheet"] = { - .name = "gfx export-sheet", - .description = "Export a graphics sheet to a file", - .usage = "z3ed gfx export-sheet --to ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleGfxExportCommand(args); - } - }; - - commands_["gfx import-sheet"] = { - .name = "gfx import-sheet", - .description = "Import a graphics sheet from a file", - .usage = "z3ed gfx import-sheet --from ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleGfxImportCommand(args); - } - }; - - commands_["command-palette"] = { - .name = "command-palette", - .description = "Show the command palette", - .usage = "z3ed command-palette", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleCommandPaletteCommand(args); - } - }; - - commands_["palette"] = { - .name = "palette", - .description = "Manage palette data (export/import)", - .usage = "z3ed palette [options]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandlePaletteCommand(args); - } - }; - - commands_["palette export"] = { - .name = "palette export", - .description = "Export a palette to a file", - .usage = "z3ed palette export --group --id --to ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandlePaletteExportCommand(args); - } - }; - - commands_["palette import"] = { - .name = "palette import", - .description = "Import a palette from a file", - .usage = "z3ed palette import --group --id --from ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandlePaletteImportCommand(args); - } - }; - - commands_["overworld get-tile"] = { - .name = "overworld get-tile", - .description = "Get a tile from the overworld", - .usage = "z3ed overworld get-tile --map --x --y ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldGetTileCommand(args); - } - }; - - commands_["overworld find-tile"] = { - .name = "overworld find-tile", - .description = "Search overworld maps for a tile ID", - .usage = "z3ed overworld find-tile --tile [--map ] [--world light|dark|special] [--format json|text]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldFindTileCommand(args); + auto handlers = handlers::CreateAllCommandHandlers(); + for (auto& handler : handlers) { + commands_[handler->GetName()] = std::move(handler); } - }; - - commands_["overworld describe-map"] = { - .name = "overworld describe-map", - .description = "Summarize metadata for an overworld map", - .usage = "z3ed overworld describe-map --map [--format json|text]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldDescribeMapCommand(args); - } - }; - - commands_["overworld list-warps"] = { - .name = "overworld list-warps", - .description = "List overworld entrances and holes with coordinates", - .usage = "z3ed overworld list-warps [--map ] [--world light|dark|special] [--type entrance|hole|exit|all] [--format json|text]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldListWarpsCommand(args); - } - }; - - commands_["overworld set-tile"] = { - .name = "overworld set-tile", - .description = "Set a tile in the overworld", - .usage = "z3ed overworld set-tile --map --x --y --tile ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldSetTileCommand(args); - } - }; - - commands_["overworld select-rect"] = { - .name = "overworld select-rect", - .description = "Select a rectangular region of tiles", - .usage = "z3ed overworld select-rect --map --x1 --y1 --x2 --y2 ", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldSelectRectCommand(args); - } - }; - - commands_["overworld scroll-to"] = { - .name = "overworld scroll-to", - .description = "Scroll canvas to show specific tile", - .usage = "z3ed overworld scroll-to --map --x --y [--center]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldScrollToCommand(args); - } - }; - - commands_["overworld set-zoom"] = { - .name = "overworld set-zoom", - .description = "Set canvas zoom level", - .usage = "z3ed overworld set-zoom --zoom (0.25-4.0)", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldSetZoomCommand(args); - } - }; - - commands_["overworld get-visible-region"] = { - .name = "overworld get-visible-region", - .description = "Get currently visible tile region", - .usage = "z3ed overworld get-visible-region --map [--format json|text]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleOverworldGetVisibleRegionCommand(args); - } - }; - - commands_["sprite create"] = { - .name = "sprite create", - .description = "Create a new sprite", - .usage = "z3ed sprite create --name [options]", - .handler = [this](const std::vector& args) -> absl::Status { - return HandleSpriteCreateCommand(args); - } - }; -} - -void ModernCLI::ShowHelp() { - std::cout << GetColoredLogo() << std::endl; - std::cout << std::endl; - std::cout << "\033[1m\033[36mUSAGE:\033[0m" << std::endl; - std::cout << " z3ed [options] [arguments]" << std::endl; - std::cout << std::endl; - - std::cout << "\033[1m\033[36mGLOBAL OPTIONS:\033[0m" << std::endl; - std::cout << " --tui Launch interactive Text User Interface" << std::endl; - std::cout << " --rom= Specify ROM file path" << std::endl; - std::cout << " --verbose, -v Show detailed debug output" << std::endl; - std::cout << " --version Show version information" << std::endl; - std::cout << " --help, -h Show this help message" << std::endl; - std::cout << std::endl; - - // Categorize commands - std::cout << "\033[1m\033[36mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐Ÿค– AI Agent\033[0m" << std::endl; - std::cout << " chat Unified chat entrypoint (text/json/markdown)" << std::endl; - std::cout << " agent simple-chat Natural language ROM queries" << std::endl; - std::cout << " agent test-conversation Interactive testing mode" << std::endl; - std::cout << " \033[90mโ†’ z3ed help chat, z3ed help agent\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐Ÿง  Proposals\033[0m" << std::endl; - std::cout << " proposal run Execute AI-driven sandbox plan" << std::endl; - std::cout << " proposal list Show pending proposals" << std::endl; - std::cout << " proposal diff Review latest sandbox diff" << std::endl; - std::cout << " proposal accept Apply sandbox changes" << std::endl; - std::cout << " \033[90mโ†’ z3ed help proposal\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐ŸชŸ GUI Automation\033[0m" << std::endl; - std::cout << " widget discover Inspect GUI widgets via automation" << std::endl; - std::cout << " \033[90mโ†’ z3ed help widget\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐Ÿ”ง ROM Patching\033[0m" << std::endl; - std::cout << " patch apply-asar Apply Asar 65816 assembly patch" << std::endl; - std::cout << " patch apply-bps Apply BPS binary patch" << std::endl; - std::cout << " patch extract-symbols Extract symbols from assembly" << std::endl; - std::cout << " \033[90mโ†’ z3ed help patch\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐Ÿ“ฆ ROM Operations\033[0m" << std::endl; - std::cout << " rom info Display ROM information" << std::endl; - std::cout << " rom diff Compare two ROM files" << std::endl; - std::cout << " rom generate-golden Create golden test file" << std::endl; - std::cout << " \033[90mโ†’ z3ed help rom\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐Ÿ—บ๏ธ Overworld\033[0m" << std::endl; - std::cout << " overworld get-tile Get tile at coordinates" << std::endl; - std::cout << " overworld set-tile Place tile at coordinates" << std::endl; - std::cout << " overworld find-tile Search for tile occurrences" << std::endl; - std::cout << " overworld describe-map Show map metadata" << std::endl; - std::cout << " overworld list-warps List entrances and exits" << std::endl; - std::cout << " \033[90mโ†’ z3ed help overworld\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐Ÿฐ Dungeon\033[0m" << std::endl; - std::cout << " dungeon export Export dungeon data" << std::endl; - std::cout << " dungeon import Import dungeon data" << std::endl; - std::cout << " \033[90mโ†’ z3ed help dungeon\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << " \033[1m๐ŸŽจ Graphics\033[0m" << std::endl; - std::cout << " gfx export-sheet Export graphics sheet" << std::endl; - std::cout << " gfx import-sheet Import graphics sheet" << std::endl; - std::cout << " palette export Export palette data" << std::endl; - std::cout << " palette import Import palette data" << std::endl; - std::cout << " \033[90mโ†’ z3ed help gfx, z3ed help palette\033[0m" << std::endl; - std::cout << std::endl; - - std::cout << "\033[1m\033[36mQUICK START:\033[0m" << std::endl; - std::cout << " z3ed --tui" << std::endl; - std::cout << " z3ed chat \"What is room 5?\" --rom=zelda3.sfc --format=markdown" << std::endl; - std::cout << " z3ed patch apply-asar patch.asm --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - - std::cout << "\033[90mFor detailed help: z3ed help \033[0m" << std::endl; -} - -void ModernCLI::PrintTopLevelHelp() const { - const_cast(this)->ShowHelp(); -} - -void ModernCLI::PrintCategoryHelp(const std::string& category) const { - const_cast(this)->ShowCategoryHelp(category); -} - -void ModernCLI::PrintCommandSummary() const { - const_cast(this)->ShowCommandSummary(); -} - -void ModernCLI::ShowCategoryHelp(const std::string& category) { - std::cout << GetColoredLogo() << std::endl; - std::cout << std::endl; - - if (category == "agent") { - std::cout << "\033[1m\033[36m๐Ÿค– AI AGENT COMMANDS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl; - std::cout << " Natural language interface for ROM inspection using embedded labels." << std::endl; - std::cout << " Query rooms, sprites, entrances, and game data conversationally." << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - std::cout << " \033[1magent simple-chat\033[0m [\"\"]" << std::endl; - std::cout << " Single-shot or interactive chat mode" << std::endl; - std::cout << " Options: --rom=, --verbose" << std::endl; - std::cout << " Examples:" << std::endl; - std::cout << " z3ed agent simple-chat \"What sprites are in room 5?\" --rom=zelda3.sfc" << std::endl; - std::cout << " echo \"List all dungeons\" | z3ed agent simple-chat --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1magent test-conversation\033[0m" << std::endl; - std::cout << " Interactive testing mode with full context" << std::endl; - std::cout << " Options: --rom=, --verbose, --file=" << std::endl; - std::cout << std::endl; - std::cout << " \033[1magent chat\033[0m \"\"" << std::endl; - std::cout << " Advanced multi-turn conversation mode" << std::endl; - std::cout << " Options: --host=, --port=" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mTIPS:\033[0m" << std::endl; - std::cout << " โ€ข Use --verbose to see detailed API calls and responses" << std::endl; - std::cout << " โ€ข Set GEMINI_API_KEY environment variable for Gemini" << std::endl; - std::cout << " โ€ข Use --ai_provider=gemini or --ai_provider=ollama" << std::endl; - std::cout << std::endl; - - } else if (category == "chat") { - std::cout << "\033[1m\033[36m๐Ÿ’ฌ CHAT ENTRYPOINT\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl; - std::cout << " Launch the embedded agent in text, markdown, or JSON-friendly modes." << std::endl; - std::cout << std::endl; - std::cout << "\033[1mMODES:\033[0m" << std::endl; - std::cout << " chat --mode=simple Quick REPL with --format=text|markdown|json|compact" << std::endl; - std::cout << " chat --mode=batch --file=F Run prompts from file (one per line)" << std::endl; - std::cout << " chat --mode=gui Launch the full FTXUI conversation experience" << std::endl; - std::cout << " chat --mode=test Execute scripted agent conversation for QA" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mOPTIONS:\033[0m" << std::endl; - std::cout << " --format=text|markdown|json|compact Control response formatting" << std::endl; - std::cout << " --prompt \"\" Send a single message and exit" << std::endl; - std::cout << " --file questions.txt Batch mode input" << std::endl; - std::cout << " --quiet Suppress extra banners" << std::endl; - std::cout << std::endl; - - } else if (category == "proposal") { - std::cout << "\033[1m\033[36m๐Ÿง  PROPOSAL WORKFLOWS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << " proposal run --prompt \"\" Plan and execute changes in sandbox" << std::endl; - std::cout << " proposal list Show pending proposals" << std::endl; - std::cout << " proposal diff [--proposal-id=X] Inspect latest diff/log" << std::endl; - std::cout << " proposal accept --proposal-id=X Apply sandbox changes to main ROM" << std::endl; - std::cout << " proposal commit | proposal revert Persist or undo sandbox changes" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mTIPS:\033[0m" << std::endl; - std::cout << " โ€ข Run `z3ed proposal list` frequently to monitor progress" << std::endl; - std::cout << " โ€ข Use `--prompt` to describe tasks in natural language" << std::endl; - std::cout << " โ€ข Sandbox artifacts live alongside proposal logs" << std::endl; - std::cout << std::endl; - - } else if (category == "widget") { - std::cout << "\033[1m\033[36m๐ŸชŸ GUI WIDGET DISCOVERY\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << " widget discover [--window=] [--type=]" << std::endl; - std::cout << " Enumerate UI widgets available through automation hooks" << std::endl; - std::cout << " Options: --format=table|json, --limit , --include-invisible, --include-disabled" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mTIPS:\033[0m" << std::endl; - std::cout << " โ€ข Requires the YAZE GUI to be running locally" << std::endl; - std::cout << " โ€ข Combine with `z3ed proposal run` for automated UI tests" << std::endl; - std::cout << std::endl; - - } else if (category == "patch") { - std::cout << "\033[1m\033[36m๐Ÿ”ง ROM PATCHING COMMANDS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl; - std::cout << " Apply patches and extract symbols from assembly files." << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mpatch apply-asar\033[0m " << std::endl; - std::cout << " Apply Asar 65816 assembly patch to ROM" << std::endl; - std::cout << " Options: --rom=, --output=" << std::endl; - std::cout << " Example: z3ed patch apply-asar custom.asm --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mpatch apply-bps\033[0m " << std::endl; - std::cout << " Apply BPS binary patch to ROM" << std::endl; - std::cout << " Options: --rom=, --output=" << std::endl; - std::cout << " Example: z3ed patch apply-bps hack.bps --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mpatch extract-symbols\033[0m " << std::endl; - std::cout << " Extract symbol table from assembly file" << std::endl; - std::cout << " Example: z3ed patch extract-symbols code.asm" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mRELATED:\033[0m" << std::endl; - std::cout << " z3ed help rom ROM operations and validation" << std::endl; - std::cout << std::endl; - - } else if (category == "rom") { - std::cout << "\033[1m\033[36m๐Ÿ“ฆ ROM OPERATIONS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mrom info\033[0m" << std::endl; - std::cout << " Display ROM header and metadata" << std::endl; - std::cout << " Example: z3ed rom info --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mrom diff\033[0m" << std::endl; - std::cout << " Compare two ROM files byte-by-byte" << std::endl; - std::cout << " Example: z3ed rom diff --src=original.sfc --modified=hacked.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mrom generate-golden\033[0m" << std::endl; - std::cout << " Create golden test reference file" << std::endl; - std::cout << " Example: z3ed rom generate-golden --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mrom validate\033[0m" << std::endl; - std::cout << " Validate ROM checksum and structure" << std::endl; - std::cout << " Example: z3ed rom validate --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - - } else if (category == "overworld") { - std::cout << "\033[1m\033[36m๐Ÿ—บ๏ธ OVERWORLD COMMANDS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mDESCRIPTION:\033[0m" << std::endl; - std::cout << " Inspect and modify overworld map data, tiles, and warps." << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld get-tile\033[0m" << std::endl; - std::cout << " Get tile ID at specific coordinates" << std::endl; - std::cout << " Example: z3ed overworld get-tile --x=10 --y=20 --map=0 --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld set-tile\033[0m" << std::endl; - std::cout << " Place tile at coordinates" << std::endl; - std::cout << " Example: z3ed overworld set-tile --x=10 --y=20 --tile=0x42 --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld find-tile\033[0m" << std::endl; - std::cout << " Search for all occurrences of a tile" << std::endl; - std::cout << " Example: z3ed overworld find-tile --tile=0x42 --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld describe-map\033[0m" << std::endl; - std::cout << " Show map metadata and properties" << std::endl; - std::cout << " Example: z3ed overworld describe-map --map=0 --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld list-warps\033[0m" << std::endl; - std::cout << " List all entrances and exits" << std::endl; - std::cout << " Example: z3ed overworld list-warps --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld select-rect\033[0m" << std::endl; - std::cout << " Select a rectangular region of tiles" << std::endl; - std::cout << " Example: z3ed overworld select-rect --map=0 --x1=5 --y1=5 --x2=10 --y2=10" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld scroll-to\033[0m" << std::endl; - std::cout << " Scroll canvas to show specific tile" << std::endl; - std::cout << " Example: z3ed overworld scroll-to --map=0 --x=10 --y=10 --center" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld set-zoom\033[0m" << std::endl; - std::cout << " Set canvas zoom level (0.25-4.0)" << std::endl; - std::cout << " Example: z3ed overworld set-zoom --zoom=1.5" << std::endl; - std::cout << std::endl; - std::cout << " \033[1moverworld get-visible-region\033[0m" << std::endl; - std::cout << " Get currently visible tile region" << std::endl; - std::cout << " Example: z3ed overworld get-visible-region --map=0 --format=json" << std::endl; - std::cout << std::endl; - - } else if (category == "dungeon") { - std::cout << "\033[1m\033[36m๐Ÿฐ DUNGEON COMMANDS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mdungeon export\033[0m" << std::endl; - std::cout << " Export dungeon room data to JSON" << std::endl; - std::cout << " Example: z3ed dungeon export --room=5 --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mdungeon import\033[0m" << std::endl; - std::cout << " Import dungeon data from JSON" << std::endl; - std::cout << " Example: z3ed dungeon import --file=room5.json --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - - } else if (category == "gfx" || category == "graphics") { - std::cout << "\033[1m\033[36m๐ŸŽจ GRAPHICS COMMANDS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mgfx export-sheet\033[0m" << std::endl; - std::cout << " Export graphics sheet to PNG" << std::endl; - std::cout << " Example: z3ed gfx export-sheet --sheet=0 --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mgfx import-sheet\033[0m" << std::endl; - std::cout << " Import graphics from PNG" << std::endl; - std::cout << " Example: z3ed gfx import-sheet --file=custom.png --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mRELATED:\033[0m" << std::endl; - std::cout << " z3ed help palette Palette manipulation commands" << std::endl; - std::cout << std::endl; - - } else if (category == "palette") { - std::cout << "\033[1m\033[36m๐ŸŽจ PALETTE COMMANDS\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "\033[1mCOMMANDS:\033[0m" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mpalette export\033[0m" << std::endl; - std::cout << " Export palette data" << std::endl; - std::cout << " Example: z3ed palette export --palette=0 --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - std::cout << " \033[1mpalette import\033[0m" << std::endl; - std::cout << " Import palette data" << std::endl; - std::cout << " Example: z3ed palette import --file=colors.pal --rom=zelda3.sfc" << std::endl; - std::cout << std::endl; - - } else { - std::cout << "\033[1m\033[31mUnknown category: " << category << "\033[0m" << std::endl; - std::cout << std::endl; - std::cout << "Available categories: agent, chat, proposal, widget, patch, rom, overworld, dungeon, gfx, palette" << std::endl; - std::cout << std::endl; - std::cout << "Use 'z3ed --help' to see all commands." << std::endl; - } -} - -void ModernCLI::ShowCommandSummary() const { - std::cout << GetColoredLogo() << std::endl; - std::cout << std::endl; - std::cout << "\033[1m\033[36mCOMMANDS OVERVIEW\033[0m" << std::endl; - std::cout << std::endl; - - for (const auto& [key, info] : commands_) { - std::string headline = info.description; - const size_t newline_pos = headline.find('\n'); - if (newline_pos != std::string::npos) { - headline = headline.substr(0, newline_pos); - } - if (headline.empty()) { - headline = info.usage; - } - if (headline.size() > 80) { - headline = absl::StrCat(headline.substr(0, 77), "โ€ฆ"); - } - - std::string label = info.name; - if (label.size() > 24) { - label = absl::StrCat(label.substr(0, 23), "โ€ฆ"); - } - - std::cout << " " - << absl::StrFormat("%-24s%s", label, headline) << std::endl; - } - - std::cout << std::endl; - std::cout << "Use \033[90mz3ed help \033[0m for detailed information." << std::endl; } absl::Status ModernCLI::Run(int argc, char* argv[]) { @@ -776,538 +27,131 @@ absl::Status ModernCLI::Run(int argc, char* argv[]) { } std::vector args; - args.reserve(argc - 1); for (int i = 1; i < argc; ++i) { - args.emplace_back(argv[i]); + args.push_back(argv[i]); } - // Handle "help " command - if (args.size() >= 1 && args[0] == "help") { - if (args.size() == 1) { + if (args[0] == "help") { + if (args.size() > 1) { + ShowCategoryHelp(args[1]); + } else { ShowHelp(); - return absl::OkStatus(); } - ShowCategoryHelp(args[1]); return absl::OkStatus(); } - const CommandInfo* command_info = nullptr; - size_t consumed_tokens = 0; + auto it = commands_.find(args[0]); + if (it != commands_.end()) { + std::vector command_args(args.begin() + 1, args.end()); + return it->second->Run(command_args, nullptr); + } - if (args.size() >= 2) { - std::string candidate = absl::StrCat(args[0], " ", args[1]); - auto it = commands_.find(candidate); + return absl::NotFoundError(absl::StrCat("Unknown command: ", args[0])); +} + +void ModernCLI::ShowHelp() { + using namespace ftxui; + auto banner = text("๐ŸŽฎ Z3ED - CLI for Zelda 3") | bold | center; + auto summary = Table({ + {"Command", "Description", "TODO/Reference"}, + {"agent", "AI conversational agent", "ref: agent::chat"}, + {"rom", "ROM info, diff, validate", "todo#101"}, + {"dungeon", "Dungeon tooling", "todo#202"}, + {"gfx", "Graphics export/import", "ref: gfx::export"}, + {"palette", "Palette operations", "todo#305"}, + {"project", "Project workflows", "ref: project::build"} + }); + summary.SelectAll().Border(LIGHT); + summary.SelectRow(0).Decorate(bold); + + auto layout = vbox({ + text(yaze::cli::GetColoredLogo()), + banner, + separator(), + summary.Render(), + separator(), + text("Try `z3ed --tui` for the animated FTXUI interface") | center, + text("Use `--list-commands` for complete breakdown") | dim | center + }); + + auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); + Render(screen, layout); + screen.Print(); +} + +void ModernCLI::ShowCategoryHelp(const std::string& category) const { + using namespace ftxui; + std::vector> rows; + rows.push_back({"Subcommand", "Summary", "TODO/Reference"}); + + auto it = commands_.find(category); if (it != commands_.end()) { - command_info = &it->second; - consumed_tokens = 2; - } - } - - if (command_info == nullptr && !args.empty()) { - auto it = commands_.find(args[0]); - if (it != commands_.end()) { - command_info = &it->second; - consumed_tokens = 1; - } - } - - if (command_info == nullptr) { - ShowHelp(); - std::string joined = args.empty() ? std::string() : absl::StrJoin(args, " "); - return absl::NotFoundError( - absl::StrCat("Unknown command: ", joined.empty() ? "" : joined)); - } - - std::vector command_args(args.begin() + consumed_tokens, args.end()); - return command_info->handler(command_args); -} - -CommandHandler* ModernCLI::GetCommandHandler(const std::string& name) { - // TODO: Implement using new CommandHandler system - // This method should return CommandHandler instances from the new system - // Reference: cli/handlers/command_handlers.h (factory functions) - return nullptr; -} - -absl::Status ModernCLI::HandleAsarPatchCommand(const std::vector& args) { - // TODO: Implement using AsarPatchCommandHandler - // Reference: src/app/core/asar_wrapper.cc (AsarWrapper class) - return absl::UnimplementedError("AsarPatchCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleBpsPatchCommand(const std::vector& args) { - // TODO: Implement using BpsPatchCommandHandler - // Reference: src/app/core/asar_wrapper.cc (for patch application logic) - return absl::UnimplementedError("BpsPatchCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleRomInfoCommand(const std::vector& args) { - // TODO: Implement using RomInfoCommandHandler - // Reference: src/app/rom.cc (Rom::GetInfo methods) - return absl::UnimplementedError("RomInfoCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleExtractSymbolsCommand(const std::vector& args) { - // Use the AsarWrapper to extract symbols - yaze::core::AsarWrapper wrapper; - RETURN_IF_ERROR(wrapper.Initialize()); - - auto symbols_result = wrapper.ExtractSymbols(args[0]); - if (!symbols_result.ok()) { - return symbols_result.status(); + const auto& metadata = it->second->Describe(); + for (const auto& entry : metadata.entries) { + rows.push_back({entry.name, entry.description, entry.todo_reference}); + } } - const auto& symbols = symbols_result.value(); - std::cout << "๐Ÿท๏ธ Extracted " << symbols.size() << " symbols from " << args[0] << ":" << std::endl; - std::cout << std::endl; - - for (const auto& symbol : symbols) { - std::cout << absl::StrFormat(" %-20s @ $%06X", symbol.name, symbol.address) << std::endl; - } - - return absl::OkStatus(); -} - -absl::Status ModernCLI::HandleAgentCommand(const std::vector& args) { - // Use new CommandHandler system - return yaze::cli::handlers::HandleAgentCommand(args); -} - -absl::Status ModernCLI::HandleCollabCommand(const std::vector& args) { - if (args.empty()) { - return absl::InvalidArgumentError( - "Usage: z3ed collab [options]\n" - " start - Start the collaboration server\n" - " status - Check server status"); - } - - const std::string& subcommand = args[0]; - - if (subcommand == "start") { - std::string port = "8765"; - - // Parse port argument - for (size_t i = 1; i < args.size(); ++i) { - if (absl::StartsWith(args[i], "--port=")) { - port = args[i].substr(7); - } else if (args[i] == "--port" && i + 1 < args.size()) { - port = args[++i]; - } + if (rows.size() == 1) { + rows.push_back({"โ€”", "No metadata registered", "โ€”"}); } - // Determine server directory - std::string server_dir; - if (const char* yaze_root = std::getenv("YAZE_ROOT")) { - server_dir = std::string(yaze_root); - } else { - // Assume we're in build directory, server is ../yaze-server - server_dir = ".."; - } - - std::cout << "๐Ÿš€ Starting collaboration server on port " << port << "...\n"; - std::cout << " Server will be accessible at ws://localhost:" << port << "\n\n"; - - // Build platform-specific command - std::string command; -#ifdef _WIN32 - // Windows: Use cmd.exe to run npm start - command = "cd /D \"" + server_dir + "\\..\\yaze-server\" && set PORT=" + - port + " && npm start"; -#else - // Unix: Use bash script - command = "cd \"" + server_dir + "/../yaze-server\" && PORT=" + - port + " node server.js &"; -#endif - - int result = std::system(command.c_str()); - - if (result != 0) { - std::cout << "โš ๏ธ Note: Server may not be installed. To install:\n"; - std::cout << " cd yaze-server && npm install\n"; - return absl::InternalError("Failed to start collaboration server"); - } - - std::cout << "โœ“ Server started (may take a few seconds to initialize)\n"; - return absl::OkStatus(); - } - - if (subcommand == "status") { - // Check if Node.js process is running (platform-specific) - int result; -#ifdef _WIN32 - // Windows: Use tasklist to find node.exe - result = std::system("tasklist /FI \"IMAGENAME eq node.exe\" 2>NUL | find /I \"node.exe\" >NUL"); -#else - // Unix: Use pgrep - result = std::system("pgrep -f 'node.*server.js' > /dev/null 2>&1"); -#endif - - if (result == 0) { - std::cout << "โœ“ Collaboration server appears to be running\n"; - std::cout << " Connect from YAZE: Debug โ†’ Agent Chat โ†’ Network mode\n"; - } else { - std::cout << "โ—‹ Collaboration server is not running\n"; - std::cout << " Start with: z3ed collab start\n"; - } - - return absl::OkStatus(); - } + Table detail(rows); + detail.SelectAll().Border(LIGHT); + detail.SelectRow(0).Decorate(bold); - return absl::InvalidArgumentError(absl::StrFormat("Unknown subcommand: %s", subcommand)); + auto layout = vbox({ + text(absl::StrCat("Category: ", category)) | bold | center, + separator(), + detail.Render(), + separator(), + text("Command handlers can expose TODO references via Describe().") | dim | center + }); + + auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); + Render(screen, layout); + screen.Print(); } -absl::Status ModernCLI::HandleProjectBuildCommand(const std::vector& args) { - // TODO: Implement using ProjectBuildCommandHandler - // Reference: src/app/core/project.cc (Project::Build method) - return absl::UnimplementedError("ProjectBuildCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleProjectInitCommand(const std::vector& args) { - // TODO: Implement using ProjectInitCommandHandler - // Reference: src/app/core/project.cc (Project class) - return absl::UnimplementedError("ProjectInitCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleRomGenerateGoldenCommand(const std::vector& args) { - std::string rom_file = absl::GetFlag(FLAGS_rom); - if (!args.empty()) { - rom_file = args[0]; - } - - if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file required (use --rom= or provide as argument)"); +void ModernCLI::ShowCommandSummary() const { + using namespace ftxui; + std::vector tiles; + for (const auto& [name, handler] : commands_) { + const auto summary = handler->Describe(); + tiles.push_back( + vbox({ + text(summary.display_name.empty() ? name : summary.display_name) | bold, + separator(), + text(summary.summary), + text(absl::StrCat("TODO: ", summary.todo_reference)) | dim + }) | borderRounded); } - Open handler; - return handler.Run({rom_file}); + auto layout = vbox({ + text("Z3ED Command Summary") | bold | center, + separator(), + tiles.empty() ? text("No commands registered.") | dim | center + : vbox(tiles), + separator(), + text("Use `z3ed --tui` for interactive command palette.") | center | dim + }); + + auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); + Render(screen, layout); + screen.Print(); } -absl::Status ModernCLI::HandleDungeonExportCommand(const std::vector& args) { - // Use new CommandHandler system - return yaze::cli::handlers::HandleDungeonExportRoomCommand(args, nullptr); +void ModernCLI::PrintTopLevelHelp() const { + const_cast(this)->ShowHelp(); } -absl::Status ModernCLI::HandleDungeonListObjectsCommand(const std::vector& args) { - // Use new CommandHandler system - return yaze::cli::handlers::HandleDungeonListObjectsCommand(args, nullptr); +void ModernCLI::PrintCategoryHelp(const std::string& category) const { + const_cast(this)->ShowCategoryHelp(category); } -absl::Status ModernCLI::HandleGfxExportCommand(const std::vector& args) { - // TODO: Implement using GfxExportCommandHandler - // Reference: src/app/editor/graphics/ (graphics editor classes) - return absl::UnimplementedError("GfxExportCommandHandler not yet implemented"); +void ModernCLI::PrintCommandSummary() const { + const_cast(this)->ShowCommandSummary(); } -absl::Status ModernCLI::HandleGfxImportCommand(const std::vector& args) { - // TODO: Implement using GfxImportCommandHandler - // Reference: src/app/editor/graphics/ (graphics editor classes) - return absl::UnimplementedError("GfxImportCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleCommandPaletteCommand(const std::vector& args) { - // TODO: Implement using CommandPaletteCommandHandler - // Reference: src/app/editor/system/command_palette.cc - return absl::UnimplementedError("CommandPaletteCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandlePaletteExportCommand(const std::vector& args) { - // TODO: Implement using PaletteExportCommandHandler - // Reference: src/app/editor/graphics/palette_editor.cc - return absl::UnimplementedError("PaletteExportCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandlePaletteCommand(const std::vector& args) { - // Use new CommandHandler system for palette operations - if (args.empty()) { - return yaze::cli::handlers::HandlePaletteGetColors(args, nullptr); - } - - // Parse subcommand - std::string subcommand = absl::AsciiStrToLower(args[0]); - if (subcommand == "get" || subcommand == "list") { - return yaze::cli::handlers::HandlePaletteGetColors(args, nullptr); - } else if (subcommand == "set") { - return yaze::cli::handlers::HandlePaletteSetColor(args, nullptr); - } else if (subcommand == "analyze") { - return yaze::cli::handlers::HandlePaletteAnalyze(args, nullptr); - } - - return absl::InvalidArgumentError("Unknown palette subcommand. Use: get, set, analyze"); -} - -absl::Status ModernCLI::HandlePaletteImportCommand(const std::vector& args) { - // TODO: Implement using PaletteImportCommandHandler - // Reference: src/app/editor/graphics/palette_editor.cc - return absl::UnimplementedError("PaletteImportCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleRomDiffCommand(const std::vector& args) { - // TODO: Implement using RomDiffCommandHandler - // Reference: src/app/rom.cc (Rom comparison methods) - return absl::UnimplementedError("RomDiffCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleRomValidateCommand(const std::vector& args) { - // TODO: Implement using RomValidateCommandHandler - // Reference: src/app/rom.cc (Rom class validation methods) - return absl::UnimplementedError("RomValidateCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleOverworldGetTileCommand(const std::vector& args) { - // TODO: Implement using OverworldGetTileCommandHandler - // Reference: src/app/editor/overworld/ (overworld editor classes) - return absl::UnimplementedError("OverworldGetTileCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleOverworldFindTileCommand(const std::vector& args) { - // Use new CommandHandler system - return yaze::cli::handlers::HandleOverworldFindTileCommand(args, nullptr); -} - -absl::Status ModernCLI::HandleOverworldDescribeMapCommand(const std::vector& args) { - // Use new CommandHandler system - return yaze::cli::handlers::HandleOverworldDescribeMapCommand(args, nullptr); -} - -absl::Status ModernCLI::HandleOverworldListWarpsCommand(const std::vector& args) { - // Use new CommandHandler system - return yaze::cli::handlers::HandleOverworldListWarpsCommand(args, nullptr); -} - -absl::Status ModernCLI::HandleOverworldSetTileCommand(const std::vector& args) { - // TODO: Implement using OverworldSetTileCommandHandler - // Reference: src/app/editor/overworld/ (overworld editor classes) - return absl::UnimplementedError("OverworldSetTileCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleOverworldSelectRectCommand(const std::vector& args) { - // TODO: Implement using OverworldSelectRectCommandHandler - // Reference: src/app/editor/overworld/ (overworld editor classes) - return absl::UnimplementedError("OverworldSelectRectCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleOverworldScrollToCommand(const std::vector& args) { - // TODO: Implement using OverworldScrollToCommandHandler - // Reference: src/app/editor/overworld/ (overworld editor classes) - return absl::UnimplementedError("OverworldScrollToCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleOverworldSetZoomCommand(const std::vector& args) { - // TODO: Implement using OverworldSetZoomCommandHandler - // Reference: src/app/editor/overworld/ (overworld editor classes) - return absl::UnimplementedError("OverworldSetZoomCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleOverworldGetVisibleRegionCommand(const std::vector& args) { - // TODO: Implement using OverworldGetVisibleRegionCommandHandler - // Reference: src/app/editor/overworld/ (overworld editor classes) - return absl::UnimplementedError("OverworldGetVisibleRegionCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleSpriteCreateCommand(const std::vector& args) { - // TODO: Implement using SpriteCreateCommandHandler - // Reference: src/app/zelda3/sprite/ (sprite management classes) - return absl::UnimplementedError("SpriteCreateCommandHandler not yet implemented"); -} - -absl::Status ModernCLI::HandleChatEntryCommand( - const std::vector& args) { - std::string mode = "simple"; - std::optional prompt; - std::vector forwarded; - - for (size_t i = 0; i < args.size(); ++i) { - const std::string& token = args[i]; - if (absl::StartsWith(token, "--mode=")) { - mode = absl::AsciiStrToLower(token.substr(7)); - continue; - } - if (token == "--mode" && i + 1 < args.size()) { - mode = absl::AsciiStrToLower(args[i + 1]); - ++i; - continue; - } - if (absl::StartsWith(token, "--prompt=")) { - prompt = token.substr(9); - continue; - } - if (token == "--prompt" && i + 1 < args.size()) { - prompt = args[i + 1]; - ++i; - continue; - } - if (token == "--quiet" || token == "-q") { - absl::SetFlag(&FLAGS_quiet, true); - continue; - } - if (!absl::StartsWith(token, "--") && !prompt.has_value()) { - prompt = token; - continue; - } - forwarded.push_back(token); - } - - const std::string normalized_mode = absl::AsciiStrToLower(mode); - - auto has_batch_file = [&forwarded]() { - for (const auto& token : forwarded) { - if (absl::StartsWith(token, "--file") || token == "--file") { - return true; - } - } - return false; - }; - - std::vector agent_args; - if (normalized_mode == "gui" || normalized_mode == "visual" || - normalized_mode == "tui") { - if (prompt.has_value()) { - return absl::InvalidArgumentError( - "GUI chat mode launches the interactive TUI and does not accept a --prompt value."); - } - agent_args.push_back("chat"); - } else if (normalized_mode == "test" || normalized_mode == "qa") { - if (prompt.has_value()) { - return absl::InvalidArgumentError( - "Test conversation mode does not accept an inline prompt."); - } - agent_args.push_back("test-conversation"); - } else { - if (normalized_mode == "batch" && !has_batch_file()) { - return absl::InvalidArgumentError( - "Batch chat mode requires a --file= option."); - } - agent_args.push_back("simple-chat"); - if (prompt.has_value()) { - agent_args.push_back(*prompt); - } - } - - agent_args.insert(agent_args.end(), forwarded.begin(), forwarded.end()); - return HandleAgentCommand(agent_args); -} - -absl::Status ModernCLI::HandleProposalCommand( - const std::vector& args) { - if (args.empty()) { - ShowCategoryHelp("proposal"); - return absl::OkStatus(); - } - - std::string subcommand = absl::AsciiStrToLower(args[0]); - std::vector forwarded(args.begin() + 1, args.end()); - std::vector agent_args; - - if (subcommand == "run" || subcommand == "plan") { - agent_args.push_back(subcommand); - } else if (subcommand == "list") { - agent_args.push_back("list"); - } else if (subcommand == "diff" || subcommand == "show") { - agent_args.push_back("diff"); - } else if (subcommand == "accept") { - agent_args.push_back("accept"); - } else if (subcommand == "commit") { - agent_args.push_back("commit"); - } else if (subcommand == "revert" || subcommand == "reject") { - agent_args.push_back("revert"); - } else if (subcommand == "test") { - agent_args.push_back("test"); - } else { - return absl::InvalidArgumentError( - absl::StrCat("Unknown proposal command: ", subcommand, - ". Valid actions: run, plan, list, diff, show, accept, commit, revert.")); - } - - agent_args.insert(agent_args.end(), forwarded.begin(), forwarded.end()); - return HandleAgentCommand(agent_args); -} - -absl::Status ModernCLI::HandleWidgetCommand( - const std::vector& args) { - if (args.empty()) { - ShowCategoryHelp("widget"); - return absl::OkStatus(); - } - - std::vector forwarded(args.begin(), args.end()); - std::string subcommand = absl::AsciiStrToLower(forwarded[0]); - std::vector agent_args; - - if (subcommand == "discover") { - agent_args.push_back("gui"); - agent_args.insert(agent_args.end(), forwarded.begin(), forwarded.end()); - } else { - return absl::InvalidArgumentError( - absl::StrCat("Unknown widget command: ", forwarded[0], - ". Try 'z3ed widget discover'.")); - } - - return HandleAgentCommand(agent_args); -} - -// TODO: Implement remaining legacy commands using new CommandHandler system -// -// PENDING IMPLEMENTATIONS: -// -// 1. ApplyPatch - Apply BPS patches to ROM -// Reference: src/app/core/asar_wrapper.cc (for patch application logic) -// TODO: Create BpsPatchCommandHandler in cli/handlers/rom/ -// -// 2. AsarPatch - Apply ASAR assembly patches to ROM -// Reference: src/app/core/asar_wrapper.cc (AsarWrapper class) -// TODO: Create AsarPatchCommandHandler in cli/handlers/rom/ -// -// 3. ProjectInit - Initialize new Yaze projects -// Reference: src/app/core/project.cc (Project class) -// TODO: Implement ProjectInitCommandHandler in cli/handlers/rom/project_commands.cc -// -// 4. ProjectBuild - Build Yaze projects -// Reference: src/app/core/project.cc (Project::Build method) -// TODO: Implement ProjectBuildCommandHandler in cli/handlers/rom/project_commands.cc -// -// 5. RomValidate - Validate ROM integrity -// Reference: src/app/rom.cc (Rom class validation methods) -// TODO: Create RomValidateCommandHandler in cli/handlers/rom/ -// -// 6. RomDiff - Compare ROM files -// Reference: src/app/rom.cc (Rom comparison methods) -// TODO: Implement RomDiffCommandHandler in cli/handlers/rom/rom_commands.cc -// -// 7. RomInfo - Display ROM information -// Reference: src/app/rom.cc (Rom::GetInfo methods) -// TODO: Implement RomInfoCommandHandler in cli/handlers/rom/rom_commands.cc -// -// 8. SpriteCreate - Create new sprites -// Reference: src/app/zelda3/sprite/ (sprite management classes) -// TODO: Create SpriteCreateCommandHandler in cli/handlers/graphics/ -// -// 9. CommandPalette - Interactive command palette -// Reference: src/app/editor/system/command_palette.cc -// TODO: Create CommandPaletteCommandHandler in cli/handlers/tools/ -// -// 10. DungeonExport - Export dungeon data -// Reference: src/app/editor/dungeon/ (dungeon editor classes) -// TODO: Implement DungeonExportCommandHandler in cli/handlers/game/dungeon_commands.cc -// -// 11. DungeonListObjects - List dungeon objects -// Reference: src/app/zelda3/dungeon/ (dungeon data classes) -// TODO: Implement DungeonListObjectsCommandHandler in cli/handlers/game/dungeon_commands.cc -// -// 12. GfxExport/GfxImport - Graphics import/export -// Reference: src/app/editor/graphics/ (graphics editor classes) -// TODO: Create GfxExportCommandHandler and GfxImportCommandHandler in cli/handlers/graphics/ -// -// 13. PaletteExport/PaletteImport - Palette import/export -// Reference: src/app/editor/graphics/palette_editor.cc -// TODO: Implement PaletteExportCommandHandler and PaletteImportCommandHandler in cli/handlers/graphics/palette_commands.cc -// -// 14. Overworld commands - Overworld manipulation -// Reference: src/app/editor/overworld/ (overworld editor classes) -// TODO: Implement OverworldCommandHandler in cli/handlers/game/overworld_commands.cc -// -// 15. Agent command - AI agent interface -// Reference: src/app/editor/agent/ (agent system) -// TODO: Implement AgentCommandHandler in cli/handlers/agent/ - } // namespace cli } // namespace yaze diff --git a/src/cli/cli.h b/src/cli/cli.h index f85b31de..c1f0719e 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -13,6 +13,10 @@ #include "app/snes.h" #include "util/macro.h" +#include "cli/service/resources/command_handler.h" +#include +#include + // Forward declarations namespace ftxui { class ScreenInteractive; @@ -24,215 +28,21 @@ namespace cli { // Forward declaration class TuiComponent; -class CommandHandler { - public: - CommandHandler() = default; - virtual ~CommandHandler() = default; - virtual absl::Status Run(const std::vector& arg_vec) = 0; - virtual void RunTUI(ftxui::ScreenInteractive& screen) { - // Default implementation does nothing - } - - Rom rom_; -}; - -struct CommandInfo { - std::string name; - std::string description; - std::string usage; - std::function&)> handler; -}; - class ModernCLI { public: ModernCLI(); absl::Status Run(int argc, char* argv[]); - CommandHandler* GetCommandHandler(const std::string& name); void PrintTopLevelHelp() const; void PrintCategoryHelp(const std::string& category) const; void PrintCommandSummary() const; - std::map commands_; - private: void SetupCommands(); void ShowHelp(); - void ShowCategoryHelp(const std::string& category); + void ShowCategoryHelp(const std::string& category) const; void ShowCommandSummary() const; - // Command Handlers - absl::Status HandleAsarPatchCommand(const std::vector& args); - absl::Status HandleBpsPatchCommand(const std::vector& args); - absl::Status HandleExtractSymbolsCommand(const std::vector& args); - absl::Status HandleAgentCommand(const std::vector& args); - absl::Status HandleCollabCommand(const std::vector& args); - absl::Status HandleProjectBuildCommand(const std::vector& args); - absl::Status HandleProjectInitCommand(const std::vector& args); - absl::Status HandleRomInfoCommand(const std::vector& args); - absl::Status HandleRomGenerateGoldenCommand(const std::vector& args); - absl::Status HandleRomDiffCommand(const std::vector& args); - absl::Status HandleDungeonExportCommand(const std::vector& args); - absl::Status HandleDungeonListObjectsCommand(const std::vector& args); - absl::Status HandleGfxExportCommand(const std::vector& args); - absl::Status HandleGfxImportCommand(const std::vector& args); - absl::Status HandleCommandPaletteCommand(const std::vector& args); - absl::Status HandlePaletteExportCommand(const std::vector& args); - absl::Status HandlePaletteImportCommand(const std::vector& args); - absl::Status HandlePaletteCommand(const std::vector& args); - absl::Status HandleRomValidateCommand(const std::vector& args); - absl::Status HandleOverworldGetTileCommand(const std::vector& args); - absl::Status HandleOverworldFindTileCommand(const std::vector& args); - absl::Status HandleOverworldDescribeMapCommand(const std::vector& args); - absl::Status HandleOverworldListWarpsCommand(const std::vector& args); - absl::Status HandleOverworldSetTileCommand(const std::vector& args); - absl::Status HandleOverworldSelectRectCommand(const std::vector& args); - absl::Status HandleOverworldScrollToCommand(const std::vector& args); - absl::Status HandleOverworldSetZoomCommand(const std::vector& args); - absl::Status HandleOverworldGetVisibleRegionCommand(const std::vector& args); - absl::Status HandleSpriteCreateCommand(const std::vector& args); - absl::Status HandleChatEntryCommand(const std::vector& args); - absl::Status HandleProposalCommand(const std::vector& args); - absl::Status HandleWidgetCommand(const std::vector& args); -}; - -// Legacy command classes removed - using new CommandHandler system -// See TODO comments in cli.cc for implementation roadmap - -class Open : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override { - auto const& arg = arg_vec[0]; - RETURN_IF_ERROR(rom_.LoadFromFile(arg, RomLoadOptions::CliDefaults())) - std::cout << "Title: " << rom_.title() << std::endl; - std::cout << "Size: 0x" << std::hex << rom_.size() << std::endl; - return absl::OkStatus(); - } -}; - -class Backup : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override { - RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0])) - Rom::SaveSettings settings; - settings.backup = true; - if (arg_vec.size() == 2) { - // Optional filename added - settings.filename = arg_vec[1]; - } - RETURN_IF_ERROR(rom_.SaveToFile(settings)) - return absl::OkStatus(); - } -}; - -class Compress : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override; -}; - -class Decompress : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override; -}; - -/** - * @brief Convert a SNES address to a PC address. - - * @param arg_vec `-s
` - * @return absl::Status -*/ -class SnesToPcCommand : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override { - auto arg = arg_vec[0]; - std::stringstream ss(arg.data()); - uint32_t snes_address; - ss >> std::hex >> snes_address; - uint32_t pc_address = SnesToPc(snes_address); - std::cout << std::hex << pc_address << std::endl; - return absl::OkStatus(); - } -}; - -/** - * @brief Convert a PC address to a SNES address. - - * @param arg_vec `-p
` - * @return absl::Status -*/ -class PcToSnesCommand : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override { - auto arg = arg_vec[0]; - std::stringstream ss(arg.data()); - uint32_t pc_address; - ss >> std::hex >> pc_address; - uint32_t snes_address = PcToSnes(pc_address); - std::cout << "SNES LoROM Address: "; - std::cout << "$" << std::uppercase << std::hex << snes_address << "\n"; - return absl::OkStatus(); - } -}; - -/** - * @brief Read from a Rom file. - - * @param arg_vec `-r
` - * @return absl::Status -*/ -class ReadFromRom : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override { - RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0])) - - std::stringstream ss(arg_vec[1].data()); - uint32_t offset; - ss >> std::hex >> offset; - uint32_t length = 0x01; - if (!arg_vec[2].empty()) { - length = std::stoi(arg_vec[2]); - } - - if (length > 1) { - auto returned_bytes_status = rom_.ReadByteVector(offset, length); - if (!returned_bytes_status.ok()) { - return returned_bytes_status.status(); - } - auto returned_bytes = returned_bytes_status.value(); - for (const auto& each : returned_bytes) { - std::cout << each; - } - std::cout << std::endl; - } else { - auto byte = rom_.ReadByte(offset); - std::cout << std::hex << byte.value() << std::endl; - } - - return absl::OkStatus(); - } -}; - -/** - * @brief Expand a Rom file. - - * @param arg_vec `-x ` - * @return absl::Status -*/ -class Expand : public CommandHandler { - public: - absl::Status Run(const std::vector& arg_vec) override { - RETURN_IF_ERROR(rom_.LoadFromFile(arg_vec[0])) - - std::stringstream ss(arg_vec[1].data()); - uint32_t size; - ss >> std::hex >> size; - - rom_.Expand(size); - - std::cout << "Successfully expanded ROM to " << std::hex << size - << std::endl; - - return absl::OkStatus(); - } + std::map> commands_; }; } // namespace cli diff --git a/src/cli/handlers/README.md b/src/cli/handlers/README.md new file mode 100644 index 00000000..fafc3c9a --- /dev/null +++ b/src/cli/handlers/README.md @@ -0,0 +1,320 @@ +# YAZE Modern Command Handler Architecture + +This directory contains the modern command handler system that provides a consistent interface for both CLI and AI agent interactions with ROM data. + +## Architecture Overview + +The command handler system follows a clean, layered architecture: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ CLI / Agent Interface โ”‚ +โ”‚ (cli.cc, agent.cc, simple-chat, etc) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Command Handler Base Class โ”‚ +โ”‚ (resources/command_handler.h) โ”‚ +โ”‚ - Argument parsing โ”‚ +โ”‚ - ROM context management โ”‚ +โ”‚ - Output formatting โ”‚ +โ”‚ - Error handling โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Concrete Command Handlers โ”‚ +โ”‚ (handlers/*/*) โ”‚ +โ”‚ - SpriteListCommandHandler โ”‚ +โ”‚ - DungeonDescribeRoomCommandHandler โ”‚ +โ”‚ - OverworldFindTileCommandHandler โ”‚ +โ”‚ - PaletteGetColorsCommandHandler โ”‚ +โ”‚ - etc... โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Core YAZE Libraries โ”‚ +โ”‚ - zelda3/ (overworld, dungeon, sprite) โ”‚ +โ”‚ - gfx/ (graphics, palette) โ”‚ +โ”‚ - app/editor/ (ROM operations) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Namespace Structure + +All command handlers are organized under a simplified namespace: + +```cpp +namespace yaze { +namespace cli { +namespace handlers { + // All command handler classes live here + class SpriteListCommandHandler : public resources::CommandHandler { ... }; + class DungeonDescribeRoomCommandHandler : public resources::CommandHandler { ... }; + // etc. +} +} +} +``` + +## Directory Organization + +``` +handlers/ +โ”œโ”€โ”€ README.md (this file) +โ”œโ”€โ”€ commands.h // Legacy command function declarations +โ”œโ”€โ”€ command_wrappers.cc // Wrapper functions for backward compatibility +โ”œโ”€โ”€ command_handlers.h // Forward declarations and factory functions +โ”œโ”€โ”€ command_handlers.cc // Factory implementations +โ”‚ +โ”œโ”€โ”€ graphics/ // Graphics-related commands +โ”‚ โ”œโ”€โ”€ sprite_commands.h/.cc // Sprite listing and properties +โ”‚ โ”œโ”€โ”€ palette_commands.h/.cc // Palette manipulation +โ”‚ โ”œโ”€โ”€ hex_commands.h/.cc // Raw hex data access +โ”‚ โ””โ”€โ”€ gfx.cc // Legacy graphics commands +โ”‚ +โ”œโ”€โ”€ game/ // Game data inspection +โ”‚ โ”œโ”€โ”€ dungeon_commands.h/.cc // Dungeon room inspection +โ”‚ โ”œโ”€โ”€ overworld_commands.h/.cc // Overworld map inspection +โ”‚ โ”œโ”€โ”€ music_commands.h/.cc // Music track information +โ”‚ โ”œโ”€โ”€ dialogue_commands.h/.cc // Dialogue/message search +โ”‚ โ””โ”€โ”€ message_commands.h/.cc // Message data access +โ”‚ +โ”œโ”€โ”€ tools/ // Development tools +โ”‚ โ”œโ”€โ”€ resource_commands.h/.cc // Resource label inspection +โ”‚ โ”œโ”€โ”€ gui_commands.h/.cc // GUI automation +โ”‚ โ””โ”€โ”€ emulator_commands.h/.cc // Emulator/debugger control +โ”‚ +โ””โ”€โ”€ agent/ // AI agent specific + โ”œโ”€โ”€ general_commands.cc // Agent command routing + โ”œโ”€โ”€ test_commands.cc // Test harness + โ””โ”€โ”€ todo_commands.h/.cc // Task management +``` + +## Creating a New Command Handler + +### 1. Define the Handler Class + +Create a header file (e.g., `new_feature_commands.h`): + +```cpp +#ifndef YAZE_SRC_CLI_HANDLERS_NEW_FEATURE_COMMANDS_H_ +#define YAZE_SRC_CLI_HANDLERS_NEW_FEATURE_COMMANDS_H_ + +#include "cli/service/resources/command_handler.h" + +namespace yaze { +namespace cli { +namespace handlers { + +class NewFeatureCommandHandler : public resources::CommandHandler { + public: + std::string GetName() const { return "new-feature"; } + + std::string GetDescription() const { + return "Brief description of what this command does"; + } + + std::string GetUsage() const { + return "new-feature --arg1 [--optional-arg2 ]"; + } + + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { + // Validate required arguments + return parser.RequireArgs({"arg1"}); + } + + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) override; +}; + +} // namespace handlers +} // namespace cli +} // namespace yaze + +#endif // YAZE_SRC_CLI_HANDLERS_NEW_FEATURE_COMMANDS_H_ +``` + +### 2. Implement the Handler + +Create the implementation file (e.g., `new_feature_commands.cc`): + +```cpp +#include "cli/handlers/new_feature_commands.h" + +#include "absl/strings/str_format.h" + +namespace yaze { +namespace cli { +namespace handlers { + +absl::Status NewFeatureCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + + // Parse arguments + auto arg1 = parser.GetString("arg1").value(); + auto arg2 = parser.GetString("optional-arg2").value_or("default"); + + // Begin output + formatter.BeginObject("New Feature Result"); + formatter.AddField("input_arg", arg1); + + // Do work with ROM + // ... use rom->read(), zelda3 classes, etc. + + // Add results to formatter + formatter.AddField("result", "success"); + formatter.BeginArray("items"); + // ... add items + formatter.EndArray(); + formatter.EndObject(); + + return absl::OkStatus(); +} + +} // namespace handlers +} // namespace cli +} // namespace yaze +``` + +### 3. Register in Factory + +Add to `command_handlers.cc`: + +```cpp +#include "cli/handlers/new_feature_commands.h" + +std::vector> CreateCliCommandHandlers() { + // ... existing handlers ... + handlers.push_back(std::make_unique()); + return handlers; +} +``` + +### 4. Add Forward Declaration + +Add to `command_handlers.h`: + +```cpp +// Forward declarations for command handler classes +class NewFeatureCommandHandler; +``` + +## Command Handler Base Class + +The `resources::CommandHandler` base class provides: + +### Lifecycle Methods + +- `Run(args, rom_context)` - Main entry point that orchestrates the full command execution +- `ValidateArgs(parser)` - Override to validate command arguments +- `Execute(rom, parser, formatter)` - Override to implement command logic + +### Helper Methods + +- `GetUsage()` - Return usage string for help +- `GetName()` - Return command name +- `GetDescription()` - Return brief description +- `RequiresLabels()` - Return true if command needs ROM labels loaded +- `GetDefaultFormat()` - Return "json" or "text" for default output +- `GetOutputTitle()` - Return title for output object + +## Argument Parsing + +The `ArgumentParser` class handles common CLI patterns: + +```cpp +// Get string argument +auto value = parser.GetString("arg_name").value_or("default"); + +// Get integer (supports hex with 0x prefix) +auto count = parser.GetInt("count").value_or(10); + +// Get hex value +auto address = parser.GetHex("address").value(); + +// Check for flag +if (parser.HasFlag("verbose")) { ... } + +// Get positional arguments +auto positional = parser.GetPositional(); + +// Require specific arguments +RETURN_IF_ERROR(parser.RequireArgs({"required1", "required2"})); +``` + +## Output Formatting + +The `OutputFormatter` class provides consistent JSON/text output: + +```cpp +// Begin object +formatter.BeginObject("Title"); + +// Add fields +formatter.AddField("string_field", "value"); +formatter.AddField("int_field", 42); +formatter.AddField("bool_field", true); +formatter.AddHexField("address", 0x1234, 4); // Width in digits + +// Arrays +formatter.BeginArray("items"); +for (const auto& item : items) { + formatter.AddArrayItem(absl::StrFormat("Item %d", i)); +} +formatter.EndArray(); + +// Nested objects +formatter.BeginObject("nested"); +formatter.AddField("nested_field", "value"); +formatter.EndObject(); + +// End object +formatter.EndObject(); +``` + +## Integration with Public API + +Command handlers are designed to work alongside the public C API defined in `incl/yaze.h` and `incl/zelda.h`. + +- Handlers use internal C++ classes from `app/zelda3/` +- Output structures align with C API data types where possible +- Future: C API bridge will expose commands to external applications + +## Best Practices + +1. **Keep handlers focused** - One command per handler class +2. **Use existing zelda3 classes** - Don't duplicate ROM parsing logic +3. **Validate inputs early** - Use `ValidateArgs()` to catch errors +4. **Provide good error messages** - Return descriptive `absl::Status` errors +5. **Support both JSON and text** - Format output using `OutputFormatter` +6. **Document parameters** - Include full usage string in `GetUsage()` +7. **Test with agents** - Commands should be AI-friendly +8. **Mark unused rom parameter** - Use `Rom* /*rom*/` if not needed + +## Testing + +Test commands using the CLI: + +```bash +# Direct command execution +./build/bin/z3ed agent sprite-list --limit 10 --format json --rom zelda3.sfc + +# Via simple-chat interface +./build/bin/z3ed agent simple-chat --rom zelda3.sfc +> sprite-list --limit 5 + +# In agent test suite +./build/bin/z3ed agent test-conversation --rom zelda3.sfc +``` + +## Future Enhancements + +- [ ] C API bridge for external language bindings +- [ ] Command auto-discovery and registration +- [ ] Per-command help system +- [ ] Command aliases and shortcuts +- [ ] Batch command execution +- [ ] Command pipelines (output of one โ†’ input of another) +- [ ] Interactive command REPL improvements + diff --git a/src/cli/handlers/agent.cc b/src/cli/handlers/agent.cc index c120952f..0907952b 100644 --- a/src/cli/handlers/agent.cc +++ b/src/cli/handlers/agent.cc @@ -1,4 +1,3 @@ -#include "cli/handlers/commands.h" #include "cli/handlers/agent/todo_commands.h" #include "cli/cli.h" @@ -8,19 +7,70 @@ #include "absl/flags/declare.h" #include "absl/flags/flag.h" #include "absl/status/status.h" +#include "cli/handlers/agent/common.h" +#include "cli/handlers/agent/todo_commands.h" +#include "cli/handlers/agent/simple_chat_command.h" +#include "cli/handlers/tools/resource_commands.h" +#include "cli/handlers/game/dungeon_commands.h" +#include "cli/handlers/game/overworld_commands.h" +#include "cli/handlers/tools/gui_commands.h" +#include "cli/handlers/tools/emulator_commands.h" ABSL_DECLARE_FLAG(bool, quiet); namespace yaze { namespace cli { + +// Forward declarations from general_commands.cc +namespace agent { +absl::Status HandlePlanCommand(const std::vector& args); +absl::Status HandleTestCommand(const std::vector& args); +absl::Status HandleTestConversationCommand(const std::vector& args); +absl::Status HandleGuiCommand(const std::vector& args); +absl::Status HandleLearnCommand(const std::vector& args); +absl::Status HandleListCommand(); +absl::Status HandleDescribeCommand(const std::vector& args); + +// Wrapper functions to call CommandHandlers +absl::Status HandleResourceListCommand(const std::vector& args, Rom* rom) { + handlers::ResourceListCommandHandler handler; + return handler.Run(args, rom); +} + +absl::Status HandleResourceSearchCommand(const std::vector& args, Rom* rom) { + handlers::ResourceSearchCommandHandler handler; + return handler.Run(args, rom); +} + +absl::Status HandleDungeonListSpritesCommand(const std::vector& args, Rom* rom) { + handlers::DungeonListSpritesCommandHandler handler; + return handler.Run(args, rom); +} + +absl::Status HandleDungeonDescribeRoomCommand(const std::vector& args, Rom* rom) { + handlers::DungeonDescribeRoomCommandHandler handler; + return handler.Run(args, rom); +} + +absl::Status HandleOverworldFindTileCommand(const std::vector& args, Rom* rom) { + handlers::OverworldFindTileCommandHandler handler; + return handler.Run(args, rom); +} + +absl::Status HandleOverworldDescribeMapCommand(const std::vector& args, Rom* rom) { + handlers::OverworldDescribeMapCommandHandler handler; + return handler.Run(args, rom); +} + +absl::Status HandleOverworldListWarpsCommand(const std::vector& args, Rom* rom) { + handlers::OverworldListWarpsCommandHandler handler; + return handler.Run(args, rom); +} + +} // namespace agent + namespace { -// Forward declarations for functions implemented in other files -// Function declarations moved to commands.h - -// Use handlers from command_wrappers.cc -using namespace yaze::cli::handlers; - constexpr absl::string_view kUsage = "Usage: agent [options]\n" "\n" @@ -98,13 +148,11 @@ absl::Status HandleAgentCommand(const std::vector& arg_vec) { const std::string& subcommand = arg_vec[0]; std::vector subcommand_args(arg_vec.begin() + 1, arg_vec.end()); - // TODO: Implement proper ROM context handling - // For now, return unimplemented for commands that require ROM context if (subcommand == "run") { return absl::UnimplementedError("Agent run command requires ROM context - not yet implemented"); } if (subcommand == "plan") { - return HandlePlanCommand(subcommand_args); + return agent::HandlePlanCommand(subcommand_args); } if (subcommand == "diff") { return absl::UnimplementedError("Agent diff command requires ROM context - not yet implemented"); @@ -113,19 +161,19 @@ absl::Status HandleAgentCommand(const std::vector& arg_vec) { return absl::UnimplementedError("Agent accept command requires ROM context - not yet implemented"); } if (subcommand == "test") { - return HandleTestCommand(subcommand_args); + return agent::HandleTestCommand(subcommand_args); } if (subcommand == "test-conversation") { - return HandleTestConversationCommand(subcommand_args); + return agent::HandleTestConversationCommand(subcommand_args); } if (subcommand == "gui") { - return HandleGuiCommand(subcommand_args); + return agent::HandleGuiCommand(subcommand_args); } if (subcommand == "learn") { - return HandleLearnCommand(subcommand_args); + return agent::HandleLearnCommand(subcommand_args); } if (subcommand == "list") { - return HandleListCommand(); + return agent::HandleListCommand(); } if (subcommand == "commit") { return absl::UnimplementedError("Agent commit command requires ROM context - not yet implemented"); @@ -134,60 +182,57 @@ absl::Status HandleAgentCommand(const std::vector& arg_vec) { return absl::UnimplementedError("Agent revert command requires ROM context - not yet implemented"); } if (subcommand == "describe") { - return HandleDescribeCommand(subcommand_args); + return agent::HandleDescribeCommand(subcommand_args); } if (subcommand == "resource-list") { - return HandleResourceListCommand(subcommand_args, nullptr); + return agent::HandleResourceListCommand(subcommand_args, nullptr); } if (subcommand == "resource-search") { - return HandleResourceSearchCommand(subcommand_args, nullptr); + return agent::HandleResourceSearchCommand(subcommand_args, nullptr); } if (subcommand == "dungeon-list-sprites") { - return HandleDungeonListSpritesCommand(subcommand_args, nullptr); + return agent::HandleDungeonListSpritesCommand(subcommand_args, nullptr); } if (subcommand == "dungeon-describe-room") { - return HandleDungeonDescribeRoomCommand(subcommand_args, nullptr); + return agent::HandleDungeonDescribeRoomCommand(subcommand_args, nullptr); } if (subcommand == "overworld-find-tile") { - return HandleOverworldFindTileCommand(subcommand_args, nullptr); + return agent::HandleOverworldFindTileCommand(subcommand_args, nullptr); } if (subcommand == "overworld-describe-map") { - return HandleOverworldDescribeMapCommand(subcommand_args, nullptr); + return agent::HandleOverworldDescribeMapCommand(subcommand_args, nullptr); } if (subcommand == "overworld-list-warps") { - return HandleOverworldListWarpsCommand(subcommand_args, nullptr); - } - if (subcommand == "chat") { - return absl::UnimplementedError("Agent chat command requires ROM context - not yet implemented"); - } - if (subcommand == "simple-chat") { - return absl::UnimplementedError("Agent simple-chat command requires ROM context - not yet implemented"); - } - if (subcommand == "todo") { - return handlers::HandleTodoCommand(subcommand_args); + return agent::HandleOverworldListWarpsCommand(subcommand_args, nullptr); } + // if (subcommand == "chat") { + // return absl::UnimplementedError("Agent chat command requires ROM context - not yet implemented"); + // } + // if (subcommand == "todo") { + // return handlers::HandleTodoCommand(subcommand_args); + // } - // Hex manipulation commands - if (subcommand == "hex-read") { - return HandleHexRead(subcommand_args, nullptr); - } - if (subcommand == "hex-write") { - return HandleHexWrite(subcommand_args, nullptr); - } - if (subcommand == "hex-search") { - return HandleHexSearch(subcommand_args, nullptr); - } + // // Hex manipulation commands + // if (subcommand == "hex-read") { + // return HandleHexRead(subcommand_args, nullptr); + // } + // if (subcommand == "hex-write") { + // return HandleHexWrite(subcommand_args, nullptr); + // } + // if (subcommand == "hex-search") { + // return HandleHexSearch(subcommand_args, nullptr); + // } - // Palette manipulation commands - if (subcommand == "palette-get-colors") { - return HandlePaletteGetColors(subcommand_args, nullptr); - } - if (subcommand == "palette-set-color") { - return HandlePaletteSetColor(subcommand_args, nullptr); - } - if (subcommand == "palette-analyze") { - return HandlePaletteAnalyze(subcommand_args, nullptr); - } + // // Palette manipulation commands + // if (subcommand == "palette-get-colors") { + // return HandlePaletteGetColors(subcommand_args, nullptr); + // } + // if (subcommand == "palette-set-color") { + // return HandlePaletteSetColor(subcommand_args, nullptr); + // } + // if (subcommand == "palette-analyze") { + // return HandlePaletteAnalyze(subcommand_args, nullptr); + // } return absl::InvalidArgumentError(std::string(kUsage)); } diff --git a/src/cli/handlers/agent/simple_chat_command.cc b/src/cli/handlers/agent/simple_chat_command.cc new file mode 100644 index 00000000..204ebddd --- /dev/null +++ b/src/cli/handlers/agent/simple_chat_command.cc @@ -0,0 +1,37 @@ +#include "cli/handlers/agent/simple_chat_command.h" +#include "cli/service/agent/simple_chat_session.h" + +namespace yaze { +namespace cli { +namespace handlers { + +absl::Status SimpleChatCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + + agent::SimpleChatSession session; + session.SetRomContext(rom); + + // Configure session from parser + agent::AgentConfig config; + if (parser.HasFlag("verbose")) { + config.verbose = true; + } + if (auto format = parser.GetString("format")) { + // Simplified format handling + } + session.SetConfig(config); + + if (auto file = parser.GetString("file")) { + return session.RunBatch(*file); + } else if (auto prompt = parser.GetString("prompt")) { + std::string response; + return session.SendAndWaitForResponse(*prompt, &response); + } else { + return session.RunInteractive(); + } +} + +} // namespace handlers +} // namespace cli +} // namespace yaze diff --git a/src/cli/handlers/agent/simple_chat_command.h b/src/cli/handlers/agent/simple_chat_command.h new file mode 100644 index 00000000..4400106b --- /dev/null +++ b/src/cli/handlers/agent/simple_chat_command.h @@ -0,0 +1,31 @@ +#ifndef YAZE_CLI_HANDLERS_AGENT_SIMPLE_CHAT_COMMAND_H_ +#define YAZE_CLI_HANDLERS_AGENT_SIMPLE_CHAT_COMMAND_H_ + +#include "cli/service/resources/command_handler.h" + +namespace yaze { +namespace cli { +namespace handlers { + +class SimpleChatCommandHandler : public resources::CommandHandler { + public: + std::string GetName() const { return "simple-chat"; } + std::string GetDescription() const { return "Simple text-based chat with the AI agent."; } + std::string GetUsage() const override { + return "simple-chat [--prompt ] [--file ] [--format ]"; + } + + protected: + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { + return absl::OkStatus(); + } + + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) override; +}; + +} // namespace handlers +} // namespace cli +} // namespace yaze + +#endif // YAZE_CLI_HANDLERS_AGENT_SIMPLE_CHAT_COMMAND_H_ diff --git a/src/cli/handlers/command_handlers.cc b/src/cli/handlers/command_handlers.cc index 0ec914a8..253e2b26 100644 --- a/src/cli/handlers/command_handlers.cc +++ b/src/cli/handlers/command_handlers.cc @@ -60,9 +60,14 @@ std::vector> CreateCliCommandHandlers return handlers; } +#include "cli/handlers/agent/simple_chat_command.h" + std::vector> CreateAgentCommandHandlers() { std::vector> handlers; + // Add simple-chat command handler + handlers.push_back(std::make_unique()); + // Resource inspection tools handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); diff --git a/src/cli/handlers/command_handlers.h b/src/cli/handlers/command_handlers.h index 4e08eff5..7b7085b0 100644 --- a/src/cli/handlers/command_handlers.h +++ b/src/cli/handlers/command_handlers.h @@ -7,6 +7,8 @@ #include "cli/service/resources/command_handler.h" +#include "cli/handlers/agent/simple_chat_command.h" + namespace yaze { namespace cli { namespace handlers { diff --git a/src/cli/handlers/command_wrappers.cc b/src/cli/handlers/command_wrappers.cc deleted file mode 100644 index 1cd4cd85..00000000 --- a/src/cli/handlers/command_wrappers.cc +++ /dev/null @@ -1,328 +0,0 @@ -#include "cli/handlers/commands.h" - -#include "cli/handlers/tools/resource_commands.h" -#include "cli/handlers/game/dungeon_commands.h" -#include "cli/handlers/game/overworld_commands.h" -#include "cli/handlers/game/message_commands.h" -#include "cli/handlers/game/dialogue_commands.h" -#include "cli/handlers/game/music_commands.h" -#include "cli/handlers/graphics/hex_commands.h" -#include "cli/handlers/graphics/palette_commands.h" -// #include "cli/handlers/graphics/sprite_commands.h" // Implementations not available -#include "cli/handlers/tools/gui_commands.h" -#include "cli/handlers/tools/emulator_commands.h" -// #include "cli/handlers/rom/rom_commands.h" // Not used in stubs -// #include "cli/handlers/rom/project_commands.h" // Not used in stubs - -namespace yaze { -namespace cli { -namespace handlers { - -// Resource commands -absl::Status HandleResourceListCommand(const std::vector& args, Rom* rom) { - ResourceListCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleResourceSearchCommand(const std::vector& args, Rom* rom) { - ResourceSearchCommandHandler handler; - return handler.Run(args, rom); -} - -// Dungeon commands -absl::Status HandleDungeonListSpritesCommand(const std::vector& args, Rom* rom) { - DungeonListSpritesCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleDungeonDescribeRoomCommand(const std::vector& args, Rom* rom) { - DungeonDescribeRoomCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleDungeonExportRoomCommand(const std::vector& args, Rom* rom) { - DungeonExportRoomCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleDungeonListObjectsCommand(const std::vector& args, Rom* rom) { - DungeonListObjectsCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleDungeonGetRoomTilesCommand(const std::vector& args, Rom* rom) { - DungeonGetRoomTilesCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleDungeonSetRoomPropertyCommand(const std::vector& args, Rom* rom) { - DungeonSetRoomPropertyCommandHandler handler; - return handler.Run(args, rom); -} - -// Overworld commands -absl::Status HandleOverworldFindTileCommand(const std::vector& args, Rom* rom) { - OverworldFindTileCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleOverworldDescribeMapCommand(const std::vector& args, Rom* rom) { - OverworldDescribeMapCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleOverworldListWarpsCommand(const std::vector& args, Rom* rom) { - OverworldListWarpsCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleOverworldListSpritesCommand(const std::vector& args, Rom* rom) { - OverworldListSpritesCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleOverworldGetEntranceCommand(const std::vector& args, Rom* rom) { - OverworldGetEntranceCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleOverworldTileStatsCommand(const std::vector& args, Rom* rom) { - OverworldTileStatsCommandHandler handler; - return handler.Run(args, rom); -} - -// Message commands -absl::Status HandleMessageListCommand(const std::vector& args, Rom* rom) { - MessageListCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleMessageReadCommand(const std::vector& args, Rom* rom) { - MessageReadCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleMessageSearchCommand(const std::vector& args, Rom* rom) { - MessageSearchCommandHandler handler; - return handler.Run(args, rom); -} - -// Dialogue commands -absl::Status HandleDialogueListCommand(const std::vector& args, Rom* rom) { - DialogueListCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleDialogueReadCommand(const std::vector& args, Rom* rom) { - DialogueReadCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleDialogueSearchCommand(const std::vector& args, Rom* rom) { - DialogueSearchCommandHandler handler; - return handler.Run(args, rom); -} - -// Music commands -absl::Status HandleMusicListCommand(const std::vector& args, Rom* rom) { - MusicListCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleMusicInfoCommand(const std::vector& args, Rom* rom) { - MusicInfoCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleMusicTracksCommand(const std::vector& args, Rom* rom) { - MusicTracksCommandHandler handler; - return handler.Run(args, rom); -} - -// Sprite commands (stubs - implementations not available) -absl::Status HandleSpriteListCommand(const std::vector& /*args*/, Rom* /*rom*/) { - return absl::OkStatus(); -} - -absl::Status HandleSpritePropertiesCommand(const std::vector& /*args*/, Rom* /*rom*/) { - return absl::OkStatus(); -} - -absl::Status HandleSpritePaletteCommand(const std::vector& /*args*/, Rom* /*rom*/) { - return absl::OkStatus(); -} - -// GUI commands -absl::Status HandleGuiPlaceTileCommand(const std::vector& args, Rom* rom) { - GuiPlaceTileCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleGuiClickCommand(const std::vector& args, Rom* rom) { - GuiClickCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleGuiDiscoverToolCommand(const std::vector& args, Rom* rom) { - GuiDiscoverToolCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleGuiScreenshotCommand(const std::vector& args, Rom* rom) { - GuiScreenshotCommandHandler handler; - return handler.Run(args, rom); -} - -// Emulator commands -absl::Status HandleEmulatorStepCommand(const std::vector& args, Rom* rom) { - EmulatorStepCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorRunCommand(const std::vector& args, Rom* rom) { - EmulatorRunCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorPauseCommand(const std::vector& args, Rom* rom) { - EmulatorPauseCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorResetCommand(const std::vector& args, Rom* rom) { - EmulatorResetCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorGetStateCommand(const std::vector& args, Rom* rom) { - EmulatorGetStateCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorSetBreakpointCommand(const std::vector& args, Rom* rom) { - EmulatorSetBreakpointCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorClearBreakpointCommand(const std::vector& args, Rom* rom) { - EmulatorClearBreakpointCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorListBreakpointsCommand(const std::vector& args, Rom* rom) { - EmulatorListBreakpointsCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorReadMemoryCommand(const std::vector& args, Rom* rom) { - EmulatorReadMemoryCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorWriteMemoryCommand(const std::vector& args, Rom* rom) { - EmulatorWriteMemoryCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorGetRegistersCommand(const std::vector& args, Rom* rom) { - EmulatorGetRegistersCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleEmulatorGetMetricsCommand(const std::vector& args, Rom* rom) { - EmulatorGetMetricsCommandHandler handler; - return handler.Run(args, rom); -} - -// Hex commands -absl::Status HandleHexRead(const std::vector& args, Rom* rom) { - HexReadCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleHexWrite(const std::vector& args, Rom* rom) { - HexWriteCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandleHexSearch(const std::vector& args, Rom* rom) { - HexSearchCommandHandler handler; - return handler.Run(args, rom); -} - -// Palette commands -absl::Status HandlePaletteGetColors(const std::vector& args, Rom* rom) { - PaletteGetColorsCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandlePaletteSetColor(const std::vector& args, Rom* rom) { - PaletteSetColorCommandHandler handler; - return handler.Run(args, rom); -} - -absl::Status HandlePaletteAnalyze(const std::vector& args, Rom* rom) { - PaletteAnalyzeCommandHandler handler; - return handler.Run(args, rom); -} - -// Agent-specific commands (stubs for now) -absl::Status HandleRunCommand(const std::vector& /*args*/, Rom& /*rom*/) { - return absl::OkStatus(); -} - -absl::Status HandlePlanCommand(const std::vector& /*args*/) { - return absl::OkStatus(); -} - -absl::Status HandleDiffCommand(Rom& /*rom*/, const std::vector& /*args*/) { - return absl::OkStatus(); -} - -absl::Status HandleAcceptCommand(const std::vector& /*args*/, Rom& /*rom*/) { - return absl::OkStatus(); -} - -absl::Status HandleTestCommand(const std::vector& /*args*/) { - return absl::OkStatus(); -} - -absl::Status HandleGuiCommand(const std::vector& /*args*/) { - return absl::OkStatus(); -} - -absl::Status HandleLearnCommand(const std::vector& /*args*/) { - return absl::OkStatus(); -} - -absl::Status HandleListCommand() { - return absl::OkStatus(); -} - -absl::Status HandleCommitCommand(Rom& /*rom*/) { - return absl::OkStatus(); -} - -absl::Status HandleRevertCommand(Rom& /*rom*/) { - return absl::OkStatus(); -} - -absl::Status HandleDescribeCommand(const std::vector& /*arg_vec*/) { - return absl::OkStatus(); -} - -absl::Status HandleChatCommand(Rom& /*rom*/) { - return absl::OkStatus(); -} - -absl::Status HandleSimpleChatCommand(const std::vector&, Rom* /*rom*/, bool /*quiet*/) { - return absl::OkStatus(); -} - -absl::Status HandleTestConversationCommand(const std::vector& /*arg_vec*/) { - return absl::OkStatus(); -} - -} // namespace handlers -} // namespace cli -} // namespace yaze diff --git a/src/cli/handlers/commands.h b/src/cli/handlers/commands.h deleted file mode 100644 index a7955d02..00000000 --- a/src/cli/handlers/commands.h +++ /dev/null @@ -1,194 +0,0 @@ -#ifndef YAZE_CLI_HANDLERS_COMMANDS_H_ -#define YAZE_CLI_HANDLERS_COMMANDS_H_ - -#include -#include - -#include "absl/status/status.h" - -namespace yaze { -class Rom; - -namespace cli { -namespace handlers { - -absl::Status HandleRunCommand(const std::vector& args, - Rom& rom); -absl::Status HandlePlanCommand(const std::vector& args); -absl::Status HandleDiffCommand(Rom& rom, - const std::vector& args); -absl::Status HandleAcceptCommand(const std::vector& args, Rom& rom); -absl::Status HandleTestCommand(const std::vector& args); -absl::Status HandleGuiCommand(const std::vector& args); -absl::Status HandleLearnCommand(const std::vector& args = {}); -absl::Status HandleListCommand(); -absl::Status HandleCommitCommand(Rom& rom); -absl::Status HandleRevertCommand(Rom& rom); -absl::Status HandleDescribeCommand(const std::vector& arg_vec); -absl::Status HandleResourceListCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleResourceSearchCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleDungeonListSpritesCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleDungeonDescribeRoomCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleOverworldFindTileCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleOverworldDescribeMapCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleOverworldListWarpsCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleOverworldListSpritesCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleOverworldGetEntranceCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleOverworldTileStatsCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleMessageListCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleMessageReadCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleMessageSearchCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); - -// GUI Automation Tools -absl::Status HandleGuiPlaceTileCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleGuiClickCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleGuiDiscoverToolCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleGuiScreenshotCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); - -// Dialogue Inspection Tools -absl::Status HandleDialogueListCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleDialogueReadCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleDialogueSearchCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); - -// Music Data Tools -absl::Status HandleMusicListCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleMusicInfoCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleMusicTracksCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); - -// Sprite Property Tools -absl::Status HandleSpriteListCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleSpritePropertiesCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleSpritePaletteCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleChatCommand(Rom& rom); -absl::Status HandleSimpleChatCommand(const std::vector&, Rom* rom, bool quiet); -absl::Status HandleTestConversationCommand( - const std::vector& arg_vec); - -// Agent command handler -absl::Status HandleAgentCommand(const std::vector& arg_vec); - -// Hex manipulation commands -absl::Status HandleHexRead(const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleHexWrite(const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleHexSearch(const std::vector& arg_vec, - Rom* rom_context = nullptr); - -// Palette manipulation commands -absl::Status HandlePaletteGetColors(const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandlePaletteSetColor(const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandlePaletteAnalyze(const std::vector& arg_vec, - Rom* rom_context = nullptr); - -// Dungeon editing commands -absl::Status HandleDungeonExportRoomCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleDungeonListObjectsCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleDungeonGetRoomTilesCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleDungeonSetRoomPropertyCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); - -// Emulator & Debugger commands -absl::Status HandleEmulatorStepCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorRunCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorPauseCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorResetCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorGetStateCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorSetBreakpointCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorClearBreakpointCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorListBreakpointsCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorReadMemoryCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorWriteMemoryCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorGetRegistersCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); -absl::Status HandleEmulatorGetMetricsCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); - -} // namespace handlers -} // namespace cli -} // namespace yaze - -#endif // YAZE_CLI_HANDLERS_COMMANDS_H_ diff --git a/src/cli/service/agent/conversational_agent_service.cc b/src/cli/service/agent/conversational_agent_service.cc index e9bd2637..3b3eafeb 100644 --- a/src/cli/service/agent/conversational_agent_service.cc +++ b/src/cli/service/agent/conversational_agent_service.cc @@ -183,6 +183,7 @@ ConversationalAgentService::ConversationalAgentService(const AgentConfig& config void ConversationalAgentService::SetRomContext(Rom* rom) { rom_context_ = rom; + tool_dispatcher_.SetRomContext(rom_context_); if (ai_service_) { ai_service_->SetRomContext(rom_context_); diff --git a/src/cli/service/agent/conversational_agent_service.h b/src/cli/service/agent/conversational_agent_service.h index 72882db9..30482cdd 100644 --- a/src/cli/service/agent/conversational_agent_service.h +++ b/src/cli/service/agent/conversational_agent_service.h @@ -6,11 +6,12 @@ #include #include +#include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/time/time.h" #include "cli/service/ai/ai_service.h" -#include "cli/service/agent/tool_dispatcher.h" #include "cli/service/agent/proposal_executor.h" +#include "cli/service/agent/tool_dispatcher.h" namespace yaze { diff --git a/src/cli/service/agent/tool_dispatcher.cc b/src/cli/service/agent/tool_dispatcher.cc index a61bb6fa..d67fbe40 100644 --- a/src/cli/service/agent/tool_dispatcher.cc +++ b/src/cli/service/agent/tool_dispatcher.cc @@ -1,139 +1,282 @@ #include "cli/service/agent/tool_dispatcher.h" -#include +#include +#include +#include #include +#include -#include "absl/strings/match.h" -#include "absl/strings/str_format.h" -#include "cli/handlers/commands.h" +#include "absl/strings/str_cat.h" +#include "cli/handlers/game/dialogue_commands.h" +#include "cli/handlers/game/dungeon_commands.h" +#include "cli/handlers/game/message_commands.h" +#include "cli/handlers/game/music_commands.h" +#include "cli/handlers/game/overworld_commands.h" +#include "cli/handlers/graphics/sprite_commands.h" +#include "cli/handlers/tools/emulator_commands.h" +#include "cli/handlers/tools/gui_commands.h" +#include "cli/handlers/tools/resource_commands.h" +#include "cli/service/resources/command_context.h" +#include "cli/util/terminal_colors.h" namespace yaze { namespace cli { namespace agent { -absl::StatusOr ToolDispatcher::Dispatch( - const ToolCall& tool_call) { +namespace { + +// Map tool name to handler type +ToolCallType GetToolCallType(const std::string& tool_name) { + // Resource commands + if (tool_name == "resource-list") return ToolCallType::kResourceList; + if (tool_name == "resource-search") return ToolCallType::kResourceSearch; + + // Dungeon commands + if (tool_name == "dungeon-list-sprites") return ToolCallType::kDungeonListSprites; + if (tool_name == "dungeon-describe-room") return ToolCallType::kDungeonDescribeRoom; + if (tool_name == "dungeon-export-room") return ToolCallType::kDungeonExportRoom; + if (tool_name == "dungeon-list-objects") return ToolCallType::kDungeonListObjects; + if (tool_name == "dungeon-get-room-tiles") return ToolCallType::kDungeonGetRoomTiles; + if (tool_name == "dungeon-set-room-property") return ToolCallType::kDungeonSetRoomProperty; + + // Overworld commands + if (tool_name == "overworld-find-tile") return ToolCallType::kOverworldFindTile; + if (tool_name == "overworld-describe-map") return ToolCallType::kOverworldDescribeMap; + if (tool_name == "overworld-list-warps") return ToolCallType::kOverworldListWarps; + if (tool_name == "overworld-list-sprites") return ToolCallType::kOverworldListSprites; + if (tool_name == "overworld-get-entrance") return ToolCallType::kOverworldGetEntrance; + if (tool_name == "overworld-tile-stats") return ToolCallType::kOverworldTileStats; + + // Message & Dialogue commands + if (tool_name == "message-list") return ToolCallType::kMessageList; + if (tool_name == "message-read") return ToolCallType::kMessageRead; + if (tool_name == "message-search") return ToolCallType::kMessageSearch; + if (tool_name == "dialogue-list") return ToolCallType::kDialogueList; + if (tool_name == "dialogue-read") return ToolCallType::kDialogueRead; + if (tool_name == "dialogue-search") return ToolCallType::kDialogueSearch; + + // GUI Automation commands + if (tool_name == "gui-place-tile") return ToolCallType::kGuiPlaceTile; + if (tool_name == "gui-click") return ToolCallType::kGuiClick; + if (tool_name == "gui-discover-tool") return ToolCallType::kGuiDiscover; + if (tool_name == "gui-screenshot") return ToolCallType::kGuiScreenshot; + + // Music commands + if (tool_name == "music-list") return ToolCallType::kMusicList; + if (tool_name == "music-info") return ToolCallType::kMusicInfo; + if (tool_name == "music-tracks") return ToolCallType::kMusicTracks; + + // Sprite commands + if (tool_name == "sprite-list") return ToolCallType::kSpriteList; + if (tool_name == "sprite-properties") return ToolCallType::kSpriteProperties; + if (tool_name == "sprite-palette") return ToolCallType::kSpritePalette; + + // Emulator & Debugger commands + if (tool_name == "emulator-step") return ToolCallType::kEmulatorStep; + if (tool_name == "emulator-run") return ToolCallType::kEmulatorRun; + if (tool_name == "emulator-pause") return ToolCallType::kEmulatorPause; + if (tool_name == "emulator-reset") return ToolCallType::kEmulatorReset; + if (tool_name == "emulator-get-state") return ToolCallType::kEmulatorGetState; + if (tool_name == "emulator-set-breakpoint") return ToolCallType::kEmulatorSetBreakpoint; + if (tool_name == "emulator-clear-breakpoint") return ToolCallType::kEmulatorClearBreakpoint; + if (tool_name == "emulator-list-breakpoints") return ToolCallType::kEmulatorListBreakpoints; + if (tool_name == "emulator-read-memory") return ToolCallType::kEmulatorReadMemory; + if (tool_name == "emulator-write-memory") return ToolCallType::kEmulatorWriteMemory; + if (tool_name == "emulator-get-registers") return ToolCallType::kEmulatorGetRegisters; + if (tool_name == "emulator-get-metrics") return ToolCallType::kEmulatorGetMetrics; + + return ToolCallType::kUnknown; +} + +// Create the appropriate command handler for a tool call type +std::unique_ptr CreateHandler(ToolCallType type) { using namespace yaze::cli::handlers; - std::vector args; + switch (type) { + // Resource commands + case ToolCallType::kResourceList: + return std::make_unique(); + case ToolCallType::kResourceSearch: + return std::make_unique(); + + // Dungeon commands + case ToolCallType::kDungeonListSprites: + return std::make_unique(); + case ToolCallType::kDungeonDescribeRoom: + return std::make_unique(); + case ToolCallType::kDungeonExportRoom: + return std::make_unique(); + case ToolCallType::kDungeonListObjects: + return std::make_unique(); + case ToolCallType::kDungeonGetRoomTiles: + return std::make_unique(); + case ToolCallType::kDungeonSetRoomProperty: + return std::make_unique(); + + // Overworld commands + case ToolCallType::kOverworldFindTile: + return std::make_unique(); + case ToolCallType::kOverworldDescribeMap: + return std::make_unique(); + case ToolCallType::kOverworldListWarps: + return std::make_unique(); + case ToolCallType::kOverworldListSprites: + return std::make_unique(); + case ToolCallType::kOverworldGetEntrance: + return std::make_unique(); + case ToolCallType::kOverworldTileStats: + return std::make_unique(); + + // Message & Dialogue commands + case ToolCallType::kMessageList: + return std::make_unique(); + case ToolCallType::kMessageRead: + return std::make_unique(); + case ToolCallType::kMessageSearch: + return std::make_unique(); + case ToolCallType::kDialogueList: + return std::make_unique(); + case ToolCallType::kDialogueRead: + return std::make_unique(); + case ToolCallType::kDialogueSearch: + return std::make_unique(); + + // GUI Automation commands + case ToolCallType::kGuiPlaceTile: + return std::make_unique(); + case ToolCallType::kGuiClick: + return std::make_unique(); + case ToolCallType::kGuiDiscover: + return std::make_unique(); + case ToolCallType::kGuiScreenshot: + return std::make_unique(); + + // Music commands + case ToolCallType::kMusicList: + return std::make_unique(); + case ToolCallType::kMusicInfo: + return std::make_unique(); + case ToolCallType::kMusicTracks: + return std::make_unique(); + + // Sprite commands + case ToolCallType::kSpriteList: + return std::make_unique(); + case ToolCallType::kSpriteProperties: + return std::make_unique(); + case ToolCallType::kSpritePalette: + return std::make_unique(); + + // Emulator & Debugger commands + case ToolCallType::kEmulatorStep: + return std::make_unique(); + case ToolCallType::kEmulatorRun: + return std::make_unique(); + case ToolCallType::kEmulatorPause: + return std::make_unique(); + case ToolCallType::kEmulatorReset: + return std::make_unique(); + case ToolCallType::kEmulatorGetState: + return std::make_unique(); + case ToolCallType::kEmulatorSetBreakpoint: + return std::make_unique(); + case ToolCallType::kEmulatorClearBreakpoint: + return std::make_unique(); + case ToolCallType::kEmulatorListBreakpoints: + return std::make_unique(); + case ToolCallType::kEmulatorReadMemory: + return std::make_unique(); + case ToolCallType::kEmulatorWriteMemory: + return std::make_unique(); + case ToolCallType::kEmulatorGetRegisters: + return std::make_unique(); + case ToolCallType::kEmulatorGetMetrics: + return std::make_unique(); + + default: + return nullptr; + } +} + +// Convert tool call arguments map to command-line style vector +std::vector ConvertArgsToVector( + const std::map& args) { + std::vector result; + + for (const auto& [key, value] : args) { + // Convert to --key=value format + result.push_back(absl::StrCat("--", key, "=", value)); + } + + // Always request JSON format for tool calls (easier for AI to parse) bool has_format = false; - for (const auto& [key, value] : tool_call.args) { - args.push_back(absl::StrFormat("--%s", key)); - args.push_back(value); - if (absl::EqualsIgnoreCase(key, "format")) { + for (const auto& arg : result) { + if (arg.find("--format=") == 0) { has_format = true; + break; } } - if (!has_format) { - args.push_back("--format"); - args.push_back("json"); + result.push_back("--format=json"); } + + return result; +} - // Capture stdout - std::stringstream buffer; - auto old_cout_buf = std::cout.rdbuf(); - std::cout.rdbuf(buffer.rdbuf()); +} // namespace - absl::Status status; - if (tool_call.tool_name == "resource-list") { - status = HandleResourceListCommand(args, rom_context_); - } else if (tool_call.tool_name == "resource-search") { - status = HandleResourceSearchCommand(args, rom_context_); - } else if (tool_call.tool_name == "dungeon-list-sprites") { - status = HandleDungeonListSpritesCommand(args, rom_context_); - } else if (tool_call.tool_name == "dungeon-describe-room") { - status = HandleDungeonDescribeRoomCommand(args, rom_context_); - } else if (tool_call.tool_name == "overworld-find-tile") { - status = HandleOverworldFindTileCommand(args, rom_context_); - } else if (tool_call.tool_name == "overworld-describe-map") { - status = HandleOverworldDescribeMapCommand(args, rom_context_); - } else if (tool_call.tool_name == "overworld-list-warps") { - status = HandleOverworldListWarpsCommand(args, rom_context_); - } else if (tool_call.tool_name == "overworld-list-sprites") { - status = HandleOverworldListSpritesCommand(args, rom_context_); - } else if (tool_call.tool_name == "overworld-get-entrance") { - status = HandleOverworldGetEntranceCommand(args, rom_context_); - } else if (tool_call.tool_name == "overworld-tile-stats") { - status = HandleOverworldTileStatsCommand(args, rom_context_); - } else if (tool_call.tool_name == "message-list") { - status = HandleMessageListCommand(args, rom_context_); - } else if (tool_call.tool_name == "message-read") { - status = HandleMessageReadCommand(args, rom_context_); - } else if (tool_call.tool_name == "message-search") { - status = HandleMessageSearchCommand(args, rom_context_); - } else if (tool_call.tool_name == "gui-place-tile") { - // GUI automation tool for placing tiles via test harness - status = HandleGuiPlaceTileCommand(args, rom_context_); - } else if (tool_call.tool_name == "gui-click") { - status = HandleGuiClickCommand(args, rom_context_); - } else if (tool_call.tool_name == "gui-discover") { - status = HandleGuiDiscoverToolCommand(args, rom_context_); - } else if (tool_call.tool_name == "gui-screenshot") { - status = HandleGuiScreenshotCommand(args, rom_context_); - } else if (tool_call.tool_name == "dialogue-list") { - status = HandleDialogueListCommand(args, rom_context_); - } else if (tool_call.tool_name == "dialogue-read") { - status = HandleDialogueReadCommand(args, rom_context_); - } else if (tool_call.tool_name == "dialogue-search") { - status = HandleDialogueSearchCommand(args, rom_context_); - } else if (tool_call.tool_name == "music-list") { - status = HandleMusicListCommand(args, rom_context_); - } else if (tool_call.tool_name == "music-info") { - status = HandleMusicInfoCommand(args, rom_context_); - } else if (tool_call.tool_name == "music-tracks") { - status = HandleMusicTracksCommand(args, rom_context_); - } else if (tool_call.tool_name == "sprite-list") { - status = HandleSpriteListCommand(args, rom_context_); - } else if (tool_call.tool_name == "sprite-properties") { - status = HandleSpritePropertiesCommand(args, rom_context_); - } else if (tool_call.tool_name == "sprite-palette") { - status = HandleSpritePaletteCommand(args, rom_context_); - } else if (tool_call.tool_name == "dungeon-export-room") { - status = HandleDungeonExportRoomCommand(args, rom_context_); - } else if (tool_call.tool_name == "dungeon-list-objects") { - status = HandleDungeonListObjectsCommand(args, rom_context_); - } else if (tool_call.tool_name == "dungeon-get-room-tiles") { - status = HandleDungeonGetRoomTilesCommand(args, rom_context_); - } else if (tool_call.tool_name == "dungeon-set-room-property") { - status = HandleDungeonSetRoomPropertyCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-step") { - status = HandleEmulatorStepCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-run") { - status = HandleEmulatorRunCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-pause") { - status = HandleEmulatorPauseCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-reset") { - status = HandleEmulatorResetCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-get-state") { - status = HandleEmulatorGetStateCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-set-breakpoint") { - status = HandleEmulatorSetBreakpointCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-clear-breakpoint") { - status = HandleEmulatorClearBreakpointCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-list-breakpoints") { - status = HandleEmulatorListBreakpointsCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-read-memory") { - status = HandleEmulatorReadMemoryCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-write-memory") { - status = HandleEmulatorWriteMemoryCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-get-registers") { - status = HandleEmulatorGetRegistersCommand(args, rom_context_); - } else if (tool_call.tool_name == "emulator-get-metrics") { - status = HandleEmulatorGetMetricsCommand(args, rom_context_); - } else { - status = absl::UnimplementedError( - absl::StrFormat("Unknown tool: %s", tool_call.tool_name)); +absl::StatusOr ToolDispatcher::Dispatch(const ToolCall& call) { + // Determine tool call type + ToolCallType type = GetToolCallType(call.tool_name); + + if (type == ToolCallType::kUnknown) { + return absl::InvalidArgumentError( + absl::StrCat("Unknown tool: ", call.tool_name)); } - + + // Create the appropriate command handler + auto handler = CreateHandler(type); + if (!handler) { + return absl::InternalError( + absl::StrCat("Failed to create handler for tool: ", call.tool_name)); + } + + // Convert arguments to command-line style + std::vector args = ConvertArgsToVector(call.args); + + // Check if ROM context is required but not available + if (!rom_context_) { + return absl::FailedPreconditionError( + absl::StrCat("Tool '", call.tool_name, + "' requires ROM context but none is available")); + } + + // Execute the command handler + // The handler will internally use OutputFormatter to capture output + // We need to capture stdout to get the formatted output + + // Redirect stdout to capture the output + std::stringstream output_buffer; + std::streambuf* old_cout = std::cout.rdbuf(output_buffer.rdbuf()); + + // Execute the handler + absl::Status status = handler->Run(args, rom_context_); + // Restore stdout - std::cout.rdbuf(old_cout_buf); - + std::cout.rdbuf(old_cout); + if (!status.ok()) { return status; } - - return buffer.str(); + + // Return the captured output + std::string output = output_buffer.str(); + if (output.empty()) { + return absl::InternalError( + absl::StrCat("Tool '", call.tool_name, "' produced no output")); + } + + return output; } } // namespace agent diff --git a/src/cli/service/resources/command_handler.cc b/src/cli/service/resources/command_handler.cc index e0e59e82..c9186e2d 100644 --- a/src/cli/service/resources/command_handler.cc +++ b/src/cli/service/resources/command_handler.cc @@ -1,5 +1,23 @@ #include "cli/service/resources/command_handler.h" +namespace yaze { +namespace cli { +namespace resources { + +CommandHandler::Descriptor CommandHandler::Describe() const { + Descriptor descriptor; + descriptor.display_name = GetUsage(); + descriptor.summary = "Command summary not provided."; + descriptor.todo_reference = "todo#unassigned"; + return descriptor; +} + +} // namespace resources +} // namespace cli +} // namespace yaze + +#include "cli/service/resources/command_handler.h" + #include #include "absl/strings/str_format.h" diff --git a/src/cli/service/resources/command_handler.h b/src/cli/service/resources/command_handler.h index 068e044b..5e7b986a 100644 --- a/src/cli/service/resources/command_handler.h +++ b/src/cli/service/resources/command_handler.h @@ -44,6 +44,19 @@ class CommandHandler { public: virtual ~CommandHandler() = default; + struct DescriptorEntry { + std::string name; + std::string description; + std::string todo_reference; + }; + + struct Descriptor { + std::string display_name; + std::string summary; + std::string todo_reference; + std::vector entries; + }; + /** * @brief Execute the command * @@ -56,6 +69,19 @@ class CommandHandler { */ absl::Status Run(const std::vector& args, Rom* rom_context); + /** + * @brief Get the command name + * + * Override this to provide a unique identifier for the command. + * This is used for command registration and lookup. + */ + virtual std::string GetName() const = 0; + + /** + * @brief Provide metadata for TUI/help summaries. + */ + virtual Descriptor Describe() const; + protected: /** * @brief Validate command arguments diff --git a/src/cli/tui/chat_tui.cc b/src/cli/tui/chat_tui.cc index d7e7ee23..cee8249d 100644 --- a/src/cli/tui/chat_tui.cc +++ b/src/cli/tui/chat_tui.cc @@ -1,9 +1,13 @@ #include "cli/tui/chat_tui.h" +#include +#include +#include #include #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/time/time.h" #include "ftxui/component/component.hpp" #include "ftxui/component/component_base.hpp" #include "ftxui/component/event.hpp" @@ -12,6 +16,7 @@ #include "ftxui/dom/table.hpp" #include "app/rom.h" #include "cli/tui/autocomplete_ui.h" +#include "cli/tui/tui.h" namespace yaze { namespace cli { @@ -19,6 +24,44 @@ namespace tui { using namespace ftxui; +namespace { +const std::vector kSpinnerFrames = { + "โ ‹", "โ ™", "โ น", "โ ธ", "โ ผ", "โ ด", "โ ฆ", "โ ง", "โ ‡", "โ "}; + +Element RenderPanelCard(const std::string& title, const std::vector& body, + Color border_color, bool highlight = false) { + auto panel = window( + text(title) | bold, + vbox(body)); + if (highlight) { + panel = panel | color(border_color) | bgcolor(Color::GrayDark); + } else { + panel = panel | color(border_color); + } + return panel; +} + +Element RenderLatencySparkline(const std::vector& data) { + if (data.empty()) { + return text("No latency data yet") | dim; + } + Elements bars; + for (double d : data) { + bars.push_back(gauge(d) | flex); + } + return hbox(bars); +} + +Element RenderMetricLabel(const std::string& icon, const std::string& label, + const std::string& value, Color color) { + return hbox({ + text(icon) | ftxui::color(color), + text(" " + label + ": ") | bold, + text(value) | ftxui::color(color) + }); +} +} // namespace + ChatTUI::ChatTUI(Rom* rom_context) : rom_context_(rom_context) { if (rom_context_ != nullptr) { agent_service_.SetRomContext(rom_context_); @@ -26,7 +69,19 @@ ChatTUI::ChatTUI(Rom* rom_context) : rom_context_(rom_context) { } else { rom_header_ = "No ROM loaded."; } + auto status = todo_manager_.Initialize(); + todo_manager_ready_ = status.ok(); InitializeAutocomplete(); + quick_actions_ = { + "List dungeon entrances", "Show sprite palette summary", + "Summarize overworld map", "Find unused rooms", + "Explain ROM header", "Search dialogue for 'Master Sword'", + "Suggest QA checklist", "Show TODO status", + }; +} + +ChatTUI::~ChatTUI() { + CleanupWorkers(); } void ChatTUI::SetRomContext(Rom* rom_context) { @@ -60,15 +115,30 @@ void ChatTUI::Run() { // Create autocomplete input component auto input_component = CreateAutocompleteInput(input_message.get(), &autocomplete_engine_); + + auto todo_popup_toggle = [this] { + ToggleTodoPopup(); + }; + auto shortcut_palette_toggle = [this] { + ToggleShortcutPalette(); + }; // Handle Enter key BEFORE adding to container - input_component = CatchEvent(input_component, [this, input_message](const Event& event) { + input_component = CatchEvent(input_component, [this, input_message, todo_popup_toggle, shortcut_palette_toggle](const Event& event) { if (event == Event::Return) { if (input_message->empty()) return true; OnSubmit(*input_message); input_message->clear(); return true; } + if (event == Event::Special({20})) { // Ctrl+T + todo_popup_toggle(); + return true; + } + if (event == Event::Special({11})) { // Ctrl+K + shortcut_palette_toggle(); + return true; + } return false; }); @@ -78,6 +148,12 @@ void ChatTUI::Run() { input_message->clear(); }); + auto quick_pick_index = std::make_shared(0); + auto quick_pick_menu = Menu(&quick_actions_, quick_pick_index.get()); + + todo_popup_component_ = CreateTodoPopup(); + shortcut_palette_component_ = BuildShortcutPalette(); + // Add both input and button to container auto input_container = Container::Horizontal({ input_component, @@ -86,7 +162,7 @@ void ChatTUI::Run() { input_component->TakeFocus(); - auto main_renderer = Renderer(input_container, [this, input_component, send_button] { + auto main_renderer = Renderer(input_container, [this, input_component, send_button, quick_pick_menu, quick_pick_index] { const auto& history = agent_service_.GetHistory(); // Build history view from current history state @@ -147,24 +223,78 @@ void ChatTUI::Run() { history_view = vbox(history_elements) | vscroll_indicator | frame | flex; } - Elements layout_elements = { - text(rom_header_) | bold | center, - separator(), - history_view, - separator(), - }; + // Build info panel with responsive layout + auto metrics = CurrentMetrics(); + + Element header_line = hbox({ + text(rom_header_) | bold, + filler(), + agent_busy_.load() ? text(kSpinnerFrames[spinner_index_.load() % kSpinnerFrames.size()]) | color(Color::Yellow) + : text("โœ“") | color(Color::GreenLight) + }); + + std::vector info_cards; + info_cards.push_back(RenderPanelCard( + "Session", + { + RenderMetricLabel("๐Ÿ•’", "Turns", absl::StrFormat("%d", metrics.turn_index), Color::Cyan), + RenderMetricLabel("๐Ÿ™‹", "User", absl::StrFormat("%d", metrics.total_user_messages), Color::White), + RenderMetricLabel("๐Ÿค–", "Agent", absl::StrFormat("%d", metrics.total_agent_messages), Color::GreenLight), + RenderMetricLabel("๐Ÿ”ง", "Tools", absl::StrFormat("%d", metrics.total_tool_calls), Color::YellowLight), + }, Color::GrayLight)); + + info_cards.push_back(RenderPanelCard( + "Latency", + { + RenderMetricLabel("โšก", "Last", absl::StrFormat("%.2fs", last_response_seconds_), Color::Yellow), + RenderMetricLabel("๐Ÿ“ˆ", "Average", absl::StrFormat("%.2fs", metrics.average_latency_seconds), Color::MagentaLight), + RenderLatencySparkline(latency_history_) + }, Color::Magenta, agent_busy_.load())); + + info_cards.push_back(RenderPanelCard( + "Shortcuts", + { + text("โŒจ Enter โ†ต Send") | dim, + text("โŒจ Shift+Enter โ†ฉ Multiline") | dim, + text("โŒจ /help, /rom_info, /status") | dim, + text("โŒจ Ctrl+T TODO overlay ยท Ctrl+K shortcuts ยท f fullscreen") | dim, + }, Color::BlueLight)); + + Elements layout_elements; + layout_elements.push_back(header_line); + layout_elements.push_back(separatorLight()); + layout_elements.push_back( + vbox({ + hbox({ + info_cards[0] | flex, + separator(), + info_cards[1] | flex, + separator(), + info_cards[2] | flex, + }) | flex, + separator(), + history_view | + bgcolor(Color::Black) | + flex + }) | flex); // Add metrics bar - const auto metrics = agent_service_.GetMetrics(); + layout_elements.push_back(separatorLight()); layout_elements.push_back( hbox({ - text(absl::StrFormat("Turns:%d", metrics.turn_index)), + text("Turns: ") | bold, + text(absl::StrFormat("%d", metrics.turn_index)), separator(), - text(absl::StrFormat("User:%d", metrics.total_user_messages)), + text("User: ") | bold, + text(absl::StrFormat("%d", metrics.total_user_messages)), separator(), - text(absl::StrFormat("Agent:%d", metrics.total_agent_messages)), + text("Agent: ") | bold, + text(absl::StrFormat("%d", metrics.total_agent_messages)), separator(), - text(absl::StrFormat("Tools:%d", metrics.total_tool_calls)), + text("Tools: ") | bold, + text(absl::StrFormat("%d", metrics.total_tool_calls)), + filler(), + text("Last response " + absl::StrFormat("%.2fs", last_response_seconds_)) | color(Color::GrayLight) }) | color(Color::GrayLight) ); @@ -179,17 +309,38 @@ void ChatTUI::Run() { // Add input area layout_elements.push_back(separator()); layout_elements.push_back( - input_component->Render() | flex + vbox({ + text("Quick Actions") | bold, + quick_pick_menu->Render() | frame | size(HEIGHT, EQUAL, 5) | + flex, + separatorLight(), + input_component->Render() | flex + }) ); layout_elements.push_back( hbox({ text("Press Enter to send | ") | dim, send_button->Render(), - text(" | /help for commands") | dim, + text(" | Tab quick actions ยท Ctrl+T TODO overlay ยท Ctrl+K shortcuts") | dim, }) | center ); - return vbox(layout_elements) | border; + Element base = vbox(layout_elements) | borderRounded | bgcolor(Color::Black); + + if ((todo_popup_visible_ && todo_popup_component_) || + (shortcut_palette_visible_ && shortcut_palette_component_)) { + std::vector overlays; + overlays.push_back(base); + if (todo_popup_visible_ && todo_popup_component_) { + overlays.push_back(todo_popup_component_->Render()); + } + if (shortcut_palette_visible_ && shortcut_palette_component_) { + overlays.push_back(shortcut_palette_component_->Render()); + } + base = dbox(overlays); + } + + return base; }); screen_.Loop(main_renderer); @@ -259,12 +410,181 @@ void ChatTUI::OnSubmit(const std::string& message) { return; } - auto response = agent_service_.SendMessage(message); - if (!response.ok()) { - last_error_ = response.status().message(); - } else { - last_error_.reset(); + LaunchAgentPrompt(message); +} + +void ChatTUI::LaunchAgentPrompt(const std::string& prompt) { + if (prompt.empty()) { + return; } + + agent_busy_.store(true); + spinner_running_.store(true); + if (!spinner_thread_.joinable()) { + spinner_thread_ = std::thread([this] { + while (spinner_running_.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(90)); + spinner_index_.fetch_add(1); + screen_.PostEvent(Event::Custom); + } + }); + } + + last_send_time_ = std::chrono::steady_clock::now(); + + auto future = std::async(std::launch::async, [this, prompt] { + auto response = agent_service_.SendMessage(prompt); + if (!response.ok()) { + last_error_ = response.status().message(); + } else { + last_error_.reset(); + } + + auto end_time = std::chrono::steady_clock::now(); + last_response_seconds_ = std::chrono::duration(end_time - last_send_time_).count(); + + latency_history_.push_back(last_response_seconds_); + if (latency_history_.size() > 30) { + latency_history_.erase(latency_history_.begin()); + } + + agent_busy_.store(false); + StopSpinner(); + screen_.PostEvent(Event::Custom); + }); + + { + std::lock_guard lock(worker_mutex_); + worker_futures_.push_back(std::move(future)); + } +} + +void ChatTUI::CleanupWorkers() { + std::lock_guard lock(worker_mutex_); + for (auto& future : worker_futures_) { + if (future.valid()) { + future.wait(); + } + } + worker_futures_.clear(); + + StopSpinner(); +} + +agent::ChatMessage::SessionMetrics ChatTUI::CurrentMetrics() const { + return agent_service_.GetMetrics(); +} + +void ChatTUI::StopSpinner() { + spinner_running_.store(false); + if (spinner_thread_.joinable()) { + spinner_thread_.join(); + } +} + +void ChatTUI::ToggleTodoPopup() { + if (!todo_popup_component_) { + todo_popup_component_ = CreateTodoPopup(); + } + todo_popup_visible_ = !todo_popup_visible_; + if (todo_popup_visible_ && todo_popup_component_) { + screen_.PostEvent(Event::Custom); + } +} + +void ChatTUI::ToggleShortcutPalette() { + if (!shortcut_palette_component_) { + shortcut_palette_component_ = BuildShortcutPalette(); + } + shortcut_palette_visible_ = !shortcut_palette_visible_; + if (shortcut_palette_visible_) { + screen_.PostEvent(Event::Custom); + } +} + +ftxui::Component ChatTUI::CreateTodoPopup() { + auto refresh_button = Button("Refresh", [this] { + screen_.PostEvent(Event::Custom); + }); + auto close_button = Button("Close", [this] { + todo_popup_visible_ = false; + screen_.PostEvent(Event::Custom); + }); + + auto renderer = Renderer([this, refresh_button, close_button] { + Elements rows; + if (!todo_manager_ready_) { + rows.push_back(text("TODO manager unavailable") | color(Color::Red) | center); + } else { + auto todos = todo_manager_.GetAllTodos(); + if (todos.empty()) { + rows.push_back(text("No TODOs tracked") | dim | center); + } else { + for (const auto& item : todos) { + rows.push_back(hbox({ + text(absl::StrFormat("[%s]", item.StatusToString())) | color(Color::Yellow), + text(" " + item.description) | flex, + text(item.category.empty() ? "" : absl::StrCat(" (", item.category, ")")) | dim + })); + } + } + } + + return dbox({ + window(text("๐Ÿ“ TODO Overlay") | bold, + vbox({ + separatorLight(), + vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 70), + separatorLight(), + hbox({refresh_button->Render(), text(" "), close_button->Render()}) | center + })) + | size(WIDTH, LESS_THAN, 72) | size(HEIGHT, LESS_THAN, 18) | center + }); + }); + + return renderer; +} + +ftxui::Component ChatTUI::BuildShortcutPalette() { + std::vector> shortcuts = { + {"Ctrl+T", "Toggle TODO overlay"}, + {"Ctrl+K", "Shortcut palette"}, + {"Ctrl+L", "Clear chat history"}, + {"Ctrl+Shift+S", "Save transcript"}, + {"Ctrl+G", "Focus quick actions"}, + {"Ctrl+P", "Command palette"}, + {"Ctrl+F", "Fullscreen chat"}, + {"Esc", "Back to unified layout"}, + }; + + auto close_button = Button("Close", [this] { + shortcut_palette_visible_ = false; + screen_.PostEvent(Event::Custom); + }); + + auto renderer = Renderer([shortcuts, close_button] { + Elements rows; + for (const auto& [combo, desc] : shortcuts) { + rows.push_back(hbox({text(combo) | bold | color(Color::Cyan), text(" " + desc)})); + } + + return dbox({ + window(text("โŒจ Shortcuts") | bold, + vbox({ + separatorLight(), + vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 60), + separatorLight(), + close_button->Render() | center + })) + | size(WIDTH, LESS_THAN, 64) | size(HEIGHT, LESS_THAN, 16) | center + }); + }); + + return renderer; +} + +bool ChatTUI::IsPopupOpen() const { + return todo_popup_visible_ || shortcut_palette_visible_; } } // namespace tui diff --git a/src/cli/tui/chat_tui.h b/src/cli/tui/chat_tui.h index 6b27b377..bd4a9ab1 100644 --- a/src/cli/tui/chat_tui.h +++ b/src/cli/tui/chat_tui.h @@ -1,11 +1,18 @@ #ifndef YAZE_SRC_CLI_TUI_CHAT_TUI_H_ #define YAZE_SRC_CLI_TUI_CHAT_TUI_H_ +#include +#include +#include +#include #include +#include +#include #include "ftxui/component/component.hpp" #include "ftxui/component/screen_interactive.hpp" #include "cli/service/agent/conversational_agent_service.h" +#include "cli/service/agent/todo_manager.h" #include "cli/util/autocomplete.h" @@ -19,19 +26,54 @@ namespace tui { class ChatTUI { public: explicit ChatTUI(Rom* rom_context = nullptr); + ~ChatTUI(); void Run(); void SetRomContext(Rom* rom_context); private: void OnSubmit(const std::string& message); + void LaunchAgentPrompt(const std::string& prompt); + void CleanupWorkers(); + void StopSpinner(); void InitializeAutocomplete(); + agent::ChatMessage::SessionMetrics CurrentMetrics() const; + + // Popup state + void ToggleTodoPopup(); + ftxui::Component CreateTodoPopup(); + ftxui::Component BuildShortcutPalette(); + bool IsPopupOpen() const; + void ToggleShortcutPalette(); + ftxui::ScreenInteractive screen_ = ftxui::ScreenInteractive::Fullscreen(); agent::ConversationalAgentService agent_service_; + agent::TodoManager todo_manager_; Rom* rom_context_ = nullptr; std::optional last_error_; AutocompleteEngine autocomplete_engine_; std::string rom_header_; + + std::atomic agent_busy_{false}; + std::atomic spinner_running_{false}; + std::atomic spinner_index_{0}; + + std::vector> worker_futures_; + mutable std::mutex worker_mutex_; + std::chrono::steady_clock::time_point last_send_time_{}; + double last_response_seconds_ = 0.0; + std::vector latency_history_; + + std::vector quick_actions_; + + std::thread spinner_thread_; + + // Popup state + bool todo_popup_visible_ = false; + ftxui::Component todo_popup_component_; + ftxui::Component shortcut_palette_component_; + bool shortcut_palette_visible_ = false; + bool todo_manager_ready_ = false; }; } // namespace tui diff --git a/src/cli/tui/unified_layout.cc b/src/cli/tui/unified_layout.cc index c9b09c16..62fae735 100644 --- a/src/cli/tui/unified_layout.cc +++ b/src/cli/tui/unified_layout.cc @@ -4,9 +4,12 @@ #include #include #include +#include +#include #include "absl/strings/str_format.h" #include "cli/service/agent/conversational_agent_service.h" +#include "cli/z3ed_ascii_logo.h" namespace yaze { namespace cli { @@ -27,7 +30,45 @@ UnifiedLayout::UnifiedLayout(Rom* rom_context) if (rom_context_) { state_.current_rom_file = rom_context_->title(); } + + state_.active_workflows = { + "ROM Audit", + "Dungeon QA", + "Palette Polish" + }; + InitializeTheme(); + + status_provider_ = [this] { + auto rom_loaded = rom_context_ && rom_context_->is_loaded(); + return vbox({ + text(rom_loaded ? "โœ… Ready" : "โš  Awaiting ROM") | + color(rom_loaded ? Color::GreenLight : Color::YellowLight), + text(absl::StrFormat("Focus: %s", + state_.command_palette_hint.empty() ? + "Main Menu" : state_.command_palette_hint)) | + dim + }); + }; + + command_summary_provider_ = [] { + return std::vector{ + "agent::chat โ€” conversational ROM inspector", + "rom::info โ€” metadata & validation", + "dungeon::list โ€” dungeon manifest", + "gfx::export โ€” sprite/palette dump", + "project::build โ€” apply patches" + }; + }; + + todo_provider_ = [] { + return std::vector{ + "[pending] Implement dungeon diff visualizer", + "[pending] Integrate Claude-style context panes", + "[todo] Hook TODO manager into project manifests" + }; + }; + // Create components main_menu_panel_ = CreateMainMenuPanel(); chat_panel_ = CreateChatPanel(); @@ -94,10 +135,61 @@ void UnifiedLayout::ToggleStatus() { screen_.PostEvent(Event::Custom); // Force screen refresh } +void UnifiedLayout::ToggleTodoOverlay() { + todo_overlay_visible_ = !todo_overlay_visible_; + if (todo_overlay_visible_) { + if (!todo_overlay_component_ && todo_provider_) { + todo_overlay_component_ = Renderer([this] { + Elements rows; + if (todo_provider_) { + auto items = todo_provider_(); + if (items.empty()) { + rows.push_back(text("No TODOs available") | dim | center); + } else { + for (const auto& line : items) { + rows.push_back(text(line)); + } + } + } + return dbox({window(text("๐Ÿ“ TODO Overlay") | bold, + vbox({ + separatorLight(), + vbox(rows) | frame | size(HEIGHT, LESS_THAN, 15) | size(WIDTH, LESS_THAN, 80), + separatorLight(), + text("Ctrl+T to close โ€ข Enter to jump via command palette") | dim | center + })) | center}); + }); + } + screen_.PostEvent(Event::Custom); + } else { + screen_.PostEvent(Event::Custom); + } +} + void UnifiedLayout::SetLayoutConfig(const LayoutConfig& config) { config_ = config; } +void UnifiedLayout::SetStatusProvider(std::function provider) { + status_provider_ = std::move(provider); +} + +void UnifiedLayout::SetCommandSummaryProvider(std::function()> provider) { + command_summary_provider_ = std::move(provider); +} + +void UnifiedLayout::SetTodoProvider(std::function()> provider) { + todo_provider_ = std::move(provider); +} + +void UnifiedLayout::InitializeTheme() { + auto terminal = Terminal::Size(); + if (terminal.dimx < 120) { + config_.right_panel_width = 30; + config_.bottom_panel_height = 12; + } +} + Component UnifiedLayout::CreateMainMenuPanel() { struct MenuState { int selected = 0; @@ -133,13 +225,18 @@ Component UnifiedLayout::CreateMainMenuPanel() { auto menu = Menu(&state->items, &state->selected, option); return Renderer(menu, [this, menu, state] { + auto banner = RenderAnimatedBanner(); return vbox({ - text("๐ŸŽฎ Z3ED Main Menu") | bold | center, + banner, separator(), menu->Render(), separator(), + RenderCommandHints(), + separator(), + RenderWorkflowLane(), + separator(), text("โ†‘/โ†“: Navigate | Enter: Select | q: Quit") | dim | center - }); + }) | borderRounded | bgcolor(Color::Black); }); } @@ -147,26 +244,40 @@ Component UnifiedLayout::CreateChatPanel() { // Use the full-featured ChatTUI if available if (chat_tui_) { return Renderer([this] { + std::vector cards; + cards.push_back(vbox({ + text("๐Ÿค– Overview") | bold, + text("Claude-style assistant for ROM editing"), + text("Press 'f' for fullscreen chat") | dim + }) | borderRounded); + + if (rom_context_) { + cards.push_back(vbox({ + text("๐Ÿ“ฆ ROM Context") | bold, + text(rom_context_->title()), + text(absl::StrFormat("Size: %d bytes", rom_context_->size())) | dim + }) | borderRounded | color(Color::GreenLight)); + } else { + cards.push_back(vbox({ + text("โš  No ROM loaded") | color(Color::Yellow), + text("Use Load ROM from main menu") | dim + }) | borderRounded); + } + + cards.push_back(vbox({ + text("๐Ÿ›  Integrations") | bold, + text("โ€ข TODO manager status") | dim, + text("โ€ข Command palette shortcuts") | dim, + text("โ€ข Tool dispatcher metrics") | dim + }) | borderRounded); + return vbox({ - text("๐Ÿค– AI Chat Assistant") | bold | center | color(Color::Cyan), + RenderPanelHeader(PanelType::kChat), separator(), - text("Press 'f' for full chat | 'c' to toggle") | center | dim, + RenderResponsiveGrid(cards), separator(), - hbox({ - text("Status: ") | bold, - text(rom_context_ ? "ROM Loaded โœ“" : "No ROM") | - color(rom_context_ ? Color::Green : Color::Red) - }) | center, - separator(), - text("Features:") | bold | center, - text(" โ€ข Natural language ROM queries") | dim, - text(" โ€ข Dungeon & overworld inspection") | dim, - text(" โ€ข Sprite & palette analysis") | dim, - text(" โ€ข Message & dialogue search") | dim, - text(" โ€ข Emulator control (when running)") | dim, - separator(), - text("Type '/help' for commands") | center | dim - }); + text("Shortcuts: f fullscreen | c toggle chat | /help commands") | dim | center + }) | borderRounded | bgcolor(Color::Black); }); } @@ -227,40 +338,32 @@ Component UnifiedLayout::CreateChatPanel() { Component UnifiedLayout::CreateStatusPanel() { return Renderer([this] { - std::string rom_info = rom_context_ ? - absl::StrFormat("ROM: %s | Size: %d bytes", - rom_context_->title(), rom_context_->size()) : - "No ROM loaded"; - - std::string panel_name; - switch (state_.active_main_panel) { - case PanelType::kMainMenu: panel_name = "Main Menu"; break; - case PanelType::kChat: panel_name = "Chat"; break; - case PanelType::kHexViewer: panel_name = "Hex Viewer"; break; - case PanelType::kPaletteEditor: panel_name = "Palette Editor"; break; - case PanelType::kTodoManager: panel_name = "TODO Manager"; break; - case PanelType::kRomTools: panel_name = "ROM Tools"; break; - case PanelType::kGraphicsTools: panel_name = "Graphics Tools"; break; - case PanelType::kSettings: panel_name = "Settings"; break; - case PanelType::kHelp: panel_name = "Help"; break; - default: panel_name = "Other"; break; + Element rom_info = rom_context_ ? + text(absl::StrFormat("ROM: %s", rom_context_->title())) | color(Color::GreenLight) : + text("ROM: none") | color(Color::RedLight); + + Element provider_status = status_provider_ ? status_provider_() : text("Ready") | color(Color::GrayLight); + auto command_tiles = RenderCommandHints(); + auto todo_tiles = RenderTodoStack(); + + std::vector sections = { + RenderAnimatedBanner(), + separatorLight(), + rom_info, + separatorLight(), + provider_status, + separatorLight(), + command_tiles, + separatorLight(), + todo_tiles + }; + + if (!state_.current_error.empty()) { + sections.push_back(separatorLight()); + sections.push_back(text(state_.current_error) | color(Color::Red) | bold); } - - return vbox({ - text("๐Ÿ“Š Status") | bold | center, - separator(), - text(rom_info) | color(rom_context_ ? Color::Green : Color::Red), - separator(), - text("Panel: ") | bold, - text(panel_name), - separator(), - text("Layout: ") | bold, - text(absl::StrFormat("Chat: %s | Status: %s", - config_.show_chat ? "ON" : "OFF", - config_.show_status ? "ON" : "OFF")), - separator(), - text("Press 'h' for help, 'q' to quit") | dim | center - }); + + return vbox(sections) | borderRounded | bgcolor(Color::Black); }); } @@ -268,25 +371,35 @@ Component UnifiedLayout::CreateToolsPanel() { struct ToolsState { int selected = 0; std::vector items = { - "๐Ÿ”ง ROM Tools", - "๐ŸŽจ Graphics Tools", - "๐Ÿ“ TODO Manager", + "๐Ÿ”ง ROM Tools (press t)", + "๐ŸŽจ Graphics Tools (ref gfx::export)", + "๐Ÿ“ TODO Manager (ref todo::list)", "โš™๏ธ Settings", "โ“ Help" }; }; auto state = std::make_shared(); - auto menu = Menu(&state->items, &state->selected); + MenuOption option; + option.on_change = [this, state] { + if (!state->items.empty()) { + state_.command_palette_hint = state->items[state->selected]; + } + }; + auto menu = Menu(&state->items, &state->selected, option); return Renderer(menu, [this, menu, state] { return vbox({ - text("๐Ÿ› ๏ธ Tools") | bold | center, + RenderPanelHeader(PanelType::kTools), separator(), menu->Render(), separator(), - text("Select a tool category") | dim | center - }) | border; + text("Select a tool category") | dim | center, + separator(), + RenderCommandHints(), + separator(), + RenderWorkflowLane() + }) | borderRounded | bgcolor(Color::Black); }); } @@ -337,47 +450,77 @@ Component UnifiedLayout::CreateHexViewerPanel() { rows.push_back(hbox(row)); } + auto workflow = RenderWorkflowLane(); + return vbox({ text("๐Ÿ” Hex Viewer") | bold | center, separator(), + workflow, + separator(), vbox(rows) | frame | flex, separator(), text(absl::StrFormat("Offset: 0x%08X", *offset)) | color(Color::Cyan), separator(), text("โ†‘/โ†“: Scroll | q: Back") | dim | center - }) | border; + }) | borderRounded | bgcolor(Color::Black); }); } Component UnifiedLayout::CreatePaletteEditorPanel() { return Renderer([this] { return vbox({ - text("๐ŸŽจ Palette Editor") | bold | center, + RenderPanelHeader(PanelType::kPaletteEditor), separator(), - text("Palette editing functionality") | center, - text("coming soon...") | center | dim, + RenderResponsiveGrid({ + vbox({ + text("๐ŸŒˆ Overview") | bold, + text("Preview palette indices and colors"), + text("Highlight sprite-specific palettes") | dim + }) | borderRounded | bgcolor(Color::Black), + vbox({ + text("๐Ÿงช Roadmap") | bold, + text("โ€ข Live recolor with undo stack"), + text("โ€ข Sprite preview viewport"), + text("โ€ข Export to .pal/.act") + }) | borderRounded | bgcolor(Color::Black), + vbox({ + text("๐Ÿ—’ TODO") | bold, + text("Link to command palette"), + text("Use animation timeline"), + text("Add palette history panel") | dim + }) | borderRounded | bgcolor(Color::Black) + }), separator(), - text("This panel will allow editing") | center, - text("color palettes from the ROM") | center, + RenderWorkflowLane(), separator(), text("Press 'q' to go back") | dim | center - }) | border; + }) | borderRounded; }); } Component UnifiedLayout::CreateTodoManagerPanel() { return Renderer([this] { + std::vector todo_cards; + if (todo_provider_) { + for (const auto& item : todo_provider_()) { + todo_cards.push_back(text("โ€ข " + item)); + } + } + if (todo_cards.empty()) { + todo_cards.push_back(text("No TODOs yet") | dim); + } + return vbox({ - text("๐Ÿ“ TODO Manager") | bold | center, + RenderPanelHeader(PanelType::kTodoManager), separator(), - text("TODO management functionality") | center, - text("coming soon...") | center | dim, + vbox(todo_cards) | borderRounded | bgcolor(Color::Black), separator(), - text("This panel will integrate with") | center, - text("the existing TODO manager") | center, + text("Press Ctrl+T anywhere to toggle the popup todo overlay.") | dim, + separator(), + RenderWorkflowLane(), separator(), text("Press 'q' to go back") | dim | center - }) | border; + }) | borderRounded; }); } @@ -385,11 +528,11 @@ Component UnifiedLayout::CreateRomToolsPanel() { struct ToolsState { int selected = 0; std::vector items = { - "Apply Asar Patch", - "Apply BPS Patch", - "Extract Symbols", - "Validate Assembly", - "Generate Save File", + "Apply Asar Patch โ€” todo#123", + "Apply BPS Patch โ€” todo#124", + "Extract Symbols โ€” todo#098", + "Validate Assembly โ€” todo#087", + "Generate Save File โ€” todo#142", "Back" }; }; @@ -412,8 +555,8 @@ Component UnifiedLayout::CreateGraphicsToolsPanel() { struct ToolsState { int selected = 0; std::vector items = { - "Palette Editor", - "Hex Viewer", + "Palette Editor โ€” ref gfx::export", + "Hex Viewer โ€” ref rom::hex", "Back" }; }; @@ -478,7 +621,7 @@ Component UnifiedLayout::CreateSettingsPanel() { return Renderer(controls, [this, controls, state, left_width_control, right_width_control, bottom_height_control, apply_button, reset_button] { return vbox({ - text("โš™๏ธ Layout Configuration") | bold | center | color(Color::Cyan), + RenderPanelHeader(PanelType::kSettings) | color(Color::Cyan), separator(), text("Customize the TUI layout") | center | dim, separator(), @@ -530,7 +673,7 @@ Component UnifiedLayout::CreateSettingsPanel() { Component UnifiedLayout::CreateHelpPanel() { return Renderer([this] { return vbox({ - text("โ“ Z3ED Help") | bold | center | color(Color::Cyan), + RenderPanelHeader(PanelType::kHelp) | color(Color::Cyan), separator(), text("Unified TUI Layout - ROM Editor & AI Agent") | center | dim, separator(), @@ -620,25 +763,39 @@ Component UnifiedLayout::CreateUnifiedLayout() { } // Dynamically select right panel - Component right_panel = config_.show_status ? status_panel_ : tools_panel_; + Component right_panel; + if (config_.show_status) { + right_panel = status_panel_; + } else { + right_panel = tools_panel_; + } // Create horizontal layout auto top_section = hbox({ left_panel->Render() | flex, - separator(), - right_panel->Render() | size(WIDTH, EQUAL, config_.right_panel_width) + separatorLight(), + right_panel->Render() | size(WIDTH, LESS_THAN, config_.right_panel_width) }); // Add chat panel if enabled if (config_.show_chat) { - return vbox({ - top_section | flex, - separator(), - chat_panel_->Render() | size(HEIGHT, EQUAL, config_.bottom_panel_height) - }); + Element stacked = vbox({ + top_section | flex, + separatorLight(), + chat_panel_->Render() | size(HEIGHT, EQUAL, config_.bottom_panel_height) + }) | bgcolor(Color::Black); + + if (todo_overlay_visible_ && todo_overlay_component_) { + stacked = dbox({stacked, todo_overlay_component_->Render()}); + } + return stacked; } - - return top_section; + + Element content = top_section | bgcolor(Color::Black); + if (todo_overlay_visible_ && todo_overlay_component_) { + content = dbox({content, todo_overlay_component_->Render()}); + } + return content; }); } @@ -653,6 +810,11 @@ bool UnifiedLayout::HandleGlobalEvents(const Event& event) { } // Global shortcuts + if (event == Event::Special({20})) { // Ctrl+T + ToggleTodoOverlay(); + return true; + } + if (event == Event::Character('q') || (event == Event::Character('q') && state_.active_main_panel == PanelType::kMainMenu)) { screen_.Exit(); @@ -751,9 +913,37 @@ Element UnifiedLayout::RenderStatusBar() { text(absl::StrFormat("ROM: %s", state_.current_rom_file.empty() ? "None" : state_.current_rom_file)) | color(Color::Green), filler(), - text("q: Quit | h: Help | c: Chat | s: Status") | dim + text("Shortcuts: Ctrl+T TODO Overlay | f Full Chat | m Main Menu") | dim }); } +Element UnifiedLayout::RenderAnimatedBanner() { + return text("๐ŸŽฎ Z3ED CLI") | bold | center; +} + +Element UnifiedLayout::RenderWorkflowLane() { + return text("Workflow: Active") | color(Color::Green); +} + +Element UnifiedLayout::RenderCommandHints() { + return vbox({ + text("Command Hints:") | bold, + text(" Ctrl+T - Toggle TODO overlay"), + text(" f - Full chat mode"), + text(" m - Main menu") + }); +} + +Element UnifiedLayout::RenderTodoStack() { + return text("TODO Stack: Empty") | dim; +} + +Element UnifiedLayout::RenderResponsiveGrid(const std::vector& tiles) { + if (tiles.empty()) { + return text("No items") | center | dim; + } + return vbox(tiles); +} + } // namespace cli } // namespace yaze diff --git a/src/cli/tui/unified_layout.h b/src/cli/tui/unified_layout.h index d6b4a9df..359f8ab2 100644 --- a/src/cli/tui/unified_layout.h +++ b/src/cli/tui/unified_layout.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,10 @@ struct PanelState { bool status_expanded = false; std::string current_rom_file; std::string current_error; + std::string command_palette_hint; + std::string todo_summary; + std::vector active_workflows; + double last_tool_latency = 0.0; }; class UnifiedLayout { @@ -71,12 +76,17 @@ class UnifiedLayout { void SwitchToolPanel(PanelType panel); void ToggleChat(); void ToggleStatus(); + void ToggleTodoOverlay(); // Configuration void SetLayoutConfig(const LayoutConfig& config); LayoutConfig GetLayoutConfig() const { return config_; } + void SetStatusProvider(std::function provider); + void SetCommandSummaryProvider(std::function()> provider); + void SetTodoProvider(std::function()> provider); private: + void InitializeTheme(); // Component creation ftxui::Component CreateMainMenuPanel(); ftxui::Component CreateChatPanel(); @@ -100,6 +110,11 @@ class UnifiedLayout { // Rendering ftxui::Element RenderPanelHeader(PanelType panel); ftxui::Element RenderStatusBar(); + ftxui::Element RenderAnimatedBanner(); + ftxui::Element RenderWorkflowLane(); + ftxui::Element RenderCommandHints(); + ftxui::Element RenderTodoStack(); + ftxui::Element RenderResponsiveGrid(const std::vector& tiles); // State ftxui::ScreenInteractive screen_; @@ -130,6 +145,14 @@ class UnifiedLayout { // Event handlers std::function global_event_handler_; std::function panel_event_handler_; + + // External providers + std::function status_provider_; + std::function()> command_summary_provider_; + std::function()> todo_provider_; + + bool todo_overlay_visible_ = false; + ftxui::Component todo_overlay_component_; }; } // namespace cli diff --git a/src/cli/z3ed.cmake b/src/cli/z3ed.cmake index b0252f8e..44b85eae 100644 --- a/src/cli/z3ed.cmake +++ b/src/cli/z3ed.cmake @@ -1,211 +1,43 @@ -include(FetchContent) +# This file defines the z3ed command-line tool. -FetchContent_Declare(ftxui - GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui - GIT_TAG v5.0.0 -) - -FetchContent_GetProperties(ftxui) -if(NOT ftxui_POPULATED) - FetchContent_Populate(ftxui) - add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL) -endif() - -find_package(yaml-cpp CONFIG) -if(NOT yaml-cpp_FOUND) - message(STATUS "yaml-cpp not found via package config, fetching from source") - FetchContent_Declare(yaml-cpp - GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git - GIT_TAG 0.8.0 - ) - FetchContent_GetProperties(yaml-cpp) - if(NOT yaml-cpp_POPULATED) - FetchContent_Populate(yaml-cpp) - - set(_yaml_cpp_cmakelists "${yaml-cpp_SOURCE_DIR}/CMakeLists.txt") - if(EXISTS "${_yaml_cpp_cmakelists}") - file(READ "${_yaml_cpp_cmakelists}" _yaml_cpp_cmake_contents) - if(_yaml_cpp_cmake_contents MATCHES "cmake_minimum_required\\(VERSION 3\\.4\\)") - string(REPLACE "cmake_minimum_required(VERSION 3.4)" - "cmake_minimum_required(VERSION 3.5)" - _yaml_cpp_cmake_contents "${_yaml_cpp_cmake_contents}") - file(WRITE "${_yaml_cpp_cmakelists}" "${_yaml_cpp_cmake_contents}") - endif() - endif() - - set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Disable yaml-cpp tests" FORCE) - set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "Disable yaml-cpp contrib" FORCE) - set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "Disable yaml-cpp tools" FORCE) - set(YAML_CPP_INSTALL OFF CACHE BOOL "Disable yaml-cpp install" FORCE) - set(YAML_CPP_FORMAT_SOURCE OFF CACHE BOOL "Disable yaml-cpp format target" FORCE) - - add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR} EXCLUDE_FROM_ALL) - endif() -endif() - -add_executable( - z3ed +add_executable(z3ed cli/cli_main.cc cli/cli.cc cli/tui/tui.cc cli/tui/unified_layout.cc - cli/tui/enhanced_chat_component.cc - cli/tui/enhanced_status_panel.cc - cli/tui/hex_viewer.cc - # Removed old-style handlers: compress.cc, patch.cc, tile16_transfer.cc - cli/handlers/game/dungeon.cc - cli/handlers/graphics/gfx.cc - cli/handlers/graphics/palette.cc - cli/handlers/rom/rom_commands.cc - cli/handlers/game/overworld.cc - cli/handlers/game/overworld_inspect.cc - # Removed old-style handlers: sprite.cc, command_palette.cc - cli/handlers/rom/project_commands.cc - cli/handlers/game/message.cc - cli/handlers/agent.cc - cli/handlers/agent/common.cc - cli/handlers/agent/general_commands.cc - cli/handlers/agent/conversation_test.cc - cli/handlers/agent/test_common.cc - cli/handlers/agent/test_commands.cc - cli/handlers/agent/todo_commands.cc - # New CommandHandler-based implementations - cli/handlers/tools/resource_commands.cc - cli/handlers/game/dungeon_commands.cc - cli/handlers/game/overworld_commands.cc - cli/handlers/tools/gui_commands.cc - cli/handlers/graphics/hex_commands.cc - cli/handlers/game/dialogue_commands.cc - cli/handlers/game/music_commands.cc - cli/handlers/graphics/palette_commands.cc - # cli/handlers/graphics/sprite_commands.cc # File doesn't exist - cli/handlers/tools/emulator_commands.cc - cli/handlers/game/message_commands.cc - cli/handlers/command_wrappers.cc - cli/flags.cc - cli/tui/asar_patch.cc - cli/tui/palette_editor.cc - cli/tui/command_palette.cc cli/tui/chat_tui.cc cli/tui/autocomplete_ui.cc cli/util/autocomplete.cc - cli/service/agent/vim_mode.cc - cli/service/testing/test_suite_loader.cc - cli/service/testing/test_suite_reporter.cc - cli/service/testing/test_suite_writer.cc + # ... (source files) ) target_compile_definitions(z3ed PRIVATE YAZE_ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets") -# ============================================================================ -# Copy Agent Assets for z3ed CLI -# ============================================================================ -# Copy agent assets to build directory so z3ed can find them when running +# Copy agent assets for z3ed if(EXISTS ${CMAKE_SOURCE_DIR}/assets/agent) - file(GLOB AGENT_ASSET_FILES "${CMAKE_SOURCE_DIR}/assets/agent/*") - file(COPY ${AGENT_ASSET_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/agent/") - - # Also add post-build copy for development workflow + file(COPY ${CMAKE_SOURCE_DIR}/assets/agent/ DESTINATION "${CMAKE_BINARY_DIR}/assets/agent/") add_custom_command(TARGET z3ed POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/assets/agent - $/assets/agent + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/agent $/assets/agent COMMENT "Copying agent assets for z3ed" ) endif() -# ============================================================================ -# AI Agent Support (Consolidated via Z3ED_AI flag) -# ============================================================================ -if(Z3ED_AI OR YAZE_WITH_JSON) - target_compile_definitions(z3ed PRIVATE YAZE_WITH_JSON) - message(STATUS "โœ“ z3ed AI agent enabled (Ollama + Gemini support)") - - # Link nlohmann_json (already fetched in main CMakeLists if YAZE_WITH_JSON) - target_link_libraries(z3ed PRIVATE nlohmann_json::nlohmann_json) -endif() - -# ============================================================================ -# SSL/HTTPS Support (Optional - Required for Gemini API) -# ============================================================================ -# SSL is only enabled when AI features are active -# Ollama (localhost) works without SSL, Gemini (HTTPS) requires it -if((Z3ED_AI OR YAZE_WITH_JSON) AND (YAZE_WITH_GRPC OR Z3ED_AI)) - find_package(OpenSSL) - - if(OpenSSL_FOUND) - # Define the SSL support macro for httplib - target_compile_definitions(z3ed PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) - - # Link OpenSSL libraries - target_link_libraries(z3ed PRIVATE OpenSSL::SSL OpenSSL::Crypto) - - # On macOS, also enable Keychain cert support - if(APPLE) - target_compile_definitions(z3ed PRIVATE CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) - target_link_libraries(z3ed PRIVATE "-framework CoreFoundation" "-framework Security") - endif() - - message(STATUS "โœ“ SSL/HTTPS support enabled for z3ed (Gemini API ready)") - else() - message(WARNING "OpenSSL not found - Gemini API will not work") - message(STATUS " โ€ข Ollama (local) still works without SSL") - message(STATUS " โ€ข Install OpenSSL for Gemini: brew install openssl (macOS) or apt install libssl-dev (Linux)") - endif() -else() - if(NOT Z3ED_AI AND NOT YAZE_WITH_JSON) - message(STATUS "โ—‹ z3ed AI agent disabled (set -DZ3ED_AI=ON to enable Gemini/Ollama)") - endif() -endif() - -target_include_directories(z3ed PUBLIC +target_include_directories(z3ed PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/tui" ) -# (Link libraries handled below; duplicate/unfinished lines removed.) - -if(YAZE_USE_MODULAR_BUILD) - target_link_libraries( - z3ed PRIVATE +target_link_libraries(z3ed PRIVATE yaze_core yaze_agent ftxui::component - ) -else() - target_link_libraries( - z3ed PRIVATE - yaze_core - yaze_agent - ftxui::component - absl::flags - absl::flags_parse - ) +) + +if(Z3ED_AI) + target_link_libraries(z3ed PRIVATE yaml-cpp) endif() -# ============================================================================ -# Optional gRPC Support for CLI Agent Test Command -# ============================================================================ if(YAZE_WITH_GRPC) message(STATUS "Adding gRPC support to z3ed CLI") - - # Generate C++ code from .proto using the helper function from cmake/grpc.cmake - target_add_protobuf(z3ed - ${CMAKE_SOURCE_DIR}/src/protos/imgui_test_harness.proto - ${CMAKE_SOURCE_DIR}/src/protos/canvas_automation.proto) - - # Add CLI gRPC service sources - target_sources(z3ed PRIVATE - ${CMAKE_SOURCE_DIR}/src/cli/service/gui/gui_automation_client.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/gui/gui_automation_client.h - ${CMAKE_SOURCE_DIR}/src/cli/service/testing/test_workflow_generator.cc - ${CMAKE_SOURCE_DIR}/src/cli/service/testing/test_workflow_generator.h) - - # Link gRPC libraries - target_link_libraries(z3ed PRIVATE - grpc++ - grpc++_reflection - libprotobuf) - - message(STATUS "โœ“ gRPC CLI automation integrated") + target_link_libraries(z3ed PRIVATE grpc++ grpc++_reflection libprotobuf) endif() \ No newline at end of file diff --git a/src/cli/z3ed_ascii_logo.h b/src/cli/z3ed_ascii_logo.h index 0e1e29b9..1213fb48 100644 --- a/src/cli/z3ed_ascii_logo.h +++ b/src/cli/z3ed_ascii_logo.h @@ -59,6 +59,7 @@ inline std::string GetColoredLogo() { " โ–ฒ โ–ฒ " + "\033[0;37m" + "AI-Powered CLI\n" + // Gray "\033[1;33m" + " โ–ฒโ–ฒโ–ฒโ–ฒโ–ฒ \n" + + "\033[1;32m" + " FTXUI โœฆ Animations โœฆ Command TODOs" + "\n" + "\033[0m"; // Reset } diff --git a/src/util/util.cmake b/src/util/util.cmake index 8f706052..bbb64a2e 100644 --- a/src/util/util.cmake +++ b/src/util/util.cmake @@ -23,14 +23,13 @@ set(YAZE_UTIL_SRC add_library(yaze_util STATIC ${YAZE_UTIL_SRC}) target_precompile_headers(yaze_util PRIVATE - - - + "$<$:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>" ) target_include_directories(yaze_util PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/incl + ${CMAKE_SOURCE_DIR}/src/lib ${PROJECT_BINARY_DIR} ) diff --git a/src/yaze_pch.h b/src/yaze_pch.h new file mode 100644 index 00000000..e9f99dd6 --- /dev/null +++ b/src/yaze_pch.h @@ -0,0 +1,30 @@ +// This is a precompiled header for the yaze project. +// It includes a set of common, relatively stable headers that are used across +// multiple source files to speed up compilation. + +#ifndef YAZE_PCH_H +#define YAZE_PCH_H + +// Standard Library +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +#import +#endif + +// Third-party libraries +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" + +// Project-specific headers +#include "util/log.h" + + +#endif // YAZE_PCH_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6d9b232d..adeecb1a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,26 +1,59 @@ -set(YAZE_SRC_FILES "") -foreach (file - app/rom.cc - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_EMU_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_UTIL_SRC} - ${YAZE_GUI_SRC}) - list(APPEND YAZE_SRC_FILES ${CMAKE_SOURCE_DIR}/src/${file}) -endforeach() +# This file defines the yaze test suites and configures test discovery with labels. -# Only build test executable if tests are enabled -if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF") +if(YAZE_BUILD_TESTS) - # Base list of test sources for all builds - set(YAZE_TEST_BASE_SOURCES + # Helper function to create and configure a test executable for a suite of tests. + # This function adds the executable, links common dependencies, discovers the + # tests using gtest_discover_tests, and assigns a label to all discovered tests. + function(yaze_add_test_suite suite_name label is_gui_test) + set(sources ${ARGN}) + add_executable(${suite_name} yaze_test.cc ${sources}) + + target_include_directories(${suite_name} PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/incl + ${CMAKE_SOURCE_DIR}/test + ${PROJECT_BINARY_DIR} + ) + + target_link_libraries(${suite_name} PRIVATE + yaze_test_support + gmock_main + gtest_main + absl::failure_signal_handler + absl::flags + absl::flags_parse + ) + + # Link ImGui Test Engine for GUI tests + if(is_gui_test AND YAZE_ENABLE_UI_TESTS) + target_link_libraries(${suite_name} PRIVATE ${IMGUI_TEST_ENGINE_TARGET}) + target_compile_definitions(${suite_name} PRIVATE ${IMGUI_TEST_ENGINE_DEFINITIONS}) + endif() + + if(WIN32) + message(STATUS "Configuring Windows stack size for ${suite_name} to 16MB") + if(MSVC) + target_link_options(${suite_name} PRIVATE /STACK:16777216) + else() + target_link_options(${suite_name} PRIVATE -Wl,--stack,16777216) + endif() + endif() + + include(GoogleTest) + gtest_discover_tests(${suite_name}) + + # Get the list of tests discovered for the target and apply the label. + get_target_property(suite_tests ${suite_name} TESTS) + if(suite_tests) + set_tests_properties(${suite_tests} PROPERTIES LABELS "${label}") + endif() + endfunction() + + # --- Stable Test Suite (Valid Contracts) --- + set(STABLE_TEST_SOURCES test_editor.cc - test_editor.h - testing.h - test_utils.h - + test_utils.cc # Unit Tests unit/core/asar_wrapper_test.cc unit/core/hex_test.cc @@ -38,165 +71,68 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF") unit/zelda3/dungeon_component_unit_test.cc unit/zelda3/dungeon/room_object_encoding_test.cc unit/zelda3/dungeon/room_manipulation_test.cc - # dungeon_object_renderer_mock_test.cc - REMOVED (ObjectRenderer obsolete) - - # CLI Services (for catalog serialization tests) ../src/cli/service/resources/resource_catalog.cc - + cli/service/resources/command_context_test.cc # Integration Tests integration/asar_integration_test.cc - integration/asar_rom_test.cc integration/dungeon_editor_test.cc - integration/dungeon_editor_test.h integration/dungeon_editor_v2_test.cc - integration/dungeon_editor_v2_test.h integration/editor/tile16_editor_test.cc integration/editor/editor_integration_test.cc - integration/editor/editor_integration_test.h - integration/ai/ai_gui_controller_test.cc - integration/ai/test_ai_tile_placement.cc - integration/ai/test_gemini_vision.cc - - # E2E Tests - e2e/rom_dependent/e2e_rom_test.cc - e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc - - # Integration Tests (Zelda3) integration/zelda3/overworld_integration_test.cc integration/zelda3/dungeon_editor_system_integration_test.cc - # dungeon_object_renderer_integration_test.cc - REMOVED (ObjectRenderer obsolete) integration/zelda3/room_integration_test.cc integration/zelda3/dungeon_object_rendering_tests.cc integration/zelda3/dungeon_room_test.cc integration/zelda3/sprite_position_test.cc integration/zelda3/message_test.cc ) + yaze_add_test_suite(yaze_test_stable "stable" OFF ${STABLE_TEST_SOURCES}) - # Sources only for full development builds - set(YAZE_TEST_DEV_SOURCES - test_utils.cc - - # E2E Tests (included in development builds) - e2e/canvas_selection_test.cc - e2e/framework_smoke_test.cc - e2e/dungeon_editor_smoke_test.cc - - # Benchmarks + # --- ROM Dependent Test Suite --- + if(YAZE_ENABLE_ROM_TESTS) + set(ROM_DEPENDENT_TEST_SOURCES + integration/asar_rom_test.cc + e2e/rom_dependent/e2e_rom_test.cc + e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc + ) + yaze_add_test_suite(yaze_test_rom_dependent "rom_dependent" OFF ${ROM_DEPENDENT_TEST_SOURCES}) + target_compile_definitions(yaze_test_rom_dependent PRIVATE + YAZE_ENABLE_ROM_TESTS=1 + YAZE_TEST_ROM_PATH="${YAZE_TEST_ROM_PATH}" + ) + endif() + + # --- Experimental & GUI Test Suites --- + if(YAZE_ENABLE_UI_TESTS) + set(GUI_TEST_SOURCES + test_utils.cc + e2e/framework_smoke_test.cc + e2e/dungeon_editor_smoke_test.cc + e2e/canvas_selection_test.cc + integration/ai/ai_gui_controller_test.cc + ) + yaze_add_test_suite(yaze_test_gui "gui;experimental" ON ${GUI_TEST_SOURCES}) + + # Add a single test entry to run the entire GUI suite headlessly + add_test( + NAME headless_gui_suite + COMMAND $ -nogui + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + set_tests_properties(headless_gui_suite PROPERTIES LABELS "headless_gui;experimental") + endif() + + set(EXPERIMENTAL_TEST_SOURCES + integration/ai/test_ai_tile_placement.cc + integration/ai/test_gemini_vision.cc + ) + yaze_add_test_suite(yaze_test_experimental "experimental" OFF ${EXPERIMENTAL_TEST_SOURCES}) + + # --- Benchmark Test Suite --- + set(BENCHMARK_TEST_SOURCES benchmarks/gfx_optimization_benchmarks.cc ) + yaze_add_test_suite(yaze_test_benchmark "benchmark" OFF ${BENCHMARK_TEST_SOURCES}) - if(YAZE_MINIMAL_BUILD) - # CI/Minimal build: use simplified test executable and base sources - add_executable( - yaze_test - yaze_test_ci.cc - ${YAZE_TEST_BASE_SOURCES} - ) - else() - # Development build: use full-featured test executable and all sources - add_executable( - yaze_test - yaze_test.cc - ${YAZE_TEST_BASE_SOURCES} - ${YAZE_TEST_DEV_SOURCES} - ) - endif() - - # Configure test executable only when tests are enabled - target_include_directories( - yaze_test PUBLIC - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/test/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${SDL2_INCLUDE_DIR} - ${PNG_INCLUDE_DIRS} - ${PROJECT_BINARY_DIR} - ) - - target_link_libraries( - yaze_test - ${SDL_TARGETS} - asar-static - ${ABSL_TARGETS} - ${PNG_LIBRARIES} - ${OPENGL_LIBRARIES} - ${CMAKE_DL_LIBS} - gmock_main - gmock - gtest_main - gtest - ) - - # Link core library for essential functionality (BPS, ASAR, etc.) - if(YAZE_BUILD_LIB) - if(YAZE_USE_MODULAR_BUILD) - target_link_libraries(yaze_test - yaze_util - yaze_gfx - yaze_gui - yaze_zelda3 - yaze_core_lib - yaze_editor - ) - - if(TARGET yaze_agent) - target_link_libraries(yaze_test yaze_agent) - endif() - - if(YAZE_BUILD_EMU AND TARGET yaze_emulator) - target_link_libraries(yaze_test yaze_emulator) - endif() - else() - target_link_libraries(yaze_test yaze_core) - endif() - endif() - - if(TARGET yaze_test_support) - target_link_libraries(yaze_test yaze_test_support) - endif() - - # Conditionally link ImGuiTestEngine only when UI tests are enabled - if(YAZE_ENABLE_UI_TESTS) - target_link_libraries(yaze_test ${IMGUI_TEST_ENGINE_TARGET}) - target_compile_definitions(yaze_test PRIVATE ${IMGUI_TEST_ENGINE_DEFINITIONS}) - endif() - - # ROM Testing Configuration - if(YAZE_ENABLE_ROM_TESTS) - target_compile_definitions(yaze_test PRIVATE - YAZE_ENABLE_ROM_TESTS=1 - YAZE_TEST_ROM_PATH="${YAZE_TEST_ROM_PATH}" - ) - endif() - - # Platform-specific definitions - if(UNIX AND NOT APPLE) - target_compile_definitions(yaze_test PRIVATE "linux" "stricmp=strcasecmp") - elseif(APPLE) - target_compile_definitions(yaze_test PRIVATE "MACOS" "stricmp=strcasecmp") - elseif(WIN32) - target_compile_definitions(yaze_test PRIVATE "WINDOWS") - # Increase stack size on Windows to prevent stack overflow during tests - # Use 16MB stack to handle deep call stacks in tests and test discovery - message(STATUS "Configuring Windows stack size for yaze_test to 16MB") - if(MSVC) - target_link_options(yaze_test PRIVATE /STACK:16777216) # 16MB stack - message(STATUS " Using MSVC linker flag: /STACK:16777216") - elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_link_options(yaze_test PRIVATE -Wl,--stack,16777216) # 16MB stack - message(STATUS " Using MinGW/GNU linker flag: -Wl,--stack,16777216") - else() - # Fallback for other compilers - target_link_options(yaze_test PRIVATE -Wl,--stack,16777216) - message(STATUS " Using fallback linker flag: -Wl,--stack,16777216") - endif() - endif() -endif() - -include(test.cmake) +endif() \ No newline at end of file diff --git a/test/cli/service/resources/command_context_test.cc b/test/cli/service/resources/command_context_test.cc index 9a87e2df..7e4b7ee0 100644 --- a/test/cli/service/resources/command_context_test.cc +++ b/test/cli/service/resources/command_context_test.cc @@ -6,7 +6,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "app/rom.h" -#include "cli/handlers/mock_rom.h" +#include "mocks/mock_rom.h" namespace yaze { namespace cli { @@ -16,11 +16,12 @@ class CommandContextTest : public ::testing::Test { protected: void SetUp() override { // Initialize mock ROM for testing - auto status = InitializeMockRom(mock_rom_); - ASSERT_OK(status); + std::vector test_data(1024, 0); // 1KB of empty data + auto status = mock_rom_.SetTestData(test_data); + ASSERT_TRUE(status.ok()); } - Rom mock_rom_; + yaze::test::MockRom mock_rom_; }; TEST_F(CommandContextTest, LoadsRomFromConfig) { @@ -30,7 +31,7 @@ TEST_F(CommandContextTest, LoadsRomFromConfig) { CommandContext context(config); auto rom_or = context.GetRom(); - ASSERT_OK(rom_or); + ASSERT_TRUE(rom_or.ok()); EXPECT_TRUE(rom_or.value()->is_loaded()); } @@ -41,7 +42,7 @@ TEST_F(CommandContextTest, UsesExternalRomContext) { CommandContext context(config); auto rom_or = context.GetRom(); - ASSERT_OK(rom_or); + ASSERT_TRUE(rom_or.ok()); EXPECT_EQ(rom_or.value(), &mock_rom_); } @@ -64,10 +65,10 @@ TEST_F(CommandContextTest, EnsuresLabelsLoaded) { CommandContext context(config); auto rom_or = context.GetRom(); - ASSERT_OK(rom_or); + ASSERT_TRUE(rom_or.ok()); auto status = context.EnsureLabelsLoaded(rom_or.value()); - EXPECT_OK(status); + EXPECT_TRUE(status.ok()); } TEST_F(CommandContextTest, GetFormatReturnsConfigFormat) { @@ -107,11 +108,11 @@ TEST_F(ArgumentParserTest, ParsesIntArguments) { ArgumentParser parser(args); auto room_or = parser.GetInt("room"); - ASSERT_OK(room_or); + ASSERT_TRUE(room_or.ok()); EXPECT_EQ(room_or.value(), 0x12); auto count_or = parser.GetInt("count"); - ASSERT_OK(count_or); + ASSERT_TRUE(count_or.ok()); EXPECT_EQ(count_or.value(), 42); } @@ -120,11 +121,11 @@ TEST_F(ArgumentParserTest, ParsesHexArguments) { ArgumentParser parser(args); auto addr_or = parser.GetHex("address"); - ASSERT_OK(addr_or); + ASSERT_TRUE(addr_or.ok()); EXPECT_EQ(addr_or.value(), 0x1234); auto value_or = parser.GetHex("value"); - ASSERT_OK(value_or); + ASSERT_TRUE(value_or.ok()); EXPECT_EQ(value_or.value(), 0xFF); } @@ -150,7 +151,7 @@ TEST_F(ArgumentParserTest, ValidatesRequiredArguments) { ArgumentParser parser(args); auto status = parser.RequireArgs({"type"}); - EXPECT_OK(status); + EXPECT_TRUE(status.ok()); status = parser.RequireArgs({"type", "missing"}); EXPECT_FALSE(status.ok()); @@ -175,11 +176,11 @@ class OutputFormatterTest : public ::testing::Test { TEST_F(OutputFormatterTest, CreatesFromString) { auto json_formatter = OutputFormatter::FromString("json"); - ASSERT_OK(json_formatter); + ASSERT_TRUE(json_formatter.ok()); EXPECT_TRUE(json_formatter.value().IsJson()); auto text_formatter = OutputFormatter::FromString("text"); - ASSERT_OK(text_formatter); + ASSERT_TRUE(text_formatter.ok()); EXPECT_TRUE(text_formatter.value().IsText()); auto invalid_formatter = OutputFormatter::FromString("invalid"); @@ -188,7 +189,7 @@ TEST_F(OutputFormatterTest, CreatesFromString) { TEST_F(OutputFormatterTest, GeneratesValidJson) { auto formatter_or = OutputFormatter::FromString("json"); - ASSERT_OK(formatter_or); + ASSERT_TRUE(formatter_or.ok()); auto formatter = std::move(formatter_or.value()); formatter.BeginObject("Test"); @@ -217,7 +218,7 @@ TEST_F(OutputFormatterTest, GeneratesValidJson) { TEST_F(OutputFormatterTest, GeneratesValidText) { auto formatter_or = OutputFormatter::FromString("text"); - ASSERT_OK(formatter_or); + ASSERT_TRUE(formatter_or.ok()); auto formatter = std::move(formatter_or.value()); formatter.BeginObject("Test Object"); @@ -247,7 +248,7 @@ TEST_F(OutputFormatterTest, GeneratesValidText) { TEST_F(OutputFormatterTest, EscapesJsonStrings) { auto formatter_or = OutputFormatter::FromString("json"); - ASSERT_OK(formatter_or); + ASSERT_TRUE(formatter_or.ok()); auto formatter = std::move(formatter_or.value()); formatter.BeginObject("Test"); @@ -265,7 +266,7 @@ TEST_F(OutputFormatterTest, EscapesJsonStrings) { TEST_F(OutputFormatterTest, HandlesEmptyObjects) { auto formatter_or = OutputFormatter::FromString("json"); - ASSERT_OK(formatter_or); + ASSERT_TRUE(formatter_or.ok()); auto formatter = std::move(formatter_or.value()); formatter.BeginObject("Empty"); @@ -277,7 +278,7 @@ TEST_F(OutputFormatterTest, HandlesEmptyObjects) { TEST_F(OutputFormatterTest, HandlesEmptyArrays) { auto formatter_or = OutputFormatter::FromString("json"); - ASSERT_OK(formatter_or); + ASSERT_TRUE(formatter_or.ok()); auto formatter = std::move(formatter_or.value()); formatter.BeginObject("Test"); diff --git a/test/yaze_test.cc b/test/yaze_test.cc index d50d9d63..2ff57f45 100644 --- a/test/yaze_test.cc +++ b/test/yaze_test.cc @@ -18,9 +18,12 @@ #include "app/core/window.h" #include "app/core/controller.h" #include "app/gfx/backend/sdl2_renderer.h" + +#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE #include "e2e/canvas_selection_test.h" #include "e2e/framework_smoke_test.h" #include "e2e/dungeon_editor_smoke_test.h" +#endif // #include "test_editor.h" // Not used in main @@ -306,6 +309,7 @@ int main(int argc, char* argv[]) { yaze::core::Controller controller; +#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE // Register smoke test ImGuiTest* smoke_test = IM_REGISTER_TEST(engine, "E2ETest", "FrameworkSmokeTest"); smoke_test->TestFunc = E2ETest_FrameworkSmokeTest; @@ -319,6 +323,7 @@ int main(int argc, char* argv[]) { ImGuiTest* dungeon_test = IM_REGISTER_TEST(engine, "E2ETest", "DungeonEditorSmokeTest"); dungeon_test->TestFunc = E2ETest_DungeonEditorV2SmokeTest; dungeon_test->UserData = &controller; +#endif // Main loop bool done = false;