diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 9e79f54d..234e4223 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -11,39 +11,27 @@ on: workflow_dispatch: jobs: - format-check: - name: "Format Check" + format-lint: + name: "Format & Lint" runs-on: ubuntu-22.04 - + steps: - uses: actions/checkout@v4 - - - name: Install clang-format + + - name: Install tooling run: | sudo apt-get update - sudo apt-get install -y clang-format-14 - + sudo apt-get install -y clang-format-14 clang-tidy-14 cppcheck + - name: Check Formatting run: | find src test -name "*.cc" -o -name "*.h" | xargs clang-format-14 --dry-run --Werror - lint-check: - name: "Lint Check" - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v4 - - - name: Install Tools - run: | - sudo apt-get update - sudo apt-get install -y clang-tidy-14 cppcheck - - name: Run cppcheck run: | cppcheck --enable=warning,style,performance --error-exitcode=0 \ --suppress=missingIncludeSystem --suppress=unusedFunction --inconclusive src/ - + - name: Run clang-tidy run: | find src -name "*.cc" -not -path "*/lib/*" | head -20 | \ @@ -71,4 +59,3 @@ jobs: platform: linux preset: ci build-type: RelWithDebInfo - diff --git a/CMakePresets.json b/CMakePresets.json index 718bad79..8714e819 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -408,6 +408,221 @@ "YAZE_ENABLE_REMOTE_AUTOMATION": "ON", "YAZE_ENABLE_AI_RUNTIME": "ON" } + }, + { + "name": "mac-dbg", + "inherits": "base", + "displayName": "macOS Debug", + "description": "Debug build for macOS", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "YAZE_BUILD_TESTS": "ON", + "YAZE_ENABLE_GRPC": "OFF", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "OFF", + "YAZE_BUILD_AGENT_UI": "ON", + "YAZE_ENABLE_REMOTE_AUTOMATION": "ON", + "YAZE_ENABLE_AI_RUNTIME": "OFF" + } + }, + { + "name": "mac-dbg-v", + "inherits": "mac-dbg", + "displayName": "macOS Debug Verbose", + "description": "Debug build with verbose warnings", + "cacheVariables": { + "YAZE_SUPPRESS_WARNINGS": "OFF" + } + }, + { + "name": "mac-rel", + "inherits": "base", + "displayName": "macOS Release", + "description": "Release build for macOS", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "YAZE_BUILD_TESTS": "OFF", + "YAZE_ENABLE_GRPC": "OFF", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "OFF", + "YAZE_ENABLE_LTO": "ON", + "YAZE_BUILD_AGENT_UI": "OFF", + "YAZE_ENABLE_REMOTE_AUTOMATION": "OFF", + "YAZE_ENABLE_AI_RUNTIME": "OFF" + } + }, + { + "name": "mac-dev", + "inherits": "base", + "displayName": "macOS Development", + "description": "Development build with ROM tests", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "YAZE_BUILD_TESTS": "ON", + "YAZE_ENABLE_GRPC": "OFF", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "OFF", + "YAZE_ENABLE_ROM_TESTS": "ON", + "YAZE_BUILD_AGENT_UI": "OFF", + "YAZE_ENABLE_REMOTE_AUTOMATION": "OFF", + "YAZE_ENABLE_AI_RUNTIME": "OFF" + } + }, + { + "name": "mac-ai", + "inherits": "base", + "displayName": "macOS AI Development", + "description": "Full development build with AI features and gRPC", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "YAZE_BUILD_TESTS": "ON", + "YAZE_ENABLE_GRPC": "ON", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "ON", + "YAZE_ENABLE_ROM_TESTS": "ON", + "YAZE_BUILD_AGENT_UI": "ON", + "YAZE_ENABLE_REMOTE_AUTOMATION": "ON", + "YAZE_ENABLE_AI_RUNTIME": "ON" + } + }, + { + "name": "mac-uni", + "inherits": "base", + "displayName": "macOS Universal Binary", + "description": "Universal binary for macOS (ARM64 + x86_64)", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_OSX_ARCHITECTURES": "arm64;x86_64", + "YAZE_BUILD_TESTS": "OFF", + "YAZE_ENABLE_GRPC": "OFF", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "OFF", + "YAZE_ENABLE_LTO": "ON", + "YAZE_BUILD_AGENT_UI": "OFF", + "YAZE_ENABLE_REMOTE_AUTOMATION": "OFF", + "YAZE_ENABLE_AI_RUNTIME": "OFF" + } + }, + { + "name": "lin-dbg", + "inherits": "base", + "displayName": "Linux Debug", + "description": "Debug build for Linux", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "YAZE_BUILD_TESTS": "ON", + "YAZE_ENABLE_GRPC": "OFF", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "OFF", + "YAZE_BUILD_AGENT_UI": "OFF", + "YAZE_ENABLE_REMOTE_AUTOMATION": "OFF", + "YAZE_ENABLE_AI_RUNTIME": "OFF" + } + }, + { + "name": "lin-dbg-v", + "inherits": "lin-dbg", + "displayName": "Linux Debug Verbose", + "description": "Debug build with verbose warnings", + "cacheVariables": { + "YAZE_SUPPRESS_WARNINGS": "OFF" + } + }, + { + "name": "lin-rel", + "inherits": "base", + "displayName": "Linux Release", + "description": "Release build for Linux", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "YAZE_BUILD_TESTS": "OFF", + "YAZE_ENABLE_GRPC": "OFF", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "OFF", + "YAZE_ENABLE_LTO": "ON", + "YAZE_BUILD_AGENT_UI": "OFF", + "YAZE_ENABLE_REMOTE_AUTOMATION": "OFF", + "YAZE_ENABLE_AI_RUNTIME": "OFF" + } + }, + { + "name": "lin-dev", + "inherits": "base", + "displayName": "Linux Development", + "description": "Development build with ROM tests", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "YAZE_BUILD_TESTS": "ON", + "YAZE_ENABLE_GRPC": "OFF", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "OFF", + "YAZE_ENABLE_ROM_TESTS": "ON", + "YAZE_BUILD_AGENT_UI": "OFF", + "YAZE_ENABLE_REMOTE_AUTOMATION": "OFF", + "YAZE_ENABLE_AI_RUNTIME": "OFF" + } + }, + { + "name": "lin-ai", + "inherits": "base", + "displayName": "Linux AI Development", + "description": "Full development build with AI features and gRPC", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "YAZE_BUILD_TESTS": "ON", + "YAZE_ENABLE_GRPC": "ON", + "YAZE_ENABLE_JSON": "ON", + "YAZE_ENABLE_AI": "ON", + "YAZE_ENABLE_ROM_TESTS": "ON", + "YAZE_BUILD_AGENT_UI": "ON", + "YAZE_ENABLE_REMOTE_AUTOMATION": "ON", + "YAZE_ENABLE_AI_RUNTIME": "ON" + } } ], "buildPresets": [ @@ -555,6 +770,83 @@ "displayName": "Windows AI Development Build (Visual Studio)", "configuration": "Debug", "jobs": 12 + }, + { + "name": "mac-dbg", + "configurePreset": "mac-dbg", + "displayName": "macOS Debug Build", + "configuration": "Debug", + "jobs": 12 + }, + { + "name": "mac-dbg-v", + "configurePreset": "mac-dbg-v", + "displayName": "macOS Debug Verbose Build", + "configuration": "Debug", + "jobs": 12 + }, + { + "name": "mac-rel", + "configurePreset": "mac-rel", + "displayName": "macOS Release Build", + "configuration": "Release", + "jobs": 12 + }, + { + "name": "mac-dev", + "configurePreset": "mac-dev", + "displayName": "macOS Development Build", + "configuration": "Debug", + "jobs": 12 + }, + { + "name": "mac-ai", + "configurePreset": "mac-ai", + "displayName": "macOS AI Development Build", + "configuration": "Debug", + "jobs": 12 + }, + { + "name": "mac-uni", + "configurePreset": "mac-uni", + "displayName": "macOS Universal Binary Build", + "configuration": "Release", + "jobs": 12 + }, + { + "name": "lin-dbg", + "configurePreset": "lin-dbg", + "displayName": "Linux Debug Build", + "configuration": "Debug", + "jobs": 12 + }, + { + "name": "lin-dbg-v", + "configurePreset": "lin-dbg-v", + "displayName": "Linux Debug Verbose Build", + "configuration": "Debug", + "jobs": 12 + }, + { + "name": "lin-rel", + "configurePreset": "lin-rel", + "displayName": "Linux Release Build", + "configuration": "Release", + "jobs": 12 + }, + { + "name": "lin-dev", + "configurePreset": "lin-dev", + "displayName": "Linux Development Build", + "configuration": "Debug", + "jobs": 12 + }, + { + "name": "lin-ai", + "configurePreset": "lin-ai", + "displayName": "Linux AI Development Build", + "configuration": "Debug", + "jobs": 12 } ], "testPresets": [ diff --git a/README.md b/README.md index 51d8537c..d83d1a2f 100644 --- a/README.md +++ b/README.md @@ -37,27 +37,10 @@ Run the environment verifier once per machine: ``` ### Configure & Build -```bash -# macOS -cmake --preset mac-dbg -cmake --build --preset mac-dbg - -# Linux -cmake --preset lin-dbg -cmake --build --preset lin-dbg - -# Windows (core preset) -cmake --preset win-dbg -cmake --build --preset win-dbg --target yaze - -# Enable AI + gRPC tooling (any platform) -cmake --preset mac-ai -cmake --build --preset mac-ai --target yaze z3ed - -# Windows AI preset -cmake --preset win-ai -cmake --build --preset win-ai --target yaze z3ed -``` +- Use the CMake preset that matches your platform (`mac-dbg`, `lin-dbg`, `win-dbg`, etc.). +- Build with `cmake --build --preset [--target …]`. +- See [`docs/public/build/quick-reference.md`](docs/public/build/quick-reference.md) for the canonical + list of presets, AI build policy, and testing commands. ### Agent Feature Flags diff --git a/assets/asm/usdasm b/assets/asm/usdasm index d53311a5..835b15b9 160000 --- a/assets/asm/usdasm +++ b/assets/asm/usdasm @@ -1 +1 @@ -Subproject commit d53311a54acd34f5e9ff3d92a03b213292f1db10 +Subproject commit 835b15b91fc93a635fbe319da045c7d0a034bb12 diff --git a/cmake/dependencies/yaml.cmake b/cmake/dependencies/yaml.cmake index b75faad2..e9411488 100644 --- a/cmake/dependencies/yaml.cmake +++ b/cmake/dependencies/yaml.cmake @@ -4,10 +4,51 @@ include(cmake/CPM.cmake) include(cmake/dependencies.lock) +if(NOT YAZE_ENABLE_AI AND NOT YAZE_ENABLE_AI_RUNTIME) + message(STATUS "Skipping yaml-cpp (AI runtime and CLI agent features disabled)") + set(YAZE_YAML_TARGETS "") + return() +endif() + message(STATUS "Setting up yaml-cpp ${YAML_CPP_VERSION} with CPM.cmake") -# Try to use system packages first if requested -if(YAZE_USE_SYSTEM_DEPS) +set(_YAZE_USE_SYSTEM_YAML ${YAZE_USE_SYSTEM_DEPS}) + +# Detect Homebrew installation automatically (helps offline builds) +if(APPLE AND NOT _YAZE_USE_SYSTEM_YAML) + set(_YAZE_YAML_PREFIX_CANDIDATES + /opt/homebrew/opt/yaml-cpp + /usr/local/opt/yaml-cpp) + + foreach(_prefix IN LISTS _YAZE_YAML_PREFIX_CANDIDATES) + if(EXISTS "${_prefix}") + list(APPEND CMAKE_PREFIX_PATH "${_prefix}") + message(STATUS "Added Homebrew yaml-cpp prefix: ${_prefix}") + set(_YAZE_USE_SYSTEM_YAML ON) + break() + endif() + endforeach() + + if(NOT _YAZE_USE_SYSTEM_YAML) + find_program(HOMEBREW_EXECUTABLE brew) + if(HOMEBREW_EXECUTABLE) + execute_process( + COMMAND "${HOMEBREW_EXECUTABLE}" --prefix yaml-cpp + OUTPUT_VARIABLE HOMEBREW_YAML_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE HOMEBREW_YAML_RESULT + ERROR_QUIET) + if(HOMEBREW_YAML_RESULT EQUAL 0 AND EXISTS "${HOMEBREW_YAML_PREFIX}") + list(APPEND CMAKE_PREFIX_PATH "${HOMEBREW_YAML_PREFIX}") + message(STATUS "Added Homebrew yaml-cpp prefix: ${HOMEBREW_YAML_PREFIX}") + set(_YAZE_USE_SYSTEM_YAML ON) + endif() + endif() + endif() +endif() + +# Try to use system packages first +if(_YAZE_USE_SYSTEM_YAML) find_package(yaml-cpp QUIET) if(yaml-cpp_FOUND) message(STATUS "Using system yaml-cpp") @@ -15,6 +56,8 @@ if(YAZE_USE_SYSTEM_DEPS) target_link_libraries(yaze_yaml INTERFACE yaml-cpp) set(YAZE_YAML_TARGETS yaze_yaml) return() + elseif(YAZE_USE_SYSTEM_DEPS) + message(WARNING "System yaml-cpp not found despite YAZE_USE_SYSTEM_DEPS=ON; falling back to CPM download") endif() endif() diff --git a/docs/internal/README.md b/docs/internal/README.md index a5b0b93a..e8289130 100644 --- a/docs/internal/README.md +++ b/docs/internal/README.md @@ -10,6 +10,22 @@ speculative without impacting the published docs. - `roadmaps/` – sequencing, feature parity analysis, and postmortems. - `research/` – emulator investigations, timing analyses, web ideas, and development trackers. - `legacy/` – superseded build guides and other historical docs kept for reference. +- `agents/` – includes the coordination board, personas, GH Actions remote guide, and helper scripts + (`scripts/agents/`) for common agent workflows. When adding new internal docs, place them under the appropriate subdirectory here instead of `docs/`. + +## Version Control & Safety Guidelines +- **Coordinate before forceful changes**: Never rewrite history on shared branches. Use dedicated + feature/bugfix branches (see `docs/public/developer/git-workflow.md`) and keep `develop/master` + clean. +- **Back up ROMs and assets**: Treat sample ROMs, palettes, and project files as irreplaceable. Work + on copies, and enable the editor’s automatic backup setting before testing risky changes. +- **Run scripts/verify-build-environment.* after pulling significant build changes** to avoid + drifting tooling setups. +- **Document risky operations**: When touching migrations, asset packers, or scripts that modify + files in bulk, add notes under `docs/internal/roadmaps/` or `blueprints/` so others understand the + impact. +- **Use the coordination board** for any change that affects multiple personas or large parts of the + tree; log blockers and handoffs to reduce conflicting edits. diff --git a/docs/public/developer/git-workflow.md b/docs/public/developer/git-workflow.md index 4a67c59f..d5a7e203 100644 --- a/docs/public/developer/git-workflow.md +++ b/docs/public/developer/git-workflow.md @@ -13,6 +13,8 @@ - **Solo work**: Push directly when you're the only one working - Warning: **Breaking changes**: Use feature branches and document in changelog - Warning: **Major refactors**: Use feature branches for safety (can always revert) +- **Always keep local backups**: copy ROMs/assets before editing; never risk the only copy. +- **Before rebasing/rewriting history**, stash or copy work elsewhere to prevent accidental loss. **Why relaxed?** - Small team / solo development @@ -199,6 +201,14 @@ git branch -d release/v0.4.0 - `experiment/vulkan-renderer` - `experiment/wasm-build` +## Git Safety Crash Course + +- Run `git status` often and avoid staging ROMs or build artifacts; add ignore rules when necessary. +- Never force-push shared branches (`develop`, `master`). PRs and feature branches are safer places + for rewrites. +- Keep backups of any tools that mutate large files (scripts, automation) so you can revert quickly. +- Before deleting branches that touched ROMs/assets, confirm those files were merged and backed up. + **Rules:** - Branch from: `develop` or `master` - May never merge (prototypes, research) diff --git a/docs/public/developer/networking.md b/docs/public/developer/networking.md index ea102421..95721ed9 100644 --- a/docs/public/developer/networking.md +++ b/docs/public/developer/networking.md @@ -1,6 +1,6 @@ # B5 - Architecture and Networking -This document provides a comprehensive overview of the yaze application's architecture, focusing on its service-oriented design, gRPC integration, and real-time collaboration features. +This document provides a comprehensive overview of the yaze application's architecture, focusing on its service-oriented design, gRPC integration, and real-time collaboration features. For build/preset instructions when enabling gRPC/automation presets, refer to the [Build & Test Quick Reference](../build/quick-reference.md). ## 1. High-Level Architecture diff --git a/docs/public/developer/testing-guide.md b/docs/public/developer/testing-guide.md index c472a3e4..4c356a6c 100644 --- a/docs/public/developer/testing-guide.md +++ b/docs/public/developer/testing-guide.md @@ -62,6 +62,9 @@ Based on the directory structure, tests fall into the following categories: ## 3. Running Tests +> 💡 Need a refresher on presets/commands? See the [Build & Test Quick Reference](../build/quick-reference.md) +> for the canonical `cmake`, `ctest`, and helper script usage before running the commands below. + ### Using the Enhanced Test Runner (`yaze_test`) The most flexible way to run tests is by using the `yaze_test` executable directly. It provides flags to filter tests by category, which is ideal for development and AI agent workflows. @@ -147,4 +150,4 @@ To run E2E tests and see the GUI interactions, use the `--show-gui` flag. The GUI testing framework is designed for AI agent automation. All major UI elements are registered with stable IDs, allowing an agent to "discover" and interact with them programmatically via the `z3ed` CLI. -Refer to the `z3ed` agent guide for details on using commands like `z3ed gui discover`, `z3ed gui click`, and `z3ed agent test replay`. \ No newline at end of file +Refer to the `z3ed` agent guide for details on using commands like `z3ed gui discover`, `z3ed gui click`, and `z3ed agent test replay`. diff --git a/docs/public/index.md b/docs/public/index.md index 5e5cf53d..09a7cc1b 100644 --- a/docs/public/index.md +++ b/docs/public/index.md @@ -13,6 +13,7 @@ and research notes were moved to `docs/internal/` so the public docs stay focuse - [Getting Started](overview/getting-started.md) ## Build & Tooling +- [Build Quick Reference](build/quick-reference.md) - [Build From Source](build/build-from-source.md) - [Platform Compatibility](build/platform-compatibility.md) - [CMake Presets](build/presets.md) diff --git a/docs/public/overview/getting-started.md b/docs/public/overview/getting-started.md index 70aa4bd2..acbdb903 100644 --- a/docs/public/overview/getting-started.md +++ b/docs/public/overview/getting-started.md @@ -9,6 +9,9 @@ This software allows you to modify "The Legend of Zelda: A Link to the Past" (US 3. **Select an Editor** from the main toolbar (e.g., Overworld, Dungeon, Graphics). 4. **Make Changes** and save your project. +> Building from source or enabling AI tooling? Use the +> [Build & Test Quick Reference](../build/quick-reference.md) for the canonical commands and presets. + ## General Tips - **Experiment Flags**: Enable or disable new features in `File > Options > Experiment Flags`. diff --git a/docs/public/usage/dungeon-editor.md b/docs/public/usage/dungeon-editor.md index 2f0b1695..7112bc6d 100644 --- a/docs/public/usage/dungeon-editor.md +++ b/docs/public/usage/dungeon-editor.md @@ -1,262 +1,116 @@ -# F2: Dungeon Editor v2 - Complete Guide +# F2: Dungeon Editor v2 Guide -**Last Updated**: October 10, 2025 -**Related**: [Architecture Overview](../developer/architecture.md), [Debugging Guide](../developer/debugging-guide.md) +**Scope**: DungeonEditorV2 (card-based UI), DungeonEditorSystem, dungeon canvases +**Related**: [Architecture Overview](../developer/architecture.md), [Canvas System](../developer/canvas-system.md) --- -## Overview +## 1. Overview -The Dungeon Editor uses a modern card-based architecture (DungeonEditorV2) with self-contained room rendering. This guide covers the architecture, recent refactoring work, and next development steps. +The Dungeon Editor ships with the multi-card workspace introduced in the 0.3.x releases. +Self-contained room buffers keep graphics, objects, and palettes isolated so you can switch between +rooms without invalidating the entire renderer. ### Key Features -- **Visual room editing** with 512x512 canvas per room -- **Object position visualization** - Colored outlines by layer (Red/Green/Blue) -- **Per-room settings** - Independent BG1/BG2 visibility and layer types -- **Flexible docking** - EditorCard system for custom workspace layouts -- **Self-contained rooms** - Each room owns its bitmaps and palettes -- **Overworld integration** - Double-click entrances to open dungeon rooms +- 512×512 canvas per room with pan/zoom, grid, and collision overlays. +- Layer-specific visualization (BG1/BG2 toggles, colored object outlines, slot labels). +- Modular cards for rooms, objects, palettes, entrances, and toolsets. +- Undo/Redo shared across cards via `DungeonEditorSystem`. +- Tight overworld integration: double-click an entrance to open the linked dungeon room. --- -### Architecture Improvements -1. **Room Buffers Decoupled** - No dependency on Arena graphics sheets -2. **ObjectRenderer Removed** - Standardized on ObjectDrawer (~1000 lines deleted) -3. **LoadGraphicsSheetsIntoArena Removed** - Using per-room graphics (~66 lines) -4. **Old Tab System Removed** - EditorCard is the standard -5. **Texture Atlas Infrastructure** - Future-proof stub created -6. **Test Suite Cleaned** - Deleted 1270 lines of redundant tests - -### UI Improvements -- Room ID in card title: `[003] Room Name` -- Properties reorganized into clean 4-column table -- Compact layer controls (1 row instead of 3) -- Room graphics canvas height fixed (1025px → 257px) -- Object count in status bar - ---- - -## Architecture - -### Component Overview +## 2. Architecture Snapshot ``` -DungeonEditorV2 (UI Layer) -├─ Card-based UI system -├─ Room window management -├─ Component coordination -└─ Lazy loading +DungeonEditorV2 (UI) +├─ Cards & docking +├─ Canvas presenter +└─ Menu + toolbar actions -DungeonEditorSystem (Backend Layer) -├─ Sprite/Item/Entrance/Door/Chest management -├─ Undo/Redo functionality -├─ Room properties management -└─ Dungeon-wide operations +DungeonEditorSystem (Backend) +├─ Room/session state +├─ Undo/Redo stack +├─ Sprite/entrance/item helpers +└─ Persistence + ROM writes -Room (Data Layer) -├─ Self-contained buffers (bg1_buffer_, bg2_buffer_) -├─ Object storage (tile_objects_) -├─ Graphics loading -└─ Rendering pipeline +Room Model (Data) +├─ bg1_buffer_, bg2_buffer_ +├─ tile_objects_, door data, metadata +└─ Palette + blockset caches ``` ### Room Rendering Pipeline +1. **Load** – `DungeonRoomLoader` reads the room header, blockset pointers, and door/entrance + metadata, producing a `Room` instance with immutable layout info. +2. **Decode** – The requested blockset is converted into `current_gfx16_` bitmaps; objects are parsed + into `tile_objects_` grouped by layer and palette slot. +3. **Draw** – `DungeonCanvasViewer` builds BG1/BG2 bitmaps, then overlays each object layer via + `ObjectDrawer`. Palette state comes from the room’s 90-color dungeon palette. +4. **Queue** – The finished bitmaps are pushed into the graphics `Arena`, which uploads a bounded + number of textures per frame so UI latency stays flat. +5. **Present** – When textures become available, the canvas displays the layers, draws interaction + widgets (selection rectangles, door gizmos, entity labels), and applies zoom/grid settings. -TODO: Update this to latest code. - -``` -1. LoadRoomGraphics(blockset) - └─> Reads blocks[] from ROM - └─> Loads blockset data → current_gfx16_ - -2. LoadObjects() - └─> Parses object data from ROM - └─> Creates tile_objects_[] - └─> SETS floor1_graphics_, floor2_graphics_ ← CRITICAL! - -3. RenderRoomGraphics() [SELF-CONTAINED] - ├─> DrawFloor(floor1_graphics_, floor2_graphics_) - ├─> DrawBackground(current_gfx16_) - ├─> SetPalette(full_90_color_dungeon_palette) - ├─> RenderObjectsToBackground() - │ └─> ObjectDrawer::DrawObjectList() - └─> QueueTextureCommand(UPDATE/CREATE) - -4. DrawRoomBackgroundLayers(room_id) - └─> ProcessTextureQueue() → GPU textures - └─> canvas_.DrawBitmap(bg1, bg2) - -5. DrawObjectPositionOutlines(room) - └─> Colored rectangles by layer - └─> Object ID labels -``` - -### Room Structure (Bottom to Top) - -Understanding ALTTP dungeon composition is critical: - -``` -Room Composition: -├─ Room Layout (BASE LAYER - immovable) -│ ├─ Walls (structural boundaries, 7 configurations of squares in 2x2 grid) -│ ├─ Floors (walkable areas, repeated tile pattern set to BG1/BG2) -├─ Layer 0 Objects (floor decorations, some walls) -├─ Layer 1 Objects (chests, decorations) -└─ Layer 2 Objects (stairs, transitions) - -Doors: Positioned at room edges to connect rooms -``` - -**Key Insight**: Layouts are immovable base structure. Objects are placed ON TOP and can be moved/edited. This allows for large rooms, 4-quadrant rooms, tall/wide rooms, etc. +Changing tiles, palettes, or objects invalidates the affected room cache so steps 2–5 rerun only for +that room. --- -## Next Development Steps +## 3. Editing Workflow -### High Priority (Must Do) +### Opening Rooms +1. Launch `yaze` with a ROM (`./build/bin/yaze --rom_file=zelda3.sfc`). +2. Use the **Room Matrix** or **Rooms List** card to choose a room. The toolbar “+” button also opens + the selector. +3. Pin multiple rooms by opening them in separate cards; each card maintains its own canvas state. -#### 1. Door Rendering at Room Edges -**What**: Render doors with proper patterns at room connections +### Working with Cards -**Pattern Reference**: ZScream's door drawing patterns +| Card | Purpose | +|------|---------| +| **Room Graphics** | Primary canvas, BG toggles, collision/grid switches. | +| **Object Editor** | Filter by type/layer, edit coordinates, duplicate/delete objects. | +| **Palette Editor** | Adjust per-room palette slots and preview results immediately. | +| **Entrances List** | Jump between overworld entrances and their mapped rooms. | +| **Room Matrix** | Visual grid of all rooms grouped per dungeon for quick navigation. | -**Implementation**: -```cpp -void DungeonCanvasViewer::DrawDoors(const zelda3::Room& room) { - // Doors stored in room data - // Position at room edges (North/South/East/West) - // Use current_gfx16_ graphics data - - // TODO: Get door data from room.GetDoors() or similar - // TODO: Use ObjectDrawer patterns for door graphics - // TODO: Draw at interpolation points between rooms -} -``` +Cards can be docked, detached, or saved as workspace presets; use the sidebar to store favorite +layouts (e.g., Room Graphics + Object Editor + Palette). + +### Canvas Interactions +- Left-click to select an object; Shift-click to add to the selection. +- Drag handles to move objects or use the property grid for precise coordinates. +- Right-click to open the context menu, which includes quick inserts for common objects and a “jump + to entrance” helper. +- Hold Space to pan, use mouse wheel (or trackpad pinch) to zoom. The status footer shows current + zoom and cursor coordinates. +- Enable **Object Labels** from the toolbar to show layer-colored labels (e.g., `L1 Chest 0x23`). + +### Saving & Undo +- The editor queues every change through `DungeonEditorSystem`. Use `Cmd/Ctrl+Z` and `Cmd/Ctrl+Shift+Z` + to undo/redo across cards. +- Saving writes back the room buffers, door metadata, and palettes for the active session. Keep + backups enabled (`File → Options → Experiment Flags`) for safety. --- -#### 2. Object Name Labels from String Array -**File**: `dungeon_canvas_viewer.cc:416` (DrawObjectPositionOutlines) +## 4. Tips & Troubleshooting -**What**: Show real object names instead of just IDs - -**Implementation**: -```cpp -// Instead of: -std::string label = absl::StrFormat("0x%02X", obj.id_); - -// Use: -std::string object_name = GetObjectName(obj.id_); -std::string label = absl::StrFormat("%s\n0x%02X", object_name.c_str(), obj.id_); - -// Helper function: -std::string GetObjectName(int16_t object_id) { - // TODO: Reference ZScream's object name arrays - // TODO: Map object ID → name string - // Example: 0x10 → "Wall (North)" - return "Object"; -} -``` +- **Layer sanity**: If objects appear on the wrong layer, check the BG toggles in Room Graphics and + the layer filter in Object Editor—they operate independently. +- **Palette issues**: Palettes are per room. After editing, ensure `Palette Editor` writes the new + values before switching rooms; the status footer confirms pending writes. +- **Door alignment**: Use the entrance/door inspector popup (right-click a door marker) to verify + leads-to IDs without leaving the canvas. +- **Performance**: Large ROMs with many rooms can accumulate textures. If the editor feels sluggish, + close unused room cards; each card releases its textures when closed. --- -#### 4. Fix Plus Button to Select Any Room -**File**: `dungeon_editor_v2.cc:228` (DrawToolset) - -**Current Issue**: Opens Room 0x00 (Ganon) always - -**Fix**: -```cpp -if (toolbar.AddAction(ICON_MD_ADD, "Open Room")) { - // Show room selector dialog instead of opening room 0 - show_room_selector_ = true; - // Or: show room picker popup - ImGui::OpenPopup("SelectRoomToOpen"); -} - -// Add popup: -if (ImGui::BeginPopup("SelectRoomToOpen")) { - static int selected_room = 0; - ImGui::InputInt("Room ID", &selected_room); - if (ImGui::Button("Open")) { - OnRoomSelected(selected_room); - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); -} -``` - ---- - -### Medium Priority (Should Do) - -#### 6. Fix InputHexByte +/- Button Events -**File**: `src/app/gui/input.cc` (likely) - -**Issue**: Buttons don't respond to clicks - -**Investigation Needed**: -- Check if button click events are being captured -- Verify event logic matches working examples -- Keep existing event style if it works elsewhere - - -### Lower Priority (Nice to Have) - -#### 9. Move Backend Logic to DungeonEditorSystem -**What**: Separate UI (V2) from data operations (System) - -**Migration**: -- Sprite management → DungeonEditorSystem -- Item management → DungeonEditorSystem -- Entrance/Door/Chest → DungeonEditorSystem -- Undo/Redo → DungeonEditorSystem - -**Result**: DungeonEditorV2 becomes pure UI coordinator - ---- - -## Quick Start - -### Build & Run -```bash -cd /Users/scawful/Code/yaze -cmake --preset mac-ai -B build_ai -cmake --build build_ai --target yaze -j12 - -# Run dungeon editor -./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon - -# Open specific room -./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0x00" -``` - ---- - -## Testing & Verification - -### Debug Commands -```bash -# Verify floor values load correctly -./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon 2>&1 | grep "floor1=" - -# Expected: floor1=4, floor2=8 (NOT 0!) - -# Check object rendering -./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon 2>&1 | grep "Drawing.*objects" - -# Check object drawing details -./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon 2>&1 | grep "Writing Tile16" -``` - -## Related Documentation - -- **Architecture Overview** (`../developer/architecture.md`) - Core architectural patterns -- **Debugging Guide** (`../developer/debugging-guide.md`) - Debugging workflows -- **Legacy context** - This document supersedes the legacy `F1-dungeon-editor-guide.md`. - ---- - -**Last Updated**: October 10, 2025 -**Contributors**: Dungeon Editor Refactoring Session - +## 5. Related Docs +- [Developer Architecture Overview](../developer/architecture.md) – patterns shared across editors. +- [Canvas System Guide](../developer/canvas-system.md) – detailed explanation of canvas usage, + context menus, and popups. +- [Debugging Guide](../developer/debugging-guide.md) – startup flags and logging tips (e.g., + `--editor=Dungeon --cards="Room 0"` for focused debugging). diff --git a/scripts/verify-build-environment.sh b/scripts/verify-build-environment.sh index 26a2fe90..04fe45fe 100755 --- a/scripts/verify-build-environment.sh +++ b/scripts/verify-build-environment.sh @@ -114,7 +114,7 @@ function test_git_submodules() { } function test_cmake_cache() { - local build_dirs=("build" "build-test" "build-grpc-test" "build-rooms" "build-windows") + local build_dirs=("build" "build_test" "build-test" "build-grpc-test" "build-rooms" "build-windows" "build_ai" "build_ai_claude" "build_agent" "build_ci") local cache_issues=0 for dir in "${build_dirs[@]}"; do @@ -191,7 +191,7 @@ function test_agent_folder_structure() { function clean_cmake_cache() { write_status "Cleaning CMake cache and build directories..." "Step" - local build_dirs=("build" "build-test" "build-grpc-test" "build-rooms" "build-windows") + local build_dirs=("build" "build_test" "build-test" "build-grpc-test" "build-rooms" "build-windows" "build_ai" "build_ai_claude" "build_agent" "build_ci") local cleaned=0 for dir in "${build_dirs[@]}"; do @@ -457,4 +457,4 @@ else echo "" exit 0 -fi \ No newline at end of file +fi diff --git a/src/app/controller.cc b/src/app/controller.cc index 1940253f..f79cc208 100644 --- a/src/app/controller.cc +++ b/src/app/controller.cc @@ -5,14 +5,14 @@ #include #include "absl/status/status.h" +#include "app/editor/editor_manager.h" +#include "app/gfx/backend/sdl2_renderer.h" // Add include for new renderer +#include "app/gfx/resource/arena.h" // Add include for Arena +#include "app/gui/automation/widget_id_registry.h" +#include "app/gui/core/background_renderer.h" +#include "app/gui/core/theme_manager.h" #include "app/platform/timing.h" #include "app/platform/window.h" -#include "app/editor/editor_manager.h" -#include "app/gui/core/background_renderer.h" -#include "app/gfx/resource/arena.h" // Add include for Arena -#include "app/gfx/backend/sdl2_renderer.h" // Add include for new renderer -#include "app/gui/core/theme_manager.h" -#include "app/gui/automation/widget_id_registry.h" #include "imgui/backends/imgui_impl_sdl2.h" #include "imgui/backends/imgui_impl_sdlrenderer2.h" #include "imgui/imgui.h" @@ -41,7 +41,7 @@ absl::Status Controller::OnEntry(std::string filename) { } void Controller::SetStartupEditor(const std::string& editor_name, - const std::string& cards) { + const std::string& cards) { // Process command-line flags for editor and cards // Example: --editor=Dungeon --cards="Rooms List,Room 0,Room 105" if (!editor_name.empty()) { diff --git a/src/app/controller.h b/src/app/controller.h index e8582b63..c87e2273 100644 --- a/src/app/controller.h +++ b/src/app/controller.h @@ -6,10 +6,10 @@ #include #include "absl/status/status.h" -#include "app/platform/window.h" -#include "app/rom.h" #include "app/editor/editor_manager.h" #include "app/gfx/backend/irenderer.h" +#include "app/platform/window.h" +#include "app/rom.h" int main(int argc, char** argv); @@ -29,9 +29,10 @@ class Controller { absl::Status OnLoad(); void DoRender() const; void OnExit(); - + // Set startup editor and cards from command-line flags - void SetStartupEditor(const std::string& editor_name, const std::string& cards); + void SetStartupEditor(const std::string& editor_name, + const std::string& cards); auto window() -> SDL_Window* { return window_.window_.get(); } void set_active(bool active) { active_ = active; } diff --git a/src/app/editor/agent/agent_chat_history_codec.cc b/src/app/editor/agent/agent_chat_history_codec.cc index 85e7234a..1a815017 100644 --- a/src/app/editor/agent/agent_chat_history_codec.cc +++ b/src/app/editor/agent/agent_chat_history_codec.cc @@ -80,7 +80,8 @@ std::optional ParseTableData( return table; } -Json SerializeProposal(const cli::agent::ChatMessage::ProposalSummary& proposal) { +Json SerializeProposal( + const cli::agent::ChatMessage::ProposalSummary& proposal) { Json json; json["id"] = proposal.id; json["change_count"] = proposal.change_count; @@ -100,7 +101,8 @@ std::optional ParseProposal( summary.id = json.value("id", ""); summary.change_count = json.value("change_count", 0); summary.executed_commands = json.value("executed_commands", 0); - if (json.contains("sandbox_rom_path") && json["sandbox_rom_path"].is_string()) { + if (json.contains("sandbox_rom_path") && + json["sandbox_rom_path"].is_string()) { summary.sandbox_rom_path = json["sandbox_rom_path"].get(); } if (json.contains("proposal_json_path") && @@ -154,9 +156,8 @@ absl::StatusOr AgentChatHistoryCodec::Load( cli::agent::ChatMessage message; std::string sender = item.value("sender", "agent"); - message.sender = sender == "user" - ? cli::agent::ChatMessage::Sender::kUser - : cli::agent::ChatMessage::Sender::kAgent; + message.sender = sender == "user" ? cli::agent::ChatMessage::Sender::kUser + : cli::agent::ChatMessage::Sender::kAgent; message.message = item.value("message", ""); message.timestamp = ParseTimestamp(item["timestamp"]); message.is_internal = item.value("is_internal", false); @@ -194,15 +195,15 @@ absl::StatusOr AgentChatHistoryCodec::Load( } } } - if (item.contains("model_metadata") && - item["model_metadata"].is_object()) { + if (item.contains("model_metadata") && item["model_metadata"].is_object()) { const auto& meta_json = item["model_metadata"]; cli::agent::ChatMessage::ModelMetadata meta; meta.provider = meta_json.value("provider", ""); meta.model = meta_json.value("model", ""); meta.latency_seconds = meta_json.value("latency_seconds", 0.0); meta.tool_iterations = meta_json.value("tool_iterations", 0); - if (meta_json.contains("tool_names") && meta_json["tool_names"].is_array()) { + if (meta_json.contains("tool_names") && + meta_json["tool_names"].is_array()) { for (const auto& name : meta_json["tool_names"]) { if (name.is_string()) { meta.tool_names.push_back(name.get()); @@ -227,8 +228,7 @@ absl::StatusOr AgentChatHistoryCodec::Load( const auto& collab_json = json["collaboration"]; snapshot.collaboration.active = collab_json.value("active", false); snapshot.collaboration.session_id = collab_json.value("session_id", ""); - snapshot.collaboration.session_name = - collab_json.value("session_name", ""); + snapshot.collaboration.session_name = collab_json.value("session_name", ""); snapshot.collaboration.participants.clear(); if (collab_json.contains("participants") && collab_json["participants"].is_array()) { @@ -245,8 +245,7 @@ absl::StatusOr AgentChatHistoryCodec::Load( } if (snapshot.collaboration.session_name.empty() && !snapshot.collaboration.session_id.empty()) { - snapshot.collaboration.session_name = - snapshot.collaboration.session_id; + snapshot.collaboration.session_name = snapshot.collaboration.session_id; } } @@ -274,7 +273,8 @@ absl::StatusOr AgentChatHistoryCodec::Load( AgentConfigSnapshot config; config.provider = config_json.value("provider", ""); config.model = config_json.value("model", ""); - config.ollama_host = config_json.value("ollama_host", "http://localhost:11434"); + config.ollama_host = + config_json.value("ollama_host", "http://localhost:11434"); config.gemini_api_key = config_json.value("gemini_api_key", ""); config.verbose = config_json.value("verbose", false); config.show_reasoning = config_json.value("show_reasoning", true); @@ -311,7 +311,8 @@ absl::StatusOr AgentChatHistoryCodec::Load( if (config_json.contains("model_presets") && config_json["model_presets"].is_array()) { for (const auto& preset_json : config_json["model_presets"]) { - if (!preset_json.is_object()) continue; + if (!preset_json.is_object()) + continue; AgentConfigSnapshot::ModelPreset preset; preset.name = preset_json.value("name", ""); preset.model = preset_json.value("model", ""); @@ -347,12 +348,12 @@ absl::StatusOr AgentChatHistoryCodec::Load( #else (void)path; return absl::UnimplementedError( - "Chat history persistence requires YAZE_WITH_GRPC=ON"); + "Chat history persistence requires YAZE_WITH_GRPC=ON"); #endif } -absl::Status AgentChatHistoryCodec::Save( - const std::filesystem::path& path, const Snapshot& snapshot) { +absl::Status AgentChatHistoryCodec::Save(const std::filesystem::path& path, + const Snapshot& snapshot) { #if defined(YAZE_WITH_JSON) Json json; json["version"] = 4; @@ -360,13 +361,12 @@ absl::Status AgentChatHistoryCodec::Save( for (const auto& message : snapshot.history) { Json entry; - entry["sender"] = - message.sender == cli::agent::ChatMessage::Sender::kUser ? "user" - : "agent"; + entry["sender"] = message.sender == cli::agent::ChatMessage::Sender::kUser + ? "user" + : "agent"; entry["message"] = message.message; - entry["timestamp"] = absl::FormatTime(absl::RFC3339_full, - message.timestamp, - absl::UTCTimeZone()); + entry["timestamp"] = absl::FormatTime(absl::RFC3339_full, message.timestamp, + absl::UTCTimeZone()); entry["is_internal"] = message.is_internal; if (message.json_pretty.has_value()) { @@ -385,8 +385,7 @@ absl::Status AgentChatHistoryCodec::Save( metrics_json["total_commands"] = metrics.total_commands; metrics_json["total_proposals"] = metrics.total_proposals; metrics_json["total_elapsed_seconds"] = metrics.total_elapsed_seconds; - metrics_json["average_latency_seconds"] = - metrics.average_latency_seconds; + metrics_json["average_latency_seconds"] = metrics.average_latency_seconds; entry["metrics"] = metrics_json; } if (message.proposal.has_value()) { @@ -420,9 +419,9 @@ absl::Status AgentChatHistoryCodec::Save( collab_json["session_name"] = snapshot.collaboration.session_name; collab_json["participants"] = snapshot.collaboration.participants; if (snapshot.collaboration.last_synced != absl::InfinitePast()) { - collab_json["last_synced"] = absl::FormatTime( - absl::RFC3339_full, snapshot.collaboration.last_synced, - absl::UTCTimeZone()); + collab_json["last_synced"] = + absl::FormatTime(absl::RFC3339_full, snapshot.collaboration.last_synced, + absl::UTCTimeZone()); } json["collaboration"] = std::move(collab_json); @@ -435,9 +434,9 @@ absl::Status AgentChatHistoryCodec::Save( } multimodal_json["status_message"] = snapshot.multimodal.status_message; if (snapshot.multimodal.last_updated != absl::InfinitePast()) { - multimodal_json["last_updated"] = absl::FormatTime( - absl::RFC3339_full, snapshot.multimodal.last_updated, - absl::UTCTimeZone()); + multimodal_json["last_updated"] = + absl::FormatTime(absl::RFC3339_full, snapshot.multimodal.last_updated, + absl::UTCTimeZone()); } json["multimodal"] = std::move(multimodal_json); @@ -510,7 +509,7 @@ absl::Status AgentChatHistoryCodec::Save( (void)path; (void)snapshot; return absl::UnimplementedError( - "Chat history persistence requires YAZE_WITH_GRPC=ON"); + "Chat history persistence requires YAZE_WITH_GRPC=ON"); #endif } diff --git a/src/app/editor/agent/agent_chat_history_popup.cc b/src/app/editor/agent/agent_chat_history_popup.cc index 8c628397..7fd255bc 100644 --- a/src/app/editor/agent/agent_chat_history_popup.cc +++ b/src/app/editor/agent/agent_chat_history_popup.cc @@ -5,9 +5,9 @@ #include #include "absl/strings/ascii.h" +#include "absl/strings/match.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" -#include "absl/strings/match.h" #include "absl/time/time.h" #include "app/editor/agent/agent_ui_theme.h" #include "app/editor/system/toast_manager.h" @@ -41,116 +41,119 @@ AgentChatHistoryPopup::AgentChatHistoryPopup() { } void AgentChatHistoryPopup::Draw() { - if (!visible_) return; + if (!visible_) + return; const auto& theme = AgentUI::GetTheme(); - + // Animate retro effects ImGuiIO& io = ImGui::GetIO(); pulse_animation_ += io.DeltaTime * 2.0f; scanline_offset_ += io.DeltaTime * 0.3f; - if (scanline_offset_ > 1.0f) scanline_offset_ -= 1.0f; + if (scanline_offset_ > 1.0f) + scanline_offset_ -= 1.0f; glitch_animation_ += io.DeltaTime * 5.0f; blink_counter_ = static_cast(pulse_animation_ * 2.0f) % 2; - + // Set drawer position on the LEFT side (full height) ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), + ImGuiCond_Always); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoTitleBar; + ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoTitleBar; // Use current theme colors with slight glow ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); - + // Pulsing border color float border_pulse = 0.7f + 0.3f * std::sin(pulse_animation_ * 0.5f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4( - theme.provider_ollama.x * border_pulse, - theme.provider_ollama.y * border_pulse, - theme.provider_ollama.z * border_pulse + 0.2f, - 0.8f - )); - + ImGui::PushStyleColor( + ImGuiCol_Border, + ImVec4(theme.provider_ollama.x * border_pulse, + theme.provider_ollama.y * border_pulse, + theme.provider_ollama.z * border_pulse + 0.2f, 0.8f)); + if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) { DrawHeader(); - + ImGui::Separator(); ImGui::Spacing(); - + // Calculate proper list height float list_height = ImGui::GetContentRegionAvail().y - 220.0f; - + // Dark terminal background ImVec4 terminal_bg = theme.code_bg_color; terminal_bg.x *= 0.9f; terminal_bg.y *= 0.9f; terminal_bg.z *= 0.95f; - + ImGui::PushStyleColor(ImGuiCol_ChildBg, terminal_bg); - ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); - + ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar); + // Draw scanline effect ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 win_pos = ImGui::GetWindowPos(); ImVec2 win_size = ImGui::GetWindowSize(); - + for (float y = 0; y < win_size.y; y += 3.0f) { float offset_y = y + scanline_offset_ * 3.0f; if (offset_y < win_size.y) { - draw_list->AddLine( - ImVec2(win_pos.x, win_pos.y + offset_y), - ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y), - IM_COL32(0, 0, 0, 15)); + draw_list->AddLine(ImVec2(win_pos.x, win_pos.y + offset_y), + ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y), + IM_COL32(0, 0, 0, 15)); } } - + DrawMessageList(); - + if (needs_scroll_) { ImGui::SetScrollHereY(1.0f); needs_scroll_ = false; } - + ImGui::EndChild(); ImGui::PopStyleColor(); - + ImGui::Spacing(); - + // Quick actions bar if (show_quick_actions_) { DrawQuickActions(); ImGui::Spacing(); } - + // Input section at bottom DrawInputSection(); } ImGui::End(); - + ImGui::PopStyleColor(); // Border color ImGui::PopStyleVar(2); } void AgentChatHistoryPopup::DrawMessageList() { if (messages_.empty()) { - ImGui::TextDisabled("No messages yet. Start a conversation in the chat window."); + ImGui::TextDisabled( + "No messages yet. Start a conversation in the chat window."); return; } - + // Calculate starting index for display limit - int start_index = messages_.size() > display_limit_ ? - messages_.size() - display_limit_ : 0; - + int start_index = + messages_.size() > display_limit_ ? messages_.size() - display_limit_ : 0; + for (int i = start_index; i < messages_.size(); ++i) { const auto& msg = messages_[i]; - + // Skip internal messages - if (msg.is_internal) continue; - + if (msg.is_internal) + continue; + if (!MessagePassesFilters(msg, i)) { continue; } @@ -159,24 +162,27 @@ void AgentChatHistoryPopup::DrawMessageList() { } } -void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int index) { +void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, + int index) { ImGui::PushID(index); - + bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser); - + // Retro terminal colors - ImVec4 header_color = from_user - ? ImVec4(1.0f, 0.85f, 0.0f, 1.0f) // Amber/Gold for user - : ImVec4(0.0f, 1.0f, 0.7f, 1.0f); // Cyan/Green for agent - + ImVec4 header_color = + from_user ? ImVec4(1.0f, 0.85f, 0.0f, 1.0f) // Amber/Gold for user + : ImVec4(0.0f, 1.0f, 0.7f, 1.0f); // Cyan/Green for agent + const char* sender_label = from_user ? "> USER:" : "> AGENT:"; - + // Message header with terminal prefix ImGui::TextColored(header_color, "%s", sender_label); - + ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - "[%s]", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str()); + ImGui::TextColored( + ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "[%s]", + absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()) + .c_str()); if (msg.model_metadata.has_value()) { const auto& meta = *msg.model_metadata; ImGui::SameLine(); @@ -204,16 +210,16 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int if (ImGui::IsItemHovered()) { ImGui::SetTooltip(is_pinned ? "Unpin message" : "Pin message"); } - + // Message content with terminal styling ImGui::Indent(15.0f); - + if (msg.table_data.has_value()) { - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), - " %s [Table Data]", ICON_MD_TABLE_CHART); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), " %s [Table Data]", + ICON_MD_TABLE_CHART); } else if (msg.json_pretty.has_value()) { - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), - " %s [Structured Response]", ICON_MD_DATA_OBJECT); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), + " %s [Structured Response]", ICON_MD_DATA_OBJECT); } else { // Truncate long messages with ellipsis std::string content = msg.message; @@ -224,18 +230,19 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int ImGui::TextWrapped(" %s", content.c_str()); ImGui::PopStyleColor(); } - + // Show proposal indicator with pulse if (msg.proposal.has_value()) { float proposal_pulse = 0.7f + 0.3f * std::sin(pulse_animation_ * 2.0f); - ImGui::TextColored(ImVec4(0.2f, proposal_pulse, 0.4f, 1.0f), - " %s Proposal: [%s]", ICON_MD_PREVIEW, msg.proposal->id.c_str()); + ImGui::TextColored(ImVec4(0.2f, proposal_pulse, 0.4f, 1.0f), + " %s Proposal: [%s]", ICON_MD_PREVIEW, + msg.proposal->id.c_str()); } if (msg.model_metadata.has_value()) { const auto& meta = *msg.model_metadata; - ImGui::TextDisabled(" Latency: %.2fs | Tools: %d", - meta.latency_seconds, meta.tool_iterations); + ImGui::TextDisabled(" Latency: %.2fs | Tools: %d", meta.latency_seconds, + meta.tool_iterations); if (!meta.tool_names.empty()) { ImGui::TextDisabled(" Tool calls: %s", absl::StrJoin(meta.tool_names, ", ").c_str()); @@ -246,23 +253,20 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int ImGui::TextColored(ImVec4(0.95f, 0.6f, 0.2f, 1.0f), " %s %s", ICON_MD_WARNING, warning.c_str()); } - + ImGui::Unindent(15.0f); ImGui::Spacing(); - + // Retro separator line ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 line_start = ImGui::GetCursorScreenPos(); float line_width = ImGui::GetContentRegionAvail().x; - draw_list->AddLine( - line_start, - ImVec2(line_start.x + line_width, line_start.y), - IM_COL32(60, 60, 70, 100), - 1.0f - ); - + draw_list->AddLine(line_start, + ImVec2(line_start.x + line_width, line_start.y), + IM_COL32(60, 60, 70, 100), 1.0f); + ImGui::Dummy(ImVec2(0, 2)); - + ImGui::PopID(); } @@ -271,7 +275,7 @@ void AgentChatHistoryPopup::DrawHeader() { ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 header_start = ImGui::GetCursorScreenPos(); ImVec2 header_size(ImGui::GetContentRegionAvail().x, 55); - + // Retro gradient with pulse float pulse = 0.5f + 0.5f * std::sin(pulse_animation_); ImVec4 bg_top = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); @@ -279,40 +283,34 @@ void AgentChatHistoryPopup::DrawHeader() { bg_top.x += 0.1f * pulse; bg_top.y += 0.1f * pulse; bg_top.z += 0.15f * pulse; - + ImU32 color_top = ImGui::GetColorU32(bg_top); ImU32 color_bottom = ImGui::GetColorU32(bg_bottom); draw_list->AddRectFilledMultiColor( header_start, ImVec2(header_start.x + header_size.x, header_start.y + header_size.y), color_top, color_top, color_bottom, color_bottom); - + // Pulsing accent line with glow float line_pulse = 0.6f + 0.4f * std::sin(pulse_animation_ * 0.7f); ImU32 accent_color = IM_COL32( - static_cast(theme.provider_ollama.x * 255 * line_pulse), - static_cast(theme.provider_ollama.y * 255 * line_pulse), - static_cast(theme.provider_ollama.z * 255 * line_pulse + 50), - 200 - ); + static_cast(theme.provider_ollama.x * 255 * line_pulse), + static_cast(theme.provider_ollama.y * 255 * line_pulse), + static_cast(theme.provider_ollama.z * 255 * line_pulse + 50), 200); draw_list->AddLine( ImVec2(header_start.x, header_start.y + header_size.y), ImVec2(header_start.x + header_size.x, header_start.y + header_size.y), accent_color, 2.0f); - + ImGui::Dummy(ImVec2(0, 8)); - + // Title with pulsing glow - ImVec4 title_color = ImVec4( - 0.4f + 0.3f * pulse, - 0.8f + 0.2f * pulse, - 1.0f, - 1.0f - ); + ImVec4 title_color = + ImVec4(0.4f + 0.3f * pulse, 0.8f + 0.2f * pulse, 1.0f, 1.0f); ImGui::PushStyleColor(ImGuiCol_Text, title_color); ImGui::Text("%s CHAT HISTORY", ICON_MD_CHAT); ImGui::PopStyleColor(); - + ImGui::SameLine(); ImGui::TextDisabled("[v0.4.x]"); @@ -331,8 +329,9 @@ void AgentChatHistoryPopup::DrawHeader() { ImGui::SameLine(); ImGui::SetNextItemWidth(150.0f); const char* provider_preview = - provider_filters_[std::min(provider_filter_index_, - static_cast(provider_filters_.size() - 1))] + provider_filters_[std::min( + provider_filter_index_, + static_cast(provider_filters_.size() - 1))] .c_str(); if (ImGui::BeginCombo("##provider_filter", provider_preview)) { for (int i = 0; i < static_cast(provider_filters_.size()); ++i) { @@ -358,16 +357,18 @@ void AgentChatHistoryPopup::DrawHeader() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Show pinned messages only"); } - + // Buttons properly spaced from right edge - ImGui::SameLine(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 75.0f); - + ImGui::SameLine(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - + 75.0f); + // Compact mode toggle with pulse bool should_highlight = (blink_counter_ == 0 && compact_mode_); if (should_highlight) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.4f, 0.6f, 0.7f)); } - if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) { + if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE + : ICON_MD_UNFOLD_LESS)) { compact_mode_ = !compact_mode_; } if (should_highlight) { @@ -376,9 +377,9 @@ void AgentChatHistoryPopup::DrawHeader() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip(compact_mode_ ? "Expand view" : "Compact view"); } - + ImGui::SameLine(); - + // Full chat button if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) { if (open_chat_callback_) { @@ -389,9 +390,9 @@ void AgentChatHistoryPopup::DrawHeader() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open full chat"); } - + ImGui::SameLine(); - + // Close button if (ImGui::SmallButton(ICON_MD_CLOSE)) { visible_ = false; @@ -399,7 +400,7 @@ void AgentChatHistoryPopup::DrawHeader() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Close (Ctrl+H)"); } - + // Message count with retro styling int visible_count = 0; for (int i = 0; i < static_cast(messages_.size()); ++i) { @@ -410,26 +411,27 @@ void AgentChatHistoryPopup::DrawHeader() { ++visible_count; } } - + ImGui::Spacing(); - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), - "> MESSAGES: [%d]", visible_count); - + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "> MESSAGES: [%d]", + visible_count); + // Animated status indicator if (unread_count_ > 0) { ImGui::SameLine(); float unread_pulse = 0.5f + 0.5f * std::sin(pulse_animation_ * 3.0f); ImGui::TextColored(ImVec4(1.0f, unread_pulse * 0.5f, 0.0f, 1.0f), - "%s %d NEW", ICON_MD_NOTIFICATION_IMPORTANT, unread_count_); + "%s %d NEW", ICON_MD_NOTIFICATION_IMPORTANT, + unread_count_); } - + ImGui::Dummy(ImVec2(0, 5)); } void AgentChatHistoryPopup::DrawQuickActions() { // 4 buttons with narrower width float button_width = (ImGui::GetContentRegionAvail().x - 15) / 4.0f; - + // Multimodal snapshot button if (ImGui::Button(ICON_MD_CAMERA, ImVec2(button_width, 30))) { if (capture_snapshot_callback_) { @@ -439,11 +441,12 @@ void AgentChatHistoryPopup::DrawQuickActions() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Capture screenshot"); } - + ImGui::SameLine(); - + // Filter button with icon indicator - const char* filter_icons[] = {ICON_MD_FILTER_LIST, ICON_MD_PERSON, ICON_MD_SMART_TOY}; + const char* filter_icons[] = {ICON_MD_FILTER_LIST, ICON_MD_PERSON, + ICON_MD_SMART_TOY}; int filter_idx = static_cast(message_filter_); if (ImGui::Button(filter_icons[filter_idx], ImVec2(button_width, 30))) { ImGui::OpenPopup("FilterPopup"); @@ -452,35 +455,39 @@ void AgentChatHistoryPopup::DrawQuickActions() { const char* filter_names[] = {"All", "User only", "Agent only"}; ImGui::SetTooltip("Filter: %s", filter_names[filter_idx]); } - + // Filter popup if (ImGui::BeginPopup("FilterPopup")) { - if (ImGui::Selectable(ICON_MD_FILTER_LIST " All Messages", message_filter_ == MessageFilter::kAll)) { + if (ImGui::Selectable(ICON_MD_FILTER_LIST " All Messages", + message_filter_ == MessageFilter::kAll)) { message_filter_ = MessageFilter::kAll; } - if (ImGui::Selectable(ICON_MD_PERSON " User Only", message_filter_ == MessageFilter::kUserOnly)) { + if (ImGui::Selectable(ICON_MD_PERSON " User Only", + message_filter_ == MessageFilter::kUserOnly)) { message_filter_ = MessageFilter::kUserOnly; } - if (ImGui::Selectable(ICON_MD_SMART_TOY " Agent Only", message_filter_ == MessageFilter::kAgentOnly)) { + if (ImGui::Selectable(ICON_MD_SMART_TOY " Agent Only", + message_filter_ == MessageFilter::kAgentOnly)) { message_filter_ = MessageFilter::kAgentOnly; } ImGui::EndPopup(); } - + ImGui::SameLine(); - + // Save session button if (ImGui::Button(ICON_MD_SAVE, ImVec2(button_width, 30))) { if (toast_manager_) { - toast_manager_->Show(ICON_MD_SAVE " Session auto-saved", ToastType::kSuccess, 1.5f); + toast_manager_->Show(ICON_MD_SAVE " Session auto-saved", + ToastType::kSuccess, 1.5f); } } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Save chat session"); } - + ImGui::SameLine(); - + // Clear button if (ImGui::Button(ICON_MD_DELETE, ImVec2(button_width, 30))) { ClearHistory(); @@ -566,35 +573,38 @@ void AgentChatHistoryPopup::TogglePin(int index) { void AgentChatHistoryPopup::DrawInputSection() { ImGui::Separator(); ImGui::Spacing(); - + // Input field using theme colors bool send_message = false; - if (ImGui::InputTextMultiline("##popup_input", input_buffer_, sizeof(input_buffer_), - ImVec2(-1, 60), - ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CtrlEnterForNewLine)) { + if (ImGui::InputTextMultiline("##popup_input", input_buffer_, + sizeof(input_buffer_), ImVec2(-1, 60), + ImGuiInputTextFlags_EnterReturnsTrue | + ImGuiInputTextFlags_CtrlEnterForNewLine)) { send_message = true; } - + // Focus input on first show if (focus_input_) { ImGui::SetKeyboardFocusHere(-1); focus_input_ = false; } - + // Send button (proper width) ImGui::Spacing(); float send_button_width = ImGui::GetContentRegionAvail().x; - if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(send_button_width, 32)) || send_message) { + if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), + ImVec2(send_button_width, 32)) || + send_message) { if (std::strlen(input_buffer_) > 0) { SendMessage(input_buffer_); std::memset(input_buffer_, 0, sizeof(input_buffer_)); } } - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Send message (Enter) • Ctrl+Enter for newline"); } - + // Info text ImGui::Spacing(); ImGui::TextDisabled(ICON_MD_INFO " Enter: send • Ctrl+Enter: newline"); @@ -603,20 +613,22 @@ void AgentChatHistoryPopup::DrawInputSection() { void AgentChatHistoryPopup::SendMessage(const std::string& message) { if (send_message_callback_) { send_message_callback_(message); - + if (toast_manager_) { - toast_manager_->Show(ICON_MD_SEND " Message sent", ToastType::kSuccess, 1.5f); + toast_manager_->Show(ICON_MD_SEND " Message sent", ToastType::kSuccess, + 1.5f); } - + // Auto-scroll to see response needs_scroll_ = true; } } -void AgentChatHistoryPopup::UpdateHistory(const std::vector& history) { +void AgentChatHistoryPopup::UpdateHistory( + const std::vector& history) { bool had_messages = !messages_.empty(); int old_size = messages_.size(); - + messages_ = history; std::unordered_set updated_pins; @@ -627,7 +639,7 @@ void AgentChatHistoryPopup::UpdateHistory(const std::vector old_size) { needs_scroll_ = true; @@ -638,10 +650,11 @@ void AgentChatHistoryPopup::NotifyNewMessage() { if (auto_scroll_) { needs_scroll_ = true; } - + // Flash the window to draw attention if (toast_manager_ && !visible_) { - toast_manager_->Show(ICON_MD_CHAT " New message received", ToastType::kInfo, 2.0f); + toast_manager_->Show(ICON_MD_CHAT " New message received", ToastType::kInfo, + 2.0f); } } @@ -651,7 +664,7 @@ void AgentChatHistoryPopup::ClearHistory() { provider_filters_.clear(); provider_filters_.push_back("All providers"); provider_filter_index_ = 0; - + if (toast_manager_) { toast_manager_->Show("Chat history popup cleared", ToastType::kInfo, 2.0f); } diff --git a/src/app/editor/agent/agent_chat_history_popup.h b/src/app/editor/agent/agent_chat_history_popup.h index d8a5b709..817cd336 100644 --- a/src/app/editor/agent/agent_chat_history_popup.h +++ b/src/app/editor/agent/agent_chat_history_popup.h @@ -48,7 +48,7 @@ class AgentChatHistoryPopup { // Update history from service void UpdateHistory(const std::vector& history); - + // Notify of new message (triggers auto-scroll) void NotifyNewMessage(); @@ -57,20 +57,20 @@ class AgentChatHistoryPopup { void SetOpenChatCallback(OpenChatCallback callback) { open_chat_callback_ = std::move(callback); } - + // Set callback for sending messages using SendMessageCallback = std::function; void SetSendMessageCallback(SendMessageCallback callback) { send_message_callback_ = std::move(callback); } - + // Set callback for capturing snapshots using CaptureSnapshotCallback = std::function; void SetCaptureSnapshotCallback(CaptureSnapshotCallback callback) { capture_snapshot_callback_ = std::move(callback); } -private: + private: void DrawHeader(); void DrawQuickActions(); void DrawInputSection(); @@ -80,55 +80,51 @@ private: int index) const; void RefreshProviderFilters(); void TogglePin(int index); - + void SendMessage(const std::string& message); void ClearHistory(); void ExportHistory(); void ScrollToBottom(); - + bool visible_ = false; bool needs_scroll_ = false; bool auto_scroll_ = true; bool compact_mode_ = true; bool show_quick_actions_ = true; - + // History state std::vector messages_; int display_limit_ = 50; // Show last 50 messages - + // Input state char input_buffer_[512] = {}; char search_buffer_[160] = {}; bool focus_input_ = false; - + // UI state float drawer_width_ = 420.0f; float min_drawer_width_ = 300.0f; float max_drawer_width_ = 700.0f; bool is_resizing_ = false; - + // Filter state - enum class MessageFilter { - kAll, - kUserOnly, - kAgentOnly - }; + enum class MessageFilter { kAll, kUserOnly, kAgentOnly }; MessageFilter message_filter_ = MessageFilter::kAll; std::vector provider_filters_; int provider_filter_index_ = 0; bool show_pinned_only_ = false; std::unordered_set pinned_messages_; - + // Visual state float header_pulse_ = 0.0f; int unread_count_ = 0; - + // Retro hacker aesthetic animations float pulse_animation_ = 0.0f; float scanline_offset_ = 0.0f; float glitch_animation_ = 0.0f; int blink_counter_ = 0; - + // Dependencies ToastManager* toast_manager_ = nullptr; OpenChatCallback open_chat_callback_; diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index 1524154e..d734392f 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -19,16 +19,18 @@ #include "absl/strings/str_join.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "core/project.h" #include "app/editor/agent/agent_chat_history_codec.h" -#include "app/editor/agent/agent_ui_theme.h" #include "app/editor/agent/agent_chat_history_popup.h" +#include "app/editor/agent/agent_ui_theme.h" #include "app/editor/system/proposal_drawer.h" #include "app/editor/system/toast_manager.h" #include "app/gui/core/icons.h" #include "app/rom.h" +#include "cli/service/ai/gemini_ai_service.h" +#include "cli/service/ai/model_registry.h" #include "cli/service/ai/ollama_ai_service.h" #include "cli/service/ai/service_factory.h" +#include "core/project.h" #include "imgui/imgui.h" #include "util/file_util.h" #include "util/platform_paths.h" @@ -64,9 +66,10 @@ std::filesystem::path ResolveHistoryPath(const std::string& session_id = "") { auto config_dir = yaze::util::PlatformPaths::GetConfigDirectory(); if (!config_dir.ok()) { // Fallback to a local directory if config can't be determined. - return fs::current_path() / ".yaze" / "agent" / "history" / (session_id.empty() ? "default.json" : session_id + ".json"); + return fs::current_path() / ".yaze" / "agent" / "history" / + (session_id.empty() ? "default.json" : session_id + ".json"); } - + fs::path base = *config_dir; if (base.empty()) { base = ExpandUserPath(".yaze"); @@ -136,8 +139,7 @@ std::string FormatRelativeTime(absl::Time timestamp) { static_cast(delta / absl::Minutes(1))); } if (delta < absl::Hours(24)) { - return absl::StrFormat("%dh ago", - static_cast(delta / absl::Hours(1))); + return absl::StrFormat("%dh ago", static_cast(delta / absl::Hours(1))); } return absl::FormatTime("%b %d", timestamp, absl::LocalTimeZone()); } @@ -164,11 +166,12 @@ AgentChatWidget::AgentChatWidget() { void AgentChatWidget::SetRomContext(Rom* rom) { // Track if we've already initialized labels for this ROM instance static Rom* last_rom_initialized = nullptr; - + agent_service_.SetRomContext(rom); // Only initialize labels ONCE per ROM instance - if (rom && rom->is_loaded() && rom->resource_label() && last_rom_initialized != rom) { + if (rom && rom->is_loaded() && rom->resource_label() && + last_rom_initialized != rom) { project::YazeProject project; project.use_embedded_labels = true; @@ -186,7 +189,8 @@ void AgentChatWidget::SetRomContext(Rom* rom) { if (toast_manager_) { toast_manager_->Show( - absl::StrFormat(ICON_MD_CHECK_CIRCLE " %d labels ready for AI", total_count), + absl::StrFormat(ICON_MD_CHECK_CIRCLE " %d labels ready for AI", + total_count), ToastType::kSuccess, 2.0f); } } @@ -474,7 +478,8 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { const auto& theme = AgentUI::GetTheme(); const bool from_user = (msg.sender == ChatMessage::Sender::kUser); - const ImVec4 header_color = from_user ? theme.user_message_color : theme.agent_message_color; + const ImVec4 header_color = + from_user ? theme.user_message_color : theme.agent_message_color; const char* header_label = from_user ? "You" : "Agent"; ImGui::TextColored(header_color, "%s", header_label); @@ -541,8 +546,8 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg, ImVec2(0, ImGui::GetFrameHeight() * 3.2f), true, ImGuiWindowFlags_None); - ImGui::TextColored(theme.proposal_accent, "%s Proposal %s", - ICON_MD_PREVIEW, proposal.id.c_str()); + ImGui::TextColored(theme.proposal_accent, "%s Proposal %s", ICON_MD_PREVIEW, + proposal.id.c_str()); ImGui::Text("Changes: %d", proposal.change_count); ImGui::Text("Commands: %d", proposal.executed_commands); @@ -613,7 +618,7 @@ void AgentChatWidget::RenderHistory() { void AgentChatWidget::RenderInputBox() { const auto& theme = AgentUI::GetTheme(); - + ImGui::Separator(); ImGui::TextColored(theme.command_text_color, ICON_MD_EDIT " Message:"); @@ -631,11 +636,12 @@ void AgentChatWidget::RenderInputBox() { ImGui::Spacing(); - // Send button row + // Send button row ImGui::PushStyleColor(ImGuiCol_Button, theme.provider_gemini); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, - ImVec4(theme.provider_gemini.x * 1.2f, theme.provider_gemini.y * 1.1f, - theme.provider_gemini.z, theme.provider_gemini.w)); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + ImVec4(theme.provider_gemini.x * 1.2f, theme.provider_gemini.y * 1.1f, + theme.provider_gemini.z, theme.provider_gemini.w)); if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(140, 0)) || send) { @@ -892,15 +898,15 @@ void AgentChatWidget::Draw() { float vertical_padding = (55.0f - content_height) / 2.0f; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + vertical_padding); - // Compact single row layout (restored) - ImGui::TextColored(accent_color, ICON_MD_SMART_TOY); - ImGui::SameLine(); - ImGui::SetNextItemWidth(95); - const char* providers[] = {"Mock", "Ollama", "Gemini"}; - int current_provider = (agent_config_.ai_provider == "mock") ? 0 - : (agent_config_.ai_provider == "ollama") ? 1 - : 2; - if (ImGui::Combo("##main_provider", ¤t_provider, providers, 3)) { + // Compact single row layout (restored) + ImGui::TextColored(accent_color, ICON_MD_SMART_TOY); + ImGui::SameLine(); + ImGui::SetNextItemWidth(95); + const char* providers[] = {"Mock", "Ollama", "Gemini"}; + int current_provider = (agent_config_.ai_provider == "mock") ? 0 + : (agent_config_.ai_provider == "ollama") ? 1 + : 2; + if (ImGui::Combo("##main_provider", ¤t_provider, providers, 3)) { agent_config_.ai_provider = (current_provider == 0) ? "mock" : (current_provider == 1) ? "ollama" : "gemini"; @@ -1100,8 +1106,10 @@ void AgentChatWidget::Draw() { ImVec2(4, 3)); // Compact padding if (ImGui::BeginTable("##commands_and_multimodal", 2)) { - ImGui::TableSetupColumn("Commands", ImGuiTableColumnFlags_WidthFixed, 180); - ImGui::TableSetupColumn("Multimodal", ImGuiTableColumnFlags_WidthFixed, ImGui::GetContentRegionAvail().x - 180); + ImGui::TableSetupColumn("Commands", ImGuiTableColumnFlags_WidthFixed, + 180); + ImGui::TableSetupColumn("Multimodal", ImGuiTableColumnFlags_WidthFixed, + ImGui::GetContentRegionAvail().x - 180); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); RenderZ3EDCommandPanel(); @@ -1143,10 +1151,12 @@ void AgentChatWidget::RenderCollaborationPanel() { // Always visible (no collapsing header) ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.14f, 0.18f, 0.95f)); - ImGui::BeginChild("CollabPanel", ImVec2(0, 140), true); // reduced height - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_PEOPLE " Collaboration"); + ImGui::BeginChild("CollabPanel", ImVec2(0, 140), true); // reduced height + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_PEOPLE " Collaboration"); ImGui::SameLine(); - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_SETTINGS_ETHERNET " Mode:"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_SETTINGS_ETHERNET " Mode:"); ImGui::SameLine(); ImGui::RadioButton(ICON_MD_FOLDER " Local##collab_mode_local", reinterpret_cast(&collaboration_state_.mode), @@ -1169,7 +1179,8 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::PushID("StatusColumn"); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.2f, 0.18f, 0.4f)); - ImGui::BeginChild("Collab_SessionDetails", ImVec2(0, 60), true); // reduced height + ImGui::BeginChild("Collab_SessionDetails", ImVec2(0, 60), + true); // reduced height ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_INFO " Session:"); if (connected) { @@ -1186,13 +1197,13 @@ void AgentChatWidget::RenderCollaborationPanel() { } if (!collaboration_state_.session_name.empty()) { - ImGui::TextColored(collaboration_status_color_, - ICON_MD_LABEL " %s", collaboration_state_.session_name.c_str()); + ImGui::TextColored(collaboration_status_color_, ICON_MD_LABEL " %s", + collaboration_state_.session_name.c_str()); } if (!collaboration_state_.session_id.empty()) { - ImGui::TextColored(collaboration_status_color_, - ICON_MD_KEY " %s", collaboration_state_.session_id.c_str()); + ImGui::TextColored(collaboration_status_color_, ICON_MD_KEY " %s", + collaboration_state_.session_id.c_str()); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.6f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, @@ -1200,17 +1211,19 @@ void AgentChatWidget::RenderCollaborationPanel() { if (ImGui::SmallButton(ICON_MD_CONTENT_COPY "##copy_session_id")) { ImGui::SetClipboardText(collaboration_state_.session_id.c_str()); if (toast_manager_) { - toast_manager_->Show("Session code copied!", ToastType::kSuccess, 2.0f); + toast_manager_->Show("Session code copied!", ToastType::kSuccess, + 2.0f); } } ImGui::PopStyleColor(2); } if (collaboration_state_.last_synced != absl::InfinitePast()) { - ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), - ICON_MD_ACCESS_TIME " %s", - absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced, - absl::LocalTimeZone()).c_str()); + ImGui::TextColored( + ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_ACCESS_TIME " %s", + absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced, + absl::LocalTimeZone()) + .c_str()); } ImGui::EndChild(); ImGui::PopStyleColor(); @@ -1221,8 +1234,8 @@ void AgentChatWidget::RenderCollaborationPanel() { if (collaboration_state_.participants.empty()) { ImGui::TextDisabled(ICON_MD_PEOPLE " No participants"); } else { - ImGui::TextColored(collaboration_status_color_, - ICON_MD_PEOPLE " %zu", collaboration_state_.participants.size()); + ImGui::TextColored(collaboration_status_color_, ICON_MD_PEOPLE " %zu", + collaboration_state_.participants.size()); for (size_t i = 0; i < collaboration_state_.participants.size(); ++i) { ImGui::PushID(static_cast(i)); ImGui::BulletText("%s", collaboration_state_.participants[i].c_str()); @@ -1255,14 +1268,17 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_CLOUD); ImGui::SameLine(); ImGui::SetNextItemWidth(100); - ImGui::InputText("##collab_server_url", server_url_buffer_, IM_ARRAYSIZE(server_url_buffer_)); + ImGui::InputText("##collab_server_url", server_url_buffer_, + IM_ARRAYSIZE(server_url_buffer_)); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); if (ImGui::SmallButton(ICON_MD_LINK "##connect_server_btn")) { collaboration_state_.server_url = server_url_buffer_; if (toast_manager_) { - toast_manager_->Show("Connecting to server...", ToastType::kInfo, 3.0f); + toast_manager_->Show("Connecting to server...", ToastType::kInfo, + 3.0f); } } ImGui::PopStyleColor(2); @@ -1275,29 +1291,41 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_ADD_CIRCLE); ImGui::SameLine(); ImGui::SetNextItemWidth(100); - ImGui::InputTextWithHint("##collab_session_name", "Session name...", session_name_buffer_, IM_ARRAYSIZE(session_name_buffer_)); + ImGui::InputTextWithHint("##collab_session_name", "Session name...", + session_name_buffer_, + IM_ARRAYSIZE(session_name_buffer_)); ImGui::SameLine(); - if (!can_host) ImGui::BeginDisabled(); + if (!can_host) + ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.5f, 0.0f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.843f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(1.0f, 0.843f, 0.0f, 1.0f)); if (ImGui::SmallButton(ICON_MD_ROCKET_LAUNCH "##host_session_btn")) { std::string name = session_name_buffer_; if (name.empty()) { if (toast_manager_) { - toast_manager_->Show("Enter a session name first", ToastType::kWarning, 3.0f); + toast_manager_->Show("Enter a session name first", + ToastType::kWarning, 3.0f); } } else { auto session_or = collaboration_callbacks_.host_session(name); if (session_or.ok()) { - ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/true); - std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", collaboration_state_.session_id.c_str()); + ApplyCollaborationSession(session_or.value(), + /*update_action_timestamp=*/true); + std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", + collaboration_state_.session_id.c_str()); session_name_buffer_[0] = '\0'; if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Hosting session %s", collaboration_state_.session_id.c_str()), ToastType::kSuccess, 3.5f); + toast_manager_->Show( + absl::StrFormat("Hosting session %s", + collaboration_state_.session_id.c_str()), + ToastType::kSuccess, 3.5f); } MarkHistoryDirty(); } else if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to host: %s", session_or.status().message()), ToastType::kError, 5.0f); + toast_manager_->Show(absl::StrFormat("Failed to host: %s", + session_or.status().message()), + ToastType::kError, 5.0f); } } } @@ -1315,28 +1343,40 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_LOGIN); ImGui::SameLine(); ImGui::SetNextItemWidth(100); - ImGui::InputTextWithHint("##collab_join_code", "Session code...", join_code_buffer_, IM_ARRAYSIZE(join_code_buffer_)); + ImGui::InputTextWithHint("##collab_join_code", "Session code...", + join_code_buffer_, + IM_ARRAYSIZE(join_code_buffer_)); ImGui::SameLine(); - if (!can_join) ImGui::BeginDisabled(); + if (!can_join) + ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.4f, 0.1f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.133f, 0.545f, 0.133f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.133f, 0.545f, 0.133f, 1.0f)); if (ImGui::SmallButton(ICON_MD_MEETING_ROOM "##join_session_btn")) { std::string code = join_code_buffer_; if (code.empty()) { if (toast_manager_) { - toast_manager_->Show("Enter a session code first", ToastType::kWarning, 3.0f); + toast_manager_->Show("Enter a session code first", + ToastType::kWarning, 3.0f); } } else { auto session_or = collaboration_callbacks_.join_session(code); if (session_or.ok()) { - ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/true); - std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", collaboration_state_.session_id.c_str()); + ApplyCollaborationSession(session_or.value(), + /*update_action_timestamp=*/true); + std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", + collaboration_state_.session_id.c_str()); if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Joined session %s", collaboration_state_.session_id.c_str()), ToastType::kSuccess, 3.5f); + toast_manager_->Show( + absl::StrFormat("Joined session %s", + collaboration_state_.session_id.c_str()), + ToastType::kSuccess, 3.5f); } MarkHistoryDirty(); } else if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to join: %s", session_or.status().message()), ToastType::kError, 5.0f); + toast_manager_->Show(absl::StrFormat("Failed to join: %s", + session_or.status().message()), + ToastType::kError, 5.0f); } } } @@ -1352,9 +1392,11 @@ void AgentChatWidget::RenderCollaborationPanel() { // Leave/Refresh if (collaboration_state_.active) { - if (!can_leave) ImGui::BeginDisabled(); + if (!can_leave) + ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.2f, 0.2f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.863f, 0.078f, 0.235f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.863f, 0.078f, 0.235f, 1.0f)); if (ImGui::SmallButton(ICON_MD_LOGOUT "##leave_session_btn")) { absl::Status status = collaboration_callbacks_.leave_session ? collaboration_callbacks_.leave_session() @@ -1363,30 +1405,39 @@ void AgentChatWidget::RenderCollaborationPanel() { collaboration_state_ = CollaborationState{}; join_code_buffer_[0] = '\0'; if (toast_manager_) { - toast_manager_->Show("Left collaborative session", ToastType::kInfo, 3.0f); + toast_manager_->Show("Left collaborative session", ToastType::kInfo, + 3.0f); } MarkHistoryDirty(); } else if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to leave: %s", status.message()), ToastType::kError, 5.0f); + toast_manager_->Show( + absl::StrFormat("Failed to leave: %s", status.message()), + ToastType::kError, 5.0f); } } ImGui::PopStyleColor(2); - if (!can_leave) ImGui::EndDisabled(); + if (!can_leave) + ImGui::EndDisabled(); ImGui::SameLine(); - if (!can_refresh) ImGui::BeginDisabled(); + if (!can_refresh) + ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); if (ImGui::SmallButton(ICON_MD_REFRESH "##refresh_collab_btn")) { RefreshCollaboration(); } ImGui::PopStyleColor(2); - if (!can_refresh && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + if (!can_refresh && + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Provide refresh_session callback to enable"); } - if (!can_refresh) ImGui::EndDisabled(); + if (!can_refresh) + ImGui::EndDisabled(); } else { - ImGui::TextDisabled(ICON_MD_INFO " Start or join a session to collaborate."); + ImGui::TextDisabled(ICON_MD_INFO + " Start or join a session to collaborate."); } ImGui::EndChild(); // Collab_Controls @@ -1398,7 +1449,7 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::EndChild(); ImGui::PopStyleColor(); // Pop the ChildBg color from line 1091 ImGui::PopStyleVar(2); // Pop the 2 StyleVars from lines 1082-1083 - ImGui::PopID(); // CollabPanel + ImGui::PopID(); // CollabPanel } void AgentChatWidget::RenderMultimodalPanel() { @@ -1407,7 +1458,8 @@ void AgentChatWidget::RenderMultimodalPanel() { // Dense header (no collapsing for small panel) AgentUI::PushPanelStyle(); - ImGui::BeginChild("Multimodal_Panel", ImVec2(0, 120), true); // Slightly taller + ImGui::BeginChild("Multimodal_Panel", ImVec2(0, 120), + true); // Slightly taller AgentUI::RenderSectionHeader(ICON_MD_CAMERA, "Vision", theme.provider_gemini); bool can_capture = static_cast(multimodal_callbacks_.capture_snapshot); @@ -1512,7 +1564,8 @@ void AgentChatWidget::RenderMultimodalPanel() { ImGui::EndDisabled(); // Screenshot preview section - if (multimodal_state_.preview.loaded && multimodal_state_.preview.show_preview) { + if (multimodal_state_.preview.loaded && + multimodal_state_.preview.show_preview) { ImGui::Spacing(); ImGui::Separator(); ImGui::Text(ICON_MD_IMAGE " Preview:"); @@ -1523,7 +1576,8 @@ void AgentChatWidget::RenderMultimodalPanel() { if (multimodal_state_.region_selection.active) { ImGui::Spacing(); ImGui::Separator(); - ImGui::TextColored(theme.provider_ollama, ICON_MD_CROP " Drag to select region"); + ImGui::TextColored(theme.provider_ollama, + ICON_MD_CROP " Drag to select region"); if (ImGui::SmallButton("Cancel##region_cancel")) { multimodal_state_.region_selection.active = false; } @@ -1532,7 +1586,7 @@ void AgentChatWidget::RenderMultimodalPanel() { ImGui::EndChild(); AgentUI::PopPanelStyle(); ImGui::PopID(); - + // Handle region selection (overlay) if (multimodal_state_.region_selection.active) { HandleRegionSelection(); @@ -1557,17 +1611,14 @@ void AgentChatWidget::RenderAutomationPanel() { if (ImGui::BeginChild("Automation_Panel", ImVec2(0, 240), true)) { // === HEADER WITH RETRO GLITCH EFFECT === float pulse = 0.5f + 0.5f * std::sin(automation_state_.pulse_animation); - ImVec4 header_glow = ImVec4( - theme.provider_ollama.x + 0.3f * pulse, - theme.provider_ollama.y + 0.2f * pulse, - theme.provider_ollama.z + 0.4f * pulse, - 1.0f - ); - + ImVec4 header_glow = ImVec4(theme.provider_ollama.x + 0.3f * pulse, + theme.provider_ollama.y + 0.2f * pulse, + theme.provider_ollama.z + 0.4f * pulse, 1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, header_glow); ImGui::TextWrapped("%s %s", ICON_MD_SMART_TOY, "GUI AUTOMATION"); ImGui::PopStyleColor(); - + ImGui::SameLine(); ImGui::TextDisabled("[v0.4.x]"); @@ -1576,51 +1627,54 @@ void AgentChatWidget::RenderAutomationPanel() { ImVec4 status_color; const char* status_text; const char* status_icon; - + if (connected) { // Pulsing green for connected - float green_pulse = 0.7f + 0.3f * std::sin(automation_state_.pulse_animation * 0.5f); + float green_pulse = + 0.7f + 0.3f * std::sin(automation_state_.pulse_animation * 0.5f); status_color = ImVec4(0.1f, green_pulse, 0.3f, 1.0f); status_text = "ONLINE"; status_icon = ICON_MD_CHECK_CIRCLE; } else { // Pulsing red for disconnected - float red_pulse = 0.6f + 0.4f * std::sin(automation_state_.pulse_animation * 1.5f); + float red_pulse = + 0.6f + 0.4f * std::sin(automation_state_.pulse_animation * 1.5f); status_color = ImVec4(red_pulse, 0.2f, 0.2f, 1.0f); status_text = "OFFLINE"; status_icon = ICON_MD_ERROR; } - + ImGui::Separator(); ImGui::TextColored(status_color, "%s %s", status_icon, status_text); ImGui::SameLine(); ImGui::TextDisabled("| %s", automation_state_.grpc_server_address.c_str()); - + // === CONTROL BAR === ImGui::Spacing(); - + // Refresh button with pulse effect when auto-refresh is on - bool auto_ref_pulse = automation_state_.auto_refresh_enabled && - (static_cast(automation_state_.pulse_animation * 2.0f) % 2 == 0); + bool auto_ref_pulse = + automation_state_.auto_refresh_enabled && + (static_cast(automation_state_.pulse_animation * 2.0f) % 2 == 0); if (auto_ref_pulse) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.7f, 0.8f)); } - + if (ImGui::SmallButton(ICON_MD_REFRESH " Refresh")) { PollAutomationStatus(); if (automation_callbacks_.show_active_tests) { automation_callbacks_.show_active_tests(); } } - + if (auto_ref_pulse) { ImGui::PopStyleColor(); } - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Refresh automation status\nAuto-refresh: %s (%.1fs)", - automation_state_.auto_refresh_enabled ? "ON" : "OFF", - automation_state_.refresh_interval_seconds); + automation_state_.auto_refresh_enabled ? "ON" : "OFF", + automation_state_.refresh_interval_seconds); } // Auto-refresh toggle @@ -1640,7 +1694,7 @@ void AgentChatWidget::RenderAutomationPanel() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open automation dashboard"); } - + ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_REPLAY " Replay")) { if (automation_callbacks_.replay_last_plan) { @@ -1654,8 +1708,9 @@ void AgentChatWidget::RenderAutomationPanel() { // === SETTINGS ROW === ImGui::Spacing(); ImGui::SetNextItemWidth(80.0f); - ImGui::SliderFloat("##refresh_interval", &automation_state_.refresh_interval_seconds, - 0.5f, 10.0f, "%.1fs"); + ImGui::SliderFloat("##refresh_interval", + &automation_state_.refresh_interval_seconds, 0.5f, 10.0f, + "%.1fs"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Auto-refresh interval"); } @@ -1671,62 +1726,63 @@ void AgentChatWidget::RenderAutomationPanel() { // === RECENT AUTOMATION ACTIONS WITH SCROLLING === ImGui::Spacing(); ImGui::Separator(); - + // Header with retro styling - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s RECENT ACTIONS", ICON_MD_LIST); + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s RECENT ACTIONS", + ICON_MD_LIST); ImGui::SameLine(); ImGui::TextDisabled("[%zu]", automation_state_.recent_tests.size()); - + if (automation_state_.recent_tests.empty()) { ImGui::Spacing(); ImGui::TextDisabled(" > No recent actions"); ImGui::TextDisabled(" > Waiting for automation tasks..."); - + // Add animated dots int dots = static_cast(automation_state_.pulse_animation) % 4; std::string dot_string(dots, '.'); ImGui::TextDisabled(" > %s", dot_string.c_str()); } else { // Scrollable action list with retro styling - ImGui::BeginChild("ActionQueue", ImVec2(0, 100), true, + ImGui::BeginChild("ActionQueue", ImVec2(0, 100), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); - + // Add scanline effect (visual only) ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 win_pos = ImGui::GetWindowPos(); ImVec2 win_size = ImGui::GetWindowSize(); - + // Draw scanlines for (float y = 0; y < win_size.y; y += 4.0f) { float offset_y = y + automation_state_.scanline_offset * 4.0f; if (offset_y < win_size.y) { draw_list->AddLine( - ImVec2(win_pos.x, win_pos.y + offset_y), - ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y), - IM_COL32(0, 0, 0, 20)); + ImVec2(win_pos.x, win_pos.y + offset_y), + ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y), + IM_COL32(0, 0, 0, 20)); } } - + for (const auto& test : automation_state_.recent_tests) { ImGui::PushID(test.test_id.c_str()); - + // Status icon with animation for running tests ImVec4 action_color; const char* status_icon; bool is_running = false; - - if (test.status == "success" || test.status == "completed" || test.status == "passed") { + + if (test.status == "success" || test.status == "completed" || + test.status == "passed") { action_color = theme.status_success; status_icon = ICON_MD_CHECK_CIRCLE; } else if (test.status == "running" || test.status == "in_progress") { is_running = true; - float running_pulse = 0.5f + 0.5f * std::sin(automation_state_.pulse_animation * 3.0f); - action_color = ImVec4( - theme.provider_ollama.x * running_pulse, - theme.provider_ollama.y * (0.8f + 0.2f * running_pulse), - theme.provider_ollama.z * running_pulse, - 1.0f - ); + float running_pulse = + 0.5f + 0.5f * std::sin(automation_state_.pulse_animation * 3.0f); + action_color = + ImVec4(theme.provider_ollama.x * running_pulse, + theme.provider_ollama.y * (0.8f + 0.2f * running_pulse), + theme.provider_ollama.z * running_pulse, 1.0f); status_icon = ICON_MD_PENDING; } else if (test.status == "failed" || test.status == "error") { action_color = theme.status_error; @@ -1735,30 +1791,30 @@ void AgentChatWidget::RenderAutomationPanel() { action_color = theme.text_secondary_color; status_icon = ICON_MD_HELP; } - + // Icon with pulse ImGui::TextColored(action_color, "%s", status_icon); ImGui::SameLine(); - + // Action name with monospace font ImGui::Text("> %s", test.name.c_str()); - + // Timestamp if (test.updated_at != absl::InfinitePast()) { ImGui::SameLine(); auto elapsed = absl::Now() - test.updated_at; if (elapsed < absl::Seconds(60)) { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - "[%ds]", static_cast(absl::ToInt64Seconds(elapsed))); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "[%ds]", + static_cast(absl::ToInt64Seconds(elapsed))); } else if (elapsed < absl::Minutes(60)) { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - "[%dm]", static_cast(absl::ToInt64Minutes(elapsed))); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "[%dm]", + static_cast(absl::ToInt64Minutes(elapsed))); } else { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - "[%dh]", static_cast(absl::ToInt64Hours(elapsed))); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "[%dh]", + static_cast(absl::ToInt64Hours(elapsed))); } } - + // Message (if any) with indentation if (!test.message.empty()) { ImGui::Indent(20.0f); @@ -1767,10 +1823,10 @@ void AgentChatWidget::RenderAutomationPanel() { ImGui::PopStyleColor(); ImGui::Unindent(20.0f); } - + ImGui::PopID(); } - + ImGui::EndChild(); } } @@ -1934,49 +1990,66 @@ void AgentChatWidget::ApplyToolPreferences() { agent_service_.SetToolPreferences(prefs); } -void AgentChatWidget::RefreshOllamaModels() { +void AgentChatWidget::RefreshModels() { + models_loading_ = true; + + auto& registry = cli::ModelRegistry::GetInstance(); + registry.ClearServices(); + + // Register Ollama service + if (!agent_config_.ollama_host.empty()) { #if defined(YAZE_WITH_JSON) - ollama_models_loading_ = true; - cli::OllamaConfig config; - config.base_url = agent_config_.ollama_host; - if (!agent_config_.ai_model.empty()) { - config.model = agent_config_.ai_model; + cli::OllamaConfig config; + config.base_url = agent_config_.ollama_host; + if (!agent_config_.ai_model.empty()) { + config.model = agent_config_.ai_model; + } + registry.RegisterService(std::make_shared(config)); +#endif } - cli::OllamaAIService ollama_service(config); - auto models_or = ollama_service.ListAvailableModels(); - ollama_models_loading_ = false; + + // Register Gemini service + if (!agent_config_.gemini_api_key.empty()) { +#if defined(YAZE_WITH_JSON) + cli::GeminiConfig config(agent_config_.gemini_api_key); + registry.RegisterService(std::make_shared(config)); +#endif + } + + auto models_or = registry.ListAllModels(); + models_loading_ = false; + if (!models_or.ok()) { if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Model refresh failed: %s", - models_or.status().message()), - ToastType::kWarning, 4.0f); + toast_manager_->Show(absl::StrFormat("Model refresh failed: %s", + models_or.status().message()), + ToastType::kWarning, 4.0f); } return; } - ollama_model_info_cache_ = *models_or; - std::sort(ollama_model_info_cache_.begin(), ollama_model_info_cache_.end(), - [](const cli::OllamaAIService::ModelInfo& lhs, - const cli::OllamaAIService::ModelInfo& rhs) { + model_info_cache_ = *models_or; + + // Sort: Provider first, then Name + std::sort(model_info_cache_.begin(), model_info_cache_.end(), + [](const cli::ModelInfo& lhs, const cli::ModelInfo& rhs) { + if (lhs.provider != rhs.provider) { + return lhs.provider < rhs.provider; + } return lhs.name < rhs.name; }); - ollama_model_cache_.clear(); - for (const auto& info : ollama_model_info_cache_) { - ollama_model_cache_.push_back(info.name); + + model_name_cache_.clear(); + for (const auto& info : model_info_cache_) { + model_name_cache_.push_back(info.name); } + last_model_refresh_ = absl::Now(); if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Loaded %zu local models", ollama_model_cache_.size()), - ToastType::kSuccess, 2.0f); + toast_manager_->Show(absl::StrFormat("Loaded %zu models from all providers", + model_info_cache_.size()), + ToastType::kSuccess, 2.0f); } -#else - if (toast_manager_) { - toast_manager_->Show("Model discovery requires JSON-enabled build", - ToastType::kWarning, 3.5f); - } -#endif } void AgentChatWidget::UpdateAgentConfig(const AgentConfigState& config) { @@ -1994,7 +2067,7 @@ void AgentChatWidget::UpdateAgentConfig(const AgentConfigState& config) { auto provider_config = BuildAIServiceConfig(); absl::Status status = agent_service_.ConfigureProvider(provider_config); if (!status.ok()) { - if (toast_manager_) { + if (toast_manager_) { toast_manager_->Show( absl::StrFormat("Provider init failed: %s", status.message()), ToastType::kError, 4.0f); @@ -2002,15 +2075,15 @@ void AgentChatWidget::UpdateAgentConfig(const AgentConfigState& config) { } else { ApplyToolPreferences(); if (toast_manager_) { - toast_manager_->Show("Agent configuration applied", - ToastType::kSuccess, 2.0f); + toast_manager_->Show("Agent configuration applied", ToastType::kSuccess, + 2.0f); } } } void AgentChatWidget::RenderAgentConfigPanel() { const auto& theme = AgentUI::GetTheme(); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color); ImGui::BeginChild("AgentConfig", ImVec2(0, 190), true); AgentUI::RenderSectionHeader(ICON_MD_SETTINGS, "Agent Builder", @@ -2071,13 +2144,13 @@ void AgentChatWidget::RenderModelConfigControls() { } if (ImGui::Button(label, ImVec2(90, 28))) { agent_config_.ai_provider = value; - std::snprintf(agent_config_.provider_buffer, + std::snprintf(agent_config_.provider_buffer, sizeof(agent_config_.provider_buffer), "%s", value); } if (active) { ImGui::PopStyleColor(); - } - ImGui::SameLine(); + } + ImGui::SameLine(); }; const auto& theme = AgentUI::GetTheme(); @@ -2087,168 +2160,18 @@ void AgentChatWidget::RenderModelConfigControls() { ImGui::NewLine(); ImGui::NewLine(); + // Provider-specific configuration if (agent_config_.ai_provider == "ollama") { - if (ImGui::InputTextWithHint("##ollama_host", "http://localhost:11434", - agent_config_.ollama_host_buffer, - IM_ARRAYSIZE(agent_config_.ollama_host_buffer))) { + if (ImGui::InputTextWithHint( + "##ollama_host", "http://localhost:11434", + agent_config_.ollama_host_buffer, + IM_ARRAYSIZE(agent_config_.ollama_host_buffer))) { agent_config_.ollama_host = agent_config_.ollama_host_buffer; } - if (ImGui::InputTextWithHint("##ollama_model", "qwen2.5-coder:7b", - agent_config_.model_buffer, - IM_ARRAYSIZE(agent_config_.model_buffer))) { - agent_config_.ai_model = agent_config_.model_buffer; - } - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f); - ImGui::InputTextWithHint("##model_search", "Search local models...", - model_search_buffer_, - IM_ARRAYSIZE(model_search_buffer_)); - ImGui::SameLine(); - if (ImGui::Button(ollama_models_loading_ ? ICON_MD_SYNC - : ICON_MD_REFRESH)) { - RefreshOllamaModels(); - } - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.14f, 0.9f)); - ImGui::BeginChild("OllamaModelList", ImVec2(0, 140), true); - std::string filter = absl::AsciiStrToLower(model_search_buffer_); - const bool has_metadata = !ollama_model_info_cache_.empty(); - if (ollama_model_cache_.empty() && ollama_model_info_cache_.empty()) { - ImGui::TextDisabled("No cached models. Refresh to discover local models."); - } else if (has_metadata) { - for (const auto& info : ollama_model_info_cache_) { - std::string lower = absl::AsciiStrToLower(info.name); - if (!filter.empty() && lower.find(filter) == std::string::npos) { - std::string param = absl::AsciiStrToLower(info.parameter_size); - if (param.find(filter) == std::string::npos) { - continue; - } - } - - bool is_selected = agent_config_.ai_model == info.name; - if (ImGui::Selectable(info.name.c_str(), is_selected)) { - agent_config_.ai_model = info.name; - std::snprintf(agent_config_.model_buffer, - sizeof(agent_config_.model_buffer), "%s", - info.name.c_str()); - } - - ImGui::SameLine(); - bool is_favorite = std::find(agent_config_.favorite_models.begin(), - agent_config_.favorite_models.end(), - info.name) != - agent_config_.favorite_models.end(); - std::string fav_id = absl::StrFormat("Fav##%s", info.name); - if (ImGui::SmallButton(is_favorite ? ICON_MD_STAR : ICON_MD_STAR_BORDER)) { - if (is_favorite) { - agent_config_.favorite_models.erase(std::remove( - agent_config_.favorite_models.begin(), - agent_config_.favorite_models.end(), info.name), - agent_config_.favorite_models.end()); - agent_config_.model_chain.erase(std::remove( - agent_config_.model_chain.begin(), agent_config_.model_chain.end(), - info.name), - agent_config_.model_chain.end()); - } else { - agent_config_.favorite_models.push_back(info.name); - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(is_favorite ? "Remove from favorites" - : "Favorite model"); - } - - ImGui::SameLine(); - std::string preset_id = absl::StrFormat("Preset##%s", info.name); - if (ImGui::SmallButton(ICON_MD_NOTE_ADD)) { - AgentConfigState::ModelPreset preset; - preset.name = info.name; - preset.model = info.name; - preset.host = agent_config_.ollama_host; - preset.tags = {"ollama"}; - preset.last_used = absl::Now(); - agent_config_.model_presets.push_back(std::move(preset)); - if (toast_manager_) { - toast_manager_->Show("Preset captured from Ollama roster", - ToastType::kSuccess, 2.0f); - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Capture preset from this model"); - } - - std::string size_label = - info.parameter_size.empty() - ? FormatByteSize(info.size_bytes) - : info.parameter_size; - ImGui::TextDisabled("%s • %s", size_label.c_str(), - info.quantization_level.c_str()); - if (!info.family.empty()) { - ImGui::TextDisabled("Family: %s", info.family.c_str()); - } - if (info.modified_at != absl::InfinitePast()) { - ImGui::TextDisabled("Updated %s", - FormatRelativeTime(info.modified_at).c_str()); - } - ImGui::Separator(); - } - } else { - for (const auto& model_name : ollama_model_cache_) { - std::string lower = absl::AsciiStrToLower(model_name); - if (!filter.empty() && lower.find(filter) == std::string::npos) { - continue; - } - - bool is_selected = agent_config_.ai_model == model_name; - if (ImGui::Selectable(model_name.c_str(), is_selected)) { - agent_config_.ai_model = model_name; - std::snprintf(agent_config_.model_buffer, - sizeof(agent_config_.model_buffer), "%s", - model_name.c_str()); - } - ImGui::SameLine(); - bool is_favorite = std::find(agent_config_.favorite_models.begin(), - agent_config_.favorite_models.end(), - model_name) != - agent_config_.favorite_models.end(); - if (ImGui::SmallButton(is_favorite ? ICON_MD_STAR : ICON_MD_STAR_BORDER)) { - if (is_favorite) { - agent_config_.favorite_models.erase(std::remove( - agent_config_.favorite_models.begin(), - agent_config_.favorite_models.end(), model_name), - agent_config_.favorite_models.end()); - agent_config_.model_chain.erase(std::remove( - agent_config_.model_chain.begin(), agent_config_.model_chain.end(), - model_name), - agent_config_.model_chain.end()); - } else { - agent_config_.favorite_models.push_back(model_name); - } - } - ImGui::Separator(); - } - } - ImGui::EndChild(); - ImGui::PopStyleColor(); - - if (last_model_refresh_ != absl::InfinitePast()) { - double seconds = - absl::ToDoubleSeconds(absl::Now() - last_model_refresh_); - ImGui::TextDisabled("Last refresh %.0fs ago", seconds); - } else { - ImGui::TextDisabled("Models not refreshed yet"); - } - - RenderChainModeControls(); } else if (agent_config_.ai_provider == "gemini") { - if (ImGui::InputTextWithHint("##gemini_model", "gemini-2.5-flash", - agent_config_.model_buffer, - IM_ARRAYSIZE(agent_config_.model_buffer))) { - agent_config_.ai_model = agent_config_.model_buffer; - } if (ImGui::InputTextWithHint("##gemini_key", "API key...", agent_config_.gemini_key_buffer, - IM_ARRAYSIZE(agent_config_.gemini_key_buffer), + IM_ARRAYSIZE(agent_config_.gemini_key_buffer), ImGuiInputTextFlags_Password)) { agent_config_.gemini_api_key = agent_config_.gemini_key_buffer; } @@ -2270,9 +2193,173 @@ void AgentChatWidget::RenderModelConfigControls() { } } + // Unified Model Selection + if (ImGui::InputTextWithHint("##ai_model", "Model name...", + agent_config_.model_buffer, + IM_ARRAYSIZE(agent_config_.model_buffer))) { + agent_config_.ai_model = agent_config_.model_buffer; + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f); + ImGui::InputTextWithHint("##model_search", "Search all models...", + model_search_buffer_, + IM_ARRAYSIZE(model_search_buffer_)); + ImGui::SameLine(); + if (ImGui::Button(models_loading_ ? ICON_MD_SYNC : ICON_MD_REFRESH)) { + RefreshModels(); + } + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.14f, 0.9f)); + ImGui::BeginChild("UnifiedModelList", ImVec2(0, 140), true); + std::string filter = absl::AsciiStrToLower(model_search_buffer_); + + if (model_info_cache_.empty() && model_name_cache_.empty()) { + ImGui::TextDisabled("No cached models. Refresh to discover."); + } else { + // Prefer rich metadata if available + if (!model_info_cache_.empty()) { + for (const auto& info : model_info_cache_) { + std::string lower_name = absl::AsciiStrToLower(info.name); + std::string lower_provider = absl::AsciiStrToLower(info.provider); + + if (!filter.empty()) { + bool match = lower_name.find(filter) != std::string::npos || + lower_provider.find(filter) != std::string::npos; + if (!match && !info.parameter_size.empty()) { + match = absl::AsciiStrToLower(info.parameter_size).find(filter) != + std::string::npos; + } + if (!match) + continue; + } + + bool is_selected = agent_config_.ai_model == info.name; + // Display provider badge + std::string label = + absl::StrFormat("%s [%s]", info.name, info.provider); + + if (ImGui::Selectable(label.c_str(), is_selected)) { + agent_config_.ai_model = info.name; + agent_config_.ai_provider = info.provider; + std::snprintf(agent_config_.model_buffer, + sizeof(agent_config_.model_buffer), "%s", + info.name.c_str()); + std::snprintf(agent_config_.provider_buffer, + sizeof(agent_config_.provider_buffer), "%s", + info.provider.c_str()); + } + + // Favorite button + ImGui::SameLine(); + bool is_favorite = + std::find(agent_config_.favorite_models.begin(), + agent_config_.favorite_models.end(), + info.name) != agent_config_.favorite_models.end(); + if (ImGui::SmallButton(is_favorite ? ICON_MD_STAR + : ICON_MD_STAR_BORDER)) { + if (is_favorite) { + agent_config_.favorite_models.erase( + std::remove(agent_config_.favorite_models.begin(), + agent_config_.favorite_models.end(), info.name), + agent_config_.favorite_models.end()); + agent_config_.model_chain.erase( + std::remove(agent_config_.model_chain.begin(), + agent_config_.model_chain.end(), info.name), + agent_config_.model_chain.end()); + } else { + agent_config_.favorite_models.push_back(info.name); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(is_favorite ? "Remove from favorites" + : "Favorite model"); + } + + // Preset button + ImGui::SameLine(); + if (ImGui::SmallButton(ICON_MD_NOTE_ADD)) { + AgentConfigState::ModelPreset preset; + preset.name = info.name; + preset.model = info.name; + preset.host = + (info.provider == "ollama") ? agent_config_.ollama_host : ""; + preset.tags = {info.provider}; + preset.last_used = absl::Now(); + agent_config_.model_presets.push_back(std::move(preset)); + if (toast_manager_) { + toast_manager_->Show("Preset captured", ToastType::kSuccess, 2.0f); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Capture preset from this model"); + } + + // Metadata + std::string size_label = info.parameter_size.empty() + ? FormatByteSize(info.size_bytes) + : info.parameter_size; + ImGui::TextDisabled("%s • %s", size_label.c_str(), + info.quantization.c_str()); + if (!info.family.empty()) { + ImGui::TextDisabled("Family: %s", info.family.c_str()); + } + // ModifiedAt not available in ModelInfo yet + ImGui::Separator(); + } + } else { + // Fallback to just names + for (const auto& model_name : model_name_cache_) { + std::string lower = absl::AsciiStrToLower(model_name); + if (!filter.empty() && lower.find(filter) == std::string::npos) { + continue; + } + + bool is_selected = agent_config_.ai_model == model_name; + if (ImGui::Selectable(model_name.c_str(), is_selected)) { + agent_config_.ai_model = model_name; + std::snprintf(agent_config_.model_buffer, + sizeof(agent_config_.model_buffer), "%s", + model_name.c_str()); + } + + ImGui::SameLine(); + bool is_favorite = + std::find(agent_config_.favorite_models.begin(), + agent_config_.favorite_models.end(), + model_name) != agent_config_.favorite_models.end(); + if (ImGui::SmallButton(is_favorite ? ICON_MD_STAR + : ICON_MD_STAR_BORDER)) { + if (is_favorite) { + agent_config_.favorite_models.erase( + std::remove(agent_config_.favorite_models.begin(), + agent_config_.favorite_models.end(), model_name), + agent_config_.favorite_models.end()); + } else { + agent_config_.favorite_models.push_back(model_name); + } + } + ImGui::Separator(); + } + } + } + ImGui::EndChild(); + ImGui::PopStyleColor(); + + if (last_model_refresh_ != absl::InfinitePast()) { + double seconds = absl::ToDoubleSeconds(absl::Now() - last_model_refresh_); + ImGui::TextDisabled("Last refresh %.0fs ago", seconds); + } else { + ImGui::TextDisabled("Models not refreshed yet"); + } + + if (agent_config_.ai_provider == "ollama") { + RenderChainModeControls(); + } + if (!agent_config_.favorite_models.empty()) { - ImGui::Separator(); - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_STAR " Favorites"); + ImGui::Separator(); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_STAR " Favorites"); for (size_t i = 0; i < agent_config_.favorite_models.size(); ++i) { auto& favorite = agent_config_.favorite_models[i]; ImGui::PushID(static_cast(i)); @@ -2283,12 +2370,12 @@ void AgentChatWidget::RenderModelConfigControls() { sizeof(agent_config_.model_buffer), "%s", favorite.c_str()); } - ImGui::SameLine(); + ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_CLOSE)) { - agent_config_.model_chain.erase(std::remove( - agent_config_.model_chain.begin(), agent_config_.model_chain.end(), - favorite), - agent_config_.model_chain.end()); + agent_config_.model_chain.erase( + std::remove(agent_config_.model_chain.begin(), + agent_config_.model_chain.end(), favorite), + agent_config_.model_chain.end()); agent_config_.favorite_models.erase( agent_config_.favorite_models.begin() + i); ImGui::PopID(); @@ -2344,13 +2431,14 @@ void AgentChatWidget::RenderModelDeck() { ApplyModelPreset(preset); } ImGui::SameLine(); - if (ImGui::SmallButton(preset.pinned ? ICON_MD_STAR : ICON_MD_STAR_BORDER)) { + if (ImGui::SmallButton(preset.pinned ? ICON_MD_STAR + : ICON_MD_STAR_BORDER)) { preset.pinned = !preset.pinned; } ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_DELETE)) { - agent_config_.model_presets.erase( - agent_config_.model_presets.begin() + i); + agent_config_.model_presets.erase(agent_config_.model_presets.begin() + + i); if (active_model_preset_index_ == i) { active_model_preset_index_ = -1; } @@ -2404,13 +2492,14 @@ void AgentChatWidget::RenderToolingControls() { "Room + sprite inspection"}, {"Overworld", &agent_config_.tool_config.overworld, "Map + entrance analysis"}, - {"Dialogue", &agent_config_.tool_config.dialogue, - "Dialogue list/search"}, + {"Dialogue", &agent_config_.tool_config.dialogue, "Dialogue list/search"}, {"Messages", &agent_config_.tool_config.messages, "Message table + ROM text"}, - {"GUI Automation", &agent_config_.tool_config.gui, "GUI automation tools"}, + {"GUI Automation", &agent_config_.tool_config.gui, + "GUI automation tools"}, {"Music", &agent_config_.tool_config.music, "Music info & tracks"}, - {"Sprite", &agent_config_.tool_config.sprite, "Sprite palette/properties"}, + {"Sprite", &agent_config_.tool_config.sprite, + "Sprite palette/properties"}, {"Emulator", &agent_config_.tool_config.emulator, "Emulator controls"}}; int columns = 2; @@ -2459,8 +2548,8 @@ void AgentChatWidget::RenderPersonaSummary() { ImGui::BulletText("%s", goal.c_str()); } } - ImGui::TextDisabled("Applied %s", - FormatRelativeTime(persona_profile_.applied_at).c_str()); + ImGui::TextDisabled( + "Applied %s", FormatRelativeTime(persona_profile_.applied_at).c_str()); } ImGui::EndChild(); AgentUI::PopPanelStyle(); @@ -2470,8 +2559,8 @@ void AgentChatWidget::RenderPersonaSummary() { void AgentChatWidget::ApplyModelPreset( const AgentConfigState::ModelPreset& preset) { agent_config_.ai_provider = "ollama"; - agent_config_.ollama_host = preset.host.empty() ? agent_config_.ollama_host - : preset.host; + agent_config_.ollama_host = + preset.host.empty() ? agent_config_.ollama_host : preset.host; agent_config_.ai_model = preset.model; std::snprintf(agent_config_.model_buffer, sizeof(agent_config_.model_buffer), "%s", agent_config_.ai_model.c_str()); @@ -2483,8 +2572,7 @@ void AgentChatWidget::ApplyModelPreset( } void AgentChatWidget::ApplyBuilderPersona( - const std::string& persona_notes, - const std::vector& goals) { + const std::string& persona_notes, const std::vector& goals) { persona_profile_.notes = persona_notes; persona_profile_.goals = goals; persona_profile_.applied_at = absl::Now(); @@ -2587,11 +2675,11 @@ void AgentChatWidget::ApplyHistoryAgentConfig( std::snprintf(agent_config_.model_buffer, sizeof(agent_config_.model_buffer), "%s", agent_config_.ai_model.c_str()); std::snprintf(agent_config_.ollama_host_buffer, - sizeof(agent_config_.ollama_host_buffer), - "%s", agent_config_.ollama_host.c_str()); + sizeof(agent_config_.ollama_host_buffer), "%s", + agent_config_.ollama_host.c_str()); std::snprintf(agent_config_.gemini_key_buffer, - sizeof(agent_config_.gemini_key_buffer), - "%s", agent_config_.gemini_api_key.c_str()); + sizeof(agent_config_.gemini_key_buffer), "%s", + agent_config_.gemini_api_key.c_str()); UpdateAgentConfig(agent_config_); } @@ -2612,8 +2700,7 @@ void AgentChatWidget::RenderChainModeControls() { const char* labels[] = {"Disabled", "Round Robin", "Consensus"}; int mode = static_cast(agent_config_.chain_mode); if (ImGui::Combo("Chain Mode", &mode, labels, IM_ARRAYSIZE(labels))) { - agent_config_.chain_mode = - static_cast(mode); + agent_config_.chain_mode = static_cast(mode); } if (agent_config_.chain_mode == AgentConfigState::ChainMode::kDisabled) { @@ -2632,10 +2719,10 @@ void AgentChatWidget::RenderChainModeControls() { favorite) != agent_config_.model_chain.end(); if (ImGui::Selectable(favorite.c_str(), selected)) { if (selected) { - agent_config_.model_chain.erase(std::remove( - agent_config_.model_chain.begin(), agent_config_.model_chain.end(), - favorite), - agent_config_.model_chain.end()); + agent_config_.model_chain.erase( + std::remove(agent_config_.model_chain.begin(), + agent_config_.model_chain.end(), favorite), + agent_config_.model_chain.end()); } else { agent_config_.model_chain.push_back(favorite); } @@ -2650,8 +2737,7 @@ void AgentChatWidget::RenderZ3EDCommandPanel() { // Dense header (no collapsing) ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.14f, 0.12f, 0.18f, 0.95f)); - ImGui::BeginChild("Z3ED_CommandsChild", ImVec2(0, 100), - true); + ImGui::BeginChild("Z3ED_CommandsChild", ImVec2(0, 100), true); ImGui::TextColored(command_color, ICON_MD_TERMINAL " Commands"); ImGui::Separator(); @@ -2957,14 +3043,16 @@ void AgentChatWidget::RenderHarnessPanel() { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.16f, 0.22f, 0.95f)); ImGui::BeginChild("HarnessPanel", ImVec2(0, 220), true); - ImGui::TextColored(ImVec4(0.392f, 0.863f, 1.0f, 1.0f), ICON_MD_PLAY_CIRCLE " Harness Automation"); + ImGui::TextColored(ImVec4(0.392f, 0.863f, 1.0f, 1.0f), + ICON_MD_PLAY_CIRCLE " Harness Automation"); ImGui::Separator(); ImGui::TextDisabled("Shared automation pipeline between CLI + Agent Chat"); ImGui::Spacing(); if (ImGui::BeginTable("HarnessActions", 2, ImGuiTableFlags_BordersInnerV)) { - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 170.0f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, + 170.0f); ImGui::TableSetupColumn("Telemetry", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextRow(); @@ -2978,7 +3066,8 @@ void AgentChatWidget::RenderHarnessPanel() { if (!has_callbacks) { ImGui::TextDisabled("Automation bridge not available"); - ImGui::TextWrapped("Hook up AutomationCallbacks via EditorManager to enable controls."); + ImGui::TextWrapped( + "Hook up AutomationCallbacks via EditorManager to enable controls."); } else { if (automation_callbacks_.open_harness_dashboard && ImGui::Button(ICON_MD_DASHBOARD " Dashboard", ImVec2(-FLT_MIN, 0))) { @@ -2986,7 +3075,8 @@ void AgentChatWidget::RenderHarnessPanel() { } if (automation_callbacks_.replay_last_plan && - ImGui::Button(ICON_MD_REPLAY " Replay Last Plan", ImVec2(-FLT_MIN, 0))) { + ImGui::Button(ICON_MD_REPLAY " Replay Last Plan", + ImVec2(-FLT_MIN, 0))) { automation_callbacks_.replay_last_plan(); } @@ -2999,7 +3089,8 @@ void AgentChatWidget::RenderHarnessPanel() { ImGui::Spacing(); ImGui::TextDisabled("Proposal tools"); if (!pending_focus_proposal_id_.empty()) { - ImGui::TextWrapped("Proposal %s active", pending_focus_proposal_id_.c_str()); + ImGui::TextWrapped("Proposal %s active", + pending_focus_proposal_id_.c_str()); if (ImGui::SmallButton(ICON_MD_VISIBILITY " View Proposal")) { automation_callbacks_.focus_proposal(pending_focus_proposal_id_); } @@ -3015,18 +3106,24 @@ void AgentChatWidget::RenderHarnessPanel() { ImGui::TableSetColumnIndex(1); ImGui::BeginGroup(); - ImGui::TextColored(ImVec4(0.6f, 0.78f, 1.0f, 1.0f), ICON_MD_QUERY_STATS " Live Telemetry"); + ImGui::TextColored(ImVec4(0.6f, 0.78f, 1.0f, 1.0f), + ICON_MD_QUERY_STATS " Live Telemetry"); ImGui::Spacing(); if (!automation_state_.recent_tests.empty()) { - const float row_height = ImGui::GetTextLineHeightWithSpacing() * 2.0f + 6.0f; + const float row_height = + ImGui::GetTextLineHeightWithSpacing() * 2.0f + 6.0f; if (ImGui::BeginTable("HarnessTelemetryRows", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Test", ImGuiTableColumnFlags_WidthStretch, 0.3f); - ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 90.0f); - ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch, 0.5f); - ImGui::TableSetupColumn("Updated", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("Test", ImGuiTableColumnFlags_WidthStretch, + 0.3f); + ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, + 90.0f); + ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch, + 0.5f); + ImGui::TableSetupColumn("Updated", ImGuiTableColumnFlags_WidthFixed, + 120.0f); ImGui::TableHeadersRow(); for (const auto& entry : automation_state_.recent_tests) { @@ -3054,7 +3151,8 @@ void AgentChatWidget::RenderHarnessPanel() { if (entry.updated_at == absl::InfinitePast()) { ImGui::TextDisabled("-"); } else { - const double seconds_ago = absl::ToDoubleSeconds(absl::Now() - entry.updated_at); + const double seconds_ago = + absl::ToDoubleSeconds(absl::Now() - entry.updated_at); ImGui::Text("%.0fs ago", seconds_ago); } } @@ -3540,7 +3638,8 @@ void AgentChatWidget::LoadAgentSettingsFromProject( } } -void AgentChatWidget::SaveAgentSettingsToProject(project::YazeProject& project) { +void AgentChatWidget::SaveAgentSettingsToProject( + project::YazeProject& project) { // Save AI provider settings to project project.agent_settings.ai_provider = agent_config_.ai_provider; project.agent_settings.ai_model = agent_config_.ai_model; @@ -3608,7 +3707,8 @@ void AgentChatWidget::UpdateHarnessTelemetry( *it = telemetry; } else { if (automation_state_.recent_tests.size() >= 16) { - automation_state_.recent_tests.erase(automation_state_.recent_tests.begin()); + automation_state_.recent_tests.erase( + automation_state_.recent_tests.begin()); } automation_state_.recent_tests.push_back(telemetry); } @@ -3631,7 +3731,7 @@ void AgentChatWidget::PollAutomationStatus() { absl::Time now = absl::Now(); absl::Duration elapsed = now - automation_state_.last_poll; - + if (elapsed < absl::Seconds(automation_state_.refresh_interval_seconds)) { return; } @@ -3646,11 +3746,11 @@ void AgentChatWidget::PollAutomationStatus() { // Notify on status change if (was_connected != automation_state_.harness_connected && toast_manager_) { if (automation_state_.harness_connected) { - toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Automation harness connected", - ToastType::kSuccess, 2.0f); + toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Automation harness connected", + ToastType::kSuccess, 2.0f); } else { - toast_manager_->Show(ICON_MD_WARNING " Automation harness disconnected", - ToastType::kWarning, 2.0f); + toast_manager_->Show(ICON_MD_WARNING " Automation harness disconnected", + ToastType::kWarning, 2.0f); } } } @@ -3661,7 +3761,7 @@ bool AgentChatWidget::CheckHarnessConnection() { // Attempt to get harness summaries from TestManager // If this succeeds, the harness infrastructure is working auto summaries = test::TestManager::Get().ListHarnessTestSummaries(); - + // If we get here, the test manager is operational // In a real implementation, you might want to ping the gRPC server automation_state_.connection_attempts = 0; @@ -3679,71 +3779,75 @@ void AgentChatWidget::SyncHistoryToPopup() { if (!chat_history_popup_) { return; } - + // Get the current chat history from the agent service const auto& history = agent_service_.GetHistory(); - + // Update the popup with the latest history chat_history_popup_->UpdateHistory(history); } // Screenshot Preview Implementation -void AgentChatWidget::LoadScreenshotPreview(const std::filesystem::path& image_path) { +void AgentChatWidget::LoadScreenshotPreview( + const std::filesystem::path& image_path) { // Unload any existing preview first UnloadScreenshotPreview(); - + // Load the image using SDL SDL_Surface* surface = SDL_LoadBMP(image_path.string().c_str()); if (!surface) { if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to load image: %s", SDL_GetError()), - ToastType::kError, 3.0f); + toast_manager_->Show( + absl::StrFormat("Failed to load image: %s", SDL_GetError()), + ToastType::kError, 3.0f); } return; } - + // Get the renderer from ImGui backend ImGuiIO& io = ImGui::GetIO(); auto* backend_data = static_cast(io.BackendRendererUserData); SDL_Renderer* renderer = nullptr; - + if (backend_data) { // Assuming SDL renderer backend // The backend data structure has renderer as first member renderer = *reinterpret_cast(backend_data); } - + if (!renderer) { SDL_FreeSurface(surface); if (toast_manager_) { - toast_manager_->Show("Failed to get SDL renderer", ToastType::kError, 3.0f); + toast_manager_->Show("Failed to get SDL renderer", ToastType::kError, + 3.0f); } return; } - + // Create texture from surface SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); if (!texture) { SDL_FreeSurface(surface); if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to create texture: %s", SDL_GetError()), - ToastType::kError, 3.0f); + toast_manager_->Show( + absl::StrFormat("Failed to create texture: %s", SDL_GetError()), + ToastType::kError, 3.0f); } return; } - + // Store texture info multimodal_state_.preview.texture_id = reinterpret_cast(texture); multimodal_state_.preview.width = surface->w; multimodal_state_.preview.height = surface->h; multimodal_state_.preview.loaded = true; multimodal_state_.preview.show_preview = true; - + SDL_FreeSurface(surface); - + if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Screenshot preview loaded (%dx%d)", - surface->w, surface->h), + toast_manager_->Show(absl::StrFormat("Screenshot preview loaded (%dx%d)", + surface->w, surface->h), ToastType::kSuccess, 2.0f); } } @@ -3751,7 +3855,8 @@ void AgentChatWidget::LoadScreenshotPreview(const std::filesystem::path& image_p void AgentChatWidget::UnloadScreenshotPreview() { if (multimodal_state_.preview.texture_id != nullptr) { // Destroy the SDL texture - SDL_Texture* texture = reinterpret_cast(multimodal_state_.preview.texture_id); + SDL_Texture* texture = + reinterpret_cast(multimodal_state_.preview.texture_id); SDL_DestroyTexture(texture); multimodal_state_.preview.texture_id = nullptr; } @@ -3767,29 +3872,32 @@ void AgentChatWidget::RenderScreenshotPreview() { } const auto& theme = AgentUI::GetTheme(); - + // Display filename - std::string filename = multimodal_state_.last_capture_path->filename().string(); + std::string filename = + multimodal_state_.last_capture_path->filename().string(); ImGui::TextColored(theme.text_secondary_color, "%s", filename.c_str()); - + // Preview controls if (ImGui::SmallButton(ICON_MD_CLOSE " Hide")) { multimodal_state_.preview.show_preview = false; } ImGui::SameLine(); - - if (multimodal_state_.preview.loaded && multimodal_state_.preview.texture_id) { + + if (multimodal_state_.preview.loaded && + multimodal_state_.preview.texture_id) { // Display the actual texture - ImVec2 preview_size( - multimodal_state_.preview.width * multimodal_state_.preview.preview_scale, - multimodal_state_.preview.height * multimodal_state_.preview.preview_scale - ); + ImVec2 preview_size(multimodal_state_.preview.width * + multimodal_state_.preview.preview_scale, + multimodal_state_.preview.height * + multimodal_state_.preview.preview_scale); ImGui::Image(multimodal_state_.preview.texture_id, preview_size); - + // Scale slider ImGui::SetNextItemWidth(150); - ImGui::SliderFloat("##preview_scale", &multimodal_state_.preview.preview_scale, - 0.1f, 2.0f, "Scale: %.1fx"); + ImGui::SliderFloat("##preview_scale", + &multimodal_state_.preview.preview_scale, 0.1f, 2.0f, + "Scale: %.1fx"); } else { // Placeholder when texture not loaded ImGui::BeginChild("PreviewPlaceholder", ImVec2(200, 150), true); @@ -3806,9 +3914,9 @@ void AgentChatWidget::RenderScreenshotPreview() { void AgentChatWidget::BeginRegionSelection() { multimodal_state_.region_selection.active = true; multimodal_state_.region_selection.dragging = false; - + if (toast_manager_) { - toast_manager_->Show(ICON_MD_CROP " Drag to select region", + toast_manager_->Show(ICON_MD_CROP " Drag to select region", ToastType::kInfo, 3.0f); } } @@ -3822,78 +3930,70 @@ void AgentChatWidget::HandleRegionSelection() { ImGuiViewport* viewport = ImGui::GetMainViewport(); ImVec2 viewport_pos = viewport->Pos; ImVec2 viewport_size = viewport->Size; - + // Draw semi-transparent overlay ImDrawList* draw_list = ImGui::GetForegroundDrawList(); ImVec2 overlay_min = viewport_pos; - ImVec2 overlay_max = ImVec2(viewport_pos.x + viewport_size.x, - viewport_pos.y + viewport_size.y); - - draw_list->AddRectFilled(overlay_min, overlay_max, - IM_COL32(0, 0, 0, 100)); - + ImVec2 overlay_max = ImVec2(viewport_pos.x + viewport_size.x, + viewport_pos.y + viewport_size.y); + + draw_list->AddRectFilled(overlay_min, overlay_max, IM_COL32(0, 0, 0, 100)); + // Handle mouse input for region selection ImGuiIO& io = ImGui::GetIO(); ImVec2 mouse_pos = io.MousePos; - + // Start dragging - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !multimodal_state_.region_selection.dragging) { multimodal_state_.region_selection.dragging = true; multimodal_state_.region_selection.start_pos = mouse_pos; multimodal_state_.region_selection.end_pos = mouse_pos; } - + // Update drag - if (multimodal_state_.region_selection.dragging && + if (multimodal_state_.region_selection.dragging && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { multimodal_state_.region_selection.end_pos = mouse_pos; - + // Calculate selection rectangle ImVec2 start = multimodal_state_.region_selection.start_pos; ImVec2 end = multimodal_state_.region_selection.end_pos; - - multimodal_state_.region_selection.selection_min = ImVec2( - std::min(start.x, end.x), - std::min(start.y, end.y) - ); - - multimodal_state_.region_selection.selection_max = ImVec2( - std::max(start.x, end.x), - std::max(start.y, end.y) - ); - + + multimodal_state_.region_selection.selection_min = + ImVec2(std::min(start.x, end.x), std::min(start.y, end.y)); + + multimodal_state_.region_selection.selection_max = + ImVec2(std::max(start.x, end.x), std::max(start.y, end.y)); + // Draw selection rectangle - draw_list->AddRect( - multimodal_state_.region_selection.selection_min, - multimodal_state_.region_selection.selection_max, - IM_COL32(100, 180, 255, 255), 0.0f, 0, 2.0f - ); - + draw_list->AddRect(multimodal_state_.region_selection.selection_min, + multimodal_state_.region_selection.selection_max, + IM_COL32(100, 180, 255, 255), 0.0f, 0, 2.0f); + // Draw dimensions label - float width = multimodal_state_.region_selection.selection_max.x - + float width = multimodal_state_.region_selection.selection_max.x - multimodal_state_.region_selection.selection_min.x; - float height = multimodal_state_.region_selection.selection_max.y - + float height = multimodal_state_.region_selection.selection_max.y - multimodal_state_.region_selection.selection_min.y; - + std::string dimensions = absl::StrFormat("%.0f x %.0f", width, height); - ImVec2 label_pos = ImVec2( - multimodal_state_.region_selection.selection_min.x + 5, - multimodal_state_.region_selection.selection_min.y + 5 - ); - - draw_list->AddText(label_pos, IM_COL32(255, 255, 255, 255), + ImVec2 label_pos = + ImVec2(multimodal_state_.region_selection.selection_min.x + 5, + multimodal_state_.region_selection.selection_min.y + 5); + + draw_list->AddText(label_pos, IM_COL32(255, 255, 255, 255), dimensions.c_str()); } - + // End dragging - if (multimodal_state_.region_selection.dragging && + if (multimodal_state_.region_selection.dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { multimodal_state_.region_selection.dragging = false; CaptureSelectedRegion(); multimodal_state_.region_selection.active = false; } - + // Cancel on Escape if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { multimodal_state_.region_selection.active = false; @@ -3902,10 +4002,10 @@ void AgentChatWidget::HandleRegionSelection() { toast_manager_->Show("Region selection cancelled", ToastType::kInfo); } } - + // Instructions overlay ImVec2 text_pos = ImVec2(viewport_pos.x + 20, viewport_pos.y + 20); - draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), + draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), "Drag to select region (ESC to cancel)"); } @@ -3913,10 +4013,10 @@ void AgentChatWidget::CaptureSelectedRegion() { // Calculate region bounds ImVec2 min = multimodal_state_.region_selection.selection_min; ImVec2 max = multimodal_state_.region_selection.selection_max; - + float width = max.x - min.x; float height = max.y - min.y; - + // Validate selection if (width < 10 || height < 10) { if (toast_manager_) { @@ -3924,103 +4024,111 @@ void AgentChatWidget::CaptureSelectedRegion() { } return; } - + // Get the renderer from ImGui backend ImGuiIO& io = ImGui::GetIO(); auto* backend_data = static_cast(io.BackendRendererUserData); SDL_Renderer* renderer = nullptr; - + if (backend_data) { renderer = *reinterpret_cast(backend_data); } - + if (!renderer) { if (toast_manager_) { - toast_manager_->Show("Failed to get SDL renderer", ToastType::kError, 3.0f); + toast_manager_->Show("Failed to get SDL renderer", ToastType::kError, + 3.0f); } return; } - + // Get renderer size int full_width = 0; int full_height = 0; if (SDL_GetRendererOutputSize(renderer, &full_width, &full_height) != 0) { if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()), - ToastType::kError, 3.0f); + toast_manager_->Show( + absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()), + ToastType::kError, 3.0f); } return; } - + // Clamp region to renderer bounds int capture_x = std::max(0, static_cast(min.x)); int capture_y = std::max(0, static_cast(min.y)); int capture_width = std::min(static_cast(width), full_width - capture_x); - int capture_height = std::min(static_cast(height), full_height - capture_y); - + int capture_height = + std::min(static_cast(height), full_height - capture_y); + if (capture_width <= 0 || capture_height <= 0) { if (toast_manager_) { toast_manager_->Show("Invalid capture region", ToastType::kError); } return; } - + // Create surface for the capture region - SDL_Surface* surface = SDL_CreateRGBSurface(0, capture_width, capture_height, - 32, 0x00FF0000, 0x0000FF00, - 0x000000FF, 0xFF000000); + SDL_Surface* surface = + SDL_CreateRGBSurface(0, capture_width, capture_height, 32, 0x00FF0000, + 0x0000FF00, 0x000000FF, 0xFF000000); if (!surface) { if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to create surface: %s", SDL_GetError()), - ToastType::kError, 3.0f); + toast_manager_->Show( + absl::StrFormat("Failed to create surface: %s", SDL_GetError()), + ToastType::kError, 3.0f); } return; } - + // Read pixels from the selected region SDL_Rect region_rect = {capture_x, capture_y, capture_width, capture_height}; if (SDL_RenderReadPixels(renderer, ®ion_rect, SDL_PIXELFORMAT_ARGB8888, surface->pixels, surface->pitch) != 0) { SDL_FreeSurface(surface); if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to read pixels: %s", SDL_GetError()), - ToastType::kError, 3.0f); + toast_manager_->Show( + absl::StrFormat("Failed to read pixels: %s", SDL_GetError()), + ToastType::kError, 3.0f); } return; } - + // Generate output path - std::filesystem::path screenshot_dir = std::filesystem::temp_directory_path() / "yaze" / "screenshots"; + std::filesystem::path screenshot_dir = + std::filesystem::temp_directory_path() / "yaze" / "screenshots"; std::error_code ec; std::filesystem::create_directories(screenshot_dir, ec); - + const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now()); - std::filesystem::path output_path = screenshot_dir / - std::filesystem::path(absl::StrFormat("region_%lld.bmp", static_cast(timestamp_ms))); - + std::filesystem::path output_path = + screenshot_dir / + std::filesystem::path(absl::StrFormat( + "region_%lld.bmp", static_cast(timestamp_ms))); + // Save the cropped image if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) { SDL_FreeSurface(surface); if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Failed to save screenshot: %s", SDL_GetError()), - ToastType::kError, 3.0f); + toast_manager_->Show( + absl::StrFormat("Failed to save screenshot: %s", SDL_GetError()), + ToastType::kError, 3.0f); } return; } - + SDL_FreeSurface(surface); - + // Store the capture path and load preview multimodal_state_.last_capture_path = output_path; LoadScreenshotPreview(output_path); - + if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Region captured: %dx%d", capture_width, capture_height), - ToastType::kSuccess, 3.0f - ); + toast_manager_->Show(absl::StrFormat("Region captured: %dx%d", + capture_width, capture_height), + ToastType::kSuccess, 3.0f); } - + // Call the Gemini callback if available if (multimodal_callbacks_.send_to_gemini) { std::filesystem::path captured_path; diff --git a/src/app/editor/agent/agent_chat_widget.h b/src/app/editor/agent/agent_chat_widget.h index ab00ff22..278fe941 100644 --- a/src/app/editor/agent/agent_chat_widget.h +++ b/src/app/editor/agent/agent_chat_widget.h @@ -12,11 +12,11 @@ #include "absl/time/time.h" #include "app/editor/agent/agent_chat_history_codec.h" #include "app/gui/widgets/text_editor.h" -#include "cli/service/ai/ollama_ai_service.h" -#include "cli/service/agent/conversational_agent_service.h" #include "cli/service/agent/advanced_routing.h" #include "cli/service/agent/agent_pretraining.h" +#include "cli/service/agent/conversational_agent_service.h" #include "cli/service/agent/prompt_manager.h" +#include "cli/service/ai/ollama_ai_service.h" #include "core/project.h" namespace yaze { @@ -50,7 +50,7 @@ class AgentChatHistoryPopup; class AgentChatWidget { public: AgentChatWidget(); - + void Draw(); void SetRomContext(Rom* rom); @@ -62,15 +62,19 @@ class AgentChatWidget { std::vector participants; }; - std::function(const std::string&)> host_session; - std::function(const std::string&)> join_session; + std::function(const std::string&)> + host_session; + std::function(const std::string&)> + join_session; std::function leave_session; std::function()> refresh_session; }; struct MultimodalCallbacks { std::function capture_snapshot; - std::function send_to_gemini; + std::function + send_to_gemini; }; struct AutomationCallbacks { @@ -91,8 +95,10 @@ class AgentChatWidget { // Z3ED Command Callbacks struct Z3EDCommandCallbacks { std::function run_agent_task; - std::function(const std::string&)> plan_agent_task; - std::function(const std::string&)> diff_proposal; + std::function(const std::string&)> + plan_agent_task; + std::function(const std::string&)> + diff_proposal; std::function accept_proposal; std::function reject_proposal; std::function>()> list_proposals; @@ -101,12 +107,13 @@ class AgentChatWidget { // ROM Sync Callbacks struct RomSyncCallbacks { std::function()> generate_rom_diff; - std::function apply_rom_diff; + std::function + apply_rom_diff; std::function get_rom_hash; }; void RenderSnapshotPreviewPanel(); - + // Screenshot preview and region selection void LoadScreenshotPreview(const std::filesystem::path& image_path); void UnloadScreenshotPreview(); @@ -119,7 +126,7 @@ class AgentChatWidget { void SetToastManager(ToastManager* toast_manager); void SetProposalDrawer(ProposalDrawer* drawer); - + void SetChatHistoryPopup(AgentChatHistoryPopup* popup); void SetCollaborationCallbacks(const CollaborationCallbacks& callbacks) { @@ -135,7 +142,7 @@ class AgentChatWidget { void UpdateHarnessTelemetry(const AutomationTelemetry& telemetry); void SetLastPlanSummary(const std::string& summary); - + // Automation status polling void PollAutomationStatus(); bool CheckHarnessConnection(); @@ -152,8 +159,8 @@ class AgentChatWidget { bool is_active() const { return active_; } void set_active(bool active) { active_ = active; } enum class CollaborationMode { - kLocal = 0, // Filesystem-based collaboration - kNetwork = 1 // WebSocket-based collaboration + kLocal = 0, // Filesystem-based collaboration + kNetwork = 1 // WebSocket-based collaboration }; struct CollaborationState { @@ -282,20 +289,20 @@ class AgentChatWidget { bool command_running = false; char command_input_buffer[512] = {}; }; - + void SetPromptMode(cli::agent::PromptMode mode) { prompt_mode_ = mode; } cli::agent::PromptMode GetPromptMode() const { return prompt_mode_; } // Accessors for capture settings CaptureMode capture_mode() const { return multimodal_state_.capture_mode; } - const char* specific_window_name() const { - return multimodal_state_.specific_window_buffer; + const char* specific_window_name() const { + return multimodal_state_.specific_window_buffer; } // Agent configuration accessors const AgentConfigState& GetAgentConfig() const { return agent_config_; } void UpdateAgentConfig(const AgentConfigState& config); - + // Load agent settings from project void LoadAgentSettingsFromProject(const project::YazeProject& project); void SaveAgentSettingsToProject(project::YazeProject& project); @@ -303,7 +310,7 @@ class AgentChatWidget { // Collaboration history management (public so EditorManager can call them) void SwitchToSharedHistory(const std::string& session_id); void SwitchToLocalHistory(); - + // File editing void OpenFileInEditor(const std::string& filepath); void CreateNewFileInEditor(const std::string& filename); @@ -344,10 +351,12 @@ class AgentChatWidget { bool update_action_timestamp); void MarkHistoryDirty(); void PollSharedHistory(); // For real-time collaboration sync - void HandleRomSyncReceived(const std::string& diff_data, const std::string& rom_hash); - void HandleSnapshotReceived(const std::string& snapshot_data, const std::string& snapshot_type); + void HandleRomSyncReceived(const std::string& diff_data, + const std::string& rom_hash); + void HandleSnapshotReceived(const std::string& snapshot_data, + const std::string& snapshot_type); void HandleProposalReceived(const std::string& proposal_data); - void RefreshOllamaModels(); + void RefreshModels(); cli::AIServiceConfig BuildAIServiceConfig() const; void ApplyToolPreferences(); void ApplyHistoryAgentConfig( @@ -355,7 +364,7 @@ class AgentChatWidget { AgentChatHistoryCodec::AgentConfigSnapshot BuildHistoryAgentConfig() const; void MarkPresetUsage(const std::string& model_name); void ApplyModelPreset(const AgentConfigState::ModelPreset& preset); - + // History synchronization void SyncHistoryToPopup(); @@ -363,7 +372,7 @@ class AgentChatWidget { bool waiting_for_response_ = false; float thinking_animation_ = 0.0f; std::string pending_message_; - + // Chat session management struct ChatSession { std::string id; @@ -376,20 +385,20 @@ class AgentChatWidget { std::filesystem::path history_path; absl::Time created_at = absl::Now(); absl::Time last_persist_time = absl::InfinitePast(); - + ChatSession(const std::string& session_id, const std::string& session_name) : id(session_id), name(session_name) {} }; - + void SaveChatSession(const ChatSession& session); void LoadChatSession(const std::string& session_id); void DeleteChatSession(const std::string& session_id); std::vector GetSavedSessions(); std::filesystem::path GetSessionsDirectory(); - + std::vector chat_sessions_; int active_session_index_ = 0; - + // Legacy single session support (will migrate to sessions) cli::agent::ConversationalAgentService agent_service_; char input_buffer_[1024]; @@ -407,7 +416,7 @@ class AgentChatWidget { AgentChatHistoryPopup* chat_history_popup_ = nullptr; std::string pending_focus_proposal_id_; absl::Time last_persist_time_ = absl::InfinitePast(); - + // Main state CollaborationState collaboration_state_; MultimodalState multimodal_state_; @@ -423,37 +432,38 @@ class AgentChatWidget { bool active = false; } persona_profile_; bool persona_highlight_active_ = false; - + // Callbacks CollaborationCallbacks collaboration_callbacks_; MultimodalCallbacks multimodal_callbacks_; AutomationCallbacks automation_callbacks_; Z3EDCommandCallbacks z3ed_callbacks_; RomSyncCallbacks rom_sync_callbacks_; - + // Input buffers char session_name_buffer_[64] = {}; char join_code_buffer_[64] = {}; char server_url_buffer_[256] = "ws://localhost:8765"; char multimodal_prompt_buffer_[256] = {}; - + // Timing absl::Time last_collaboration_action_ = absl::InfinitePast(); absl::Time last_shared_history_poll_ = absl::InfinitePast(); size_t last_known_history_size_ = 0; - + // UI state - int active_tab_ = 0; // 0=Chat, 1=Config, 2=Commands, 3=Collab, 4=ROM Sync, 5=Files, 6=Prompt + int active_tab_ = + 0; // 0=Chat, 1=Config, 2=Commands, 3=Collab, 4=ROM Sync, 5=Files, 6=Prompt bool show_agent_config_ = false; cli::agent::PromptMode prompt_mode_ = cli::agent::PromptMode::kStandard; bool show_z3ed_commands_ = false; bool show_rom_sync_ = false; bool show_snapshot_preview_ = false; std::vector snapshot_preview_data_; - + // Reactive UI colors ImVec4 collaboration_status_color_ = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); - + // File editing state struct FileEditorTab { std::string filepath; @@ -466,10 +476,10 @@ class AgentChatWidget { int active_file_tab_ = -1; // Model roster cache - std::vector ollama_model_info_cache_; - std::vector ollama_model_cache_; + std::vector model_info_cache_; + std::vector model_name_cache_; absl::Time last_model_refresh_ = absl::InfinitePast(); - bool ollama_models_loading_ = false; + bool models_loading_ = false; char model_search_buffer_[64] = {}; char new_preset_name_[64] = {}; int active_model_preset_index_ = -1; diff --git a/src/app/editor/agent/agent_collaboration_coordinator.cc b/src/app/editor/agent/agent_collaboration_coordinator.cc index 0ecfecf8..1e99d3b4 100644 --- a/src/app/editor/agent/agent_collaboration_coordinator.cc +++ b/src/app/editor/agent/agent_collaboration_coordinator.cc @@ -18,8 +18,8 @@ #include "absl/strings/str_format.h" #include "absl/strings/strip.h" #include "util/file_util.h" -#include "util/platform_paths.h" #include "util/macro.h" +#include "util/platform_paths.h" #include namespace fs = std::filesystem; @@ -213,7 +213,8 @@ absl::Status AgentCollaborationCoordinator::EnsureDirectory() const { std::string AgentCollaborationCoordinator::LocalUserName() const { const char* override_name = std::getenv("YAZE_USER_NAME"); - const char* user = override_name != nullptr ? override_name : std::getenv("USER"); + const char* user = + override_name != nullptr ? override_name : std::getenv("USER"); if (user == nullptr) { user = std::getenv("USERNAME"); } @@ -236,17 +237,17 @@ std::string AgentCollaborationCoordinator::LocalUserName() const { std::string AgentCollaborationCoordinator::NormalizeSessionCode( const std::string& input) const { std::string normalized = Trimmed(input); - normalized.erase(std::remove_if(normalized.begin(), normalized.end(), - [](unsigned char c) { - return !std::isalnum( - static_cast(c)); - }), - normalized.end()); - std::transform(normalized.begin(), normalized.end(), normalized.begin(), - [](unsigned char c) { - return static_cast( - std::toupper(static_cast(c))); - }); + normalized.erase( + std::remove_if(normalized.begin(), normalized.end(), + [](unsigned char c) { + return !std::isalnum(static_cast(c)); + }), + normalized.end()); + std::transform( + normalized.begin(), normalized.end(), normalized.begin(), + [](unsigned char c) { + return static_cast(std::toupper(static_cast(c))); + }); return normalized; } @@ -304,8 +305,8 @@ AgentCollaborationCoordinator::LoadSessionFile( data.host = value; data.participants.push_back(value); } else if (key == "participant") { - if (std::find(data.participants.begin(), data.participants.end(), value) == - data.participants.end()) { + if (std::find(data.participants.begin(), data.participants.end(), + value) == data.participants.end()) { data.participants.push_back(value); } } @@ -320,8 +321,7 @@ AgentCollaborationCoordinator::LoadSessionFile( if (host_it == data.participants.end()) { data.participants.insert(data.participants.begin(), data.host); } else if (host_it != data.participants.begin()) { - std::rotate(data.participants.begin(), host_it, - std::next(host_it)); + std::rotate(data.participants.begin(), host_it, std::next(host_it)); } } diff --git a/src/app/editor/agent/agent_editor.cc b/src/app/editor/agent/agent_editor.cc index 55c36594..4fbf6baf 100644 --- a/src/app/editor/agent/agent_editor.cc +++ b/src/app/editor/agent/agent_editor.cc @@ -8,14 +8,14 @@ #include "absl/strings/match.h" #include "absl/strings/str_format.h" #include "absl/time/clock.h" -#include "app/platform/asset_loader.h" #include "app/editor/agent/agent_chat_widget.h" #include "app/editor/agent/agent_collaboration_coordinator.h" #include "app/editor/system/proposal_drawer.h" #include "app/editor/system/toast_manager.h" #include "app/gui/core/icons.h" -#include "imgui/misc/cpp/imgui_stdlib.h" +#include "app/platform/asset_loader.h" #include "app/rom.h" +#include "imgui/misc/cpp/imgui_stdlib.h" #include "util/file_util.h" #include "util/platform_paths.h" @@ -156,18 +156,16 @@ void AgentEditor::DrawDashboard() { ImGuiIO& io = ImGui::GetIO(); pulse_animation_ += io.DeltaTime * 2.0f; scanline_offset_ += io.DeltaTime * 0.4f; - if (scanline_offset_ > 1.0f) scanline_offset_ -= 1.0f; + if (scanline_offset_ > 1.0f) + scanline_offset_ -= 1.0f; glitch_timer_ += io.DeltaTime * 5.0f; blink_counter_ = static_cast(pulse_animation_ * 2.0f) % 2; // Pulsing glow for window float pulse = 0.5f + 0.5f * std::sin(pulse_animation_); - ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4( - 0.1f + 0.1f * pulse, - 0.2f + 0.15f * pulse, - 0.3f + 0.2f * pulse, - 1.0f - )); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, + ImVec4(0.1f + 0.1f * pulse, 0.2f + 0.15f * pulse, + 0.3f + 0.2f * pulse, 1.0f)); ImGui::SetNextWindowSize(ImVec2(1200, 800), ImGuiCond_FirstUseEver); ImGui::Begin(ICON_MD_SMART_TOY " AI AGENT PLATFORM [v0.4.x]", &active_, @@ -324,7 +322,7 @@ void AgentEditor::DrawDashboard() { } ImGui::End(); - + // Pop the TitleBgActive color pushed at the beginning of DrawDashboard ImGui::PopStyleColor(); } @@ -1040,8 +1038,7 @@ void AgentEditor::DrawNewPromptCreator() { } if (ImGui::Button(ICON_MD_FILE_COPY " v2 (Enhanced)", ImVec2(-1, 0))) { - auto content = - AssetLoader::LoadTextFile("agent/system_prompt_v2.txt"); + auto content = AssetLoader::LoadTextFile("agent/system_prompt_v2.txt"); if (content.ok() && prompt_editor_) { prompt_editor_->SetText(*content); if (toast_manager_) { @@ -1051,8 +1048,7 @@ void AgentEditor::DrawNewPromptCreator() { } if (ImGui::Button(ICON_MD_FILE_COPY " v3 (Proactive)", ImVec2(-1, 0))) { - auto content = - AssetLoader::LoadTextFile("agent/system_prompt_v3.txt"); + auto content = AssetLoader::LoadTextFile("agent/system_prompt_v3.txt"); if (content.ok() && prompt_editor_) { prompt_editor_->SetText(*content); if (toast_manager_) { @@ -1149,9 +1145,9 @@ void AgentEditor::DrawAgentBuilderPanel() { ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.6f, 1.0f), "Stage Details"); ImGui::Separator(); - int stage_index = std::clamp(builder_state_.active_stage, 0, - static_cast(builder_state_.stages.size()) - - 1); + int stage_index = + std::clamp(builder_state_.active_stage, 0, + static_cast(builder_state_.stages.size()) - 1); int completed_stages = 0; for (const auto& stage : builder_state_.stages) { if (stage.completed) { @@ -1163,8 +1159,7 @@ void AgentEditor::DrawAgentBuilderPanel() { static std::string new_goal; ImGui::Text("Persona + Goals"); ImGui::InputTextMultiline("##persona_notes", - &builder_state_.persona_notes, - ImVec2(-1, 120)); + &builder_state_.persona_notes, ImVec2(-1, 120)); ImGui::Spacing(); ImGui::TextDisabled("Add Goal"); ImGui::InputTextWithHint("##goal_input", "e.g. Document dungeon plan", @@ -1293,8 +1288,8 @@ void AgentEditor::DrawAgentBuilderPanel() { toast_manager_->Show("Builder blueprint saved", ToastType::kSuccess, 2.0f); } else { - toast_manager_->Show(std::string(status.message()), - ToastType::kError, 3.5f); + toast_manager_->Show(std::string(status.message()), ToastType::kError, + 3.5f); } } } @@ -1306,8 +1301,8 @@ void AgentEditor::DrawAgentBuilderPanel() { toast_manager_->Show("Builder blueprint loaded", ToastType::kSuccess, 2.0f); } else { - toast_manager_->Show(std::string(status.message()), - ToastType::kError, 3.5f); + toast_manager_->Show(std::string(status.message()), ToastType::kError, + 3.5f); } } } @@ -1337,9 +1332,9 @@ absl::Status AgentEditor::SaveBuilderBlueprint( }; json["stages"] = nlohmann::json::array(); for (const auto& stage : builder_state_.stages) { - json["stages"].push_back( - {{"name", stage.name}, {"summary", stage.summary}, - {"completed", stage.completed}}); + json["stages"].push_back({{"name", stage.name}, + {"summary", stage.summary}, + {"completed", stage.completed}}); } std::error_code ec; diff --git a/src/app/editor/agent/agent_editor.h b/src/app/editor/agent/agent_editor.h index e12ef3aa..17e59477 100644 --- a/src/app/editor/agent/agent_editor.h +++ b/src/app/editor/agent/agent_editor.h @@ -1,10 +1,10 @@ #ifndef YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_ #define YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_ +#include #include #include #include -#include #include #include "absl/status/status.h" @@ -57,20 +57,30 @@ class AgentEditor : public Editor { absl::Status Load() override; absl::Status Save() override; absl::Status Update() override; - absl::Status Cut() override { return absl::UnimplementedError("Not applicable"); } - absl::Status Copy() override { return absl::UnimplementedError("Not applicable"); } - absl::Status Paste() override { return absl::UnimplementedError("Not applicable"); } - absl::Status Undo() override { return absl::UnimplementedError("Not applicable"); } - absl::Status Redo() override { return absl::UnimplementedError("Not applicable"); } - absl::Status Find() override { return absl::UnimplementedError("Not applicable"); } + absl::Status Cut() override { + return absl::UnimplementedError("Not applicable"); + } + absl::Status Copy() override { + return absl::UnimplementedError("Not applicable"); + } + absl::Status Paste() override { + return absl::UnimplementedError("Not applicable"); + } + absl::Status Undo() override { + return absl::UnimplementedError("Not applicable"); + } + absl::Status Redo() override { + return absl::UnimplementedError("Not applicable"); + } + absl::Status Find() override { + return absl::UnimplementedError("Not applicable"); + } // Initialization with dependencies - void InitializeWithDependencies(ToastManager* toast_manager, - ProposalDrawer* proposal_drawer, - Rom* rom); + void InitializeWithDependencies(ToastManager* toast_manager, + ProposalDrawer* proposal_drawer, Rom* rom); void SetRomContext(Rom* rom); - // Main rendering (called by Update()) void DrawDashboard(); @@ -129,13 +139,13 @@ class AgentEditor : public Editor { std::string blueprint_path; bool ready_for_e2e = false; }; - + // Retro hacker animation state float pulse_animation_ = 0.0f; float scanline_offset_ = 0.0f; float glitch_timer_ = 0.0f; int blink_counter_ = 0; - + AgentConfig GetCurrentConfig() const; void ApplyConfig(const AgentConfig& config); @@ -146,7 +156,8 @@ class AgentEditor : public Editor { std::vector GetAllProfiles() const; BotProfile GetCurrentProfile() const { return current_profile_; } void SetCurrentProfile(const BotProfile& profile); - absl::Status ExportProfile(const BotProfile& profile, const std::filesystem::path& path); + absl::Status ExportProfile(const BotProfile& profile, + const std::filesystem::path& path); absl::Status ImportProfile(const std::filesystem::path& path); // Chat widget access (for EditorManager) @@ -155,8 +166,8 @@ class AgentEditor : public Editor { void SetChatActive(bool active); void ToggleChat(); void OpenChatWindow(); - - // Collaboration and session management + + // Collaboration and session management enum class CollaborationMode { kLocal, // Filesystem-based collaboration kNetwork // WebSocket-based collaboration @@ -168,25 +179,23 @@ class AgentEditor : public Editor { std::vector participants; }; - absl::StatusOr HostSession(const std::string& session_name, - CollaborationMode mode = CollaborationMode::kLocal); - absl::StatusOr JoinSession(const std::string& session_code, - CollaborationMode mode = CollaborationMode::kLocal); + absl::StatusOr HostSession( + const std::string& session_name, + CollaborationMode mode = CollaborationMode::kLocal); + absl::StatusOr JoinSession( + const std::string& session_code, + CollaborationMode mode = CollaborationMode::kLocal); absl::Status LeaveSession(); absl::StatusOr RefreshSession(); - + struct CaptureConfig { - enum class CaptureMode { - kFullWindow, - kActiveEditor, - kSpecificWindow - }; + enum class CaptureMode { kFullWindow, kActiveEditor, kSpecificWindow }; CaptureMode mode = CaptureMode::kActiveEditor; std::string specific_window_name; }; absl::Status CaptureSnapshot(std::filesystem::path* output_path, - const CaptureConfig& config); + const CaptureConfig& config); absl::Status SendToGemini(const std::filesystem::path& image_path, const std::string& prompt); @@ -201,9 +210,13 @@ class AgentEditor : public Editor { std::optional GetCurrentSession() const; // Access to underlying components - AgentCollaborationCoordinator* GetLocalCoordinator() { return local_coordinator_.get(); } + AgentCollaborationCoordinator* GetLocalCoordinator() { + return local_coordinator_.get(); + } #ifdef YAZE_WITH_GRPC - NetworkCollaborationCoordinator* GetNetworkCoordinator() { return network_coordinator_.get(); } + NetworkCollaborationCoordinator* GetNetworkCoordinator() { + return network_coordinator_.get(); + } #endif private: @@ -244,12 +257,12 @@ class AgentEditor : public Editor { // Configuration state (legacy) AgentConfig current_config_; - + // Bot Profile System BotProfile current_profile_; std::vector loaded_profiles_; AgentBuilderState builder_state_; - + // System Prompt Editor std::unique_ptr prompt_editor_; std::unique_ptr common_tiles_editor_; @@ -257,14 +270,14 @@ class AgentEditor : public Editor { bool common_tiles_initialized_ = false; std::string active_prompt_file_ = "system_prompt_v3.txt"; char new_prompt_name_[128] = {}; - + // Collaboration state CollaborationMode current_mode_ = CollaborationMode::kLocal; bool in_session_ = false; std::string current_session_id_; std::string current_session_name_; std::vector current_participants_; - + // UI state bool show_advanced_settings_ = false; bool show_prompt_editor_ = false; @@ -272,7 +285,7 @@ class AgentEditor : public Editor { bool show_chat_history_ = false; bool show_metrics_dashboard_ = false; int selected_tab_ = 0; // 0=Config, 1=Prompts, 2=Bots, 3=History, 4=Metrics - + // Chat history viewer state std::vector cached_history_; bool history_needs_refresh_ = true; diff --git a/src/app/editor/agent/agent_ui_theme.cc b/src/app/editor/agent/agent_ui_theme.cc index cea54125..129bc1ac 100644 --- a/src/app/editor/agent/agent_ui_theme.cc +++ b/src/app/editor/agent/agent_ui_theme.cc @@ -1,7 +1,7 @@ #include "app/editor/agent/agent_ui_theme.h" -#include "app/gui/core/theme_manager.h" #include "app/gui/core/color.h" +#include "app/gui/core/theme_manager.h" #include "imgui/imgui.h" namespace yaze { @@ -14,70 +14,61 @@ static bool g_theme_initialized = false; AgentUITheme AgentUITheme::FromCurrentTheme() { AgentUITheme theme; const auto& current = gui::ThemeManager::Get().GetCurrentTheme(); - + // Message colors - derived from theme primary/secondary - theme.user_message_color = ImVec4( - current.primary.red * 1.1f, - current.primary.green * 0.95f, - current.primary.blue * 0.6f, - 1.0f - ); - - theme.agent_message_color = ImVec4( - current.secondary.red * 0.9f, - current.secondary.green * 1.3f, - current.secondary.blue * 1.0f, - 1.0f - ); - - theme.system_message_color = ImVec4( - current.info.red, - current.info.green, - current.info.blue, - 1.0f - ); - + theme.user_message_color = + ImVec4(current.primary.red * 1.1f, current.primary.green * 0.95f, + current.primary.blue * 0.6f, 1.0f); + + theme.agent_message_color = + ImVec4(current.secondary.red * 0.9f, current.secondary.green * 1.3f, + current.secondary.blue * 1.0f, 1.0f); + + theme.system_message_color = + ImVec4(current.info.red, current.info.green, current.info.blue, 1.0f); + // Content colors 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); - + // UI element colors theme.panel_bg_color = ImVec4(0.12f, 0.14f, 0.18f, 0.95f); theme.panel_bg_darker = ImVec4(0.08f, 0.10f, 0.14f, 0.95f); theme.panel_border_color = ConvertColorToImVec4(current.border); theme.accent_color = ConvertColorToImVec4(current.accent); - + // Status colors theme.status_active = ConvertColorToImVec4(current.success); theme.status_inactive = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); theme.status_success = ConvertColorToImVec4(current.success); theme.status_warning = ConvertColorToImVec4(current.warning); theme.status_error = ConvertColorToImVec4(current.error); - + // Provider-specific colors - theme.provider_ollama = ImVec4(0.2f, 0.8f, 0.4f, 1.0f); // Green - theme.provider_gemini = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); // Blue - theme.provider_mock = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); // Gray - + theme.provider_ollama = ImVec4(0.2f, 0.8f, 0.4f, 1.0f); // Green + theme.provider_gemini = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); // Blue + theme.provider_mock = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); // Gray + // Collaboration colors - theme.collaboration_active = ImVec4(0.133f, 0.545f, 0.133f, 1.0f); // Forest green + theme.collaboration_active = + ImVec4(0.133f, 0.545f, 0.133f, 1.0f); // Forest green theme.collaboration_inactive = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); - + // Proposal colors theme.proposal_panel_bg = ImVec4(0.20f, 0.35f, 0.20f, 0.35f); theme.proposal_accent = ImVec4(0.8f, 1.0f, 0.8f, 1.0f); - + // Button colors theme.button_copy = ImVec4(0.3f, 0.3f, 0.4f, 0.6f); theme.button_copy_hover = ImVec4(0.4f, 0.4f, 0.5f, 0.8f); - + // Gradient colors theme.gradient_top = ImVec4(0.18f, 0.22f, 0.28f, 1.0f); theme.gradient_bottom = ImVec4(0.12f, 0.16f, 0.22f, 1.0f); - + return theme; } @@ -107,7 +98,8 @@ void PopPanelStyle() { ImGui::PopStyleColor(); } -void RenderSectionHeader(const char* icon, const char* label, const ImVec4& color) { +void RenderSectionHeader(const char* icon, const char* label, + const ImVec4& color) { ImGui::TextColored(color, "%s %s", icon, label); ImGui::Separator(); } @@ -115,23 +107,23 @@ void RenderSectionHeader(const char* icon, const char* label, const ImVec4& colo void RenderStatusIndicator(const char* label, bool active) { const auto& theme = GetTheme(); ImVec4 color = active ? theme.status_active : theme.status_inactive; - + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 pos = ImGui::GetCursorScreenPos(); float radius = 4.0f; - + pos.x += radius + 2; pos.y += ImGui::GetTextLineHeight() * 0.5f; - + draw_list->AddCircleFilled(pos, radius, ImGui::GetColorU32(color)); - + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8); ImGui::Text("%s", label); } void RenderProviderBadge(const char* provider) { const auto& theme = GetTheme(); - + ImVec4 badge_color; if (strcmp(provider, "ollama") == 0) { badge_color = theme.provider_ollama; @@ -140,7 +132,7 @@ void RenderProviderBadge(const char* provider) { } else { badge_color = theme.provider_mock; } - + ImGui::PushStyleColor(ImGuiCol_Button, badge_color); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.0f); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 4)); @@ -151,16 +143,26 @@ void RenderProviderBadge(const char* provider) { void StatusBadge(const char* text, ButtonColor color) { const auto& theme = GetTheme(); - + ImVec4 badge_color; switch (color) { - case ButtonColor::Success: badge_color = theme.status_success; break; - case ButtonColor::Warning: badge_color = theme.status_warning; break; - case ButtonColor::Error: badge_color = theme.status_error; break; - case ButtonColor::Info: badge_color = theme.accent_color; break; - default: badge_color = theme.status_inactive; break; + case ButtonColor::Success: + badge_color = theme.status_success; + break; + case ButtonColor::Warning: + badge_color = theme.status_warning; + break; + case ButtonColor::Error: + badge_color = theme.status_error; + break; + case ButtonColor::Info: + badge_color = theme.accent_color; + break; + default: + badge_color = theme.status_inactive; + break; } - + ImGui::PushStyleColor(ImGuiCol_Button, badge_color); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 10.0f); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 2)); @@ -180,27 +182,29 @@ void HorizontalSpacing(float amount) { bool StyledButton(const char* label, const ImVec4& color, const ImVec2& size) { ImGui::PushStyleColor(ImGuiCol_Button, color); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, - ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, - ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w)); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w)); + ImGui::PushStyleColor( + ImGuiCol_ButtonActive, + ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); - + bool result = ImGui::Button(label, size); - + ImGui::PopStyleVar(); ImGui::PopStyleColor(3); - + return result; } bool IconButton(const char* icon, const char* tooltip) { bool result = ImGui::SmallButton(icon); - + if (tooltip && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } - + return result; } diff --git a/src/app/editor/agent/agent_ui_theme.h b/src/app/editor/agent/agent_ui_theme.h index aec824c7..ca8cf854 100644 --- a/src/app/editor/agent/agent_ui_theme.h +++ b/src/app/editor/agent/agent_ui_theme.h @@ -1,9 +1,9 @@ #ifndef YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H #define YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H -#include "imgui/imgui.h" -#include "app/gui/core/theme_manager.h" #include "app/gui/core/color.h" +#include "app/gui/core/theme_manager.h" +#include "imgui/imgui.h" namespace yaze { namespace editor { @@ -22,46 +22,46 @@ struct AgentUITheme { ImVec4 system_message_color; ImVec4 text_secondary_color; - + // Content colors ImVec4 json_text_color; ImVec4 command_text_color; ImVec4 code_bg_color; - + // UI element colors ImVec4 panel_bg_color; ImVec4 panel_bg_darker; ImVec4 panel_border_color; ImVec4 accent_color; - + // Status colors ImVec4 status_active; ImVec4 status_inactive; ImVec4 status_success; ImVec4 status_warning; ImVec4 status_error; - + // Provider colors ImVec4 provider_ollama; ImVec4 provider_gemini; ImVec4 provider_mock; - + // Collaboration colors ImVec4 collaboration_active; ImVec4 collaboration_inactive; - + // Proposal colors ImVec4 proposal_panel_bg; ImVec4 proposal_accent; - + // Button colors ImVec4 button_copy; ImVec4 button_copy_hover; - + // Gradient colors ImVec4 gradient_top; ImVec4 gradient_bottom; - + // Initialize from current theme static AgentUITheme FromCurrentTheme(); }; @@ -79,7 +79,8 @@ void RefreshTheme(); void PushPanelStyle(); void PopPanelStyle(); -void RenderSectionHeader(const char* icon, const char* label, const ImVec4& color); +void RenderSectionHeader(const char* icon, const char* label, + const ImVec4& color); void RenderStatusIndicator(const char* label, bool active); void RenderProviderBadge(const char* provider); @@ -92,7 +93,8 @@ void VerticalSpacing(float amount = 8.0f); void HorizontalSpacing(float amount = 8.0f); // Common button styles -bool StyledButton(const char* label, const ImVec4& color, const ImVec2& size = ImVec2(0, 0)); +bool StyledButton(const char* label, const ImVec4& color, + const ImVec2& size = ImVec2(0, 0)); bool IconButton(const char* icon, const char* tooltip = nullptr); } // namespace AgentUI diff --git a/src/app/editor/agent/automation_bridge.cc b/src/app/editor/agent/automation_bridge.cc index 3bfb9768..6a5a23b0 100644 --- a/src/app/editor/agent/automation_bridge.cc +++ b/src/app/editor/agent/automation_bridge.cc @@ -26,8 +26,8 @@ void AutomationBridge::OnHarnessTestUpdated( telemetry.message = execution.error_message; telemetry.updated_at = (execution.completed_at == absl::InfiniteFuture() || execution.completed_at == absl::InfinitePast()) - ? absl::Now() - : execution.completed_at; + ? absl::Now() + : execution.completed_at; chat_widget_->UpdateHarnessTelemetry(telemetry); } diff --git a/src/app/editor/agent/network_collaboration_coordinator.cc b/src/app/editor/agent/network_collaboration_coordinator.cc index b739a4aa..55a6afdc 100644 --- a/src/app/editor/agent/network_collaboration_coordinator.cc +++ b/src/app/editor/agent/network_collaboration_coordinator.cc @@ -34,15 +34,17 @@ class WebSocketClient { client_ = std::make_unique(host_.c_str(), port_); client_->set_connection_timeout(5); // 5 seconds client_->set_read_timeout(30); // 30 seconds - + // For now, mark as connected and use HTTP polling fallback // A full WebSocket implementation would do the upgrade handshake here connected_ = true; - - std::cout << "✓ Connected to collaboration server at " << host_ << ":" << port_ << std::endl; + + std::cout << "✓ Connected to collaboration server at " << host_ << ":" + << port_ << std::endl; return true; } catch (const std::exception& e) { - std::cerr << "Failed to connect to " << host_ << ":" << port_ << ": " << e.what() << std::endl; + std::cerr << "Failed to connect to " << host_ << ":" << port_ << ": " + << e.what() << std::endl; return false; } } @@ -53,8 +55,9 @@ class WebSocketClient { } bool Send(const std::string& message) { - if (!connected_ || !client_) return false; - + if (!connected_ || !client_) + return false; + // For HTTP fallback: POST message to server // A full WebSocket would send WebSocket frames auto res = client_->Post("/message", message, "application/json"); @@ -62,8 +65,9 @@ class WebSocketClient { } std::string Receive() { - if (!connected_ || !client_) return ""; - + if (!connected_ || !client_) + return ""; + // For HTTP fallback: Poll for messages // A full WebSocket would read frames from the socket auto res = client_->Get("/poll"); @@ -108,7 +112,7 @@ void NetworkCollaborationCoordinator::ConnectWebSocket() { // Parse URL (simple implementation - assumes ws://host:port format) std::string host = "localhost"; int port = 8765; - + // Extract from server_url_ if needed if (server_url_.find("ws://") == 0) { std::string url_part = server_url_.substr(5); // Skip "ws://" @@ -122,10 +126,10 @@ void NetworkCollaborationCoordinator::ConnectWebSocket() { } ws_client_ = std::make_unique(host, port); - + if (ws_client_->Connect("/")) { connected_ = true; - + // Start receive thread should_stop_ = false; receive_thread_ = std::make_unique( @@ -147,26 +151,22 @@ NetworkCollaborationCoordinator::HostSession(const std::string& session_name, const std::string& rom_hash, bool ai_enabled) { if (!connected_) { - return absl::FailedPreconditionError("Not connected to collaboration server"); + return absl::FailedPreconditionError( + "Not connected to collaboration server"); } username_ = username; // Build host_session message with v2.0 fields - Json payload = { - {"session_name", session_name}, - {"username", username}, - {"ai_enabled", ai_enabled} - }; - + Json payload = {{"session_name", session_name}, + {"username", username}, + {"ai_enabled", ai_enabled}}; + if (!rom_hash.empty()) { payload["rom_hash"] = rom_hash; } - Json message = { - {"type", "host_session"}, - {"payload", payload} - }; + Json message = {{"type", "host_session"}, {"payload", payload}}; SendWebSocketMessage("host_session", message["payload"].dump()); @@ -176,7 +176,7 @@ NetworkCollaborationCoordinator::HostSession(const std::string& session_name, info.session_name = session_name; info.session_code = "PENDING"; // Will be updated from server response info.participants = {username}; - + in_session_ = true; session_name_ = session_name; @@ -187,7 +187,8 @@ absl::StatusOr NetworkCollaborationCoordinator::JoinSession(const std::string& session_code, const std::string& username) { if (!connected_) { - return absl::FailedPreconditionError("Not connected to collaboration server"); + return absl::FailedPreconditionError( + "Not connected to collaboration server"); } username_ = username; @@ -196,18 +197,14 @@ NetworkCollaborationCoordinator::JoinSession(const std::string& session_code, // Build join_session message Json message = { {"type", "join_session"}, - {"payload", { - {"session_code", session_code}, - {"username", username} - }} - }; + {"payload", {{"session_code", session_code}, {"username", username}}}}; SendWebSocketMessage("join_session", message["payload"].dump()); // TODO: Wait for session_joined response and parse it SessionInfo info; info.session_code = session_code; - + in_session_ = true; return info; @@ -237,19 +234,13 @@ absl::Status NetworkCollaborationCoordinator::SendChatMessage( } Json payload = { - {"sender", sender}, - {"message", message}, - {"message_type", message_type} - }; - + {"sender", sender}, {"message", message}, {"message_type", message_type}}; + if (!metadata.empty()) { payload["metadata"] = Json::parse(metadata); } - Json msg = { - {"type", "chat_message"}, - {"payload", payload} - }; + Json msg = {{"type", "chat_message"}, {"payload", payload}}; SendWebSocketMessage("chat_message", msg["payload"].dump()); return absl::OkStatus(); @@ -264,12 +255,8 @@ absl::Status NetworkCollaborationCoordinator::SendRomSync( Json msg = { {"type", "rom_sync"}, - {"payload", { - {"sender", sender}, - {"diff_data", diff_data}, - {"rom_hash", rom_hash} - }} - }; + {"payload", + {{"sender", sender}, {"diff_data", diff_data}, {"rom_hash", rom_hash}}}}; SendWebSocketMessage("rom_sync", msg["payload"].dump()); return absl::OkStatus(); @@ -282,14 +269,11 @@ absl::Status NetworkCollaborationCoordinator::SendSnapshot( return absl::FailedPreconditionError("Not in a session"); } - Json msg = { - {"type", "snapshot_share"}, - {"payload", { - {"sender", sender}, - {"snapshot_data", snapshot_data}, - {"snapshot_type", snapshot_type} - }} - }; + Json msg = {{"type", "snapshot_share"}, + {"payload", + {{"sender", sender}, + {"snapshot_data", snapshot_data}, + {"snapshot_type", snapshot_type}}}}; SendWebSocketMessage("snapshot_share", msg["payload"].dump()); return absl::OkStatus(); @@ -301,13 +285,10 @@ absl::Status NetworkCollaborationCoordinator::SendProposal( return absl::FailedPreconditionError("Not in a session"); } - Json msg = { - {"type", "proposal_share"}, - {"payload", { - {"sender", sender}, - {"proposal_data", Json::parse(proposal_data_json)} - }} - }; + Json msg = {{"type", "proposal_share"}, + {"payload", + {{"sender", sender}, + {"proposal_data", Json::parse(proposal_data_json)}}}}; SendWebSocketMessage("proposal_share", msg["payload"].dump()); return absl::OkStatus(); @@ -319,13 +300,8 @@ absl::Status NetworkCollaborationCoordinator::UpdateProposal( return absl::FailedPreconditionError("Not in a session"); } - Json msg = { - {"type", "proposal_update"}, - {"payload", { - {"proposal_id", proposal_id}, - {"status", status} - }} - }; + Json msg = {{"type", "proposal_update"}, + {"payload", {{"proposal_id", proposal_id}, {"status", status}}}}; SendWebSocketMessage("proposal_update", msg["payload"].dump()); return absl::OkStatus(); @@ -337,13 +313,8 @@ absl::Status NetworkCollaborationCoordinator::SendAIQuery( return absl::FailedPreconditionError("Not in a session"); } - Json msg = { - {"type", "ai_query"}, - {"payload", { - {"username", username}, - {"query", query} - }} - }; + Json msg = {{"type", "ai_query"}, + {"payload", {{"username", username}, {"query", query}}}}; SendWebSocketMessage("ai_query", msg["payload"].dump()); return absl::OkStatus(); @@ -353,7 +324,8 @@ bool NetworkCollaborationCoordinator::IsConnected() const { return connected_; } -void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback callback) { +void NetworkCollaborationCoordinator::SetMessageCallback( + MessageCallback callback) { absl::MutexLock lock(&mutex_); message_callback_ = std::move(callback); } @@ -369,27 +341,32 @@ void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback callback) { error_callback_ = std::move(callback); } -void NetworkCollaborationCoordinator::SetRomSyncCallback(RomSyncCallback callback) { +void NetworkCollaborationCoordinator::SetRomSyncCallback( + RomSyncCallback callback) { absl::MutexLock lock(&mutex_); rom_sync_callback_ = std::move(callback); } -void NetworkCollaborationCoordinator::SetSnapshotCallback(SnapshotCallback callback) { +void NetworkCollaborationCoordinator::SetSnapshotCallback( + SnapshotCallback callback) { absl::MutexLock lock(&mutex_); snapshot_callback_ = std::move(callback); } -void NetworkCollaborationCoordinator::SetProposalCallback(ProposalCallback callback) { +void NetworkCollaborationCoordinator::SetProposalCallback( + ProposalCallback callback) { absl::MutexLock lock(&mutex_); proposal_callback_ = std::move(callback); } -void NetworkCollaborationCoordinator::SetProposalUpdateCallback(ProposalUpdateCallback callback) { +void NetworkCollaborationCoordinator::SetProposalUpdateCallback( + ProposalUpdateCallback callback) { absl::MutexLock lock(&mutex_); proposal_update_callback_ = std::move(callback); } -void NetworkCollaborationCoordinator::SetAIResponseCallback(AIResponseCallback callback) { +void NetworkCollaborationCoordinator::SetAIResponseCallback( + AIResponseCallback callback) { absl::MutexLock lock(&mutex_); ai_response_callback_ = std::move(callback); } @@ -400,10 +377,7 @@ void NetworkCollaborationCoordinator::SendWebSocketMessage( return; } - Json message = { - {"type", type}, - {"payload", Json::parse(payload_json)} - }; + Json message = {{"type", type}, {"payload", Json::parse(payload_json)}}; ws_client_->Send(message.dump()); } @@ -419,7 +393,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( session_id_ = payload["session_id"]; session_code_ = payload["session_code"]; session_name_ = payload["session_name"]; - + if (payload.contains("participants")) { absl::MutexLock lock(&mutex_); if (participant_callback_) { @@ -432,7 +406,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( session_id_ = payload["session_id"]; session_code_ = payload["session_code"]; session_name_ = payload["session_name"]; - + if (payload.contains("participants")) { absl::MutexLock lock(&mutex_); if (participant_callback_) { @@ -450,7 +424,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( if (payload.contains("metadata") && !payload["metadata"].is_null()) { msg.metadata = payload["metadata"].dump(); } - + absl::MutexLock lock(&mutex_); if (message_callback_) { message_callback_(msg); @@ -463,7 +437,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( sync.diff_data = payload["diff_data"]; sync.rom_hash = payload["rom_hash"]; sync.timestamp = payload["timestamp"]; - + absl::MutexLock lock(&mutex_); if (rom_sync_callback_) { rom_sync_callback_(sync); @@ -476,7 +450,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( snapshot.snapshot_data = payload["snapshot_data"]; snapshot.snapshot_type = payload["snapshot_type"]; snapshot.timestamp = payload["timestamp"]; - + absl::MutexLock lock(&mutex_); if (snapshot_callback_) { snapshot_callback_(snapshot); @@ -489,7 +463,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( proposal.proposal_data = payload["proposal_data"].dump(); proposal.status = payload["status"]; proposal.timestamp = payload["timestamp"]; - + absl::MutexLock lock(&mutex_); if (proposal_callback_) { proposal_callback_(proposal); @@ -498,7 +472,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( Json payload = message["payload"]; std::string proposal_id = payload["proposal_id"]; std::string status = payload["status"]; - + absl::MutexLock lock(&mutex_); if (proposal_update_callback_) { proposal_update_callback_(proposal_id, status); @@ -511,20 +485,21 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( response.query = payload["query"]; response.response = payload["response"]; response.timestamp = payload["timestamp"]; - + absl::MutexLock lock(&mutex_); if (ai_response_callback_) { ai_response_callback_(response); } } else if (type == "server_shutdown") { Json payload = message["payload"]; - std::string error = "Server shutdown: " + payload["message"].get(); - + std::string error = + "Server shutdown: " + payload["message"].get(); + absl::MutexLock lock(&mutex_); if (error_callback_) { error_callback_(error); } - + // Disconnect connected_ = false; } else if (type == "participant_joined" || type == "participant_left") { @@ -539,7 +514,7 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( } else if (type == "error") { Json payload = message["payload"]; std::string error = payload["error"]; - + absl::MutexLock lock(&mutex_); if (error_callback_) { error_callback_(error); @@ -552,13 +527,14 @@ void NetworkCollaborationCoordinator::HandleWebSocketMessage( void NetworkCollaborationCoordinator::WebSocketReceiveLoop() { while (!should_stop_ && connected_) { - if (!ws_client_) break; - + if (!ws_client_) + break; + std::string message = ws_client_->Receive(); if (!message.empty()) { HandleWebSocketMessage(message); } - + // Small sleep to avoid busy-waiting std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -568,69 +544,92 @@ void NetworkCollaborationCoordinator::WebSocketReceiveLoop() { // Stub implementations when JSON is not available NetworkCollaborationCoordinator::NetworkCollaborationCoordinator( - const std::string& server_url) : server_url_(server_url) {} + const std::string& server_url) + : server_url_(server_url) {} NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() = default; absl::StatusOr -NetworkCollaborationCoordinator::HostSession(const std::string&, const std::string&, +NetworkCollaborationCoordinator::HostSession(const std::string&, + const std::string&, const std::string&, bool) { - return absl::UnimplementedError("Network collaboration requires JSON support"); + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } absl::StatusOr -NetworkCollaborationCoordinator::JoinSession(const std::string&, const std::string&) { - return absl::UnimplementedError("Network collaboration requires JSON support"); +NetworkCollaborationCoordinator::JoinSession(const std::string&, + const std::string&) { + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } absl::Status NetworkCollaborationCoordinator::LeaveSession() { - return absl::UnimplementedError("Network collaboration requires JSON support"); + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } absl::Status NetworkCollaborationCoordinator::SendChatMessage( - const std::string&, const std::string&, const std::string&, const std::string&) { - return absl::UnimplementedError("Network collaboration requires JSON support"); + const std::string&, const std::string&, const std::string&, + const std::string&) { + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } -absl::Status NetworkCollaborationCoordinator::SendRomSync( - const std::string&, const std::string&, const std::string&) { - return absl::UnimplementedError("Network collaboration requires JSON support"); +absl::Status NetworkCollaborationCoordinator::SendRomSync(const std::string&, + const std::string&, + const std::string&) { + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } -absl::Status NetworkCollaborationCoordinator::SendSnapshot( - const std::string&, const std::string&, const std::string&) { - return absl::UnimplementedError("Network collaboration requires JSON support"); +absl::Status NetworkCollaborationCoordinator::SendSnapshot(const std::string&, + const std::string&, + const std::string&) { + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } -absl::Status NetworkCollaborationCoordinator::SendProposal( - const std::string&, const std::string&) { - return absl::UnimplementedError("Network collaboration requires JSON support"); +absl::Status NetworkCollaborationCoordinator::SendProposal(const std::string&, + const std::string&) { + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } absl::Status NetworkCollaborationCoordinator::UpdateProposal( const std::string&, const std::string&) { - return absl::UnimplementedError("Network collaboration requires JSON support"); + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } -absl::Status NetworkCollaborationCoordinator::SendAIQuery( - const std::string&, const std::string&) { - return absl::UnimplementedError("Network collaboration requires JSON support"); +absl::Status NetworkCollaborationCoordinator::SendAIQuery(const std::string&, + const std::string&) { + return absl::UnimplementedError( + "Network collaboration requires JSON support"); } -bool NetworkCollaborationCoordinator::IsConnected() const { return false; } +bool NetworkCollaborationCoordinator::IsConnected() const { + return false; +} void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback) {} -void NetworkCollaborationCoordinator::SetParticipantCallback(ParticipantCallback) {} +void NetworkCollaborationCoordinator::SetParticipantCallback( + ParticipantCallback) {} void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback) {} void NetworkCollaborationCoordinator::SetRomSyncCallback(RomSyncCallback) {} void NetworkCollaborationCoordinator::SetSnapshotCallback(SnapshotCallback) {} void NetworkCollaborationCoordinator::SetProposalCallback(ProposalCallback) {} -void NetworkCollaborationCoordinator::SetProposalUpdateCallback(ProposalUpdateCallback) {} -void NetworkCollaborationCoordinator::SetAIResponseCallback(AIResponseCallback) {} +void NetworkCollaborationCoordinator::SetProposalUpdateCallback( + ProposalUpdateCallback) {} +void NetworkCollaborationCoordinator::SetAIResponseCallback( + AIResponseCallback) {} void NetworkCollaborationCoordinator::ConnectWebSocket() {} void NetworkCollaborationCoordinator::DisconnectWebSocket() {} -void NetworkCollaborationCoordinator::SendWebSocketMessage(const std::string&, const std::string&) {} -void NetworkCollaborationCoordinator::HandleWebSocketMessage(const std::string&) {} +void NetworkCollaborationCoordinator::SendWebSocketMessage(const std::string&, + const std::string&) { +} +void NetworkCollaborationCoordinator::HandleWebSocketMessage( + const std::string&) {} void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {} #endif // YAZE_WITH_JSON diff --git a/src/app/editor/agent/network_collaboration_coordinator.h b/src/app/editor/agent/network_collaboration_coordinator.h index 0eb8c7f9..538f7f4a 100644 --- a/src/app/editor/agent/network_collaboration_coordinator.h +++ b/src/app/editor/agent/network_collaboration_coordinator.h @@ -74,12 +74,14 @@ class NetworkCollaborationCoordinator { // Callbacks for handling incoming events using MessageCallback = std::function; - using ParticipantCallback = std::function&)>; + using ParticipantCallback = + std::function&)>; using ErrorCallback = std::function; using RomSyncCallback = std::function; using SnapshotCallback = std::function; using ProposalCallback = std::function; - using ProposalUpdateCallback = std::function; + using ProposalUpdateCallback = + std::function; using AIResponseCallback = std::function; explicit NetworkCollaborationCoordinator(const std::string& server_url); @@ -95,28 +97,28 @@ class NetworkCollaborationCoordinator { absl::Status LeaveSession(); // Communication methods - absl::Status SendChatMessage(const std::string& sender, - const std::string& message, - const std::string& message_type = "chat", - const std::string& metadata = ""); - + absl::Status SendChatMessage(const std::string& sender, + const std::string& message, + const std::string& message_type = "chat", + const std::string& metadata = ""); + // Advanced features absl::Status SendRomSync(const std::string& sender, - const std::string& diff_data, - const std::string& rom_hash); - + const std::string& diff_data, + const std::string& rom_hash); + absl::Status SendSnapshot(const std::string& sender, - const std::string& snapshot_data, - const std::string& snapshot_type); - + const std::string& snapshot_data, + const std::string& snapshot_type); + absl::Status SendProposal(const std::string& sender, - const std::string& proposal_data_json); - + const std::string& proposal_data_json); + absl::Status UpdateProposal(const std::string& proposal_id, - const std::string& status); - + const std::string& status); + absl::Status SendAIQuery(const std::string& username, - const std::string& query); + const std::string& query); // Connection status bool IsConnected() const; @@ -137,7 +139,8 @@ class NetworkCollaborationCoordinator { private: void ConnectWebSocket(); void DisconnectWebSocket(); - void SendWebSocketMessage(const std::string& type, const std::string& payload_json); + void SendWebSocketMessage(const std::string& type, + const std::string& payload_json); void HandleWebSocketMessage(const std::string& message); void WebSocketReceiveLoop(); @@ -147,12 +150,12 @@ class NetworkCollaborationCoordinator { std::string session_code_; std::string session_name_; bool in_session_ = false; - + std::unique_ptr ws_client_; std::atomic connected_{false}; std::atomic should_stop_{false}; std::unique_ptr receive_thread_; - + mutable absl::Mutex mutex_; MessageCallback message_callback_ ABSL_GUARDED_BY(mutex_); ParticipantCallback participant_callback_ ABSL_GUARDED_BY(mutex_); diff --git a/src/app/editor/code/assembly_editor.cc b/src/app/editor/code/assembly_editor.cc index 455b4a78..5707522e 100644 --- a/src/app/editor/code/assembly_editor.cc +++ b/src/app/editor/code/assembly_editor.cc @@ -5,12 +5,12 @@ #include #include -#include "absl/strings/str_cat.h" #include "absl/strings/match.h" -#include "util/file_util.h" +#include "absl/strings/str_cat.h" #include "app/gui/core/icons.h" #include "app/gui/core/ui_helpers.h" #include "app/gui/widgets/text_editor.h" +#include "util/file_util.h" namespace yaze::editor { @@ -18,7 +18,7 @@ using util::FileDialogWrapper; namespace { -static const char *const kKeywords[] = { +static const char* const kKeywords[] = { "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", "BPL", "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV", "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", "INY", "JMP", "JSR", @@ -29,7 +29,7 @@ static const char *const kKeywords[] = { "TCS", "TDC", "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA", "TYX", "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM"}; -static const char *const kIdentifiers[] = { +static const char* const kIdentifiers[] = { "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", @@ -42,9 +42,10 @@ static const char *const kIdentifiers[] = { TextEditor::LanguageDefinition GetAssemblyLanguageDef() { TextEditor::LanguageDefinition language_65816; - for (auto &k : kKeywords) language_65816.mKeywords.emplace(k); + for (auto& k : kKeywords) + language_65816.mKeywords.emplace(k); - for (auto &k : kIdentifiers) { + for (auto& k : kIdentifiers) { TextEditor::Identifier id; id.mDeclaration = "Built-in function"; language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id)); @@ -175,27 +176,35 @@ FolderItem LoadFolder(const std::string& folder) { void AssemblyEditor::Initialize() { text_editor_.SetLanguageDefinition(GetAssemblyLanguageDef()); - + // Register cards with EditorCardManager - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - card_registry->RegisterCard({.card_id = "assembly.editor", .display_name = "Assembly Editor", - .icon = ICON_MD_CODE, .category = "Assembly", - .shortcut_hint = "", .priority = 10}); - card_registry->RegisterCard({.card_id = "assembly.file_browser", .display_name = "File Browser", - .icon = ICON_MD_FOLDER_OPEN, .category = "Assembly", - .shortcut_hint = "", .priority = 20}); - + card_registry->RegisterCard({.card_id = "assembly.editor", + .display_name = "Assembly Editor", + .icon = ICON_MD_CODE, + .category = "Assembly", + .shortcut_hint = "", + .priority = 10}); + card_registry->RegisterCard({.card_id = "assembly.file_browser", + .display_name = "File Browser", + .icon = ICON_MD_FOLDER_OPEN, + .category = "Assembly", + .shortcut_hint = "", + .priority = 20}); + // Don't show by default - only show when user explicitly opens Assembly Editor } absl::Status AssemblyEditor::Load() { // Register cards with EditorCardRegistry (dependency injection) // Note: Assembly editor uses dynamic file tabs, so we register the main editor window - if (!dependencies_.card_registry) return absl::OkStatus(); + if (!dependencies_.card_registry) + return absl::OkStatus(); auto* card_registry = dependencies_.card_registry; - - return absl::OkStatus(); + + return absl::OkStatus(); } void AssemblyEditor::OpenFolder(const std::string& folder_path) { @@ -239,7 +248,8 @@ void AssemblyEditor::UpdateCodeView() { gui::VerticalSpacing(2.0f); // Create session-aware card (non-static for multi-session support) - gui::EditorCard file_browser_card(MakeCardTitle("File Browser").c_str(), ICON_MD_FOLDER); + gui::EditorCard file_browser_card(MakeCardTitle("File Browser").c_str(), + ICON_MD_FOLDER); bool file_browser_open = true; if (file_browser_card.Begin(&file_browser_open)) { if (current_folder_.name != "") { @@ -259,22 +269,22 @@ void AssemblyEditor::UpdateCodeView() { // Ensure we have a TextEditor instance for this file if (file_id >= open_files_.size()) { - open_files_.resize(file_id + 1); + open_files_.resize(file_id + 1); } if (file_id >= files_.size()) { - // This can happen if a file was closed and its ID is being reused. - // For now, we just skip it. - continue; + // This can happen if a file was closed and its ID is being reused. + // For now, we just skip it. + continue; } // Create session-aware card title for each file std::string card_name = MakeCardTitle(files_[file_id]); gui::EditorCard file_card(card_name.c_str(), ICON_MD_DESCRIPTION, &open); if (file_card.Begin()) { - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) { - active_file_id_ = file_id; - } - open_files_[file_id].Render(absl::StrCat("##", card_name).c_str()); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) { + active_file_id_ = file_id; + } + open_files_[file_id].Render(absl::StrCat("##", card_name).c_str()); } file_card.End(); // ALWAYS call End after Begin @@ -286,26 +296,26 @@ void AssemblyEditor::UpdateCodeView() { } absl::Status AssemblyEditor::Save() { - if (active_file_id_ != -1 && active_file_id_ < open_files_.size()) { - std::string content = open_files_[active_file_id_].GetText(); - util::SaveFile(files_[active_file_id_], content); - return absl::OkStatus(); - } - return absl::FailedPreconditionError("No active file to save."); + if (active_file_id_ != -1 && active_file_id_ < open_files_.size()) { + std::string content = open_files_[active_file_id_].GetText(); + util::SaveFile(files_[active_file_id_], content); + return absl::OkStatus(); + } + return absl::FailedPreconditionError("No active file to save."); } void AssemblyEditor::DrawToolset() { - static gui::Toolset toolbar; - toolbar.Begin(); + static gui::Toolset toolbar; + toolbar.Begin(); - if (toolbar.AddAction(ICON_MD_FOLDER_OPEN, "Open Folder")) { - current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog()); - } - if (toolbar.AddAction(ICON_MD_SAVE, "Save File")) { - Save(); - } + if (toolbar.AddAction(ICON_MD_FOLDER_OPEN, "Open Folder")) { + current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog()); + } + if (toolbar.AddAction(ICON_MD_SAVE, "Save File")) { + Save(); + } - toolbar.End(); + toolbar.End(); } void AssemblyEditor::DrawCurrentFolder() { @@ -358,7 +368,6 @@ void AssemblyEditor::DrawCurrentFolder() { } } - void AssemblyEditor::DrawFileMenu() { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Open", "Ctrl+O")) { @@ -398,36 +407,36 @@ void AssemblyEditor::DrawEditMenu() { } } -void AssemblyEditor::ChangeActiveFile(const std::string_view &filename) { - // Check if file is already open - for (int i = 0; i < active_files_.Size; ++i) { - int file_id = active_files_[i]; - if (files_[file_id] == filename) { - // Optional: Focus window - return; - } +void AssemblyEditor::ChangeActiveFile(const std::string_view& filename) { + // Check if file is already open + for (int i = 0; i < active_files_.Size; ++i) { + int file_id = active_files_[i]; + if (files_[file_id] == filename) { + // Optional: Focus window + return; } + } - // Add new file - int new_file_id = files_.size(); - files_.push_back(std::string(filename)); - active_files_.push_back(new_file_id); + // Add new file + int new_file_id = files_.size(); + files_.push_back(std::string(filename)); + active_files_.push_back(new_file_id); - // Resize open_files_ if needed - if (new_file_id >= open_files_.size()) { - open_files_.resize(new_file_id + 1); - } + // Resize open_files_ if needed + if (new_file_id >= open_files_.size()) { + open_files_.resize(new_file_id + 1); + } - // Load file content using utility - std::string content = util::LoadFile(std::string(filename)); - if (!content.empty()) { - open_files_[new_file_id].SetText(content); - open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef()); - open_files_[new_file_id].SetPalette(TextEditor::GetDarkPalette()); - } else { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n", - std::string(filename).c_str()); - } + // Load file content using utility + std::string content = util::LoadFile(std::string(filename)); + if (!content.empty()) { + open_files_[new_file_id].SetText(content); + open_files_[new_file_id].SetLanguageDefinition(GetAssemblyLanguageDef()); + open_files_[new_file_id].SetPalette(TextEditor::GetDarkPalette()); + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n", + std::string(filename).c_str()); + } } absl::Status AssemblyEditor::Cut() { @@ -455,6 +464,8 @@ absl::Status AssemblyEditor::Redo() { return absl::OkStatus(); } -absl::Status AssemblyEditor::Update() { return absl::OkStatus(); } +absl::Status AssemblyEditor::Update() { + return absl::OkStatus(); +} } // namespace yaze::editor diff --git a/src/app/editor/code/assembly_editor.h b/src/app/editor/code/assembly_editor.h index 93a6acc3..5da9dd2f 100644 --- a/src/app/editor/code/assembly_editor.h +++ b/src/app/editor/code/assembly_editor.h @@ -6,9 +6,9 @@ #include "absl/container/flat_hash_map.h" #include "app/editor/editor.h" -#include "app/gui/widgets/text_editor.h" #include "app/gui/app/editor_layout.h" #include "app/gui/core/style.h" +#include "app/gui/widgets/text_editor.h" #include "app/rom.h" namespace yaze { @@ -31,11 +31,11 @@ class AssemblyEditor : public Editor { text_editor_.SetShowWhitespaces(false); type_ = EditorType::kAssembly; } - void ChangeActiveFile(const std::string_view &filename); + void ChangeActiveFile(const std::string_view& filename); void Initialize() override; absl::Status Load() override; - void Update(bool &is_loaded); + void Update(bool& is_loaded); void InlineUpdate(); void UpdateCodeView(); @@ -52,7 +52,7 @@ class AssemblyEditor : public Editor { absl::Status Save() override; - void OpenFolder(const std::string &folder_path); + void OpenFolder(const std::string& folder_path); void set_rom(Rom* rom) { rom_ = rom; } Rom* rom() const { return rom_; } diff --git a/src/app/editor/code/memory_editor.cc b/src/app/editor/code/memory_editor.cc index d43e9285..08a5b5b2 100644 --- a/src/app/editor/code/memory_editor.cc +++ b/src/app/editor/code/memory_editor.cc @@ -12,14 +12,14 @@ void MemoryEditorWithDiffChecker::DrawToolbar() { // Modern compact toolbar with icon-only buttons ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 4)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); - + if (ImGui::Button(ICON_MD_LOCATION_SEARCHING " Jump")) { ImGui::OpenPopup("JumpToAddress"); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Jump to specific address"); } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_SEARCH " Search")) { ImGui::OpenPopup("SearchPattern"); @@ -27,7 +27,7 @@ void MemoryEditorWithDiffChecker::DrawToolbar() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Search for hex pattern"); } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_BOOKMARK " Bookmarks")) { ImGui::OpenPopup("Bookmarks"); @@ -35,35 +35,38 @@ void MemoryEditorWithDiffChecker::DrawToolbar() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Manage address bookmarks"); } - + ImGui::SameLine(); ImGui::Text(ICON_MD_MORE_VERT); ImGui::SameLine(); - + // Show current address if (current_address_ != 0) { - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), - ICON_MD_LOCATION_ON " 0x%06X", current_address_); + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), + ICON_MD_LOCATION_ON " 0x%06X", current_address_); } - + ImGui::PopStyleVar(2); ImGui::Separator(); - + DrawJumpToAddressPopup(); DrawSearchPopup(); DrawBookmarksPopup(); } void MemoryEditorWithDiffChecker::DrawJumpToAddressPopup() { - if (ImGui::BeginPopupModal("JumpToAddress", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), - ICON_MD_LOCATION_SEARCHING " Jump to Address"); + if (ImGui::BeginPopupModal("JumpToAddress", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), + ICON_MD_LOCATION_SEARCHING " Jump to Address"); ImGui::Separator(); ImGui::Spacing(); - + ImGui::SetNextItemWidth(200); - if (ImGui::InputText("##jump_addr", jump_address_, IM_ARRAYSIZE(jump_address_), - ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) { + if (ImGui::InputText("##jump_addr", jump_address_, + IM_ARRAYSIZE(jump_address_), + ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_EnterReturnsTrue)) { // Parse and jump on Enter key unsigned int addr; if (sscanf(jump_address_, "%X", &addr) == 1) { @@ -72,11 +75,11 @@ void MemoryEditorWithDiffChecker::DrawJumpToAddressPopup() { } } ImGui::TextDisabled("Format: 0x1C800 or 1C800"); - + ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button(ICON_MD_CHECK " Go", ImVec2(120, 0))) { unsigned int addr; if (sscanf(jump_address_, "%X", &addr) == 1) { @@ -93,21 +96,23 @@ void MemoryEditorWithDiffChecker::DrawJumpToAddressPopup() { } void MemoryEditorWithDiffChecker::DrawSearchPopup() { - if (ImGui::BeginPopupModal("SearchPattern", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f), - ICON_MD_SEARCH " Search Hex Pattern"); + if (ImGui::BeginPopupModal("SearchPattern", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f), + ICON_MD_SEARCH " Search Hex Pattern"); ImGui::Separator(); ImGui::Spacing(); - + ImGui::SetNextItemWidth(300); - if (ImGui::InputText("##search_pattern", search_pattern_, IM_ARRAYSIZE(search_pattern_), - ImGuiInputTextFlags_EnterReturnsTrue)) { + if (ImGui::InputText("##search_pattern", search_pattern_, + IM_ARRAYSIZE(search_pattern_), + ImGuiInputTextFlags_EnterReturnsTrue)) { // TODO: Implement search ImGui::CloseCurrentPopup(); } ImGui::TextDisabled("Use ?? for wildcard (e.g. FF 00 ?? 12)"); ImGui::Spacing(); - + // Quick preset patterns ImGui::Text(ICON_MD_LIST " Quick Patterns:"); if (ImGui::SmallButton("LDA")) { @@ -121,11 +126,11 @@ void MemoryEditorWithDiffChecker::DrawSearchPopup() { if (ImGui::SmallButton("JSR")) { snprintf(search_pattern_, sizeof(search_pattern_), "20 ?? ??"); } - + ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button(ICON_MD_SEARCH " Search", ImVec2(120, 0))) { // TODO: Implement search using hex-search handler ImGui::CloseCurrentPopup(); @@ -139,64 +144,71 @@ void MemoryEditorWithDiffChecker::DrawSearchPopup() { } void MemoryEditorWithDiffChecker::DrawBookmarksPopup() { - if (ImGui::BeginPopupModal("Bookmarks", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), - ICON_MD_BOOKMARK " Memory Bookmarks"); + if (ImGui::BeginPopupModal("Bookmarks", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_BOOKMARK " Memory Bookmarks"); ImGui::Separator(); ImGui::Spacing(); - + if (bookmarks_.empty()) { ImGui::TextDisabled(ICON_MD_INFO " No bookmarks yet"); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button(ICON_MD_ADD " Add Current Address", ImVec2(250, 0))) { Bookmark new_bookmark; new_bookmark.address = current_address_; - new_bookmark.name = absl::StrFormat("Bookmark %zu", bookmarks_.size() + 1); + new_bookmark.name = + absl::StrFormat("Bookmark %zu", bookmarks_.size() + 1); new_bookmark.description = "User-defined bookmark"; bookmarks_.push_back(new_bookmark); } } else { // Bookmarks table ImGui::BeginChild("##bookmarks_list", ImVec2(500, 300), true); - if (ImGui::BeginTable("##bookmarks_table", 3, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | - ImGuiTableFlags_Resizable)) { + if (ImGui::BeginTable("##bookmarks_table", 3, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 150); - ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, + 100); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); - + for (size_t i = 0; i < bookmarks_.size(); ++i) { const auto& bm = bookmarks_[i]; ImGui::PushID(static_cast(i)); - + ImGui::TableNextRow(); ImGui::TableNextColumn(); - if (ImGui::Selectable(bm.name.c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) { + if (ImGui::Selectable(bm.name.c_str(), false, + ImGuiSelectableFlags_SpanAllColumns)) { current_address_ = bm.address; ImGui::CloseCurrentPopup(); } - + ImGui::TableNextColumn(); - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "0x%06X", bm.address); - + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "0x%06X", + bm.address); + ImGui::TableNextColumn(); ImGui::TextDisabled("%s", bm.description.c_str()); - + ImGui::PopID(); } ImGui::EndTable(); } ImGui::EndChild(); - + ImGui::Spacing(); if (ImGui::Button(ICON_MD_ADD " Add Bookmark", ImVec2(150, 0))) { Bookmark new_bookmark; new_bookmark.address = current_address_; - new_bookmark.name = absl::StrFormat("Bookmark %zu", bookmarks_.size() + 1); + new_bookmark.name = + absl::StrFormat("Bookmark %zu", bookmarks_.size() + 1); new_bookmark.description = "User-defined bookmark"; bookmarks_.push_back(new_bookmark); } @@ -205,15 +217,15 @@ void MemoryEditorWithDiffChecker::DrawBookmarksPopup() { bookmarks_.clear(); } } - + ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button(ICON_MD_CLOSE " Close", ImVec2(250, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } } diff --git a/src/app/editor/code/memory_editor.h b/src/app/editor/code/memory_editor.h index 1062953a..a33526a6 100644 --- a/src/app/editor/code/memory_editor.h +++ b/src/app/editor/code/memory_editor.h @@ -1,7 +1,6 @@ #ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H #define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H -#include "util/file_util.h" #include "absl/container/flat_hash_map.h" #include "app/editor/editor.h" #include "app/gui/core/input.h" @@ -9,6 +8,7 @@ #include "app/snes.h" #include "imgui/imgui.h" #include "imgui_memory_editor.h" +#include "util/file_util.h" #include "util/macro.h" namespace yaze { @@ -19,8 +19,8 @@ using ImGui::Text; struct MemoryEditorWithDiffChecker { explicit MemoryEditorWithDiffChecker(Rom* rom = nullptr) : rom_(rom) {} - - void Update(bool &show_memory_editor) { + + void Update(bool& show_memory_editor) { DrawToolbar(); ImGui::Separator(); static MemoryEditor mem_edit; @@ -35,7 +35,7 @@ struct MemoryEditorWithDiffChecker { } static uint64_t convert_address = 0; - gui::InputHex("SNES to PC", (int *)&convert_address, 6, 200.f); + gui::InputHex("SNES to PC", (int*)&convert_address, 6, 200.f); SameLine(); Text("%x", SnesToPc(convert_address)); @@ -46,15 +46,15 @@ struct MemoryEditorWithDiffChecker { NEXT_COLUMN() Text("%s", rom()->filename().data()); - mem_edit.DrawContents((void *)&(*rom()), rom()->size()); + mem_edit.DrawContents((void*)&(*rom()), rom()->size()); NEXT_COLUMN() if (show_compare_rom) { - comp_edit.SetComparisonData((void *)&(*rom())); + comp_edit.SetComparisonData((void*)&(*rom())); ImGui::BeginGroup(); ImGui::BeginChild("Comparison ROM"); Text("%s", comparison_rom.filename().data()); - comp_edit.DrawContents((void *)&(comparison_rom), comparison_rom.size()); + comp_edit.DrawContents((void*)&(comparison_rom), comparison_rom.size()); ImGui::EndChild(); ImGui::EndGroup(); } @@ -65,7 +65,7 @@ struct MemoryEditorWithDiffChecker { // Set the ROM pointer void set_rom(Rom* rom) { rom_ = rom; } - + // Get the ROM pointer Rom* rom() const { return rom_; } @@ -74,14 +74,14 @@ struct MemoryEditorWithDiffChecker { void DrawJumpToAddressPopup(); void DrawSearchPopup(); void DrawBookmarksPopup(); - + Rom* rom_; - + // Toolbar state char jump_address_[16] = "0x000000"; char search_pattern_[256] = ""; uint32_t current_address_ = 0; - + struct Bookmark { uint32_t address; std::string name; diff --git a/src/app/editor/code/project_file_editor.cc b/src/app/editor/code/project_file_editor.cc index d212012f..32ee4f00 100644 --- a/src/app/editor/code/project_file_editor.cc +++ b/src/app/editor/code/project_file_editor.cc @@ -6,11 +6,11 @@ #include "absl/strings/match.h" #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" -#include "core/project.h" -#include "util/file_util.h" #include "app/editor/system/toast_manager.h" #include "app/gui/core/icons.h" +#include "core/project.h" #include "imgui/imgui.h" +#include "util/file_util.h" namespace yaze { namespace editor { @@ -22,38 +22,43 @@ ProjectFileEditor::ProjectFileEditor() { } void ProjectFileEditor::Draw() { - if (!active_) return; - + if (!active_) + return; + ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver); if (!ImGui::Begin(absl::StrFormat("%s Project Editor###ProjectFileEditor", - ICON_MD_DESCRIPTION).c_str(), - &active_)) { + ICON_MD_DESCRIPTION) + .c_str(), + &active_)) { ImGui::End(); return; } - + // Toolbar - if (ImGui::BeginTable("ProjectEditorToolbar", 8, ImGuiTableFlags_SizingFixedFit)) { + if (ImGui::BeginTable("ProjectEditorToolbar", 8, + ImGuiTableFlags_SizingFixedFit)) { ImGui::TableNextColumn(); if (ImGui::Button(absl::StrFormat("%s New", ICON_MD_NOTE_ADD).c_str())) { NewFile(); } - + ImGui::TableNextColumn(); - if (ImGui::Button(absl::StrFormat("%s Open", ICON_MD_FOLDER_OPEN).c_str())) { + if (ImGui::Button( + absl::StrFormat("%s Open", ICON_MD_FOLDER_OPEN).c_str())) { auto file = util::FileDialogWrapper::ShowOpenFileDialog(); if (!file.empty()) { auto status = LoadFile(file); if (!status.ok() && toast_manager_) { - toast_manager_->Show(std::string(status.message()), - ToastType::kError); + toast_manager_->Show(std::string(status.message()), + ToastType::kError); } } } - + ImGui::TableNextColumn(); bool can_save = !filepath_.empty() && IsModified(); - if (!can_save) ImGui::BeginDisabled(); + if (!can_save) + ImGui::BeginDisabled(); if (ImGui::Button(absl::StrFormat("%s Save", ICON_MD_SAVE).c_str())) { auto status = SaveFile(); if (status.ok() && toast_manager_) { @@ -62,8 +67,9 @@ void ProjectFileEditor::Draw() { toast_manager_->Show(std::string(status.message()), ToastType::kError); } } - if (!can_save) ImGui::EndDisabled(); - + if (!can_save) + ImGui::EndDisabled(); + ImGui::TableNextColumn(); if (ImGui::Button(absl::StrFormat("%s Save As", ICON_MD_SAVE_AS).c_str())) { auto file = util::FileDialogWrapper::ShowSaveFileDialog( @@ -73,41 +79,43 @@ void ProjectFileEditor::Draw() { if (status.ok() && toast_manager_) { toast_manager_->Show("Project file saved", ToastType::kSuccess); } else if (!status.ok() && toast_manager_) { - toast_manager_->Show(std::string(status.message()), ToastType::kError); + toast_manager_->Show(std::string(status.message()), + ToastType::kError); } } } - + ImGui::TableNextColumn(); ImGui::Text("|"); - + ImGui::TableNextColumn(); - if (ImGui::Button(absl::StrFormat("%s Validate", ICON_MD_CHECK_CIRCLE).c_str())) { + if (ImGui::Button( + absl::StrFormat("%s Validate", ICON_MD_CHECK_CIRCLE).c_str())) { ValidateContent(); show_validation_ = true; } - + ImGui::TableNextColumn(); ImGui::Checkbox("Show Validation", &show_validation_); - + ImGui::TableNextColumn(); if (!filepath_.empty()) { ImGui::TextDisabled("%s", filepath_.c_str()); } else { ImGui::TextDisabled("No file loaded"); } - + ImGui::EndTable(); } - + ImGui::Separator(); - + // Validation errors panel if (show_validation_ && !validation_errors_.empty()) { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.3f, 0.2f, 0.2f, 0.5f)); if (ImGui::BeginChild("ValidationErrors", ImVec2(0, 100), true)) { - ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), - "%s Validation Errors:", ICON_MD_ERROR); + ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), + "%s Validation Errors:", ICON_MD_ERROR); for (const auto& error : validation_errors_) { ImGui::BulletText("%s", error.c_str()); } @@ -115,11 +123,11 @@ void ProjectFileEditor::Draw() { ImGui::EndChild(); ImGui::PopStyleColor(); } - + // Main editor ImVec2 editor_size = ImGui::GetContentRegionAvail(); text_editor_.Render("##ProjectEditor", editor_size); - + ImGui::End(); } @@ -129,17 +137,17 @@ absl::Status ProjectFileEditor::LoadFile(const std::string& filepath) { return absl::InvalidArgumentError( absl::StrFormat("Cannot open file: %s", filepath)); } - + std::stringstream buffer; buffer << file.rdbuf(); file.close(); - + text_editor_.SetText(buffer.str()); filepath_ = filepath; modified_ = false; - + ValidateContent(); - + return absl::OkStatus(); } @@ -147,7 +155,7 @@ absl::Status ProjectFileEditor::SaveFile() { if (filepath_.empty()) { return absl::InvalidArgumentError("No file path specified"); } - + return SaveFileAs(filepath_); } @@ -157,24 +165,24 @@ absl::Status ProjectFileEditor::SaveFileAs(const std::string& filepath) { if (!absl::EndsWith(final_path, ".yaze")) { final_path += ".yaze"; } - + std::ofstream file(final_path); if (!file.is_open()) { return absl::InvalidArgumentError( absl::StrFormat("Cannot create file: %s", final_path)); } - + file << text_editor_.GetText(); file.close(); - + filepath_ = final_path; modified_ = false; - + // Add to recent files auto& recent_mgr = project::RecentFilesManager::GetInstance(); recent_mgr.AddFile(filepath_); recent_mgr.Save(); - + return absl::OkStatus(); } @@ -217,7 +225,7 @@ autosave_enabled=true autosave_interval_secs=300 theme=dark )"; - + text_editor_.SetText(template_content); filepath_.clear(); modified_ = true; @@ -231,54 +239,54 @@ void ProjectFileEditor::ApplySyntaxHighlighting() { void ProjectFileEditor::ValidateContent() { validation_errors_.clear(); - + std::string content = text_editor_.GetText(); std::vector lines = absl::StrSplit(content, '\n'); - + std::string current_section; int line_num = 0; - + for (const auto& line : lines) { line_num++; std::string trimmed = std::string(absl::StripAsciiWhitespace(line)); - + // Skip empty lines and comments - if (trimmed.empty() || trimmed[0] == '#') continue; - + if (trimmed.empty() || trimmed[0] == '#') + continue; + // Check for section headers if (trimmed[0] == '[' && trimmed[trimmed.size() - 1] == ']') { current_section = trimmed.substr(1, trimmed.size() - 2); - + // Validate known sections - if (current_section != "project" && - current_section != "files" && + if (current_section != "project" && current_section != "files" && current_section != "feature_flags" && current_section != "workspace_settings" && current_section != "build_settings") { - validation_errors_.push_back( - absl::StrFormat("Line %d: Unknown section [%s]", - line_num, current_section)); + validation_errors_.push_back(absl::StrFormat( + "Line %d: Unknown section [%s]", line_num, current_section)); } continue; } - + // Check for key=value pairs size_t equals_pos = trimmed.find('='); if (equals_pos == std::string::npos) { - validation_errors_.push_back( - absl::StrFormat("Line %d: Invalid format, expected key=value", line_num)); + validation_errors_.push_back(absl::StrFormat( + "Line %d: Invalid format, expected key=value", line_num)); continue; } } - + if (validation_errors_.empty() && show_validation_ && toast_manager_) { toast_manager_->Show("Project file validation passed", ToastType::kSuccess); } } void ProjectFileEditor::ShowValidationErrors() { - if (validation_errors_.empty()) return; - + if (validation_errors_.empty()) + return; + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Validation Errors:"); for (const auto& error : validation_errors_) { ImGui::BulletText("%s", error.c_str()); diff --git a/src/app/editor/code/project_file_editor.h b/src/app/editor/code/project_file_editor.h index d943bf6d..d8462f06 100644 --- a/src/app/editor/code/project_file_editor.h +++ b/src/app/editor/code/project_file_editor.h @@ -4,8 +4,8 @@ #include #include "absl/status/status.h" -#include "core/project.h" #include "app/gui/widgets/text_editor.h" +#include "core/project.h" namespace yaze { namespace editor { @@ -25,61 +25,61 @@ class ToastManager; class ProjectFileEditor { public: ProjectFileEditor(); - + void Draw(); - + /** * @brief Load a project file into the editor */ absl::Status LoadFile(const std::string& filepath); - + /** * @brief Save the current editor contents to disk */ absl::Status SaveFile(); - + /** * @brief Save to a new file path */ absl::Status SaveFileAs(const std::string& filepath); - + /** * @brief Get whether the file has unsaved changes */ bool IsModified() const { return text_editor_.IsTextChanged() || modified_; } - + /** * @brief Get the current filepath */ const std::string& filepath() const { return filepath_; } - + /** * @brief Set whether the editor window is active */ void set_active(bool active) { active_ = active; } - + /** * @brief Get pointer to active state for ImGui */ bool* active() { return &active_; } - + /** * @brief Set toast manager for notifications */ void SetToastManager(ToastManager* toast_manager) { toast_manager_ = toast_manager; } - + /** * @brief Create a new empty project file */ void NewFile(); - + private: void ApplySyntaxHighlighting(); void ValidateContent(); void ShowValidationErrors(); - + TextEditor text_editor_; std::string filepath_; bool active_ = false; diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.cc b/src/app/editor/dungeon/dungeon_canvas_viewer.cc index d18bc7ea..73976ff3 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.cc +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.cc @@ -5,10 +5,10 @@ #include "app/gfx/types/snes_palette.h" #include "app/gui/core/input.h" #include "app/rom.h" -#include "zelda3/dungeon/room.h" -#include "zelda3/sprite/sprite.h" #include "imgui/imgui.h" #include "util/log.h" +#include "zelda3/dungeon/room.h" +#include "zelda3/sprite/sprite.h" namespace yaze::editor { @@ -24,14 +24,14 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { ImGui::Text("Invalid room ID: %d", room_id); return; } - + if (!rom_ || !rom_->is_loaded()) { ImGui::Text("ROM not loaded"); return; } ImGui::BeginGroup(); - + // CRITICAL: Canvas coordinate system for dungeons // The canvas system uses a two-stage scaling model: // 1. Canvas size: UNSCALED content dimensions (512x512 for dungeon rooms) @@ -40,147 +40,165 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { // 4. Bitmaps: drawn with scale = global_scale (matches viewport) constexpr int kRoomPixelWidth = 512; // 64 tiles * 8 pixels (UNSCALED) constexpr int kRoomPixelHeight = 512; - constexpr int kDungeonTileSize = 8; // Dungeon tiles are 8x8 pixels - + constexpr int kDungeonTileSize = 8; // Dungeon tiles are 8x8 pixels + // Configure canvas for dungeon display canvas_.SetCanvasSize(ImVec2(kRoomPixelWidth, kRoomPixelHeight)); canvas_.SetGridSize(gui::CanvasGridSize::k8x8); // Match dungeon tile size - + // DEBUG: Log canvas configuration static int debug_frame_count = 0; if (debug_frame_count++ % 60 == 0) { // Log once per second (assuming 60fps) - LOG_DEBUG("[DungeonCanvas]", "Canvas config: size=(%.0f,%.0f) scale=%.2f grid=%.0f", - canvas_.width(), canvas_.height(), canvas_.global_scale(), canvas_.custom_step()); - LOG_DEBUG("[DungeonCanvas]", "Canvas viewport: p0=(%.0f,%.0f) p1=(%.0f,%.0f)", - canvas_.zero_point().x, canvas_.zero_point().y, - canvas_.zero_point().x + canvas_.width() * canvas_.global_scale(), - canvas_.zero_point().y + canvas_.height() * canvas_.global_scale()); + LOG_DEBUG("[DungeonCanvas]", + "Canvas config: size=(%.0f,%.0f) scale=%.2f grid=%.0f", + canvas_.width(), canvas_.height(), canvas_.global_scale(), + canvas_.custom_step()); + LOG_DEBUG( + "[DungeonCanvas]", "Canvas viewport: p0=(%.0f,%.0f) p1=(%.0f,%.0f)", + canvas_.zero_point().x, canvas_.zero_point().y, + canvas_.zero_point().x + canvas_.width() * canvas_.global_scale(), + canvas_.zero_point().y + canvas_.height() * canvas_.global_scale()); } if (rooms_) { auto& room = (*rooms_)[room_id]; - + // Store previous values to detect changes static int prev_blockset = -1; static int prev_palette = -1; static int prev_layout = -1; static int prev_spriteset = -1; - + // Room properties in organized table - if (ImGui::BeginTable("##RoomProperties", 4, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) { + if (ImGui::BeginTable( + "##RoomProperties", 4, + ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Graphics"); ImGui::TableSetupColumn("Layout"); ImGui::TableSetupColumn("Floors"); ImGui::TableSetupColumn("Message"); ImGui::TableHeadersRow(); - + ImGui::TableNextRow(); - + // Column 1: Graphics (Blockset, Spriteset, Palette) ImGui::TableNextColumn(); gui::InputHexByte("Gfx", &room.blockset, 50.f); gui::InputHexByte("Sprite", &room.spriteset, 50.f); gui::InputHexByte("Palette", &room.palette, 50.f); - + // Column 2: Layout ImGui::TableNextColumn(); gui::InputHexByte("Layout", &room.layout, 50.f); - + // Column 3: Floors ImGui::TableNextColumn(); uint8_t floor1_val = room.floor1(); uint8_t floor2_val = room.floor2(); - if (gui::InputHexByte("Floor1", &floor1_val, 50.f) && ImGui::IsItemDeactivatedAfterEdit()) { + if (gui::InputHexByte("Floor1", &floor1_val, 50.f) && + ImGui::IsItemDeactivatedAfterEdit()) { room.set_floor1(floor1_val); if (room.rom() && room.rom()->is_loaded()) { room.RenderRoomGraphics(); } } - if (gui::InputHexByte("Floor2", &floor2_val, 50.f) && ImGui::IsItemDeactivatedAfterEdit()) { + if (gui::InputHexByte("Floor2", &floor2_val, 50.f) && + ImGui::IsItemDeactivatedAfterEdit()) { room.set_floor2(floor2_val); if (room.rom() && room.rom()->is_loaded()) { room.RenderRoomGraphics(); } } - + // Column 4: Message ImGui::TableNextColumn(); gui::InputHexWord("MsgID", &room.message_id_, 70.f); - + ImGui::EndTable(); } - + // Advanced room properties (Effect, Tags, Layer Merge) ImGui::Separator(); - if (ImGui::BeginTable("##AdvancedProperties", 3, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) { + if (ImGui::BeginTable( + "##AdvancedProperties", 3, + ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Effect"); ImGui::TableSetupColumn("Tag 1"); ImGui::TableSetupColumn("Tag 2"); ImGui::TableHeadersRow(); - + ImGui::TableNextRow(); - + // Effect dropdown ImGui::TableNextColumn(); - const char* effect_names[] = {"Nothing", "One", "Moving Floor", "Moving Water", "Four", "Red Flashes", "Torch Show Floor", "Ganon Room"}; + const char* effect_names[] = { + "Nothing", "One", "Moving Floor", "Moving Water", + "Four", "Red Flashes", "Torch Show Floor", "Ganon Room"}; int effect_val = static_cast(room.effect()); if (ImGui::Combo("##Effect", &effect_val, effect_names, 8)) { room.SetEffect(static_cast(effect_val)); } - + // Tag 1 dropdown (abbreviated for space) ImGui::TableNextColumn(); - const char* tag_names[] = {"Nothing", "NW Kill", "NE Kill", "SW Kill", "SE Kill", "W Kill", "E Kill", "N Kill", "S Kill", - "Clear Quad", "Clear Room", "NW Push", "NE Push", "SW Push", "SE Push", "W Push", "E Push", - "N Push", "S Push", "Push Block", "Pull Lever", "Clear Level", "Switch Hold", "Switch Toggle"}; + const char* tag_names[] = { + "Nothing", "NW Kill", "NE Kill", "SW Kill", + "SE Kill", "W Kill", "E Kill", "N Kill", + "S Kill", "Clear Quad", "Clear Room", "NW Push", + "NE Push", "SW Push", "SE Push", "W Push", + "E Push", "N Push", "S Push", "Push Block", + "Pull Lever", "Clear Level", "Switch Hold", "Switch Toggle"}; int tag1_val = static_cast(room.tag1()); if (ImGui::Combo("##Tag1", &tag1_val, tag_names, 24)) { room.SetTag1(static_cast(tag1_val)); } - + // Tag 2 dropdown ImGui::TableNextColumn(); int tag2_val = static_cast(room.tag2()); if (ImGui::Combo("##Tag2", &tag2_val, tag_names, 24)) { room.SetTag2(static_cast(tag2_val)); } - + ImGui::EndTable(); } - + // Layer visibility and merge controls ImGui::Separator(); - if (ImGui::BeginTable("##LayerControls", 4, ImGuiTableFlags_SizingStretchSame)) { + if (ImGui::BeginTable("##LayerControls", 4, + ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); - + ImGui::TableNextColumn(); auto& layer_settings = GetRoomLayerSettings(room_id); ImGui::Checkbox("BG1", &layer_settings.bg1_visible); - + ImGui::TableNextColumn(); ImGui::Checkbox("BG2", &layer_settings.bg2_visible); - + ImGui::TableNextColumn(); // BG2 layer type const char* bg2_types[] = {"Norm", "Trans", "Add", "Dark", "Off"}; ImGui::SetNextItemWidth(-FLT_MIN); ImGui::Combo("##BG2Type", &layer_settings.bg2_layer_type, bg2_types, 5); - + ImGui::TableNextColumn(); // Layer merge type - const char* merge_types[] = {"Off", "Parallax", "Dark", "On top", "Translucent", "Addition", "Normal", "Transparent", "Dark room"}; + const char* merge_types[] = {"Off", "Parallax", "Dark", + "On top", "Translucent", "Addition", + "Normal", "Transparent", "Dark room"}; int merge_val = room.layer_merging().ID; if (ImGui::Combo("##Merge", &merge_val, merge_types, 9)) { room.SetLayerMerging(zelda3::kLayerMergeTypeList[merge_val]); } - + ImGui::EndTable(); } - + // Check if critical properties changed and trigger reload - if (prev_blockset != room.blockset || prev_palette != room.palette || + if (prev_blockset != room.blockset || prev_palette != room.palette || prev_layout != room.layout || prev_spriteset != room.spriteset) { - + // Only reload if ROM is properly loaded if (room.rom() && room.rom()->is_loaded()) { // Force reload of room graphics @@ -188,7 +206,7 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { room.LoadRoomGraphics(room.blockset); room.RenderRoomGraphics(); // Applies palettes internally } - + prev_blockset = room.blockset; prev_palette = room.palette; prev_layout = room.layout; @@ -201,187 +219,167 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { // CRITICAL: Draw canvas with explicit size to ensure viewport matches content // Pass the unscaled room size directly to DrawBackground canvas_.DrawBackground(ImVec2(kRoomPixelWidth, kRoomPixelHeight)); - + // DEBUG: Log canvas state after DrawBackground if (debug_frame_count % 60 == 1) { - LOG_DEBUG("[DungeonCanvas]", "After DrawBackground: canvas_sz=(%.0f,%.0f) canvas_p0=(%.0f,%.0f) canvas_p1=(%.0f,%.0f)", + LOG_DEBUG("[DungeonCanvas]", + "After DrawBackground: canvas_sz=(%.0f,%.0f) " + "canvas_p0=(%.0f,%.0f) canvas_p1=(%.0f,%.0f)", canvas_.canvas_size().x, canvas_.canvas_size().y, canvas_.zero_point().x, canvas_.zero_point().y, - canvas_.zero_point().x + canvas_.canvas_size().x, canvas_.zero_point().y + canvas_.canvas_size().y); + canvas_.zero_point().x + canvas_.canvas_size().x, + canvas_.zero_point().y + canvas_.canvas_size().y); } - + // Add dungeon-specific context menu items canvas_.ClearContextMenuItems(); - + if (rooms_ && rom_->is_loaded()) { auto& room = (*rooms_)[room_id]; auto& layer_settings = GetRoomLayerSettings(room_id); - + // Add object placement option - canvas_.AddContextMenuItem( - gui::CanvasMenuItem(ICON_MD_ADD " Place Object", ICON_MD_ADD, - []() { - // TODO: Show object palette/selector - }, - "Ctrl+P") - ); - + canvas_.AddContextMenuItem(gui::CanvasMenuItem( + ICON_MD_ADD " Place Object", ICON_MD_ADD, + []() { + // TODO: Show object palette/selector + }, + "Ctrl+P")); + // Add object deletion for selected objects - canvas_.AddContextMenuItem( - gui::CanvasMenuItem(ICON_MD_DELETE " Delete Selected", ICON_MD_DELETE, - [this]() { - object_interaction_.HandleDeleteSelected(); - }, - "Del") - ); - + canvas_.AddContextMenuItem(gui::CanvasMenuItem( + ICON_MD_DELETE " Delete Selected", ICON_MD_DELETE, + [this]() { object_interaction_.HandleDeleteSelected(); }, "Del")); + // Add room property quick toggles - canvas_.AddContextMenuItem( - gui::CanvasMenuItem(ICON_MD_LAYERS " Toggle BG1", ICON_MD_LAYERS, - [this, room_id]() { - auto& settings = GetRoomLayerSettings(room_id); - settings.bg1_visible = !settings.bg1_visible; - }, - "1") - ); - - canvas_.AddContextMenuItem( - gui::CanvasMenuItem(ICON_MD_LAYERS " Toggle BG2", ICON_MD_LAYERS, - [this, room_id]() { - auto& settings = GetRoomLayerSettings(room_id); - settings.bg2_visible = !settings.bg2_visible; - }, - "2") - ); - + canvas_.AddContextMenuItem(gui::CanvasMenuItem( + ICON_MD_LAYERS " Toggle BG1", ICON_MD_LAYERS, + [this, room_id]() { + auto& settings = GetRoomLayerSettings(room_id); + settings.bg1_visible = !settings.bg1_visible; + }, + "1")); + + canvas_.AddContextMenuItem(gui::CanvasMenuItem( + ICON_MD_LAYERS " Toggle BG2", ICON_MD_LAYERS, + [this, room_id]() { + auto& settings = GetRoomLayerSettings(room_id); + settings.bg2_visible = !settings.bg2_visible; + }, + "2")); + // Add re-render option - canvas_.AddContextMenuItem( - gui::CanvasMenuItem(ICON_MD_REFRESH " Re-render Room", ICON_MD_REFRESH, - [&room]() { - room.RenderRoomGraphics(); - }, - "Ctrl+R") - ); - + canvas_.AddContextMenuItem(gui::CanvasMenuItem( + ICON_MD_REFRESH " Re-render Room", ICON_MD_REFRESH, + [&room]() { room.RenderRoomGraphics(); }, "Ctrl+R")); + // === DEBUG MENU === gui::CanvasMenuItem debug_menu; debug_menu.label = ICON_MD_BUG_REPORT " Debug"; - + // Show room info - debug_menu.subitems.push_back( - gui::CanvasMenuItem(ICON_MD_INFO " Show Room Info", ICON_MD_INFO, - [this]() { - show_room_debug_info_ = !show_room_debug_info_; - }) - ); - + debug_menu.subitems.push_back(gui::CanvasMenuItem( + ICON_MD_INFO " Show Room Info", ICON_MD_INFO, + [this]() { show_room_debug_info_ = !show_room_debug_info_; })); + // Show texture info - debug_menu.subitems.push_back( - gui::CanvasMenuItem(ICON_MD_IMAGE " Show Texture Debug", ICON_MD_IMAGE, - [this]() { - show_texture_debug_ = !show_texture_debug_; - }) - ); - + debug_menu.subitems.push_back(gui::CanvasMenuItem( + ICON_MD_IMAGE " Show Texture Debug", ICON_MD_IMAGE, + [this]() { show_texture_debug_ = !show_texture_debug_; })); + // Show object bounds with sub-menu for categories gui::CanvasMenuItem object_bounds_menu; object_bounds_menu.label = ICON_MD_CROP_SQUARE " Show Object Bounds"; object_bounds_menu.callback = [this]() { show_object_bounds_ = !show_object_bounds_; }; - + // Sub-menu for filtering by type object_bounds_menu.subitems.push_back( - gui::CanvasMenuItem("Type 1 (0x00-0xFF)", - [this]() { - object_outline_toggles_.show_type1_objects = !object_outline_toggles_.show_type1_objects; - }) - ); + gui::CanvasMenuItem("Type 1 (0x00-0xFF)", [this]() { + object_outline_toggles_.show_type1_objects = + !object_outline_toggles_.show_type1_objects; + })); object_bounds_menu.subitems.push_back( - gui::CanvasMenuItem("Type 2 (0x100-0x1FF)", - [this]() { - object_outline_toggles_.show_type2_objects = !object_outline_toggles_.show_type2_objects; - }) - ); + gui::CanvasMenuItem("Type 2 (0x100-0x1FF)", [this]() { + object_outline_toggles_.show_type2_objects = + !object_outline_toggles_.show_type2_objects; + })); object_bounds_menu.subitems.push_back( - gui::CanvasMenuItem("Type 3 (0xF00-0xFFF)", - [this]() { - object_outline_toggles_.show_type3_objects = !object_outline_toggles_.show_type3_objects; - }) - ); - + gui::CanvasMenuItem("Type 3 (0xF00-0xFFF)", [this]() { + object_outline_toggles_.show_type3_objects = + !object_outline_toggles_.show_type3_objects; + })); + // Separator gui::CanvasMenuItem sep; sep.label = "---"; - sep.enabled_condition = []() { return false; }; + sep.enabled_condition = []() { + return false; + }; object_bounds_menu.subitems.push_back(sep); - + // Sub-menu for filtering by layer object_bounds_menu.subitems.push_back( - gui::CanvasMenuItem("Layer 0 (BG1)", - [this]() { - object_outline_toggles_.show_layer0_objects = !object_outline_toggles_.show_layer0_objects; - }) - ); + gui::CanvasMenuItem("Layer 0 (BG1)", [this]() { + object_outline_toggles_.show_layer0_objects = + !object_outline_toggles_.show_layer0_objects; + })); object_bounds_menu.subitems.push_back( - gui::CanvasMenuItem("Layer 1 (BG2)", - [this]() { - object_outline_toggles_.show_layer1_objects = !object_outline_toggles_.show_layer1_objects; - }) - ); + gui::CanvasMenuItem("Layer 1 (BG2)", [this]() { + object_outline_toggles_.show_layer1_objects = + !object_outline_toggles_.show_layer1_objects; + })); object_bounds_menu.subitems.push_back( - gui::CanvasMenuItem("Layer 2 (BG3)", - [this]() { - object_outline_toggles_.show_layer2_objects = !object_outline_toggles_.show_layer2_objects; - }) - ); - + gui::CanvasMenuItem("Layer 2 (BG3)", [this]() { + object_outline_toggles_.show_layer2_objects = + !object_outline_toggles_.show_layer2_objects; + })); + debug_menu.subitems.push_back(object_bounds_menu); - + // Show layer info - debug_menu.subitems.push_back( - gui::CanvasMenuItem(ICON_MD_LAYERS " Show Layer Info", ICON_MD_LAYERS, - [this]() { - show_layer_info_ = !show_layer_info_; - }) - ); - + debug_menu.subitems.push_back(gui::CanvasMenuItem( + ICON_MD_LAYERS " Show Layer Info", ICON_MD_LAYERS, + [this]() { show_layer_info_ = !show_layer_info_; })); + // Force reload room - debug_menu.subitems.push_back( - gui::CanvasMenuItem(ICON_MD_REFRESH " Force Reload", ICON_MD_REFRESH, - [&room]() { - room.LoadObjects(); - room.LoadRoomGraphics(room.blockset); - room.RenderRoomGraphics(); - }) - ); - + debug_menu.subitems.push_back(gui::CanvasMenuItem( + ICON_MD_REFRESH " Force Reload", ICON_MD_REFRESH, [&room]() { + room.LoadObjects(); + room.LoadRoomGraphics(room.blockset); + room.RenderRoomGraphics(); + })); + // Log room state - debug_menu.subitems.push_back( - gui::CanvasMenuItem(ICON_MD_PRINT " Log Room State", ICON_MD_PRINT, - [&room, room_id]() { - LOG_DEBUG("DungeonDebug", "=== Room %03X Debug ===", room_id); - LOG_DEBUG("DungeonDebug", "Blockset: %d, Palette: %d, Layout: %d", - room.blockset, room.palette, room.layout); - LOG_DEBUG("DungeonDebug", "Objects: %zu, Sprites: %zu", - room.GetTileObjects().size(), room.GetSprites().size()); - LOG_DEBUG("DungeonDebug", "BG1: %dx%d, BG2: %dx%d", - room.bg1_buffer().bitmap().width(), room.bg1_buffer().bitmap().height(), - room.bg2_buffer().bitmap().width(), room.bg2_buffer().bitmap().height()); - }) - ); - + debug_menu.subitems.push_back(gui::CanvasMenuItem( + ICON_MD_PRINT " Log Room State", ICON_MD_PRINT, [&room, room_id]() { + LOG_DEBUG("DungeonDebug", "=== Room %03X Debug ===", room_id); + LOG_DEBUG("DungeonDebug", "Blockset: %d, Palette: %d, Layout: %d", + room.blockset, room.palette, room.layout); + LOG_DEBUG("DungeonDebug", "Objects: %zu, Sprites: %zu", + room.GetTileObjects().size(), room.GetSprites().size()); + LOG_DEBUG("DungeonDebug", "BG1: %dx%d, BG2: %dx%d", + room.bg1_buffer().bitmap().width(), + room.bg1_buffer().bitmap().height(), + room.bg2_buffer().bitmap().width(), + room.bg2_buffer().bitmap().height()); + })); + canvas_.AddContextMenuItem(debug_menu); } - + canvas_.DrawContextMenu(); - + // Draw persistent debug overlays if (show_room_debug_info_ && rooms_ && rom_->is_loaded()) { auto& room = (*rooms_)[room_id]; - ImGui::SetNextWindowPos(ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos( + ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10), + ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Room Debug Info", &show_room_debug_info_, ImGuiWindowFlags_NoCollapse)) { + if (ImGui::Begin("Room Debug Info", &show_room_debug_info_, + ImGuiWindowFlags_NoCollapse)) { ImGui::Text("Room: 0x%03X (%d)", room_id, room_id); ImGui::Separator(); ImGui::Text("Graphics"); @@ -397,7 +395,7 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { ImGui::Text("Buffers"); auto& bg1 = room.bg1_buffer().bitmap(); auto& bg2 = room.bg2_buffer().bitmap(); - ImGui::Text(" BG1: %dx%d %s", bg1.width(), bg1.height(), + ImGui::Text(" BG1: %dx%d %s", bg1.width(), bg1.height(), bg1.texture() ? "(has texture)" : "(NO TEXTURE)"); ImGui::Text(" BG2: %dx%d %s", bg2.width(), bg2.height(), bg2.texture() ? "(has texture)" : "(NO TEXTURE)"); @@ -407,7 +405,7 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { ImGui::Checkbox("BG1 Visible", &layer_settings.bg1_visible); ImGui::Checkbox("BG2 Visible", &layer_settings.bg2_visible); ImGui::SliderInt("BG2 Type", &layer_settings.bg2_layer_type, 0, 4); - + ImGui::Separator(); ImGui::Text("Layout Override"); static bool enable_override = false; @@ -415,7 +413,7 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { if (enable_override) { ImGui::SliderInt("Layout ID", &layout_override_, 0, 7); } else { - layout_override_ = -1; // Disable override + layout_override_ = -1; // Disable override } if (show_object_bounds_) { @@ -426,40 +424,46 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { ImGui::Checkbox("Type 2", &object_outline_toggles_.show_type2_objects); ImGui::Checkbox("Type 3", &object_outline_toggles_.show_type3_objects); ImGui::Text("By Layer:"); - ImGui::Checkbox("Layer 0", &object_outline_toggles_.show_layer0_objects); - ImGui::Checkbox("Layer 1", &object_outline_toggles_.show_layer1_objects); - ImGui::Checkbox("Layer 2", &object_outline_toggles_.show_layer2_objects); + ImGui::Checkbox("Layer 0", + &object_outline_toggles_.show_layer0_objects); + ImGui::Checkbox("Layer 1", + &object_outline_toggles_.show_layer1_objects); + ImGui::Checkbox("Layer 2", + &object_outline_toggles_.show_layer2_objects); } } ImGui::End(); } - + if (show_texture_debug_ && rooms_ && rom_->is_loaded()) { - ImGui::SetNextWindowPos(ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos( + ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10), + ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Texture Debug", &show_texture_debug_, ImGuiWindowFlags_NoCollapse)) { + if (ImGui::Begin("Texture Debug", &show_texture_debug_, + ImGuiWindowFlags_NoCollapse)) { auto& room = (*rooms_)[room_id]; auto& bg1 = room.bg1_buffer().bitmap(); auto& bg2 = room.bg2_buffer().bitmap(); - + ImGui::Text("BG1 Bitmap"); ImGui::Text(" Size: %dx%d", bg1.width(), bg1.height()); ImGui::Text(" Active: %s", bg1.is_active() ? "YES" : "NO"); ImGui::Text(" Texture: 0x%p", bg1.texture()); ImGui::Text(" Modified: %s", bg1.modified() ? "YES" : "NO"); - + if (bg1.texture()) { ImGui::Text(" Preview:"); ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128)); } - + ImGui::Separator(); ImGui::Text("BG2 Bitmap"); ImGui::Text(" Size: %dx%d", bg2.width(), bg2.height()); ImGui::Text(" Active: %s", bg2.is_active() ? "YES" : "NO"); ImGui::Text(" Texture: 0x%p", bg2.texture()); ImGui::Text(" Modified: %s", bg2.modified() ? "YES" : "NO"); - + if (bg2.texture()) { ImGui::Text(" Preview:"); ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128)); @@ -467,90 +471,100 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { } ImGui::End(); } - + if (show_layer_info_) { - ImGui::SetNextWindowPos(ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos( + ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10), + ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(200, 0), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Layer Info", &show_layer_info_, ImGuiWindowFlags_NoCollapse)) { + if (ImGui::Begin("Layer Info", &show_layer_info_, + ImGuiWindowFlags_NoCollapse)) { ImGui::Text("Canvas Scale: %.2f", canvas_.global_scale()); ImGui::Text("Canvas Size: %.0fx%.0f", canvas_.width(), canvas_.height()); auto& layer_settings = GetRoomLayerSettings(room_id); ImGui::Separator(); ImGui::Text("Layer Visibility:"); - ImGui::Text(" BG1: %s", layer_settings.bg1_visible ? "VISIBLE" : "hidden"); - ImGui::Text(" BG2: %s", layer_settings.bg2_visible ? "VISIBLE" : "hidden"); + ImGui::Text(" BG1: %s", + layer_settings.bg1_visible ? "VISIBLE" : "hidden"); + ImGui::Text(" BG2: %s", + layer_settings.bg2_visible ? "VISIBLE" : "hidden"); ImGui::Text("BG2 Type: %d", layer_settings.bg2_layer_type); - const char* bg2_type_names[] = {"Normal", "Translucent", "Addition", "Dark", "Off"}; - ImGui::Text(" (%s)", bg2_type_names[std::min(layer_settings.bg2_layer_type, 4)]); + const char* bg2_type_names[] = {"Normal", "Translucent", "Addition", + "Dark", "Off"}; + ImGui::Text(" (%s)", + bg2_type_names[std::min(layer_settings.bg2_layer_type, 4)]); } ImGui::End(); } - + if (rooms_ && rom_->is_loaded()) { auto& room = (*rooms_)[room_id]; - + // Update object interaction context object_interaction_.SetCurrentRoom(rooms_, room_id); - + // Check if THIS ROOM's buffers need rendering (not global arena!) auto& bg1_bitmap = room.bg1_buffer().bitmap(); bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0; - + // Render immediately if needed (but only once per room change) static int last_rendered_room = -1; static bool has_rendered = false; if (needs_render && (last_rendered_room != room_id || !has_rendered)) { - printf("[DungeonCanvasViewer] Loading and rendering graphics for room %d\n", room_id); + printf( + "[DungeonCanvasViewer] Loading and rendering graphics for room %d\n", + room_id); (void)LoadAndRenderRoomGraphics(room_id); last_rendered_room = room_id; has_rendered = true; } - + // Load room objects if not already loaded if (room.GetTileObjects().empty()) { room.LoadObjects(); } - + // CRITICAL: Process texture queue BEFORE drawing to ensure textures are ready // This must happen before DrawRoomBackgroundLayers() attempts to draw bitmaps if (rom_ && rom_->is_loaded()) { gfx::Arena::Get().ProcessTextureQueue(nullptr); } - + // Draw the room's background layers to canvas // This already includes objects rendered by ObjectDrawer in Room::RenderObjectsToBackground() DrawRoomBackgroundLayers(room_id); - + // Render sprites as simple 16x16 squares with labels // (Sprites are not part of the background buffers) RenderSprites(room); - + // Handle object interaction if enabled if (object_interaction_enabled_) { object_interaction_.HandleCanvasMouseInput(); object_interaction_.CheckForObjectSelection(); object_interaction_.DrawSelectBox(); - object_interaction_.DrawSelectionHighlights(); // Draw selection highlights on top + object_interaction_ + .DrawSelectionHighlights(); // Draw selection highlights on top object_interaction_.ShowContextMenu(); // Show dungeon-aware context menu } } - + // Draw optional overlays on top of background bitmap if (rooms_ && rom_->is_loaded()) { auto& room = (*rooms_)[room_id]; - + // Draw the room layout first as the base layer - + // VISUALIZATION: Draw object position rectangles (for debugging) // This shows where objects are placed regardless of whether graphics render if (show_object_bounds_) { DrawObjectPositionOutlines(room); } } - + canvas_.DrawGrid(); canvas_.DrawOverlay(); - + // Draw layer information overlay if (rooms_ && rom_->is_loaded()) { auto& room = (*rooms_)[room_id]; @@ -559,13 +573,12 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { "Layers are game concept: Objects exist on different levels\n" "connected by stair objects for player navigation", room_id, room.GetTileObjects().size(), room.GetSprites().size()); - + canvas_.DrawText(layer_info, 10, canvas_.height() - 60); } } - -void DungeonCanvasViewer::DisplayObjectInfo(const zelda3::RoomObject &object, +void DungeonCanvasViewer::DisplayObjectInfo(const zelda3::RoomObject& object, int canvas_x, int canvas_y) { // Display object information as text overlay std::string info_text = absl::StrFormat("ID:%d X:%d Y:%d S:%d", object.id_, @@ -579,74 +592,78 @@ void DungeonCanvasViewer::RenderSprites(const zelda3::Room& room) { // Render sprites as simple 8x8 squares with sprite name/ID for (const auto& sprite : room.GetSprites()) { auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(sprite.x(), sprite.y()); - + if (IsWithinCanvasBounds(canvas_x, canvas_y, 8)) { // Draw 8x8 square for sprite ImVec4 sprite_color; - + // Color-code sprites based on layer if (sprite.layer() == 0) { - sprite_color = ImVec4(0.2f, 0.8f, 0.2f, 0.8f); // Green for layer 0 + sprite_color = ImVec4(0.2f, 0.8f, 0.2f, 0.8f); // Green for layer 0 } else { - sprite_color = ImVec4(0.2f, 0.2f, 0.8f, 0.8f); // Blue for layer 1 + sprite_color = ImVec4(0.2f, 0.2f, 0.8f, 0.8f); // Blue for layer 1 } - + canvas_.DrawRect(canvas_x, canvas_y, 8, 8, sprite_color); - + // Draw sprite border - canvas_.DrawRect(canvas_x, canvas_y, 8, 8, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - + canvas_.DrawRect(canvas_x, canvas_y, 8, 8, + ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + // Draw sprite ID and name std::string sprite_text; - if (sprite.id() >= 0) { // sprite.id() is uint8_t so always < 256 + if (sprite.id() >= 0) { // sprite.id() is uint8_t so always < 256 // Extract just the sprite name part (remove ID prefix) std::string full_name = zelda3::kSpriteDefaultNames[sprite.id()]; auto space_pos = full_name.find(' '); - if (space_pos != std::string::npos && space_pos < full_name.length() - 1) { + if (space_pos != std::string::npos && + space_pos < full_name.length() - 1) { std::string sprite_name = full_name.substr(space_pos + 1); // Truncate long names if (sprite_name.length() > 8) { sprite_name = sprite_name.substr(0, 8) + "..."; } - sprite_text = absl::StrFormat("%02X\n%s", sprite.id(), sprite_name.c_str()); + sprite_text = + absl::StrFormat("%02X\n%s", sprite.id(), sprite_name.c_str()); } else { sprite_text = absl::StrFormat("%02X", sprite.id()); } } else { sprite_text = absl::StrFormat("%02X", sprite.id()); } - + canvas_.DrawText(sprite_text, canvas_x + 18, canvas_y); } } } -// Coordinate conversion helper functions -std::pair DungeonCanvasViewer::RoomToCanvasCoordinates(int room_x, - int room_y) const { +// Coordinate conversion helper functions +std::pair DungeonCanvasViewer::RoomToCanvasCoordinates( + int room_x, int room_y) const { // Convert room coordinates (tile units) to UNSCALED canvas pixel coordinates // Dungeon tiles are 8x8 pixels (not 16x16!) // IMPORTANT: Return UNSCALED coordinates - Canvas drawing functions apply scale internally // Do NOT multiply by scale here or we get double-scaling! - + // Simple conversion: tile units → pixel units (no scale, no offset) return {room_x * 8, room_y * 8}; } -std::pair DungeonCanvasViewer::CanvasToRoomCoordinates(int canvas_x, - int canvas_y) const { +std::pair DungeonCanvasViewer::CanvasToRoomCoordinates( + int canvas_x, int canvas_y) const { // Convert canvas screen coordinates (pixels) to room coordinates (tile units) // Input: Screen-space coordinates (affected by zoom/scale) // Output: Logical tile coordinates (0-63 for each axis) - + // IMPORTANT: Mouse coordinates are in screen space, must undo scale first float scale = canvas_.global_scale(); - if (scale <= 0.0f) scale = 1.0f; // Prevent division by zero - + if (scale <= 0.0f) + scale = 1.0f; // Prevent division by zero + // Step 1: Convert screen space → logical pixel space int logical_x = static_cast(canvas_x / scale); int logical_y = static_cast(canvas_y / scale); - + // Step 2: Convert logical pixels → tile units (8 pixels per tile) return {logical_x / 8, logical_y / 8}; } @@ -661,21 +678,22 @@ bool DungeonCanvasViewer::IsWithinCanvasBounds(int canvas_x, int canvas_y, canvas_y <= canvas_height + margin); } -void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height) { +void DungeonCanvasViewer::CalculateWallDimensions( + const zelda3::RoomObject& object, int& width, int& height) { // Default base size width = 8; height = 8; - + // For walls, use the size field to determine length and orientation if (object.id_ >= 0x10 && object.id_ <= 0x1F) { // Wall objects: size determines length and orientation uint8_t size_x = object.size_ & 0x0F; uint8_t size_y = (object.size_ >> 4) & 0x0F; - + // Walls can be horizontal or vertical based on size parameters if (size_x > size_y) { // Horizontal wall - width = 8 + size_x * 8; // Each unit adds 8 pixels + width = 8 + size_x * 8; // Each unit adds 8 pixels height = 8; } else if (size_y > size_x) { // Vertical wall @@ -691,7 +709,7 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje width = 8 + (object.size_ & 0x0F) * 4; height = 8 + ((object.size_ >> 4) & 0x0F) * 4; } - + // Clamp to reasonable limits width = std::min(width, 256); height = std::min(height, 256); @@ -703,9 +721,9 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) { // Draw colored rectangles showing object positions // This helps visualize object placement even if graphics don't render correctly - + const auto& objects = room.GetTileObjects(); - + for (const auto& obj : objects) { // Filter by object type (default to true if unknown type) bool show_this_type = true; // Default to showing @@ -717,7 +735,7 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) { show_this_type = object_outline_toggles_.show_type3_objects; } // else: unknown type, use default (true) - + // Filter by layer (default to true if unknown layer) bool show_this_layer = true; // Default to showing if (obj.GetLayerValue() == 0) { @@ -728,33 +746,33 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) { show_this_layer = object_outline_toggles_.show_layer2_objects; } // else: unknown layer, use default (true) - + // Skip if filtered out if (!show_this_type || !show_this_layer) { continue; } - + // Convert object position (tile coordinates) to canvas pixel coordinates (UNSCALED) auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y()); - + // Calculate object dimensions based on type and size (UNSCALED logical pixels) int width = 8; // Default 8x8 pixels int height = 8; - + // Use ZScream pattern: size field determines dimensions // Lower nibble = horizontal size, upper nibble = vertical size int size_h = (obj.size() & 0x0F); int size_v = (obj.size() >> 4) & 0x0F; - + // Objects are typically (size+1) tiles wide/tall (8 pixels per tile) width = (size_h + 1) * 8; height = (size_v + 1) * 8; - + // IMPORTANT: Do NOT apply canvas scale here - DrawRect handles it // Clamp to reasonable sizes (in logical space) width = std::min(width, 512); height = std::min(height, 512); - + // Color-code by layer ImVec4 outline_color; if (obj.GetLayerValue() == 0) { @@ -764,10 +782,10 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) { } else { outline_color = ImVec4(0.0f, 0.0f, 1.0f, 0.5f); // Blue for layer 2 } - + // Draw outline rectangle canvas_.DrawRect(canvas_x, canvas_y, width, height, outline_color); - + // Draw object ID label (smaller, less obtrusive) std::string label = absl::StrFormat("%02X", obj.id_); canvas_.DrawText(label, canvas_x + 1, canvas_y + 1); @@ -777,68 +795,76 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) { // Room graphics management methods absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) { LOG_DEBUG("[LoadAndRender]", "START room_id=%d", room_id); - + if (room_id < 0 || room_id >= 128) { LOG_DEBUG("[LoadAndRender]", "ERROR: Invalid room ID"); return absl::InvalidArgumentError("Invalid room ID"); } - + if (!rom_ || !rom_->is_loaded()) { LOG_DEBUG("[LoadAndRender]", "ERROR: ROM not loaded"); return absl::FailedPreconditionError("ROM not loaded"); } - + if (!rooms_) { LOG_DEBUG("[LoadAndRender]", "ERROR: Room data not available"); return absl::FailedPreconditionError("Room data not available"); } - + auto& room = (*rooms_)[room_id]; LOG_DEBUG("[LoadAndRender]", "Got room reference"); - + // Load room graphics with proper blockset - LOG_DEBUG("[LoadAndRender]", "Loading graphics for blockset %d", room.blockset); + LOG_DEBUG("[LoadAndRender]", "Loading graphics for blockset %d", + room.blockset); room.LoadRoomGraphics(room.blockset); LOG_DEBUG("[LoadAndRender]", "Graphics loaded"); - + // Load the room's palette with bounds checking - if (room.palette < rom_->paletteset_ids.size() && + if (room.palette < rom_->paletteset_ids.size() && !rom_->paletteset_ids[room.palette].empty()) { auto dungeon_palette_ptr = rom_->paletteset_ids[room.palette][0]; auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr); if (palette_id.ok()) { current_palette_group_id_ = palette_id.value() / 180; - if (current_palette_group_id_ < rom_->palette_group().dungeon_main.size()) { - auto full_palette = rom_->palette_group().dungeon_main[current_palette_group_id_]; + if (current_palette_group_id_ < + rom_->palette_group().dungeon_main.size()) { + auto full_palette = + rom_->palette_group().dungeon_main[current_palette_group_id_]; // TODO: Fix palette assignment to buffer. - ASSIGN_OR_RETURN(current_palette_group_, - gfx::CreatePaletteGroupFromLargePalette(full_palette, 16)); - LOG_DEBUG("[LoadAndRender]", "Palette loaded: group_id=%zu", current_palette_group_id_); + ASSIGN_OR_RETURN( + current_palette_group_, + gfx::CreatePaletteGroupFromLargePalette(full_palette, 16)); + LOG_DEBUG("[LoadAndRender]", "Palette loaded: group_id=%zu", + current_palette_group_id_); } } } - + // Render the room graphics (self-contained - handles all palette application) LOG_DEBUG("[LoadAndRender]", "Calling room.RenderRoomGraphics()..."); room.RenderRoomGraphics(); - LOG_DEBUG("[LoadAndRender]", "RenderRoomGraphics() complete - room buffers self-contained"); - + LOG_DEBUG("[LoadAndRender]", + "RenderRoomGraphics() complete - room buffers self-contained"); + LOG_DEBUG("[LoadAndRender]", "SUCCESS"); return absl::OkStatus(); } void DungeonCanvasViewer::DrawRoomBackgroundLayers(int room_id) { - if (room_id < 0 || room_id >= zelda3::NumberOfRooms || !rooms_) return; - + if (room_id < 0 || room_id >= zelda3::NumberOfRooms || !rooms_) + return; + auto& room = (*rooms_)[room_id]; auto& layer_settings = GetRoomLayerSettings(room_id); - + // Use THIS room's own buffers, not global arena! auto& bg1_bitmap = room.bg1_buffer().bitmap(); auto& bg2_bitmap = room.bg2_buffer().bitmap(); - + // Draw BG1 layer if visible and active - if (layer_settings.bg1_visible && bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) { + if (layer_settings.bg1_visible && bg1_bitmap.is_active() && + bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) { if (!bg1_bitmap.texture()) { // Queue texture creation for background layer 1 via Arena's deferred system // BATCHING FIX: Don't process immediately - let the main loop handle batching @@ -853,15 +879,18 @@ void DungeonCanvasViewer::DrawRoomBackgroundLayers(int room_id) { if (bg1_bitmap.texture()) { // Use canvas global scale so bitmap scales with zoom float scale = canvas_.global_scale(); - LOG_DEBUG("DungeonCanvasViewer", "Drawing BG1 bitmap to canvas with texture %p, scale=%.2f", bg1_bitmap.texture(), scale); + LOG_DEBUG("DungeonCanvasViewer", + "Drawing BG1 bitmap to canvas with texture %p, scale=%.2f", + bg1_bitmap.texture(), scale); canvas_.DrawBitmap(bg1_bitmap, 0, 0, scale, 255); } else { LOG_DEBUG("DungeonCanvasViewer", "ERROR: BG1 bitmap has no texture!"); } - } - + } + // Draw BG2 layer if visible and active - if (layer_settings.bg2_visible && bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) { + if (layer_settings.bg2_visible && bg2_bitmap.is_active() && + bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) { if (!bg2_bitmap.texture()) { // Queue texture creation for background layer 2 via Arena's deferred system // BATCHING FIX: Don't process immediately - let the main loop handle batching @@ -871,54 +900,74 @@ void DungeonCanvasViewer::DrawRoomBackgroundLayers(int room_id) { // Queue will be processed at the end of the frame in DrawDungeonCanvas() // This allows multiple rooms to batch their texture operations together } - + // Only draw if texture was successfully created if (bg2_bitmap.texture()) { // Use the selected BG2 layer type alpha value const int bg2_alpha_values[] = {255, 191, 127, 64, 0}; - int alpha_value = bg2_alpha_values[std::min(layer_settings.bg2_layer_type, 4)]; + int alpha_value = + bg2_alpha_values[std::min(layer_settings.bg2_layer_type, 4)]; // Use canvas global scale so bitmap scales with zoom float scale = canvas_.global_scale(); - LOG_DEBUG("DungeonCanvasViewer", "Drawing BG2 bitmap to canvas with texture %p, alpha=%d, scale=%.2f", bg2_bitmap.texture(), alpha_value, scale); + LOG_DEBUG( + "DungeonCanvasViewer", + "Drawing BG2 bitmap to canvas with texture %p, alpha=%d, scale=%.2f", + bg2_bitmap.texture(), alpha_value, scale); canvas_.DrawBitmap(bg2_bitmap, 0, 0, scale, alpha_value); } else { LOG_DEBUG("DungeonCanvasViewer", "ERROR: BG2 bitmap has no texture!"); } } - + // DEBUG: Check if background buffers have content if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0) { - LOG_DEBUG("DungeonCanvasViewer", "BG1 bitmap: %dx%d, active=%d, visible=%d, texture=%p", - bg1_bitmap.width(), bg1_bitmap.height(), bg1_bitmap.is_active(), layer_settings.bg1_visible, bg1_bitmap.texture()); - + LOG_DEBUG("DungeonCanvasViewer", + "BG1 bitmap: %dx%d, active=%d, visible=%d, texture=%p", + bg1_bitmap.width(), bg1_bitmap.height(), bg1_bitmap.is_active(), + layer_settings.bg1_visible, bg1_bitmap.texture()); + // Check bitmap data content auto& bg1_data = bg1_bitmap.mutable_data(); int non_zero_pixels = 0; - for (size_t i = 0; i < bg1_data.size(); i += 100) { // Sample every 100th pixel - if (bg1_data[i] != 0) non_zero_pixels++; + for (size_t i = 0; i < bg1_data.size(); + i += 100) { // Sample every 100th pixel + if (bg1_data[i] != 0) + non_zero_pixels++; } - LOG_DEBUG("DungeonCanvasViewer", "BG1 bitmap data: %zu pixels, ~%d non-zero samples", - bg1_data.size(), non_zero_pixels); + LOG_DEBUG("DungeonCanvasViewer", + "BG1 bitmap data: %zu pixels, ~%d non-zero samples", + bg1_data.size(), non_zero_pixels); } if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0) { - LOG_DEBUG("DungeonCanvasViewer", "BG2 bitmap: %dx%d, active=%d, visible=%d, layer_type=%d, texture=%p", - bg2_bitmap.width(), bg2_bitmap.height(), bg2_bitmap.is_active(), layer_settings.bg2_visible, layer_settings.bg2_layer_type, bg2_bitmap.texture()); - + LOG_DEBUG( + "DungeonCanvasViewer", + "BG2 bitmap: %dx%d, active=%d, visible=%d, layer_type=%d, texture=%p", + bg2_bitmap.width(), bg2_bitmap.height(), bg2_bitmap.is_active(), + layer_settings.bg2_visible, layer_settings.bg2_layer_type, + bg2_bitmap.texture()); + // Check bitmap data content auto& bg2_data = bg2_bitmap.mutable_data(); int non_zero_pixels = 0; - for (size_t i = 0; i < bg2_data.size(); i += 100) { // Sample every 100th pixel - if (bg2_data[i] != 0) non_zero_pixels++; + for (size_t i = 0; i < bg2_data.size(); + i += 100) { // Sample every 100th pixel + if (bg2_data[i] != 0) + non_zero_pixels++; } - LOG_DEBUG("DungeonCanvasViewer", "BG2 bitmap data: %zu pixels, ~%d non-zero samples", - bg2_data.size(), non_zero_pixels); + LOG_DEBUG("DungeonCanvasViewer", + "BG2 bitmap data: %zu pixels, ~%d non-zero samples", + bg2_data.size(), non_zero_pixels); } - + // DEBUG: Show canvas and bitmap info - LOG_DEBUG("DungeonCanvasViewer", "Canvas pos: (%.1f, %.1f), Canvas size: (%.1f, %.1f)", - canvas_.zero_point().x, canvas_.zero_point().y, canvas_.width(), canvas_.height()); - LOG_DEBUG("DungeonCanvasViewer", "BG1 bitmap size: %dx%d, BG2 bitmap size: %dx%d", - bg1_bitmap.width(), bg1_bitmap.height(), bg2_bitmap.width(), bg2_bitmap.height()); + LOG_DEBUG("DungeonCanvasViewer", + "Canvas pos: (%.1f, %.1f), Canvas size: (%.1f, %.1f)", + canvas_.zero_point().x, canvas_.zero_point().y, canvas_.width(), + canvas_.height()); + LOG_DEBUG("DungeonCanvasViewer", + "BG1 bitmap size: %dx%d, BG2 bitmap size: %dx%d", + bg1_bitmap.width(), bg1_bitmap.height(), bg2_bitmap.width(), + bg2_bitmap.height()); } } // namespace yaze::editor diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.h b/src/app/editor/dungeon/dungeon_canvas_viewer.h index e8f5ec8e..a0cb6c34 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.h +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.h @@ -3,12 +3,12 @@ #include +#include "app/gfx/types/snes_palette.h" #include "app/gui/canvas/canvas.h" #include "app/rom.h" -#include "zelda3/dungeon/room.h" -#include "app/gfx/types/snes_palette.h" #include "dungeon_object_interaction.h" #include "imgui/imgui.h" +#include "zelda3/dungeon/room.h" namespace yaze { namespace editor { @@ -29,10 +29,8 @@ class DungeonCanvasViewer { // DrawDungeonTabView() removed - using EditorCard system instead void DrawDungeonCanvas(int room_id); void Draw(int room_id); - - void SetRom(Rom* rom) { - rom_ = rom; - } + + void SetRom(Rom* rom) { rom_ = rom; } Rom* rom() const { return rom_; } // Room data access @@ -42,70 +40,80 @@ class DungeonCanvasViewer { void set_current_active_room_tab(int tab) { current_active_room_tab_ = tab; } // Palette access - void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; } + void set_current_palette_group_id(uint64_t id) { + current_palette_group_id_ = id; + } void SetCurrentPaletteId(uint64_t id) { current_palette_id_ = id; } - void SetCurrentPaletteGroup(const gfx::PaletteGroup& group) { current_palette_group_ = group; } + void SetCurrentPaletteGroup(const gfx::PaletteGroup& group) { + current_palette_group_ = group; + } // Canvas access gui::Canvas& canvas() { return canvas_; } const gui::Canvas& canvas() const { return canvas_; } - + // Object interaction access DungeonObjectInteraction& object_interaction() { return object_interaction_; } - + // Enable/disable object interaction mode - void SetObjectInteractionEnabled(bool enabled) { object_interaction_enabled_ = enabled; } - bool IsObjectInteractionEnabled() const { return object_interaction_enabled_; } - + void SetObjectInteractionEnabled(bool enabled) { + object_interaction_enabled_ = enabled; + } + bool IsObjectInteractionEnabled() const { + return object_interaction_enabled_; + } + // Layer visibility controls (per-room) - void SetBG1Visible(int room_id, bool visible) { - GetRoomLayerSettings(room_id).bg1_visible = visible; + void SetBG1Visible(int room_id, bool visible) { + GetRoomLayerSettings(room_id).bg1_visible = visible; } - void SetBG2Visible(int room_id, bool visible) { - GetRoomLayerSettings(room_id).bg2_visible = visible; + void SetBG2Visible(int room_id, bool visible) { + GetRoomLayerSettings(room_id).bg2_visible = visible; } - bool IsBG1Visible(int room_id) const { + bool IsBG1Visible(int room_id) const { auto it = room_layer_settings_.find(room_id); return it != room_layer_settings_.end() ? it->second.bg1_visible : true; } - bool IsBG2Visible(int room_id) const { + bool IsBG2Visible(int room_id) const { auto it = room_layer_settings_.find(room_id); return it != room_layer_settings_.end() ? it->second.bg2_visible : true; } - + // BG2 layer type controls (per-room) - void SetBG2LayerType(int room_id, int type) { - GetRoomLayerSettings(room_id).bg2_layer_type = type; + void SetBG2LayerType(int room_id, int type) { + GetRoomLayerSettings(room_id).bg2_layer_type = type; } - int GetBG2LayerType(int room_id) const { + int GetBG2LayerType(int room_id) const { auto it = room_layer_settings_.find(room_id); return it != room_layer_settings_.end() ? it->second.bg2_layer_type : 0; } - + // Set the object to be placed void SetPreviewObject(const zelda3::RoomObject& object) { object_interaction_.SetPreviewObject(object, true); } void ClearPreviewObject() { - object_interaction_.SetPreviewObject(zelda3::RoomObject{0, 0, 0, 0, 0}, false); + object_interaction_.SetPreviewObject(zelda3::RoomObject{0, 0, 0, 0, 0}, + false); } private: - void DisplayObjectInfo(const zelda3::RoomObject &object, int canvas_x, + void DisplayObjectInfo(const zelda3::RoomObject& object, int canvas_x, int canvas_y); void RenderSprites(const zelda3::Room& room); - + // Coordinate conversion helpers std::pair RoomToCanvasCoordinates(int room_x, int room_y) const; std::pair CanvasToRoomCoordinates(int canvas_x, int canvas_y) const; bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const; - + // Object dimension calculation - void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height); - + void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, + int& height); + // Visualization void DrawObjectPositionOutlines(const zelda3::Room& room); - + // Room graphics management // Load: Read from ROM, Render: Process pixels, Draw: Display on canvas absl::Status LoadAndRenderRoomGraphics(int room_id); @@ -115,16 +123,16 @@ class DungeonCanvasViewer { gui::Canvas canvas_{"##DungeonCanvas", ImVec2(0x200, 0x200)}; // ObjectRenderer removed - use ObjectDrawer for rendering (production system) DungeonObjectInteraction object_interaction_; - + // Room data std::array* rooms_ = nullptr; // Used by overworld editor for double-click entrance → open dungeon room ImVector active_rooms_; int current_active_room_tab_ = 0; - + // Object interaction state bool object_interaction_enabled_ = true; - + // Per-room layer visibility settings struct RoomLayerSettings { bool bg1_visible = true; @@ -132,12 +140,12 @@ class DungeonCanvasViewer { int bg2_layer_type = 0; // 0=Normal, 1=Translucent, 2=Addition, etc. }; std::map room_layer_settings_; - + // Helper to get settings for a room (creates default if not exists) RoomLayerSettings& GetRoomLayerSettings(int room_id) { return room_layer_settings_[room_id]; } - + // Palette data uint64_t current_palette_group_id_ = 0; uint64_t current_palette_id_ = 0; @@ -153,13 +161,13 @@ class DungeonCanvasViewer { }; std::vector object_render_cache_; uint64_t last_palette_hash_ = 0; - + // Debug state flags bool show_room_debug_info_ = false; bool show_texture_debug_ = false; bool show_object_bounds_ = false; bool show_layer_info_ = false; - int layout_override_ = -1; // -1 for no override + int layout_override_ = -1; // -1 for no override // Object outline filtering toggles struct ObjectOutlineToggles { diff --git a/src/app/editor/dungeon/dungeon_editor_v2.cc b/src/app/editor/dungeon/dungeon_editor_v2.cc index 903bdf51..4cc50c01 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.cc +++ b/src/app/editor/dungeon/dungeon_editor_v2.cc @@ -6,13 +6,13 @@ #include "absl/strings/str_format.h" #include "app/gfx/resource/arena.h" -#include "app/gfx/util/palette_manager.h" #include "app/gfx/types/snes_palette.h" -#include "zelda3/dungeon/room.h" +#include "app/gfx/util/palette_manager.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" #include "imgui/imgui.h" #include "util/log.h" +#include "zelda3/dungeon/room.h" namespace yaze::editor { @@ -23,95 +23,82 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { rom_ = rom; // Don't initialize emulator preview yet - ROM might not be loaded // Will be initialized in Load() instead - + // Setup docking class for room windows (ImGui::GetID will be called in Update when ImGui is ready) - room_window_class_.DockingAllowUnclassed = true; // Room windows can dock with anything - room_window_class_.DockingAlwaysTabBar = true; // Always show tabs when multiple rooms - + room_window_class_.DockingAllowUnclassed = + true; // Room windows can dock with anything + room_window_class_.DockingAlwaysTabBar = + true; // Always show tabs when multiple rooms + // Register all cards with EditorCardRegistry (dependency injection) - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.control_panel"), - .display_name = "Dungeon Controls", - .icon = ICON_MD_CASTLE, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+D", - .visibility_flag = &show_control_panel_, - .priority = 10 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.room_selector"), - .display_name = "Room Selector", - .icon = ICON_MD_LIST, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+R", - .visibility_flag = &show_room_selector_, - .priority = 20 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.room_matrix"), - .display_name = "Room Matrix", - .icon = ICON_MD_GRID_VIEW, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+M", - .visibility_flag = &show_room_matrix_, - .priority = 30 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.entrances"), - .display_name = "Entrances", - .icon = ICON_MD_DOOR_FRONT, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+E", - .visibility_flag = &show_entrances_list_, - .priority = 40 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.room_graphics"), - .display_name = "Room Graphics", - .icon = ICON_MD_IMAGE, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+G", - .visibility_flag = &show_room_graphics_, - .priority = 50 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.object_editor"), - .display_name = "Object Editor", - .icon = ICON_MD_CONSTRUCTION, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+O", - .visibility_flag = &show_object_editor_, - .priority = 60 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.palette_editor"), - .display_name = "Palette Editor", - .icon = ICON_MD_PALETTE, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+P", - .visibility_flag = &show_palette_editor_, - .priority = 70 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("dungeon.debug_controls"), - .display_name = "Debug Controls", - .icon = ICON_MD_BUG_REPORT, - .category = "Dungeon", - .shortcut_hint = "Ctrl+Shift+B", - .visibility_flag = &show_debug_controls_, - .priority = 80 - }); - + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.control_panel"), + .display_name = "Dungeon Controls", + .icon = ICON_MD_CASTLE, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+D", + .visibility_flag = &show_control_panel_, + .priority = 10}); + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.room_selector"), + .display_name = "Room Selector", + .icon = ICON_MD_LIST, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+R", + .visibility_flag = &show_room_selector_, + .priority = 20}); + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.room_matrix"), + .display_name = "Room Matrix", + .icon = ICON_MD_GRID_VIEW, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+M", + .visibility_flag = &show_room_matrix_, + .priority = 30}); + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.entrances"), + .display_name = "Entrances", + .icon = ICON_MD_DOOR_FRONT, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+E", + .visibility_flag = &show_entrances_list_, + .priority = 40}); + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.room_graphics"), + .display_name = "Room Graphics", + .icon = ICON_MD_IMAGE, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+G", + .visibility_flag = &show_room_graphics_, + .priority = 50}); + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.object_editor"), + .display_name = "Object Editor", + .icon = ICON_MD_CONSTRUCTION, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+O", + .visibility_flag = &show_object_editor_, + .priority = 60}); + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.palette_editor"), + .display_name = "Palette Editor", + .icon = ICON_MD_PALETTE, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+P", + .visibility_flag = &show_palette_editor_, + .priority = 70}); + + card_registry->RegisterCard({.card_id = MakeCardId("dungeon.debug_controls"), + .display_name = "Debug Controls", + .icon = ICON_MD_BUG_REPORT, + .category = "Dungeon", + .shortcut_hint = "Ctrl+Shift+B", + .visibility_flag = &show_debug_controls_, + .priority = 80}); + // Show control panel and room selector by default when Dungeon Editor is activated show_control_panel_ = true; show_room_selector_ = true; @@ -151,7 +138,7 @@ absl::Status DungeonEditorV2::Load() { // NOW initialize emulator preview with loaded ROM object_emulator_preview_.Initialize(renderer_, rom_); - + // Initialize centralized PaletteManager with ROM data // This MUST be done before initializing palette_editor_ gfx::PaletteManager::Get().Initialize(rom_); @@ -160,7 +147,8 @@ absl::Status DungeonEditorV2::Load() { palette_editor_.Initialize(rom_); // Initialize unified object editor card - object_editor_card_ = std::make_unique(renderer_, rom_, &canvas_viewer_); + object_editor_card_ = + std::make_unique(renderer_, rom_, &canvas_viewer_); // Wire palette changes to trigger room re-renders // PaletteManager now tracks all modifications globally @@ -183,14 +171,16 @@ absl::Status DungeonEditorV2::Update() { if (room_window_class_.ClassId == 0) { room_window_class_.ClassId = ImGui::GetID("DungeonRoomClass"); } - + if (!is_loaded_) { // CARD-BASED EDITOR: Create a minimal loading card gui::EditorCard loading_card("Dungeon Editor Loading", ICON_MD_CASTLE); loading_card.SetDefaultSize(400, 200); if (loading_card.Begin()) { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Loading dungeon data..."); - ImGui::TextWrapped("Independent editor cards will appear once ROM data is loaded."); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + "Loading dungeon data..."); + ImGui::TextWrapped( + "Independent editor cards will appear once ROM data is loaded."); } loading_card.End(); return absl::OkStatus(); @@ -198,9 +188,9 @@ absl::Status DungeonEditorV2::Update() { // CARD-BASED EDITOR: All windows are independent top-level cards // No parent wrapper - this allows closing control panel without affecting rooms - + DrawLayout(); - + return absl::OkStatus(); } @@ -237,25 +227,25 @@ absl::Status DungeonEditorV2::Save() { void DungeonEditorV2::DrawLayout() { // NO TABLE LAYOUT - All independent dockable EditorCards // All cards check their visibility flags and can be closed with X button - + // 1. Room Selector Card (independent, dockable) if (show_room_selector_) { DrawRoomsListCard(); // Card handles its own closing via &show_room_selector_ in constructor } - + // 2. Room Matrix Card (visual navigation) if (show_room_matrix_) { DrawRoomMatrixCard(); // Card handles its own closing via &show_room_matrix_ in constructor } - + // 3. Entrances List Card if (show_entrances_list_) { DrawEntrancesListCard(); // Card handles its own closing via &show_entrances_list_ in constructor } - + // 4. Room Graphics Card if (show_room_graphics_) { DrawRoomGraphicsCard(); @@ -267,12 +257,11 @@ void DungeonEditorV2::DrawLayout() { object_editor_card_->Draw(&show_object_editor_); // ObjectEditorCard handles closing via p_open parameter } - + // 6. Palette Editor Card (independent, dockable) if (show_palette_editor_) { - gui::EditorCard palette_card( - MakeCardTitle("Palette Editor").c_str(), - ICON_MD_PALETTE, &show_palette_editor_); + gui::EditorCard palette_card(MakeCardTitle("Palette Editor").c_str(), + ICON_MD_PALETTE, &show_palette_editor_); if (palette_card.Begin()) { palette_editor_.Draw(); } @@ -292,33 +281,35 @@ void DungeonEditorV2::DrawLayout() { // Create session-aware card title with room ID prominent std::string base_name; - if (room_id >= 0 && static_cast(room_id) < std::size(zelda3::kRoomNames)) { - base_name = absl::StrFormat("[%03X] %s", room_id, zelda3::kRoomNames[room_id].data()); + if (room_id >= 0 && + static_cast(room_id) < std::size(zelda3::kRoomNames)) { + base_name = absl::StrFormat("[%03X] %s", room_id, + zelda3::kRoomNames[room_id].data()); } else { base_name = absl::StrFormat("Room %03X", room_id); } - - std::string card_name_str = absl::StrFormat("%s###RoomCard%d", - MakeCardTitle(base_name).c_str(), room_id); + + std::string card_name_str = absl::StrFormat( + "%s###RoomCard%d", MakeCardTitle(base_name).c_str(), room_id); // Track or create card for jump-to functionality if (room_cards_.find(room_id) == room_cards_.end()) { room_cards_[room_id] = std::make_shared( card_name_str.c_str(), ICON_MD_GRID_ON, &open); room_cards_[room_id]->SetDefaultSize(700, 600); - + // Set default position for first room to be docked with main window if (active_rooms_.Size == 1) { room_cards_[room_id]->SetPosition(gui::EditorCard::Position::Floating); } } - + auto& room_card = room_cards_[room_id]; - + // CRITICAL: Use docking class BEFORE Begin() to make rooms dock together // This creates a separate docking space for all room cards ImGui::SetNextWindowClass(&room_window_class_); - + // Make room cards fully dockable and independent if (room_card->Begin(&open)) { DrawRoomTab(room_id); @@ -346,7 +337,7 @@ void DungeonEditorV2::DrawRoomTab(int room_id) { auto status = room_loader_.LoadRoom(room_id, room); if (!status.ok()) { ImGui::TextColored(ImVec4(1, 0, 0, 1), "Failed to load room: %s", - status.message().data()); + status.message().data()); return; } } @@ -355,21 +346,23 @@ void DungeonEditorV2::DrawRoomTab(int room_id) { // Critical sequence: 1. Load data from ROM, 2. Load objects (sets floor graphics), 3. Render if (room.IsLoaded()) { bool needs_render = false; - + // Step 1: Load room data from ROM (blocks, blockset info) if (room.blocks().empty()) { room.LoadRoomGraphics(room.blockset); needs_render = true; - LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d graphics from ROM", room_id); + LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d graphics from ROM", + room_id); } - + // Step 2: Load objects from ROM (CRITICAL: sets floor1_graphics_, floor2_graphics_!) if (room.GetTileObjects().empty()) { room.LoadObjects(); needs_render = true; - LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d objects from ROM", room_id); + LOG_DEBUG("[DungeonEditorV2]", "Loaded room %d objects from ROM", + room_id); } - + // Step 3: Render to bitmaps (now floor graphics are set correctly!) auto& bg1_bitmap = room.bg1_buffer().bitmap(); if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) { @@ -382,7 +375,8 @@ void DungeonEditorV2::DrawRoomTab(int room_id) { if (room.IsLoaded()) { ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f), ICON_MD_CHECK " Loaded"); } else { - ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.4f, 1.0f), ICON_MD_PENDING " Not Loaded"); + ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.4f, 1.0f), + ICON_MD_PENDING " Not Loaded"); } ImGui::SameLine(); ImGui::TextDisabled("Objects: %zu", room.GetTileObjects().size()); @@ -415,10 +409,10 @@ void DungeonEditorV2::OnEntranceSelected(int entrance_id) { if (entrance_id < 0 || entrance_id >= static_cast(entrances_.size())) { return; } - + // Get the room ID associated with this entrance int room_id = entrances_[entrance_id].room_; - + // Open and focus the room OnRoomSelected(room_id); } @@ -436,12 +430,11 @@ void DungeonEditorV2::FocusRoom(int room_id) { } void DungeonEditorV2::DrawRoomsListCard() { - gui::EditorCard selector_card( - MakeCardTitle("Rooms List").c_str(), - ICON_MD_LIST, &show_room_selector_); - + gui::EditorCard selector_card(MakeCardTitle("Rooms List").c_str(), + ICON_MD_LIST, &show_room_selector_); + selector_card.SetDefaultSize(350, 600); - + if (selector_card.Begin()) { if (!rom_ || !rom_->is_loaded()) { ImGui::Text("ROM not loaded"); @@ -449,19 +442,21 @@ void DungeonEditorV2::DrawRoomsListCard() { // Add text filter static char room_filter[256] = ""; ImGui::SetNextItemWidth(-1); - if (ImGui::InputTextWithHint("##RoomFilter", ICON_MD_SEARCH " Filter rooms...", + if (ImGui::InputTextWithHint("##RoomFilter", + ICON_MD_SEARCH " Filter rooms...", room_filter, sizeof(room_filter))) { // Filter updated } - + ImGui::Separator(); - + // Scrollable room list - simple and reliable if (ImGui::BeginChild("##RoomsList", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { std::string filter_str = room_filter; - std::transform(filter_str.begin(), filter_str.end(), filter_str.begin(), ::tolower); - + std::transform(filter_str.begin(), filter_str.end(), filter_str.begin(), + ::tolower); + for (int i = 0; i < zelda3::NumberOfRooms; i++) { // Get room name std::string room_name; @@ -470,25 +465,26 @@ void DungeonEditorV2::DrawRoomsListCard() { } else { room_name = absl::StrFormat("Room %03X", i); } - + // Apply filter if (!filter_str.empty()) { std::string name_lower = room_name; - std::transform(name_lower.begin(), name_lower.end(), - name_lower.begin(), ::tolower); + std::transform(name_lower.begin(), name_lower.end(), + name_lower.begin(), ::tolower); if (name_lower.find(filter_str) == std::string::npos) { continue; } } - + // Simple selectable with room ID and name - std::string label = absl::StrFormat("[%03X] %s", i, room_name.c_str()); + std::string label = + absl::StrFormat("[%03X] %s", i, room_name.c_str()); bool is_selected = (current_room_id_ == i); - + if (ImGui::Selectable(label.c_str(), is_selected)) { OnRoomSelected(i); } - + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { OnRoomSelected(i); } @@ -501,71 +497,82 @@ void DungeonEditorV2::DrawRoomsListCard() { } void DungeonEditorV2::DrawEntrancesListCard() { - gui::EditorCard entrances_card( - MakeCardTitle("Entrances").c_str(), - ICON_MD_DOOR_FRONT, &show_entrances_list_); - + gui::EditorCard entrances_card(MakeCardTitle("Entrances").c_str(), + ICON_MD_DOOR_FRONT, &show_entrances_list_); + entrances_card.SetDefaultSize(400, 700); - + if (entrances_card.Begin()) { if (!rom_ || !rom_->is_loaded()) { ImGui::Text("ROM not loaded"); } else { // Full entrance configuration UI (matching dungeon_room_selector layout) auto& current_entrance = entrances_[current_entrance_id_]; - + gui::InputHexWord("Entrance ID", ¤t_entrance.entrance_id_); - gui::InputHexWord("Room ID", reinterpret_cast(¤t_entrance.room_)); + gui::InputHexWord("Room ID", + reinterpret_cast(¤t_entrance.room_)); ImGui::SameLine(); - gui::InputHexByte("Dungeon ID", ¤t_entrance.dungeon_id_, 50.f, true); - + gui::InputHexByte("Dungeon ID", ¤t_entrance.dungeon_id_, 50.f, + true); + gui::InputHexByte("Blockset", ¤t_entrance.blockset_, 50.f, true); ImGui::SameLine(); gui::InputHexByte("Music", ¤t_entrance.music_, 50.f, true); ImGui::SameLine(); gui::InputHexByte("Floor", ¤t_entrance.floor_); - + ImGui::Separator(); - + gui::InputHexWord("Player X ", ¤t_entrance.x_position_); ImGui::SameLine(); gui::InputHexWord("Player Y ", ¤t_entrance.y_position_); - + gui::InputHexWord("Camera X", ¤t_entrance.camera_trigger_x_); ImGui::SameLine(); gui::InputHexWord("Camera Y", ¤t_entrance.camera_trigger_y_); - + gui::InputHexWord("Scroll X ", ¤t_entrance.camera_x_); ImGui::SameLine(); gui::InputHexWord("Scroll Y ", ¤t_entrance.camera_y_); - - gui::InputHexWord("Exit", reinterpret_cast(¤t_entrance.exit_), 50.f, true); - + + gui::InputHexWord("Exit", + reinterpret_cast(¤t_entrance.exit_), + 50.f, true); + ImGui::Separator(); ImGui::Text("Camera Boundaries"); ImGui::Separator(); ImGui::Text("\t\t\t\t\tNorth East South West"); - - gui::InputHexByte("Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f, true); + + gui::InputHexByte("Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f, + true); ImGui::SameLine(); - gui::InputHexByte("##QE", ¤t_entrance.camera_boundary_qe_, 50.f, true); + gui::InputHexByte("##QE", ¤t_entrance.camera_boundary_qe_, 50.f, + true); ImGui::SameLine(); - gui::InputHexByte("##QS", ¤t_entrance.camera_boundary_qs_, 50.f, true); + gui::InputHexByte("##QS", ¤t_entrance.camera_boundary_qs_, 50.f, + true); ImGui::SameLine(); - gui::InputHexByte("##QW", ¤t_entrance.camera_boundary_qw_, 50.f, true); - - gui::InputHexByte("Full room", ¤t_entrance.camera_boundary_fn_, 50.f, true); + gui::InputHexByte("##QW", ¤t_entrance.camera_boundary_qw_, 50.f, + true); + + gui::InputHexByte("Full room", ¤t_entrance.camera_boundary_fn_, + 50.f, true); ImGui::SameLine(); - gui::InputHexByte("##FE", ¤t_entrance.camera_boundary_fe_, 50.f, true); + gui::InputHexByte("##FE", ¤t_entrance.camera_boundary_fe_, 50.f, + true); ImGui::SameLine(); - gui::InputHexByte("##FS", ¤t_entrance.camera_boundary_fs_, 50.f, true); + gui::InputHexByte("##FS", ¤t_entrance.camera_boundary_fs_, 50.f, + true); ImGui::SameLine(); - gui::InputHexByte("##FW", ¤t_entrance.camera_boundary_fw_, 50.f, true); - + gui::InputHexByte("##FW", ¤t_entrance.camera_boundary_fw_, 50.f, + true); + ImGui::Separator(); - + // Entrance list - simple and reliable - if (ImGui::BeginChild("##EntrancesList", ImVec2(0, 0), true, + if (ImGui::BeginChild("##EntrancesList", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { for (int i = 0; i < 0x8C; i++) { // The last seven are spawn points @@ -575,17 +582,18 @@ void DungeonEditorV2::DrawEntrancesListCard() { } else { entrance_name = absl::StrFormat("Spawn Point %d", i - 0x85); } - + // Get associated room name int room_id = entrances_[i].room_; std::string room_name = "Unknown"; - if (room_id >= 0 && room_id < static_cast(std::size(zelda3::kRoomNames))) { + if (room_id >= 0 && + room_id < static_cast(std::size(zelda3::kRoomNames))) { room_name = std::string(zelda3::kRoomNames[room_id]); } - - std::string label = absl::StrFormat("[%02X] %s -> %s", - i, entrance_name.c_str(), room_name.c_str()); - + + std::string label = absl::StrFormat( + "[%02X] %s -> %s", i, entrance_name.c_str(), room_name.c_str()); + bool is_selected = (current_entrance_id_ == i); if (ImGui::Selectable(label.c_str(), is_selected)) { current_entrance_id_ = i; @@ -600,70 +608,82 @@ void DungeonEditorV2::DrawEntrancesListCard() { } void DungeonEditorV2::DrawRoomMatrixCard() { - gui::EditorCard matrix_card( - MakeCardTitle("Room Matrix").c_str(), - ICON_MD_GRID_VIEW, &show_room_matrix_); - + gui::EditorCard matrix_card(MakeCardTitle("Room Matrix").c_str(), + ICON_MD_GRID_VIEW, &show_room_matrix_); + matrix_card.SetDefaultSize(440, 520); - + if (matrix_card.Begin()) { // 16 wide x 19 tall = 304 cells (296 rooms + 8 empty) constexpr int kRoomsPerRow = 16; constexpr int kRoomsPerCol = 19; - constexpr int kTotalRooms = 0x128; // 296 rooms (0x00-0x127) + constexpr int kTotalRooms = 0x128; // 296 rooms (0x00-0x127) constexpr float kRoomCellSize = 24.0f; // Smaller cells like ZScream - constexpr float kCellSpacing = 1.0f; // Tighter spacing - + constexpr float kCellSpacing = 1.0f; // Tighter spacing + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); - + int room_index = 0; for (int row = 0; row < kRoomsPerCol; row++) { for (int col = 0; col < kRoomsPerRow; col++) { int room_id = room_index; bool is_valid_room = (room_id < kTotalRooms); - - ImVec2 cell_min = ImVec2( - canvas_pos.x + col * (kRoomCellSize + kCellSpacing), - canvas_pos.y + row * (kRoomCellSize + kCellSpacing)); - ImVec2 cell_max = ImVec2( - cell_min.x + kRoomCellSize, - cell_min.y + kRoomCellSize); - + + ImVec2 cell_min = + ImVec2(canvas_pos.x + col * (kRoomCellSize + kCellSpacing), + canvas_pos.y + row * (kRoomCellSize + kCellSpacing)); + ImVec2 cell_max = + ImVec2(cell_min.x + kRoomCellSize, cell_min.y + kRoomCellSize); + if (is_valid_room) { // Use simple deterministic color based on room ID (no loading needed) ImU32 bg_color; - + // Generate color from room ID - much faster than loading int hue = (room_id * 37) % 360; // Distribute colors across spectrum int saturation = 40 + (room_id % 3) * 15; int brightness = 50 + (room_id % 5) * 10; - + // Convert HSV to RGB float h = hue / 60.0f; float s = saturation / 100.0f; float v = brightness / 100.0f; - + int i = static_cast(h); float f = h - i; int p = static_cast(v * (1 - s) * 255); int q = static_cast(v * (1 - s * f) * 255); int t = static_cast(v * (1 - s * (1 - f)) * 255); int val = static_cast(v * 255); - + switch (i % 6) { - case 0: bg_color = IM_COL32(val, t, p, 255); break; - case 1: bg_color = IM_COL32(q, val, p, 255); break; - case 2: bg_color = IM_COL32(p, val, t, 255); break; - case 3: bg_color = IM_COL32(p, q, val, 255); break; - case 4: bg_color = IM_COL32(t, p, val, 255); break; - case 5: bg_color = IM_COL32(val, p, q, 255); break; - default: bg_color = IM_COL32(60, 60, 70, 255); break; + case 0: + bg_color = IM_COL32(val, t, p, 255); + break; + case 1: + bg_color = IM_COL32(q, val, p, 255); + break; + case 2: + bg_color = IM_COL32(p, val, t, 255); + break; + case 3: + bg_color = IM_COL32(p, q, val, 255); + break; + case 4: + bg_color = IM_COL32(t, p, val, 255); + break; + case 5: + bg_color = IM_COL32(val, p, q, 255); + break; + default: + bg_color = IM_COL32(60, 60, 70, 255); + break; } - + // Check if room is currently selected bool is_current = (current_room_id_ == room_id); - + // Check if room is open in a card bool is_open = false; for (int i = 0; i < active_rooms_.Size; i++) { @@ -672,46 +692,45 @@ void DungeonEditorV2::DrawRoomMatrixCard() { break; } } - + // Draw cell background with palette color draw_list->AddRectFilled(cell_min, cell_max, bg_color); - + // Draw outline ONLY for current/open rooms if (is_current) { // Light green for current room - draw_list->AddRect(cell_min, cell_max, - IM_COL32(144, 238, 144, 255), 0.0f, 0, 2.5f); + draw_list->AddRect(cell_min, cell_max, IM_COL32(144, 238, 144, 255), + 0.0f, 0, 2.5f); } else if (is_open) { // Green for open rooms - draw_list->AddRect(cell_min, cell_max, - IM_COL32(0, 200, 0, 255), 0.0f, 0, 2.0f); + draw_list->AddRect(cell_min, cell_max, IM_COL32(0, 200, 0, 255), + 0.0f, 0, 2.0f); } else { // Subtle gray border for all rooms - draw_list->AddRect(cell_min, cell_max, - IM_COL32(80, 80, 80, 200), 0.0f, 0, 1.0f); + draw_list->AddRect(cell_min, cell_max, IM_COL32(80, 80, 80, 200), + 0.0f, 0, 1.0f); } - + // Draw room ID (small text) std::string room_label = absl::StrFormat("%02X", room_id); ImVec2 text_size = ImGui::CalcTextSize(room_label.c_str()); - ImVec2 text_pos = ImVec2( - cell_min.x + (kRoomCellSize - text_size.x) * 0.5f, - cell_min.y + (kRoomCellSize - text_size.y) * 0.5f); - + ImVec2 text_pos = + ImVec2(cell_min.x + (kRoomCellSize - text_size.x) * 0.5f, + cell_min.y + (kRoomCellSize - text_size.y) * 0.5f); + // Use smaller font if available - draw_list->AddText(text_pos, IM_COL32(220, 220, 220, 255), - room_label.c_str()); - + draw_list->AddText(text_pos, IM_COL32(220, 220, 220, 255), + room_label.c_str()); + // Handle clicks ImGui::SetCursorScreenPos(cell_min); - ImGui::InvisibleButton( - absl::StrFormat("##room%d", room_id).c_str(), - ImVec2(kRoomCellSize, kRoomCellSize)); - + ImGui::InvisibleButton(absl::StrFormat("##room%d", room_id).c_str(), + ImVec2(kRoomCellSize, kRoomCellSize)); + if (ImGui::IsItemClicked()) { OnRoomSelected(room_id); } - + // Hover tooltip with room name and status if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); @@ -720,117 +739,117 @@ void DungeonEditorV2::DrawRoomMatrixCard() { } else { ImGui::Text("Room %03X", room_id); } - ImGui::Text("Status: %s", is_open ? "Open" : is_current ? "Current" : "Closed"); + ImGui::Text("Status: %s", is_open ? "Open" + : is_current ? "Current" + : "Closed"); ImGui::Text("Click to %s", is_open ? "focus" : "open"); ImGui::EndTooltip(); } } else { // Empty cell - draw_list->AddRectFilled(cell_min, cell_max, - IM_COL32(30, 30, 30, 255)); - draw_list->AddRect(cell_min, cell_max, - IM_COL32(50, 50, 50, 255)); + draw_list->AddRectFilled(cell_min, cell_max, + IM_COL32(30, 30, 30, 255)); + draw_list->AddRect(cell_min, cell_max, IM_COL32(50, 50, 50, 255)); } - + room_index++; } } - + // Advance cursor past the grid - ImGui::Dummy(ImVec2( - kRoomsPerRow * (kRoomCellSize + kCellSpacing), - kRoomsPerCol * (kRoomCellSize + kCellSpacing))); + ImGui::Dummy(ImVec2(kRoomsPerRow * (kRoomCellSize + kCellSpacing), + kRoomsPerCol * (kRoomCellSize + kCellSpacing))); } matrix_card.End(); } void DungeonEditorV2::DrawRoomGraphicsCard() { - gui::EditorCard graphics_card( - MakeCardTitle("Room Graphics").c_str(), - ICON_MD_IMAGE, &show_room_graphics_); - + gui::EditorCard graphics_card(MakeCardTitle("Room Graphics").c_str(), + ICON_MD_IMAGE, &show_room_graphics_); + graphics_card.SetDefaultSize(350, 500); graphics_card.SetPosition(gui::EditorCard::Position::Right); - + if (graphics_card.Begin()) { if (!rom_ || !rom_->is_loaded()) { ImGui::Text("ROM not loaded"); - } else if (current_room_id_ >= 0 && current_room_id_ < static_cast(rooms_.size())) { + } else if (current_room_id_ >= 0 && + current_room_id_ < static_cast(rooms_.size())) { // Show graphics for current room auto& room = rooms_[current_room_id_]; - + ImGui::Text("Room %03X Graphics", current_room_id_); ImGui::Text("Blockset: %02X", room.blockset); ImGui::Separator(); - + // Create a canvas for displaying room graphics (16 blocks, 2 columns, 8 rows) // Each block is 128x32, so 2 cols = 256 wide, 8 rows = 256 tall - static gui::Canvas room_gfx_canvas("##RoomGfxCanvas", ImVec2(256 + 1, 256 + 1)); - + static gui::Canvas room_gfx_canvas("##RoomGfxCanvas", + ImVec2(256 + 1, 256 + 1)); + room_gfx_canvas.DrawBackground(); room_gfx_canvas.DrawContextMenu(); room_gfx_canvas.DrawTileSelector(32); - + auto blocks = room.blocks(); - + // Load graphics for this room if not already loaded if (blocks.empty()) { room.LoadRoomGraphics(room.blockset); blocks = room.blocks(); } - + // Only render room graphics if ROM is properly loaded if (room.rom() && room.rom()->is_loaded()) { room.RenderRoomGraphics(); } - + int current_block = 0; constexpr int max_blocks_per_row = 2; constexpr int block_width = 128; constexpr int block_height = 32; - + for (int block : blocks) { - if (current_block >= 16) break; // Show first 16 blocks - + if (current_block >= 16) + break; // Show first 16 blocks + // Ensure the graphics sheet is loaded if (block < static_cast(gfx::Arena::Get().gfx_sheets().size())) { auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block]; - + // Create texture if it doesn't exist - if (!gfx_sheet.texture() && gfx_sheet.is_active() && gfx_sheet.width() > 0) { + if (!gfx_sheet.texture() && gfx_sheet.is_active() && + gfx_sheet.width() > 0) { gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, &gfx_sheet); gfx::Arena::Get().ProcessTextureQueue(nullptr); } - + // Calculate grid position int row = current_block / max_blocks_per_row; int col = current_block % max_blocks_per_row; - + int x = room_gfx_canvas.zero_point().x + 2 + (col * block_width); int y = room_gfx_canvas.zero_point().y + 2 + (row * block_height); - + // Draw if texture is valid if (gfx_sheet.texture() != 0) { room_gfx_canvas.draw_list()->AddImage( - (ImTextureID)(intptr_t)gfx_sheet.texture(), - ImVec2(x, y), + (ImTextureID)(intptr_t)gfx_sheet.texture(), ImVec2(x, y), ImVec2(x + block_width, y + block_height)); } else { // Draw placeholder for missing graphics room_gfx_canvas.draw_list()->AddRectFilled( - ImVec2(x, y), - ImVec2(x + block_width, y + block_height), + ImVec2(x, y), ImVec2(x + block_width, y + block_height), IM_COL32(64, 64, 64, 255)); - room_gfx_canvas.draw_list()->AddText( - ImVec2(x + 10, y + 10), - IM_COL32(255, 255, 255, 255), - "No Graphics"); + room_gfx_canvas.draw_list()->AddText(ImVec2(x + 10, y + 10), + IM_COL32(255, 255, 255, 255), + "No Graphics"); } } current_block++; } - + room_gfx_canvas.DrawGrid(32.0f); room_gfx_canvas.DrawOverlay(); } else { @@ -841,19 +860,18 @@ void DungeonEditorV2::DrawRoomGraphicsCard() { } void DungeonEditorV2::DrawDebugControlsCard() { - gui::EditorCard debug_card( - MakeCardTitle("Debug Controls").c_str(), - ICON_MD_BUG_REPORT, &show_debug_controls_); - + gui::EditorCard debug_card(MakeCardTitle("Debug Controls").c_str(), + ICON_MD_BUG_REPORT, &show_debug_controls_); + debug_card.SetDefaultSize(350, 500); - + if (debug_card.Begin()) { ImGui::TextWrapped("Runtime debug controls for development"); ImGui::Separator(); - + // ===== LOGGING CONTROLS ===== ImGui::SeparatorText(ICON_MD_TERMINAL " Logging"); - + bool debug_enabled = util::LogManager::instance().IsDebugEnabled(); if (ImGui::Checkbox("Enable DEBUG Logs", &debug_enabled)) { if (debug_enabled) { @@ -867,54 +885,64 @@ void DungeonEditorV2::DrawDebugControlsCard() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Toggle LOG_DEBUG visibility\nShortcut: Ctrl+Shift+D"); } - + // Log level selector const char* log_levels[] = {"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; - int current_level = static_cast(util::LogManager::instance().GetLogLevel()); + int current_level = + static_cast(util::LogManager::instance().GetLogLevel()); if (ImGui::Combo("Log Level", ¤t_level, log_levels, 5)) { - util::LogManager::instance().SetLogLevel(static_cast(current_level)); - LOG_INFO("DebugControls", "Log level set to %s", log_levels[current_level]); + util::LogManager::instance().SetLogLevel( + static_cast(current_level)); + LOG_INFO("DebugControls", "Log level set to %s", + log_levels[current_level]); } - + ImGui::Separator(); - + // ===== ROOM RENDERING CONTROLS ===== ImGui::SeparatorText(ICON_MD_IMAGE " Rendering"); - - if (current_room_id_ >= 0 && current_room_id_ < static_cast(rooms_.size())) { + + if (current_room_id_ >= 0 && + current_room_id_ < static_cast(rooms_.size())) { auto& room = rooms_[current_room_id_]; - + ImGui::Text("Current Room: %03X", current_room_id_); ImGui::Text("Objects: %zu", room.GetTileObjects().size()); ImGui::Text("Sprites: %zu", room.GetSprites().size()); - - if (ImGui::Button(ICON_MD_REFRESH " Force Re-render", ImVec2(-FLT_MIN, 0))) { + + if (ImGui::Button(ICON_MD_REFRESH " Force Re-render", + ImVec2(-FLT_MIN, 0))) { room.LoadRoomGraphics(room.blockset); room.LoadObjects(); room.RenderRoomGraphics(); - LOG_INFO("DebugControls", "Forced re-render of room %03X", current_room_id_); + LOG_INFO("DebugControls", "Forced re-render of room %03X", + current_room_id_); } - - if (ImGui::Button(ICON_MD_CLEANING_SERVICES " Clear Room Buffers", ImVec2(-FLT_MIN, 0))) { + + if (ImGui::Button(ICON_MD_CLEANING_SERVICES " Clear Room Buffers", + ImVec2(-FLT_MIN, 0))) { room.ClearTileObjects(); - LOG_INFO("DebugControls", "Cleared room %03X buffers", current_room_id_); + LOG_INFO("DebugControls", "Cleared room %03X buffers", + current_room_id_); } - + ImGui::Separator(); - + // Floor graphics override ImGui::Text("Floor Graphics Override:"); uint8_t floor1 = room.floor1(); uint8_t floor2 = room.floor2(); static uint8_t floor_min = 0; static uint8_t floor_max = 15; - if (ImGui::SliderScalar("Floor1", ImGuiDataType_U8, &floor1, &floor_min, &floor_max)) { + if (ImGui::SliderScalar("Floor1", ImGuiDataType_U8, &floor1, &floor_min, + &floor_max)) { room.set_floor1(floor1); if (room.rom() && room.rom()->is_loaded()) { room.RenderRoomGraphics(); } } - if (ImGui::SliderScalar("Floor2", ImGuiDataType_U8, &floor2, &floor_min, &floor_max)) { + if (ImGui::SliderScalar("Floor2", ImGuiDataType_U8, &floor2, &floor_min, + &floor_max)) { room.set_floor2(floor2); if (room.rom() && room.rom()->is_loaded()) { room.RenderRoomGraphics(); @@ -923,40 +951,43 @@ void DungeonEditorV2::DrawDebugControlsCard() { } else { ImGui::TextDisabled("No room selected"); } - + ImGui::Separator(); - + // ===== TEXTURE CONTROLS ===== ImGui::SeparatorText(ICON_MD_TEXTURE " Textures"); - - if (ImGui::Button(ICON_MD_DELETE_SWEEP " Process Texture Queue", ImVec2(-FLT_MIN, 0))) { + + if (ImGui::Button(ICON_MD_DELETE_SWEEP " Process Texture Queue", + ImVec2(-FLT_MIN, 0))) { gfx::Arena::Get().ProcessTextureQueue(renderer_); LOG_INFO("DebugControls", "Manually processed texture queue"); } - + // Texture stats - ImGui::Text("Arena Graphics Sheets: %zu", gfx::Arena::Get().gfx_sheets().size()); - + ImGui::Text("Arena Graphics Sheets: %zu", + gfx::Arena::Get().gfx_sheets().size()); + ImGui::Separator(); - + // ===== MEMORY CONTROLS ===== ImGui::SeparatorText(ICON_MD_MEMORY " Memory"); - + size_t active_rooms_count = active_rooms_.Size; ImGui::Text("Active Rooms: %zu", active_rooms_count); - ImGui::Text("Estimated Memory: ~%zu MB", active_rooms_count * 2); // 2MB per room - + ImGui::Text("Estimated Memory: ~%zu MB", + active_rooms_count * 2); // 2MB per room + if (ImGui::Button(ICON_MD_CLOSE " Close All Rooms", ImVec2(-FLT_MIN, 0))) { active_rooms_.clear(); room_cards_.clear(); LOG_INFO("DebugControls", "Closed all room cards"); } - + ImGui::Separator(); - + // ===== QUICK ACTIONS ===== ImGui::SeparatorText(ICON_MD_FLASH_ON " Quick Actions"); - + if (ImGui::Button(ICON_MD_SAVE " Save All Rooms", ImVec2(-FLT_MIN, 0))) { auto status = Save(); if (status.ok()) { @@ -965,10 +996,13 @@ void DungeonEditorV2::DrawDebugControlsCard() { LOG_ERROR("DebugControls", "Save failed: %s", status.message().data()); } } - - if (ImGui::Button(ICON_MD_REPLAY " Reload Current Room", ImVec2(-FLT_MIN, 0))) { - if (current_room_id_ >= 0 && current_room_id_ < static_cast(rooms_.size())) { - auto status = room_loader_.LoadRoom(current_room_id_, rooms_[current_room_id_]); + + if (ImGui::Button(ICON_MD_REPLAY " Reload Current Room", + ImVec2(-FLT_MIN, 0))) { + if (current_room_id_ >= 0 && + current_room_id_ < static_cast(rooms_.size())) { + auto status = + room_loader_.LoadRoom(current_room_id_, rooms_[current_room_id_]); if (status.ok()) { LOG_INFO("DebugControls", "Reloaded room %03X", current_room_id_); } @@ -985,4 +1019,3 @@ void DungeonEditorV2::ProcessDeferredTextures() { } } // namespace yaze::editor - diff --git a/src/app/editor/dungeon/dungeon_editor_v2.h b/src/app/editor/dungeon/dungeon_editor_v2.h index 6f008809..db98f7bb 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.h +++ b/src/app/editor/dungeon/dungeon_editor_v2.h @@ -8,18 +8,18 @@ #include "absl/strings/str_format.h" #include "app/editor/editor.h" #include "app/gfx/types/snes_palette.h" -#include "app/rom.h" -#include "dungeon_room_selector.h" -#include "dungeon_canvas_viewer.h" -#include "dungeon_object_selector.h" -#include "dungeon_room_loader.h" -#include "object_editor_card.h" -#include "zelda3/dungeon/room.h" -#include "zelda3/dungeon/room_entrance.h" #include "app/gui/app/editor_layout.h" #include "app/gui/widgets/dungeon_object_emulator_preview.h" #include "app/gui/widgets/palette_editor_widget.h" +#include "app/rom.h" +#include "dungeon_canvas_viewer.h" +#include "dungeon_object_selector.h" +#include "dungeon_room_loader.h" +#include "dungeon_room_selector.h" #include "imgui/imgui.h" +#include "object_editor_card.h" +#include "zelda3/dungeon/room.h" +#include "zelda3/dungeon/room_entrance.h" namespace yaze { namespace editor { @@ -81,20 +81,23 @@ class DungeonEditorV2 : public Editor { // ROM state bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); } std::string GetRomStatus() const override { - if (!rom_) return "No ROM loaded"; - if (!rom_->is_loaded()) return "ROM failed to load"; + if (!rom_) + return "No ROM loaded"; + if (!rom_->is_loaded()) + return "ROM failed to load"; return absl::StrFormat("ROM loaded: %s", rom_->title()); } // Card visibility flags - Public for command-line flag access - bool show_room_selector_ = false; // Room selector/list card - bool show_room_matrix_ = false; // Dungeon matrix layout - bool show_entrances_list_ = false; // Entrance list card (renamed from entrances_matrix_) - bool show_room_graphics_ = false; // Room graphics card - bool show_object_editor_ = false; // Object editor card - bool show_palette_editor_ = false; // Palette editor card - bool show_debug_controls_ = false; // Debug controls card - bool show_control_panel_ = true; // Control panel (visible by default) + bool show_room_selector_ = false; // Room selector/list card + bool show_room_matrix_ = false; // Dungeon matrix layout + bool show_entrances_list_ = + false; // Entrance list card (renamed from entrances_matrix_) + bool show_room_graphics_ = false; // Room graphics card + bool show_object_editor_ = false; // Object editor card + bool show_palette_editor_ = false; // Palette editor card + bool show_debug_controls_ = false; // Debug controls card + bool show_control_panel_ = true; // Control panel (visible by default) private: gfx::IRenderer* renderer_ = nullptr; @@ -106,10 +109,10 @@ class DungeonEditorV2 : public Editor { void DrawEntrancesListCard(); void DrawRoomGraphicsCard(); void DrawDebugControlsCard(); - + // Texture processing (critical for rendering) void ProcessDeferredTextures(); - + // Room selection callback void OnRoomSelected(int room_id); void OnEntranceSelected(int entrance_id); @@ -118,23 +121,23 @@ class DungeonEditorV2 : public Editor { Rom* rom_; std::array rooms_; std::array entrances_; - + // Current selection state int current_entrance_id_ = 0; - + // Active room tabs and card tracking for jump-to ImVector active_rooms_; std::unordered_map> room_cards_; int current_room_id_ = 0; - + bool control_panel_minimized_ = false; - + // Palette management gfx::SnesPalette current_palette_; gfx::PaletteGroup current_palette_group_; uint64_t current_palette_id_ = 0; uint64_t current_palette_group_id_ = 0; - + // Components - these do all the work DungeonRoomLoader room_loader_; DungeonRoomSelector room_selector_; @@ -142,10 +145,11 @@ class DungeonEditorV2 : public Editor { DungeonObjectSelector object_selector_; gui::DungeonObjectEmulatorPreview object_emulator_preview_; gui::PaletteEditorWidget palette_editor_; - std::unique_ptr object_editor_card_; // Unified object editor - + std::unique_ptr + object_editor_card_; // Unified object editor + bool is_loaded_ = false; - + // Docking class for room windows to dock together ImGuiWindowClass room_window_class_; }; @@ -154,4 +158,3 @@ class DungeonEditorV2 : public Editor { } // namespace yaze #endif // YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H - diff --git a/src/app/editor/dungeon/dungeon_object_interaction.cc b/src/app/editor/dungeon/dungeon_object_interaction.cc index 9ab4a403..9e0653e1 100644 --- a/src/app/editor/dungeon/dungeon_object_interaction.cc +++ b/src/app/editor/dungeon/dungeon_object_interaction.cc @@ -8,21 +8,21 @@ namespace yaze::editor { void DungeonObjectInteraction::HandleCanvasMouseInput() { const ImGuiIO& io = ImGui::GetIO(); - + // Check if mouse is over the canvas if (!canvas_->IsMouseHovering()) { return; } - + // Get mouse position relative to canvas ImVec2 mouse_pos = io.MousePos; ImVec2 canvas_pos = canvas_->zero_point(); ImVec2 canvas_size = canvas_->canvas_size(); - + // Convert to canvas coordinates ImVec2 canvas_mouse_pos = ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y); - + // Handle mouse clicks if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || @@ -48,18 +48,18 @@ void DungeonObjectInteraction::HandleCanvasMouseInput() { } } } - + // Handle mouse drag if (is_selecting_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { select_current_pos_ = canvas_mouse_pos; UpdateSelectedObjects(); } - + if (is_dragging_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { drag_current_pos_ = canvas_mouse_pos; DrawDragPreview(); } - + // Handle mouse release if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { if (is_selecting_) { @@ -69,28 +69,31 @@ void DungeonObjectInteraction::HandleCanvasMouseInput() { if (is_dragging_) { is_dragging_ = false; // Apply drag transformation to selected objects - if (!selected_object_indices_.empty() && rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) { + if (!selected_object_indices_.empty() && rooms_ && + current_room_id_ >= 0 && current_room_id_ < 296) { auto& room = (*rooms_)[current_room_id_]; ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x, drag_current_pos_.y - drag_start_pos_.y); - + // Convert pixel delta to tile delta int tile_delta_x = static_cast(drag_delta.x) / 8; int tile_delta_y = static_cast(drag_delta.y) / 8; - + // Move all selected objects auto& objects = room.GetTileObjects(); for (size_t index : selected_object_indices_) { if (index < objects.size()) { objects[index].x_ += tile_delta_x; objects[index].y_ += tile_delta_y; - + // Clamp to room bounds (64x64 tiles) - objects[index].x_ = std::clamp(static_cast(objects[index].x_), 0, 63); - objects[index].y_ = std::clamp(static_cast(objects[index].y_), 0, 63); + objects[index].x_ = + std::clamp(static_cast(objects[index].x_), 0, 63); + objects[index].y_ = + std::clamp(static_cast(objects[index].y_), 0, 63); } } - + // Trigger cache invalidation and re-render if (cache_invalidation_callback_) { cache_invalidation_callback_(); @@ -103,7 +106,7 @@ void DungeonObjectInteraction::HandleCanvasMouseInput() { void DungeonObjectInteraction::CheckForObjectSelection() { // Draw object selection rectangle similar to OverworldEditor DrawObjectSelectRect(); - + // Handle object selection when rectangle is active if (object_select_active_) { SelectObjectsInRect(); @@ -111,16 +114,17 @@ void DungeonObjectInteraction::CheckForObjectSelection() { } void DungeonObjectInteraction::DrawObjectSelectRect() { - if (!canvas_->IsMouseHovering()) return; - + if (!canvas_->IsMouseHovering()) + return; + const ImGuiIO& io = ImGui::GetIO(); const ImVec2 canvas_pos = canvas_->zero_point(); const ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y); - + static bool dragging = false; static ImVec2 drag_start_pos; - + // Right click to start object selection if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !object_loaded_) { drag_start_pos = mouse_pos; @@ -129,24 +133,24 @@ void DungeonObjectInteraction::DrawObjectSelectRect() { object_select_active_ = false; dragging = false; } - + // Right drag to create selection rectangle if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && !object_loaded_) { object_select_end_ = mouse_pos; dragging = true; - + // Draw selection rectangle ImVec2 start = ImVec2(canvas_pos.x + std::min(drag_start_pos.x, mouse_pos.x), canvas_pos.y + std::min(drag_start_pos.y, mouse_pos.y)); ImVec2 end = ImVec2(canvas_pos.x + std::max(drag_start_pos.x, mouse_pos.x), canvas_pos.y + std::max(drag_start_pos.y, mouse_pos.y)); - + ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32)); } - + // Complete selection on mouse release if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) { dragging = false; @@ -156,11 +160,12 @@ void DungeonObjectInteraction::DrawObjectSelectRect() { } void DungeonObjectInteraction::SelectObjectsInRect() { - if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return; - + if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) + return; + auto& room = (*rooms_)[current_room_id_]; selected_object_indices_.clear(); - + // Calculate selection bounds in room coordinates auto [start_room_x, start_room_y] = CanvasToRoomCoordinates( static_cast(std::min(object_select_start_.x, object_select_end_.x)), @@ -168,7 +173,7 @@ void DungeonObjectInteraction::SelectObjectsInRect() { auto [end_room_x, end_room_y] = CanvasToRoomCoordinates( static_cast(std::max(object_select_start_.x, object_select_end_.x)), static_cast(std::max(object_select_start_.y, object_select_end_.y))); - + // Find objects within selection rectangle const auto& objects = room.GetTileObjects(); for (size_t i = 0; i < objects.size(); ++i) { @@ -181,79 +186,83 @@ void DungeonObjectInteraction::SelectObjectsInRect() { } void DungeonObjectInteraction::DrawSelectionHighlights() { - if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return; - + if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) + return; + auto& room = (*rooms_)[current_room_id_]; const auto& objects = room.GetTileObjects(); - + // Draw highlights for all selected objects ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 canvas_pos = canvas_->zero_point(); - + for (size_t index : selected_object_indices_) { if (index < objects.size()) { const auto& object = objects[index]; auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_); - + // Calculate object size for highlight int obj_width = 8 + (object.size_ & 0x0F) * 4; int obj_height = 8 + ((object.size_ >> 4) & 0x0F) * 4; obj_width = std::min(obj_width, 64); obj_height = std::min(obj_height, 64); - + // Draw cyan selection highlight ImVec2 obj_start(canvas_pos.x + canvas_x - 2, canvas_pos.y + canvas_y - 2); ImVec2 obj_end(canvas_pos.x + canvas_x + obj_width + 2, canvas_pos.y + canvas_y + obj_height + 2); - + // Animated selection (pulsing effect) - float pulse = 0.7f + 0.3f * std::sin(static_cast(ImGui::GetTime()) * 4.0f); - draw_list->AddRect(obj_start, obj_end, - IM_COL32(0, static_cast(255 * pulse), 255, 255), - 0.0f, 0, 2.5f); - + float pulse = + 0.7f + 0.3f * std::sin(static_cast(ImGui::GetTime()) * 4.0f); + draw_list->AddRect(obj_start, obj_end, + IM_COL32(0, static_cast(255 * pulse), 255, 255), + 0.0f, 0, 2.5f); + // Draw corner handles for selected objects constexpr float handle_size = 4.0f; draw_list->AddRectFilled( - ImVec2(obj_start.x - handle_size/2, obj_start.y - handle_size/2), - ImVec2(obj_start.x + handle_size/2, obj_start.y + handle_size/2), + ImVec2(obj_start.x - handle_size / 2, obj_start.y - handle_size / 2), + ImVec2(obj_start.x + handle_size / 2, obj_start.y + handle_size / 2), IM_COL32(0, 255, 255, 255)); draw_list->AddRectFilled( - ImVec2(obj_end.x - handle_size/2, obj_start.y - handle_size/2), - ImVec2(obj_end.x + handle_size/2, obj_start.y + handle_size/2), + ImVec2(obj_end.x - handle_size / 2, obj_start.y - handle_size / 2), + ImVec2(obj_end.x + handle_size / 2, obj_start.y + handle_size / 2), IM_COL32(0, 255, 255, 255)); draw_list->AddRectFilled( - ImVec2(obj_start.x - handle_size/2, obj_end.y - handle_size/2), - ImVec2(obj_start.x + handle_size/2, obj_end.y + handle_size/2), + ImVec2(obj_start.x - handle_size / 2, obj_end.y - handle_size / 2), + ImVec2(obj_start.x + handle_size / 2, obj_end.y + handle_size / 2), IM_COL32(0, 255, 255, 255)); draw_list->AddRectFilled( - ImVec2(obj_end.x - handle_size/2, obj_end.y - handle_size/2), - ImVec2(obj_end.x + handle_size/2, obj_end.y + handle_size/2), + ImVec2(obj_end.x - handle_size / 2, obj_end.y - handle_size / 2), + ImVec2(obj_end.x + handle_size / 2, obj_end.y + handle_size / 2), IM_COL32(0, 255, 255, 255)); } } } void DungeonObjectInteraction::PlaceObjectAtPosition(int room_x, int room_y) { - if (!object_loaded_ || preview_object_.id_ < 0 || !rooms_) return; - - if (current_room_id_ < 0 || current_room_id_ >= 296) return; - + if (!object_loaded_ || preview_object_.id_ < 0 || !rooms_) + return; + + if (current_room_id_ < 0 || current_room_id_ >= 296) + return; + // Create new object at the specified position auto new_object = preview_object_; new_object.x_ = room_x; new_object.y_ = room_y; - + // Add object to room auto& room = (*rooms_)[current_room_id_]; room.AddTileObject(new_object); - + // Notify callback if set if (object_placed_callback_) { object_placed_callback_(new_object); } - + // Trigger cache invalidation if (cache_invalidation_callback_) { cache_invalidation_callback_(); @@ -261,11 +270,12 @@ void DungeonObjectInteraction::PlaceObjectAtPosition(int room_x, int room_y) { } void DungeonObjectInteraction::DrawSelectBox() { - if (!is_selecting_) return; - + if (!is_selecting_) + return; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 canvas_pos = canvas_->zero_point(); - + // Calculate select box bounds ImVec2 start = ImVec2( canvas_pos.x + std::min(select_start_pos_.x, select_current_pos_.x), @@ -273,58 +283,65 @@ void DungeonObjectInteraction::DrawSelectBox() { ImVec2 end = ImVec2( canvas_pos.x + std::max(select_start_pos_.x, select_current_pos_.x), canvas_pos.y + std::max(select_start_pos_.y, select_current_pos_.y)); - + // Draw selection box draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32)); } void DungeonObjectInteraction::DrawDragPreview() { - if (!is_dragging_ || selected_object_indices_.empty() || !rooms_) return; - if (current_room_id_ < 0 || current_room_id_ >= 296) return; - + if (!is_dragging_ || selected_object_indices_.empty() || !rooms_) + return; + if (current_room_id_ < 0 || current_room_id_ >= 296) + return; + // Draw drag preview for selected objects ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 canvas_pos = canvas_->zero_point(); ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x, drag_current_pos_.y - drag_start_pos_.y); - + auto& room = (*rooms_)[current_room_id_]; const auto& objects = room.GetTileObjects(); - + // Draw preview of where objects would be moved for (size_t index : selected_object_indices_) { if (index < objects.size()) { const auto& object = objects[index]; auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_); - + // Calculate object size int obj_width = 8 + (object.size_ & 0x0F) * 4; int obj_height = 8 + ((object.size_ >> 4) & 0x0F) * 4; obj_width = std::min(obj_width, 64); obj_height = std::min(obj_height, 64); - + // Draw semi-transparent preview at new position ImVec2 preview_start(canvas_pos.x + canvas_x + drag_delta.x, - canvas_pos.y + canvas_y + drag_delta.y); - ImVec2 preview_end(preview_start.x + obj_width, preview_start.y + obj_height); - + canvas_pos.y + canvas_y + drag_delta.y); + ImVec2 preview_end(preview_start.x + obj_width, + preview_start.y + obj_height); + // Draw ghosted object - draw_list->AddRectFilled(preview_start, preview_end, IM_COL32(0, 255, 255, 64)); - draw_list->AddRect(preview_start, preview_end, IM_COL32(0, 255, 255, 255), 0.0f, 0, 1.5f); + draw_list->AddRectFilled(preview_start, preview_end, + IM_COL32(0, 255, 255, 64)); + draw_list->AddRect(preview_start, preview_end, IM_COL32(0, 255, 255, 255), + 0.0f, 0, 1.5f); } } } void DungeonObjectInteraction::UpdateSelectedObjects() { - if (!is_selecting_ || !rooms_) return; - + if (!is_selecting_ || !rooms_) + return; + selected_objects_.clear(); - - if (current_room_id_ < 0 || current_room_id_ >= 296) return; - + + if (current_room_id_ < 0 || current_room_id_ >= 296) + return; + auto& room = (*rooms_)[current_room_id_]; - + // Check each object in the room for (const auto& object : room.GetTileObjects()) { if (IsObjectInSelectBox(object)) { @@ -335,49 +352,55 @@ void DungeonObjectInteraction::UpdateSelectedObjects() { bool DungeonObjectInteraction::IsObjectInSelectBox( const zelda3::RoomObject& object) const { - if (!is_selecting_) return false; - + if (!is_selecting_) + return false; + // Convert object position to canvas coordinates auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_); - + // Calculate select box bounds float min_x = std::min(select_start_pos_.x, select_current_pos_.x); float max_x = std::max(select_start_pos_.x, select_current_pos_.x); float min_y = std::min(select_start_pos_.y, select_current_pos_.y); float max_y = std::max(select_start_pos_.y, select_current_pos_.y); - + // Check if object is within select box return (canvas_x >= min_x && canvas_x <= max_x && canvas_y >= min_y && canvas_y <= max_y); } -std::pair DungeonObjectInteraction::RoomToCanvasCoordinates(int room_x, int room_y) const { +std::pair DungeonObjectInteraction::RoomToCanvasCoordinates( + int room_x, int room_y) const { // Dungeon tiles are 8x8 pixels, convert room coordinates (tiles) to pixels return {room_x * 8, room_y * 8}; } -std::pair DungeonObjectInteraction::CanvasToRoomCoordinates(int canvas_x, int canvas_y) const { +std::pair DungeonObjectInteraction::CanvasToRoomCoordinates( + int canvas_x, int canvas_y) const { // Convert canvas pixels back to room coordinates (tiles) return {canvas_x / 8, canvas_y / 8}; } -bool DungeonObjectInteraction::IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin) const { +bool DungeonObjectInteraction::IsWithinCanvasBounds(int canvas_x, int canvas_y, + int margin) const { auto canvas_size = canvas_->canvas_size(); auto global_scale = canvas_->global_scale(); int scaled_width = static_cast(canvas_size.x * global_scale); int scaled_height = static_cast(canvas_size.y * global_scale); - + return (canvas_x >= -margin && canvas_y >= -margin && canvas_x <= scaled_width + margin && canvas_y <= scaled_height + margin); } -void DungeonObjectInteraction::SetCurrentRoom(std::array* rooms, int room_id) { +void DungeonObjectInteraction::SetCurrentRoom( + std::array* rooms, int room_id) { rooms_ = rooms; current_room_id_ = room_id; } -void DungeonObjectInteraction::SetPreviewObject(const zelda3::RoomObject& object, bool loaded) { +void DungeonObjectInteraction::SetPreviewObject( + const zelda3::RoomObject& object, bool loaded) { preview_object_ = object; object_loaded_ = loaded; } @@ -390,13 +413,14 @@ void DungeonObjectInteraction::ClearSelection() { } void DungeonObjectInteraction::ShowContextMenu() { - if (!canvas_->IsMouseHovering()) return; - + if (!canvas_->IsMouseHovering()) + return; + // Show context menu on right-click when not dragging if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !is_dragging_) { ImGui::OpenPopup("DungeonObjectContextMenu"); } - + if (ImGui::BeginPopup("DungeonObjectContextMenu")) { // Show different options based on current state if (!selected_object_indices_.empty()) { @@ -408,14 +432,14 @@ void DungeonObjectInteraction::ShowContextMenu() { } ImGui::Separator(); } - + if (has_clipboard_data_) { if (ImGui::MenuItem("Paste Objects", "Ctrl+V")) { HandlePasteObjects(); } ImGui::Separator(); } - + if (object_loaded_) { ImGui::Text("Placing: Object 0x%02X", preview_object_.id_); if (ImGui::MenuItem("Cancel Placement", "Esc")) { @@ -425,29 +449,31 @@ void DungeonObjectInteraction::ShowContextMenu() { ImGui::Text("Right-click + drag to select"); ImGui::Text("Left-click + drag to move"); } - + ImGui::EndPopup(); } } void DungeonObjectInteraction::HandleDeleteSelected() { - if (selected_object_indices_.empty() || !rooms_) return; - if (current_room_id_ < 0 || current_room_id_ >= 296) return; - + if (selected_object_indices_.empty() || !rooms_) + return; + if (current_room_id_ < 0 || current_room_id_ >= 296) + return; + auto& room = (*rooms_)[current_room_id_]; - + // Sort indices in descending order to avoid index shifts during deletion std::vector sorted_indices = selected_object_indices_; std::sort(sorted_indices.rbegin(), sorted_indices.rend()); - + // Delete selected objects using Room's RemoveTileObject method for (size_t index : sorted_indices) { room.RemoveTileObject(index); } - + // Clear selection ClearSelection(); - + // Trigger cache invalidation and re-render if (cache_invalidation_callback_) { cache_invalidation_callback_(); @@ -455,12 +481,14 @@ void DungeonObjectInteraction::HandleDeleteSelected() { } void DungeonObjectInteraction::HandleCopySelected() { - if (selected_object_indices_.empty() || !rooms_) return; - if (current_room_id_ < 0 || current_room_id_ >= 296) return; - + if (selected_object_indices_.empty() || !rooms_) + return; + if (current_room_id_ < 0 || current_room_id_ >= 296) + return; + auto& room = (*rooms_)[current_room_id_]; const auto& objects = room.GetTileObjects(); - + // Copy selected objects to clipboard clipboard_.clear(); for (size_t index : selected_object_indices_) { @@ -468,44 +496,46 @@ void DungeonObjectInteraction::HandleCopySelected() { clipboard_.push_back(objects[index]); } } - + has_clipboard_data_ = !clipboard_.empty(); } void DungeonObjectInteraction::HandlePasteObjects() { - if (!has_clipboard_data_ || !rooms_) return; - if (current_room_id_ < 0 || current_room_id_ >= 296) return; - + if (!has_clipboard_data_ || !rooms_) + return; + if (current_room_id_ < 0 || current_room_id_ >= 296) + return; + auto& room = (*rooms_)[current_room_id_]; - + // Get mouse position for paste location const ImGuiIO& io = ImGui::GetIO(); ImVec2 mouse_pos = io.MousePos; ImVec2 canvas_pos = canvas_->zero_point(); ImVec2 canvas_mouse_pos = ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y); - auto [paste_x, paste_y] = CanvasToRoomCoordinates( - static_cast(canvas_mouse_pos.x), - static_cast(canvas_mouse_pos.y)); - + auto [paste_x, paste_y] = + CanvasToRoomCoordinates(static_cast(canvas_mouse_pos.x), + static_cast(canvas_mouse_pos.y)); + // Calculate offset from first object in clipboard if (!clipboard_.empty()) { int offset_x = paste_x - clipboard_[0].x_; int offset_y = paste_y - clipboard_[0].y_; - + // Paste all objects with offset for (const auto& obj : clipboard_) { auto new_obj = obj; new_obj.x_ = obj.x_ + offset_x; new_obj.y_ = obj.y_ + offset_y; - + // Clamp to room bounds new_obj.x_ = std::clamp(static_cast(new_obj.x_), 0, 63); new_obj.y_ = std::clamp(static_cast(new_obj.y_), 0, 63); - + room.AddTileObject(new_obj); } - + // Trigger cache invalidation and re-render if (cache_invalidation_callback_) { cache_invalidation_callback_(); diff --git a/src/app/editor/dungeon/dungeon_object_interaction.h b/src/app/editor/dungeon/dungeon_object_interaction.h index abfed03e..3fd00161 100644 --- a/src/app/editor/dungeon/dungeon_object_interaction.h +++ b/src/app/editor/dungeon/dungeon_object_interaction.h @@ -1,11 +1,11 @@ #ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_INTERACTION_H #define YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_INTERACTION_H -#include #include +#include -#include "imgui/imgui.h" #include "app/gui/canvas/canvas.h" +#include "imgui/imgui.h" #include "zelda3/dungeon/room.h" #include "zelda3/dungeon/room_object.h" @@ -21,45 +21,48 @@ namespace editor { class DungeonObjectInteraction { public: explicit DungeonObjectInteraction(gui::Canvas* canvas) : canvas_(canvas) {} - + // Main interaction handling void HandleCanvasMouseInput(); void CheckForObjectSelection(); void PlaceObjectAtPosition(int room_x, int room_y); - + // Selection rectangle (like OverworldEditor) void DrawObjectSelectRect(); void SelectObjectsInRect(); void DrawSelectionHighlights(); // Draw highlights for selected objects - + // Drag and select box functionality void DrawSelectBox(); void DrawDragPreview(); void UpdateSelectedObjects(); bool IsObjectInSelectBox(const zelda3::RoomObject& object) const; - + // Coordinate conversion std::pair RoomToCanvasCoordinates(int room_x, int room_y) const; std::pair CanvasToRoomCoordinates(int canvas_x, int canvas_y) const; bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const; - + // State management void SetCurrentRoom(std::array* rooms, int room_id); void SetPreviewObject(const zelda3::RoomObject& object, bool loaded); - + // Selection state - const std::vector& GetSelectedObjectIndices() const { return selected_object_indices_; } + const std::vector& GetSelectedObjectIndices() const { + return selected_object_indices_; + } bool IsObjectSelectActive() const { return object_select_active_; } void ClearSelection(); - + // Context menu void ShowContextMenu(); void HandleDeleteSelected(); void HandleCopySelected(); void HandlePasteObjects(); - + // Callbacks - void SetObjectPlacedCallback(std::function callback) { + void SetObjectPlacedCallback( + std::function callback) { object_placed_callback_ = callback; } void SetCacheInvalidationCallback(std::function callback) { @@ -70,11 +73,11 @@ class DungeonObjectInteraction { gui::Canvas* canvas_; std::array* rooms_ = nullptr; int current_room_id_ = 0; - + // Preview object state zelda3::RoomObject preview_object_{0, 0, 0, 0, 0}; bool object_loaded_ = false; - + // Drag and select infrastructure bool is_dragging_ = false; bool is_selecting_ = false; @@ -83,17 +86,17 @@ class DungeonObjectInteraction { ImVec2 select_start_pos_; ImVec2 select_current_pos_; std::vector selected_objects_; - + // Object selection rectangle (like OverworldEditor) bool object_select_active_ = false; ImVec2 object_select_start_; ImVec2 object_select_end_; std::vector selected_object_indices_; - + // Callbacks std::function object_placed_callback_; std::function cache_invalidation_callback_; - + // Clipboard for copy/paste std::vector clipboard_; bool has_clipboard_data_ = false; diff --git a/src/app/editor/dungeon/dungeon_object_selector.cc b/src/app/editor/dungeon/dungeon_object_selector.cc index f24db4f8..3a1ce9fc 100644 --- a/src/app/editor/dungeon/dungeon_object_selector.cc +++ b/src/app/editor/dungeon/dungeon_object_selector.cc @@ -1,19 +1,19 @@ #include "dungeon_object_selector.h" #include -#include #include +#include -#include "app/platform/window.h" #include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" #include "app/gui/canvas/canvas.h" #include "app/gui/widgets/asset_browser.h" +#include "app/platform/window.h" #include "app/rom.h" -#include "zelda3/dungeon/room.h" +#include "imgui/imgui.h" #include "zelda3/dungeon/dungeon_editor_system.h" #include "zelda3/dungeon/dungeon_object_editor.h" -#include "imgui/imgui.h" +#include "zelda3/dungeon/room.h" namespace yaze::editor { @@ -26,7 +26,7 @@ using ImGui::Separator; void DungeonObjectSelector::DrawTileSelector() { if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) { if (ImGui::BeginTabItem("Room Graphics")) { - if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3); + if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3); BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { DrawRoomGraphics(); @@ -45,17 +45,25 @@ void DungeonObjectSelector::DrawTileSelector() { void DungeonObjectSelector::DrawObjectRenderer() { // Use AssetBrowser for better object selection - if (ImGui::BeginTable("DungeonObjectEditorTable", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV, ImVec2(0, 0))) { - ImGui::TableSetupColumn("Object Browser", ImGuiTableColumnFlags_WidthFixed, 400); - ImGui::TableSetupColumn("Preview Canvas", ImGuiTableColumnFlags_WidthStretch); + if (ImGui::BeginTable( + "DungeonObjectEditorTable", 2, + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | + ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | + ImGuiTableFlags_BordersV, + ImVec2(0, 0))) { + ImGui::TableSetupColumn("Object Browser", ImGuiTableColumnFlags_WidthFixed, + 400); + ImGui::TableSetupColumn("Preview Canvas", + ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); // Left column: AssetBrowser for object selection ImGui::TableNextColumn(); - ImGui::BeginChild("AssetBrowser", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); - + ImGui::BeginChild("AssetBrowser", ImVec2(0, 0), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar); + DrawObjectAssetBrowser(); - + ImGui::EndChild(); // Right column: Preview and placement controls @@ -67,13 +75,13 @@ void DungeonObjectSelector::DrawObjectRenderer() { static int place_x = 0, place_y = 0; ImGui::InputInt("X Position", &place_x); ImGui::InputInt("Y Position", &place_y); - + if (ImGui::Button("Place Object") && object_loaded_) { PlaceObjectAtPosition(place_x, place_y); } - + ImGui::Separator(); - + // Preview canvas object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1)); object_canvas_.DrawContextMenu(); @@ -101,19 +109,19 @@ void DungeonObjectSelector::DrawObjectRenderer() { ImGui::Text("Position: (%d, %d)", preview_object_.x_, preview_object_.y_); ImGui::Text("Size: 0x%02X", preview_object_.size_); ImGui::Text("Layer: %d", static_cast(preview_object_.layer_)); - + // Add object placement controls ImGui::Separator(); ImGui::Text("Placement Controls:"); static int place_x = 0, place_y = 0; ImGui::InputInt("X Position", &place_x); ImGui::InputInt("Y Position", &place_y); - + if (ImGui::Button("Place Object")) { // TODO: Implement object placement in the main canvas ImGui::Text("Object placed at (%d, %d)", place_x, place_y); } - + ImGui::End(); } } @@ -121,129 +129,156 @@ void DungeonObjectSelector::DrawObjectRenderer() { void DungeonObjectSelector::DrawObjectBrowser() { static int selected_object_type = 0; static int selected_object_id = 0; - + // Object type selector - const char* object_types[] = {"Type 1 (0x00-0xFF)", "Type 2 (0x100-0x1FF)", "Type 3 (0x200+)"}; + const char* object_types[] = {"Type 1 (0x00-0xFF)", "Type 2 (0x100-0x1FF)", + "Type 3 (0x200+)"}; if (ImGui::Combo("Object Type", &selected_object_type, object_types, 3)) { - selected_object_id = 0; // Reset selection when changing type + selected_object_id = 0; // Reset selection when changing type } - + ImGui::Separator(); - + // Object list with previews - optimized for 300px column width - const int preview_size = 48; // Larger 48x48 pixel preview for better visibility - const int items_per_row = 5; // 5 items per row to fit in 300px column - + const int preview_size = + 48; // Larger 48x48 pixel preview for better visibility + const int items_per_row = 5; // 5 items per row to fit in 300px column + if (rom_ && rom_->is_loaded()) { - auto palette = rom_->palette_group().dungeon_main[current_palette_group_id_]; - + auto palette = + rom_->palette_group().dungeon_main[current_palette_group_id_]; + // Determine object range based on type int start_id, end_id; switch (selected_object_type) { - case 0: start_id = 0x00; end_id = 0xFF; break; - case 1: start_id = 0x100; end_id = 0x1FF; break; - case 2: start_id = 0x200; end_id = 0x2FF; break; - default: start_id = 0x00; end_id = 0xFF; break; + case 0: + start_id = 0x00; + end_id = 0xFF; + break; + case 1: + start_id = 0x100; + end_id = 0x1FF; + break; + case 2: + start_id = 0x200; + end_id = 0x2FF; + break; + default: + start_id = 0x00; + end_id = 0xFF; + break; } - + // Create a grid layout for object previews int current_row = 0; int current_col = 0; - - for (int obj_id = start_id; obj_id <= end_id && obj_id <= start_id + 63; ++obj_id) { // Limit to 64 objects for performance + + for (int obj_id = start_id; obj_id <= end_id && obj_id <= start_id + 63; + ++obj_id) { // Limit to 64 objects for performance // Create object for preview auto test_object = zelda3::RoomObject(obj_id, 0, 0, 0x12, 0); test_object.set_rom(rom_); test_object.EnsureTilesLoaded(); - + // Calculate position in grid - better sizing for 300px column float available_width = ImGui::GetContentRegionAvail().x; float spacing = ImGui::GetStyle().ItemSpacing.x; - float item_width = (available_width - (items_per_row - 1) * spacing) / items_per_row; - float item_height = preview_size + 30; // Preview + text (reduced padding) - + float item_width = + (available_width - (items_per_row - 1) * spacing) / items_per_row; + float item_height = + preview_size + 30; // Preview + text (reduced padding) + ImGui::PushID(obj_id); - + // Create a selectable button with preview bool is_selected = (selected_object_id == obj_id); - if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None, ImVec2(item_width, item_height))) { + if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None, + ImVec2(item_width, item_height))) { selected_object_id = obj_id; - + // Update preview object preview_object_ = test_object; preview_palette_ = palette; object_loaded_ = true; - + // Notify the main editor that an object was selected if (object_selected_callback_) { object_selected_callback_(preview_object_); } } - + // Draw preview image ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); - ImVec2 preview_pos = ImVec2(cursor_pos.x + (item_width - preview_size) / 2, - cursor_pos.y - item_height + 5); - + ImVec2 preview_pos = + ImVec2(cursor_pos.x + (item_width - preview_size) / 2, + cursor_pos.y - item_height + 5); + // Draw simplified primitive preview for object selector ImGui::SetCursorScreenPos(preview_pos); - + // Draw object as colored rectangle with ID ImU32 object_color = GetObjectTypeColor(obj_id); ImGui::GetWindowDrawList()->AddRectFilled( - preview_pos, - ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size), - object_color); - + preview_pos, + ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size), + object_color); + // Draw border ImGui::GetWindowDrawList()->AddRect( - preview_pos, - ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size), - IM_COL32(0, 0, 0, 255), 0.0f, 0, 2.0f); - + preview_pos, + ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size), + IM_COL32(0, 0, 0, 255), 0.0f, 0, 2.0f); + // Draw object type symbol in center std::string symbol = GetObjectTypeSymbol(obj_id); ImVec2 text_size = ImGui::CalcTextSize(symbol.c_str()); - ImVec2 text_pos = ImVec2( - preview_pos.x + (preview_size - text_size.x) / 2, - preview_pos.y + (preview_size - text_size.y) / 2); - + ImVec2 text_pos = + ImVec2(preview_pos.x + (preview_size - text_size.x) / 2, + preview_pos.y + (preview_size - text_size.y) / 2); + ImGui::GetWindowDrawList()->AddText( - text_pos, IM_COL32(255, 255, 255, 255), symbol.c_str()); - + text_pos, IM_COL32(255, 255, 255, 255), symbol.c_str()); + // Draw object ID below preview - ImGui::SetCursorScreenPos(ImVec2(preview_pos.x, preview_pos.y + preview_size + 2)); + ImGui::SetCursorScreenPos( + ImVec2(preview_pos.x, preview_pos.y + preview_size + 2)); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); ImGui::Text("0x%02X", obj_id); ImGui::PopStyleColor(); - + // Try to get object name std::string object_name = "Unknown"; - if (obj_id < 0x100) { // Type1RoomObjectNames has 248 elements (0-247, 0x00-0xF7) + if (obj_id < + 0x100) { // Type1RoomObjectNames has 248 elements (0-247, 0x00-0xF7) if (obj_id < std::size(zelda3::Type1RoomObjectNames)) { const char* name_ptr = zelda3::Type1RoomObjectNames[obj_id]; if (name_ptr != nullptr) { object_name = std::string(name_ptr); } } - } else if (obj_id < 0x140) { // Type2RoomObjectNames has 64 elements (0x100-0x13F) + } else if (obj_id < + 0x140) { // Type2RoomObjectNames has 64 elements (0x100-0x13F) int type2_index = obj_id - 0x100; - if (type2_index >= 0 && type2_index < std::size(zelda3::Type2RoomObjectNames)) { + if (type2_index >= 0 && + type2_index < std::size(zelda3::Type2RoomObjectNames)) { const char* name_ptr = zelda3::Type2RoomObjectNames[type2_index]; if (name_ptr != nullptr) { object_name = std::string(name_ptr); } } - } else if (obj_id < 0x1C0) { // Type3RoomObjectNames has 128 elements (0x140-0x1BF) + } else if ( + obj_id < + 0x1C0) { // Type3RoomObjectNames has 128 elements (0x140-0x1BF) int type3_index = obj_id - 0x140; - if (type3_index >= 0 && type3_index < std::size(zelda3::Type3RoomObjectNames)) { + if (type3_index >= 0 && + type3_index < std::size(zelda3::Type3RoomObjectNames)) { const char* name_ptr = zelda3::Type3RoomObjectNames[type3_index]; if (name_ptr != nullptr) { object_name = std::string(name_ptr); } } } - + // Draw object name with better sizing ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 2, cursor_pos.y - 8)); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(200, 200, 200, 255)); @@ -253,9 +288,9 @@ void DungeonObjectSelector::DrawObjectBrowser() { } ImGui::Text("%s", object_name.c_str()); ImGui::PopStyleColor(); - + ImGui::PopID(); - + // Move to next position current_col++; if (current_col >= items_per_row) { @@ -269,9 +304,9 @@ void DungeonObjectSelector::DrawObjectBrowser() { } else { ImGui::Text("ROM not loaded"); } - + ImGui::Separator(); - + // Selected object info if (object_loaded_) { ImGui::Text("Selected: 0x%03X", selected_object_id); @@ -287,19 +322,19 @@ void DungeonObjectSelector::Draw() { DrawObjectRenderer(); ImGui::EndTabItem(); } - + // Room Graphics tab - 8 bitmaps viewer if (ImGui::BeginTabItem("Room Graphics")) { DrawRoomGraphics(); ImGui::EndTabItem(); } - + // Object Editor tab - experimental editor if (ImGui::BeginTabItem("Object Editor")) { DrawIntegratedEditingPanels(); ImGui::EndTabItem(); } - + ImGui::EndTabBar(); } } @@ -309,46 +344,48 @@ void DungeonObjectSelector::DrawRoomGraphics() { room_gfx_canvas_.DrawBackground(); room_gfx_canvas_.DrawContextMenu(); room_gfx_canvas_.DrawTileSelector(32); - + if (rom_ && rom_->is_loaded() && rooms_) { int active_room_id = current_room_id_; auto& room = (*rooms_)[active_room_id]; auto blocks = room.blocks(); - + // Load graphics for this room if not already loaded if (blocks.empty()) { room.LoadRoomGraphics(room.blockset); blocks = room.blocks(); } - + int current_block = 0; - const int max_blocks_per_row = 2; // 2 blocks per row for 300px column - const int block_width = 128; // Reduced size to fit column - const int block_height = 32; // Reduced height - + const int max_blocks_per_row = 2; // 2 blocks per row for 300px column + const int block_width = 128; // Reduced size to fit column + const int block_height = 32; // Reduced height + for (int block : blocks) { - if (current_block >= 16) break; // Only show first 16 blocks - + if (current_block >= 16) + break; // Only show first 16 blocks + // Ensure the graphics sheet is loaded and has a valid texture if (block < gfx::Arena::Get().gfx_sheets().size()) { auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block]; - + // Calculate position in a grid layout instead of horizontal concatenation int row = current_block / max_blocks_per_row; int col = current_block % max_blocks_per_row; - + int x = room_gfx_canvas_.zero_point().x + 2 + (col * block_width); int y = room_gfx_canvas_.zero_point().y + 2 + (row * block_height); - + // Ensure we don't exceed canvas bounds - if (x + block_width <= room_gfx_canvas_.zero_point().x + room_gfx_canvas_.width() && - y + block_height <= room_gfx_canvas_.zero_point().y + room_gfx_canvas_.height()) { - + if (x + block_width <= + room_gfx_canvas_.zero_point().x + room_gfx_canvas_.width() && + y + block_height <= + room_gfx_canvas_.zero_point().y + room_gfx_canvas_.height()) { + // Only draw if the texture is valid if (gfx_sheet.texture() != 0) { room_gfx_canvas_.draw_list()->AddImage( - (ImTextureID)(intptr_t)gfx_sheet.texture(), - ImVec2(x, y), + (ImTextureID)(intptr_t)gfx_sheet.texture(), ImVec2(x, y), ImVec2(x + block_width, y + block_height)); } } @@ -421,13 +458,14 @@ void DungeonObjectSelector::DrawCompactObjectEditor() { } auto& editor = *object_editor_; - + ImGui::Text("Object Editor"); Separator(); // Display current editing mode auto mode = editor.GetMode(); - const char *mode_names[] = {"Select", "Insert", "Delete", "Edit", "Layer", "Preview"}; + const char* mode_names[] = {"Select", "Insert", "Delete", + "Edit", "Layer", "Preview"}; ImGui::Text("Mode: %s", mode_names[static_cast(mode)]); // Compact mode selection @@ -486,58 +524,60 @@ void DungeonObjectSelector::DrawCompactObjectEditor() { ImU32 DungeonObjectSelector::GetObjectTypeColor(int object_id) { // Color-code objects based on their type and function if (object_id >= 0x10 && object_id <= 0x1F) { - return IM_COL32(128, 128, 128, 255); // Gray for walls + return IM_COL32(128, 128, 128, 255); // Gray for walls } else if (object_id >= 0x20 && object_id <= 0x2F) { - return IM_COL32(139, 69, 19, 255); // Brown for floors + return IM_COL32(139, 69, 19, 255); // Brown for floors } else if (object_id == 0xF9 || object_id == 0xFA) { - return IM_COL32(255, 215, 0, 255); // Gold for chests + return IM_COL32(255, 215, 0, 255); // Gold for chests } else if (object_id >= 0x17 && object_id <= 0x1E) { - return IM_COL32(139, 69, 19, 255); // Brown for doors + return IM_COL32(139, 69, 19, 255); // Brown for doors } else if (object_id == 0x2F || object_id == 0x2B) { - return IM_COL32(160, 82, 45, 255); // Saddle brown for pots + return IM_COL32(160, 82, 45, 255); // Saddle brown for pots } else if (object_id >= 0x138 && object_id <= 0x13B) { - return IM_COL32(255, 255, 0, 255); // Yellow for stairs + return IM_COL32(255, 255, 0, 255); // Yellow for stairs } else if (object_id >= 0x30 && object_id <= 0x3F) { - return IM_COL32(105, 105, 105, 255); // Dim gray for decorations + return IM_COL32(105, 105, 105, 255); // Dim gray for decorations } else { - return IM_COL32(96, 96, 96, 255); // Default gray + return IM_COL32(96, 96, 96, 255); // Default gray } } std::string DungeonObjectSelector::GetObjectTypeSymbol(int object_id) { // Return symbol representing object type if (object_id >= 0x10 && object_id <= 0x1F) { - return "■"; // Wall + return "■"; // Wall } else if (object_id >= 0x20 && object_id <= 0x2F) { - return "□"; // Floor + return "□"; // Floor } else if (object_id == 0xF9 || object_id == 0xFA) { - return "⬛"; // Chest + return "⬛"; // Chest } else if (object_id >= 0x17 && object_id <= 0x1E) { - return "◊"; // Door + return "◊"; // Door } else if (object_id == 0x2F || object_id == 0x2B) { - return "●"; // Pot + return "●"; // Pot } else if (object_id >= 0x138 && object_id <= 0x13B) { - return "▲"; // Stairs + return "▲"; // Stairs } else if (object_id >= 0x30 && object_id <= 0x3F) { - return "◆"; // Decoration + return "◆"; // Decoration } else { - return "?"; // Unknown + return "?"; // Unknown } } -void DungeonObjectSelector::RenderObjectPrimitive(const zelda3::RoomObject& object, int x, int y) { +void DungeonObjectSelector::RenderObjectPrimitive( + const zelda3::RoomObject& object, int x, int y) { // Render object as primitive shape on canvas ImU32 color = GetObjectTypeColor(object.id_); - + // Calculate object size with proper wall length handling int obj_width, obj_height; CalculateObjectDimensions(object, obj_width, obj_height); - + // Draw object rectangle ImVec4 color_vec = ImGui::ColorConvertU32ToFloat4(color); object_canvas_.DrawRect(x, y, obj_width, obj_height, color_vec); - object_canvas_.DrawRect(x, y, obj_width, obj_height, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - + object_canvas_.DrawRect(x, y, obj_width, obj_height, + ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + // Draw object ID as text std::string obj_text = absl::StrFormat("0x%X", object.id_); object_canvas_.DrawText(obj_text, x + obj_width + 2, y + 4); @@ -545,144 +585,151 @@ void DungeonObjectSelector::RenderObjectPrimitive(const zelda3::RoomObject& obje void DungeonObjectSelector::DrawObjectAssetBrowser() { ImGui::SeparatorText("Dungeon Objects"); - + // Debug info - ImGui::Text("Asset Browser Debug: Available width: %.1f", ImGui::GetContentRegionAvail().x); - + ImGui::Text("Asset Browser Debug: Available width: %.1f", + ImGui::GetContentRegionAvail().x); + // Object type filter static int object_type_filter = 0; - const char* object_types[] = {"All", "Walls", "Floors", "Chests", "Doors", "Decorations", "Stairs"}; + const char* object_types[] = {"All", "Walls", "Floors", "Chests", + "Doors", "Decorations", "Stairs"}; if (ImGui::Combo("Object Type", &object_type_filter, object_types, 7)) { // Filter will be applied in the loop below } - + ImGui::Separator(); - + // Create asset browser-style grid const float item_size = 64.0f; const float item_spacing = 8.0f; - const int columns = std::max(1, static_cast((ImGui::GetContentRegionAvail().x - item_spacing) / (item_size + item_spacing))); - + const int columns = std::max( + 1, static_cast((ImGui::GetContentRegionAvail().x - item_spacing) / + (item_size + item_spacing))); + ImGui::Text("Columns: %d, Item size: %.1f", columns, item_size); - + int current_column = 0; int items_drawn = 0; - + // Draw object grid based on filter for (int obj_id = 0; obj_id <= 0xFF && items_drawn < 100; ++obj_id) { // Apply object type filter - if (object_type_filter > 0 && !MatchesObjectFilter(obj_id, object_type_filter)) { + if (object_type_filter > 0 && + !MatchesObjectFilter(obj_id, object_type_filter)) { continue; } - + if (current_column > 0) { ImGui::SameLine(); } - + ImGui::PushID(obj_id); - + // Create selectable button for object bool is_selected = (selected_object_id_ == obj_id); ImVec2 button_size(item_size, item_size); - - if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None, button_size)) { + + if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None, + button_size)) { selected_object_id_ = obj_id; - + // Create and update preview object preview_object_ = zelda3::RoomObject(obj_id, 0, 0, 0x12, 0); preview_object_.set_rom(rom_); if (rom_) { - auto palette = rom_->palette_group().dungeon_main[current_palette_group_id_]; + auto palette = + rom_->palette_group().dungeon_main[current_palette_group_id_]; preview_palette_ = palette; } object_loaded_ = true; - + // Notify callback if (object_selected_callback_) { object_selected_callback_(preview_object_); } } - + // Draw object preview on the button ImVec2 button_pos = ImGui::GetItemRectMin(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - + // Draw object as colored rectangle with symbol ImU32 obj_color = GetObjectTypeColor(obj_id); - draw_list->AddRectFilled(button_pos, - ImVec2(button_pos.x + item_size, button_pos.y + item_size), - obj_color); - + draw_list->AddRectFilled( + button_pos, ImVec2(button_pos.x + item_size, button_pos.y + item_size), + obj_color); + // Draw border - ImU32 border_color = is_selected ? IM_COL32(255, 255, 0, 255) : IM_COL32(0, 0, 0, 255); - draw_list->AddRect(button_pos, - ImVec2(button_pos.x + item_size, button_pos.y + item_size), - border_color, 0.0f, 0, is_selected ? 3.0f : 1.0f); - + ImU32 border_color = + is_selected ? IM_COL32(255, 255, 0, 255) : IM_COL32(0, 0, 0, 255); + draw_list->AddRect( + button_pos, ImVec2(button_pos.x + item_size, button_pos.y + item_size), + border_color, 0.0f, 0, is_selected ? 3.0f : 1.0f); + // Draw object symbol std::string symbol = GetObjectTypeSymbol(obj_id); ImVec2 text_size = ImGui::CalcTextSize(symbol.c_str()); - ImVec2 text_pos = ImVec2( - button_pos.x + (item_size - text_size.x) / 2, - button_pos.y + (item_size - text_size.y) / 2); + ImVec2 text_pos = ImVec2(button_pos.x + (item_size - text_size.x) / 2, + button_pos.y + (item_size - text_size.y) / 2); draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), symbol.c_str()); - + // Draw object ID at bottom std::string id_text = absl::StrFormat("%02X", obj_id); ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str()); - ImVec2 id_pos = ImVec2( - button_pos.x + (item_size - id_size.x) / 2, - button_pos.y + item_size - id_size.y - 2); + ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2, + button_pos.y + item_size - id_size.y - 2); draw_list->AddText(id_pos, IM_COL32(255, 255, 255, 255), id_text.c_str()); - + ImGui::PopID(); - + current_column = (current_column + 1) % columns; if (current_column == 0) { // Force new line } - + items_drawn++; } - + ImGui::Separator(); ImGui::Text("Items drawn: %d", items_drawn); } bool DungeonObjectSelector::MatchesObjectFilter(int obj_id, int filter_type) { switch (filter_type) { - case 1: // Walls + case 1: // Walls return obj_id >= 0x10 && obj_id <= 0x1F; - case 2: // Floors + case 2: // Floors return obj_id >= 0x20 && obj_id <= 0x2F; - case 3: // Chests + case 3: // Chests return obj_id == 0xF9 || obj_id == 0xFA; - case 4: // Doors + case 4: // Doors return obj_id >= 0x17 && obj_id <= 0x1E; - case 5: // Decorations + case 5: // Decorations return obj_id >= 0x30 && obj_id <= 0x3F; - case 6: // Stairs + case 6: // Stairs return obj_id >= 0x138 && obj_id <= 0x13B; - default: // All + default: // All return true; } } -void DungeonObjectSelector::CalculateObjectDimensions(const zelda3::RoomObject& object, int& width, int& height) { +void DungeonObjectSelector::CalculateObjectDimensions( + const zelda3::RoomObject& object, int& width, int& height) { // Default base size width = 16; height = 16; - + // For walls, use the size field to determine length if (object.id_ >= 0x10 && object.id_ <= 0x1F) { // Wall objects: size determines length and orientation uint8_t size_x = object.size_ & 0x0F; uint8_t size_y = (object.size_ >> 4) & 0x0F; - + // Walls can be horizontal or vertical based on size parameters if (size_x > size_y) { // Horizontal wall - width = 16 + size_x * 16; // Each unit adds 16 pixels + width = 16 + size_x * 16; // Each unit adds 16 pixels height = 16; } else if (size_y > size_x) { // Vertical wall @@ -698,7 +745,7 @@ void DungeonObjectSelector::CalculateObjectDimensions(const zelda3::RoomObject& width = 16 + (object.size_ & 0x0F) * 8; height = 16 + ((object.size_ >> 4) & 0x0F) * 8; } - + // Clamp to reasonable limits width = std::min(width, 256); height = std::min(height, 256); @@ -708,12 +755,12 @@ void DungeonObjectSelector::PlaceObjectAtPosition(int x, int y) { if (!object_loaded_ || !object_placement_callback_) { return; } - + // Create object with specified position auto placed_object = preview_object_; placed_object.set_x(static_cast(x)); placed_object.set_y(static_cast(y)); - + // Call placement callback object_placement_callback_(placed_object); } @@ -725,7 +772,7 @@ void DungeonObjectSelector::DrawCompactSpriteEditor() { } auto& system = **dungeon_editor_system_; - + ImGui::Text("Sprite Editor"); Separator(); @@ -740,7 +787,7 @@ void DungeonObjectSelector::DrawCompactSpriteEditor() { // Show first few sprites in compact format int display_count = std::min(3, static_cast(sprites.size())); for (int i = 0; i < display_count; ++i) { - const auto &sprite = sprites[i]; + const auto& sprite = sprites[i]; ImGui::Text("ID:%d Type:%d (%d,%d)", sprite.sprite_id, static_cast(sprite.type), sprite.x, sprite.y); } @@ -785,7 +832,7 @@ void DungeonObjectSelector::DrawCompactItemEditor() { } auto& system = **dungeon_editor_system_; - + ImGui::Text("Item Editor"); Separator(); @@ -800,7 +847,7 @@ void DungeonObjectSelector::DrawCompactItemEditor() { // Show first few items in compact format int display_count = std::min(3, static_cast(items.size())); for (int i = 0; i < display_count; ++i) { - const auto &item = items[i]; + const auto& item = items[i]; ImGui::Text("ID:%d Type:%d (%d,%d)", item.item_id, static_cast(item.type), item.x, item.y); } @@ -846,7 +893,7 @@ void DungeonObjectSelector::DrawCompactEntranceEditor() { } auto& system = **dungeon_editor_system_; - + ImGui::Text("Entrance Editor"); Separator(); @@ -858,7 +905,7 @@ void DungeonObjectSelector::DrawCompactEntranceEditor() { auto entrances = entrances_result.value(); ImGui::Text("Entrances: %zu", entrances.size()); - for (const auto &entrance : entrances) { + for (const auto& entrance : entrances) { ImGui::Text("ID:%d -> Room:%d (%d,%d)", entrance.entrance_id, entrance.target_room_id, entrance.target_x, entrance.target_y); @@ -884,7 +931,8 @@ void DungeonObjectSelector::DrawCompactEntranceEditor() { ImGui::InputInt("Target Y", &target_y); if (ImGui::Button("Connect")) { - auto status = system.ConnectRooms(current_room, target_room_id, source_x, source_y, target_x, target_y); + auto status = system.ConnectRooms(current_room, target_room_id, source_x, + source_y, target_x, target_y); if (!status.ok()) { ImGui::Text("Error connecting rooms"); } @@ -898,7 +946,7 @@ void DungeonObjectSelector::DrawCompactDoorEditor() { } auto& system = **dungeon_editor_system_; - + ImGui::Text("Door Editor"); Separator(); @@ -910,7 +958,7 @@ void DungeonObjectSelector::DrawCompactDoorEditor() { auto doors = doors_result.value(); ImGui::Text("Doors: %zu", doors.size()); - for (const auto &door : doors) { + for (const auto& door : doors) { ImGui::Text("ID:%d (%d,%d) -> Room:%d", door.door_id, door.x, door.y, door.target_room_id); } @@ -959,7 +1007,7 @@ void DungeonObjectSelector::DrawCompactChestEditor() { } auto& system = **dungeon_editor_system_; - + ImGui::Text("Chest Editor"); Separator(); @@ -971,7 +1019,7 @@ void DungeonObjectSelector::DrawCompactChestEditor() { auto chests = chests_result.value(); ImGui::Text("Chests: %zu", chests.size()); - for (const auto &chest : chests) { + for (const auto& chest : chests) { ImGui::Text("ID:%d (%d,%d) Item:%d", chest.chest_id, chest.x, chest.y, chest.item_id); } @@ -1016,16 +1064,16 @@ void DungeonObjectSelector::DrawCompactPropertiesEditor() { } auto& system = **dungeon_editor_system_; - + ImGui::Text("Room Properties"); Separator(); auto current_room = system.GetCurrentRoom(); auto properties_result = system.GetRoomProperties(current_room); - + if (properties_result.ok()) { auto properties = properties_result.value(); - + static char room_name[128] = {0}; static int dungeon_id = 0; static int floor_level = 0; @@ -1060,7 +1108,7 @@ void DungeonObjectSelector::DrawCompactPropertiesEditor() { new_properties.is_boss_room = is_boss_room; new_properties.is_save_room = is_save_room; new_properties.music_id = music_id; - + auto status = system.SetRoomProperties(current_room, new_properties); if (!status.ok()) { ImGui::Text("Error saving properties"); @@ -1073,7 +1121,7 @@ void DungeonObjectSelector::DrawCompactPropertiesEditor() { // Dungeon settings summary Separator(); ImGui::Text("Dungeon Settings"); - + auto dungeon_settings_result = system.GetDungeonSettings(); if (dungeon_settings_result.ok()) { auto settings = dungeon_settings_result.value(); diff --git a/src/app/editor/dungeon/dungeon_object_selector.h b/src/app/editor/dungeon/dungeon_object_selector.h index 11fb9b71..6781adfa 100644 --- a/src/app/editor/dungeon/dungeon_object_selector.h +++ b/src/app/editor/dungeon/dungeon_object_selector.h @@ -4,10 +4,10 @@ #include "app/gui/canvas/canvas.h" #include "app/rom.h" // object_renderer.h removed - using ObjectDrawer for production rendering -#include "zelda3/dungeon/dungeon_object_editor.h" -#include "zelda3/dungeon/dungeon_editor_system.h" #include "app/gfx/types/snes_palette.h" #include "imgui/imgui.h" +#include "zelda3/dungeon/dungeon_editor_system.h" +#include "zelda3/dungeon/dungeon_object_editor.h" namespace yaze { namespace editor { @@ -23,20 +23,17 @@ class DungeonObjectSelector { void DrawObjectRenderer(); void DrawIntegratedEditingPanels(); void Draw(); - - void set_rom(Rom* rom) { - rom_ = rom; - } - void SetRom(Rom* rom) { - rom_ = rom; - } + + void set_rom(Rom* rom) { rom_ = rom; } + void SetRom(Rom* rom) { rom_ = rom; } Rom* rom() const { return rom_; } // Editor system access - void set_dungeon_editor_system(std::unique_ptr* system) { - dungeon_editor_system_ = system; + void set_dungeon_editor_system( + std::unique_ptr* system) { + dungeon_editor_system_ = system; } - void set_object_editor(std::unique_ptr* editor) { + void set_object_editor(std::unique_ptr* editor) { object_editor_ = editor ? editor->get() : nullptr; } @@ -45,19 +42,27 @@ class DungeonObjectSelector { void set_current_room_id(int room_id) { current_room_id_ = room_id; } // Palette access - void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; } - void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group) { current_palette_group_ = palette_group; } - void SetCurrentPaletteId(uint64_t palette_id) { current_palette_id_ = palette_id; } - + void set_current_palette_group_id(uint64_t id) { + current_palette_group_id_ = id; + } + void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group) { + current_palette_group_ = palette_group; + } + void SetCurrentPaletteId(uint64_t palette_id) { + current_palette_id_ = palette_id; + } + // Object selection callbacks - void SetObjectSelectedCallback(std::function callback) { + void SetObjectSelectedCallback( + std::function callback) { object_selected_callback_ = callback; } - - void SetObjectPlacementCallback(std::function callback) { + + void SetObjectPlacementCallback( + std::function callback) { object_placement_callback_ = callback; } - + // Get current preview object for placement const zelda3::RoomObject& GetPreviewObject() const { return preview_object_; } bool IsObjectLoaded() const { return object_loaded_; } @@ -67,16 +72,17 @@ class DungeonObjectSelector { void DrawObjectBrowser(); void DrawCompactObjectEditor(); void DrawCompactSpriteEditor(); - + // Helper methods for primitive object rendering ImU32 GetObjectTypeColor(int object_id); std::string GetObjectTypeSymbol(int object_id); void RenderObjectPrimitive(const zelda3::RoomObject& object, int x, int y); - + // AssetBrowser-style object selection void DrawObjectAssetBrowser(); bool MatchesObjectFilter(int obj_id, int filter_type); - void CalculateObjectDimensions(const zelda3::RoomObject& object, int& width, int& height); + void CalculateObjectDimensions(const zelda3::RoomObject& object, int& width, + int& height); void PlaceObjectAtPosition(int x, int y); void DrawCompactItemEditor(); void DrawCompactEntranceEditor(); @@ -85,32 +91,34 @@ class DungeonObjectSelector { void DrawCompactPropertiesEditor(); Rom* rom_ = nullptr; - gui::Canvas room_gfx_canvas_{"##RoomGfxCanvas", ImVec2(0x100 + 1, 0x10 * 0x40 + 1)}; + gui::Canvas room_gfx_canvas_{"##RoomGfxCanvas", + ImVec2(0x100 + 1, 0x10 * 0x40 + 1)}; gui::Canvas object_canvas_; // ObjectRenderer removed - using ObjectDrawer in Room::RenderObjectsToBackground() - + // Editor systems - std::unique_ptr* dungeon_editor_system_ = nullptr; + std::unique_ptr* dungeon_editor_system_ = + nullptr; zelda3::DungeonObjectEditor* object_editor_ = nullptr; - + // Room data std::array* rooms_ = nullptr; int current_room_id_ = 0; - + // Palette data uint64_t current_palette_group_id_ = 0; uint64_t current_palette_id_ = 0; gfx::PaletteGroup current_palette_group_; - + // Object preview system zelda3::RoomObject preview_object_{0, 0, 0, 0, 0}; gfx::SnesPalette preview_palette_; bool object_loaded_ = false; - + // Callback for object selection std::function object_selected_callback_; std::function object_placement_callback_; - + // Object selection state int selected_object_id_ = -1; }; diff --git a/src/app/editor/dungeon/dungeon_room_loader.cc b/src/app/editor/dungeon/dungeon_room_loader.cc index 059d243b..bd062784 100644 --- a/src/app/editor/dungeon/dungeon_room_loader.cc +++ b/src/app/editor/dungeon/dungeon_room_loader.cc @@ -1,15 +1,15 @@ #include "dungeon_room_loader.h" #include -#include #include -#include +#include #include +#include #include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/types/snes_palette.h" -#include "zelda3/dungeon/room.h" #include "util/log.h" +#include "zelda3/dungeon/room.h" namespace yaze::editor { @@ -27,55 +27,60 @@ absl::Status DungeonRoomLoader::LoadRoom(int room_id, zelda3::Room& room) { return absl::OkStatus(); } -absl::Status DungeonRoomLoader::LoadAllRooms(std::array& rooms) { +absl::Status DungeonRoomLoader::LoadAllRooms( + std::array& rooms) { if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - - constexpr int kTotalRooms = 0x100 + 40; // 296 rooms - constexpr int kMaxConcurrency = 8; // Reasonable thread limit for room loading - + + constexpr int kTotalRooms = 0x100 + 40; // 296 rooms + constexpr int kMaxConcurrency = + 8; // Reasonable thread limit for room loading + // Determine optimal number of threads - const int max_concurrency = std::min(kMaxConcurrency, - static_cast(std::thread::hardware_concurrency())); - const int rooms_per_thread = (kTotalRooms + max_concurrency - 1) / max_concurrency; - - LOG_DEBUG("Dungeon", "Loading %d dungeon rooms using %d threads (%d rooms per thread)", - kTotalRooms, max_concurrency, rooms_per_thread); - + const int max_concurrency = std::min( + kMaxConcurrency, static_cast(std::thread::hardware_concurrency())); + const int rooms_per_thread = + (kTotalRooms + max_concurrency - 1) / max_concurrency; + + LOG_DEBUG("Dungeon", + "Loading %d dungeon rooms using %d threads (%d rooms per thread)", + kTotalRooms, max_concurrency, rooms_per_thread); + // Thread-safe data structures for collecting results std::mutex results_mutex; std::vector> room_size_results; std::vector> room_palette_results; - + // Process rooms in parallel batches std::vector> futures; - + for (int thread_id = 0; thread_id < max_concurrency; ++thread_id) { - auto task = [this, &rooms, thread_id, rooms_per_thread, &results_mutex, - &room_size_results, &room_palette_results, kTotalRooms]() -> absl::Status { + auto task = [this, &rooms, thread_id, rooms_per_thread, &results_mutex, + &room_size_results, &room_palette_results, + kTotalRooms]() -> absl::Status { const int start_room = thread_id * rooms_per_thread; const int end_room = std::min(start_room + rooms_per_thread, kTotalRooms); - + auto dungeon_man_pal_group = rom_->palette_group().dungeon_main; - + for (int i = start_room; i < end_room; ++i) { // Load room data (this is the expensive operation) rooms[i] = zelda3::LoadRoomFromRom(rom_, i); - + // Calculate room size auto room_size = zelda3::CalculateRoomSize(rom_, i); - + // Load room objects rooms[i].LoadObjects(); - + // Process palette auto dungeon_palette_ptr = rom_->paletteset_ids[rooms[i].palette][0]; auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr); if (palette_id.status() == absl::OkStatus()) { int p_id = palette_id.value() / 180; auto color = dungeon_man_pal_group[p_id][3]; - + // Thread-safe collection of results { std::lock_guard lock(results_mutex); @@ -84,28 +89,28 @@ absl::Status DungeonRoomLoader::LoadAllRooms(std::array& ro } } } - + return absl::OkStatus(); }; - + futures.emplace_back(std::async(std::launch::async, task)); } - + // Wait for all threads to complete for (auto& future : futures) { RETURN_IF_ERROR(future.get()); } - + // Process collected results on main thread { gfx::ScopedTimer postprocess_timer("DungeonRoomLoader::PostProcessResults"); - + // Sort results by room ID for consistent ordering - std::sort(room_size_results.begin(), room_size_results.end(), + std::sort(room_size_results.begin(), room_size_results.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); std::sort(room_palette_results.begin(), room_palette_results.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); - + // Process room size results for (const auto& [room_id, room_size] : room_size_results) { room_size_pointers_.push_back(room_size.room_size_pointer); @@ -114,22 +119,23 @@ absl::Status DungeonRoomLoader::LoadAllRooms(std::array& ro room_size_addresses_[room_id] = room_size.room_size_pointer; } } - + // Process palette results for (const auto& [palette_id, color] : room_palette_results) { room_palette_[palette_id] = color; } } - + LoadDungeonRoomSize(); return absl::OkStatus(); } -absl::Status DungeonRoomLoader::LoadRoomEntrances(std::array& entrances) { +absl::Status DungeonRoomLoader::LoadRoomEntrances( + std::array& entrances) { if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Load entrances for (int i = 0; i < 0x07; ++i) { entrances[i] = zelda3::RoomEntrance(rom_, i, true); @@ -138,7 +144,7 @@ absl::Status DungeonRoomLoader::LoadRoomEntrances(std::arrayis_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Load room graphics with proper blockset room.LoadRoomGraphics(room.blockset); - + // Render the room graphics to the graphics arena room.RenderRoomGraphics(); - + return absl::OkStatus(); } -absl::Status DungeonRoomLoader::ReloadAllRoomGraphics(std::array& rooms) { +absl::Status DungeonRoomLoader::ReloadAllRoomGraphics( + std::array& rooms) { if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Reload graphics for all rooms for (auto& room : rooms) { auto status = LoadAndRenderRoomGraphics(room); if (!status.ok()) { - continue; // Log error but continue with other rooms + continue; // Log error but continue with other rooms } } - + return absl::OkStatus(); } diff --git a/src/app/editor/dungeon/dungeon_room_loader.h b/src/app/editor/dungeon/dungeon_room_loader.h index a13b5d54..5946f975 100644 --- a/src/app/editor/dungeon/dungeon_room_loader.h +++ b/src/app/editor/dungeon/dungeon_room_loader.h @@ -1,8 +1,8 @@ #ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_LOADER_H #define YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_LOADER_H -#include #include +#include #include "absl/status/status.h" #include "app/rom.h" @@ -21,29 +21,36 @@ namespace editor { class DungeonRoomLoader { public: explicit DungeonRoomLoader(Rom* rom) : rom_(rom) {} - + // Room loading absl::Status LoadRoom(int room_id, zelda3::Room& room); absl::Status LoadAllRooms(std::array& rooms); - absl::Status LoadRoomEntrances(std::array& entrances); - + absl::Status LoadRoomEntrances( + std::array& entrances); + // Room size management void LoadDungeonRoomSize(); uint64_t GetTotalRoomSize() const { return total_room_size_; } - + // Room graphics absl::Status LoadAndRenderRoomGraphics(zelda3::Room& room); absl::Status ReloadAllRoomGraphics(std::array& rooms); - + // Data access - const std::vector& GetRoomSizePointers() const { return room_size_pointers_; } + const std::vector& GetRoomSizePointers() const { + return room_size_pointers_; + } const std::vector& GetRoomSizes() const { return room_sizes_; } - const std::unordered_map& GetRoomSizeAddresses() const { return room_size_addresses_; } - const std::unordered_map& GetRoomPalette() const { return room_palette_; } + const std::unordered_map& GetRoomSizeAddresses() const { + return room_size_addresses_; + } + const std::unordered_map& GetRoomPalette() const { + return room_palette_; + } private: Rom* rom_; - + std::vector room_size_pointers_; std::vector room_sizes_; std::unordered_map room_size_addresses_; diff --git a/src/app/editor/dungeon/dungeon_room_selector.cc b/src/app/editor/dungeon/dungeon_room_selector.cc index b08043d8..5bb5dc1e 100644 --- a/src/app/editor/dungeon/dungeon_room_selector.cc +++ b/src/app/editor/dungeon/dungeon_room_selector.cc @@ -1,10 +1,10 @@ #include "dungeon_room_selector.h" #include "app/gui/core/input.h" -#include "zelda3/dungeon/room.h" -#include "zelda3/dungeon/room_entrance.h" #include "imgui/imgui.h" #include "util/hex.h" +#include "zelda3/dungeon/room.h" +#include "zelda3/dungeon/room_entrance.h" namespace yaze::editor { @@ -34,7 +34,7 @@ void DungeonRoomSelector::DrawRoomSelector() { gui::InputHexWord("Room ID", ¤t_room_id_, 50.f, true); - if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9); + if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9); BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { int i = 0; @@ -125,8 +125,8 @@ void DungeonRoomSelector::DrawEntranceSelector() { entrance_name = std::string(zelda3::kEntranceNames[i]); } rom_->resource_label()->SelectableLabelWithNameEdit( - current_entrance_id_ == i, "Dungeon Entrance Names", - util::HexByte(i), entrance_name); + current_entrance_id_ == i, "Dungeon Entrance Names", util::HexByte(i), + entrance_name); if (ImGui::IsItemClicked()) { current_entrance_id_ = i; diff --git a/src/app/editor/dungeon/dungeon_room_selector.h b/src/app/editor/dungeon/dungeon_room_selector.h index ed27ff14..53cd1fc8 100644 --- a/src/app/editor/dungeon/dungeon_room_selector.h +++ b/src/app/editor/dungeon/dungeon_room_selector.h @@ -2,10 +2,10 @@ #define YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_SELECTOR_H #include -#include "imgui/imgui.h" #include "app/rom.h" -#include "zelda3/dungeon/room_entrance.h" +#include "imgui/imgui.h" #include "zelda3/dungeon/room.h" +#include "zelda3/dungeon/room_entrance.h" namespace yaze { namespace editor { @@ -20,29 +20,33 @@ class DungeonRoomSelector { void Draw(); void DrawRoomSelector(); void DrawEntranceSelector(); - + void set_rom(Rom* rom) { rom_ = rom; } Rom* rom() const { return rom_; } // Room selection void set_current_room_id(uint16_t room_id) { current_room_id_ = room_id; } int current_room_id() const { return current_room_id_; } - + void set_active_rooms(const ImVector& rooms) { active_rooms_ = rooms; } const ImVector& active_rooms() const { return active_rooms_; } ImVector& mutable_active_rooms() { return active_rooms_; } // Entrance selection - void set_current_entrance_id(int entrance_id) { current_entrance_id_ = entrance_id; } + void set_current_entrance_id(int entrance_id) { + current_entrance_id_ = entrance_id; + } int current_entrance_id() const { return current_entrance_id_; } // Room data access void set_rooms(std::array* rooms) { rooms_ = rooms; } - void set_entrances(std::array* entrances) { entrances_ = entrances; } + void set_entrances(std::array* entrances) { + entrances_ = entrances; + } // Callback for room selection events - void set_room_selected_callback(std::function callback) { - room_selected_callback_ = callback; + void set_room_selected_callback(std::function callback) { + room_selected_callback_ = callback; } private: @@ -50,10 +54,10 @@ class DungeonRoomSelector { uint16_t current_room_id_ = 0; int current_entrance_id_ = 0; ImVector active_rooms_; - + std::array* rooms_ = nullptr; std::array* entrances_ = nullptr; - + // Callback for room selection events std::function room_selected_callback_; }; diff --git a/src/app/editor/dungeon/dungeon_toolset.cc b/src/app/editor/dungeon/dungeon_toolset.cc index da5cbf96..26fff349 100644 --- a/src/app/editor/dungeon/dungeon_toolset.cc +++ b/src/app/editor/dungeon/dungeon_toolset.cc @@ -17,7 +17,8 @@ using ImGui::TableSetupColumn; using ImGui::Text; void DungeonToolset::Draw() { - if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0))) { + if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit, + ImVec2(0, 0))) { static std::array tool_names = { "Undo", "Redo", "Separator", "All", "BG1", "BG2", "BG3", "Separator", "Object", "Sprite", "Item", "Entrance", @@ -28,13 +29,15 @@ void DungeonToolset::Draw() { // Undo button TableNextColumn(); if (Button(ICON_MD_UNDO)) { - if (undo_callback_) undo_callback_(); + if (undo_callback_) + undo_callback_(); } // Redo button TableNextColumn(); if (Button(ICON_MD_REDO)) { - if (redo_callback_) redo_callback_(); + if (redo_callback_) + redo_callback_(); } // Separator @@ -138,14 +141,17 @@ void DungeonToolset::Draw() { // Palette button TableNextColumn(); if (Button(ICON_MD_PALETTE)) { - if (palette_toggle_callback_) palette_toggle_callback_(); + if (palette_toggle_callback_) + palette_toggle_callback_(); } ImGui::EndTable(); } - + ImGui::Separator(); - ImGui::Text("Instructions: Click to place objects, Ctrl+Click to select, drag to move"); + ImGui::Text( + "Instructions: Click to place objects, Ctrl+Click to select, drag to " + "move"); } } // namespace yaze::editor diff --git a/src/app/editor/dungeon/dungeon_toolset.h b/src/app/editor/dungeon/dungeon_toolset.h index 63bc1d73..dad599eb 100644 --- a/src/app/editor/dungeon/dungeon_toolset.h +++ b/src/app/editor/dungeon/dungeon_toolset.h @@ -1,8 +1,8 @@ #ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_TOOLSET_H #define YAZE_APP_EDITOR_DUNGEON_DUNGEON_TOOLSET_H -#include #include +#include #include "imgui/imgui.h" @@ -24,9 +24,9 @@ class DungeonToolset { kBackground3, kBackgroundAny, }; - - enum PlacementType { - kNoType, + + enum PlacementType { + kNoType, kObject, // Object editing mode kSprite, // Sprite editing mode kItem, // Item placement mode @@ -37,26 +37,32 @@ class DungeonToolset { }; DungeonToolset() = default; - + void Draw(); - + // Getters BackgroundType background_type() const { return background_type_; } PlacementType placement_type() const { return placement_type_; } - + // Setters void set_background_type(BackgroundType type) { background_type_ = type; } void set_placement_type(PlacementType type) { placement_type_ = type; } - + // Callbacks - void SetUndoCallback(std::function callback) { undo_callback_ = callback; } - void SetRedoCallback(std::function callback) { redo_callback_ = callback; } - void SetPaletteToggleCallback(std::function callback) { palette_toggle_callback_ = callback; } + void SetUndoCallback(std::function callback) { + undo_callback_ = callback; + } + void SetRedoCallback(std::function callback) { + redo_callback_ = callback; + } + void SetPaletteToggleCallback(std::function callback) { + palette_toggle_callback_ = callback; + } private: BackgroundType background_type_ = kBackgroundAny; PlacementType placement_type_ = kNoType; - + // Callbacks for editor actions std::function undo_callback_; std::function redo_callback_; diff --git a/src/app/editor/dungeon/dungeon_usage_tracker.cc b/src/app/editor/dungeon/dungeon_usage_tracker.cc index 9b774cc0..d7457363 100644 --- a/src/app/editor/dungeon/dungeon_usage_tracker.cc +++ b/src/app/editor/dungeon/dungeon_usage_tracker.cc @@ -4,11 +4,12 @@ namespace yaze::editor { -void DungeonUsageTracker::CalculateUsageStats(const std::array& rooms) { +void DungeonUsageTracker::CalculateUsageStats( + const std::array& rooms) { blockset_usage_.clear(); spriteset_usage_.clear(); palette_usage_.clear(); - + for (const auto& room : rooms) { if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) { blockset_usage_[room.blockset] = 1; @@ -34,29 +35,29 @@ void DungeonUsageTracker::DrawUsageStats() { if (ImGui::Button("Refresh")) { ClearUsageStats(); } - + ImGui::Text("Usage Statistics"); ImGui::Separator(); - + ImGui::Text("Blocksets: %zu used", blockset_usage_.size()); ImGui::Text("Spritesets: %zu used", spriteset_usage_.size()); ImGui::Text("Palettes: %zu used", palette_usage_.size()); - + ImGui::Separator(); - + // Detailed usage breakdown if (ImGui::CollapsingHeader("Blockset Usage")) { for (const auto& [blockset, count] : blockset_usage_) { ImGui::Text("Blockset 0x%02X: %d rooms", blockset, count); } } - + if (ImGui::CollapsingHeader("Spriteset Usage")) { for (const auto& [spriteset, count] : spriteset_usage_) { ImGui::Text("Spriteset 0x%02X: %d rooms", spriteset, count); } } - + if (ImGui::CollapsingHeader("Palette Usage")) { for (const auto& [palette, count] : palette_usage_) { ImGui::Text("Palette 0x%02X: %d rooms", palette, count); @@ -69,8 +70,9 @@ void DungeonUsageTracker::DrawUsageGrid() { ImGui::Text("Usage grid visualization not yet implemented"); } -void DungeonUsageTracker::RenderSetUsage(const absl::flat_hash_map& usage_map, - uint16_t& selected_set, int spriteset_offset) { +void DungeonUsageTracker::RenderSetUsage( + const absl::flat_hash_map& usage_map, uint16_t& selected_set, + int spriteset_offset) { // TODO: Implement set usage rendering ImGui::Text("Set usage rendering not yet implemented"); } diff --git a/src/app/editor/dungeon/dungeon_usage_tracker.h b/src/app/editor/dungeon/dungeon_usage_tracker.h index f1535ae2..a5b85a81 100644 --- a/src/app/editor/dungeon/dungeon_usage_tracker.h +++ b/src/app/editor/dungeon/dungeon_usage_tracker.h @@ -16,28 +16,36 @@ namespace editor { class DungeonUsageTracker { public: DungeonUsageTracker() = default; - + // Statistics calculation void CalculateUsageStats(const std::array& rooms); void DrawUsageStats(); void DrawUsageGrid(); void RenderSetUsage(const absl::flat_hash_map& usage_map, uint16_t& selected_set, int spriteset_offset = 0x00); - + // Data access - const absl::flat_hash_map& GetBlocksetUsage() const { return blockset_usage_; } - const absl::flat_hash_map& GetSpritesetUsage() const { return spriteset_usage_; } - const absl::flat_hash_map& GetPaletteUsage() const { return palette_usage_; } - + const absl::flat_hash_map& GetBlocksetUsage() const { + return blockset_usage_; + } + const absl::flat_hash_map& GetSpritesetUsage() const { + return spriteset_usage_; + } + const absl::flat_hash_map& GetPaletteUsage() const { + return palette_usage_; + } + // Selection state uint16_t GetSelectedBlockset() const { return selected_blockset_; } uint16_t GetSelectedSpriteset() const { return selected_spriteset_; } uint16_t GetSelectedPalette() const { return selected_palette_; } - + void SetSelectedBlockset(uint16_t blockset) { selected_blockset_ = blockset; } - void SetSelectedSpriteset(uint16_t spriteset) { selected_spriteset_ = spriteset; } + void SetSelectedSpriteset(uint16_t spriteset) { + selected_spriteset_ = spriteset; + } void SetSelectedPalette(uint16_t palette) { selected_palette_ = palette; } - + // Clear data void ClearUsageStats(); diff --git a/src/app/editor/dungeon/object_editor_card.cc b/src/app/editor/dungeon/object_editor_card.cc index ef734ac2..1e4a17e1 100644 --- a/src/app/editor/dungeon/object_editor_card.cc +++ b/src/app/editor/dungeon/object_editor_card.cc @@ -8,8 +8,12 @@ namespace yaze::editor { -ObjectEditorCard::ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, DungeonCanvasViewer* canvas_viewer) - : renderer_(renderer), rom_(rom), canvas_viewer_(canvas_viewer), object_selector_(rom) { +ObjectEditorCard::ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, + DungeonCanvasViewer* canvas_viewer) + : renderer_(renderer), + rom_(rom), + canvas_viewer_(canvas_viewer), + object_selector_(rom) { emulator_preview_.Initialize(renderer, rom); } @@ -17,20 +21,22 @@ void ObjectEditorCard::Draw(bool* p_open) { gui::EditorCard card("Object Editor", ICON_MD_CONSTRUCTION, p_open); card.SetDefaultSize(450, 750); card.SetPosition(gui::EditorCard::Position::Right); - + if (card.Begin(p_open)) { // Interaction mode controls at top (moved from tab) ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Mode:"); ImGui::SameLine(); - - if (ImGui::RadioButton("None", interaction_mode_ == InteractionMode::None)) { + + if (ImGui::RadioButton("None", + interaction_mode_ == InteractionMode::None)) { interaction_mode_ = InteractionMode::None; canvas_viewer_->SetObjectInteractionEnabled(false); canvas_viewer_->ClearPreviewObject(); } ImGui::SameLine(); - - if (ImGui::RadioButton("Place", interaction_mode_ == InteractionMode::Place)) { + + if (ImGui::RadioButton("Place", + interaction_mode_ == InteractionMode::Place)) { interaction_mode_ = InteractionMode::Place; canvas_viewer_->SetObjectInteractionEnabled(true); if (has_preview_object_) { @@ -38,40 +44,42 @@ void ObjectEditorCard::Draw(bool* p_open) { } } ImGui::SameLine(); - - if (ImGui::RadioButton("Select", interaction_mode_ == InteractionMode::Select)) { + + if (ImGui::RadioButton("Select", + interaction_mode_ == InteractionMode::Select)) { interaction_mode_ = InteractionMode::Select; canvas_viewer_->SetObjectInteractionEnabled(true); canvas_viewer_->ClearPreviewObject(); } ImGui::SameLine(); - - if (ImGui::RadioButton("Delete", interaction_mode_ == InteractionMode::Delete)) { + + if (ImGui::RadioButton("Delete", + interaction_mode_ == InteractionMode::Delete)) { interaction_mode_ = InteractionMode::Delete; canvas_viewer_->SetObjectInteractionEnabled(true); canvas_viewer_->ClearPreviewObject(); } - + // Current object info DrawSelectedObjectInfo(); - + ImGui::Separator(); - + // Tabbed interface for Browser and Preview if (ImGui::BeginTabBar("##ObjectEditorTabs", ImGuiTabBarFlags_None)) { - + // Tab 1: Object Browser if (ImGui::BeginTabItem(ICON_MD_LIST " Browser")) { DrawObjectSelector(); ImGui::EndTabItem(); } - + // Tab 2: Emulator Preview (enhanced) if (ImGui::BeginTabItem(ICON_MD_MONITOR " Preview")) { DrawEmulatorPreview(); ImGui::EndTabItem(); } - + ImGui::EndTabBar(); } } @@ -81,61 +89,61 @@ void ObjectEditorCard::Draw(bool* p_open) { void ObjectEditorCard::DrawObjectSelector() { ImGui::Text(ICON_MD_INFO " Select an object to place on the canvas"); ImGui::Separator(); - + // Text filter for objects static char object_filter[256] = ""; ImGui::SetNextItemWidth(-1); - if (ImGui::InputTextWithHint("##ObjectFilter", - ICON_MD_SEARCH " Filter objects...", + if (ImGui::InputTextWithHint("##ObjectFilter", + ICON_MD_SEARCH " Filter objects...", object_filter, sizeof(object_filter))) { // Filter updated } - + ImGui::Separator(); - + // Object list with categories if (ImGui::BeginChild("##ObjectList", ImVec2(0, 0), true)) { // Floor objects - if (ImGui::CollapsingHeader(ICON_MD_GRID_ON " Floor Objects", + if (ImGui::CollapsingHeader(ICON_MD_GRID_ON " Floor Objects", ImGuiTreeNodeFlags_DefaultOpen)) { for (int i = 0; i < 0x100; i++) { std::string filter_str = object_filter; if (!filter_str.empty()) { // Simple name-based filtering std::string object_name = absl::StrFormat("Object %02X", i); - std::transform(filter_str.begin(), filter_str.end(), - filter_str.begin(), ::tolower); + std::transform(filter_str.begin(), filter_str.end(), + filter_str.begin(), ::tolower); std::transform(object_name.begin(), object_name.end(), - object_name.begin(), ::tolower); + object_name.begin(), ::tolower); if (object_name.find(filter_str) == std::string::npos) { continue; } } - + // Create preview icon with small canvas ImGui::BeginGroup(); - + // Small preview canvas (32x32 pixels) DrawObjectPreviewIcon(i, ImVec2(32, 32)); - + ImGui::SameLine(); - + // Object label and selection std::string object_label = absl::StrFormat("%02X - Floor Object", i); - - if (ImGui::Selectable(object_label.c_str(), - has_preview_object_ && preview_object_.id_ == i, - 0, ImVec2(0, 32))) { // Match preview height - preview_object_ = zelda3::RoomObject{ - static_cast(i), 0, 0, 0, 0}; + + if (ImGui::Selectable(object_label.c_str(), + has_preview_object_ && preview_object_.id_ == i, + 0, ImVec2(0, 32))) { // Match preview height + preview_object_ = + zelda3::RoomObject{static_cast(i), 0, 0, 0, 0}; has_preview_object_ = true; canvas_viewer_->SetPreviewObject(preview_object_); canvas_viewer_->SetObjectInteractionEnabled(true); interaction_mode_ = InteractionMode::Place; } - + ImGui::EndGroup(); - + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Object ID: 0x%02X", i); @@ -145,17 +153,17 @@ void ObjectEditorCard::DrawObjectSelector() { } } } - + // Wall objects if (ImGui::CollapsingHeader(ICON_MD_BORDER_ALL " Wall Objects")) { for (int i = 0; i < 0x50; i++) { - std::string object_label = absl::StrFormat( - "%s %02X - Wall Object", ICON_MD_BORDER_VERTICAL, i); - + std::string object_label = absl::StrFormat("%s %02X - Wall Object", + ICON_MD_BORDER_VERTICAL, i); + if (ImGui::Selectable(object_label.c_str())) { // Wall objects have special handling - preview_object_ = zelda3::RoomObject{ - static_cast(i), 0, 0, 0, 1}; // layer=1 for walls + preview_object_ = zelda3::RoomObject{static_cast(i), 0, 0, 0, + 1}; // layer=1 for walls has_preview_object_ = true; canvas_viewer_->SetPreviewObject(preview_object_); canvas_viewer_->SetObjectInteractionEnabled(true); @@ -163,22 +171,21 @@ void ObjectEditorCard::DrawObjectSelector() { } } } - + // Special objects if (ImGui::CollapsingHeader(ICON_MD_STAR " Special Objects")) { - const char* special_objects[] = { - "Stairs Down", "Stairs Up", "Chest", "Door", "Pot", "Block", - "Switch", "Torch" - }; - + const char* special_objects[] = {"Stairs Down", "Stairs Up", "Chest", + "Door", "Pot", "Block", + "Switch", "Torch"}; + for (int i = 0; i < IM_ARRAYSIZE(special_objects); i++) { - std::string object_label = absl::StrFormat( - "%s %s", ICON_MD_STAR, special_objects[i]); - + std::string object_label = + absl::StrFormat("%s %s", ICON_MD_STAR, special_objects[i]); + if (ImGui::Selectable(object_label.c_str())) { // Special object IDs start at 0xF8 - preview_object_ = zelda3::RoomObject{ - static_cast(0xF8 + i), 0, 0, 0, 2}; + preview_object_ = + zelda3::RoomObject{static_cast(0xF8 + i), 0, 0, 0, 2}; has_preview_object_ = true; canvas_viewer_->SetPreviewObject(preview_object_); canvas_viewer_->SetObjectInteractionEnabled(true); @@ -186,10 +193,10 @@ void ObjectEditorCard::DrawObjectSelector() { } } } - + ImGui::EndChild(); } - + // Quick actions at bottom if (ImGui::Button(ICON_MD_CLEAR " Clear Selection", ImVec2(-1, 0))) { has_preview_object_ = false; @@ -200,110 +207,115 @@ void ObjectEditorCard::DrawObjectSelector() { } void ObjectEditorCard::DrawEmulatorPreview() { - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), ICON_MD_INFO " Real-time object rendering preview"); ImGui::Separator(); - + // Toggle emulator preview visibility ImGui::Checkbox("Enable Preview", &show_emulator_preview_); ImGui::SameLine(); - gui::HelpMarker("Uses SNES emulation to render objects accurately.\n" - "May impact performance."); - + gui::HelpMarker( + "Uses SNES emulation to render objects accurately.\n" + "May impact performance."); + if (show_emulator_preview_) { ImGui::Separator(); - + // Embed the emulator preview with improved layout ImGui::BeginChild("##EmulatorPreviewRegion", ImVec2(0, 0), true); - + emulator_preview_.Render(); - + ImGui::EndChild(); } else { ImGui::Separator(); ImGui::TextDisabled(ICON_MD_PREVIEW " Preview disabled for performance"); - ImGui::TextWrapped("Enable to see accurate object rendering using " - "SNES emulation."); + ImGui::TextWrapped( + "Enable to see accurate object rendering using " + "SNES emulation."); } } // DrawInteractionControls removed - controls moved to top of card -void ObjectEditorCard::DrawObjectPreviewIcon(int object_id, const ImVec2& size) { +void ObjectEditorCard::DrawObjectPreviewIcon(int object_id, + const ImVec2& size) { // Create a small preview box for the object ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); ImVec2 box_min = cursor_pos; ImVec2 box_max = ImVec2(cursor_pos.x + size.x, cursor_pos.y + size.y); - + // Draw background draw_list->AddRectFilled(box_min, box_max, IM_COL32(40, 40, 45, 255)); draw_list->AddRect(box_min, box_max, IM_COL32(100, 100, 100, 255)); - + // Draw a simple representation based on object ID // For now, use colored squares and icons as placeholders // Later this can be replaced with actual object bitmaps - + // Color based on object ID for visual variety float hue = (object_id % 16) / 16.0f; ImU32 obj_color = ImGui::ColorConvertFloat4ToU32( ImVec4(0.5f + hue * 0.3f, 0.4f, 0.6f - hue * 0.2f, 1.0f)); - + // Draw inner colored square (16x16 in the center) ImVec2 inner_min = ImVec2(cursor_pos.x + 8, cursor_pos.y + 8); ImVec2 inner_max = ImVec2(cursor_pos.x + 24, cursor_pos.y + 24); draw_list->AddRectFilled(inner_min, inner_max, obj_color); draw_list->AddRect(inner_min, inner_max, IM_COL32(200, 200, 200, 255)); - + // Draw object ID text (very small) std::string id_text = absl::StrFormat("%02X", object_id); ImVec2 text_size = ImGui::CalcTextSize(id_text.c_str()); - ImVec2 text_pos = ImVec2( - cursor_pos.x + (size.x - text_size.x) * 0.5f, - cursor_pos.y + size.y - text_size.y - 2); + ImVec2 text_pos = ImVec2(cursor_pos.x + (size.x - text_size.x) * 0.5f, + cursor_pos.y + size.y - text_size.y - 2); draw_list->AddText(text_pos, IM_COL32(180, 180, 180, 255), id_text.c_str()); - + // Advance cursor ImGui::Dummy(size); } void ObjectEditorCard::DrawSelectedObjectInfo() { ImGui::BeginGroup(); - + // Show current object for placement - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), - ICON_MD_INFO " Current:"); - + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_MD_INFO " Current:"); + if (has_preview_object_) { ImGui::SameLine(); ImGui::Text("ID: 0x%02X", preview_object_.id_); ImGui::SameLine(); - ImGui::Text("Layer: %s", - preview_object_.layer_ == zelda3::RoomObject::BG1 ? "BG1" : - preview_object_.layer_ == zelda3::RoomObject::BG2 ? "BG2" : "BG3"); + ImGui::Text("Layer: %s", + preview_object_.layer_ == zelda3::RoomObject::BG1 ? "BG1" + : preview_object_.layer_ == zelda3::RoomObject::BG2 ? "BG2" + : "BG3"); } else { ImGui::SameLine(); ImGui::TextDisabled("None"); } - + // Show selection count auto& interaction = canvas_viewer_->object_interaction(); const auto& selected = interaction.GetSelectedObjectIndices(); - + ImGui::SameLine(); ImGui::Text("|"); ImGui::SameLine(); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f), + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f), ICON_MD_CHECKLIST " Selected: %zu", selected.size()); - + ImGui::SameLine(); ImGui::Text("|"); ImGui::SameLine(); - ImGui::Text("Mode: %s", - interaction_mode_ == InteractionMode::Place ? ICON_MD_ADD_BOX " Place" : - interaction_mode_ == InteractionMode::Select ? ICON_MD_CHECK_BOX " Select" : - interaction_mode_ == InteractionMode::Delete ? ICON_MD_DELETE " Delete" : "None"); - + ImGui::Text("Mode: %s", interaction_mode_ == InteractionMode::Place + ? ICON_MD_ADD_BOX " Place" + : interaction_mode_ == InteractionMode::Select + ? ICON_MD_CHECK_BOX " Select" + : interaction_mode_ == InteractionMode::Delete + ? ICON_MD_DELETE " Delete" + : "None"); + // Show quick actions for selections if (!selected.empty()) { ImGui::SameLine(); @@ -311,7 +323,7 @@ void ObjectEditorCard::DrawSelectedObjectInfo() { interaction.ClearSelection(); } } - + ImGui::EndGroup(); } diff --git a/src/app/editor/dungeon/object_editor_card.h b/src/app/editor/dungeon/object_editor_card.h index f403016e..0385b7c3 100644 --- a/src/app/editor/dungeon/object_editor_card.h +++ b/src/app/editor/dungeon/object_editor_card.h @@ -5,10 +5,10 @@ #include #include "app/editor/dungeon/dungeon_canvas_viewer.h" -#include "app/gfx/backend/irenderer.h" -#include "app/gui/canvas/canvas.h" #include "app/editor/dungeon/dungeon_object_selector.h" +#include "app/gfx/backend/irenderer.h" #include "app/gui/app/editor_layout.h" +#include "app/gui/canvas/canvas.h" #include "app/gui/widgets/dungeon_object_emulator_preview.h" #include "app/rom.h" #include "zelda3/dungeon/room_object.h" @@ -28,51 +28,49 @@ namespace editor { */ class ObjectEditorCard { public: - ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, DungeonCanvasViewer* canvas_viewer); - + ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, + DungeonCanvasViewer* canvas_viewer); + // Main update function void Draw(bool* p_open); - + // Access to components DungeonObjectSelector& object_selector() { return object_selector_; } - gui::DungeonObjectEmulatorPreview& emulator_preview() { return emulator_preview_; } - + gui::DungeonObjectEmulatorPreview& emulator_preview() { + return emulator_preview_; + } + // Update current room context void SetCurrentRoom(int room_id) { current_room_id_ = room_id; } - + private: void DrawObjectSelector(); void DrawEmulatorPreview(); void DrawInteractionControls(); void DrawSelectedObjectInfo(); void DrawObjectPreviewIcon(int object_id, const ImVec2& size); - + Rom* rom_; DungeonCanvasViewer* canvas_viewer_; int current_room_id_ = 0; - + // Components DungeonObjectSelector object_selector_; gui::DungeonObjectEmulatorPreview emulator_preview_; - + // Object preview canvases (one per object type) std::unordered_map object_preview_canvases_; - + // UI state int selected_tab_ = 0; bool show_emulator_preview_ = false; // Disabled by default for performance bool show_object_list_ = true; bool show_interaction_controls_ = true; - + // Object interaction mode - enum class InteractionMode { - None, - Place, - Select, - Delete - }; + enum class InteractionMode { None, Place, Select, Delete }; InteractionMode interaction_mode_ = InteractionMode::None; - + // Selected object for placement zelda3::RoomObject preview_object_{0, 0, 0, 0, 0}; bool has_preview_object_ = false; diff --git a/src/app/editor/editor.h b/src/app/editor/editor.h index 584edf9c..bd72d934 100644 --- a/src/app/editor/editor.h +++ b/src/app/editor/editor.h @@ -3,8 +3,8 @@ #include #include -#include #include +#include #include "absl/status/status.h" #include "absl/status/statusor.h" @@ -108,9 +108,9 @@ enum class EditorType { }; constexpr std::array kEditorNames = { - "Unknown", - "Assembly", "Dungeon", "Emulator", "Graphics", "Music", "Overworld", - "Palette", "Screen", "Sprite", "Message", "Hex", "Agent", "Settings", + "Unknown", "Assembly", "Dungeon", "Emulator", "Graphics", + "Music", "Overworld", "Palette", "Screen", "Sprite", + "Message", "Hex", "Agent", "Settings", }; /** @@ -157,7 +157,9 @@ class Editor { // ROM loading state helpers (default implementations) virtual bool IsRomLoaded() const { return false; } - virtual std::string GetRomStatus() const { return "ROM state not implemented"; } + virtual std::string GetRomStatus() const { + return "ROM state not implemented"; + } protected: bool active_ = false; @@ -171,7 +173,7 @@ class Editor { } return base_title; } - + // Helper method to create session-aware card IDs for multi-session support std::string MakeCardId(const std::string& base_id) const { if (dependencies_.session_id > 0) { @@ -181,18 +183,20 @@ class Editor { } // Helper method for ROM access with safety check - template - absl::StatusOr SafeRomAccess(std::function accessor, const std::string& operation = "") const { + template + absl::StatusOr SafeRomAccess(std::function accessor, + const std::string& operation = "") const { if (!IsRomLoaded()) { return absl::FailedPreconditionError( - operation.empty() ? "ROM not loaded" : - absl::StrFormat("%s: ROM not loaded", operation)); + operation.empty() ? "ROM not loaded" + : absl::StrFormat("%s: ROM not loaded", operation)); } try { return accessor(); } catch (const std::exception& e) { return absl::InternalError(absl::StrFormat( - "%s: %s", operation.empty() ? "ROM access failed" : operation, e.what())); + "%s: %s", operation.empty() ? "ROM access failed" : operation, + e.what())); } } }; diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 52267cfc..92508087 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -16,10 +16,6 @@ #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" -#include "core/features.h" -#include "core/project.h" -#include "app/platform/timing.h" -#include "app/editor/session_types.h" #include "app/editor/code/assembly_editor.h" #include "app/editor/dungeon/dungeon_editor_v2.h" #include "app/editor/graphics/graphics_editor.h" @@ -27,6 +23,7 @@ #include "app/editor/music/music_editor.h" #include "app/editor/overworld/overworld_editor.h" #include "app/editor/palette/palette_editor.h" +#include "app/editor/session_types.h" #include "app/editor/sprite/sprite_editor.h" #include "app/editor/system/editor_card_registry.h" #include "app/editor/system/editor_registry.h" @@ -42,8 +39,11 @@ #include "app/gui/core/icons.h" #include "app/gui/core/input.h" #include "app/gui/core/theme_manager.h" +#include "app/platform/timing.h" #include "app/rom.h" #include "app/test/test_manager.h" +#include "core/features.h" +#include "core/project.h" #include "imgui/imgui.h" #include "util/file_util.h" #include "util/log.h" @@ -65,13 +65,13 @@ #include "app/gfx/debug/performance/performance_dashboard.h" #ifdef YAZE_WITH_GRPC -#include "app/service/screenshot_utils.h" #include "app/editor/agent/agent_chat_widget.h" +#include "app/editor/agent/automation_bridge.h" +#include "app/service/screenshot_utils.h" #include "app/test/z3ed_test_suite.h" #include "cli/service/agent/agent_control_server.h" #include "cli/service/agent/conversational_agent_service.h" #include "cli/service/ai/gemini_ai_service.h" -#include "app/editor/agent/automation_bridge.h" #endif #include "imgui/misc/cpp/imgui_stdlib.h" @@ -105,7 +105,7 @@ void EditorManager::HideCurrentEditorCards() { // Using EditorCardRegistry directly std::string category = - editor_registry_.GetEditorCategory(current_editor_->type()); + editor_registry_.GetEditorCategory(current_editor_->type()); card_registry_.HideAllCardsInCategory(category); } @@ -124,7 +124,7 @@ void EditorManager::ShowChatHistory() { } #endif -EditorManager::EditorManager() +EditorManager::EditorManager() : blank_editor_set_(nullptr, &user_settings_), project_manager_(&toast_manager_), rom_file_manager_(&toast_manager_) { @@ -132,7 +132,7 @@ EditorManager::EditorManager() ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "." << YAZE_VERSION_PATCH; ss >> version_; - + // ============================================================================ // DELEGATION INFRASTRUCTURE INITIALIZATION // ============================================================================ @@ -163,31 +163,32 @@ EditorManager::EditorManager() // If this order is violated, you will get SIGSEGV crashes when menu callbacks // try to call popup_manager_.Show() with an uninitialized PopupManager! // ============================================================================ - + // STEP 1: Initialize PopupManager FIRST popup_manager_ = std::make_unique(this); popup_manager_->Initialize(); // Registers all popups with PopupID constants - + // STEP 2: Initialize SessionCoordinator (independent of popups) session_coordinator_ = std::make_unique( - static_cast(&sessions_), &card_registry_, &toast_manager_, &user_settings_); - + static_cast(&sessions_), &card_registry_, &toast_manager_, + &user_settings_); + // STEP 3: Initialize MenuOrchestrator (depends on popup_manager_, session_coordinator_) menu_orchestrator_ = std::make_unique( this, menu_builder_, rom_file_manager_, project_manager_, editor_registry_, *session_coordinator_, toast_manager_, *popup_manager_); session_coordinator_->SetEditorManager(this); - + // STEP 4: Initialize UICoordinator (depends on popup_manager_, session_coordinator_, card_registry_) ui_coordinator_ = std::make_unique( - this, rom_file_manager_, project_manager_, editor_registry_, card_registry_, - *session_coordinator_, window_delegate_, toast_manager_, *popup_manager_, - shortcut_manager_); - + this, rom_file_manager_, project_manager_, editor_registry_, + card_registry_, *session_coordinator_, window_delegate_, toast_manager_, + *popup_manager_, shortcut_manager_); + // STEP 4.5: Initialize LayoutManager (DockBuilder layouts for editors) layout_manager_ = std::make_unique(); - + // STEP 5: ShortcutConfigurator created later in Initialize() method // It depends on all above coordinators being available } @@ -258,55 +259,55 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, // Register emulator cards early (emulator Initialize might not be called) // Using EditorCardRegistry directly card_registry_.RegisterCard({.card_id = "emulator.cpu_debugger", - .display_name = "CPU Debugger", - .icon = ICON_MD_BUG_REPORT, - .category = "Emulator", - .priority = 10}); + .display_name = "CPU Debugger", + .icon = ICON_MD_BUG_REPORT, + .category = "Emulator", + .priority = 10}); card_registry_.RegisterCard({.card_id = "emulator.ppu_viewer", - .display_name = "PPU Viewer", - .icon = ICON_MD_VIDEOGAME_ASSET, - .category = "Emulator", - .priority = 20}); + .display_name = "PPU Viewer", + .icon = ICON_MD_VIDEOGAME_ASSET, + .category = "Emulator", + .priority = 20}); card_registry_.RegisterCard({.card_id = "emulator.memory_viewer", - .display_name = "Memory Viewer", - .icon = ICON_MD_MEMORY, - .category = "Emulator", - .priority = 30}); + .display_name = "Memory Viewer", + .icon = ICON_MD_MEMORY, + .category = "Emulator", + .priority = 30}); card_registry_.RegisterCard({.card_id = "emulator.breakpoints", - .display_name = "Breakpoints", - .icon = ICON_MD_STOP, - .category = "Emulator", - .priority = 40}); + .display_name = "Breakpoints", + .icon = ICON_MD_STOP, + .category = "Emulator", + .priority = 40}); card_registry_.RegisterCard({.card_id = "emulator.performance", - .display_name = "Performance", - .icon = ICON_MD_SPEED, - .category = "Emulator", - .priority = 50}); + .display_name = "Performance", + .icon = ICON_MD_SPEED, + .category = "Emulator", + .priority = 50}); card_registry_.RegisterCard({.card_id = "emulator.ai_agent", - .display_name = "AI Agent", - .icon = ICON_MD_SMART_TOY, - .category = "Emulator", - .priority = 60}); + .display_name = "AI Agent", + .icon = ICON_MD_SMART_TOY, + .category = "Emulator", + .priority = 60}); card_registry_.RegisterCard({.card_id = "emulator.save_states", - .display_name = "Save States", - .icon = ICON_MD_SAVE, - .category = "Emulator", - .priority = 70}); + .display_name = "Save States", + .icon = ICON_MD_SAVE, + .category = "Emulator", + .priority = 70}); card_registry_.RegisterCard({.card_id = "emulator.keyboard_config", - .display_name = "Keyboard Config", - .icon = ICON_MD_KEYBOARD, - .category = "Emulator", - .priority = 80}); + .display_name = "Keyboard Config", + .icon = ICON_MD_KEYBOARD, + .category = "Emulator", + .priority = 80}); card_registry_.RegisterCard({.card_id = "emulator.apu_debugger", - .display_name = "APU Debugger", - .icon = ICON_MD_AUDIOTRACK, - .category = "Emulator", - .priority = 90}); + .display_name = "APU Debugger", + .icon = ICON_MD_AUDIOTRACK, + .category = "Emulator", + .priority = 90}); card_registry_.RegisterCard({.card_id = "emulator.audio_mixer", - .display_name = "Audio Mixer", - .icon = ICON_MD_AUDIO_FILE, - .category = "Emulator", - .priority = 100}); + .display_name = "Audio Mixer", + .icon = ICON_MD_AUDIO_FILE, + .category = "Emulator", + .priority = 100}); // Show CPU debugger and PPU viewer by default for emulator card_registry_.ShowCard("emulator.cpu_debugger"); @@ -314,10 +315,10 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, // Register memory/hex editor card card_registry_.RegisterCard({.card_id = "memory.hex_editor", - .display_name = "Hex Editor", - .icon = ICON_MD_MEMORY, - .category = "Memory", - .priority = 10}); + .display_name = "Hex Editor", + .icon = ICON_MD_MEMORY, + .category = "Memory", + .priority = 10}); // Initialize project file editor project_file_editor_.SetToastManager(&toast_manager_); @@ -419,8 +420,8 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, return absl::OkStatus(); }; #else - multimodal_callbacks.send_to_gemini = - [](const std::filesystem::path&, const std::string&) -> absl::Status { + multimodal_callbacks.send_to_gemini = [](const std::filesystem::path&, + const std::string&) -> absl::Status { return absl::FailedPreconditionError( "Gemini AI runtime is disabled in this build"); }; @@ -591,8 +592,8 @@ void EditorManager::OpenEditorAndCardsFromFlags(const std::string& editor_name, // Activate the main editor window if (auto* editor_set = GetCurrentEditorSet()) { - auto* editor = editor_set - ->active_editors_[static_cast(editor_type_to_open)]; + auto* editor = + editor_set->active_editors_[static_cast(editor_type_to_open)]; if (editor) { editor->set_active(true); } @@ -601,40 +602,40 @@ void EditorManager::OpenEditorAndCardsFromFlags(const std::string& editor_name, // Handle specific cards for the Dungeon Editor if (editor_type_to_open == EditorType::kDungeon && !cards_str.empty()) { if (auto* editor_set = GetCurrentEditorSet()) { - std::stringstream ss(cards_str); - std::string card_name; - while (std::getline(ss, card_name, ',')) { - // Trim whitespace - card_name.erase(0, card_name.find_first_not_of(" \t")); - card_name.erase(card_name.find_last_not_of(" \t") + 1); + std::stringstream ss(cards_str); + std::string card_name; + while (std::getline(ss, card_name, ',')) { + // Trim whitespace + card_name.erase(0, card_name.find_first_not_of(" \t")); + card_name.erase(card_name.find_last_not_of(" \t") + 1); - LOG_DEBUG("EditorManager", "Attempting to open card: '%s'", - card_name.c_str()); + LOG_DEBUG("EditorManager", "Attempting to open card: '%s'", + card_name.c_str()); - if (card_name == "Rooms List") { - editor_set->dungeon_editor_.show_room_selector_ = true; - } else if (card_name == "Room Matrix") { - editor_set->dungeon_editor_.show_room_matrix_ = true; - } else if (card_name == "Entrances List") { - editor_set->dungeon_editor_.show_entrances_list_ = true; - } else if (card_name == "Room Graphics") { - editor_set->dungeon_editor_.show_room_graphics_ = true; - } else if (card_name == "Object Editor") { - editor_set->dungeon_editor_.show_object_editor_ = true; - } else if (card_name == "Palette Editor") { - editor_set->dungeon_editor_.show_palette_editor_ = true; - } else if (absl::StartsWith(card_name, "Room ")) { - try { - int room_id = std::stoi(card_name.substr(5)); - editor_set->dungeon_editor_.add_room(room_id); - } catch (const std::exception& e) { - LOG_WARN("EditorManager", "Invalid room ID format: %s", + if (card_name == "Rooms List") { + editor_set->dungeon_editor_.show_room_selector_ = true; + } else if (card_name == "Room Matrix") { + editor_set->dungeon_editor_.show_room_matrix_ = true; + } else if (card_name == "Entrances List") { + editor_set->dungeon_editor_.show_entrances_list_ = true; + } else if (card_name == "Room Graphics") { + editor_set->dungeon_editor_.show_room_graphics_ = true; + } else if (card_name == "Object Editor") { + editor_set->dungeon_editor_.show_object_editor_ = true; + } else if (card_name == "Palette Editor") { + editor_set->dungeon_editor_.show_palette_editor_ = true; + } else if (absl::StartsWith(card_name, "Room ")) { + try { + int room_id = std::stoi(card_name.substr(5)); + editor_set->dungeon_editor_.add_room(room_id); + } catch (const std::exception& e) { + LOG_WARN("EditorManager", "Invalid room ID format: %s", + card_name.c_str()); + } + } else { + LOG_WARN("EditorManager", "Unknown card name for Dungeon Editor: %s", card_name.c_str()); } - } else { - LOG_WARN("EditorManager", "Unknown card name for Dungeon Editor: %s", - card_name.c_str()); - } } } } @@ -663,10 +664,10 @@ absl::Status EditorManager::Update() { // Delegate to PopupManager for modal dialog rendering popup_manager_->DrawPopups(); - + // Execute keyboard shortcuts (registered via ShortcutConfigurator) ExecuteShortcuts(shortcut_manager_); - + // Delegate to ToastManager for notification rendering toast_manager_.Draw(); @@ -874,7 +875,8 @@ absl::Status EditorManager::Update() { for (auto editor : session.editors.active_editors_) { if (*editor->active() && IsCardBasedEditor(editor->type())) { - std::string category = EditorRegistry::GetEditorCategory(editor->type()); + std::string category = + EditorRegistry::GetEditorCategory(editor->type()); if (std::find(active_categories.begin(), active_categories.end(), category) == active_categories.end()) { active_categories.push_back(category); @@ -903,7 +905,8 @@ absl::Status EditorManager::Update() { if (!sidebar_category.empty()) { // Callback to switch editors when category button is clicked auto category_switch_callback = [this](const std::string& new_category) { - EditorType editor_type = EditorRegistry::GetEditorTypeFromCategory(new_category); + EditorType editor_type = + EditorRegistry::GetEditorTypeFromCategory(new_category); if (editor_type != EditorType::kUnknown) { SwitchToEditor(editor_type); } @@ -916,7 +919,7 @@ absl::Status EditorManager::Update() { }; card_registry_.DrawSidebar(sidebar_category, active_categories, - category_switch_callback, collapse_callback); + category_switch_callback, collapse_callback); } } @@ -984,7 +987,7 @@ void EditorManager::DrawMenuBar() { ui_coordinator_->SetImGuiDemoVisible(false); } } - + if (ui_coordinator_ && ui_coordinator_->IsImGuiMetricsVisible()) { bool visible = true; ImGui::ShowMetricsWindow(&visible); @@ -1003,11 +1006,11 @@ void EditorManager::DrawMenuBar() { } if (ui_coordinator_ && ui_coordinator_->IsAsmEditorVisible()) { - bool visible = true; - editor_set->assembly_editor_.Update(visible); - if (!visible) { - ui_coordinator_->SetAsmEditorVisible(false); - } + bool visible = true; + editor_set->assembly_editor_.Update(visible); + if (!visible) { + ui_coordinator_->SetAsmEditorVisible(false); + } } } @@ -1055,7 +1058,7 @@ void EditorManager::DrawMenuBar() { bool visible = true; ImGui::Begin("Palette Editor", &visible); if (auto* editor_set = GetCurrentEditorSet()) { - status_ = editor_set->palette_editor_.Update(); + status_ = editor_set->palette_editor_.Update(); } // Route palette editor errors to toast manager @@ -1071,7 +1074,8 @@ void EditorManager::DrawMenuBar() { } } - if (ui_coordinator_ && ui_coordinator_->IsResourceLabelManagerVisible() && GetCurrentRom()) { + if (ui_coordinator_ && ui_coordinator_->IsResourceLabelManagerVisible() && + GetCurrentRom()) { bool visible = true; GetCurrentRom()->resource_label()->DisplayLabels(&visible); if (current_project_.project_opened() && @@ -1129,12 +1133,14 @@ absl::Status EditorManager::LoadRom() { Rom temp_rom; RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, file_name)); - auto session_or = session_coordinator_->CreateSessionFromRom(std::move(temp_rom), file_name); + auto session_or = session_coordinator_->CreateSessionFromRom( + std::move(temp_rom), file_name); if (!session_or.ok()) { return session_or.status(); } - ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), GetCurrentSessionId()); + ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), + GetCurrentSessionId()); #ifdef YAZE_ENABLE_TESTING test::TestManager::Get().SetCurrentRom(GetCurrentRom()); @@ -1147,9 +1153,9 @@ absl::Status EditorManager::LoadRom() { RETURN_IF_ERROR(LoadAssets()); if (ui_coordinator_) { - ui_coordinator_->SetWelcomeScreenVisible(false); - editor_selection_dialog_.ClearRecentEditors(); - ui_coordinator_->SetEditorSelectionVisible(true); + ui_coordinator_->SetWelcomeScreenVisible(false); + editor_selection_dialog_.ClearRecentEditors(); + ui_coordinator_->SetEditorSelectionVisible(true); } return absl::OkStatus(); @@ -1179,7 +1185,8 @@ absl::Status EditorManager::LoadAssets() { current_editor_set->palette_editor_.Initialize(); current_editor_set->assembly_editor_.Initialize(); current_editor_set->music_editor_.Initialize(); - current_editor_set->settings_editor_.Initialize(); // Initialize settings editor to register System cards + current_editor_set->settings_editor_ + .Initialize(); // Initialize settings editor to register System cards // Initialize the dungeon editor with the renderer current_editor_set->dungeon_editor_.Initialize(renderer_, current_rom); ASSIGN_OR_RETURN(*gfx::Arena::Get().mutable_gfx_sheets(), @@ -1284,14 +1291,16 @@ absl::Status EditorManager::OpenRomOrProject(const std::string& filename) { } else { Rom temp_rom; RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, filename)); - - auto session_or = session_coordinator_->CreateSessionFromRom(std::move(temp_rom), filename); + + auto session_or = session_coordinator_->CreateSessionFromRom( + std::move(temp_rom), filename); if (!session_or.ok()) { return session_or.status(); } RomSession* session = *session_or; - ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), GetCurrentSessionId()); + ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), + GetCurrentSessionId()); // Apply project feature flags to the session session->feature_flags = current_project_.feature_flags; @@ -1304,9 +1313,9 @@ absl::Status EditorManager::OpenRomOrProject(const std::string& filename) { test::TestManager::Get().SetCurrentRom(GetCurrentRom()); #endif - if (auto* editor_set = GetCurrentEditorSet(); editor_set && !current_project_.code_folder.empty()) { - editor_set->assembly_editor_.OpenFolder( - current_project_.code_folder); + if (auto* editor_set = GetCurrentEditorSet(); + editor_set && !current_project_.code_folder.empty()) { + editor_set->assembly_editor_.OpenFolder(current_project_.code_folder); } RETURN_IF_ERROR(LoadAssets()); @@ -1324,8 +1333,8 @@ absl::Status EditorManager::CreateNewProject(const std::string& template_name) { auto status = project_manager_.CreateNewProject(template_name); if (status.ok()) { current_project_ = project_manager_.GetCurrentProject(); - // Show project creation dialog - popup_manager_->Show("Create New Project"); + // Show project creation dialog + popup_manager_->Show("Create New Project"); } return status; } @@ -1357,14 +1366,16 @@ absl::Status EditorManager::OpenProject() { Rom temp_rom; RETURN_IF_ERROR( rom_file_manager_.LoadRom(&temp_rom, current_project_.rom_filename)); - - auto session_or = session_coordinator_->CreateSessionFromRom(std::move(temp_rom), current_project_.rom_filename); + + auto session_or = session_coordinator_->CreateSessionFromRom( + std::move(temp_rom), current_project_.rom_filename); if (!session_or.ok()) { return session_or.status(); } RomSession* session = *session_or; - ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), GetCurrentSessionId()); + ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), + GetCurrentSessionId()); // Apply project feature flags to the session session->feature_flags = current_project_.feature_flags; @@ -1377,9 +1388,9 @@ absl::Status EditorManager::OpenProject() { test::TestManager::Get().SetCurrentRom(GetCurrentRom()); #endif - if (auto* editor_set = GetCurrentEditorSet(); editor_set && !current_project_.code_folder.empty()) { - editor_set->assembly_editor_.OpenFolder( - current_project_.code_folder); + if (auto* editor_set = GetCurrentEditorSet(); + editor_set && !current_project_.code_folder.empty()) { + editor_set->assembly_editor_.OpenFolder(current_project_.code_folder); } RETURN_IF_ERROR(LoadAssets()); @@ -1534,11 +1545,11 @@ absl::Status EditorManager::SetCurrentRom(Rom* rom) { void EditorManager::CreateNewSession() { if (session_coordinator_) { session_coordinator_->CreateNewSession(); - + // Wire editor contexts for new session if (!sessions_.empty()) { - RomSession& session = sessions_.back(); - session.editors.set_user_settings(&user_settings_); + RomSession& session = sessions_.back(); + session.editors.set_user_settings(&user_settings_); ConfigureEditorDependencies(&session.editors, &session.rom, session.editors.session_id()); session_coordinator_->SwitchToSession(sessions_.size() - 1); @@ -1567,7 +1578,7 @@ void EditorManager::DuplicateCurrentSession() { if (session_coordinator_) { session_coordinator_->DuplicateCurrentSession(); - + // Wire editor contexts for duplicated session if (!sessions_.empty()) { RomSession& session = sessions_.back(); @@ -1581,7 +1592,7 @@ void EditorManager::DuplicateCurrentSession() { void EditorManager::CloseCurrentSession() { if (session_coordinator_) { session_coordinator_->CloseCurrentSession(); - + // Update current pointers after session change -- no longer needed } } @@ -1589,7 +1600,7 @@ void EditorManager::CloseCurrentSession() { void EditorManager::RemoveSession(size_t index) { if (session_coordinator_) { session_coordinator_->RemoveSession(index); - + // Update current pointers after session change -- no longer needed } } @@ -1599,8 +1610,8 @@ void EditorManager::SwitchToSession(size_t index) { return; } - session_coordinator_->SwitchToSession(index); - + session_coordinator_->SwitchToSession(index); + if (index >= sessions_.size()) { return; } @@ -1616,7 +1627,7 @@ size_t EditorManager::GetCurrentSessionIndex() const { if (session_coordinator_) { return session_coordinator_->GetActiveSessionIndex(); } - + // Fallback to finding by ROM pointer for (size_t i = 0; i < sessions_.size(); ++i) { if (&sessions_[i].rom == GetCurrentRom() && @@ -1631,7 +1642,7 @@ size_t EditorManager::GetActiveSessionCount() const { if (session_coordinator_) { return session_coordinator_->GetActiveSessionCount(); } - + // Fallback to counting non-closed sessions size_t count = 0; for (const auto& session : sessions_) { @@ -1699,9 +1710,10 @@ void EditorManager::SwitchToEditor(EditorType editor_type) { // Editor activated - set its category card_registry_.SetActiveCategory( EditorRegistry::GetEditorCategory(editor_type)); - + // Initialize default layout on first activation - if (layout_manager_ && !layout_manager_->IsLayoutInitialized(editor_type)) { + if (layout_manager_ && + !layout_manager_->IsLayoutInitialized(editor_type)) { ImGuiID dockspace_id = ImGui::GetID("MainDockSpace"); layout_manager_->InitializeEditorLayout(editor_type, dockspace_id); } @@ -1710,8 +1722,8 @@ void EditorManager::SwitchToEditor(EditorType editor_type) { for (auto* other : editor_set->active_editors_) { if (*other->active() && IsCardBasedEditor(other->type()) && other != editor) { - card_registry_.SetActiveCategory( - EditorRegistry::GetEditorCategory(other->type())); + card_registry_.SetActiveCategory( + EditorRegistry::GetEditorCategory(other->type())); break; } } @@ -1723,12 +1735,15 @@ void EditorManager::SwitchToEditor(EditorType editor_type) { // Handle non-editor-class cases if (editor_type == EditorType::kAssembly) { - if (ui_coordinator_) ui_coordinator_->SetAsmEditorVisible(!ui_coordinator_->IsAsmEditorVisible()); + if (ui_coordinator_) + ui_coordinator_->SetAsmEditorVisible( + !ui_coordinator_->IsAsmEditorVisible()); } else if (editor_type == EditorType::kEmulator) { if (ui_coordinator_) { - ui_coordinator_->SetEmulatorVisible(!ui_coordinator_->IsEmulatorVisible()); + ui_coordinator_->SetEmulatorVisible( + !ui_coordinator_->IsEmulatorVisible()); if (ui_coordinator_->IsEmulatorVisible()) { - card_registry_.SetActiveCategory("Emulator"); + card_registry_.SetActiveCategory("Emulator"); } } } @@ -1741,7 +1756,7 @@ EditorManager::SessionScope::SessionScope(EditorManager* manager, prev_rom_(manager->GetCurrentRom()), prev_editor_set_(manager->GetCurrentEditorSet()), prev_session_id_(manager->GetCurrentSessionId()) { - + // Set new session context manager_->session_coordinator_->SwitchToSession(session_id); } diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index fd3318e4..a56ae1f1 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -4,9 +4,9 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include "app/editor/editor.h" +#include "app/editor/session_types.h" #include "app/editor/system/user_settings.h" #include "app/editor/ui/workspace_manager.h" -#include "app/editor/session_types.h" #include "imgui/imgui.h" @@ -16,7 +16,6 @@ #include #include "absl/status/status.h" -#include "core/project.h" #include "app/editor/agent/agent_chat_history_popup.h" #include "app/editor/code/project_file_editor.h" #include "app/editor/system/editor_card_registry.h" @@ -35,9 +34,10 @@ #include "app/editor/ui/ui_coordinator.h" #include "app/editor/ui/welcome_screen.h" #include "app/emu/emulator.h" -#include "zelda3/overworld/overworld.h" #include "app/rom.h" +#include "core/project.h" #include "yaze_config.h" +#include "zelda3/overworld/overworld.h" #ifdef YAZE_WITH_GRPC #include "app/editor/agent/agent_editor.h" @@ -85,14 +85,23 @@ class EditorManager { WorkspaceManager* workspace_manager() { return &workspace_manager_; } absl::Status SetCurrentRom(Rom* rom); - auto GetCurrentRom() const -> Rom* { return session_coordinator_ ? session_coordinator_->GetCurrentRom() : nullptr; } - auto GetCurrentEditorSet() const -> EditorSet* { return session_coordinator_ ? session_coordinator_->GetCurrentEditorSet() : nullptr; } + auto GetCurrentRom() const -> Rom* { + return session_coordinator_ ? session_coordinator_->GetCurrentRom() + : nullptr; + } + auto GetCurrentEditorSet() const -> EditorSet* { + return session_coordinator_ ? session_coordinator_->GetCurrentEditorSet() + : nullptr; + } auto GetCurrentEditor() const -> Editor* { return current_editor_; } - size_t GetCurrentSessionId() const { return session_coordinator_ ? session_coordinator_->GetActiveSessionIndex() : 0; } + size_t GetCurrentSessionId() const { + return session_coordinator_ ? session_coordinator_->GetActiveSessionIndex() + : 0; + } UICoordinator* ui_coordinator() { return ui_coordinator_.get(); } auto overworld() const -> yaze::zelda3::Overworld* { if (auto* editor_set = GetCurrentEditorSet()) { - return &editor_set->overworld_editor_.overworld(); + return &editor_set->overworld_editor_.overworld(); } return nullptr; } @@ -204,9 +213,18 @@ class EditorManager { ui_coordinator_->SetImGuiMetricsVisible(true); } void ShowHexEditor(); - void ShowEmulator() { if (ui_coordinator_) ui_coordinator_->SetEmulatorVisible(true); } - void ShowMemoryEditor() { if (ui_coordinator_) ui_coordinator_->SetMemoryEditorVisible(true); } - void ShowResourceLabelManager() { if (ui_coordinator_) ui_coordinator_->SetResourceLabelManagerVisible(true); } + void ShowEmulator() { + if (ui_coordinator_) + ui_coordinator_->SetEmulatorVisible(true); + } + void ShowMemoryEditor() { + if (ui_coordinator_) + ui_coordinator_->SetMemoryEditorVisible(true); + } + void ShowResourceLabelManager() { + if (ui_coordinator_) + ui_coordinator_->SetResourceLabelManagerVisible(true); + } void ShowCardBrowser() { if (ui_coordinator_) ui_coordinator_->ShowCardBrowser(); @@ -240,8 +258,8 @@ class EditorManager { absl::Status RepairCurrentProject(); private: - absl::Status DrawRomSelector() = delete; // Moved to UICoordinator - void DrawContextSensitiveCardControl(); // Card control for current editor + absl::Status DrawRomSelector() = delete; // Moved to UICoordinator + void DrawContextSensitiveCardControl(); // Card control for current editor absl::Status LoadAssets(); @@ -252,7 +270,7 @@ class EditorManager { // Note: All show_* flags are being moved to UICoordinator // Access via ui_coordinator_->IsXxxVisible() or SetXxxVisible() - + // Workspace dialog flags (managed by EditorManager, not UI) bool show_workspace_layout = false; size_t session_to_rename_ = 0; @@ -292,7 +310,6 @@ class EditorManager { emu::Emulator emulator_; public: - private: std::deque sessions_; Editor* current_editor_ = nullptr; @@ -319,7 +336,8 @@ class EditorManager { std::unique_ptr ui_coordinator_; WindowDelegate window_delegate_; std::unique_ptr session_coordinator_; - std::unique_ptr layout_manager_; // DockBuilder layout management + std::unique_ptr + layout_manager_; // DockBuilder layout management WorkspaceManager workspace_manager_{&toast_manager_}; float autosave_timer_ = 0.0f; diff --git a/src/app/editor/editor_safeguards.h b/src/app/editor/editor_safeguards.h index 65e260d4..b314fc7b 100644 --- a/src/app/editor/editor_safeguards.h +++ b/src/app/editor/editor_safeguards.h @@ -9,26 +9,28 @@ namespace yaze { namespace editor { // Macro for checking ROM loading state in editor methods -#define REQUIRE_ROM_LOADED(rom_ptr, operation) \ - do { \ - if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \ - return absl::FailedPreconditionError( \ +#define REQUIRE_ROM_LOADED(rom_ptr, operation) \ + do { \ + if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \ + return absl::FailedPreconditionError( \ absl::StrFormat("%s: ROM not loaded", (operation))); \ - } \ + } \ } while (0) // Macro for ROM state checking with custom error message -#define CHECK_ROM_STATE(rom_ptr, message) \ - do { \ - if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \ +#define CHECK_ROM_STATE(rom_ptr, message) \ + do { \ + if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \ return absl::FailedPreconditionError(message); \ - } \ + } \ } while (0) // Helper function for generating consistent ROM status messages inline std::string GetRomStatusMessage(const Rom* rom) { - if (!rom) return "No ROM loaded"; - if (!rom->is_loaded()) return "ROM failed to load"; + if (!rom) + return "No ROM loaded"; + if (!rom->is_loaded()) + return "ROM failed to load"; return absl::StrFormat("ROM loaded: %s", rom->title()); } diff --git a/src/app/editor/graphics/gfx_group_editor.cc b/src/app/editor/graphics/gfx_group_editor.cc index aa0dd5e5..da1dcb60 100644 --- a/src/app/editor/graphics/gfx_group_editor.cc +++ b/src/app/editor/graphics/gfx_group_editor.cc @@ -112,7 +112,7 @@ void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) { BeginGroup(); for (int i = 0; i < 8; i++) { int sheet_id = rom()->main_blockset_ids[selected_blockset_][i]; - auto &sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id); + auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id); gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04, 0x20, true, false, 22); } @@ -165,7 +165,7 @@ void GfxGroupEditor::DrawRoomsetViewer() { BeginGroup(); for (int i = 0; i < 4; i++) { int sheet_id = rom()->room_blockset_ids[selected_roomset_][i]; - auto &sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id); + auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id); gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04, 0x20, true, false, 23); } @@ -203,7 +203,7 @@ void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) { BeginGroup(); for (int i = 0; i < 4; i++) { int sheet_id = rom()->spriteset_ids[selected_spriteset_][i]; - auto &sheet = + auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(115 + sheet_id); gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04, 0x20, true, false, 24); @@ -215,20 +215,20 @@ void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) { } namespace { -void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) { +void DrawPaletteFromPaletteGroup(gfx::SnesPalette& palette) { if (palette.empty()) { return; } for (size_t n = 0; n < palette.size(); n++) { PushID(n); - if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y); + if ((n % 8) != 0) + SameLine(0.0f, GetStyle().ItemSpacing.y); // Small icon of the color in the palette if (gui::SnesColorButton(absl::StrCat("Palette", n), palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | - ImGuiColorEditFlags_NoTooltip)) { - } + ImGuiColorEditFlags_NoTooltip)) {} PopID(); } @@ -247,13 +247,13 @@ void GfxGroupEditor::DrawPaletteViewer() { false, "paletteset", "0x" + std::to_string(selected_paletteset_), "Paletteset " + std::to_string(selected_paletteset_)); - uint8_t &dungeon_main_palette_val = + uint8_t& dungeon_main_palette_val = rom()->paletteset_ids[selected_paletteset_][0]; - uint8_t &dungeon_spr_pal_1_val = + uint8_t& dungeon_spr_pal_1_val = rom()->paletteset_ids[selected_paletteset_][1]; - uint8_t &dungeon_spr_pal_2_val = + uint8_t& dungeon_spr_pal_2_val = rom()->paletteset_ids[selected_paletteset_][2]; - uint8_t &dungeon_spr_pal_3_val = + uint8_t& dungeon_spr_pal_3_val = rom()->paletteset_ids[selected_paletteset_][3]; gui::InputHexByte("Dungeon Main", &dungeon_main_palette_val); @@ -261,13 +261,13 @@ void GfxGroupEditor::DrawPaletteViewer() { rom()->resource_label()->SelectableLabelWithNameEdit( false, kPaletteGroupNames[PaletteCategory::kDungeons].data(), std::to_string(dungeon_main_palette_val), "Unnamed dungeon palette"); - auto &palette = *rom()->mutable_palette_group()->dungeon_main.mutable_palette( + auto& palette = *rom()->mutable_palette_group()->dungeon_main.mutable_palette( rom()->paletteset_ids[selected_paletteset_][0]); DrawPaletteFromPaletteGroup(palette); Separator(); gui::InputHexByte("Dungeon Spr Pal 1", &dungeon_spr_pal_1_val); - auto &spr_aux_pal1 = + auto& spr_aux_pal1 = *rom()->mutable_palette_group()->sprites_aux1.mutable_palette( rom()->paletteset_ids[selected_paletteset_][1]); DrawPaletteFromPaletteGroup(spr_aux_pal1); @@ -278,7 +278,7 @@ void GfxGroupEditor::DrawPaletteViewer() { Separator(); gui::InputHexByte("Dungeon Spr Pal 2", &dungeon_spr_pal_2_val); - auto &spr_aux_pal2 = + auto& spr_aux_pal2 = *rom()->mutable_palette_group()->sprites_aux2.mutable_palette( rom()->paletteset_ids[selected_paletteset_][2]); DrawPaletteFromPaletteGroup(spr_aux_pal2); @@ -289,7 +289,7 @@ void GfxGroupEditor::DrawPaletteViewer() { Separator(); gui::InputHexByte("Dungeon Spr Pal 3", &dungeon_spr_pal_3_val); - auto &spr_aux_pal3 = + auto& spr_aux_pal3 = *rom()->mutable_palette_group()->sprites_aux3.mutable_palette( rom()->paletteset_ids[selected_paletteset_][3]); DrawPaletteFromPaletteGroup(spr_aux_pal3); diff --git a/src/app/editor/graphics/graphics_editor.cc b/src/app/editor/graphics/graphics_editor.cc index c5ce371b..8800581b 100644 --- a/src/app/editor/graphics/graphics_editor.cc +++ b/src/app/editor/graphics/graphics_editor.cc @@ -6,26 +6,26 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" -#include "app/gui/core/ui_helpers.h" -#include "util/file_util.h" -#include "app/platform/window.h" -#include "app/gfx/resource/arena.h" #include "app/gfx/core/bitmap.h" -#include "app/gfx/util/compression.h" -#include "app/gfx/util/scad_format.h" +#include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" #include "app/gfx/types/snes_tile.h" +#include "app/gfx/util/compression.h" +#include "app/gfx/util/scad_format.h" #include "app/gui/canvas/canvas.h" #include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" -#include "app/gui/widgets/asset_browser.h" #include "app/gui/core/style.h" +#include "app/gui/core/ui_helpers.h" +#include "app/gui/widgets/asset_browser.h" +#include "app/platform/window.h" #include "app/rom.h" -#include "app/gfx/debug/performance/performance_profiler.h" #include "imgui/imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" #include "imgui_memory_editor.h" +#include "util/file_util.h" #include "util/log.h" namespace yaze { @@ -44,47 +44,61 @@ constexpr ImGuiTableFlags kGfxEditTableFlags = ImGuiTableFlags_SizingFixedFit; void GraphicsEditor::Initialize() { - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - - card_registry->RegisterCard({.card_id = "graphics.sheet_editor", .display_name = "Sheet Editor", - .icon = ICON_MD_EDIT, .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+1", .priority = 10}); - card_registry->RegisterCard({.card_id = "graphics.sheet_browser", .display_name = "Sheet Browser", - .icon = ICON_MD_VIEW_LIST, .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+2", .priority = 20}); - card_registry->RegisterCard({.card_id = "graphics.player_animations", .display_name = "Player Animations", - .icon = ICON_MD_PERSON, .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+3", .priority = 30}); - card_registry->RegisterCard({.card_id = "graphics.prototype_viewer", .display_name = "Prototype Viewer", - .icon = ICON_MD_CONSTRUCTION, .category = "Graphics", - .shortcut_hint = "Ctrl+Shift+4", .priority = 40}); - + + card_registry->RegisterCard({.card_id = "graphics.sheet_editor", + .display_name = "Sheet Editor", + .icon = ICON_MD_EDIT, + .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+1", + .priority = 10}); + card_registry->RegisterCard({.card_id = "graphics.sheet_browser", + .display_name = "Sheet Browser", + .icon = ICON_MD_VIEW_LIST, + .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+2", + .priority = 20}); + card_registry->RegisterCard({.card_id = "graphics.player_animations", + .display_name = "Player Animations", + .icon = ICON_MD_PERSON, + .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+3", + .priority = 30}); + card_registry->RegisterCard({.card_id = "graphics.prototype_viewer", + .display_name = "Prototype Viewer", + .icon = ICON_MD_CONSTRUCTION, + .category = "Graphics", + .shortcut_hint = "Ctrl+Shift+4", + .priority = 40}); + // Show sheet editor by default when Graphics Editor is activated card_registry->ShowCard("graphics.sheet_editor"); } -absl::Status GraphicsEditor::Load() { +absl::Status GraphicsEditor::Load() { gfx::ScopedTimer timer("GraphicsEditor::Load"); - + // Initialize all graphics sheets with appropriate palettes from ROM // This ensures textures are created for editing if (rom()->is_loaded()) { auto& sheets = gfx::Arena::Get().gfx_sheets(); - + // Apply default palettes to all sheets based on common SNES ROM structure // Sheets 0-112: Use overworld/dungeon palettes // Sheets 113-127: Use sprite palettes // Sheets 128-222: Use auxiliary/menu palettes - - LOG_INFO("GraphicsEditor", "Initializing textures for %d graphics sheets", kNumGfxSheets); - + + LOG_INFO("GraphicsEditor", "Initializing textures for %d graphics sheets", + kNumGfxSheets); + int sheets_queued = 0; for (int i = 0; i < kNumGfxSheets; i++) { if (!sheets[i].is_active() || !sheets[i].surface()) { continue; // Skip inactive or surface-less sheets } - + // Palettes are now applied during ROM loading in LoadAllGraphicsData() // Just queue texture creation for sheets that don't have textures yet if (!sheets[i].texture()) { @@ -93,21 +107,24 @@ absl::Status GraphicsEditor::Load() { sheets_queued++; } } - - LOG_INFO("GraphicsEditor", "Queued texture creation for %d graphics sheets", sheets_queued); + + LOG_INFO("GraphicsEditor", "Queued texture creation for %d graphics sheets", + sheets_queued); } - - return absl::OkStatus(); + + return absl::OkStatus(); } absl::Status GraphicsEditor::Update() { - if (!dependencies_.card_registry) return absl::OkStatus(); + if (!dependencies_.card_registry) + return absl::OkStatus(); auto* card_registry = dependencies_.card_registry; - + static gui::EditorCard sheet_editor_card("Sheet Editor", ICON_MD_EDIT); static gui::EditorCard sheet_browser_card("Sheet Browser", ICON_MD_VIEW_LIST); static gui::EditorCard player_anims_card("Player Animations", ICON_MD_PERSON); - static gui::EditorCard prototype_card("Prototype Viewer", ICON_MD_CONSTRUCTION); + static gui::EditorCard prototype_card("Prototype Viewer", + ICON_MD_CONSTRUCTION); sheet_editor_card.SetDefaultSize(900, 700); sheet_browser_card.SetDefaultSize(400, 600); @@ -115,7 +132,8 @@ absl::Status GraphicsEditor::Update() { prototype_card.SetDefaultSize(600, 500); // Sheet Editor Card - Check visibility flag exists and is true before rendering - bool* sheet_editor_visible = card_registry->GetVisibilityFlag("graphics.sheet_editor"); + bool* sheet_editor_visible = + card_registry->GetVisibilityFlag("graphics.sheet_editor"); if (sheet_editor_visible && *sheet_editor_visible) { if (sheet_editor_card.Begin(sheet_editor_visible)) { status_ = UpdateGfxEdit(); @@ -124,7 +142,8 @@ absl::Status GraphicsEditor::Update() { } // Sheet Browser Card - Check visibility flag exists and is true before rendering - bool* sheet_browser_visible = card_registry->GetVisibilityFlag("graphics.sheet_browser"); + bool* sheet_browser_visible = + card_registry->GetVisibilityFlag("graphics.sheet_browser"); if (sheet_browser_visible && *sheet_browser_visible) { if (sheet_browser_card.Begin(sheet_browser_visible)) { if (asset_browser_.Initialized == false) { @@ -136,7 +155,8 @@ absl::Status GraphicsEditor::Update() { } // Player Animations Card - Check visibility flag exists and is true before rendering - bool* player_anims_visible = card_registry->GetVisibilityFlag("graphics.player_animations"); + bool* player_anims_visible = + card_registry->GetVisibilityFlag("graphics.player_animations"); if (player_anims_visible && *player_anims_visible) { if (player_anims_card.Begin(player_anims_visible)) { status_ = UpdateLinkGfxView(); @@ -145,7 +165,8 @@ absl::Status GraphicsEditor::Update() { } // Prototype Viewer Card - Check visibility flag exists and is true before rendering - bool* prototype_visible = card_registry->GetVisibilityFlag("graphics.prototype_viewer"); + bool* prototype_visible = + card_registry->GetVisibilityFlag("graphics.prototype_viewer"); if (prototype_visible && *prototype_visible) { if (prototype_card.Begin(prototype_visible)) { status_ = UpdateScadView(); @@ -158,28 +179,28 @@ absl::Status GraphicsEditor::Update() { } absl::Status GraphicsEditor::UpdateGfxEdit() { - if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags, - ImVec2(0, 0))) { - for (const auto& name : - {"Tilesheets", "Current Graphics", "Palette Controls"}) - ImGui::TableSetupColumn(name); + if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags, + ImVec2(0, 0))) { + for (const auto& name : + {"Tilesheets", "Current Graphics", "Palette Controls"}) + ImGui::TableSetupColumn(name); - ImGui::TableHeadersRow(); - ImGui::TableNextColumn(); - status_ = UpdateGfxSheetList(); + ImGui::TableHeadersRow(); + ImGui::TableNextColumn(); + status_ = UpdateGfxSheetList(); - ImGui::TableNextColumn(); - if (rom()->is_loaded()) { - DrawGfxEditToolset(); - status_ = UpdateGfxTabView(); - } - - ImGui::TableNextColumn(); - if (rom()->is_loaded()) { - status_ = UpdatePaletteColumn(); - } + ImGui::TableNextColumn(); + if (rom()->is_loaded()) { + DrawGfxEditToolset(); + status_ = UpdateGfxTabView(); } - ImGui::EndTable(); + + ImGui::TableNextColumn(); + if (rom()->is_loaded()) { + status_ = UpdatePaletteColumn(); + } + } + ImGui::EndTable(); return absl::OkStatus(); } @@ -254,31 +275,32 @@ void GraphicsEditor::DrawGfxEditToolset() { // Enhanced palette color picker with SNES-specific features auto bitmap = gfx::Arena::Get().gfx_sheets()[current_sheet_]; auto palette = bitmap.palette(); - + // Display palette colors in a grid layout for better ROM hacking workflow for (int i = 0; i < palette.size(); i++) { if (i > 0 && i % 8 == 0) { - ImGui::NewLine(); // New row every 8 colors (SNES palette standard) + ImGui::NewLine(); // New row every 8 colors (SNES palette standard) } ImGui::SameLine(); - + // Convert SNES color to ImGui format with proper scaling - auto color = ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f, - palette[i].rgb().z / 255.0f, 1.0f); - + auto color = + ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f, + palette[i].rgb().z / 255.0f, 1.0f); + // Enhanced color button with tooltip showing SNES color value if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(), color, ImGuiColorEditFlags_NoTooltip)) { current_color_ = color; } - + // Add tooltip with SNES color information if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("SNES Color: $%04X\nRGB: (%d, %d, %d)", - palette[i].snes(), - static_cast(palette[i].rgb().x), - static_cast(palette[i].rgb().y), - static_cast(palette[i].rgb().z)); + ImGui::SetTooltip("SNES Color: $%04X\nRGB: (%d, %d, %d)", + palette[i].snes(), + static_cast(palette[i].rgb().x), + static_cast(palette[i].rgb().y), + static_cast(palette[i].rgb().z)); } } @@ -322,7 +344,7 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() { gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, &value); } - + auto texture = value.texture(); if (texture) { graphics_bin_canvas_.draw_list()->AddImage( @@ -347,8 +369,8 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() { ImVec2 rect_min(text_pos.x, text_pos.y); ImVec2 rect_max(text_pos.x + text_size.x, text_pos.y + text_size.y); - graphics_bin_canvas_.draw_list()->AddRectFilled(rect_min, rect_max, - IM_COL32(0, 125, 0, 128)); + graphics_bin_canvas_.draw_list()->AddRectFilled( + rect_min, rect_max, IM_COL32(0, 125, 0, 128)); graphics_bin_canvas_.draw_list()->AddText( text_pos, IM_COL32(125, 255, 125, 255), @@ -371,7 +393,7 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() { absl::Status GraphicsEditor::UpdateGfxTabView() { gfx::ScopedTimer timer("graphics_editor_update_gfx_tab_view"); - + static int next_tab_id = 0; constexpr ImGuiTabBarFlags kGfxEditTabBarFlags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | @@ -412,8 +434,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { auto draw_tile_event = [&]() { current_sheet_canvas_.DrawTileOnBitmap(tile_size_, ¤t_bitmap, current_color_); - // Notify Arena that this sheet has been modified for cross-editor synchronization - gfx::Arena::Get().NotifySheetModified(sheet_id); + // Notify Arena that this sheet has been modified for cross-editor synchronization + gfx::Arena::Get().NotifySheetModified(sheet_id); }; current_sheet_canvas_.UpdateColorPainter( @@ -427,7 +449,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { ImGui::EndTabItem(); } - if (!open) release_queue_.push(sheet_id); + if (!open) + release_queue_.push(sheet_id); } ImGui::EndTabBar(); @@ -453,7 +476,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { current_sheet_ = id; // ImVec2(0x100, 0x40), current_sheet_canvas_.UpdateColorPainter( - nullptr, gfx::Arena::Get().mutable_gfx_sheets()->at(id), current_color_, + nullptr, gfx::Arena::Get().mutable_gfx_sheets()->at(id), + current_color_, [&]() { }, @@ -478,7 +502,7 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() { kPaletteGroupAddressesKeys[edit_palette_group_name_index_]); auto palette = palette_group.palette(edit_palette_index_); gui::TextWithSeparators("ROM Palette Management"); - + // Quick palette presets for common SNES graphics types ImGui::Text("Quick Presets:"); if (ImGui::Button("Overworld")) { @@ -499,7 +523,7 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() { refresh_graphics_ = true; } ImGui::Separator(); - + // Apply current palette to current sheet if (ImGui::Button("Apply to Current Sheet") && !open_sheets_.empty()) { refresh_graphics_ = true; @@ -517,7 +541,7 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() { } } ImGui::Separator(); - + ImGui::SetNextItemWidth(150.f); ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_, kPaletteGroupAddressesKeys, @@ -531,7 +555,8 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() { palette); if (refresh_graphics_ && !open_sheets_.empty()) { - auto& current = gfx::Arena::Get().mutable_gfx_sheets()->data()[current_sheet_]; + auto& current = + gfx::Arena::Get().mutable_gfx_sheets()->data()[current_sheet_]; if (current.is_active() && current.surface()) { current.SetPaletteWithTransparent(palette, edit_palette_sub_index_); // Notify Arena that this sheet has been modified @@ -613,7 +638,9 @@ absl::Status GraphicsEditor::UpdateScadView() { status_ = DrawExperimentalFeatures(); } - NEXT_COLUMN() { status_ = DrawPaletteControls(); } + NEXT_COLUMN() { + status_ = DrawPaletteControls(); + } NEXT_COLUMN() gui::BitmapCanvasPipeline(scr_canvas_, scr_bitmap_, 0x200, 0x200, 0x20, @@ -908,8 +935,8 @@ absl::Status GraphicsEditor::DecompressImportData(int size) { } } - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, &bin_bitmap_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + &bin_bitmap_); gfx_loaded_ = true; return absl::OkStatus(); diff --git a/src/app/editor/graphics/graphics_editor.h b/src/app/editor/graphics/graphics_editor.h index c3589199..c02f1ec2 100644 --- a/src/app/editor/graphics/graphics_editor.h +++ b/src/app/editor/graphics/graphics_editor.h @@ -8,13 +8,13 @@ #include "app/editor/palette/palette_editor.h" #include "app/gfx/core/bitmap.h" #include "app/gfx/types/snes_tile.h" -#include "app/gui/canvas/canvas.h" #include "app/gui/app/editor_layout.h" +#include "app/gui/canvas/canvas.h" #include "app/gui/widgets/asset_browser.h" #include "app/rom.h" -#include "zelda3/overworld/overworld.h" #include "imgui/imgui.h" #include "imgui_memory_editor.h" +#include "zelda3/overworld/overworld.h" namespace yaze { namespace editor { @@ -57,8 +57,8 @@ const std::string kSuperDonkeySprites[] = { */ class GraphicsEditor : public Editor { public: - explicit GraphicsEditor(Rom* rom = nullptr) : rom_(rom) { - type_ = EditorType::kGraphics; + explicit GraphicsEditor(Rom* rom = nullptr) : rom_(rom) { + type_ = EditorType::kGraphics; } void Initialize() override; @@ -71,10 +71,10 @@ class GraphicsEditor : public Editor { absl::Status Undo() override { return absl::UnimplementedError("Undo"); } absl::Status Redo() override { return absl::UnimplementedError("Redo"); } absl::Status Find() override { return absl::UnimplementedError("Find"); } - + // Set the ROM pointer void set_rom(Rom* rom) { rom_ = rom; } - + // Get the ROM pointer Rom* rom() const { return rom_; } diff --git a/src/app/editor/graphics/screen_editor.cc b/src/app/editor/graphics/screen_editor.cc index b36ece05..83f7465b 100644 --- a/src/app/editor/graphics/screen_editor.cc +++ b/src/app/editor/graphics/screen_editor.cc @@ -6,45 +6,60 @@ #include #include "absl/strings/str_format.h" -#include "app/gfx/debug/performance/performance_profiler.h" -#include "util/file_util.h" -#include "app/gfx/resource/arena.h" #include "app/gfx/core/bitmap.h" +#include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_tile.h" #include "app/gui/canvas/canvas.h" #include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" #include "imgui/imgui.h" +#include "util/file_util.h" #include "util/hex.h" #include "util/macro.h" namespace yaze { namespace editor { - constexpr uint32_t kRedPen = 0xFF0000FF; void ScreenEditor::Initialize() { - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - - card_registry->RegisterCard({.card_id = "screen.dungeon_maps", .display_name = "Dungeon Maps", - .icon = ICON_MD_MAP, .category = "Screen", - .shortcut_hint = "Alt+1", .priority = 10}); - card_registry->RegisterCard({.card_id = "screen.inventory_menu", .display_name = "Inventory Menu", - .icon = ICON_MD_INVENTORY, .category = "Screen", - .shortcut_hint = "Alt+2", .priority = 20}); - card_registry->RegisterCard({.card_id = "screen.overworld_map", .display_name = "Overworld Map", - .icon = ICON_MD_PUBLIC, .category = "Screen", - .shortcut_hint = "Alt+3", .priority = 30}); - card_registry->RegisterCard({.card_id = "screen.title_screen", .display_name = "Title Screen", - .icon = ICON_MD_TITLE, .category = "Screen", - .shortcut_hint = "Alt+4", .priority = 40}); - card_registry->RegisterCard({.card_id = "screen.naming_screen", .display_name = "Naming Screen", - .icon = ICON_MD_EDIT, .category = "Screen", - .shortcut_hint = "Alt+5", .priority = 50}); - + + card_registry->RegisterCard({.card_id = "screen.dungeon_maps", + .display_name = "Dungeon Maps", + .icon = ICON_MD_MAP, + .category = "Screen", + .shortcut_hint = "Alt+1", + .priority = 10}); + card_registry->RegisterCard({.card_id = "screen.inventory_menu", + .display_name = "Inventory Menu", + .icon = ICON_MD_INVENTORY, + .category = "Screen", + .shortcut_hint = "Alt+2", + .priority = 20}); + card_registry->RegisterCard({.card_id = "screen.overworld_map", + .display_name = "Overworld Map", + .icon = ICON_MD_PUBLIC, + .category = "Screen", + .shortcut_hint = "Alt+3", + .priority = 30}); + card_registry->RegisterCard({.card_id = "screen.title_screen", + .display_name = "Title Screen", + .icon = ICON_MD_TITLE, + .category = "Screen", + .shortcut_hint = "Alt+4", + .priority = 40}); + card_registry->RegisterCard({.card_id = "screen.naming_screen", + .display_name = "Naming Screen", + .icon = ICON_MD_EDIT, + .category = "Screen", + .shortcut_hint = "Alt+5", + .priority = 50}); + // Show title screen by default card_registry->ShowCard("screen.title_screen"); } @@ -76,45 +91,48 @@ absl::Status ScreenEditor::Load() { const int tile8_width = 128; const int tile8_height = 128; // 4 sheets × 32 pixels each std::vector tile8_data(tile8_width * tile8_height); - + // Copy data from all 4 sheets into the combined bitmap for (int sheet_idx = 0; sheet_idx < 4; sheet_idx++) { const auto& sheet = sheets_[sheet_idx]; int dest_y_offset = sheet_idx * 32; // Each sheet is 32 pixels tall - + for (int y = 0; y < 32; y++) { for (int x = 0; x < 128; x++) { int src_index = y * 128 + x; int dest_index = (dest_y_offset + y) * 128 + x; - + if (src_index < sheet.size() && dest_index < tile8_data.size()) { tile8_data[dest_index] = sheet.data()[src_index]; } } } } - + // Create tilemap with 8x8 tile size tile8_tilemap_.tile_size = {8, 8}; tile8_tilemap_.map_size = {256, 256}; // Logical size for tile count tile8_tilemap_.atlas.Create(tile8_width, tile8_height, 8, tile8_data); tile8_tilemap_.atlas.SetPalette(*rom()->mutable_dungeon_palette(3)); - + // Queue single texture creation for the atlas (not individual tiles) - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, &tile8_tilemap_.atlas); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + &tile8_tilemap_.atlas); return absl::OkStatus(); } absl::Status ScreenEditor::Update() { - if (!dependencies_.card_registry) return absl::OkStatus(); + if (!dependencies_.card_registry) + return absl::OkStatus(); auto* card_registry = dependencies_.card_registry; static gui::EditorCard dungeon_maps_card("Dungeon Maps", ICON_MD_MAP); - static gui::EditorCard inventory_menu_card("Inventory Menu", ICON_MD_INVENTORY); + static gui::EditorCard inventory_menu_card("Inventory Menu", + ICON_MD_INVENTORY); static gui::EditorCard overworld_map_card("Overworld Map", ICON_MD_PUBLIC); static gui::EditorCard title_screen_card("Title Screen", ICON_MD_TITLE); - static gui::EditorCard naming_screen_card("Naming Screen", ICON_MD_EDIT_ATTRIBUTES); + static gui::EditorCard naming_screen_card("Naming Screen", + ICON_MD_EDIT_ATTRIBUTES); dungeon_maps_card.SetDefaultSize(800, 600); inventory_menu_card.SetDefaultSize(800, 600); @@ -123,43 +141,48 @@ absl::Status ScreenEditor::Update() { naming_screen_card.SetDefaultSize(500, 400); // Dungeon Maps Card - Check visibility flag exists and is true before rendering - bool* dungeon_maps_visible = card_registry->GetVisibilityFlag("screen.dungeon_maps"); + bool* dungeon_maps_visible = + card_registry->GetVisibilityFlag("screen.dungeon_maps"); if (dungeon_maps_visible && *dungeon_maps_visible) { if (dungeon_maps_card.Begin(dungeon_maps_visible)) { DrawDungeonMapsEditor(); } dungeon_maps_card.End(); } - + // Inventory Menu Card - Check visibility flag exists and is true before rendering - bool* inventory_menu_visible = card_registry->GetVisibilityFlag("screen.inventory_menu"); + bool* inventory_menu_visible = + card_registry->GetVisibilityFlag("screen.inventory_menu"); if (inventory_menu_visible && *inventory_menu_visible) { if (inventory_menu_card.Begin(inventory_menu_visible)) { DrawInventoryMenuEditor(); } inventory_menu_card.End(); } - + // Overworld Map Card - Check visibility flag exists and is true before rendering - bool* overworld_map_visible = card_registry->GetVisibilityFlag("screen.overworld_map"); + bool* overworld_map_visible = + card_registry->GetVisibilityFlag("screen.overworld_map"); if (overworld_map_visible && *overworld_map_visible) { if (overworld_map_card.Begin(overworld_map_visible)) { DrawOverworldMapEditor(); } overworld_map_card.End(); } - + // Title Screen Card - Check visibility flag exists and is true before rendering - bool* title_screen_visible = card_registry->GetVisibilityFlag("screen.title_screen"); + bool* title_screen_visible = + card_registry->GetVisibilityFlag("screen.title_screen"); if (title_screen_visible && *title_screen_visible) { if (title_screen_card.Begin(title_screen_visible)) { DrawTitleScreenEditor(); } title_screen_card.End(); } - + // Naming Screen Card - Check visibility flag exists and is true before rendering - bool* naming_screen_visible = card_registry->GetVisibilityFlag("screen.naming_screen"); + bool* naming_screen_visible = + card_registry->GetVisibilityFlag("screen.naming_screen"); if (naming_screen_visible && *naming_screen_visible) { if (naming_screen_card.Begin(naming_screen_visible)) { DrawNamingScreenEditor(); @@ -176,58 +199,58 @@ void ScreenEditor::DrawToolset() { } void ScreenEditor::DrawInventoryMenuEditor() { - static bool create = false; - if (!create && rom()->is_loaded()) { - status_ = inventory_.Create(rom()); - if (status_.ok()) { - palette_ = inventory_.palette(); - create = true; - } else { - ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading inventory: %s", - status_.message().data()); - return; - } + static bool create = false; + if (!create && rom()->is_loaded()) { + status_ = inventory_.Create(rom()); + if (status_.ok()) { + palette_ = inventory_.palette(); + create = true; + } else { + ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error loading inventory: %s", + status_.message().data()); + return; } + } - DrawInventoryToolset(); + DrawInventoryToolset(); - if (ImGui::BeginTable("InventoryScreen", 4, ImGuiTableFlags_Resizable)) { - ImGui::TableSetupColumn("Canvas"); - ImGui::TableSetupColumn("Tilesheet"); - ImGui::TableSetupColumn("Item Icons"); - ImGui::TableSetupColumn("Palette"); - ImGui::TableHeadersRow(); + if (ImGui::BeginTable("InventoryScreen", 4, ImGuiTableFlags_Resizable)) { + ImGui::TableSetupColumn("Canvas"); + ImGui::TableSetupColumn("Tilesheet"); + ImGui::TableSetupColumn("Item Icons"); + ImGui::TableSetupColumn("Palette"); + ImGui::TableHeadersRow(); - ImGui::TableNextColumn(); - screen_canvas_.DrawBackground(); - screen_canvas_.DrawContextMenu(); - screen_canvas_.DrawBitmap(inventory_.bitmap(), 2, create); - screen_canvas_.DrawGrid(32.0f); - screen_canvas_.DrawOverlay(); + ImGui::TableNextColumn(); + screen_canvas_.DrawBackground(); + screen_canvas_.DrawContextMenu(); + screen_canvas_.DrawBitmap(inventory_.bitmap(), 2, create); + screen_canvas_.DrawGrid(32.0f); + screen_canvas_.DrawOverlay(); - ImGui::TableNextColumn(); - tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4)); - tilesheet_canvas_.DrawContextMenu(); - tilesheet_canvas_.DrawBitmap(inventory_.tilesheet(), 2, create); - tilesheet_canvas_.DrawGrid(16.0f); - tilesheet_canvas_.DrawOverlay(); + ImGui::TableNextColumn(); + tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4)); + tilesheet_canvas_.DrawContextMenu(); + tilesheet_canvas_.DrawBitmap(inventory_.tilesheet(), 2, create); + tilesheet_canvas_.DrawGrid(16.0f); + tilesheet_canvas_.DrawOverlay(); - ImGui::TableNextColumn(); - DrawInventoryItemIcons(); + ImGui::TableNextColumn(); + DrawInventoryItemIcons(); - ImGui::TableNextColumn(); - gui::DisplayPalette(palette_, create); + ImGui::TableNextColumn(); + gui::DisplayPalette(palette_, create); - ImGui::EndTable(); - } - ImGui::Separator(); + ImGui::EndTable(); + } + ImGui::Separator(); - // TODO(scawful): Future Oracle of Secrets menu editor integration - // - Full inventory screen layout editor - // - Item slot assignment and positioning - // - Heart container and magic meter editor - // - Equipment display customization - // - A/B button equipment quick-select editor + // TODO(scawful): Future Oracle of Secrets menu editor integration + // - Full inventory screen layout editor + // - Item slot assignment and positioning + // - Heart container and magic meter editor + // - Equipment display customization + // - A/B button equipment quick-select editor } void ScreenEditor::DrawInventoryToolset() { @@ -283,8 +306,9 @@ void ScreenEditor::DrawInventoryItemIcons() { auto& icons = inventory_.item_icons(); if (icons.empty()) { - ImGui::TextWrapped("No item icons loaded. Icons will be loaded when the " - "inventory is initialized."); + ImGui::TextWrapped( + "No item icons loaded. Icons will be loaded when the " + "inventory is initialized."); ImGui::EndChild(); return; } @@ -366,11 +390,12 @@ void ScreenEditor::DrawDungeonMapScreen(int i) { const int tiles_per_row = tile16_blockset_.atlas.width() / 16; const int tile_x = (tile16_id % tiles_per_row) * 16; const int tile_y = (tile16_id / tiles_per_row) * 16; - + std::vector tile_data(16 * 16); int tile_data_offset = 0; - tile16_blockset_.atlas.Get16x16Tile(tile_x, tile_y, tile_data, tile_data_offset); - + tile16_blockset_.atlas.Get16x16Tile(tile_x, tile_y, tile_data, + tile_data_offset); + // Create or update cached tile auto* cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id); if (!cached_tile) { @@ -383,7 +408,7 @@ void ScreenEditor::DrawDungeonMapScreen(int i) { // Update existing cached tile data cached_tile->set_data(tile_data); } - + if (cached_tile && cached_tile->is_active()) { // Ensure the cached tile has a valid texture if (!cached_tile->texture()) { @@ -535,17 +560,18 @@ void ScreenEditor::DrawDungeonMapsRoomGfx() { ImGui::Separator(); current_tile_canvas_.DrawBackground(); // ImVec2(64 * 2 + 2, 64 * 2 + 4)); current_tile_canvas_.DrawContextMenu(); - + // Get tile8 from cache on-demand (only create texture when needed) if (selected_tile8_ >= 0 && selected_tile8_ < 256) { auto* cached_tile8 = tile8_tilemap_.tile_cache.GetTile(selected_tile8_); - + if (!cached_tile8) { // Extract tile from atlas and cache it - const int tiles_per_row = tile8_tilemap_.atlas.width() / 8; // 128 / 8 = 16 + const int tiles_per_row = + tile8_tilemap_.atlas.width() / 8; // 128 / 8 = 16 const int tile_x = (selected_tile8_ % tiles_per_row) * 8; const int tile_y = (selected_tile8_ / tiles_per_row) * 8; - + // Extract 8x8 tile data from atlas std::vector tile_data(64); for (int py = 0; py < 8; py++) { @@ -554,26 +580,27 @@ void ScreenEditor::DrawDungeonMapsRoomGfx() { int src_y = tile_y + py; int src_index = src_y * tile8_tilemap_.atlas.width() + src_x; int dst_index = py * 8 + px; - + if (src_index < tile8_tilemap_.atlas.size() && dst_index < 64) { tile_data[dst_index] = tile8_tilemap_.atlas.data()[src_index]; } } } - + gfx::Bitmap new_tile8(8, 8, 8, tile_data); new_tile8.SetPalette(tile8_tilemap_.atlas.palette()); - tile8_tilemap_.tile_cache.CacheTile(selected_tile8_, std::move(new_tile8)); + tile8_tilemap_.tile_cache.CacheTile(selected_tile8_, + std::move(new_tile8)); cached_tile8 = tile8_tilemap_.tile_cache.GetTile(selected_tile8_); } - + if (cached_tile8 && cached_tile8->is_active()) { // Create texture on-demand only when needed if (!cached_tile8->texture()) { gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, cached_tile8); } - + if (current_tile_canvas_.DrawTilePainter(*cached_tile8, 16)) { // Modify the tile16 based on the selected tile and current_tile16_info gfx::ModifyTile16(tile16_blockset_, rom()->graphics_buffer(), @@ -775,13 +802,14 @@ void ScreenEditor::DrawTitleScreenEditor() { ImGui::Checkbox("Show BG1", &show_title_bg1_); ImGui::SameLine(); ImGui::Checkbox("Show BG2", &show_title_bg2_); - + // Re-render composite if visibility changed if (prev_bg1 != show_title_bg1_ || prev_bg2 != show_title_bg2_) { - status_ = title_screen_.RenderCompositeLayer(show_title_bg1_, show_title_bg2_); + status_ = + title_screen_.RenderCompositeLayer(show_title_bg1_, show_title_bg2_); if (status_.ok()) { gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, + gfx::Arena::TextureCommandType::UPDATE, &title_screen_.composite_bitmap()); } } @@ -822,27 +850,30 @@ void ScreenEditor::DrawTitleScreenCompositeCanvas() { auto click_pos = title_bg1_canvas_.points().front(); int tile_x = static_cast(click_pos.x) / 8; int tile_y = static_cast(click_pos.y) / 8; - + if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) { int tilemap_index = tile_y * 32 + tile_x; - + // Create tile word: tile_id | (palette << 10) | h_flip | v_flip uint16_t tile_word = selected_title_tile16_ & 0x3FF; tile_word |= (title_palette_ & 0x07) << 10; - if (title_h_flip_) tile_word |= 0x4000; - if (title_v_flip_) tile_word |= 0x8000; - + if (title_h_flip_) + tile_word |= 0x4000; + if (title_v_flip_) + tile_word |= 0x8000; + // Update BG1 buffer and re-render both layers and composite title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word; status_ = title_screen_.RenderBG1Layer(); if (status_.ok()) { // Update BG1 texture gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, + gfx::Arena::TextureCommandType::UPDATE, &title_screen_.bg1_bitmap()); - + // Re-render and update composite - status_ = title_screen_.RenderCompositeLayer(show_title_bg1_, show_title_bg2_); + status_ = title_screen_.RenderCompositeLayer(show_title_bg1_, + show_title_bg2_); if (status_.ok()) { gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::UPDATE, &composite_bitmap); @@ -874,16 +905,18 @@ void ScreenEditor::DrawTitleScreenBG1Canvas() { auto click_pos = title_bg1_canvas_.points().front(); int tile_x = static_cast(click_pos.x) / 8; int tile_y = static_cast(click_pos.y) / 8; - + if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) { int tilemap_index = tile_y * 32 + tile_x; - + // Create tile word: tile_id | (palette << 10) | h_flip | v_flip uint16_t tile_word = selected_title_tile16_ & 0x3FF; tile_word |= (title_palette_ & 0x07) << 10; - if (title_h_flip_) tile_word |= 0x4000; - if (title_v_flip_) tile_word |= 0x8000; - + if (title_h_flip_) + tile_word |= 0x4000; + if (title_v_flip_) + tile_word |= 0x8000; + // Update buffer and re-render title_screen_.mutable_bg1_buffer()[tilemap_index] = tile_word; status_ = title_screen_.RenderBG1Layer(); @@ -917,16 +950,18 @@ void ScreenEditor::DrawTitleScreenBG2Canvas() { auto click_pos = title_bg2_canvas_.points().front(); int tile_x = static_cast(click_pos.x) / 8; int tile_y = static_cast(click_pos.y) / 8; - + if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) { int tilemap_index = tile_y * 32 + tile_x; - + // Create tile word: tile_id | (palette << 10) | h_flip | v_flip uint16_t tile_word = selected_title_tile16_ & 0x3FF; tile_word |= (title_palette_ & 0x07) << 10; - if (title_h_flip_) tile_word |= 0x4000; - if (title_v_flip_) tile_word |= 0x8000; - + if (title_h_flip_) + tile_word |= 0x4000; + if (title_v_flip_) + tile_word |= 0x8000; + // Update buffer and re-render title_screen_.mutable_bg2_buffer()[tilemap_index] = tile_word; status_ = title_screen_.RenderBG2Layer(); @@ -971,20 +1006,19 @@ void ScreenEditor::DrawTitleScreenBlocksetSelector() { // Show selected tile preview and controls if (selected_title_tile16_ >= 0) { ImGui::Text("Selected Tile: %d", selected_title_tile16_); - + // Flip controls ImGui::Checkbox("H Flip", &title_h_flip_); ImGui::SameLine(); ImGui::Checkbox("V Flip", &title_v_flip_); - + // Palette selector (0-7 for 3BPP graphics) ImGui::SetNextItemWidth(100); ImGui::SliderInt("Palette", &title_palette_, 0, 7); } } -void ScreenEditor::DrawNamingScreenEditor() { -} +void ScreenEditor::DrawNamingScreenEditor() {} void ScreenEditor::DrawOverworldMapEditor() { // Initialize overworld map on first draw @@ -1015,7 +1049,7 @@ void ScreenEditor::DrawOverworldMapEditor() { } } ImGui::SameLine(); - + // World toggle if (ImGui::Button(ow_show_dark_world_ ? "Dark World" : "Light World")) { ow_show_dark_world_ = !ow_show_dark_world_; @@ -1027,7 +1061,7 @@ void ScreenEditor::DrawOverworldMapEditor() { } } ImGui::SameLine(); - + // Custom map load/save buttons if (ImGui::Button("Load Custom Map...")) { std::string path = util::FileDialogWrapper::ShowOpenFileDialog(); @@ -1048,10 +1082,10 @@ void ScreenEditor::DrawOverworldMapEditor() { } } } - + ImGui::SameLine(); ImGui::Text("Selected Tile: %d", selected_ow_tile_); - + // Custom map error/success popups if (ImGui::BeginPopup("CustomMapLoadError")) { ImGui::Text("Error loading custom map: %s", status_.message().data()); @@ -1093,17 +1127,17 @@ void ScreenEditor::DrawOverworldMapEditor() { auto click_pos = ow_map_canvas_.points().front(); int tile_x = static_cast(click_pos.x) / 8; int tile_y = static_cast(click_pos.y) / 8; - + if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) { int tile_index = tile_x + (tile_y * 64); - + // Update appropriate world's tile data if (ow_show_dark_world_) { ow_map_screen_.mutable_dw_tiles()[tile_index] = selected_ow_tile_; } else { ow_map_screen_.mutable_lw_tiles()[tile_index] = selected_ow_tile_; } - + // Re-render map status_ = ow_map_screen_.RenderMapLayer(ow_show_dark_world_); if (status_.ok()) { @@ -1143,7 +1177,7 @@ void ScreenEditor::DrawOverworldMapEditor() { // Column 3: Palette Display ImGui::TableNextColumn(); - auto& palette = ow_show_dark_world_ ? ow_map_screen_.dw_palette() + auto& palette = ow_show_dark_world_ ? ow_map_screen_.dw_palette() : ow_map_screen_.lw_palette(); // Use inline palette editor for full 128-color palette gui::InlinePaletteEditor(palette, "Overworld Map Palette"); diff --git a/src/app/editor/graphics/screen_editor.h b/src/app/editor/graphics/screen_editor.h index 5dcbec53..71875a2d 100644 --- a/src/app/editor/graphics/screen_editor.h +++ b/src/app/editor/graphics/screen_editor.h @@ -6,16 +6,16 @@ #include "absl/status/status.h" #include "app/editor/editor.h" #include "app/gfx/core/bitmap.h" -#include "app/gfx/types/snes_palette.h" #include "app/gfx/render/tilemap.h" +#include "app/gfx/types/snes_palette.h" +#include "app/gui/app/editor_layout.h" #include "app/gui/canvas/canvas.h" #include "app/rom.h" +#include "imgui/imgui.h" #include "zelda3/screen/dungeon_map.h" #include "zelda3/screen/inventory.h" -#include "zelda3/screen/title_screen.h" #include "zelda3/screen/overworld_map_screen.h" -#include "app/gui/app/editor_layout.h" -#include "imgui/imgui.h" +#include "zelda3/screen/title_screen.h" namespace yaze { namespace editor { @@ -120,8 +120,7 @@ class ScreenEditor : public Editor { gui::Canvas title_bg2_canvas_{"##TitleBG2Canvas", ImVec2(256, 256), gui::CanvasGridSize::k8x8, 2.0f}; // Blockset is 128 pixels wide x 512 pixels tall (16x64 8x8 tiles) - gui::Canvas title_blockset_canvas_{"##TitleBlocksetCanvas", - ImVec2(128, 512), + gui::Canvas title_blockset_canvas_{"##TitleBlocksetCanvas", ImVec2(128, 512), gui::CanvasGridSize::k8x8, 2.0f}; zelda3::Inventory inventory_; diff --git a/src/app/editor/message/message_data.cc b/src/app/editor/message/message_data.cc index ade53419..88ff013f 100644 --- a/src/app/editor/message/message_data.cc +++ b/src/app/editor/message/message_data.cc @@ -97,7 +97,7 @@ std::string ParseTextDataByte(uint8_t value) { // Check for dictionary. int8_t dictionary = FindDictionaryEntry(value); if (dictionary >= 0) { - return absl::StrFormat("[%s:%02X]", DICTIONARYTOKEN, + return absl::StrFormat("[%s:%02X]", DICTIONARYTOKEN, static_cast(dictionary)); } @@ -181,8 +181,8 @@ std::vector BuildDictionaryEntries(Rom* rom) { return AllDictionaries; } -std::string ReplaceAllDictionaryWords(std::string str, - const std::vector& dictionary) { +std::string ReplaceAllDictionaryWords( + std::string str, const std::vector& dictionary) { std::string temp = std::move(str); for (const auto& entry : dictionary) { if (entry.ContainedInString(temp)) { @@ -251,7 +251,8 @@ absl::StatusOr ParseSingleMessage( current_message_raw.append("["); current_message_raw.append(DICTIONARYTOKEN); current_message_raw.append(":"); - current_message_raw.append(util::HexWord(static_cast(dictionary))); + current_message_raw.append( + util::HexWord(static_cast(dictionary))); current_message_raw.append("]"); auto mutable_rom_data = const_cast(rom_data.data()); @@ -292,13 +293,13 @@ std::vector ParseMessageData( // Use index-based loop to properly skip argument bytes for (size_t pos = 0; pos < message.Data.size(); ++pos) { uint8_t byte = message.Data[pos]; - + // Check for text commands first (they may have arguments to skip) auto text_element = FindMatchingCommand(byte); if (text_element != std::nullopt) { // Add newline for certain commands - if (text_element->ID == kScrollVertical || - text_element->ID == kLine2 || text_element->ID == kLine3) { + if (text_element->ID == kScrollVertical || text_element->ID == kLine2 || + text_element->ID == kLine3) { parsed_message.append("\n"); } // If command has an argument, get it from next byte and skip it @@ -311,14 +312,14 @@ std::vector ParseMessageData( } continue; // Move to next byte } - + // Check for special characters auto special_element = FindMatchingSpecial(byte); if (special_element != std::nullopt) { parsed_message.append(special_element->GetParamToken()); continue; } - + // Check for dictionary entries if (byte >= DICTOFF && byte < (DICTOFF + 97)) { DictionaryEntry dic_entry; @@ -331,7 +332,7 @@ std::vector ParseMessageData( parsed_message.append(dic_entry.Contents); continue; } - + // Finally check for regular characters if (CharEncoder.contains(byte)) { parsed_message.push_back(CharEncoder.at(byte)); @@ -401,8 +402,9 @@ std::vector ReadAllTextData(uint8_t* rom, int pos) { // Check for dictionary. int8_t dictionary = FindDictionaryEntry(current_byte); if (dictionary >= 0) { - current_raw_message.append(absl::StrFormat("[%s:%s]", DICTIONARYTOKEN, - util::HexByte(static_cast(dictionary)))); + current_raw_message.append(absl::StrFormat( + "[%s:%s]", DICTIONARYTOKEN, + util::HexByte(static_cast(dictionary)))); uint32_t address = Get24LocalFromPC(rom, kPointersDictionaries + (dictionary * 2)); diff --git a/src/app/editor/message/message_data.h b/src/app/editor/message/message_data.h index 09c2bb58..892aca44 100644 --- a/src/app/editor/message/message_data.h +++ b/src/app/editor/message/message_data.h @@ -42,7 +42,7 @@ // ## Data Flow // // ### Reading from ROM: -// ROM bytes → ReadAllTextData() → MessageData (raw) → ParseMessageData() → +// ROM bytes → ReadAllTextData() → MessageData (raw) → ParseMessageData() → // Human-readable string with [command] tokens // // ### Writing to ROM: @@ -64,7 +64,7 @@ // // Messages are stored as byte sequences terminated by 0x7F: // Example: [0x00, 0x01, 0x02, 0x7F] = "ABC" -// Example: [0x6A, 0x59, 0x2C, 0x61, 0x32, 0x28, 0x2B, 0x23, 0x7F] +// Example: [0x6A, 0x59, 0x2C, 0x61, 0x32, 0x28, 0x2B, 0x23, 0x7F] // = "[L] saved Hyrule" (0x6A = player name command) // // ## Token Syntax (Human-Readable Format) @@ -81,13 +81,13 @@ #include #include #include +#include #include #include -#include +#include "absl/strings/match.h" #include "absl/strings/str_format.h" #include "absl/strings/str_replace.h" -#include "absl/strings/match.h" #include "app/rom.h" namespace yaze { @@ -137,11 +137,11 @@ std::vector ParseMessageToData(std::string str); // Dictionary entries are stored separately in ROM and referenced by bytes 0x88-0xE8 // Example: Dictionary entry 0x00 might contain "the" and be referenced as [D:00] struct DictionaryEntry { - uint8_t ID = 0; // Dictionary index (0-96) - std::string Contents = ""; // The actual text this entry represents - std::vector Data; // Binary representation of Contents - int Length = 0; // Character count - std::string Token = ""; // Human-readable token like "[D:00]" + uint8_t ID = 0; // Dictionary index (0-96) + std::string Contents = ""; // The actual text this entry represents + std::vector Data; // Binary representation of Contents + int Length = 0; // Character count + std::string Token = ""; // Human-readable token like "[D:00]" DictionaryEntry() = default; DictionaryEntry(uint8_t i, std::string_view s) @@ -183,8 +183,8 @@ std::vector BuildDictionaryEntries(Rom* rom); // Replaces all dictionary words in a string with their [D:XX] tokens // Used for text compression when saving messages back to ROM -std::string ReplaceAllDictionaryWords(std::string str, - const std::vector& dictionary); +std::string ReplaceAllDictionaryWords( + std::string str, const std::vector& dictionary); // Looks up a dictionary entry by its ROM byte value DictionaryEntry FindRealDictionaryEntry( @@ -199,12 +199,12 @@ const std::string CHEESE = "\uBEBE"; // 1. Raw: Direct ROM bytes with dictionary references as [D:XX] tokens // 2. Parsed: Fully expanded with dictionary words replaced by actual text struct MessageData { - int ID = 0; // Message index in the ROM - int Address = 0; // ROM address where this message is stored - std::string RawString; // Human-readable with [D:XX] dictionary tokens - std::string ContentsParsed; // Fully expanded human-readable text - std::vector Data; // Raw ROM bytes (may contain dict references) - std::vector DataParsed; // Expanded bytes (dict entries expanded) + int ID = 0; // Message index in the ROM + int Address = 0; // ROM address where this message is stored + std::string RawString; // Human-readable with [D:XX] dictionary tokens + std::string ContentsParsed; // Fully expanded human-readable text + std::vector Data; // Raw ROM bytes (may contain dict references) + std::vector DataParsed; // Expanded bytes (dict entries expanded) MessageData() = default; MessageData(int id, int address, const std::string& rawString, @@ -410,9 +410,9 @@ std::optional FindMatchingSpecial(uint8_t b); // Result of parsing a text token like "[W:02]" // Contains both the command definition and its argument value struct ParsedElement { - TextElement Parent; // The command or special character definition - uint8_t Value; // Argument value (if command has argument) - bool Active = false; // True if parsing was successful + TextElement Parent; // The command or special character definition + uint8_t Value; // Argument value (if command has argument) + bool Active = false; // True if parsing was successful ParsedElement() = default; ParsedElement(const TextElement& textElement, uint8_t value) diff --git a/src/app/editor/message/message_editor.cc b/src/app/editor/message/message_editor.cc index 03cea448..1d079a28 100644 --- a/src/app/editor/message/message_editor.cc +++ b/src/app/editor/message/message_editor.cc @@ -7,19 +7,19 @@ #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" -#include "app/gfx/resource/arena.h" -#include "app/gfx/debug/performance/performance_profiler.h" -#include "util/file_util.h" #include "app/gfx/core/bitmap.h" +#include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" #include "app/gfx/types/snes_tile.h" #include "app/gui/canvas/canvas.h" -#include "app/gui/core/style.h" #include "app/gui/core/icons.h" -#include "app/rom.h" #include "app/gui/core/input.h" +#include "app/gui/core/style.h" +#include "app/rom.h" #include "imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" +#include "util/file_util.h" #include "util/hex.h" #include "util/log.h" @@ -41,7 +41,6 @@ std::string DisplayTextOverflowError(int pos, bool bank) { } } // namespace - using ImGui::BeginChild; using ImGui::BeginTable; using ImGui::Button; @@ -64,45 +63,38 @@ constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable | void MessageEditor::Initialize() { // Register cards with EditorCardRegistry (dependency injection) - if (!dependencies_.card_registry) return; - + if (!dependencies_.card_registry) + return; + auto* card_registry = dependencies_.card_registry; - - card_registry->RegisterCard({ - .card_id = MakeCardId("message.message_list"), - .display_name = "Message List", - .icon = ICON_MD_LIST, - .category = "Message", - .priority = 10 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("message.message_editor"), - .display_name = "Message Editor", - .icon = ICON_MD_EDIT, - .category = "Message", - .priority = 20 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("message.font_atlas"), - .display_name = "Font Atlas", - .icon = ICON_MD_FONT_DOWNLOAD, - .category = "Message", - .priority = 30 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("message.dictionary"), - .display_name = "Dictionary", - .icon = ICON_MD_BOOK, - .category = "Message", - .priority = 40 - }); - + + card_registry->RegisterCard({.card_id = MakeCardId("message.message_list"), + .display_name = "Message List", + .icon = ICON_MD_LIST, + .category = "Message", + .priority = 10}); + + card_registry->RegisterCard({.card_id = MakeCardId("message.message_editor"), + .display_name = "Message Editor", + .icon = ICON_MD_EDIT, + .category = "Message", + .priority = 20}); + + card_registry->RegisterCard({.card_id = MakeCardId("message.font_atlas"), + .display_name = "Font Atlas", + .icon = ICON_MD_FONT_DOWNLOAD, + .category = "Message", + .priority = 30}); + + card_registry->RegisterCard({.card_id = MakeCardId("message.dictionary"), + .display_name = "Dictionary", + .icon = ICON_MD_BOOK, + .category = "Message", + .priority = 40}); + // Show message list by default card_registry->ShowCard(MakeCardId("message.message_list")); - + for (int i = 0; i < kWidthArraySize; i++) { message_preview_.width_array[i] = rom()->data()[kCharactersWidth + i]; } @@ -116,16 +108,17 @@ void MessageEditor::Initialize() { } message_preview_.font_gfx16_data_ = gfx::SnesTo8bppSheet(raw_font_gfx_data_, /*bpp=*/2, /*num_sheets=*/2); - + // Create bitmap for font graphics - font_gfx_bitmap_.Create(kFontGfxMessageSize, kFontGfxMessageSize, - kFontGfxMessageDepth, message_preview_.font_gfx16_data_); + font_gfx_bitmap_.Create(kFontGfxMessageSize, kFontGfxMessageSize, + kFontGfxMessageDepth, + message_preview_.font_gfx16_data_); font_gfx_bitmap_.SetPalette(font_preview_colors_); - + // Queue texture creation - will be processed in render loop - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, &font_gfx_bitmap_); - + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + &font_gfx_bitmap_); + LOG_INFO("MessageEditor", "Font bitmap created and texture queued"); *current_font_gfx16_bitmap_.mutable_palette() = font_preview_colors_; @@ -140,18 +133,20 @@ void MessageEditor::Initialize() { DrawMessagePreview(); } -absl::Status MessageEditor::Load() { +absl::Status MessageEditor::Load() { gfx::ScopedTimer timer("MessageEditor::Load"); - return absl::OkStatus(); + return absl::OkStatus(); } absl::Status MessageEditor::Update() { - if (!dependencies_.card_registry) return absl::OkStatus(); - + if (!dependencies_.card_registry) + return absl::OkStatus(); + auto* card_registry = dependencies_.card_registry; - + // Message List Card - Get visibility flag and pass to Begin() for proper X button - bool* list_visible = card_registry->GetVisibilityFlag(MakeCardId("message.message_list")); + bool* list_visible = + card_registry->GetVisibilityFlag(MakeCardId("message.message_list")); if (list_visible && *list_visible) { static gui::EditorCard list_card("Message List", ICON_MD_LIST); list_card.SetDefaultSize(400, 600); @@ -160,9 +155,10 @@ absl::Status MessageEditor::Update() { } list_card.End(); } - + // Message Editor Card - Get visibility flag and pass to Begin() for proper X button - bool* editor_visible = card_registry->GetVisibilityFlag(MakeCardId("message.message_editor")); + bool* editor_visible = + card_registry->GetVisibilityFlag(MakeCardId("message.message_editor")); if (editor_visible && *editor_visible) { static gui::EditorCard editor_card("Message Editor", ICON_MD_EDIT); editor_card.SetDefaultSize(500, 600); @@ -171,9 +167,10 @@ absl::Status MessageEditor::Update() { } editor_card.End(); } - + // Font Atlas Card - Get visibility flag and pass to Begin() for proper X button - bool* font_visible = card_registry->GetVisibilityFlag(MakeCardId("message.font_atlas")); + bool* font_visible = + card_registry->GetVisibilityFlag(MakeCardId("message.font_atlas")); if (font_visible && *font_visible) { static gui::EditorCard font_card("Font Atlas", ICON_MD_FONT_DOWNLOAD); font_card.SetDefaultSize(400, 500); @@ -183,9 +180,10 @@ absl::Status MessageEditor::Update() { } font_card.End(); } - + // Dictionary Card - Get visibility flag and pass to Begin() for proper X button - bool* dict_visible = card_registry->GetVisibilityFlag(MakeCardId("message.dictionary")); + bool* dict_visible = + card_registry->GetVisibilityFlag(MakeCardId("message.dictionary")); if (dict_visible && *dict_visible) { static gui::EditorCard dict_card("Dictionary", ICON_MD_BOOK); dict_card.SetDefaultSize(400, 500); @@ -196,7 +194,7 @@ absl::Status MessageEditor::Update() { } dict_card.End(); } - + return absl::OkStatus(); } @@ -405,39 +403,44 @@ void MessageEditor::DrawDictionary() { void MessageEditor::DrawMessagePreview() { // Render the message to the preview bitmap message_preview_.DrawMessagePreview(current_message_); - + // Validate preview data before updating if (message_preview_.current_preview_data_.empty()) { LOG_WARN("MessageEditor", "Preview data is empty, skipping bitmap update"); return; } - + if (current_font_gfx16_bitmap_.is_active()) { // CRITICAL: Use set_data() to properly update both data_ AND surface_ // mutable_data() returns a reference but doesn't update the surface! current_font_gfx16_bitmap_.set_data(message_preview_.current_preview_data_); - + // Validate surface was updated if (!current_font_gfx16_bitmap_.surface()) { LOG_ERROR("MessageEditor", "Bitmap surface is null after set_data()"); return; } - + // Queue texture update so changes are visible immediately gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::UPDATE, ¤t_font_gfx16_bitmap_); - - LOG_DEBUG("MessageEditor", "Updated message preview bitmap (size: %zu) and queued texture update", - message_preview_.current_preview_data_.size()); + + LOG_DEBUG( + "MessageEditor", + "Updated message preview bitmap (size: %zu) and queued texture update", + message_preview_.current_preview_data_.size()); } else { // Create bitmap and queue texture creation with 8-bit indexed depth - current_font_gfx16_bitmap_.Create(kCurrentMessageWidth, kCurrentMessageHeight, - 8, message_preview_.current_preview_data_); + current_font_gfx16_bitmap_.Create(kCurrentMessageWidth, + kCurrentMessageHeight, 8, + message_preview_.current_preview_data_); current_font_gfx16_bitmap_.SetPalette(font_preview_colors_); gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::CREATE, ¤t_font_gfx16_bitmap_); - - LOG_INFO("MessageEditor", "Created message preview bitmap (%dx%d) with 8-bit depth and queued texture creation", + + LOG_INFO("MessageEditor", + "Created message preview bitmap (%dx%d) with 8-bit depth and " + "queued texture creation", kCurrentMessageWidth, kCurrentMessageHeight); } } diff --git a/src/app/editor/message/message_editor.h b/src/app/editor/message/message_editor.h index 29172b98..c1cff2b1 100644 --- a/src/app/editor/message/message_editor.h +++ b/src/app/editor/message/message_editor.h @@ -9,8 +9,8 @@ #include "app/editor/editor.h" #include "app/editor/message/message_data.h" #include "app/editor/message/message_preview.h" -#include "app/gui/app/editor_layout.h" #include "app/gfx/core/bitmap.h" +#include "app/gui/app/editor_layout.h" #include "app/gui/canvas/canvas.h" #include "app/gui/core/style.h" #include "app/rom.h" @@ -22,7 +22,8 @@ constexpr int kGfxFont = 0x70000; // 2bpp format constexpr int kCharactersWidth = 0x74ADF; constexpr int kNumMessages = 396; constexpr int kFontGfxMessageSize = 128; -constexpr int kFontGfxMessageDepth = 8; // Fixed: Must be 8 for indexed palette mode +constexpr int kFontGfxMessageDepth = + 8; // Fixed: Must be 8 for indexed palette mode constexpr int kFontGfx16Size = 172 * 4096; constexpr uint8_t kBlockTerminator = 0x80; @@ -90,7 +91,7 @@ class MessageEditor : public Editor { gui::TextBox message_text_box_; Rom* rom_; Rom expanded_message_bin_; - + // Card visibility states bool show_message_list_ = false; bool show_message_editor_ = false; diff --git a/src/app/editor/music/music_editor.cc b/src/app/editor/music/music_editor.cc index c41e943f..91a0047c 100644 --- a/src/app/editor/music/music_editor.cc +++ b/src/app/editor/music/music_editor.cc @@ -2,9 +2,9 @@ #include "app/editor/system/editor_card_registry.h" #include "absl/strings/str_format.h" -#include "app/gfx/debug/performance/performance_profiler.h" #include "app/editor/code/assembly_editor.h" #include "app/emu/emulator.h" +#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" #include "imgui/imgui.h" @@ -14,19 +14,29 @@ namespace yaze { namespace editor { void MusicEditor::Initialize() { - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - - card_registry->RegisterCard({.card_id = "music.tracker", .display_name = "Music Tracker", - .icon = ICON_MD_MUSIC_NOTE, .category = "Music", - .shortcut_hint = "Ctrl+Shift+M", .priority = 10}); - card_registry->RegisterCard({.card_id = "music.instrument_editor", .display_name = "Instrument Editor", - .icon = ICON_MD_PIANO, .category = "Music", - .shortcut_hint = "Ctrl+Shift+I", .priority = 20}); - card_registry->RegisterCard({.card_id = "music.assembly", .display_name = "Assembly View", - .icon = ICON_MD_CODE, .category = "Music", - .shortcut_hint = "Ctrl+Shift+A", .priority = 30}); - + + card_registry->RegisterCard({.card_id = "music.tracker", + .display_name = "Music Tracker", + .icon = ICON_MD_MUSIC_NOTE, + .category = "Music", + .shortcut_hint = "Ctrl+Shift+M", + .priority = 10}); + card_registry->RegisterCard({.card_id = "music.instrument_editor", + .display_name = "Instrument Editor", + .icon = ICON_MD_PIANO, + .category = "Music", + .shortcut_hint = "Ctrl+Shift+I", + .priority = 20}); + card_registry->RegisterCard({.card_id = "music.assembly", + .display_name = "Assembly View", + .icon = ICON_MD_CODE, + .category = "Music", + .shortcut_hint = "Ctrl+Shift+A", + .priority = 30}); + // Show tracker by default card_registry->ShowCard("music.tracker"); } @@ -37,17 +47,18 @@ absl::Status MusicEditor::Load() { } absl::Status MusicEditor::Update() { - if (!dependencies_.card_registry) return absl::OkStatus(); + if (!dependencies_.card_registry) + return absl::OkStatus(); auto* card_registry = dependencies_.card_registry; - + static gui::EditorCard tracker_card("Music Tracker", ICON_MD_MUSIC_NOTE); static gui::EditorCard instrument_card("Instrument Editor", ICON_MD_PIANO); static gui::EditorCard assembly_card("Assembly View", ICON_MD_CODE); - + tracker_card.SetDefaultSize(900, 700); instrument_card.SetDefaultSize(600, 500); assembly_card.SetDefaultSize(700, 600); - + // Music Tracker Card - Check visibility flag exists and is true before rendering bool* tracker_visible = card_registry->GetVisibilityFlag("music.tracker"); if (tracker_visible && *tracker_visible) { @@ -56,16 +67,17 @@ absl::Status MusicEditor::Update() { } tracker_card.End(); } - + // Instrument Editor Card - Check visibility flag exists and is true before rendering - bool* instrument_visible = card_registry->GetVisibilityFlag("music.instrument_editor"); + bool* instrument_visible = + card_registry->GetVisibilityFlag("music.instrument_editor"); if (instrument_visible && *instrument_visible) { if (instrument_card.Begin(instrument_visible)) { DrawInstrumentEditor(); } instrument_card.End(); } - + // Assembly View Card - Check visibility flag exists and is true before rendering bool* assembly_visible = card_registry->GetVisibilityFlag("music.assembly"); if (assembly_visible && *assembly_visible) { @@ -108,7 +120,8 @@ static void DrawPianoStaff() { // Draw the ledger lines const int NUM_LEDGER_LINES = 3; for (int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) { - if (i % 2 == 0) continue; // skip every other line + if (i % 2 == 0) + continue; // skip every other line auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2); auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x, canvas_p0.y + i * LINE_SPACING / 2); @@ -273,19 +286,19 @@ void MusicEditor::PlaySong(int song_id) { LOG_WARN("MusicEditor", "No emulator instance - cannot play song"); return; } - + if (!emulator_->snes().running()) { LOG_WARN("MusicEditor", "Emulator not running - cannot play song"); return; } - + // Write song request to game memory ($7E012C) // This triggers the NMI handler to send the song to APU try { emulator_->snes().Write(0x7E012C, static_cast(song_id)); - LOG_INFO("MusicEditor", "Requested song %d (%s)", song_id, + LOG_INFO("MusicEditor", "Requested song %d (%s)", song_id, song_id < 30 ? kGameSongs[song_id] : "Unknown"); - + // Ensure audio backend is playing if (auto* audio = emulator_->audio_backend()) { auto status = audio->GetStatus(); @@ -294,7 +307,7 @@ void MusicEditor::PlaySong(int song_id) { LOG_INFO("MusicEditor", "Started audio backend playback"); } } - + is_playing_ = true; } catch (const std::exception& e) { LOG_ERROR("MusicEditor", "Failed to play song: %s", e.what()); @@ -302,18 +315,19 @@ void MusicEditor::PlaySong(int song_id) { } void MusicEditor::StopSong() { - if (!emulator_) return; - + if (!emulator_) + return; + // Write stop command to game memory try { emulator_->snes().Write(0x7E012C, 0xFF); // 0xFF = stop music LOG_INFO("MusicEditor", "Stopped music playback"); - + // Optional: pause audio backend to save CPU if (auto* audio = emulator_->audio_backend()) { audio->Pause(); } - + is_playing_ = false; } catch (const std::exception& e) { LOG_ERROR("MusicEditor", "Failed to stop song: %s", e.what()); @@ -321,11 +335,12 @@ void MusicEditor::StopSong() { } void MusicEditor::SetVolume(float volume) { - if (!emulator_) return; - + if (!emulator_) + return; + // Clamp volume to valid range volume = std::clamp(volume, 0.0f, 1.0f); - + if (auto* audio = emulator_->audio_backend()) { audio->SetVolume(volume); LOG_DEBUG("MusicEditor", "Set volume to %.2f", volume); diff --git a/src/app/editor/music/music_editor.h b/src/app/editor/music/music_editor.h index 76c4d4bc..b716b6fb 100644 --- a/src/app/editor/music/music_editor.h +++ b/src/app/editor/music/music_editor.h @@ -6,8 +6,8 @@ #include "app/emu/audio/apu.h" #include "app/gui/app/editor_layout.h" #include "app/rom.h" -#include "zelda3/music/tracker.h" #include "imgui/imgui.h" +#include "zelda3/music/tracker.h" namespace yaze { @@ -84,11 +84,11 @@ class MusicEditor : public Editor { // Get the ROM pointer Rom* rom() const { return rom_; } - + // Emulator integration for live audio playback void set_emulator(emu::Emulator* emulator) { emulator_ = emulator; } emu::Emulator* emulator() const { return emulator_; } - + // Audio control methods void PlaySong(int song_id); void StopSong(); diff --git a/src/app/editor/overworld/entity.cc b/src/app/editor/overworld/entity.cc index 5a07677c..ae513ef8 100644 --- a/src/app/editor/overworld/entity.cc +++ b/src/app/editor/overworld/entity.cc @@ -21,22 +21,22 @@ using ImGui::Text; constexpr float kInputFieldSize = 30.f; -bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity, +bool IsMouseHoveringOverEntity(const zelda3::GameEntity& entity, ImVec2 canvas_p0, ImVec2 scrolling) { // Get the mouse position relative to the canvas - const ImGuiIO &io = ImGui::GetIO(); + const ImGuiIO& io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); // Check if the mouse is hovering over the entity return mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 && - mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16; + mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16; } -void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, +void MoveEntityOnGrid(zelda3::GameEntity* entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement) { // Get the mouse position relative to the canvas - const ImGuiIO &io = ImGui::GetIO(); + const ImGuiIO& io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); @@ -53,8 +53,6 @@ void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, entity->set_y(new_y); } - - bool DrawEntranceInserterPopup() { bool set_done = false; if (set_done) { @@ -79,28 +77,28 @@ bool DrawEntranceInserterPopup() { return set_done; } -bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance) { +bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance& entrance) { static bool set_done = false; if (set_done) { set_done = false; return true; } - + if (ImGui::BeginPopupModal("Entrance Editor", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Entrance ID: %d", entrance.entrance_id_); ImGui::Separator(); - + gui::InputHexWord("Map ID", &entrance.map_id_); gui::InputHexByte("Entrance ID", &entrance.entrance_id_, kInputFieldSize + 20); gui::InputHex("X Position", &entrance.x_); gui::InputHex("Y Position", &entrance.y_); - + ImGui::Checkbox("Is Hole", &entrance.is_hole_); - + ImGui::Separator(); - + if (Button("Save")) { set_done = true; ImGui::CloseCurrentPopup(); @@ -115,7 +113,7 @@ bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance) { if (Button("Cancel")) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } return set_done; @@ -127,10 +125,10 @@ void DrawExitInserterPopup() { static int room_id = 0; static int x_pos = 0; static int y_pos = 0; - + ImGui::Text("Insert New Exit"); ImGui::Separator(); - + gui::InputHex("Exit ID", &exit_id); gui::InputHex("Room ID", &room_id); gui::InputHex("X Position", &x_pos); @@ -150,7 +148,7 @@ void DrawExitInserterPopup() { } } -bool DrawExitEditorPopup(zelda3::OverworldExit &exit) { +bool DrawExitEditorPopup(zelda3::OverworldExit& exit) { static bool set_done = false; if (set_done) { set_done = false; @@ -208,7 +206,8 @@ bool DrawExitEditorPopup(zelda3::OverworldExit &exit) { if (show_properties) { Text("Deleted? %s", exit.deleted_ ? "true" : "false"); Text("Hole? %s", exit.is_hole_ ? "true" : "false"); - Text("Auto-calc scroll/camera? %s", exit.is_automatic_ ? "true" : "false"); + Text("Auto-calc scroll/camera? %s", + exit.is_automatic_ ? "true" : "false"); Text("Map ID: 0x%02X", exit.map_id_); Text("Game coords: (%d, %d)", exit.game_x_, exit.game_y_); } @@ -317,7 +316,7 @@ void DrawItemInsertPopup() { } // TODO: Implement deleting OverworldItem objects, currently only hides them -bool DrawItemEditorPopup(zelda3::OverworldItem &item) { +bool DrawItemEditorPopup(zelda3::OverworldItem& item) { static bool set_done = false; if (set_done) { set_done = false; @@ -359,7 +358,7 @@ bool DrawItemEditorPopup(zelda3::OverworldItem &item) { return set_done; } -const ImGuiTableSortSpecs *SpriteItem::s_current_sort_specs = nullptr; +const ImGuiTableSortSpecs* SpriteItem::s_current_sort_specs = nullptr; void DrawSpriteTable(std::function onSpriteSelect) { static ImGuiTextFilter filter; @@ -383,7 +382,7 @@ void DrawSpriteTable(std::function onSpriteSelect) { ImGui::TableHeadersRow(); // Handle sorting - if (ImGuiTableSortSpecs *sort_specs = ImGui::TableGetSortSpecs()) { + if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) { if (sort_specs->SpecsDirty) { SpriteItem::SortWithSortSpecs(sort_specs, items); sort_specs->SpecsDirty = false; @@ -391,7 +390,7 @@ void DrawSpriteTable(std::function onSpriteSelect) { } // Display filtered and sorted items - for (const auto &item : items) { + for (const auto& item : items) { if (filter.PassFilter(item.name)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); @@ -414,15 +413,15 @@ void DrawSpriteInserterPopup() { static int new_sprite_id = 0; static int x_pos = 0; static int y_pos = 0; - + ImGui::Text("Add New Sprite"); ImGui::Separator(); - + BeginChild("ScrollRegion", ImVec2(250, 200), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); DrawSpriteTable([](int selected_id) { new_sprite_id = selected_id; }); EndChild(); - + ImGui::Separator(); ImGui::Text("Position:"); gui::InputHex("X Position", &x_pos); @@ -445,7 +444,7 @@ void DrawSpriteInserterPopup() { } } -bool DrawSpriteEditorPopup(zelda3::Sprite &sprite) { +bool DrawSpriteEditorPopup(zelda3::Sprite& sprite) { static bool set_done = false; if (set_done) { set_done = false; diff --git a/src/app/editor/overworld/entity.h b/src/app/editor/overworld/entity.h index 7d051f6e..c9b8581c 100644 --- a/src/app/editor/overworld/entity.h +++ b/src/app/editor/overworld/entity.h @@ -1,33 +1,31 @@ #ifndef YAZE_APP_EDITOR_OVERWORLD_ENTITY_H #define YAZE_APP_EDITOR_OVERWORLD_ENTITY_H +#include "imgui/imgui.h" #include "zelda3/common.h" #include "zelda3/overworld/overworld_entrance.h" #include "zelda3/overworld/overworld_exit.h" #include "zelda3/overworld/overworld_item.h" #include "zelda3/sprite/sprite.h" -#include "imgui/imgui.h" namespace yaze { namespace editor { -bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity, +bool IsMouseHoveringOverEntity(const zelda3::GameEntity& entity, ImVec2 canvas_p0, ImVec2 scrolling); -void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, +void MoveEntityOnGrid(zelda3::GameEntity* entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement = false); - - bool DrawEntranceInserterPopup(); -bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance); +bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance& entrance); void DrawExitInserterPopup(); -bool DrawExitEditorPopup(zelda3::OverworldExit &exit); +bool DrawExitEditorPopup(zelda3::OverworldExit& exit); void DrawItemInsertPopup(); -bool DrawItemEditorPopup(zelda3::OverworldItem &item); +bool DrawItemEditorPopup(zelda3::OverworldItem& item); /** * @brief Column IDs for the sprite table. @@ -41,11 +39,11 @@ enum SpriteItemColumnID { struct SpriteItem { int id; - const char *name; - static const ImGuiTableSortSpecs *s_current_sort_specs; + const char* name; + static const ImGuiTableSortSpecs* s_current_sort_specs; - static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs, - std::vector &items) { + static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, + std::vector& items) { s_current_sort_specs = sort_specs; // Store for access by the compare function. if (items.size() > 1) @@ -53,9 +51,9 @@ struct SpriteItem { s_current_sort_specs = nullptr; } - static bool CompareWithSortSpecs(const SpriteItem &a, const SpriteItem &b) { + static bool CompareWithSortSpecs(const SpriteItem& a, const SpriteItem& b) { for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) { - const ImGuiTableColumnSortSpecs *sort_spec = + const ImGuiTableColumnSortSpecs* sort_spec = &s_current_sort_specs->Specs[n]; int delta = 0; switch (sort_spec->ColumnUserID) { @@ -77,7 +75,7 @@ struct SpriteItem { void DrawSpriteTable(std::function onSpriteSelect); void DrawSpriteInserterPopup(); -bool DrawSpriteEditorPopup(zelda3::Sprite &sprite); +bool DrawSpriteEditorPopup(zelda3::Sprite& sprite); } // namespace editor } // namespace yaze diff --git a/src/app/editor/overworld/entity_operations.cc b/src/app/editor/overworld/entity_operations.cc index 8d289ce6..26266af9 100644 --- a/src/app/editor/overworld/entity_operations.cc +++ b/src/app/editor/overworld/entity_operations.cc @@ -7,20 +7,20 @@ namespace yaze { namespace editor { absl::StatusOr InsertEntrance( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, bool is_hole) { - + if (!overworld || !overworld->is_loaded()) { return absl::FailedPreconditionError("Overworld not loaded"); } - + // Snap to 16x16 grid and clamp to bounds (ZScream: EntranceMode.cs:86-87) ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); - + // Get parent map ID (ZScream: EntranceMode.cs:78-82) auto* current_ow_map = overworld->overworld_map(current_map); uint8_t map_id = GetParentMapId(current_ow_map, current_map); - + if (is_hole) { // Search for first deleted hole slot (ZScream: EntranceMode.cs:74-100) auto& holes = overworld->holes(); @@ -33,38 +33,40 @@ absl::StatusOr InsertEntrance( holes[i].y_ = static_cast(snapped_pos.y); holes[i].entrance_id_ = 0; // Default, user configures in popup holes[i].is_hole_ = true; - - // Update map properties (ZScream: EntranceMode.cs:90) - holes[i].UpdateMapProperties(map_id, overworld); - - LOG_DEBUG("EntityOps", "Inserted hole at slot %zu: pos=(%d,%d) map=0x%02X", - i, holes[i].x_, holes[i].y_, map_id); - - return &holes[i]; + + // Update map properties (ZScream: EntranceMode.cs:90) + holes[i].UpdateMapProperties(map_id, overworld); + + LOG_DEBUG("EntityOps", + "Inserted hole at slot %zu: pos=(%d,%d) map=0x%02X", i, + holes[i].x_, holes[i].y_, map_id); + + return &holes[i]; + } } - } - return absl::ResourceExhaustedError( - "No space available for new hole. Delete one first."); - -} else { - // Search for first deleted entrance slot (ZScream: EntranceMode.cs:104-130) - auto* entrances = overworld->mutable_entrances(); - for (size_t i = 0; i < entrances->size(); ++i) { - if (entrances->at(i).deleted) { - // Reuse deleted slot - entrances->at(i).deleted = false; - entrances->at(i).map_id_ = map_id; - entrances->at(i).x_ = static_cast(snapped_pos.x); - entrances->at(i).y_ = static_cast(snapped_pos.y); - entrances->at(i).entrance_id_ = 0; // Default, user configures in popup - entrances->at(i).is_hole_ = false; - - // Update map properties (ZScream: EntranceMode.cs:120) - entrances->at(i).UpdateMapProperties(map_id, overworld); - - LOG_DEBUG("EntityOps", "Inserted entrance at slot %zu: pos=(%d,%d) map=0x%02X", - i, entrances->at(i).x_, entrances->at(i).y_, map_id); - + return absl::ResourceExhaustedError( + "No space available for new hole. Delete one first."); + + } else { + // Search for first deleted entrance slot (ZScream: EntranceMode.cs:104-130) + auto* entrances = overworld->mutable_entrances(); + for (size_t i = 0; i < entrances->size(); ++i) { + if (entrances->at(i).deleted) { + // Reuse deleted slot + entrances->at(i).deleted = false; + entrances->at(i).map_id_ = map_id; + entrances->at(i).x_ = static_cast(snapped_pos.x); + entrances->at(i).y_ = static_cast(snapped_pos.y); + entrances->at(i).entrance_id_ = 0; // Default, user configures in popup + entrances->at(i).is_hole_ = false; + + // Update map properties (ZScream: EntranceMode.cs:120) + entrances->at(i).UpdateMapProperties(map_id, overworld); + + LOG_DEBUG("EntityOps", + "Inserted entrance at slot %zu: pos=(%d,%d) map=0x%02X", i, + entrances->at(i).x_, entrances->at(i).y_, map_id); + return &entrances->at(i); } } @@ -73,20 +75,21 @@ absl::StatusOr InsertEntrance( } } -absl::StatusOr InsertExit( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map) { - +absl::StatusOr InsertExit(zelda3::Overworld* overworld, + ImVec2 mouse_pos, + int current_map) { + if (!overworld || !overworld->is_loaded()) { return absl::FailedPreconditionError("Overworld not loaded"); } - + // Snap to 16x16 grid and clamp to bounds (ZScream: ExitMode.cs:71-72) ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); - + // Get parent map ID (ZScream: ExitMode.cs:63-67) auto* current_ow_map = overworld->overworld_map(current_map); uint8_t map_id = GetParentMapId(current_ow_map, current_map); - + // Search for first deleted exit slot (ZScream: ExitMode.cs:59-124) auto& exits = *overworld->mutable_exits(); for (size_t i = 0; i < exits.size(); ++i) { @@ -96,7 +99,7 @@ absl::StatusOr InsertExit( exits[i].map_id_ = map_id; exits[i].x_ = static_cast(snapped_pos.x); exits[i].y_ = static_cast(snapped_pos.y); - + // Initialize with default values (ZScream: ExitMode.cs:95-112) // User will configure room_id, scroll, camera in popup exits[i].room_id_ = 0; @@ -110,126 +113,130 @@ absl::StatusOr InsertExit( exits[i].scroll_mod_y_ = 0; exits[i].door_type_1_ = 0; exits[i].door_type_2_ = 0; - + // Update map properties with overworld context for area size detection exits[i].UpdateMapProperties(map_id, overworld); - - LOG_DEBUG("EntityOps", "Inserted exit at slot %zu: pos=(%d,%d) map=0x%02X", - i, exits[i].x_, exits[i].y_, map_id); - + + LOG_DEBUG("EntityOps", + "Inserted exit at slot %zu: pos=(%d,%d) map=0x%02X", i, + exits[i].x_, exits[i].y_, map_id); + return &exits[i]; } } - + return absl::ResourceExhaustedError( "No space available for new exit. Delete one first."); } -absl::StatusOr InsertSprite( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, - int game_state, uint8_t sprite_id) { - +absl::StatusOr InsertSprite(zelda3::Overworld* overworld, + ImVec2 mouse_pos, int current_map, + int game_state, + uint8_t sprite_id) { + if (!overworld || !overworld->is_loaded()) { return absl::FailedPreconditionError("Overworld not loaded"); } - + if (game_state < 0 || game_state > 2) { return absl::InvalidArgumentError("Invalid game state (must be 0-2)"); } - + // Snap to 16x16 grid and clamp to bounds (ZScream: SpriteMode.cs similar logic) ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); - + // Get parent map ID (ZScream: SpriteMode.cs:90-95) auto* current_ow_map = overworld->overworld_map(current_map); uint8_t map_id = GetParentMapId(current_ow_map, current_map); - + // Calculate map position (ZScream uses mapHover for parent tracking) // For sprites, we need the actual map coordinates within the 512x512 map int map_local_x = static_cast(snapped_pos.x) % 512; int map_local_y = static_cast(snapped_pos.y) % 512; - + // Convert to game coordinates (0-63 for X/Y within map) uint8_t game_x = static_cast(map_local_x / 16); uint8_t game_y = static_cast(map_local_y / 16); - + // Add new sprite to the game state array (ZScream: SpriteMode.cs:34-35) auto& sprites = *overworld->mutable_sprites(game_state); - + // Create new sprite zelda3::Sprite new_sprite( - current_ow_map->current_graphics(), - static_cast(map_id), + current_ow_map->current_graphics(), static_cast(map_id), sprite_id, // Sprite ID (user will configure in popup) game_x, // X position in map coordinates game_y, // Y position in map coordinates static_cast(snapped_pos.x), // Real X (world coordinates) static_cast(snapped_pos.y) // Real Y (world coordinates) ); - + sprites.push_back(new_sprite); - + // Return pointer to the newly added sprite zelda3::Sprite* inserted_sprite = &sprites.back(); - - LOG_DEBUG("EntityOps", "Inserted sprite at game_state=%d: pos=(%d,%d) map=0x%02X id=0x%02X", - game_state, inserted_sprite->x_, inserted_sprite->y_, map_id, sprite_id); - + + LOG_DEBUG( + "EntityOps", + "Inserted sprite at game_state=%d: pos=(%d,%d) map=0x%02X id=0x%02X", + game_state, inserted_sprite->x_, inserted_sprite->y_, map_id, sprite_id); + return inserted_sprite; } -absl::StatusOr InsertItem( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, - uint8_t item_id) { - +absl::StatusOr InsertItem(zelda3::Overworld* overworld, + ImVec2 mouse_pos, + int current_map, + uint8_t item_id) { + if (!overworld || !overworld->is_loaded()) { return absl::FailedPreconditionError("Overworld not loaded"); } - + // Snap to 16x16 grid and clamp to bounds (ZScream: ItemMode.cs similar logic) ImVec2 snapped_pos = ClampToOverworldBounds(SnapToEntityGrid(mouse_pos)); - + // Get parent map ID (ZScream: ItemMode.cs:60-64) auto* current_ow_map = overworld->overworld_map(current_map); uint8_t map_id = GetParentMapId(current_ow_map, current_map); - + // Calculate game coordinates (0-63 for X/Y within map) // Following LoadItems logic in overworld.cc:840-854 int fake_id = current_map % 0x40; int sy = fake_id / 8; int sx = fake_id - (sy * 8); - + // Calculate map-local coordinates int map_local_x = static_cast(snapped_pos.x) % 512; int map_local_y = static_cast(snapped_pos.y) % 512; - + // Game coordinates (0-63 range) uint8_t game_x = static_cast(map_local_x / 16); uint8_t game_y = static_cast(map_local_y / 16); - + // Add new item to the all_items array (ZScream: ItemMode.cs:92-108) auto& items = *overworld->mutable_all_items(); - + // Create new item with calculated coordinates - items.emplace_back( - item_id, // Item ID - static_cast(map_id), // Room map ID - static_cast(snapped_pos.x), // X (world coordinates) - static_cast(snapped_pos.y), // Y (world coordinates) - false // Not deleted + items.emplace_back(item_id, // Item ID + static_cast(map_id), // Room map ID + static_cast(snapped_pos.x), // X (world coordinates) + static_cast(snapped_pos.y), // Y (world coordinates) + false // Not deleted ); - + // Set game coordinates zelda3::OverworldItem* inserted_item = &items.back(); inserted_item->game_x_ = game_x; inserted_item->game_y_ = game_y; - - LOG_DEBUG("EntityOps", "Inserted item: pos=(%d,%d) game=(%d,%d) map=0x%02X id=0x%02X", - inserted_item->x_, inserted_item->y_, game_x, game_y, map_id, item_id); - + + LOG_DEBUG("EntityOps", + "Inserted item: pos=(%d,%d) game=(%d,%d) map=0x%02X id=0x%02X", + inserted_item->x_, inserted_item->y_, game_x, game_y, map_id, + item_id); + return inserted_item; } } // namespace editor } // namespace yaze - diff --git a/src/app/editor/overworld/entity_operations.h b/src/app/editor/overworld/entity_operations.h index 39f821a2..bcbf556a 100644 --- a/src/app/editor/overworld/entity_operations.h +++ b/src/app/editor/overworld/entity_operations.h @@ -2,12 +2,12 @@ #define YAZE_APP_EDITOR_OVERWORLD_ENTITY_OPERATIONS_H #include "absl/status/statusor.h" +#include "imgui/imgui.h" #include "zelda3/overworld/overworld.h" #include "zelda3/overworld/overworld_entrance.h" #include "zelda3/overworld/overworld_exit.h" #include "zelda3/overworld/overworld_item.h" #include "zelda3/sprite/sprite.h" -#include "imgui/imgui.h" namespace yaze { namespace editor { @@ -41,7 +41,7 @@ namespace editor { * @return Pointer to newly inserted entrance, or error if no slots available */ absl::StatusOr InsertEntrance( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, + zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, bool is_hole = false); /** @@ -58,8 +58,9 @@ absl::StatusOr InsertEntrance( * @param current_map Current map index being edited * @return Pointer to newly inserted exit, or error if no slots available */ -absl::StatusOr InsertExit( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map); +absl::StatusOr InsertExit(zelda3::Overworld* overworld, + ImVec2 mouse_pos, + int current_map); /** * @brief Insert a new sprite at the specified position @@ -76,9 +77,10 @@ absl::StatusOr InsertExit( * @param sprite_id Sprite ID to insert (default 0) * @return Pointer to newly inserted sprite */ -absl::StatusOr InsertSprite( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, - int game_state, uint8_t sprite_id = 0); +absl::StatusOr InsertSprite(zelda3::Overworld* overworld, + ImVec2 mouse_pos, int current_map, + int game_state, + uint8_t sprite_id = 0); /** * @brief Insert a new item at the specified position @@ -94,9 +96,10 @@ absl::StatusOr InsertSprite( * @param item_id Item ID to insert (default 0x00 - Nothing) * @return Pointer to newly inserted item */ -absl::StatusOr InsertItem( - zelda3::Overworld* overworld, ImVec2 mouse_pos, int current_map, - uint8_t item_id = 0); +absl::StatusOr InsertItem(zelda3::Overworld* overworld, + ImVec2 mouse_pos, + int current_map, + uint8_t item_id = 0); /** * @brief Helper to get parent map ID for multi-area maps @@ -104,7 +107,8 @@ absl::StatusOr InsertItem( * Returns the parent map ID, handling the case where a map is its own parent. * Matches ZScream logic where ParentID == 255 means use current map. */ -inline uint8_t GetParentMapId(const zelda3::OverworldMap* map, int current_map) { +inline uint8_t GetParentMapId(const zelda3::OverworldMap* map, + int current_map) { uint8_t parent = map->parent(); return (parent == 0xFF) ? static_cast(current_map) : parent; } @@ -113,24 +117,19 @@ inline uint8_t GetParentMapId(const zelda3::OverworldMap* map, int current_map) * @brief Snap position to 16x16 grid (standard entity positioning) */ inline ImVec2 SnapToEntityGrid(ImVec2 pos) { - return ImVec2( - static_cast(static_cast(pos.x / 16) * 16), - static_cast(static_cast(pos.y / 16) * 16) - ); + return ImVec2(static_cast(static_cast(pos.x / 16) * 16), + static_cast(static_cast(pos.y / 16) * 16)); } /** * @brief Clamp position to valid overworld bounds */ inline ImVec2 ClampToOverworldBounds(ImVec2 pos) { - return ImVec2( - std::clamp(pos.x, 0.0f, 4080.0f), // 4096 - 16 - std::clamp(pos.y, 0.0f, 4080.0f) - ); + return ImVec2(std::clamp(pos.x, 0.0f, 4080.0f), // 4096 - 16 + std::clamp(pos.y, 0.0f, 4080.0f)); } } // namespace editor } // namespace yaze #endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_OPERATIONS_H - diff --git a/src/app/editor/overworld/map_properties.cc b/src/app/editor/overworld/map_properties.cc index 627de14b..557e2660 100644 --- a/src/app/editor/overworld/map_properties.cc +++ b/src/app/editor/overworld/map_properties.cc @@ -1,16 +1,16 @@ #include "app/editor/overworld/map_properties.h" -#include "app/gfx/debug/performance/performance_profiler.h" #include "app/editor/overworld/overworld_editor.h" #include "app/editor/overworld/ui_constants.h" +#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gui/canvas/canvas.h" #include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" #include "app/gui/core/layout_helpers.h" +#include "imgui/imgui.h" #include "zelda3/overworld/overworld_map.h" #include "zelda3/overworld/overworld_version_helper.h" -#include "imgui/imgui.h" namespace yaze { namespace editor { @@ -29,7 +29,7 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings( bool& show_overlay_editor, bool& show_overlay_preview, int& game_state, int& current_mode) { (void)show_overlay_editor; // Reserved for future use - (void)current_mode; // Reserved for future use + (void)current_mode; // Reserved for future use // Enhanced settings table with popup buttons for quick access and integrated toolset if (BeginTable("SimplifiedMapSettings", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit, @@ -63,18 +63,17 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings( TableNextColumn(); // Use centralized version detection auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - + // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall. int current_area_size = static_cast(overworld_->overworld_map(current_map)->area_size()); ImGui::SetNextItemWidth(kComboAreaSizeWidth); - + if (zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version)) { // v3+ ROM: Show all 4 area size options if (ImGui::Combo("##AreaSize", ¤t_area_size, kAreaSizeNames, 4)) { auto status = overworld_->ConfigureMultiAreaMap( - current_map, - static_cast(current_area_size)); + current_map, static_cast(current_area_size)); if (status.ok()) { RefreshSiblingMapGraphics(current_map, true); RefreshOverworldMap(); @@ -83,11 +82,13 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings( } else { // Vanilla/v1/v2 ROM: Show only Small/Large (first 2 options) const char* limited_names[] = {"Small (1x1)", "Large (2x2)"}; - int limited_size = (current_area_size == 0 || current_area_size == 1) ? current_area_size : 0; - + int limited_size = (current_area_size == 0 || current_area_size == 1) + ? current_area_size + : 0; + if (ImGui::Combo("##AreaSize", &limited_size, limited_names, 2)) { // limited_size is 0 (Small) or 1 (Large) - auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea + auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea : zelda3::AreaSizeEnum::SmallArea; auto status = overworld_->ConfigureMultiAreaMap(current_map, size); if (status.ok()) { @@ -95,8 +96,8 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings( RefreshOverworldMap(); } } - - if (rom_version == zelda3::OverworldVersion::kVanilla || + + if (rom_version == zelda3::OverworldVersion::kVanilla || !zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version)) { HOVER_HINT("Small (1x1) and Large (2x2) maps. Wide/Tall require v3+"); } @@ -125,7 +126,8 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings( DrawGraphicsPopup(current_map, game_state); TableNextColumn(); - if (ImGui::Button(ICON_MD_PALETTE " Palettes", ImVec2(kTableButtonPalettes, 0))) { + if (ImGui::Button(ICON_MD_PALETTE " Palettes", + ImVec2(kTableButtonPalettes, 0))) { ImGui::OpenPopup("PalettesPopup"); } if (ImGui::IsItemHovered()) { @@ -140,7 +142,8 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings( DrawPalettesPopup(current_map, game_state, show_custom_bg_color_editor); TableNextColumn(); - if (ImGui::Button(ICON_MD_TUNE " Config", ImVec2(kTableButtonProperties, 0))) { + if (ImGui::Button(ICON_MD_TUNE " Config", + ImVec2(kTableButtonProperties, 0))) { ImGui::OpenPopup("ConfigPopup"); } if (ImGui::IsItemHovered()) { @@ -161,7 +164,8 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings( TableNextColumn(); // View Controls - if (ImGui::Button(ICON_MD_VISIBILITY " View", ImVec2(kTableButtonView, 0))) { + if (ImGui::Button(ICON_MD_VISIBILITY " View", + ImVec2(kTableButtonView, 0))) { ImGui::OpenPopup("ViewPopup"); } if (ImGui::IsItemHovered()) { @@ -226,7 +230,7 @@ void MapPropertiesSystem::DrawMapPropertiesPanel( // Custom Overworld Features Tab auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - if (rom_version != zelda3::OverworldVersion::kVanilla && + if (rom_version != zelda3::OverworldVersion::kVanilla && ImGui::BeginTabItem("Custom Features")) { DrawCustomFeaturesTab(current_map); ImGui::EndTabItem(); @@ -311,15 +315,15 @@ void MapPropertiesSystem::DrawOverlayEditor(int current_map, } auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_MD_LAYERS " Visual Effects Configuration"); ImGui::Text("Map: 0x%02X", current_map); Separator(); if (rom_version == zelda3::OverworldVersion::kVanilla) { - ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.4f, 1.0f), - ICON_MD_WARNING " Subscreen overlays require ZSCustomOverworld v1+"); + ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.4f, 1.0f), ICON_MD_WARNING + " Subscreen overlays require ZSCustomOverworld v1+"); ImGui::Separator(); ImGui::TextWrapped( "To use visual effect overlays, you need to upgrade your ROM to " @@ -334,7 +338,8 @@ void MapPropertiesSystem::DrawOverlayEditor(int current_map, ImGui::Indent(); ImGui::TextWrapped( "Visual effects (subscreen overlays) are semi-transparent layers drawn " - "on top of or behind your map. They reference special area maps (0x80-0x9F) " + "on top of or behind your map. They reference special area maps " + "(0x80-0x9F) " "for their tile16 graphics data."); ImGui::Spacing(); ImGui::Text("Common uses:"); @@ -349,7 +354,7 @@ void MapPropertiesSystem::DrawOverlayEditor(int current_map, // Enable/disable subscreen overlay static bool use_subscreen_overlay = false; - if (ImGui::Checkbox(ICON_MD_VISIBILITY " Enable Visual Effect for This Area", + if (ImGui::Checkbox(ICON_MD_VISIBILITY " Enable Visual Effect for This Area", &use_subscreen_overlay)) { // Update ROM data (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] = @@ -363,8 +368,8 @@ void MapPropertiesSystem::DrawOverlayEditor(int current_map, ImGui::Spacing(); uint16_t current_overlay = overworld_->overworld_map(current_map)->subscreen_overlay(); - if (gui::InputHexWord(ICON_MD_PHOTO " Visual Effect Map ID", ¤t_overlay, - kInputFieldSize + 30)) { + if (gui::InputHexWord(ICON_MD_PHOTO " Visual Effect Map ID", + ¤t_overlay, kInputFieldSize + 30)) { overworld_->mutable_overworld_map(current_map) ->set_subscreen_overlay(current_overlay); @@ -383,11 +388,12 @@ void MapPropertiesSystem::DrawOverlayEditor(int current_map, // Show description std::string overlay_desc = GetOverlayDescription(current_overlay); - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), - ICON_MD_INFO " %s", overlay_desc.c_str()); + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), ICON_MD_INFO " %s", + overlay_desc.c_str()); ImGui::Separator(); - if (ImGui::CollapsingHeader(ICON_MD_LIGHTBULB " Common Visual Effect IDs")) { + if (ImGui::CollapsingHeader(ICON_MD_LIGHTBULB + " Common Visual Effect IDs")) { ImGui::Indent(); ImGui::BulletText("0x0093 - Triforce Room Curtain"); ImGui::BulletText("0x0094 - Under the Bridge"); @@ -403,8 +409,8 @@ void MapPropertiesSystem::DrawOverlayEditor(int current_map, } } else { ImGui::Spacing(); - ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), - ICON_MD_BLOCK " No visual effects enabled for this area"); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_BLOCK + " No visual effects enabled for this area"); } } @@ -415,12 +421,12 @@ void MapPropertiesSystem::SetupCanvasContextMenu( (void)current_map; // Used for future context-sensitive menu items // Clear any existing context menu items canvas.ClearContextMenuItems(); - + // Add entity insertion submenu (only in MOUSE mode) if (current_mode == 0 && entity_insert_callback_) { // 0 = EditingMode::MOUSE gui::CanvasMenuItem entity_menu; entity_menu.label = ICON_MD_ADD_LOCATION " Insert Entity"; - + // Entrance submenu item gui::CanvasMenuItem entrance_item; entrance_item.label = ICON_MD_DOOR_FRONT " Entrance"; @@ -430,7 +436,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu( } }; entity_menu.subitems.push_back(entrance_item); - + // Hole submenu item gui::CanvasMenuItem hole_item; hole_item.label = ICON_MD_CYCLONE " Hole"; @@ -440,7 +446,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu( } }; entity_menu.subitems.push_back(hole_item); - + // Exit submenu item gui::CanvasMenuItem exit_item; exit_item.label = ICON_MD_DOOR_BACK " Exit"; @@ -450,7 +456,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu( } }; entity_menu.subitems.push_back(exit_item); - + // Item submenu item gui::CanvasMenuItem item_item; item_item.label = ICON_MD_GRASS " Item"; @@ -460,7 +466,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu( } }; entity_menu.subitems.push_back(item_item); - + // Sprite submenu item gui::CanvasMenuItem sprite_item; sprite_item.label = ICON_MD_PEST_CONTROL_RODENT " Sprite"; @@ -470,7 +476,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu( } }; entity_menu.subitems.push_back(sprite_item); - + canvas.AddContextMenuItem(entity_menu); } @@ -540,7 +546,7 @@ void MapPropertiesSystem::SetupCanvasContextMenu( void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { if (ImGui::BeginPopup("GraphicsPopup")) { ImGui::PushID("GraphicsPopup"); // Fix ImGui duplicate ID warnings - + // Use theme-aware spacing instead of hardcoded constants float spacing = gui::LayoutHelpers::GetStandardSpacing(); float padding = gui::LayoutHelpers::GetButtonPadding(); @@ -552,36 +558,36 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { // Area Graphics if (gui::InputHexByte(ICON_MD_IMAGE " Area Graphics", - overworld_->mutable_overworld_map(current_map) - ->mutable_area_graphics(), - kHexByteInputWidth)) { + overworld_->mutable_overworld_map(current_map) + ->mutable_area_graphics(), + kHexByteInputWidth)) { // CORRECT ORDER: Properties first, then graphics reload - + // 1. Propagate properties to siblings FIRST (calls LoadAreaGraphics on siblings) RefreshMapProperties(); - + // 2. Force immediate refresh of current map (*maps_bmp_)[current_map].set_modified(true); overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics(); - + // 3. Refresh siblings immediately RefreshSiblingMapGraphics(current_map); - - // 4. Update tile selector + + // 4. Update tile selector RefreshTile16Blockset(); - + // 5. Final refresh RefreshOverworldMap(); } HOVER_HINT("Main tileset graphics for this map area"); // Sprite Graphics - if (gui::InputHexByte( - absl::StrFormat(ICON_MD_PETS " Sprite GFX (%s)", kGameStateNames[game_state]) - .c_str(), - overworld_->mutable_overworld_map(current_map) - ->mutable_sprite_graphics(game_state), - kHexByteInputWidth)) { + if (gui::InputHexByte(absl::StrFormat(ICON_MD_PETS " Sprite GFX (%s)", + kGameStateNames[game_state]) + .c_str(), + overworld_->mutable_overworld_map(current_map) + ->mutable_sprite_graphics(game_state), + kHexByteInputWidth)) { ForceRefreshGraphics(current_map); RefreshMapProperties(); RefreshOverworldMap(); @@ -591,9 +597,9 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { auto rom_version_gfx = zelda3::OverworldVersionHelper::GetVersion(*rom_); if (zelda3::OverworldVersionHelper::SupportsAnimatedGFX(rom_version_gfx)) { if (gui::InputHexByte(ICON_MD_ANIMATION " Animated GFX", - overworld_->mutable_overworld_map(current_map) - ->mutable_animated_gfx(), - kHexByteInputWidth)) { + overworld_->mutable_overworld_map(current_map) + ->mutable_animated_gfx(), + kHexByteInputWidth)) { ForceRefreshGraphics(current_map); RefreshMapProperties(); RefreshTile16Blockset(); @@ -603,21 +609,21 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { } // Custom Tile Graphics - Only available for v1+ ROMs - if (zelda3::OverworldVersionHelper::SupportsExpandedSpace(rom_version_gfx)) { + if (zelda3::OverworldVersionHelper::SupportsExpandedSpace( + rom_version_gfx)) { ImGui::Separator(); ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics"); ImGui::Separator(); // Show the 8 custom graphics IDs in a 2-column layout for density - if (BeginTable("CustomTileGraphics", 2, - ImGuiTableFlags_SizingFixedFit)) { + if (BeginTable("CustomTileGraphics", 2, ImGuiTableFlags_SizingFixedFit)) { for (int i = 0; i < 8; i++) { TableNextColumn(); std::string label = absl::StrFormat(ICON_MD_LAYERS " Sheet %d", i); if (gui::InputHexByte(label.c_str(), - overworld_->mutable_overworld_map(current_map) - ->mutable_custom_tileset(i), - 90.f)) { + overworld_->mutable_overworld_map(current_map) + ->mutable_custom_tileset(i), + 90.f)) { ForceRefreshGraphics(current_map); RefreshMapProperties(); RefreshTile16Blockset(); @@ -631,7 +637,7 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { } } else if (rom_version_gfx == zelda3::OverworldVersion::kVanilla) { ImGui::Separator(); - ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_INFO " Custom Tile Graphics"); ImGui::TextWrapped( "Custom tile graphics require ZSCustomOverworld v1+.\n" @@ -639,7 +645,7 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) { } ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed - ImGui::PopID(); // Pop GraphicsPopup ID scope + ImGui::PopID(); // Pop GraphicsPopup ID scope ImGui::EndPopup(); } } @@ -648,7 +654,7 @@ void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state, bool& show_custom_bg_color_editor) { if (ImGui::BeginPopup("PalettesPopup")) { ImGui::PushID("PalettesPopup"); // Fix ImGui duplicate ID warnings - + // Use theme-aware spacing instead of hardcoded constants float spacing = gui::LayoutHelpers::GetStandardSpacing(); float padding = gui::LayoutHelpers::GetButtonPadding(); @@ -660,9 +666,9 @@ void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state, // Area Palette if (gui::InputHexByte(ICON_MD_PALETTE " Area Palette", - overworld_->mutable_overworld_map(current_map) - ->mutable_area_palette(), - kHexByteInputWidth)) { + overworld_->mutable_overworld_map(current_map) + ->mutable_area_palette(), + kHexByteInputWidth)) { RefreshMapProperties(); auto status = RefreshMapPalette(); RefreshOverworldMap(); @@ -671,11 +677,12 @@ void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state, // Read fresh to reflect ROM upgrades auto rom_version_pal = zelda3::OverworldVersionHelper::GetVersion(*rom_); - if (zelda3::OverworldVersionHelper::SupportsCustomBGColors(rom_version_pal)) { + if (zelda3::OverworldVersionHelper::SupportsCustomBGColors( + rom_version_pal)) { if (gui::InputHexByte(ICON_MD_COLOR_LENS " Main Palette", - overworld_->mutable_overworld_map(current_map) - ->mutable_main_palette(), - kHexByteInputWidth)) { + overworld_->mutable_overworld_map(current_map) + ->mutable_main_palette(), + kHexByteInputWidth)) { RefreshMapProperties(); auto status = RefreshMapPalette(); RefreshOverworldMap(); @@ -684,26 +691,26 @@ void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state, } // Sprite Palette - if (gui::InputHexByte( - absl::StrFormat(ICON_MD_COLORIZE " Sprite Pal (%s)", kGameStateNames[game_state]) - .c_str(), - overworld_->mutable_overworld_map(current_map) - ->mutable_sprite_palette(game_state), - kHexByteInputWidth)) { + if (gui::InputHexByte(absl::StrFormat(ICON_MD_COLORIZE " Sprite Pal (%s)", + kGameStateNames[game_state]) + .c_str(), + overworld_->mutable_overworld_map(current_map) + ->mutable_sprite_palette(game_state), + kHexByteInputWidth)) { RefreshMapProperties(); RefreshOverworldMap(); } HOVER_HINT("Color palette for sprites in current game state"); ImGui::Separator(); - if (ImGui::Button(ICON_MD_FORMAT_COLOR_FILL " Custom Background Color", - ImVec2(-1, 0))) { + if (ImGui::Button(ICON_MD_FORMAT_COLOR_FILL " Custom Background Color", + ImVec2(-1, 0))) { show_custom_bg_color_editor = !show_custom_bg_color_editor; } HOVER_HINT("Open custom background color editor (v2+)"); ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed - ImGui::PopID(); // Pop PalettesPopup ID scope + ImGui::PopID(); // Pop PalettesPopup ID scope ImGui::EndPopup(); } } @@ -714,7 +721,7 @@ void MapPropertiesSystem::DrawPropertiesPopup(int current_map, int& game_state) { if (ImGui::BeginPopup("ConfigPopup")) { ImGui::PushID("ConfigPopup"); // Fix ImGui duplicate ID warnings - + // Use theme-aware spacing instead of hardcoded constants float spacing = gui::LayoutHelpers::GetStandardSpacing(); float padding = gui::LayoutHelpers::GetButtonPadding(); @@ -751,7 +758,8 @@ void MapPropertiesSystem::DrawPropertiesPopup(int current_map, RefreshOverworldMap(); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Affects sprite graphics/palettes based on story progress"); + ImGui::SetTooltip( + "Affects sprite graphics/palettes based on story progress"); } ImGui::EndTable(); @@ -763,19 +771,18 @@ void MapPropertiesSystem::DrawPropertiesPopup(int current_map, ImGui::Separator(); // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall. - uint8_t asm_version = - (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + int current_area_size = static_cast(overworld_->overworld_map(current_map)->area_size()); ImGui::SetNextItemWidth(kComboAreaSizeWidth); - + if (asm_version >= 3 && asm_version != 0xFF) { // v3+ ROM: Show all 4 area size options - if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size", ¤t_area_size, kAreaSizeNames, 4)) { + if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size", + ¤t_area_size, kAreaSizeNames, 4)) { auto status = overworld_->ConfigureMultiAreaMap( - current_map, - static_cast(current_area_size)); + current_map, static_cast(current_area_size)); if (status.ok()) { RefreshSiblingMapGraphics(current_map, true); RefreshOverworldMap(); @@ -785,10 +792,13 @@ void MapPropertiesSystem::DrawPropertiesPopup(int current_map, } else { // Vanilla/v1/v2 ROM: Show only Small/Large const char* limited_names[] = {"Small (1x1)", "Large (2x2)"}; - int limited_size = (current_area_size == 0 || current_area_size == 1) ? current_area_size : 0; - - if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size", &limited_size, limited_names, 2)) { - auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea + int limited_size = (current_area_size == 0 || current_area_size == 1) + ? current_area_size + : 0; + + if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size", &limited_size, + limited_names, 2)) { + auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea : zelda3::AreaSizeEnum::SmallArea; auto status = overworld_->ConfigureMultiAreaMap(current_map, size); if (status.ok()) { @@ -817,7 +827,7 @@ void MapPropertiesSystem::DrawPropertiesPopup(int current_map, HOVER_HINT("Open detailed area configuration with all settings tabs"); ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed - ImGui::PopID(); // Pop ConfigPopup ID scope + ImGui::PopID(); // Pop ConfigPopup ID scope ImGui::EndPopup(); } } @@ -990,7 +1000,8 @@ void MapPropertiesSystem::DrawSpritePropertiesTab(int current_map) { RefreshOverworldMap(); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Second sprite graphics sheet for Master Sword obtained state"); + ImGui::SetTooltip( + "Second sprite graphics sheet for Master Sword obtained state"); } TableNextColumn(); @@ -1018,7 +1029,8 @@ void MapPropertiesSystem::DrawSpritePropertiesTab(int current_map) { RefreshOverworldMap(); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Color palette for sprites - Master Sword obtained state"); + ImGui::SetTooltip( + "Color palette for sprites - Master Sword obtained state"); } ImGui::EndTable(); @@ -1036,34 +1048,36 @@ void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) { TableNextColumn(); // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall. auto rom_version_basic = zelda3::OverworldVersionHelper::GetVersion(*rom_); - + int current_area_size = static_cast(overworld_->overworld_map(current_map)->area_size()); ImGui::SetNextItemWidth(130.f); - + if (zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version_basic)) { // v3+ ROM: Show all 4 area size options static const char* all_sizes[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; if (ImGui::Combo("##AreaSize", ¤t_area_size, all_sizes, 4)) { auto status = overworld_->ConfigureMultiAreaMap( - current_map, - static_cast(current_area_size)); + current_map, static_cast(current_area_size)); if (status.ok()) { RefreshSiblingMapGraphics(current_map, true); RefreshOverworldMap(); } } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Map size: Small (1x1), Large (2x2), Wide (2x1), Tall (1x2)"); + ImGui::SetTooltip( + "Map size: Small (1x1), Large (2x2), Wide (2x1), Tall (1x2)"); } } else { // Vanilla/v1/v2 ROM: Show only Small/Large static const char* limited_sizes[] = {"Small (1x1)", "Large (2x2)"}; - int limited_size = (current_area_size == 0 || current_area_size == 1) ? current_area_size : 0; - + int limited_size = (current_area_size == 0 || current_area_size == 1) + ? current_area_size + : 0; + if (ImGui::Combo("##AreaSize", &limited_size, limited_sizes, 2)) { - auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea + auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea : zelda3::AreaSizeEnum::SmallArea; auto status = overworld_->ConfigureMultiAreaMap(current_map, size); if (status.ok()) { @@ -1072,11 +1086,13 @@ void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) { } } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Map size: Small (1x1), Large (2x2). Wide/Tall require v3+"); + ImGui::SetTooltip( + "Map size: Small (1x1), Large (2x2). Wide/Tall require v3+"); } } - if (zelda3::OverworldVersionHelper::SupportsCustomBGColors(rom_version_basic)) { + if (zelda3::OverworldVersionHelper::SupportsCustomBGColors( + rom_version_basic)) { TableNextColumn(); ImGui::Text(ICON_MD_COLOR_LENS " Main Palette"); TableNextColumn(); @@ -1093,7 +1109,8 @@ void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) { } } - if (zelda3::OverworldVersionHelper::SupportsAnimatedGFX(rom_version_basic)) { + if (zelda3::OverworldVersionHelper::SupportsAnimatedGFX( + rom_version_basic)) { TableNextColumn(); ImGui::Text(ICON_MD_ANIMATION " Animated GFX"); TableNextColumn(); @@ -1129,7 +1146,7 @@ void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) { void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) { auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - + // Only show custom tile graphics for v1+ ROMs if (zelda3::OverworldVersionHelper::SupportsExpandedSpace(rom_version)) { ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics (8 sheets)"); @@ -1137,7 +1154,8 @@ void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) { if (BeginTable("TileGraphics", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180); + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, + 180); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); for (int i = 0; i < 8; i++) { @@ -1162,10 +1180,11 @@ void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) { ImGui::EndTable(); } - + Separator(); - ImGui::TextWrapped("These 8 sheets allow custom tile graphics per map. " - "Each sheet references a graphics ID loaded into VRAM."); + ImGui::TextWrapped( + "These 8 sheets allow custom tile graphics per map. " + "Each sheet references a graphics ID loaded into VRAM."); } else { // Vanilla ROM - show info message ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), @@ -1191,7 +1210,7 @@ void MapPropertiesSystem::DrawMusicTab(int current_map) { ImGuiTableColumnFlags_WidthStretch); const char* music_state_names[] = { - ICON_MD_PLAY_ARROW " Beginning (Pre-Zelda)", + ICON_MD_PLAY_ARROW " Beginning (Pre-Zelda)", ICON_MD_FAVORITE " Zelda Rescued", ICON_MD_OFFLINE_BOLT " Master Sword Obtained", ICON_MD_CASTLE " Agahnim Defeated"}; @@ -1245,12 +1264,13 @@ void MapPropertiesSystem::DrawMusicTab(int current_map) { } Separator(); - ImGui::TextWrapped("Music tracks control the background music for different " - "game progression states on this overworld map."); + ImGui::TextWrapped( + "Music tracks control the background music for different " + "game progression states on this overworld map."); // Show common music track IDs for reference in a collapsing section Separator(); - if (ImGui::CollapsingHeader(ICON_MD_HELP_OUTLINE " Common Music Track IDs", + if (ImGui::CollapsingHeader(ICON_MD_HELP_OUTLINE " Common Music Track IDs", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Indent(); ImGui::BulletText("0x02 - Overworld Theme"); @@ -1295,19 +1315,21 @@ void MapPropertiesSystem::ForceRefreshGraphics(int map_index) { } } -void MapPropertiesSystem::RefreshSiblingMapGraphics(int map_index, bool include_self) { - if (!overworld_ || !maps_bmp_ || map_index < 0 || map_index >= zelda3::kNumOverworldMaps) { +void MapPropertiesSystem::RefreshSiblingMapGraphics(int map_index, + bool include_self) { + if (!overworld_ || !maps_bmp_ || map_index < 0 || + map_index >= zelda3::kNumOverworldMaps) { return; } - + auto* map = overworld_->mutable_overworld_map(map_index); if (map->area_size() == zelda3::AreaSizeEnum::SmallArea) { return; // No siblings for small areas } - + int parent_id = map->parent(); std::vector siblings; - + switch (map->area_size()) { case zelda3::AreaSizeEnum::LargeArea: siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9}; @@ -1321,26 +1343,26 @@ void MapPropertiesSystem::RefreshSiblingMapGraphics(int map_index, bool include_ default: return; } - + for (int sibling : siblings) { if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) { // Skip self unless include_self is true if (sibling == map_index && !include_self) { continue; } - + // Mark as modified FIRST (*maps_bmp_)[sibling].set_modified(true); - + // Load graphics from ROM overworld_->mutable_overworld_map(sibling)->LoadAreaGraphics(); - + // CRITICAL FIX: Force immediate refresh on the sibling // This will trigger the callback to OverworldEditor's RefreshChildMapOnDemand ForceRefreshGraphics(sibling); } } - + // After marking all siblings, trigger a refresh // This ensures all marked maps get processed RefreshOverworldMap(); @@ -1382,7 +1404,7 @@ void MapPropertiesSystem::DrawOverlayControls(int current_map, if (is_special_overworld_map) { // Special overworld maps (0x80-0x9F) serve as visual effect sources - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_MD_INFO " Special Area Map (0x%02X)", current_map); ImGui::Separator(); ImGui::TextWrapped( @@ -1398,28 +1420,28 @@ void MapPropertiesSystem::DrawOverlayControls(int current_map, // Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps support subscreen overlays // Comprehensive help section - ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_MD_HELP_OUTLINE " Visual Effects Overview"); ImGui::SameLine(); if (ImGui::Button(ICON_MD_INFO "##HelpButton")) { ImGui::OpenPopup("OverlayTypesHelp"); } - + if (ImGui::BeginPopup("OverlayTypesHelp")) { ImGui::Text(ICON_MD_HELP " Understanding Overlay Types"); ImGui::Separator(); - - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), - ICON_MD_LAYERS " 1. Subscreen Overlays (Visual Effects)"); + + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), ICON_MD_LAYERS + " 1. Subscreen Overlays (Visual Effects)"); ImGui::Indent(); ImGui::BulletText("Displayed as semi-transparent layers"); ImGui::BulletText("Reference special area maps (0x80-0x9F)"); ImGui::BulletText("Examples: fog, rain, forest canopy, sky"); ImGui::BulletText("Purely visual - don't affect collision"); ImGui::Unindent(); - + ImGui::Spacing(); - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f), + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f), ICON_MD_EDIT_NOTE " 2. Map Overlays (Interactive)"); ImGui::Indent(); ImGui::BulletText("Dynamic tile16 changes on the map"); @@ -1428,12 +1450,12 @@ void MapPropertiesSystem::DrawOverlayControls(int current_map, ImGui::BulletText("Affect collision and interaction"); ImGui::BulletText("Triggered by game events/progression"); ImGui::Unindent(); - + ImGui::Separator(); ImGui::TextWrapped( "Note: Subscreen overlays are what you configure here. " "Map overlays are event-driven and edited separately."); - + ImGui::EndPopup(); } ImGui::Separator(); @@ -1460,13 +1482,13 @@ void MapPropertiesSystem::DrawOverlayControls(int current_map, // Show subscreen overlay description with color coding std::string overlay_desc = GetOverlayDescription(current_overlay); if (current_overlay == 0x00FF) { - ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), - ICON_MD_CHECK " %s", overlay_desc.c_str()); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_CHECK " %s", + overlay_desc.c_str()); } else if (current_overlay >= 0x80 && current_overlay < 0xA0) { - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), ICON_MD_VISIBILITY " %s", overlay_desc.c_str()); } else { - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f), + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f), ICON_MD_HELP_OUTLINE " %s", overlay_desc.c_str()); } @@ -1538,9 +1560,10 @@ void MapPropertiesSystem::DrawOverlayControls(int current_map, ImGui::BulletText("Visual effects use maps 0x80-0x9F"); ImGui::BulletText("Map overlays are read-only"); } else { - const char* version_name = zelda3::OverworldVersionHelper::GetVersionName(rom_version); - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), - ICON_MD_UPGRADE " %s", version_name); + const char* version_name = + zelda3::OverworldVersionHelper::GetVersionName(rom_version); + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), ICON_MD_UPGRADE " %s", + version_name); ImGui::BulletText("Enhanced visual effect control"); if (zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version)) { ImGui::BulletText("Extended overlay system"); @@ -1580,7 +1603,7 @@ void MapPropertiesSystem::DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview) { gfx::ScopedTimer timer("map_properties_draw_overlay_preview"); - + if (!show_overlay_preview || !maps_bmp_ || !canvas_) return; @@ -1653,7 +1676,7 @@ void MapPropertiesSystem::DrawOverlayPreviewOnMap(int current_map, void MapPropertiesSystem::DrawViewPopup() { if (ImGui::BeginPopup("ViewPopup")) { ImGui::PushID("ViewPopup"); // Fix ImGui duplicate ID warnings - + // Use theme-aware spacing instead of hardcoded constants float spacing = gui::LayoutHelpers::GetStandardSpacing(); float padding = gui::LayoutHelpers::GetButtonPadding(); @@ -1683,7 +1706,7 @@ void MapPropertiesSystem::DrawViewPopup() { HOVER_HINT("Toggle fullscreen canvas (F11)"); ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed - ImGui::PopID(); // Pop ViewPopup ID scope + ImGui::PopID(); // Pop ViewPopup ID scope ImGui::EndPopup(); } } @@ -1691,7 +1714,7 @@ void MapPropertiesSystem::DrawViewPopup() { void MapPropertiesSystem::DrawQuickAccessPopup() { if (ImGui::BeginPopup("QuickPopup")) { ImGui::PushID("QuickPopup"); // Fix ImGui duplicate ID warnings - + // Use theme-aware spacing instead of hardcoded constants float spacing = gui::LayoutHelpers::GetStandardSpacing(); float padding = gui::LayoutHelpers::GetButtonPadding(); @@ -1723,7 +1746,7 @@ void MapPropertiesSystem::DrawQuickAccessPopup() { HOVER_HINT("Lock/unlock current map (Ctrl+L)"); ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed - ImGui::PopID(); // Pop QuickPopup ID scope + ImGui::PopID(); // Pop QuickPopup ID scope ImGui::EndPopup(); } } diff --git a/src/app/editor/overworld/map_properties.h b/src/app/editor/overworld/map_properties.h index 5b508e57..4c5ac71d 100644 --- a/src/app/editor/overworld/map_properties.h +++ b/src/app/editor/overworld/map_properties.h @@ -3,16 +3,16 @@ #include -#include "zelda3/overworld/overworld.h" -#include "app/rom.h" #include "app/gui/canvas/canvas.h" +#include "app/rom.h" +#include "zelda3/overworld/overworld.h" // Forward declaration namespace yaze { namespace editor { class OverworldEditor; } -} +} // namespace yaze namespace yaze { namespace editor { @@ -23,25 +23,30 @@ class MapPropertiesSystem { using RefreshCallback = std::function; using RefreshPaletteCallback = std::function; using ForceRefreshGraphicsCallback = std::function; - - explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom, - std::array* maps_bmp = nullptr, - gui::Canvas* canvas = nullptr) - : overworld_(overworld), rom_(rom), maps_bmp_(maps_bmp), canvas_(canvas) {} + + explicit MapPropertiesSystem( + zelda3::Overworld* overworld, Rom* rom, + std::array* maps_bmp = nullptr, + gui::Canvas* canvas = nullptr) + : overworld_(overworld), + rom_(rom), + maps_bmp_(maps_bmp), + canvas_(canvas) {} // Set callbacks for refresh operations - void SetRefreshCallbacks(RefreshCallback refresh_map_properties, - RefreshCallback refresh_overworld_map, - RefreshPaletteCallback refresh_map_palette, - RefreshPaletteCallback refresh_tile16_blockset = nullptr, - ForceRefreshGraphicsCallback force_refresh_graphics = nullptr) { + void SetRefreshCallbacks( + RefreshCallback refresh_map_properties, + RefreshCallback refresh_overworld_map, + RefreshPaletteCallback refresh_map_palette, + RefreshPaletteCallback refresh_tile16_blockset = nullptr, + ForceRefreshGraphicsCallback force_refresh_graphics = nullptr) { refresh_map_properties_ = std::move(refresh_map_properties); refresh_overworld_map_ = std::move(refresh_overworld_map); refresh_map_palette_ = std::move(refresh_map_palette); refresh_tile16_blockset_ = std::move(refresh_tile16_blockset); force_refresh_graphics_ = std::move(force_refresh_graphics); } - + // Set callbacks for entity operations void SetEntityCallbacks( std::function insert_callback) { @@ -49,74 +54,82 @@ class MapPropertiesSystem { } // Main interface methods - void DrawSimplifiedMapSettings(int& current_world, int& current_map, - bool& current_map_lock, bool& show_map_properties_panel, - bool& show_custom_bg_color_editor, bool& show_overlay_editor, - bool& show_overlay_preview, int& game_state, int& current_mode); - + void DrawSimplifiedMapSettings(int& current_world, int& current_map, + bool& current_map_lock, + bool& show_map_properties_panel, + bool& show_custom_bg_color_editor, + bool& show_overlay_editor, + bool& show_overlay_preview, int& game_state, + int& current_mode); + void DrawMapPropertiesPanel(int current_map, bool& show_map_properties_panel); - - void DrawCustomBackgroundColorEditor(int current_map, bool& show_custom_bg_color_editor); - + + void DrawCustomBackgroundColorEditor(int current_map, + bool& show_custom_bg_color_editor); + void DrawOverlayEditor(int current_map, bool& show_overlay_editor); // Overlay preview functionality - void DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview); + void DrawOverlayPreviewOnMap(int current_map, int current_world, + bool show_overlay_preview); // Context menu integration - void SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, bool current_map_lock, - bool& show_map_properties_panel, bool& show_custom_bg_color_editor, - bool& show_overlay_editor, int current_mode = 0); + void SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, + bool current_map_lock, + bool& show_map_properties_panel, + bool& show_custom_bg_color_editor, + bool& show_overlay_editor, int current_mode = 0); private: // Property category drawers void DrawGraphicsPopup(int current_map, int game_state); - void DrawPalettesPopup(int current_map, int game_state, bool& show_custom_bg_color_editor); - void DrawPropertiesPopup(int current_map, bool& show_map_properties_panel, - bool& show_overlay_preview, int& game_state); - + void DrawPalettesPopup(int current_map, int game_state, + bool& show_custom_bg_color_editor); + void DrawPropertiesPopup(int current_map, bool& show_map_properties_panel, + bool& show_overlay_preview, int& game_state); + // Overlay and mosaic functionality void DrawMosaicControls(int current_map); void DrawOverlayControls(int current_map, bool& show_overlay_preview); std::string GetOverlayDescription(uint16_t overlay_id); - + // Integrated toolset popup functions void DrawToolsPopup(int& current_mode); void DrawViewPopup(); void DrawQuickAccessPopup(); - + // Tab content drawers void DrawBasicPropertiesTab(int current_map); void DrawSpritePropertiesTab(int current_map); void DrawCustomFeaturesTab(int current_map); void DrawTileGraphicsTab(int current_map); void DrawMusicTab(int current_map); - + // Utility methods - now call the callbacks void RefreshMapProperties(); void RefreshOverworldMap(); absl::Status RefreshMapPalette(); absl::Status RefreshTile16Blockset(); void ForceRefreshGraphics(int map_index); - + // Helper to refresh sibling map graphics for multi-area maps void RefreshSiblingMapGraphics(int map_index, bool include_self = false); - + zelda3::Overworld* overworld_; Rom* rom_; std::array* maps_bmp_; gui::Canvas* canvas_; - + // Callbacks for refresh operations RefreshCallback refresh_map_properties_; RefreshCallback refresh_overworld_map_; RefreshPaletteCallback refresh_map_palette_; RefreshPaletteCallback refresh_tile16_blockset_; ForceRefreshGraphicsCallback force_refresh_graphics_; - + // Callback for entity insertion (generic, editor handles entity types) std::function entity_insert_callback_; - + // Using centralized UI constants from ui_constants.h }; diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 400337fb..afe3b63f 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -492,7 +492,9 @@ void OverworldEditor::DrawToolset() { // IMPORTANT: Don't cache version - it needs to update after ROM upgrade auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; // Still needed for badge display + uint8_t asm_version = (*rom_) + [zelda3:: + OverworldCustomASMHasBeenApplied]; // Still needed for badge display // Don't use WidgetIdScope here - it conflicts with ImGui::Begin/End ID stack in cards // Widgets register themselves individually instead @@ -1258,7 +1260,8 @@ absl::Status OverworldEditor::CheckForCurrentMap() { // Use centralized version detection auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - bool use_v3_area_sizes = zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); + bool use_v3_area_sizes = + zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); // Get area size for v3+ ROMs, otherwise use legacy logic if (use_v3_area_sizes) { @@ -1623,7 +1626,8 @@ void OverworldEditor::DrawOverworldCanvas() { ow_map_canvas_.scrolling(), dragged_entity_free_movement_); // Pass overworld context for proper area size detection - dragged_entity_->UpdateMapProperties(dragged_entity_->map_id_, &overworld_); + dragged_entity_->UpdateMapProperties(dragged_entity_->map_id_, + &overworld_); rom_->set_dirty(true); } is_dragging_entity_ = false; @@ -1637,7 +1641,6 @@ void OverworldEditor::DrawOverworldCanvas() { ow_map_canvas_.DrawGrid(); ow_map_canvas_.DrawOverlay(); ImGui::EndChild(); - } absl::Status OverworldEditor::DrawTile16Selector() { @@ -2121,7 +2124,8 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) { // Handle multi-area maps (large, wide, tall) with safe coordination // Use centralized version detection auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - bool use_v3_area_sizes = zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); + bool use_v3_area_sizes = + zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); if (use_v3_area_sizes) { // Use v3 multi-area coordination @@ -2305,7 +2309,8 @@ absl::Status OverworldEditor::RefreshMapPalette() { // Use centralized version detection auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - bool use_v3_area_sizes = zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); + bool use_v3_area_sizes = + zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); if (use_v3_area_sizes) { // Use v3 area size system @@ -2447,7 +2452,8 @@ void OverworldEditor::RefreshMapProperties() { // Use centralized version detection auto rom_version = zelda3::OverworldVersionHelper::GetVersion(*rom_); - bool use_v3_area_sizes = zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); + bool use_v3_area_sizes = + zelda3::OverworldVersionHelper::SupportsAreaEnum(rom_version); if (use_v3_area_sizes) { // Use v3 area size system diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index 353d90ae..6a6986f6 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -1,23 +1,23 @@ #ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H #define YAZE_APP_EDITOR_OVERWORLDEDITOR_H +#include #include "absl/status/status.h" #include "app/editor/editor.h" #include "app/editor/graphics/gfx_group_editor.h" -#include "app/editor/palette/palette_editor.h" -#include "app/editor/overworld/tile16_editor.h" #include "app/editor/overworld/map_properties.h" #include "app/editor/overworld/overworld_entity_renderer.h" +#include "app/editor/overworld/tile16_editor.h" +#include "app/editor/palette/palette_editor.h" #include "app/gfx/core/bitmap.h" -#include "app/gfx/types/snes_palette.h" #include "app/gfx/render/tilemap.h" +#include "app/gfx/types/snes_palette.h" #include "app/gui/canvas/canvas.h" -#include "app/gui/widgets/tile_selector_widget.h" #include "app/gui/core/input.h" +#include "app/gui/widgets/tile_selector_widget.h" #include "app/rom.h" -#include "zelda3/overworld/overworld.h" #include "imgui/imgui.h" -#include +#include "zelda3/overworld/overworld.h" namespace yaze { namespace editor { @@ -86,7 +86,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { absl::Status Save() override; absl::Status Clear() override; zelda3::Overworld& overworld() { return overworld_; } - + /** * @brief Apply ZSCustomOverworld ASM patch to upgrade ROM version */ @@ -103,18 +103,21 @@ class OverworldEditor : public Editor, public gfx::GfxContext { // ROM state methods (from Editor base class) bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); } std::string GetRomStatus() const override { - if (!rom_) return "No ROM loaded"; - if (!rom_->is_loaded()) return "ROM failed to load"; + if (!rom_) + return "No ROM loaded"; + if (!rom_->is_loaded()) + return "ROM failed to load"; return absl::StrFormat("ROM loaded: %s", rom_->title()); } - + Rom* rom() const { return rom_; } // Jump-to functionality void set_current_map(int map_id) { if (map_id >= 0 && map_id < zelda3::kNumOverworldMaps) { current_map_ = map_id; - current_world_ = map_id / 0x40; // Calculate which world the map belongs to + current_world_ = + map_id / 0x40; // Calculate which world the map belongs to } } @@ -126,7 +129,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { * assembling the OverworldMap Bitmap objects. */ absl::Status LoadGraphics(); - + /** * @brief Handle entity insertion from context menu * @@ -171,7 +174,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { * @brief Draw and create the tile16 IDs that are currently selected. */ void CheckForSelectRectangle(); - + // Selected tile IDs for rectangle operations (moved from local static) std::vector selected_tile16_ids_; @@ -211,26 +214,26 @@ class OverworldEditor : public Editor, public gfx::GfxContext { void DrawOverworldProperties(); void HandleMapInteraction(); // SetupOverworldCanvasContextMenu removed (Phase 3B) - now handled by MapPropertiesSystem - + // Canvas pan/zoom helpers (Overworld Refactoring) void HandleOverworldPan(); void HandleOverworldZoom(); void ResetOverworldView(); void CenterOverworldView(); - + // Canvas Automation API integration (Phase 4) void SetupCanvasAutomation(); gui::Canvas* GetOverworldCanvas() { return &ow_map_canvas_; } - + // Tile operations for automation callbacks bool AutomationSetTile(int x, int y, int tile_id); int AutomationGetTile(int x, int y); - + /** * @brief Scroll the blockset canvas to show the current selected tile16 */ void ScrollBlocksetCanvasToCurrentTile(); - + // Scratch space canvas methods absl::Status DrawScratchSpace(); absl::Status SaveCurrentSelectionToScratch(int slot); @@ -239,20 +242,21 @@ class OverworldEditor : public Editor, public gfx::GfxContext { void DrawScratchSpaceEdits(); void DrawScratchSpacePattern(); void DrawScratchSpaceSelection(); - void UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, int slot = -1); + void UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, + int slot = -1); absl::Status UpdateUsageStats(); void DrawUsageGrid(); void DrawDebugWindow(); enum class EditingMode { - MOUSE, // Navigation, selection, entity management via context menu - DRAW_TILE // Tile painting mode + MOUSE, // Navigation, selection, entity management via context menu + DRAW_TILE // Tile painting mode }; EditingMode current_mode = EditingMode::DRAW_TILE; EditingMode previous_mode = EditingMode::DRAW_TILE; - + // Entity editing state (managed via context menu now) enum class EntityEditMode { NONE, @@ -263,7 +267,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { TRANSPORTS, MUSIC }; - + EntityEditMode entity_edit_mode_ = EntityEditMode::NONE; enum OverworldProperty { @@ -310,7 +314,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { bool use_area_specific_bg_color_ = false; bool show_map_properties_panel_ = false; bool show_overlay_preview_ = false; - + // Card visibility states - Start hidden to prevent crash bool show_overworld_canvas_ = true; bool show_tile16_selector_ = false; @@ -324,12 +328,12 @@ class OverworldEditor : public Editor, public gfx::GfxContext { // Map properties system for UI organization std::unique_ptr map_properties_system_; std::unique_ptr entity_renderer_; - + // Scratch space for large layouts // Scratch space canvas for tile16 drawing (like a mini overworld) struct ScratchSpaceSlot { gfx::Bitmap scratch_bitmap; - std::array, 32> tile_data; // 32x32 grid of tile16 IDs + std::array, 32> tile_data; // 32x32 grid of tile16 IDs bool in_use = false; std::string name = "Empty"; int width = 16; // Default 16x16 tiles @@ -361,7 +365,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { std::array maps_bmp_; gfx::BitmapTable current_graphics_set_; std::vector sprite_previews_; - + // Deferred texture creation for performance optimization // Deferred texture management now handled by gfx::Arena::Get() @@ -388,7 +392,8 @@ class OverworldEditor : public Editor, public gfx::GfxContext { gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize, gui::CanvasGridSize::k16x16}; gui::Canvas properties_canvas_; - gui::Canvas scratch_canvas_{"ScratchSpace", ImVec2(320, 480), gui::CanvasGridSize::k32x32}; + gui::Canvas scratch_canvas_{"ScratchSpace", ImVec2(320, 480), + gui::CanvasGridSize::k32x32}; absl::Status status_; }; diff --git a/src/app/editor/overworld/overworld_entity_renderer.cc b/src/app/editor/overworld/overworld_entity_renderer.cc index d1dae2a4..008c0922 100644 --- a/src/app/editor/overworld/overworld_entity_renderer.cc +++ b/src/app/editor/overworld/overworld_entity_renderer.cc @@ -2,12 +2,12 @@ #include #include "absl/strings/str_format.h" -#include "core/features.h" #include "app/editor/overworld/entity.h" #include "app/gui/canvas/canvas.h" -#include "zelda3/common.h" -#include "util/hex.h" +#include "core/features.h" #include "imgui/imgui.h" +#include "util/hex.h" +#include "zelda3/common.h" #include "zelda3/overworld/overworld_item.h" namespace yaze { @@ -17,15 +17,23 @@ using namespace ImGui; // Entity colors - solid with good visibility namespace { -ImVec4 GetEntranceColor() { return ImVec4{1.0f, 1.0f, 0.0f, 1.0f}; } // Solid yellow (#FFFF00FF, fully opaque) -ImVec4 GetExitColor() { return ImVec4{1.0f, 1.0f, 1.0f, 1.0f}; } // Solid white (#FFFFFFFF, fully opaque) -ImVec4 GetItemColor() { return ImVec4{1.0f, 0.0f, 0.0f, 1.0f}; } // Solid red (#FF0000FF, fully opaque) -ImVec4 GetSpriteColor() { return ImVec4{1.0f, 0.0f, 1.0f, 1.0f}; } // Solid magenta (#FF00FFFF, fully opaque) +ImVec4 GetEntranceColor() { + return ImVec4{1.0f, 1.0f, 0.0f, 1.0f}; +} // Solid yellow (#FFFF00FF, fully opaque) +ImVec4 GetExitColor() { + return ImVec4{1.0f, 1.0f, 1.0f, 1.0f}; +} // Solid white (#FFFFFFFF, fully opaque) +ImVec4 GetItemColor() { + return ImVec4{1.0f, 0.0f, 0.0f, 1.0f}; +} // Solid red (#FF0000FF, fully opaque) +ImVec4 GetSpriteColor() { + return ImVec4{1.0f, 0.0f, 1.0f, 1.0f}; +} // Solid magenta (#FF00FFFF, fully opaque) } // namespace void OverworldEntityRenderer::DrawEntrances(ImVec2 canvas_p0, ImVec2 scrolling, - int current_world, - int current_mode) { + int current_world, + int current_mode) { // Don't reset hovered_entity_ here - DrawExits resets it (called first) int i = 0; for (auto& each : overworld_->entrances()) { @@ -43,24 +51,17 @@ void OverworldEntityRenderer::DrawEntrances(ImVec2 canvas_p0, ImVec2 scrolling, } std::string str = util::HexByte(each.entrance_id_); - - - - canvas_->DrawText(str, each.x_, each.y_); } i++; } - - } void OverworldEntityRenderer::DrawExits(ImVec2 canvas_p0, ImVec2 scrolling, - int current_world, - int current_mode) { + int current_world, int current_mode) { // Reset hover state at the start of entity rendering (DrawExits is called first) hovered_entity_ = nullptr; - + int i = 0; for (auto& each : *overworld_->mutable_exits()) { if (each.map_id_ < 0x40 + (current_world * 0x40) && @@ -71,16 +72,12 @@ void OverworldEntityRenderer::DrawExits(ImVec2 canvas_p0, ImVec2 scrolling, hovered_entity_ = &each; } each.entity_id_ = i; - - std::string str = util::HexByte(i); canvas_->DrawText(str, each.x_, each.y_); } i++; } - - } void OverworldEntityRenderer::DrawItems(int current_world, int current_mode) { @@ -92,11 +89,10 @@ void OverworldEntityRenderer::DrawItems(int current_world, int current_mode) { canvas_->DrawRect(item.x_, item.y_, 16, 16, GetItemColor()); if (IsMouseHoveringOverEntity(item, canvas_->zero_point(), - canvas_->scrolling())) { + canvas_->scrolling())) { hovered_entity_ = &item; } - std::string item_name = ""; if (item.id_ < zelda3::kSecretItemNames.size()) { item_name = zelda3::kSecretItemNames[item.id_]; @@ -107,12 +103,10 @@ void OverworldEntityRenderer::DrawItems(int current_world, int current_mode) { } i++; } - - } void OverworldEntityRenderer::DrawSprites(int current_world, int game_state, - int current_mode) { + int current_mode) { int i = 0; for (auto& sprite : *overworld_->mutable_sprites(game_state)) { // Filter sprites by current world - only show sprites for the current world @@ -129,20 +123,19 @@ void OverworldEntityRenderer::DrawSprites(int current_world, int game_state, canvas_->DrawRect(sprite_x, sprite_y, 16, 16, GetSpriteColor()); if (IsMouseHoveringOverEntity(sprite, canvas_->zero_point(), - canvas_->scrolling())) { + canvas_->scrolling())) { hovered_entity_ = &sprite; } - if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) { if ((*sprite_previews_)[sprite.id()].is_active()) { canvas_->DrawBitmap((*sprite_previews_)[sprite.id()], sprite_x, - sprite_y, 2.0f); + sprite_y, 2.0f); } } canvas_->DrawText(absl::StrFormat("%s", sprite.name()), sprite_x, - sprite_y); + sprite_y); // Restore original coordinates sprite.x_ = original_x; @@ -150,10 +143,7 @@ void OverworldEntityRenderer::DrawSprites(int current_world, int game_state, } i++; } - - } } // namespace editor } // namespace yaze - diff --git a/src/app/editor/overworld/overworld_entity_renderer.h b/src/app/editor/overworld/overworld_entity_renderer.h index e7a79824..95933e27 100644 --- a/src/app/editor/overworld/overworld_entity_renderer.h +++ b/src/app/editor/overworld/overworld_entity_renderer.h @@ -5,9 +5,9 @@ #include "app/gfx/core/bitmap.h" #include "app/gui/canvas/canvas.h" +#include "imgui/imgui.h" #include "zelda3/common.h" #include "zelda3/overworld/overworld.h" -#include "imgui/imgui.h" namespace yaze { namespace editor { @@ -24,15 +24,16 @@ class OverworldEditor; // Forward declaration class OverworldEntityRenderer { public: OverworldEntityRenderer(zelda3::Overworld* overworld, gui::Canvas* canvas, - std::vector* sprite_previews) - : overworld_(overworld), canvas_(canvas), + std::vector* sprite_previews) + : overworld_(overworld), + canvas_(canvas), sprite_previews_(sprite_previews) {} // Main rendering methods void DrawEntrances(ImVec2 canvas_p0, ImVec2 scrolling, int current_world, - int current_mode); + int current_mode); void DrawExits(ImVec2 canvas_p0, ImVec2 scrolling, int current_world, - int current_mode); + int current_mode); void DrawItems(int current_world, int current_mode); void DrawSprites(int current_world, int game_state, int current_mode); @@ -49,4 +50,3 @@ class OverworldEntityRenderer { } // namespace yaze #endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_ENTITY_RENDERER_H - diff --git a/src/app/editor/overworld/scratch_space.cc b/src/app/editor/overworld/scratch_space.cc index c6f310f0..a4ca0988 100644 --- a/src/app/editor/overworld/scratch_space.cc +++ b/src/app/editor/overworld/scratch_space.cc @@ -10,30 +10,29 @@ #include "absl/status/status.h" #include "absl/strings/str_format.h" -#include "core/asar_wrapper.h" -#include "app/gfx/debug/performance/performance_profiler.h" -#include "app/platform/window.h" #include "app/editor/overworld/entity.h" #include "app/editor/overworld/map_properties.h" #include "app/editor/overworld/tile16_editor.h" -#include "app/gfx/resource/arena.h" #include "app/gfx/core/bitmap.h" #include "app/gfx/debug/performance/performance_profiler.h" -#include "app/gfx/types/snes_palette.h" #include "app/gfx/render/tilemap.h" +#include "app/gfx/resource/arena.h" +#include "app/gfx/types/snes_palette.h" #include "app/gui/canvas/canvas.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" #include "app/gui/core/style.h" +#include "app/platform/window.h" #include "app/rom.h" -#include "zelda3/common.h" -#include "zelda3/overworld/overworld.h" -#include "zelda3/overworld/overworld_map.h" +#include "core/asar_wrapper.h" #include "imgui/imgui.h" #include "imgui_memory_editor.h" #include "util/hex.h" #include "util/log.h" #include "util/macro.h" +#include "zelda3/common.h" +#include "zelda3/overworld/overworld.h" +#include "zelda3/overworld/overworld_map.h" namespace yaze::editor { @@ -229,8 +228,7 @@ void OverworldEditor::DrawScratchSpacePattern() { return; } - const auto& tile_ids = - dependencies_.shared_clipboard->overworld_tile16_ids; + const auto& tile_ids = dependencies_.shared_clipboard->overworld_tile16_ids; int pattern_width = dependencies_.shared_clipboard->overworld_width; int pattern_height = dependencies_.shared_clipboard->overworld_height; @@ -320,8 +318,8 @@ void OverworldEditor::UpdateScratchBitmapTile(int tile_x, int tile_y, scratch_slot.scratch_bitmap.set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, &scratch_slot.scratch_bitmap); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + &scratch_slot.scratch_bitmap); scratch_slot.in_use = true; } @@ -366,7 +364,7 @@ absl::Status OverworldEditor::SaveCurrentSelectionToScratch(int slot) { scratch_spaces_[slot].scratch_bitmap.SetPalette(palette_); // Queue texture creation via Arena's deferred system gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, + gfx::Arena::TextureCommandType::CREATE, &scratch_spaces_[slot].scratch_bitmap); } @@ -404,7 +402,6 @@ absl::Status OverworldEditor::SaveCurrentSelectionToScratch(int slot) { scratch_spaces_[slot].in_use = true; } - return absl::OkStatus(); } @@ -439,10 +436,11 @@ absl::Status OverworldEditor::ClearScratchSpace(int slot) { scratch_spaces_[slot].scratch_bitmap.set_modified(true); // Queue texture update via Arena's deferred system gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, &scratch_spaces_[slot].scratch_bitmap); + gfx::Arena::TextureCommandType::UPDATE, + &scratch_spaces_[slot].scratch_bitmap); } return absl::OkStatus(); } -} \ No newline at end of file +} // namespace yaze::editor \ No newline at end of file diff --git a/src/app/editor/overworld/tile16_editor.cc b/src/app/editor/overworld/tile16_editor.cc index 8a8355ca..62feeb42 100644 --- a/src/app/editor/overworld/tile16_editor.cc +++ b/src/app/editor/overworld/tile16_editor.cc @@ -3,20 +3,20 @@ #include #include "absl/status/status.h" -#include "app/gfx/resource/arena.h" -#include "app/gfx/core/bitmap.h" #include "app/gfx/backend/irenderer.h" +#include "app/gfx/core/bitmap.h" #include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" #include "app/gui/canvas/canvas.h" #include "app/gui/core/input.h" #include "app/gui/core/style.h" #include "app/rom.h" -#include "zelda3/overworld/overworld.h" #include "imgui/imgui.h" #include "util/hex.h" #include "util/log.h" #include "util/macro.h" +#include "zelda3/overworld/overworld.h" namespace yaze { namespace editor { @@ -347,8 +347,8 @@ absl::Status Tile16Editor::RefreshTile16Blockset() { tile16_blockset_->atlas.set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, &tile16_blockset_->atlas); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + &tile16_blockset_->atlas); util::logf("Tile16 blockset refreshed and regenerated"); return absl::OkStatus(); @@ -517,8 +517,8 @@ absl::Status Tile16Editor::RegenerateTile16BitmapFromROM() { } // Queue texture creation via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + ¤t_tile16_bmp_); util::logf("Regenerated Tile16 bitmap for tile %d from ROM data", current_tile16_); @@ -610,8 +610,8 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 pos, // Mark the bitmap as modified and queue texture update current_tile16_bmp_.set_modified(true); - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); // Update ROM data when painting to tile16 auto* tile_data = GetCurrentTile16Data(); @@ -902,8 +902,7 @@ absl::Status Tile16Editor::UpdateTile16Edit() { // CRITICAL FIX: Handle tile painting with simple click instead of click+drag // Draw the preview first - tile16_edit_canvas_.DrawTilePainter( - display_tile, 8, 4); + tile16_edit_canvas_.DrawTilePainter(display_tile, 8, 4); // Check for simple click to paint tile8 to tile16 if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { @@ -913,10 +912,8 @@ absl::Status Tile16Editor::UpdateTile16Edit() { io.MousePos.y - canvas_pos.y); // Convert canvas coordinates to tile16 coordinates with dynamic zoom - int tile_x = static_cast(mouse_pos.x / - 4); - int tile_y = static_cast(mouse_pos.y / - 4); + int tile_x = static_cast(mouse_pos.x / 4); + int tile_y = static_cast(mouse_pos.y / 4); // Clamp to valid range tile_x = std::max(0, std::min(15, tile_x)); @@ -937,10 +934,8 @@ absl::Status Tile16Editor::UpdateTile16Edit() { io.MousePos.y - canvas_pos.y); // Convert with dynamic zoom - int tile_x = static_cast(mouse_pos.x / - 4); - int tile_y = static_cast(mouse_pos.y / - 4); + int tile_x = static_cast(mouse_pos.x / 4); + int tile_y = static_cast(mouse_pos.y / 4); // Clamp to valid range tile_x = std::max(0, std::min(15, tile_x)); @@ -1369,8 +1364,8 @@ absl::Status Tile16Editor::SetCurrentTile(int tile_id) { } // Queue texture creation via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + ¤t_tile16_bmp_); // Simple success logging util::logf("SetCurrentTile: loaded tile %d successfully", tile_id); @@ -1390,8 +1385,8 @@ absl::Status Tile16Editor::CopyTile16ToClipboard(int tile_id) { clipboard_tile16_.SetPalette(tile16_blockset_->atlas.palette()); } // Queue texture creation via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, &clipboard_tile16_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + &clipboard_tile16_); clipboard_has_data_ = true; return absl::OkStatus(); @@ -1406,8 +1401,8 @@ absl::Status Tile16Editor::PasteTile16FromClipboard() { current_tile16_bmp_.Create(16, 16, 8, clipboard_tile16_.vector()); current_tile16_bmp_.SetPalette(clipboard_tile16_.palette()); // Queue texture creation via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + ¤t_tile16_bmp_); return absl::OkStatus(); } @@ -1421,8 +1416,8 @@ absl::Status Tile16Editor::SaveTile16ToScratchSpace(int slot) { scratch_space_[slot].Create(16, 16, 8, current_tile16_bmp_.vector()); scratch_space_[slot].SetPalette(current_tile16_bmp_.palette()); // Queue texture creation via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, &scratch_space_[slot]); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + &scratch_space_[slot]); scratch_space_used_[slot] = true; return absl::OkStatus(); @@ -1441,8 +1436,8 @@ absl::Status Tile16Editor::LoadTile16FromScratchSpace(int slot) { current_tile16_bmp_.Create(16, 16, 8, scratch_space_[slot].vector()); current_tile16_bmp_.SetPalette(scratch_space_[slot].palette()); // Queue texture creation via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::CREATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE, + ¤t_tile16_bmp_); return absl::OkStatus(); } @@ -1487,8 +1482,8 @@ absl::Status Tile16Editor::FlipTile16Horizontal() { current_tile16_bmp_.set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); return absl::OkStatus(); } @@ -1522,8 +1517,8 @@ absl::Status Tile16Editor::FlipTile16Vertical() { current_tile16_bmp_.set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); return absl::OkStatus(); } @@ -1557,8 +1552,8 @@ absl::Status Tile16Editor::RotateTile16() { current_tile16_bmp_.set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); return absl::OkStatus(); } @@ -1596,8 +1591,8 @@ absl::Status Tile16Editor::FillTile16WithTile8(int tile8_id) { current_tile16_bmp_.set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); return absl::OkStatus(); } @@ -1614,8 +1609,8 @@ absl::Status Tile16Editor::ClearTile16() { current_tile16_bmp_.set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); return absl::OkStatus(); } @@ -1719,8 +1714,8 @@ absl::Status Tile16Editor::Undo() { priority_tile = previous_state.priority; // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); undo_stack_.pop_back(); return absl::OkStatus(); @@ -1744,8 +1739,8 @@ absl::Status Tile16Editor::Redo() { priority_tile = next_state.priority; // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_tile16_bmp_); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_tile16_bmp_); redo_stack_.pop_back(); return absl::OkStatus(); @@ -2111,8 +2106,8 @@ absl::Status Tile16Editor::UpdateTile8Palette(int tile8_id) { current_gfx_individual_[tile8_id].set_modified(true); // Queue texture update via Arena's deferred system - gfx::Arena::Get().QueueTextureCommand( - gfx::Arena::TextureCommandType::UPDATE, ¤t_gfx_individual_[tile8_id]); + gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE, + ¤t_gfx_individual_[tile8_id]); util::logf("Updated tile8 %d with palette slot %d (palette size: %zu colors)", tile8_id, current_palette_, display_palette.size()); diff --git a/src/app/editor/overworld/tile16_editor.h b/src/app/editor/overworld/tile16_editor.h index ab22f10d..7b4499eb 100644 --- a/src/app/editor/overworld/tile16_editor.h +++ b/src/app/editor/overworld/tile16_editor.h @@ -13,9 +13,9 @@ #include "app/gfx/types/snes_tile.h" #include "app/gui/canvas/canvas.h" #include "app/gui/core/input.h" -#include "util/log.h" #include "app/rom.h" #include "imgui/imgui.h" +#include "util/log.h" #include "util/notify.h" namespace yaze { @@ -54,7 +54,8 @@ class Tile16Editor : public gfx::GfxContext { absl::Status SaveLayoutToScratch(int slot); absl::Status LoadLayoutFromScratch(int slot); - absl::Status DrawToCurrentTile16(ImVec2 pos, const gfx::Bitmap* source_tile = nullptr); + absl::Status DrawToCurrentTile16(ImVec2 pos, + const gfx::Bitmap* source_tile = nullptr); absl::Status UpdateTile16Edit(); @@ -111,16 +112,15 @@ class Tile16Editor : public gfx::GfxContext { absl::Status UpdateTile8Palette(int tile8_id); absl::Status RefreshAllPalettes(); void DrawPaletteSettings(); - + // Get the appropriate palette slot for current graphics sheet int GetPaletteSlotForSheet(int sheet_index) const; - + // NEW: Core palette mapping methods for fixing color alignment int GetActualPaletteSlot(int palette_button, int sheet_index) const; int GetSheetIndexForTile8(int tile8_id) const; int GetActualPaletteSlotForCurrentTile16() const; - - + // ROM data access and modification absl::Status UpdateROMTile16Data(); absl::Status RefreshTile16Blockset(); @@ -128,39 +128,45 @@ class Tile16Editor : public gfx::GfxContext { absl::Status RegenerateTile16BitmapFromROM(); absl::Status UpdateBlocksetBitmap(); absl::Status PickTile8FromTile16(const ImVec2& position); - + // Manual tile8 input controls void DrawManualTile8Inputs(); void set_rom(Rom* rom) { rom_ = rom; } Rom* rom() const { return rom_; } - + // Set the palette from overworld to ensure color consistency - void set_palette(const gfx::SnesPalette& palette) { + void set_palette(const gfx::SnesPalette& palette) { palette_ = palette; - + // Store the complete 256-color overworld palette if (palette.size() >= 256) { overworld_palette_ = palette; - util::logf("Tile16 editor received complete overworld palette with %zu colors", palette.size()); + util::logf( + "Tile16 editor received complete overworld palette with %zu colors", + palette.size()); } else { - util::logf("Warning: Received incomplete palette with %zu colors", palette.size()); + util::logf("Warning: Received incomplete palette with %zu colors", + palette.size()); overworld_palette_ = palette; } - + // CRITICAL FIX: Load tile8 graphics now that we have the proper palette if (rom_ && current_gfx_bmp_.is_active()) { auto status = LoadTile8(); if (!status.ok()) { - util::logf("Failed to load tile8 graphics with new palette: %s", status.message().data()); + util::logf("Failed to load tile8 graphics with new palette: %s", + status.message().data()); } else { - util::logf("Successfully loaded tile8 graphics with complete overworld palette"); + util::logf( + "Successfully loaded tile8 graphics with complete overworld " + "palette"); } } - + util::logf("Tile16 editor palette coordination complete"); } - + // Callback for when changes are committed to notify parent editor void set_on_changes_committed(std::function callback) { on_changes_committed_ = callback; @@ -225,8 +231,10 @@ class Tile16Editor : public gfx::GfxContext { // Palette management settings bool show_palette_settings_ = false; int current_palette_group_ = 0; // 0=overworld_main, 1=aux1, 2=aux2, etc. - uint8_t palette_normalization_mask_ = 0xFF; // Default 8-bit mask (preserve full palette index) - bool auto_normalize_pixels_ = false; // Disabled by default to preserve palette offsets + uint8_t palette_normalization_mask_ = + 0xFF; // Default 8-bit mask (preserve full palette index) + bool auto_normalize_pixels_ = + false; // Disabled by default to preserve palette offsets // Performance tracking std::chrono::steady_clock::time_point last_edit_time_; @@ -244,9 +252,10 @@ class Tile16Editor : public gfx::GfxContext { gfx::Bitmap tile16_blockset_bmp_; // Canvas for editing the selected tile - optimized for 2x2 grid of 8x8 tiles (16x16 total) - gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", - ImVec2(64, 64), // Fixed 64x64 display size (16x16 pixels at 4x scale) - gui::CanvasGridSize::k8x8, 8.0F}; // 8x8 grid with 4x scale for clarity + gui::Canvas tile16_edit_canvas_{ + "Tile16EditCanvas", + ImVec2(64, 64), // Fixed 64x64 display size (16x16 pixels at 4x scale) + gui::CanvasGridSize::k8x8, 8.0F}; // 8x8 grid with 4x scale for clarity gfx::Bitmap current_tile16_bmp_; // Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_ @@ -256,7 +265,8 @@ class Tile16Editor : public gfx::GfxContext { gui::CanvasGridSize::k32x32}; gfx::Bitmap current_gfx_bmp_; - gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders, ImVec2(0, 0)}; + gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders, + ImVec2(0, 0)}; gfx::Tilemap* tile16_blockset_ = nullptr; std::vector current_gfx_individual_; @@ -266,10 +276,10 @@ class Tile16Editor : public gfx::GfxContext { gfx::SnesPalette overworld_palette_; // Complete 256-color overworld palette absl::Status status_; - + // Callback to notify parent editor when changes are committed std::function on_changes_committed_; - + // Instance variable to store current tile16 data for proper persistence gfx::Tile16 current_tile16_data_; }; diff --git a/src/app/editor/overworld/ui_constants.h b/src/app/editor/overworld/ui_constants.h index 4def2bf7..5a48e322 100644 --- a/src/app/editor/overworld/ui_constants.h +++ b/src/app/editor/overworld/ui_constants.h @@ -5,26 +5,16 @@ namespace yaze { namespace editor { // Game State Labels -inline constexpr const char* kGameStateNames[] = { - "Rain & Rescue Zelda", - "Pendants", - "Crystals" -}; +inline constexpr const char* kGameStateNames[] = {"Rain & Rescue Zelda", + "Pendants", "Crystals"}; // World Labels -inline constexpr const char* kWorldNames[] = { - "Light World", - "Dark World", - "Special World" -}; +inline constexpr const char* kWorldNames[] = {"Light World", "Dark World", + "Special World"}; // Area Size Names -inline constexpr const char* kAreaSizeNames[] = { - "Small (1x1)", - "Large (2x2)", - "Wide (2x1)", - "Tall (1x2)" -}; +inline constexpr const char* kAreaSizeNames[] = {"Small (1x1)", "Large (2x2)", + "Wide (2x1)", "Tall (1x2)"}; // UI Styling Constants inline constexpr float kInputFieldSize = 30.f; diff --git a/src/app/editor/palette/palette_editor.cc b/src/app/editor/palette/palette_editor.cc index 65859b8c..15c69a69 100644 --- a/src/app/editor/palette/palette_editor.cc +++ b/src/app/editor/palette/palette_editor.cc @@ -3,11 +3,11 @@ #include "absl/status/status.h" #include "absl/strings/str_cat.h" -#include "app/gfx/util/palette_manager.h" #include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/types/snes_palette.h" -#include "app/gui/core/color.h" +#include "app/gfx/util/palette_manager.h" #include "app/gui/app/editor_layout.h" +#include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "imgui/imgui.h" @@ -64,8 +64,10 @@ int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) { int w = vsnprintf(buf, buf_size, fmt, args); #endif va_end(args); - if (buf == nullptr) return w; - if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1; + if (buf == nullptr) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; buf[w] = 0; return w; } @@ -137,8 +139,7 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) { SameLine(); Text("Previous"); - if (Button("Update Map Palette")) { - } + if (Button("Update Map Palette")) {} ColorButton( "##current", color, @@ -157,7 +158,8 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) { Text("Palette"); for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) { PushID(n); - if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y); + if ((n % 8) != 0) + SameLine(0.0f, GetStyle().ItemSpacing.y); if (ColorButton("##palette", current_palette[n], kPalButtonFlags, ImVec2(20, 20))) @@ -185,119 +187,98 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) { void PaletteEditor::Initialize() { // Register all cards with EditorCardRegistry (done once during initialization) - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - card_registry->RegisterCard({ - .card_id = "palette.control_panel", - .display_name = "Palette Controls", - .icon = ICON_MD_PALETTE, - .category = "Palette", - .shortcut_hint = "Ctrl+Shift+P", - .visibility_flag = &show_control_panel_, - .priority = 10 - }); + card_registry->RegisterCard({.card_id = "palette.control_panel", + .display_name = "Palette Controls", + .icon = ICON_MD_PALETTE, + .category = "Palette", + .shortcut_hint = "Ctrl+Shift+P", + .visibility_flag = &show_control_panel_, + .priority = 10}); - card_registry->RegisterCard({ - .card_id = "palette.ow_main", - .display_name = "Overworld Main", - .icon = ICON_MD_LANDSCAPE, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+1", - .visibility_flag = &show_ow_main_card_, - .priority = 20 - }); + card_registry->RegisterCard({.card_id = "palette.ow_main", + .display_name = "Overworld Main", + .icon = ICON_MD_LANDSCAPE, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+1", + .visibility_flag = &show_ow_main_card_, + .priority = 20}); - card_registry->RegisterCard({ - .card_id = "palette.ow_animated", - .display_name = "Overworld Animated", - .icon = ICON_MD_WATER, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+2", - .visibility_flag = &show_ow_animated_card_, - .priority = 30 - }); + card_registry->RegisterCard({.card_id = "palette.ow_animated", + .display_name = "Overworld Animated", + .icon = ICON_MD_WATER, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+2", + .visibility_flag = &show_ow_animated_card_, + .priority = 30}); - card_registry->RegisterCard({ - .card_id = "palette.dungeon_main", - .display_name = "Dungeon Main", - .icon = ICON_MD_CASTLE, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+3", - .visibility_flag = &show_dungeon_main_card_, - .priority = 40 - }); + card_registry->RegisterCard({.card_id = "palette.dungeon_main", + .display_name = "Dungeon Main", + .icon = ICON_MD_CASTLE, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+3", + .visibility_flag = &show_dungeon_main_card_, + .priority = 40}); - card_registry->RegisterCard({ - .card_id = "palette.sprites", - .display_name = "Global Sprite Palettes", - .icon = ICON_MD_PETS, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+4", - .visibility_flag = &show_sprite_card_, - .priority = 50 - }); + card_registry->RegisterCard({.card_id = "palette.sprites", + .display_name = "Global Sprite Palettes", + .icon = ICON_MD_PETS, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+4", + .visibility_flag = &show_sprite_card_, + .priority = 50}); - card_registry->RegisterCard({ - .card_id = "palette.sprites_aux1", - .display_name = "Sprites Aux 1", - .icon = ICON_MD_FILTER_1, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+7", - .visibility_flag = &show_sprites_aux1_card_, - .priority = 51 - }); + card_registry->RegisterCard({.card_id = "palette.sprites_aux1", + .display_name = "Sprites Aux 1", + .icon = ICON_MD_FILTER_1, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+7", + .visibility_flag = &show_sprites_aux1_card_, + .priority = 51}); - card_registry->RegisterCard({ - .card_id = "palette.sprites_aux2", - .display_name = "Sprites Aux 2", - .icon = ICON_MD_FILTER_2, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+8", - .visibility_flag = &show_sprites_aux2_card_, - .priority = 52 - }); + card_registry->RegisterCard({.card_id = "palette.sprites_aux2", + .display_name = "Sprites Aux 2", + .icon = ICON_MD_FILTER_2, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+8", + .visibility_flag = &show_sprites_aux2_card_, + .priority = 52}); - card_registry->RegisterCard({ - .card_id = "palette.sprites_aux3", - .display_name = "Sprites Aux 3", - .icon = ICON_MD_FILTER_3, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+9", - .visibility_flag = &show_sprites_aux3_card_, - .priority = 53 - }); + card_registry->RegisterCard({.card_id = "palette.sprites_aux3", + .display_name = "Sprites Aux 3", + .icon = ICON_MD_FILTER_3, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+9", + .visibility_flag = &show_sprites_aux3_card_, + .priority = 53}); - card_registry->RegisterCard({ - .card_id = "palette.equipment", - .display_name = "Equipment Palettes", - .icon = ICON_MD_SHIELD, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+5", - .visibility_flag = &show_equipment_card_, - .priority = 60 - }); + card_registry->RegisterCard({.card_id = "palette.equipment", + .display_name = "Equipment Palettes", + .icon = ICON_MD_SHIELD, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+5", + .visibility_flag = &show_equipment_card_, + .priority = 60}); - card_registry->RegisterCard({ - .card_id = "palette.quick_access", - .display_name = "Quick Access", - .icon = ICON_MD_COLOR_LENS, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+Q", - .visibility_flag = &show_quick_access_, - .priority = 70 - }); + card_registry->RegisterCard({.card_id = "palette.quick_access", + .display_name = "Quick Access", + .icon = ICON_MD_COLOR_LENS, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+Q", + .visibility_flag = &show_quick_access_, + .priority = 70}); + + card_registry->RegisterCard({.card_id = "palette.custom", + .display_name = "Custom Palette", + .icon = ICON_MD_BRUSH, + .category = "Palette", + .shortcut_hint = "Ctrl+Alt+C", + .visibility_flag = &show_custom_palette_, + .priority = 80}); - card_registry->RegisterCard({ - .card_id = "palette.custom", - .display_name = "Custom Palette", - .icon = ICON_MD_BRUSH, - .category = "Palette", - .shortcut_hint = "Ctrl+Alt+C", - .visibility_flag = &show_custom_palette_, - .priority = 80 - }); - // Show control panel by default when Palette Editor is activated show_control_panel_ = true; } @@ -339,7 +320,8 @@ absl::Status PaletteEditor::Update() { gui::EditorCard loading_card("Palette Editor Loading", ICON_MD_PALETTE); loading_card.SetDefaultSize(400, 200); if (loading_card.Begin()) { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Loading palette data..."); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + "Loading palette data..."); ImGui::TextWrapped("Palette cards will appear once ROM data is loaded."); } loading_card.End(); @@ -356,11 +338,10 @@ absl::Status PaletteEditor::Update() { // Draw floating icon button to reopen ImGui::SetNextWindowPos(ImVec2(10, 100)); ImGui::SetNextWindowSize(ImVec2(50, 50)); - ImGuiWindowFlags icon_flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoDocking; + ImGuiWindowFlags icon_flags = + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoDocking; if (ImGui::Begin("##PaletteControlIcon", nullptr, icon_flags)) { if (ImGui::Button(ICON_MD_PALETTE, ImVec2(40, 40))) { @@ -377,52 +358,68 @@ absl::Status PaletteEditor::Update() { // Draw all independent palette cards // Each card has its own show_ flag that needs to be synced with our visibility flags if (show_ow_main_card_ && ow_main_card_) { - if (!ow_main_card_->IsVisible()) ow_main_card_->Show(); + if (!ow_main_card_->IsVisible()) + ow_main_card_->Show(); ow_main_card_->Draw(); // Sync back if user closed the card with X button - if (!ow_main_card_->IsVisible()) show_ow_main_card_ = false; + if (!ow_main_card_->IsVisible()) + show_ow_main_card_ = false; } if (show_ow_animated_card_ && ow_animated_card_) { - if (!ow_animated_card_->IsVisible()) ow_animated_card_->Show(); + if (!ow_animated_card_->IsVisible()) + ow_animated_card_->Show(); ow_animated_card_->Draw(); - if (!ow_animated_card_->IsVisible()) show_ow_animated_card_ = false; + if (!ow_animated_card_->IsVisible()) + show_ow_animated_card_ = false; } if (show_dungeon_main_card_ && dungeon_main_card_) { - if (!dungeon_main_card_->IsVisible()) dungeon_main_card_->Show(); + if (!dungeon_main_card_->IsVisible()) + dungeon_main_card_->Show(); dungeon_main_card_->Draw(); - if (!dungeon_main_card_->IsVisible()) show_dungeon_main_card_ = false; + if (!dungeon_main_card_->IsVisible()) + show_dungeon_main_card_ = false; } if (show_sprite_card_ && sprite_card_) { - if (!sprite_card_->IsVisible()) sprite_card_->Show(); + if (!sprite_card_->IsVisible()) + sprite_card_->Show(); sprite_card_->Draw(); - if (!sprite_card_->IsVisible()) show_sprite_card_ = false; + if (!sprite_card_->IsVisible()) + show_sprite_card_ = false; } if (show_sprites_aux1_card_ && sprites_aux1_card_) { - if (!sprites_aux1_card_->IsVisible()) sprites_aux1_card_->Show(); + if (!sprites_aux1_card_->IsVisible()) + sprites_aux1_card_->Show(); sprites_aux1_card_->Draw(); - if (!sprites_aux1_card_->IsVisible()) show_sprites_aux1_card_ = false; + if (!sprites_aux1_card_->IsVisible()) + show_sprites_aux1_card_ = false; } if (show_sprites_aux2_card_ && sprites_aux2_card_) { - if (!sprites_aux2_card_->IsVisible()) sprites_aux2_card_->Show(); + if (!sprites_aux2_card_->IsVisible()) + sprites_aux2_card_->Show(); sprites_aux2_card_->Draw(); - if (!sprites_aux2_card_->IsVisible()) show_sprites_aux2_card_ = false; + if (!sprites_aux2_card_->IsVisible()) + show_sprites_aux2_card_ = false; } if (show_sprites_aux3_card_ && sprites_aux3_card_) { - if (!sprites_aux3_card_->IsVisible()) sprites_aux3_card_->Show(); + if (!sprites_aux3_card_->IsVisible()) + sprites_aux3_card_->Show(); sprites_aux3_card_->Draw(); - if (!sprites_aux3_card_->IsVisible()) show_sprites_aux3_card_ = false; + if (!sprites_aux3_card_->IsVisible()) + show_sprites_aux3_card_ = false; } if (show_equipment_card_ && equipment_card_) { - if (!equipment_card_->IsVisible()) equipment_card_->Show(); + if (!equipment_card_->IsVisible()) + equipment_card_->Show(); equipment_card_->Draw(); - if (!equipment_card_->IsVisible()) show_equipment_card_ = false; + if (!equipment_card_->IsVisible()) + show_equipment_card_ = false; } // Draw quick access and custom palette cards @@ -475,7 +472,8 @@ void PaletteEditor::DrawQuickAccessTab() { Text("Recently Used Colors"); for (int i = 0; i < recently_used_colors_.size(); i++) { PushID(i); - if (i % 8 != 0) SameLine(); + if (i % 8 != 0) + SameLine(); ImVec4 displayColor = gui::ConvertSnesColorToImVec4(recently_used_colors_[i]); if (ImGui::ColorButton("##recent", displayColor)) { @@ -508,7 +506,8 @@ void PaletteEditor::DrawCustomPalette() { ImGuiWindowFlags_HorizontalScrollbar)) { for (int i = 0; i < custom_palette_.size(); i++) { PushID(i); - if (i > 0) SameLine(0.0f, GetStyle().ItemSpacing.y); + if (i > 0) + SameLine(0.0f, GetStyle().ItemSpacing.y); // Enhanced color button with context menu and drag-drop support ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]); @@ -588,7 +587,8 @@ void PaletteEditor::DrawCustomPalette() { } } -absl::Status PaletteEditor::DrawPaletteGroup(int category, bool /*right_side*/) { +absl::Status PaletteEditor::DrawPaletteGroup(int category, + bool /*right_side*/) { if (!rom()->is_loaded()) { return absl::NotFoundError("ROM not open, no palettes to display"); } @@ -613,7 +613,8 @@ absl::Status PaletteEditor::DrawPaletteGroup(int category, bool /*right_side*/) for (int n = 0; n < pal_size; n++) { PushID(n); - if (n > 0 && n % 8 != 0) SameLine(0.0f, 2.0f); + if (n > 0 && n % 8 != 0) + SameLine(0.0f, 2.0f); auto popup_id = absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n); @@ -685,22 +686,27 @@ absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i, Separator(); - if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy"); + if (Button("Copy as..", ImVec2(-1, 0))) + OpenPopup("Copy"); if (BeginPopup("Copy")) { CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0], col[1], col[2]); - if (Selectable(buf)) SetClipboardText(buf); + if (Selectable(buf)) + SetClipboardText(buf); CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb); - if (Selectable(buf)) SetClipboardText(buf); + if (Selectable(buf)) + SetClipboardText(buf); CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb); - if (Selectable(buf)) SetClipboardText(buf); + if (Selectable(buf)) + SetClipboardText(buf); // SNES Format CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X", ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f))); - if (Selectable(buf)) SetClipboardText(buf); + if (Selectable(buf)) + SetClipboardText(buf); EndPopup(); } @@ -760,7 +766,8 @@ void PaletteEditor::DrawControlPanel() { ImGuiWindowFlags flags = ImGuiWindowFlags_None; - if (ImGui::Begin(ICON_MD_PALETTE " Palette Controls", &show_control_panel_, flags)) { + if (ImGui::Begin(ICON_MD_PALETTE " Palette Controls", &show_control_panel_, + flags)) { // Toolbar with quick toggles DrawToolset(); @@ -858,8 +865,9 @@ void PaletteEditor::DrawControlPanel() { size_t modified_count = gfx::PaletteManager::Get().GetModifiedColorCount(); ImGui::BeginDisabled(!has_unsaved); - if (ImGui::Button(absl::StrFormat("Save All (%zu colors)", modified_count).c_str(), - ImVec2(-1, 0))) { + if (ImGui::Button( + absl::StrFormat("Save All (%zu colors)", modified_count).c_str(), + ImVec2(-1, 0))) { auto status = gfx::PaletteManager::Get().SaveAllToRom(); if (!status.ok()) { // TODO: Show error toast/notification @@ -895,7 +903,8 @@ void PaletteEditor::DrawControlPanel() { ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Discard all unsaved changes?"); ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), - "This will revert %zu modified colors.", modified_count); + "This will revert %zu modified colors.", + modified_count); ImGui::Separator(); if (ImGui::Button("Discard", ImVec2(120, 0))) { @@ -912,7 +921,8 @@ void PaletteEditor::DrawControlPanel() { // Error popup for save failures if (ImGui::BeginPopupModal("SaveError", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Failed to save changes"); + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), + "Failed to save changes"); ImGui::Text("An error occurred while saving to ROM."); ImGui::Separator(); @@ -930,14 +940,15 @@ void PaletteEditor::DrawControlPanel() { } if (ImGui::BeginPopup("PaletteCardManager")) { - ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), - "%s Palette Card Manager", ICON_MD_PALETTE); + ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), + "%s Palette Card Manager", ICON_MD_PALETTE); ImGui::Separator(); - + // View menu section now handled by EditorCardRegistry in EditorManager - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - + ImGui::EndPopup(); } @@ -992,7 +1003,8 @@ void PaletteEditor::DrawQuickAccessCard() { } else { for (int i = 0; i < recently_used_colors_.size(); i++) { PushID(i); - if (i % 8 != 0) SameLine(); + if (i % 8 != 0) + SameLine(); ImVec4 displayColor = gui::ConvertSnesColorToImVec4(recently_used_colors_[i]); if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags, @@ -1011,8 +1023,7 @@ void PaletteEditor::DrawQuickAccessCard() { } void PaletteEditor::DrawCustomPaletteCard() { - gui::EditorCard card("Custom Palette", ICON_MD_BRUSH, - &show_custom_palette_); + gui::EditorCard card("Custom Palette", ICON_MD_BRUSH, &show_custom_palette_); card.SetDefaultSize(420, 200); card.SetPosition(gui::EditorCard::Position::Bottom); @@ -1030,13 +1041,14 @@ void PaletteEditor::DrawCustomPaletteCard() { } else { for (int i = 0; i < custom_palette_.size(); i++) { PushID(i); - if (i > 0 && i % 16 != 0) SameLine(0.0f, 2.0f); + if (i > 0 && i % 16 != 0) + SameLine(0.0f, 2.0f); // Enhanced color button with context menu and drag-drop support ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]); - bool open_color_picker = ImGui::ColorButton( - absl::StrFormat("##customPal%d", i).c_str(), displayColor, - kPalButtonFlags, ImVec2(28, 28)); + bool open_color_picker = + ImGui::ColorButton(absl::StrFormat("##customPal%d", i).c_str(), + displayColor, kPalButtonFlags, ImVec2(28, 28)); if (open_color_picker) { current_color_ = custom_palette_[i]; @@ -1122,7 +1134,8 @@ void PaletteEditor::DrawCustomPaletteCard() { } } -void PaletteEditor::JumpToPalette(const std::string& group_name, int palette_index) { +void PaletteEditor::JumpToPalette(const std::string& group_name, + int palette_index) { // Hide all cards first show_ow_main_card_ = false; show_ow_animated_card_ = false; diff --git a/src/app/editor/palette/palette_group_card.cc b/src/app/editor/palette/palette_group_card.cc index c4dc88de..6fb2ec47 100644 --- a/src/app/editor/palette/palette_group_card.cc +++ b/src/app/editor/palette/palette_group_card.cc @@ -3,8 +3,8 @@ #include #include "absl/strings/str_format.h" -#include "app/gfx/util/palette_manager.h" #include "app/gfx/types/snes_palette.h" +#include "app/gfx/util/palette_manager.h" #include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "app/gui/core/layout_helpers.h" @@ -15,18 +15,15 @@ namespace yaze { namespace editor { using namespace yaze::gui; +using gui::DangerButton; +using gui::PrimaryButton; +using gui::SectionHeader; using gui::ThemedButton; using gui::ThemedIconButton; -using gui::PrimaryButton; -using gui::DangerButton; -using gui::SectionHeader; PaletteGroupCard::PaletteGroupCard(const std::string& group_name, - const std::string& display_name, - Rom* rom) - : group_name_(group_name), - display_name_(display_name), - rom_(rom) { + const std::string& display_name, Rom* rom) + : group_name_(group_name), display_name_(display_name), rom_(rom) { // Note: We can't call GetPaletteGroup() here because it's a pure virtual function // and the derived class isn't fully constructed yet. Original palettes will be // loaded on first Draw() call instead. @@ -46,10 +43,12 @@ void PaletteGroupCard::Draw() { ImGui::Separator(); // Two-column layout: Grid on left, picker on right - if (ImGui::BeginTable("##PaletteCardLayout", 2, - ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { + if (ImGui::BeginTable( + "##PaletteCardLayout", 2, + ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { ImGui::TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch, + 0.4f); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -123,7 +122,7 @@ void PaletteGroupCard::DrawToolbar() { } } ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s %zu modified", - ICON_MD_EDIT, modified_count); + ICON_MD_EDIT, modified_count); } ImGui::SameLine(); @@ -169,7 +168,8 @@ void PaletteGroupCard::DrawToolbar() { void PaletteGroupCard::DrawPaletteSelector() { auto* palette_group = GetPaletteGroup(); - if (!palette_group) return; + if (!palette_group) + return; int num_palettes = palette_group->size(); @@ -177,8 +177,9 @@ void PaletteGroupCard::DrawPaletteSelector() { ImGui::SameLine(); ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth()); - if (ImGui::BeginCombo("##PaletteSelect", - absl::StrFormat("Palette %d", selected_palette_).c_str())) { + if (ImGui::BeginCombo( + "##PaletteSelect", + absl::StrFormat("Palette %d", selected_palette_).c_str())) { for (int i = 0; i < num_palettes; i++) { bool is_selected = (selected_palette_ == i); bool is_modified = IsPaletteModified(i); @@ -209,10 +210,12 @@ void PaletteGroupCard::DrawPaletteSelector() { } void PaletteGroupCard::DrawColorPicker() { - if (selected_color_ < 0) return; + if (selected_color_ < 0) + return; auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; SectionHeader("Color Editor"); @@ -223,9 +226,9 @@ void PaletteGroupCard::DrawColorPicker() { ImVec4 col = ConvertSnesColorToImVec4(editing_color_); if (ImGui::ColorPicker4("##picker", &col.x, ImGuiColorEditFlags_NoAlpha | - ImGuiColorEditFlags_PickerHueWheel | - ImGuiColorEditFlags_DisplayRGB | - ImGuiColorEditFlags_DisplayHSV)) { + ImGuiColorEditFlags_PickerHueWheel | + ImGuiColorEditFlags_DisplayRGB | + ImGuiColorEditFlags_DisplayHSV)) { editing_color_ = ConvertImVec4ToSnesColor(col); SetColor(selected_palette_, selected_color_, editing_color_); } @@ -235,17 +238,18 @@ void PaletteGroupCard::DrawColorPicker() { ImGui::Text("Current vs Original"); ImGui::ColorButton("##current", col, - ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, - ImVec2(60, 40)); + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, + ImVec2(60, 40)); LayoutHelpers::HelpMarker("Current color being edited"); ImGui::SameLine(); ImVec4 orig_col = ConvertSnesColorToImVec4(original); - if (ImGui::ColorButton("##original", orig_col, - ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, - ImVec2(60, 40))) { + if (ImGui::ColorButton( + "##original", orig_col, + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, + ImVec2(60, 40))) { // Click to restore original editing_color_ = original; SetColor(selected_palette_, selected_color_, original); @@ -265,7 +269,8 @@ void PaletteGroupCard::DrawColorPicker() { } void PaletteGroupCard::DrawColorInfo() { - if (selected_color_ < 0) return; + if (selected_color_ < 0) + return; SectionHeader("Color Information"); @@ -284,7 +289,8 @@ void PaletteGroupCard::DrawColorInfo() { if (show_snes_format_) { ImGui::Text("SNES BGR555: $%04X", editing_color_.snes()); if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(absl::StrFormat("$%04X", editing_color_.snes()).c_str()); + ImGui::SetClipboardText( + absl::StrFormat("$%04X", editing_color_.snes()).c_str()); } } @@ -292,7 +298,8 @@ void PaletteGroupCard::DrawColorInfo() { if (show_hex_format_) { ImGui::Text("Hex: #%02X%02X%02X", r, g, b); if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(absl::StrFormat("#%02X%02X%02X", r, g, b).c_str()); + ImGui::SetClipboardText( + absl::StrFormat("#%02X%02X%02X", r, g, b).c_str()); } } @@ -301,7 +308,8 @@ void PaletteGroupCard::DrawColorInfo() { void PaletteGroupCard::DrawMetadataInfo() { const auto& metadata = GetMetadata(); - if (selected_palette_ >= metadata.palettes.size()) return; + if (selected_palette_ >= metadata.palettes.size()) + return; const auto& pal_meta = metadata.palettes[selected_palette_]; @@ -323,11 +331,11 @@ void PaletteGroupCard::DrawMetadataInfo() { ImGui::Separator(); // Palette dimensions and color depth - ImGui::Text("Dimensions: %d colors (%dx%d)", - metadata.colors_per_palette, + ImGui::Text("Dimensions: %d colors (%dx%d)", metadata.colors_per_palette, metadata.colors_per_row, - (metadata.colors_per_palette + metadata.colors_per_row - 1) / metadata.colors_per_row); - + (metadata.colors_per_palette + metadata.colors_per_row - 1) / + metadata.colors_per_row); + ImGui::Text("Color Depth: %d BPP (4-bit SNES)", 4); ImGui::TextDisabled("(16 colors per palette possible)"); @@ -336,7 +344,8 @@ void PaletteGroupCard::DrawMetadataInfo() { // ROM Address ImGui::Text("ROM Address: $%06X", pal_meta.rom_address); if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(absl::StrFormat("$%06X", pal_meta.rom_address).c_str()); + ImGui::SetClipboardText( + absl::StrFormat("$%06X", pal_meta.rom_address).c_str()); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Click to copy address"); @@ -346,7 +355,8 @@ void PaletteGroupCard::DrawMetadataInfo() { if (pal_meta.vram_address > 0) { ImGui::Text("VRAM Address: $%04X", pal_meta.vram_address); if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText(absl::StrFormat("$%04X", pal_meta.vram_address).c_str()); + ImGui::SetClipboardText( + absl::StrFormat("$%04X", pal_meta.vram_address).c_str()); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Click to copy VRAM address"); @@ -389,10 +399,10 @@ void PaletteGroupCard::DrawBatchOperationsPopup() { // ========== Palette Operations ========== void PaletteGroupCard::SetColor(int palette_index, int color_index, - const gfx::SnesColor& new_color) { + const gfx::SnesColor& new_color) { // Delegate to PaletteManager for centralized tracking and undo/redo auto status = gfx::PaletteManager::Get().SetColor(group_name_, palette_index, - color_index, new_color); + color_index, new_color); if (!status.ok()) { // TODO: Show error notification return; @@ -425,7 +435,7 @@ void PaletteGroupCard::ResetPalette(int palette_index) { void PaletteGroupCard::ResetColor(int palette_index, int color_index) { // Delegate to PaletteManager for centralized reset operation gfx::PaletteManager::Get().ResetColor(group_name_, palette_index, - color_index); + color_index); } // ========== History Management ========== @@ -450,14 +460,14 @@ void PaletteGroupCard::ClearHistory() { bool PaletteGroupCard::IsPaletteModified(int palette_index) const { // Query PaletteManager for modification status return gfx::PaletteManager::Get().IsPaletteModified(group_name_, - palette_index); + palette_index); } bool PaletteGroupCard::IsColorModified(int palette_index, - int color_index) const { + int color_index) const { // Query PaletteManager for modification status return gfx::PaletteManager::Get().IsColorModified(group_name_, palette_index, - color_index); + color_index); } bool PaletteGroupCard::HasUnsavedChanges() const { @@ -486,15 +496,17 @@ gfx::SnesPalette* PaletteGroupCard::GetMutablePalette(int index) { } gfx::SnesColor PaletteGroupCard::GetOriginalColor(int palette_index, - int color_index) const { + int color_index) const { // Get original color from PaletteManager's snapshots return gfx::PaletteManager::Get().GetColor(group_name_, palette_index, - color_index); + color_index); } -absl::Status PaletteGroupCard::WriteColorToRom(int palette_index, int color_index, - const gfx::SnesColor& color) { - uint32_t address = gfx::GetPaletteAddress(group_name_, palette_index, color_index); +absl::Status PaletteGroupCard::WriteColorToRom(int palette_index, + int color_index, + const gfx::SnesColor& color) { + uint32_t address = + gfx::GetPaletteAddress(group_name_, palette_index, color_index); return rom_->WriteColor(address, color); } @@ -606,7 +618,8 @@ const gfx::PaletteGroup* OverworldMainPaletteCard::GetPaletteGroup() const { void OverworldMainPaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 32.0f; const int colors_per_row = GetColorsPerRow(); @@ -618,8 +631,8 @@ void OverworldMainPaletteCard::DrawPaletteGrid() { ImGui::PushID(i); if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -654,10 +667,12 @@ PaletteGroupMetadata OverworldAnimatedPaletteCard::InitializeMetadata() { PaletteMetadata pal; pal.palette_id = i; pal.name = anim_names[i]; - pal.description = absl::StrFormat("%s animated palette cycle", anim_names[i]); + pal.description = + absl::StrFormat("%s animated palette cycle", anim_names[i]); pal.rom_address = 0xDE86C + (i * 16); pal.vram_address = 0; - pal.usage_notes = "These palettes cycle through multiple frames for animation"; + pal.usage_notes = + "These palettes cycle through multiple frames for animation"; metadata.palettes.push_back(pal); } @@ -669,12 +684,14 @@ gfx::PaletteGroup* OverworldAnimatedPaletteCard::GetPaletteGroup() { } const gfx::PaletteGroup* OverworldAnimatedPaletteCard::GetPaletteGroup() const { - return const_cast(rom_)->mutable_palette_group()->get_group("ow_animated"); + return const_cast(rom_)->mutable_palette_group()->get_group( + "ow_animated"); } void OverworldAnimatedPaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 32.0f; const int colors_per_row = GetColorsPerRow(); @@ -686,8 +703,8 @@ void OverworldAnimatedPaletteCard::DrawPaletteGrid() { ImGui::PushID(i); if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -717,12 +734,11 @@ PaletteGroupMetadata DungeonMainPaletteCard::InitializeMetadata() { // Dungeon palettes (0-19) const char* dungeon_names[] = { - "Sewers", "Hyrule Castle", "Eastern Palace", "Desert Palace", - "Agahnim's Tower", "Swamp Palace", "Palace of Darkness", "Misery Mire", - "Skull Woods", "Ice Palace", "Tower of Hera", "Thieves' Town", - "Turtle Rock", "Ganon's Tower", "Generic 1", "Generic 2", - "Generic 3", "Generic 4", "Generic 5", "Generic 6" - }; + "Sewers", "Hyrule Castle", "Eastern Palace", "Desert Palace", + "Agahnim's Tower", "Swamp Palace", "Palace of Darkness", "Misery Mire", + "Skull Woods", "Ice Palace", "Tower of Hera", "Thieves' Town", + "Turtle Rock", "Ganon's Tower", "Generic 1", "Generic 2", + "Generic 3", "Generic 4", "Generic 5", "Generic 6"}; for (int i = 0; i < 20; i++) { PaletteMetadata pal; @@ -743,12 +759,14 @@ gfx::PaletteGroup* DungeonMainPaletteCard::GetPaletteGroup() { } const gfx::PaletteGroup* DungeonMainPaletteCard::GetPaletteGroup() const { - return const_cast(rom_)->mutable_palette_group()->get_group("dungeon_main"); + return const_cast(rom_)->mutable_palette_group()->get_group( + "dungeon_main"); } void DungeonMainPaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 28.0f; const int colors_per_row = GetColorsPerRow(); @@ -760,8 +778,8 @@ void DungeonMainPaletteCard::DrawPaletteGrid() { ImGui::PushID(i); if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -786,24 +804,26 @@ PaletteGroupMetadata SpritePaletteCard::InitializeMetadata() { PaletteGroupMetadata metadata; metadata.group_name = "global_sprites"; metadata.display_name = "Global Sprite Palettes"; - metadata.colors_per_palette = 60; // 60 colors: 4 rows of 16 colors (with transparent at 0, 16, 32, 48) - metadata.colors_per_row = 16; // Display in 16-color rows + metadata.colors_per_palette = + 60; // 60 colors: 4 rows of 16 colors (with transparent at 0, 16, 32, 48) + metadata.colors_per_row = 16; // Display in 16-color rows // 2 palette sets: Light World and Dark World - const char* sprite_names[] = { - "Global Sprites (Light World)", - "Global Sprites (Dark World)" - }; + const char* sprite_names[] = {"Global Sprites (Light World)", + "Global Sprites (Dark World)"}; for (int i = 0; i < 2; i++) { PaletteMetadata pal; pal.palette_id = i; pal.name = sprite_names[i]; - pal.description = "60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, 32, 48"; + pal.description = + "60 colors = 4 sprite sub-palettes (rows) with transparent at 0, 16, " + "32, 48"; pal.rom_address = (i == 0) ? 0xDD218 : 0xDD290; // LW or DW address - pal.vram_address = 0; // Loaded dynamically - pal.usage_notes = "4 sprite sub-palettes of 15 colors + transparent each. " - "Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59"; + pal.vram_address = 0; // Loaded dynamically + pal.usage_notes = + "4 sprite sub-palettes of 15 colors + transparent each. " + "Row 0: colors 0-15, Row 1: 16-31, Row 2: 32-47, Row 3: 48-59"; metadata.palettes.push_back(pal); } @@ -815,12 +835,14 @@ gfx::PaletteGroup* SpritePaletteCard::GetPaletteGroup() { } const gfx::PaletteGroup* SpritePaletteCard::GetPaletteGroup() const { - return const_cast(rom_)->mutable_palette_group()->get_group("global_sprites"); + return const_cast(rom_)->mutable_palette_group()->get_group( + "global_sprites"); } void SpritePaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 28.0f; const int colors_per_row = GetColorsPerRow(); @@ -836,8 +858,8 @@ void SpritePaletteCard::DrawPaletteGrid() { if (is_transparent_slot) { ImGui::BeginGroup(); if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -847,14 +869,15 @@ void SpritePaletteCard::DrawPaletteGrid() { ImVec2(pos.x + button_size / 2 - 4, pos.y + button_size / 2 - 8), IM_COL32(255, 255, 255, 200), "T"); ImGui::EndGroup(); - + if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Transparent color slot for sprite sub-palette %d", i / 16); + ImGui::SetTooltip("Transparent color slot for sprite sub-palette %d", + i / 16); } } else { if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -877,8 +900,9 @@ void SpritePaletteCard::DrawCustomPanels() { const auto& pal_meta = metadata.palettes[selected_palette_]; ImGui::TextWrapped("This sprite palette is loaded to VRAM address $%04X", - pal_meta.vram_address); - ImGui::TextDisabled("VRAM palettes are used by the SNES PPU for sprite rendering"); + pal_meta.vram_address); + ImGui::TextDisabled( + "VRAM palettes are used by the SNES PPU for sprite rendering"); } } @@ -923,7 +947,8 @@ const gfx::PaletteGroup* EquipmentPaletteCard::GetPaletteGroup() const { void EquipmentPaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 32.0f; const int colors_per_row = GetColorsPerRow(); @@ -935,8 +960,8 @@ void EquipmentPaletteCard::DrawPaletteGrid() { ImGui::PushID(i); if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -983,12 +1008,14 @@ gfx::PaletteGroup* SpritesAux1PaletteCard::GetPaletteGroup() { } const gfx::PaletteGroup* SpritesAux1PaletteCard::GetPaletteGroup() const { - return const_cast(rom_)->mutable_palette_group()->get_group("sprites_aux1"); + return const_cast(rom_)->mutable_palette_group()->get_group( + "sprites_aux1"); } void SpritesAux1PaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 32.0f; const int colors_per_row = GetColorsPerRow(); @@ -999,12 +1026,12 @@ void SpritesAux1PaletteCard::DrawPaletteGrid() { ImGui::PushID(i); - if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { - selected_color_ = i; - editing_color_ = (*palette)[i]; - } + if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { + selected_color_ = i; + editing_color_ = (*palette)[i]; + } ImGui::PopID(); @@ -1048,12 +1075,14 @@ gfx::PaletteGroup* SpritesAux2PaletteCard::GetPaletteGroup() { } const gfx::PaletteGroup* SpritesAux2PaletteCard::GetPaletteGroup() const { - return const_cast(rom_)->mutable_palette_group()->get_group("sprites_aux2"); + return const_cast(rom_)->mutable_palette_group()->get_group( + "sprites_aux2"); } void SpritesAux2PaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 32.0f; const int colors_per_row = GetColorsPerRow(); @@ -1068,8 +1097,8 @@ void SpritesAux2PaletteCard::DrawPaletteGrid() { if (i == 0) { ImGui::BeginGroup(); if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -1081,8 +1110,8 @@ void SpritesAux2PaletteCard::DrawPaletteGrid() { ImGui::EndGroup(); } else { if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -1130,12 +1159,14 @@ gfx::PaletteGroup* SpritesAux3PaletteCard::GetPaletteGroup() { } const gfx::PaletteGroup* SpritesAux3PaletteCard::GetPaletteGroup() const { - return const_cast(rom_)->mutable_palette_group()->get_group("sprites_aux3"); + return const_cast(rom_)->mutable_palette_group()->get_group( + "sprites_aux3"); } void SpritesAux3PaletteCard::DrawPaletteGrid() { auto* palette = GetMutablePalette(selected_palette_); - if (!palette) return; + if (!palette) + return; const float button_size = 32.0f; const int colors_per_row = GetColorsPerRow(); @@ -1150,8 +1181,8 @@ void SpritesAux3PaletteCard::DrawPaletteGrid() { if (i == 0) { ImGui::BeginGroup(); if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } @@ -1163,8 +1194,8 @@ void SpritesAux3PaletteCard::DrawPaletteGrid() { ImGui::EndGroup(); } else { if (yaze::gui::PaletteColorButton(absl::StrFormat("##color%d", i).c_str(), - (*palette)[i], is_selected, is_modified, - ImVec2(button_size, button_size))) { + (*palette)[i], is_selected, is_modified, + ImVec2(button_size, button_size))) { selected_color_ = i; editing_color_ = (*palette)[i]; } diff --git a/src/app/editor/palette/palette_group_card.h b/src/app/editor/palette/palette_group_card.h index a893a8d6..14fa242e 100644 --- a/src/app/editor/palette/palette_group_card.h +++ b/src/app/editor/palette/palette_group_card.h @@ -32,23 +32,23 @@ struct ColorChange { * @brief Metadata for a single palette in a group */ struct PaletteMetadata { - int palette_id; // Palette ID in ROM - std::string name; // Display name (e.g., "Light World Main") - std::string description; // Usage description - uint32_t rom_address; // Base ROM address for this palette - uint32_t vram_address; // VRAM address (for sprite palettes, 0 if N/A) - std::string usage_notes; // Additional usage information + int palette_id; // Palette ID in ROM + std::string name; // Display name (e.g., "Light World Main") + std::string description; // Usage description + uint32_t rom_address; // Base ROM address for this palette + uint32_t vram_address; // VRAM address (for sprite palettes, 0 if N/A) + std::string usage_notes; // Additional usage information }; /** * @brief Metadata for an entire palette group */ struct PaletteGroupMetadata { - std::string group_name; // Internal group name - std::string display_name; // Display name for UI + std::string group_name; // Internal group name + std::string display_name; // Display name for UI std::vector palettes; // Metadata for each palette - int colors_per_palette; // Number of colors per palette (usually 8 or 16) - int colors_per_row; // Colors per row for grid layout + int colors_per_palette; // Number of colors per palette (usually 8 or 16) + int colors_per_row; // Colors per row for grid layout }; /** @@ -73,8 +73,7 @@ class PaletteGroupCard { * @param rom ROM instance for reading/writing palettes */ PaletteGroupCard(const std::string& group_name, - const std::string& display_name, - Rom* rom); + const std::string& display_name, Rom* rom); virtual ~PaletteGroupCard() = default; @@ -117,7 +116,8 @@ class PaletteGroupCard { /** * @brief Set a color value (records change for undo) */ - void SetColor(int palette_index, int color_index, const gfx::SnesColor& new_color); + void SetColor(int palette_index, int color_index, + const gfx::SnesColor& new_color); // ========== History Management ========== @@ -231,7 +231,7 @@ class PaletteGroupCard { * @brief Write a single color to ROM */ absl::Status WriteColorToRom(int palette_index, int color_index, - const gfx::SnesColor& color); + const gfx::SnesColor& color); /** * @brief Mark palette as modified @@ -245,15 +245,15 @@ class PaletteGroupCard { // ========== Member Variables ========== - std::string group_name_; // Internal name (e.g., "ow_main") - std::string display_name_; // Display name (e.g., "Overworld Main") - Rom* rom_; // ROM instance - bool show_ = false; // Visibility flag + std::string group_name_; // Internal name (e.g., "ow_main") + std::string display_name_; // Display name (e.g., "Overworld Main") + Rom* rom_; // ROM instance + bool show_ = false; // Visibility flag // Selection state - int selected_palette_ = 0; // Currently selected palette index - int selected_color_ = -1; // Currently selected color (-1 = none) - gfx::SnesColor editing_color_; // Color being edited in picker + int selected_palette_ = 0; // Currently selected palette index + int selected_color_ = -1; // Currently selected color (-1 = none) + gfx::SnesColor editing_color_; // Color being edited in picker // Settings bool auto_save_enabled_ = false; // Auto-save to ROM on every change diff --git a/src/app/editor/palette/palette_utility.cc b/src/app/editor/palette/palette_utility.cc index 9d74a986..fa869768 100644 --- a/src/app/editor/palette/palette_utility.cc +++ b/src/app/editor/palette/palette_utility.cc @@ -15,16 +15,16 @@ bool DrawPaletteJumpButton(const char* label, const std::string& group_name, int palette_index, PaletteEditor* editor) { bool clicked = ImGui::SmallButton( absl::StrFormat("%s %s", ICON_MD_PALETTE, label).c_str()); - + if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Jump to palette editor:\n%s - Palette %d", - group_name.c_str(), palette_index); + ImGui::SetTooltip("Jump to palette editor:\n%s - Palette %d", + group_name.c_str(), palette_index); } - + if (clicked && editor) { editor->JumpToPalette(group_name, palette_index); } - + return clicked; } @@ -32,17 +32,17 @@ bool DrawInlineColorEdit(const char* label, gfx::SnesColor* color, const std::string& group_name, int palette_index, int color_index, PaletteEditor* editor) { ImGui::PushID(label); - + // Draw color button ImVec4 col = gui::ConvertSnesColorToImVec4(*color); - bool changed = ImGui::ColorEdit4(label, &col.x, - ImGuiColorEditFlags_NoInputs | - ImGuiColorEditFlags_NoLabel); - + bool changed = ImGui::ColorEdit4( + label, &col.x, + ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); + if (changed) { *color = gui::ConvertImVec4ToSnesColor(col); } - + // Draw jump button ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); @@ -52,16 +52,16 @@ bool DrawInlineColorEdit(const char* label, gfx::SnesColor* color, } } ImGui::PopStyleColor(); - + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Jump to Palette Editor"); - ImGui::TextDisabled("%s - Palette %d, Color %d", - group_name.c_str(), palette_index, color_index); + ImGui::TextDisabled("%s - Palette %d, Color %d", group_name.c_str(), + palette_index, color_index); DrawColorInfoTooltip(*color); ImGui::EndTooltip(); } - + ImGui::PopID(); return changed; } @@ -70,20 +70,22 @@ bool DrawPaletteIdSelector(const char* label, int* palette_id, const std::string& group_name, PaletteEditor* editor) { ImGui::PushID(label); - + // Draw combo box bool changed = ImGui::InputInt(label, palette_id); - + // Clamp to valid range (0-255 typically) - if (*palette_id < 0) *palette_id = 0; - if (*palette_id > 255) *palette_id = 255; - + if (*palette_id < 0) + *palette_id = 0; + if (*palette_id > 255) + *palette_id = 255; + // Draw jump button ImGui::SameLine(); if (DrawPaletteJumpButton("Jump", group_name, *palette_id, editor)) { // Button clicked, editor will handle jump } - + ImGui::PopID(); return changed; } @@ -91,52 +93,49 @@ bool DrawPaletteIdSelector(const char* label, int* palette_id, void DrawColorInfoTooltip(const gfx::SnesColor& color) { auto rgb = color.rgb(); ImGui::Separator(); - ImGui::Text("RGB: (%d, %d, %d)", - static_cast(rgb.x), - static_cast(rgb.y), - static_cast(rgb.z)); + ImGui::Text("RGB: (%d, %d, %d)", static_cast(rgb.x), + static_cast(rgb.y), static_cast(rgb.z)); ImGui::Text("SNES: $%04X", color.snes()); - ImGui::Text("Hex: #%02X%02X%02X", - static_cast(rgb.x), - static_cast(rgb.y), - static_cast(rgb.z)); + ImGui::Text("Hex: #%02X%02X%02X", static_cast(rgb.x), + static_cast(rgb.y), static_cast(rgb.z)); } void DrawPalettePreview(const std::string& group_name, int palette_index, - Rom* rom) { + Rom* rom) { if (!rom || !rom->is_loaded()) { ImGui::TextDisabled("(ROM not loaded)"); return; } - + auto* group = rom->mutable_palette_group()->get_group(group_name); if (!group || palette_index >= group->size()) { ImGui::TextDisabled("(Palette not found)"); return; } - + auto palette = group->palette(palette_index); - + // Draw colors in a row int preview_size = std::min(8, static_cast(palette.size())); for (int i = 0; i < preview_size; i++) { - if (i > 0) ImGui::SameLine(); - + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); ImVec4 col = gui::ConvertSnesColorToImVec4(palette[i]); - ImGui::ColorButton("##preview", col, - ImGuiColorEditFlags_NoAlpha | - ImGuiColorEditFlags_NoPicker | - ImGuiColorEditFlags_NoTooltip, - ImVec2(16, 16)); - + ImGui::ColorButton("##preview", col, + ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_NoPicker | + ImGuiColorEditFlags_NoTooltip, + ImVec2(16, 16)); + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Color %d", i); DrawColorInfoTooltip(palette[i]); ImGui::EndTooltip(); } - + ImGui::PopID(); } } @@ -144,4 +143,3 @@ void DrawPalettePreview(const std::string& group_name, int palette_index, } // namespace palette_utility } // namespace editor } // namespace yaze - diff --git a/src/app/editor/palette/palette_utility.h b/src/app/editor/palette/palette_utility.h index 3fd834df..1061bde9 100644 --- a/src/app/editor/palette/palette_utility.h +++ b/src/app/editor/palette/palette_utility.h @@ -5,8 +5,8 @@ #include "app/gfx/types/snes_color.h" #include "app/gui/core/color.h" -#include "imgui/imgui.h" #include "app/rom.h" +#include "imgui/imgui.h" namespace yaze { namespace editor { @@ -68,7 +68,7 @@ void DrawColorInfoTooltip(const gfx::SnesColor& color); * @param rom ROM instance to read palette from */ void DrawPalettePreview(const std::string& group_name, int palette_index, - class Rom* rom); + class Rom* rom); } // namespace palette_utility @@ -76,4 +76,3 @@ void DrawPalettePreview(const std::string& group_name, int palette_index, } // namespace yaze #endif // YAZE_APP_EDITOR_PALETTE_UTILITY_H - diff --git a/src/app/editor/session_types.cc b/src/app/editor/session_types.cc index fe1ddb6b..3f397f60 100644 --- a/src/app/editor/session_types.cc +++ b/src/app/editor/session_types.cc @@ -1,7 +1,7 @@ #include "app/editor/session_types.h" -#include "app/editor/editor.h" // For EditorDependencies, needed by ApplyDependencies -#include "app/editor/system/user_settings.h" // For UserSettings forward decl in header +#include "app/editor/editor.h" // For EditorDependencies, needed by ApplyDependencies +#include "app/editor/system/user_settings.h" // For UserSettings forward decl in header namespace yaze::editor { diff --git a/src/app/editor/session_types.h b/src/app/editor/session_types.h index 3bc2e01e..12408c9d 100644 --- a/src/app/editor/session_types.h +++ b/src/app/editor/session_types.h @@ -1,7 +1,6 @@ #ifndef YAZE_APP_EDITOR_SESSION_TYPES_H_ #define YAZE_APP_EDITOR_SESSION_TYPES_H_ -#include "core/features.h" #include "app/editor/code/assembly_editor.h" #include "app/editor/code/memory_editor.h" #include "app/editor/dungeon/dungeon_editor_v2.h" @@ -14,6 +13,7 @@ #include "app/editor/sprite/sprite_editor.h" #include "app/editor/system/settings_editor.h" #include "app/rom.h" +#include "core/features.h" #include #include diff --git a/src/app/editor/sprite/sprite_editor.cc b/src/app/editor/sprite/sprite_editor.cc index 3288a245..fde54926 100644 --- a/src/app/editor/sprite/sprite_editor.cc +++ b/src/app/editor/sprite/sprite_editor.cc @@ -1,15 +1,15 @@ #include "sprite_editor.h" #include "app/editor/system/editor_card_registry.h" -#include "app/gfx/debug/performance/performance_profiler.h" -#include "app/gui/core/ui_helpers.h" -#include "util/file_util.h" #include "app/editor/sprite/zsprite.h" +#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/resource/arena.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" -#include "zelda3/sprite/sprite.h" +#include "app/gui/core/ui_helpers.h" +#include "util/file_util.h" #include "util/hex.h" +#include "zelda3/sprite/sprite.h" namespace yaze { namespace editor { @@ -26,23 +26,30 @@ using ImGui::TableSetupColumn; using ImGui::Text; void SpriteEditor::Initialize() { - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - - card_registry->RegisterCard({.card_id = "sprite.vanilla_editor", .display_name = "Vanilla Sprites", - .icon = ICON_MD_SMART_TOY, .category = "Sprite", - .shortcut_hint = "Alt+Shift+1", .priority = 10}); - card_registry->RegisterCard({.card_id = "sprite.custom_editor", .display_name = "Custom Sprites", - .icon = ICON_MD_ADD_CIRCLE, .category = "Sprite", - .shortcut_hint = "Alt+Shift+2", .priority = 20}); - + + card_registry->RegisterCard({.card_id = "sprite.vanilla_editor", + .display_name = "Vanilla Sprites", + .icon = ICON_MD_SMART_TOY, + .category = "Sprite", + .shortcut_hint = "Alt+Shift+1", + .priority = 10}); + card_registry->RegisterCard({.card_id = "sprite.custom_editor", + .display_name = "Custom Sprites", + .icon = ICON_MD_ADD_CIRCLE, + .category = "Sprite", + .shortcut_hint = "Alt+Shift+2", + .priority = 20}); + // Show vanilla editor by default card_registry->ShowCard("sprite.vanilla_editor"); } -absl::Status SpriteEditor::Load() { +absl::Status SpriteEditor::Load() { gfx::ScopedTimer timer("SpriteEditor::Load"); - return absl::OkStatus(); + return absl::OkStatus(); } absl::Status SpriteEditor::Update() { @@ -50,7 +57,8 @@ absl::Status SpriteEditor::Update() { sheets_loaded_ = true; } - if (!dependencies_.card_registry) return absl::OkStatus(); + if (!dependencies_.card_registry) + return absl::OkStatus(); auto* card_registry = dependencies_.card_registry; static gui::EditorCard vanilla_card("Vanilla Sprites", ICON_MD_SMART_TOY); @@ -60,7 +68,8 @@ absl::Status SpriteEditor::Update() { custom_card.SetDefaultSize(800, 600); // Vanilla Sprites Card - Check visibility flag exists and is true before rendering - bool* vanilla_visible = card_registry->GetVisibilityFlag("sprite.vanilla_editor"); + bool* vanilla_visible = + card_registry->GetVisibilityFlag("sprite.vanilla_editor"); if (vanilla_visible && *vanilla_visible) { if (vanilla_card.Begin(vanilla_visible)) { DrawVanillaSpriteEditor(); @@ -69,7 +78,8 @@ absl::Status SpriteEditor::Update() { } // Custom Sprites Card - Check visibility flag exists and is true before rendering - bool* custom_visible = card_registry->GetVisibilityFlag("sprite.custom_editor"); + bool* custom_visible = + card_registry->GetVisibilityFlag("sprite.custom_editor"); if (custom_visible && *custom_visible) { if (custom_card.Begin(custom_visible)) { DrawCustomSprites(); @@ -85,7 +95,6 @@ void SpriteEditor::DrawToolset() { // This method kept for compatibility but sidebar handles card toggles } - void SpriteEditor::DrawVanillaSpriteEditor() { if (ImGui::BeginTable("##SpriteCanvasTable", 3, ImGuiTableFlags_Resizable, ImVec2(0, 0))) { @@ -212,7 +221,8 @@ void SpriteEditor::DrawCurrentSheets() { for (int i = 0; i < 8; i++) { std::string sheet_label = absl::StrFormat("Sheet %d", i); gui::InputHexByte(sheet_label.c_str(), ¤t_sheets_[i]); - if (i % 2 == 0) ImGui::SameLine(); + if (i % 2 == 0) + ImGui::SameLine(); } graphics_sheet_canvas_.DrawBackground(); diff --git a/src/app/editor/sprite/sprite_editor.h b/src/app/editor/sprite/sprite_editor.h index 98b7bad2..e48db5c4 100644 --- a/src/app/editor/sprite/sprite_editor.h +++ b/src/app/editor/sprite/sprite_editor.h @@ -7,8 +7,8 @@ #include "absl/status/status.h" #include "app/editor/editor.h" #include "app/editor/sprite/zsprite.h" -#include "app/gui/canvas/canvas.h" #include "app/gui/app/editor_layout.h" +#include "app/gui/canvas/canvas.h" #include "app/rom.h" namespace yaze { diff --git a/src/app/editor/system/command_manager.cc b/src/app/editor/system/command_manager.cc index 3a73e1d3..b1dddf91 100644 --- a/src/app/editor/system/command_manager.cc +++ b/src/app/editor/system/command_manager.cc @@ -8,7 +8,6 @@ namespace yaze { namespace editor { - // When the player presses Space, a popup will appear fixed to the bottom of the // ImGui window with a list of the available key commands which can be used. void CommandManager::ShowWhichKey() { @@ -39,23 +38,23 @@ void CommandManager::ShowWhichKey() { if (ImGui::BeginTable("CommandsTable", commands_.size(), ImGuiTableFlags_SizingStretchProp)) { - for (const auto &[shortcut, group] : commands_) { - ImGui::TableNextColumn(); - ImGui::TextColored(colors[colorIndex], "%c: %s", - group.main_command.mnemonic, - group.main_command.name.c_str()); - colorIndex = (colorIndex + 1) % numColors; - } + for (const auto& [shortcut, group] : commands_) { + ImGui::TableNextColumn(); + ImGui::TextColored(colors[colorIndex], "%c: %s", + group.main_command.mnemonic, + group.main_command.name.c_str()); + colorIndex = (colorIndex + 1) % numColors; + } ImGui::EndTable(); } ImGui::EndPopup(); } } -void CommandManager::SaveKeybindings(const std::string &filepath) { +void CommandManager::SaveKeybindings(const std::string& filepath) { std::ofstream out(filepath); if (out.is_open()) { - for (const auto &[shortcut, group] : commands_) { + for (const auto& [shortcut, group] : commands_) { out << shortcut << " " << group.main_command.mnemonic << " " << group.main_command.name << " " << group.main_command.desc << "\n"; } @@ -63,7 +62,7 @@ void CommandManager::SaveKeybindings(const std::string &filepath) { } } -void CommandManager::LoadKeybindings(const std::string &filepath) { +void CommandManager::LoadKeybindings(const std::string& filepath) { std::ifstream in(filepath); if (in.is_open()) { commands_.clear(); @@ -116,8 +115,8 @@ void CommandManager::ShowWhichKeyHierarchical() { // Show breadcrumb navigation if (!current_prefix_.empty()) { - ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), - "Space > %s", current_prefix_.c_str()); + ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "Space > %s", + current_prefix_.c_str()); ImGui::Separator(); } else { ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "Space > ..."); @@ -137,14 +136,14 @@ void CommandManager::ShowWhichKeyHierarchical() { // Show commands based on current navigation level if (current_prefix_.empty()) { // Root level - show main groups - if (ImGui::BeginTable("RootCommands", 6, ImGuiTableFlags_SizingStretchProp)) { + if (ImGui::BeginTable("RootCommands", 6, + ImGuiTableFlags_SizingStretchProp)) { int colorIndex = 0; - for (const auto &[shortcut, group] : commands_) { + for (const auto& [shortcut, group] : commands_) { ImGui::TableNextColumn(); - ImGui::TextColored(colors[colorIndex % 6], - "%c: %s", - group.main_command.mnemonic, - group.main_command.name.c_str()); + ImGui::TextColored(colors[colorIndex % 6], "%c: %s", + group.main_command.mnemonic, + group.main_command.name.c_str()); colorIndex++; } ImGui::EndTable(); @@ -156,15 +155,13 @@ void CommandManager::ShowWhichKeyHierarchical() { const auto& group = it->second; if (!group.subcommands.empty()) { if (ImGui::BeginTable("Subcommands", - std::min(6, (int)group.subcommands.size()), - ImGuiTableFlags_SizingStretchProp)) { + std::min(6, (int)group.subcommands.size()), + ImGuiTableFlags_SizingStretchProp)) { int colorIndex = 0; for (const auto& [key, cmd] : group.subcommands) { ImGui::TableNextColumn(); - ImGui::TextColored(colors[colorIndex % 6], - "%c: %s", - cmd.mnemonic, - cmd.name.c_str()); + ImGui::TextColored(colors[colorIndex % 6], "%c: %s", cmd.mnemonic, + cmd.name.c_str()); colorIndex++; } ImGui::EndTable(); @@ -184,7 +181,8 @@ void CommandManager::ShowWhichKeyHierarchical() { // Handle keyboard input for WhichKey navigation void CommandManager::HandleWhichKeyInput() { - if (!whichkey_active_) return; + if (!whichkey_active_) + return; // Check for prefix keys (w, l, f, b, s, t, etc.) for (const auto& [shortcut, group] : commands_) { diff --git a/src/app/editor/system/command_manager.h b/src/app/editor/system/command_manager.h index a87af3cd..5ba6634f 100644 --- a/src/app/editor/system/command_manager.h +++ b/src/app/editor/system/command_manager.h @@ -28,8 +28,8 @@ class CommandManager { char mnemonic; std::string name; std::string desc; - CommandInfo(Command command, char mnemonic, const std::string &name, - const std::string &desc) + CommandInfo(Command command, char mnemonic, const std::string& name, + const std::string& desc) : command(std::move(command)), mnemonic(mnemonic), name(name), @@ -41,30 +41,32 @@ class CommandManager { struct CommandGroup { CommandInfo main_command; std::unordered_map subcommands; - + CommandGroup() = default; CommandGroup(CommandInfo main) : main_command(std::move(main)) {} }; - void RegisterPrefix(const std::string &group_name, const char prefix, - const std::string &name, const std::string &desc) { + void RegisterPrefix(const std::string& group_name, const char prefix, + const std::string& name, const std::string& desc) { commands_[group_name].main_command = {nullptr, prefix, name, desc}; } - void RegisterSubcommand(const std::string &group_name, - const std::string &shortcut, const char mnemonic, - const std::string &name, const std::string &desc, + void RegisterSubcommand(const std::string& group_name, + const std::string& shortcut, const char mnemonic, + const std::string& name, const std::string& desc, Command command) { - commands_[group_name].subcommands[shortcut] = {command, mnemonic, name, desc}; + commands_[group_name].subcommands[shortcut] = {command, mnemonic, name, + desc}; } - void RegisterCommand(const std::string &shortcut, Command command, - char mnemonic, const std::string &name, - const std::string &desc) { - commands_[shortcut].main_command = {std::move(command), mnemonic, name, desc}; + void RegisterCommand(const std::string& shortcut, Command command, + char mnemonic, const std::string& name, + const std::string& desc) { + commands_[shortcut].main_command = {std::move(command), mnemonic, name, + desc}; } - void ExecuteCommand(const std::string &shortcut) { + void ExecuteCommand(const std::string& shortcut) { if (commands_.find(shortcut) != commands_.end()) { commands_[shortcut].main_command.command(); } @@ -76,8 +78,8 @@ class CommandManager { void ShowWhichKeyHierarchical(); void HandleWhichKeyInput(); - void SaveKeybindings(const std::string &filepath); - void LoadKeybindings(const std::string &filepath); + void SaveKeybindings(const std::string& filepath); + void LoadKeybindings(const std::string& filepath); // Navigation state bool IsWhichKeyActive() const { return whichkey_active_; } @@ -88,7 +90,8 @@ class CommandManager { // WhichKey state bool whichkey_active_ = false; - std::string current_prefix_; // Current navigation prefix (e.g., "w", "l", "f") + std::string + current_prefix_; // Current navigation prefix (e.g., "w", "l", "f") float whichkey_timer_ = 0.0f; // Auto-close timer }; diff --git a/src/app/editor/system/command_palette.cc b/src/app/editor/system/command_palette.cc index b56c9afa..fd42a7b8 100644 --- a/src/app/editor/system/command_palette.cc +++ b/src/app/editor/system/command_palette.cc @@ -7,9 +7,11 @@ namespace yaze { namespace editor { -void CommandPalette::AddCommand(const std::string& name, const std::string& category, - const std::string& description, const std::string& shortcut, - std::function callback) { +void CommandPalette::AddCommand(const std::string& name, + const std::string& category, + const std::string& description, + const std::string& shortcut, + std::function callback) { CommandEntry entry; entry.name = name; entry.category = category; @@ -23,33 +25,41 @@ void CommandPalette::RecordUsage(const std::string& name) { auto it = commands_.find(name); if (it != commands_.end()) { it->second.usage_count++; - it->second.last_used_ms = + it->second.last_used_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); } } -int CommandPalette::FuzzyScore(const std::string& text, const std::string& query) { - if (query.empty()) return 0; - +int CommandPalette::FuzzyScore(const std::string& text, + const std::string& query) { + if (query.empty()) + return 0; + int score = 0; size_t text_idx = 0; size_t query_idx = 0; - + std::string text_lower = text; std::string query_lower = query; - std::transform(text_lower.begin(), text_lower.end(), text_lower.begin(), ::tolower); - std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(), ::tolower); - + std::transform(text_lower.begin(), text_lower.end(), text_lower.begin(), + ::tolower); + std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(), + ::tolower); + // Exact match bonus - if (text_lower == query_lower) return 1000; - + if (text_lower == query_lower) + return 1000; + // Starts with bonus - if (text_lower.find(query_lower) == 0) return 500; - + if (text_lower.find(query_lower) == 0) + return 500; + // Contains bonus - if (text_lower.find(query_lower) != std::string::npos) return 250; - + if (text_lower.find(query_lower) != std::string::npos) + return 250; + // Fuzzy match - characters in order while (text_idx < text_lower.length() && query_idx < query_lower.length()) { if (text_lower[text_idx] == query_lower[query_idx]) { @@ -58,91 +68,94 @@ int CommandPalette::FuzzyScore(const std::string& text, const std::string& query } text_idx++; } - + // Penalty if not all characters matched - if (query_idx != query_lower.length()) return 0; - + if (query_idx != query_lower.length()) + return 0; + return score; } -std::vector CommandPalette::SearchCommands(const std::string& query) { +std::vector CommandPalette::SearchCommands( + const std::string& query) { std::vector> scored; - + for (const auto& [name, entry] : commands_) { int score = FuzzyScore(entry.name, query); - + // Also check category and description score += FuzzyScore(entry.category, query) / 2; score += FuzzyScore(entry.description, query) / 4; - + // Frecency bonus (frequency + recency) score += entry.usage_count * 2; - + auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); int64_t age_ms = now_ms - entry.last_used_ms; if (age_ms < 60000) { // Used in last minute score += 50; } else if (age_ms < 3600000) { // Last hour score += 25; } - + if (score > 0) { scored.push_back({score, entry}); } } - + // Sort by score descending - std::sort(scored.begin(), scored.end(), + std::sort(scored.begin(), scored.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); - + std::vector results; for (const auto& [score, entry] : scored) { results.push_back(entry); } - + return results; } std::vector CommandPalette::GetRecentCommands(int limit) { std::vector recent; - + for (const auto& [name, entry] : commands_) { if (entry.usage_count > 0) { recent.push_back(entry); } } - + std::sort(recent.begin(), recent.end(), [](const CommandEntry& a, const CommandEntry& b) { return a.last_used_ms > b.last_used_ms; }); - + if (recent.size() > static_cast(limit)) { recent.resize(limit); } - + return recent; } std::vector CommandPalette::GetFrequentCommands(int limit) { std::vector frequent; - + for (const auto& [name, entry] : commands_) { if (entry.usage_count > 0) { frequent.push_back(entry); } } - + std::sort(frequent.begin(), frequent.end(), [](const CommandEntry& a, const CommandEntry& b) { return a.usage_count > b.usage_count; }); - + if (frequent.size() > static_cast(limit)) { frequent.resize(limit); } - + return frequent; } diff --git a/src/app/editor/system/command_palette.h b/src/app/editor/system/command_palette.h index 7bd625ab..fa73fa23 100644 --- a/src/app/editor/system/command_palette.h +++ b/src/app/editor/system/command_palette.h @@ -1,10 +1,10 @@ #ifndef YAZE_APP_EDITOR_SYSTEM_COMMAND_PALETTE_H_ #define YAZE_APP_EDITOR_SYSTEM_COMMAND_PALETTE_H_ -#include -#include -#include #include +#include +#include +#include namespace yaze { namespace editor { @@ -24,21 +24,21 @@ class CommandPalette { void AddCommand(const std::string& name, const std::string& category, const std::string& description, const std::string& shortcut, std::function callback); - + void RecordUsage(const std::string& name); - + std::vector SearchCommands(const std::string& query); - + std::vector GetRecentCommands(int limit = 10); - + std::vector GetFrequentCommands(int limit = 10); - + void SaveHistory(const std::string& filepath); void LoadHistory(const std::string& filepath); - + private: std::unordered_map commands_; - + int FuzzyScore(const std::string& text, const std::string& query); }; diff --git a/src/app/editor/system/editor_card_registry.cc b/src/app/editor/system/editor_card_registry.cc index f4752f2f..35ad5a10 100644 --- a/src/app/editor/system/editor_card_registry.cc +++ b/src/app/editor/system/editor_card_registry.cc @@ -19,9 +19,11 @@ namespace editor { void EditorCardRegistry::RegisterSession(size_t session_id) { if (session_cards_.find(session_id) == session_cards_.end()) { session_cards_[session_id] = std::vector(); - session_card_mapping_[session_id] = std::unordered_map(); + session_card_mapping_[session_id] = + std::unordered_map(); UpdateSessionCount(); - LOG_INFO("EditorCardRegistry", "Registered session %zu (total: %zu)", session_id, session_count_); + LOG_INFO("EditorCardRegistry", "Registered session %zu (total: %zu)", + session_id, session_count_); } } @@ -32,7 +34,7 @@ void EditorCardRegistry::UnregisterSession(size_t session_id) { session_cards_.erase(it); session_card_mapping_.erase(session_id); UpdateSessionCount(); - + // Reset active session if it was the one being removed if (active_session_ == session_id) { active_session_ = 0; @@ -40,8 +42,9 @@ void EditorCardRegistry::UnregisterSession(size_t session_id) { active_session_ = session_cards_.begin()->first; } } - - LOG_INFO("EditorCardRegistry", "Unregistered session %zu (total: %zu)", session_id, session_count_); + + LOG_INFO("EditorCardRegistry", "Unregistered session %zu (total: %zu)", + session_id, session_count_); } } @@ -55,47 +58,47 @@ void EditorCardRegistry::SetActiveSession(size_t session_id) { // Card Registration // ============================================================================ -void EditorCardRegistry::RegisterCard(size_t session_id, const CardInfo& base_info) { +void EditorCardRegistry::RegisterCard(size_t session_id, + const CardInfo& base_info) { RegisterSession(session_id); // Ensure session exists - + std::string prefixed_id = MakeCardId(session_id, base_info.card_id); - + // Check if already registered to avoid duplicates if (cards_.find(prefixed_id) != cards_.end()) { - LOG_WARN("EditorCardRegistry", "Card '%s' already registered, skipping duplicate", prefixed_id.c_str()); + LOG_WARN("EditorCardRegistry", + "Card '%s' already registered, skipping duplicate", + prefixed_id.c_str()); return; } - + // Create new CardInfo with prefixed ID CardInfo prefixed_info = base_info; prefixed_info.card_id = prefixed_id; - + // If no visibility_flag provided, create centralized one if (!prefixed_info.visibility_flag) { centralized_visibility_[prefixed_id] = false; // Hidden by default prefixed_info.visibility_flag = ¢ralized_visibility_[prefixed_id]; } - + // Register the card cards_[prefixed_id] = prefixed_info; - + // Track in our session mapping session_cards_[session_id].push_back(prefixed_id); session_card_mapping_[session_id][base_info.card_id] = prefixed_id; - - LOG_INFO("EditorCardRegistry", "Registered card %s -> %s for session %zu", base_info.card_id.c_str(), prefixed_id.c_str(), session_id); + + LOG_INFO("EditorCardRegistry", "Registered card %s -> %s for session %zu", + base_info.card_id.c_str(), prefixed_id.c_str(), session_id); } -void EditorCardRegistry::RegisterCard(size_t session_id, - const std::string& card_id, - const std::string& display_name, - const std::string& icon, - const std::string& category, - const std::string& shortcut_hint, - int priority, - std::function on_show, - std::function on_hide, - bool visible_by_default) { +void EditorCardRegistry::RegisterCard( + size_t session_id, const std::string& card_id, + const std::string& display_name, const std::string& icon, + const std::string& category, const std::string& shortcut_hint, int priority, + std::function on_show, std::function on_hide, + bool visible_by_default) { CardInfo info; info.card_id = card_id; info.display_name = display_name; @@ -106,62 +109,64 @@ void EditorCardRegistry::RegisterCard(size_t session_id, info.visibility_flag = nullptr; // Will be created in RegisterCard info.on_show = on_show; info.on_hide = on_hide; - + RegisterCard(session_id, info); - + // Set initial visibility if requested if (visible_by_default) { ShowCard(session_id, card_id); } } -void EditorCardRegistry::UnregisterCard(size_t session_id, const std::string& base_card_id) { +void EditorCardRegistry::UnregisterCard(size_t session_id, + const std::string& base_card_id) { std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return; } - + auto it = cards_.find(prefixed_id); if (it != cards_.end()) { - LOG_INFO("EditorCardRegistry", "Unregistered card: %s", prefixed_id.c_str()); + LOG_INFO("EditorCardRegistry", "Unregistered card: %s", + prefixed_id.c_str()); cards_.erase(it); centralized_visibility_.erase(prefixed_id); - + // Remove from session tracking auto& session_card_list = session_cards_[session_id]; - session_card_list.erase( - std::remove(session_card_list.begin(), session_card_list.end(), prefixed_id), - session_card_list.end()); - + session_card_list.erase(std::remove(session_card_list.begin(), + session_card_list.end(), prefixed_id), + session_card_list.end()); + session_card_mapping_[session_id].erase(base_card_id); } } void EditorCardRegistry::UnregisterCardsWithPrefix(const std::string& prefix) { std::vector to_remove; - + // Find all cards with the given prefix for (const auto& [card_id, card_info] : cards_) { if (card_id.find(prefix) == 0) { // Starts with prefix to_remove.push_back(card_id); } } - + // Remove them for (const auto& card_id : to_remove) { cards_.erase(card_id); centralized_visibility_.erase(card_id); - LOG_INFO("EditorCardRegistry", "Unregistered card with prefix '%s': %s", prefix.c_str(), card_id.c_str()); + LOG_INFO("EditorCardRegistry", "Unregistered card with prefix '%s': %s", + prefix.c_str(), card_id.c_str()); } - + // Also clean up session tracking for (auto& [session_id, card_list] : session_cards_) { - card_list.erase( - std::remove_if(card_list.begin(), card_list.end(), - [&prefix](const std::string& id) { - return id.find(prefix) == 0; - }), - card_list.end()); + card_list.erase(std::remove_if(card_list.begin(), card_list.end(), + [&prefix](const std::string& id) { + return id.find(prefix) == 0; + }), + card_list.end()); } } @@ -171,19 +176,20 @@ void EditorCardRegistry::ClearAllCards() { session_cards_.clear(); session_card_mapping_.clear(); session_count_ = 0; - LOG_INFO("EditorCardRegistry", "Cleared all cards"); + LOG_INFO("EditorCardRegistry", "Cleared all cards"); } // ============================================================================ // Card Control (Programmatic, No GUI) // ============================================================================ -bool EditorCardRegistry::ShowCard(size_t session_id, const std::string& base_card_id) { +bool EditorCardRegistry::ShowCard(size_t session_id, + const std::string& base_card_id) { std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return false; } - + auto it = cards_.find(prefixed_id); if (it != cards_.end()) { if (it->second.visibility_flag) { @@ -197,12 +203,13 @@ bool EditorCardRegistry::ShowCard(size_t session_id, const std::string& base_car return false; } -bool EditorCardRegistry::HideCard(size_t session_id, const std::string& base_card_id) { +bool EditorCardRegistry::HideCard(size_t session_id, + const std::string& base_card_id) { std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return false; } - + auto it = cards_.find(prefixed_id); if (it != cards_.end()) { if (it->second.visibility_flag) { @@ -216,17 +223,18 @@ bool EditorCardRegistry::HideCard(size_t session_id, const std::string& base_car return false; } -bool EditorCardRegistry::ToggleCard(size_t session_id, const std::string& base_card_id) { +bool EditorCardRegistry::ToggleCard(size_t session_id, + const std::string& base_card_id) { std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return false; } - + auto it = cards_.find(prefixed_id); if (it != cards_.end() && it->second.visibility_flag) { bool new_state = !(*it->second.visibility_flag); *it->second.visibility_flag = new_state; - + if (new_state && it->second.on_show) { it->second.on_show(); } else if (!new_state && it->second.on_hide) { @@ -237,12 +245,13 @@ bool EditorCardRegistry::ToggleCard(size_t session_id, const std::string& base_c return false; } -bool EditorCardRegistry::IsCardVisible(size_t session_id, const std::string& base_card_id) const { +bool EditorCardRegistry::IsCardVisible(size_t session_id, + const std::string& base_card_id) const { std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return false; } - + auto it = cards_.find(prefixed_id); if (it != cards_.end() && it->second.visibility_flag) { return *it->second.visibility_flag; @@ -250,12 +259,13 @@ bool EditorCardRegistry::IsCardVisible(size_t session_id, const std::string& bas return false; } -bool* EditorCardRegistry::GetVisibilityFlag(size_t session_id, const std::string& base_card_id) { +bool* EditorCardRegistry::GetVisibilityFlag(size_t session_id, + const std::string& base_card_id) { std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return nullptr; } - + auto it = cards_.find(prefixed_id); if (it != cards_.end()) { return it->second.visibility_flag; @@ -297,7 +307,8 @@ void EditorCardRegistry::HideAllCardsInSession(size_t session_id) { } } -void EditorCardRegistry::ShowAllCardsInCategory(size_t session_id, const std::string& category) { +void EditorCardRegistry::ShowAllCardsInCategory(size_t session_id, + const std::string& category) { auto it = session_cards_.find(session_id); if (it != session_cards_.end()) { for (const auto& prefixed_card_id : it->second) { @@ -314,7 +325,8 @@ void EditorCardRegistry::ShowAllCardsInCategory(size_t session_id, const std::st } } -void EditorCardRegistry::HideAllCardsInCategory(size_t session_id, const std::string& category) { +void EditorCardRegistry::HideAllCardsInCategory(size_t session_id, + const std::string& category) { auto it = session_cards_.find(session_id); if (it != session_cards_.end()) { for (const auto& prefixed_card_id : it->second) { @@ -331,23 +343,24 @@ void EditorCardRegistry::HideAllCardsInCategory(size_t session_id, const std::st } } -void EditorCardRegistry::ShowOnlyCard(size_t session_id, const std::string& base_card_id) { +void EditorCardRegistry::ShowOnlyCard(size_t session_id, + const std::string& base_card_id) { // First get the category of the target card std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return; } - + auto target_it = cards_.find(prefixed_id); if (target_it == cards_.end()) { return; } - + std::string category = target_it->second.category; - + // Hide all cards in the same category HideAllCardsInCategory(session_id, category); - + // Show the target card ShowCard(session_id, base_card_id); } @@ -356,7 +369,8 @@ void EditorCardRegistry::ShowOnlyCard(size_t session_id, const std::string& base // Query Methods // ============================================================================ -std::vector EditorCardRegistry::GetCardsInSession(size_t session_id) const { +std::vector EditorCardRegistry::GetCardsInSession( + size_t session_id) const { auto it = session_cards_.find(session_id); if (it != session_cards_.end()) { return it->second; @@ -364,10 +378,10 @@ std::vector EditorCardRegistry::GetCardsInSession(size_t session_id return {}; } -std::vector EditorCardRegistry::GetCardsInCategory(size_t session_id, - const std::string& category) const { +std::vector EditorCardRegistry::GetCardsInCategory( + size_t session_id, const std::string& category) const { std::vector result; - + auto it = session_cards_.find(session_id); if (it != session_cards_.end()) { for (const auto& prefixed_card_id : it->second) { @@ -377,26 +391,27 @@ std::vector EditorCardRegistry::GetCardsInCategory(size_t session_id, } } } - + // Sort by priority std::sort(result.begin(), result.end(), - [](const CardInfo& a, const CardInfo& b) { - return a.priority < b.priority; - }); - + [](const CardInfo& a, const CardInfo& b) { + return a.priority < b.priority; + }); + return result; } -std::vector EditorCardRegistry::GetAllCategories(size_t session_id) const { +std::vector EditorCardRegistry::GetAllCategories( + size_t session_id) const { std::vector categories; - + auto it = session_cards_.find(session_id); if (it != session_cards_.end()) { for (const auto& prefixed_card_id : it->second) { auto card_it = cards_.find(prefixed_card_id); if (card_it != cards_.end()) { - if (std::find(categories.begin(), categories.end(), - card_it->second.category) == categories.end()) { + if (std::find(categories.begin(), categories.end(), + card_it->second.category) == categories.end()) { categories.push_back(card_it->second.category); } } @@ -405,13 +420,13 @@ std::vector EditorCardRegistry::GetAllCategories(size_t session_id) return categories; } -const CardInfo* EditorCardRegistry::GetCardInfo(size_t session_id, - const std::string& base_card_id) const { +const CardInfo* EditorCardRegistry::GetCardInfo( + size_t session_id, const std::string& base_card_id) const { std::string prefixed_id = GetPrefixedCardId(session_id, base_card_id); if (prefixed_id.empty()) { return nullptr; } - + auto it = cards_.find(prefixed_id); if (it != cards_.end()) { return &it->second; @@ -422,8 +437,8 @@ const CardInfo* EditorCardRegistry::GetCardInfo(size_t session_id, std::vector EditorCardRegistry::GetAllCategories() const { std::vector categories; for (const auto& [card_id, card_info] : cards_) { - if (std::find(categories.begin(), categories.end(), - card_info.category) == categories.end()) { + if (std::find(categories.begin(), categories.end(), card_info.category) == + categories.end()) { categories.push_back(card_info.category); } } @@ -434,13 +449,14 @@ std::vector EditorCardRegistry::GetAllCategories() const { // View Menu Integration // ============================================================================ -void EditorCardRegistry::DrawViewMenuSection(size_t session_id, const std::string& category) { +void EditorCardRegistry::DrawViewMenuSection(size_t session_id, + const std::string& category) { auto cards = GetCardsInCategory(session_id, category); - + if (cards.empty()) { return; } - + if (ImGui::BeginMenu(category.c_str())) { for (const auto& card : cards) { DrawCardMenuItem(card); @@ -451,7 +467,7 @@ void EditorCardRegistry::DrawViewMenuSection(size_t session_id, const std::strin void EditorCardRegistry::DrawViewMenuAll(size_t session_id) { auto categories = GetAllCategories(session_id); - + for (const auto& category : categories) { DrawViewMenuSection(session_id, category); } @@ -461,59 +477,60 @@ void EditorCardRegistry::DrawViewMenuAll(size_t session_id) { // VSCode-Style Sidebar // ============================================================================ -void EditorCardRegistry::DrawSidebar(size_t session_id, - const std::string& category, - const std::vector& active_categories, - std::function on_category_switch, - std::function on_collapse) { +void EditorCardRegistry::DrawSidebar( + size_t session_id, const std::string& category, + const std::vector& active_categories, + std::function on_category_switch, + std::function on_collapse) { // Use ThemeManager for consistent theming const auto& theme = gui::ThemeManager::Get().GetCurrentTheme(); const float sidebar_width = GetSidebarWidth(); - + // Fixed sidebar window on the left edge of screen - exactly like VSCode // Positioned below menu bar, spans full height, fixed 48px width ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetFrameHeight())); - ImGui::SetNextWindowSize(ImVec2(sidebar_width, -1)); // Exactly 48px wide, full height - - ImGuiWindowFlags sidebar_flags = - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoDocking | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoFocusOnAppearing | + ImGui::SetNextWindowSize( + ImVec2(sidebar_width, -1)); // Exactly 48px wide, full height + + ImGuiWindowFlags sidebar_flags = + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNavFocus; - + // VSCode-style dark sidebar background with visible border ImVec4 sidebar_bg = ImVec4(0.18f, 0.18f, 0.20f, 1.0f); ImVec4 sidebar_border = ImVec4(0.4f, 0.4f, 0.45f, 1.0f); - + ImGui::PushStyleColor(ImGuiCol_WindowBg, sidebar_bg); ImGui::PushStyleColor(ImGuiCol_Border, sidebar_border); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f, 8.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 6.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); - + if (ImGui::Begin("##EditorCardSidebar", nullptr, sidebar_flags)) { // Category switcher buttons at top (only if multiple editors are active) if (active_categories.size() > 1) { ImVec4 accent = gui::ConvertColorToImVec4(theme.accent); ImVec4 inactive = gui::ConvertColorToImVec4(theme.button); - + for (const auto& cat : active_categories) { bool is_current = (cat == category); - + // Highlight current category with accent color if (is_current) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(accent.x, accent.y, accent.z, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(accent.x, accent.y, accent.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, + ImVec4(accent.x, accent.y, accent.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(accent.x, accent.y, accent.z, 1.0f)); } else { ImGui::PushStyleColor(ImGuiCol_Button, inactive); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gui::ConvertColorToImVec4(theme.button_hovered)); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + gui::ConvertColorToImVec4(theme.button_hovered)); } - + // Show first letter of category as button label std::string btn_label = cat.empty() ? "?" : std::string(1, cat[0]); if (ImGui::Button(btn_label.c_str(), ImVec2(40.0f, 32.0f))) { @@ -524,86 +541,97 @@ void EditorCardRegistry::DrawSidebar(size_t session_id, SetActiveCategory(cat); } } - + ImGui::PopStyleColor(2); - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s Editor\nClick to switch", cat.c_str()); } } - + ImGui::Dummy(ImVec2(0, 2.0f)); ImGui::Separator(); ImGui::Spacing(); } - + // Get cards for current category auto cards = GetCardsInCategory(session_id, category); - + // Set this category as active when showing cards if (!cards.empty()) { SetActiveCategory(category); } - + // Close All and Show All buttons (only if cards exist) if (!cards.empty()) { ImVec4 error_color = gui::ConvertColorToImVec4(theme.error); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4( - error_color.x * 0.6f, error_color.y * 0.6f, error_color.z * 0.6f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_Button, + ImVec4(error_color.x * 0.6f, error_color.y * 0.6f, + error_color.z * 0.6f, 0.9f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, error_color); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4( - error_color.x * 1.2f, error_color.y * 1.2f, error_color.z * 1.2f, 1.0f)); - + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ImVec4(error_color.x * 1.2f, error_color.y * 1.2f, + error_color.z * 1.2f, 1.0f)); + if (ImGui::Button(ICON_MD_CLOSE, ImVec2(40.0f, 36.0f))) { HideAllCardsInCategory(session_id, category); } - + ImGui::PopStyleColor(3); - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Close All %s Cards", category.c_str()); } - + // Show All button ImVec4 success_color = gui::ConvertColorToImVec4(theme.success); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4( - success_color.x * 0.6f, success_color.y * 0.6f, success_color.z * 0.6f, 0.7f)); + ImGui::PushStyleColor( + ImGuiCol_Button, + ImVec4(success_color.x * 0.6f, success_color.y * 0.6f, + success_color.z * 0.6f, 0.7f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, success_color); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4( - success_color.x * 1.2f, success_color.y * 1.2f, success_color.z * 1.2f, 1.0f)); - + ImGui::PushStyleColor( + ImGuiCol_ButtonActive, + ImVec4(success_color.x * 1.2f, success_color.y * 1.2f, + success_color.z * 1.2f, 1.0f)); + if (ImGui::Button(ICON_MD_DONE_ALL, ImVec2(40.0f, 36.0f))) { ShowAllCardsInCategory(session_id, category); } - + ImGui::PopStyleColor(3); - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Show All %s Cards", category.c_str()); } - + ImGui::Dummy(ImVec2(0, 2.0f)); - + // Draw individual card toggle buttons ImVec4 accent_color = gui::ConvertColorToImVec4(theme.accent); ImVec4 button_bg = gui::ConvertColorToImVec4(theme.button); - + for (const auto& card : cards) { ImGui::PushID(card.card_id.c_str()); bool is_active = card.visibility_flag && *card.visibility_flag; - + // Highlight active cards with accent color if (is_active) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4( - accent_color.x, accent_color.y, accent_color.z, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4( - accent_color.x, accent_color.y, accent_color.z, 0.7f)); + ImGui::PushStyleColor( + ImGuiCol_Button, + ImVec4(accent_color.x, accent_color.y, accent_color.z, 0.5f)); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + ImVec4(accent_color.x, accent_color.y, accent_color.z, 0.7f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, accent_color); } else { ImGui::PushStyleColor(ImGuiCol_Button, button_bg); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gui::ConvertColorToImVec4(theme.button_hovered)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, gui::ConvertColorToImVec4(theme.button_active)); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + gui::ConvertColorToImVec4(theme.button_hovered)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + gui::ConvertColorToImVec4(theme.button_active)); } // Icon-only button for each card @@ -617,40 +645,43 @@ void EditorCardRegistry::DrawSidebar(size_t session_id, // Show tooltip with card name and shortcut if (ImGui::IsItemHovered() || ImGui::IsItemActive()) { SetActiveCategory(category); - - ImGui::SetTooltip("%s\n%s", card.display_name.c_str(), - card.shortcut_hint.empty() ? "" : card.shortcut_hint.c_str()); + + ImGui::SetTooltip( + "%s\n%s", card.display_name.c_str(), + card.shortcut_hint.empty() ? "" : card.shortcut_hint.c_str()); } ImGui::PopID(); } } // End if (!cards.empty()) - + // Card Browser and Collapse buttons at bottom if (on_collapse) { ImGui::Dummy(ImVec2(0, 10.0f)); ImGui::Separator(); ImGui::Spacing(); - + // Collapse button ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.2f, 0.22f, 0.9f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.32f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.25f, 0.25f, 0.27f, 1.0f)); - + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.3f, 0.3f, 0.32f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ImVec4(0.25f, 0.25f, 0.27f, 1.0f)); + if (ImGui::Button(ICON_MD_KEYBOARD_ARROW_LEFT, ImVec2(40.0f, 36.0f))) { on_collapse(); } - + ImGui::PopStyleColor(3); - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hide Sidebar\nCtrl+B"); } } } ImGui::End(); - - ImGui::PopStyleVar(3); // WindowPadding, ItemSpacing, WindowBorderSize + + ImGui::PopStyleVar(3); // WindowPadding, ItemSpacing, WindowBorderSize ImGui::PopStyleColor(2); // WindowBg, Border } @@ -658,20 +689,24 @@ void EditorCardRegistry::DrawSidebar(size_t session_id, // Compact Controls for Menu Bar // ============================================================================ -void EditorCardRegistry::DrawCompactCardControl(size_t session_id, const std::string& category) { +void EditorCardRegistry::DrawCompactCardControl(size_t session_id, + const std::string& category) { auto cards = GetCardsInCategory(session_id, category); - + if (cards.empty()) { return; } - + // Compact dropdown - if (ImGui::BeginCombo("##CardControl", absl::StrFormat("%s Cards", ICON_MD_DASHBOARD).c_str())) { + if (ImGui::BeginCombo( + "##CardControl", + absl::StrFormat("%s Cards", ICON_MD_DASHBOARD).c_str())) { for (const auto& card : cards) { bool visible = card.visibility_flag ? *card.visibility_flag : false; - if (ImGui::MenuItem(absl::StrFormat("%s %s", card.icon.c_str(), - card.display_name.c_str()).c_str(), - nullptr, visible)) { + if (ImGui::MenuItem(absl::StrFormat("%s %s", card.icon.c_str(), + card.display_name.c_str()) + .c_str(), + nullptr, visible)) { ToggleCard(session_id, card.card_id); } } @@ -679,16 +714,17 @@ void EditorCardRegistry::DrawCompactCardControl(size_t session_id, const std::st } } -void EditorCardRegistry::DrawInlineCardToggles(size_t session_id, const std::string& category) { +void EditorCardRegistry::DrawInlineCardToggles(size_t session_id, + const std::string& category) { auto cards = GetCardsInCategory(session_id, category); - + size_t visible_count = 0; for (const auto& card : cards) { if (card.visibility_flag && *card.visibility_flag) { visible_count++; } } - + ImGui::Text("(%zu/%zu)", visible_count, cards.size()); } @@ -698,27 +734,29 @@ void EditorCardRegistry::DrawInlineCardToggles(size_t session_id, const std::str void EditorCardRegistry::DrawCardBrowser(size_t session_id, bool* p_open) { ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - - if (ImGui::Begin(absl::StrFormat("%s Card Browser", ICON_MD_DASHBOARD).c_str(), - p_open)) { - + + if (ImGui::Begin( + absl::StrFormat("%s Card Browser", ICON_MD_DASHBOARD).c_str(), + p_open)) { + static char search_filter[256] = ""; static std::string category_filter = "All"; - + // Search bar ImGui::SetNextItemWidth(300); - ImGui::InputTextWithHint("##Search", absl::StrFormat("%s Search cards...", - ICON_MD_SEARCH).c_str(), - search_filter, sizeof(search_filter)); - + ImGui::InputTextWithHint( + "##Search", + absl::StrFormat("%s Search cards...", ICON_MD_SEARCH).c_str(), + search_filter, sizeof(search_filter)); + ImGui::SameLine(); - + // Category filter if (ImGui::BeginCombo("##CategoryFilter", category_filter.c_str())) { if (ImGui::Selectable("All", category_filter == "All")) { category_filter = "All"; } - + auto categories = GetAllCategories(session_id); for (const auto& cat : categories) { if (ImGui::Selectable(cat.c_str(), category_filter == cat)) { @@ -727,56 +765,61 @@ void EditorCardRegistry::DrawCardBrowser(size_t session_id, bool* p_open) { } ImGui::EndCombo(); } - + ImGui::Separator(); - + // Card table - if (ImGui::BeginTable("##CardTable", 4, - ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | - ImGuiTableFlags_Borders)) { - + if (ImGui::BeginTable("##CardTable", 4, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("Visible", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, 120); - ImGui::TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, + 120); + ImGui::TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthFixed, + 100); ImGui::TableHeadersRow(); - - auto cards = (category_filter == "All") - ? GetCardsInSession(session_id) - : std::vector{}; - + + auto cards = (category_filter == "All") ? GetCardsInSession(session_id) + : std::vector{}; + if (category_filter != "All") { auto cat_cards = GetCardsInCategory(session_id, category_filter); for (const auto& card : cat_cards) { cards.push_back(card.card_id); } } - + for (const auto& card_id : cards) { auto card_it = cards_.find(card_id); - if (card_it == cards_.end()) continue; - + if (card_it == cards_.end()) + continue; + const auto& card = card_it->second; - + // Apply search filter std::string search_str = search_filter; if (!search_str.empty()) { std::string card_lower = card.display_name; - std::transform(card_lower.begin(), card_lower.end(), card_lower.begin(), ::tolower); - std::transform(search_str.begin(), search_str.end(), search_str.begin(), ::tolower); + std::transform(card_lower.begin(), card_lower.end(), + card_lower.begin(), ::tolower); + std::transform(search_str.begin(), search_str.end(), + search_str.begin(), ::tolower); if (card_lower.find(search_str) == std::string::npos) { continue; } } - + ImGui::TableNextRow(); - + // Visibility toggle ImGui::TableNextColumn(); if (card.visibility_flag) { bool visible = *card.visibility_flag; - if (ImGui::Checkbox(absl::StrFormat("##vis_%s", card.card_id.c_str()).c_str(), - &visible)) { + if (ImGui::Checkbox( + absl::StrFormat("##vis_%s", card.card_id.c_str()).c_str(), + &visible)) { *card.visibility_flag = visible; if (visible && card.on_show) { card.on_show(); @@ -785,20 +828,20 @@ void EditorCardRegistry::DrawCardBrowser(size_t session_id, bool* p_open) { } } } - + // Name with icon ImGui::TableNextColumn(); ImGui::Text("%s %s", card.icon.c_str(), card.display_name.c_str()); - + // Category ImGui::TableNextColumn(); ImGui::Text("%s", card.category.c_str()); - + // Shortcut ImGui::TableNextColumn(); ImGui::TextDisabled("%s", card.shortcut_hint.c_str()); } - + ImGui::EndTable(); } } @@ -809,21 +852,23 @@ void EditorCardRegistry::DrawCardBrowser(size_t session_id, bool* p_open) { // Workspace Presets // ============================================================================ -void EditorCardRegistry::SavePreset(const std::string& name, const std::string& description) { +void EditorCardRegistry::SavePreset(const std::string& name, + const std::string& description) { WorkspacePreset preset; preset.name = name; preset.description = description; - + // Collect all visible cards across all sessions for (const auto& [card_id, card_info] : cards_) { if (card_info.visibility_flag && *card_info.visibility_flag) { preset.visible_cards.push_back(card_id); } } - + presets_[name] = preset; SavePresetsToFile(); - LOG_INFO("EditorCardRegistry", "Saved preset: %s (%zu cards)", name.c_str(), preset.visible_cards.size()); + LOG_INFO("EditorCardRegistry", "Saved preset: %s (%zu cards)", name.c_str(), + preset.visible_cards.size()); } bool EditorCardRegistry::LoadPreset(const std::string& name) { @@ -831,14 +876,14 @@ bool EditorCardRegistry::LoadPreset(const std::string& name) { if (it == presets_.end()) { return false; } - + // First hide all cards for (auto& [card_id, card_info] : cards_) { if (card_info.visibility_flag) { *card_info.visibility_flag = false; } } - + // Then show preset cards for (const auto& card_id : it->second.visible_cards) { auto card_it = cards_.find(card_id); @@ -849,7 +894,7 @@ bool EditorCardRegistry::LoadPreset(const std::string& name) { } } } - + LOG_INFO("EditorCardRegistry", "Loaded preset: %s", name.c_str()); return true; } @@ -859,7 +904,8 @@ void EditorCardRegistry::DeletePreset(const std::string& name) { SavePresetsToFile(); } -std::vector EditorCardRegistry::GetPresets() const { +std::vector +EditorCardRegistry::GetPresets() const { std::vector result; for (const auto& [name, preset] : presets_) { result.push_back(preset); @@ -882,9 +928,10 @@ void EditorCardRegistry::HideAll(size_t session_id) { void EditorCardRegistry::ResetToDefaults(size_t session_id) { // Hide all cards first HideAllCardsInSession(session_id); - + // TODO: Load default visibility from config file or hardcoded defaults - LOG_INFO("EditorCardRegistry", "Reset to defaults for session %zu", session_id); + LOG_INFO("EditorCardRegistry", "Reset to defaults for session %zu", + session_id); } // ============================================================================ @@ -911,7 +958,8 @@ size_t EditorCardRegistry::GetVisibleCardCount(size_t session_id) const { // Session Prefixing Utilities // ============================================================================ -std::string EditorCardRegistry::MakeCardId(size_t session_id, const std::string& base_id) const { +std::string EditorCardRegistry::MakeCardId(size_t session_id, + const std::string& base_id) const { if (ShouldPrefixCards()) { return absl::StrFormat("s%zu.%s", session_id, base_id); } @@ -926,8 +974,8 @@ void EditorCardRegistry::UpdateSessionCount() { session_count_ = session_cards_.size(); } -std::string EditorCardRegistry::GetPrefixedCardId(size_t session_id, - const std::string& base_id) const { +std::string EditorCardRegistry::GetPrefixedCardId( + size_t session_id, const std::string& base_id) const { auto session_it = session_card_mapping_.find(session_id); if (session_it != session_card_mapping_.end()) { auto card_it = session_it->second.find(base_id); @@ -935,12 +983,12 @@ std::string EditorCardRegistry::GetPrefixedCardId(size_t session_id, return card_it->second; } } - + // Fallback: try unprefixed ID (for single session or direct access) if (cards_.find(base_id) != cards_.end()) { return base_id; } - + return ""; // Card not found } @@ -966,12 +1014,13 @@ void EditorCardRegistry::LoadPresetsFromFile() { void EditorCardRegistry::DrawCardMenuItem(const CardInfo& info) { bool visible = info.visibility_flag ? *info.visibility_flag : false; - - std::string label = absl::StrFormat("%s %s", info.icon.c_str(), - info.display_name.c_str()); - - const char* shortcut = info.shortcut_hint.empty() ? nullptr : info.shortcut_hint.c_str(); - + + std::string label = + absl::StrFormat("%s %s", info.icon.c_str(), info.display_name.c_str()); + + const char* shortcut = + info.shortcut_hint.empty() ? nullptr : info.shortcut_hint.c_str(); + if (ImGui::MenuItem(label.c_str(), shortcut, visible)) { if (info.visibility_flag) { *info.visibility_flag = !visible; @@ -984,14 +1033,16 @@ void EditorCardRegistry::DrawCardMenuItem(const CardInfo& info) { } } -void EditorCardRegistry::DrawCardInSidebar(const CardInfo& info, bool is_active) { +void EditorCardRegistry::DrawCardInSidebar(const CardInfo& info, + bool is_active) { if (is_active) { ImGui::PushStyleColor(ImGuiCol_Button, gui::GetPrimaryVec4()); } - - if (ImGui::Button(absl::StrFormat("%s %s", info.icon.c_str(), - info.display_name.c_str()).c_str(), - ImVec2(-1, 0))) { + + if (ImGui::Button( + absl::StrFormat("%s %s", info.icon.c_str(), info.display_name.c_str()) + .c_str(), + ImVec2(-1, 0))) { if (info.visibility_flag) { *info.visibility_flag = !*info.visibility_flag; if (*info.visibility_flag && info.on_show) { @@ -1001,7 +1052,7 @@ void EditorCardRegistry::DrawCardInSidebar(const CardInfo& info, bool is_active) } } } - + if (is_active) { ImGui::PopStyleColor(); } @@ -1009,4 +1060,3 @@ void EditorCardRegistry::DrawCardInSidebar(const CardInfo& info, bool is_active) } // namespace editor } // namespace yaze - diff --git a/src/app/editor/system/editor_card_registry.h b/src/app/editor/system/editor_card_registry.h index 1f4d2f39..842be657 100644 --- a/src/app/editor/system/editor_card_registry.h +++ b/src/app/editor/system/editor_card_registry.h @@ -23,16 +23,16 @@ class EditorCard; * organized by category, and controlled programmatically. */ struct CardInfo { - std::string card_id; // Unique identifier (e.g., "dungeon.room_selector") - std::string display_name; // Human-readable name (e.g., "Room Selector") - std::string icon; // Material icon - std::string category; // Category (e.g., "Dungeon", "Graphics", "Palette") - std::string shortcut_hint; // Display hint (e.g., "Ctrl+Shift+R") - bool* visibility_flag; // Pointer to bool controlling visibility - EditorCard* card_instance; // Pointer to actual card (optional) - std::function on_show; // Callback when card is shown - std::function on_hide; // Callback when card is hidden - int priority; // Display priority for menus (lower = higher) + std::string card_id; // Unique identifier (e.g., "dungeon.room_selector") + std::string display_name; // Human-readable name (e.g., "Room Selector") + std::string icon; // Material icon + std::string category; // Category (e.g., "Dungeon", "Graphics", "Palette") + std::string shortcut_hint; // Display hint (e.g., "Ctrl+Shift+R") + bool* visibility_flag; // Pointer to bool controlling visibility + EditorCard* card_instance; // Pointer to actual card (optional) + std::function on_show; // Callback when card is shown + std::function on_hide; // Callback when card is hidden + int priority; // Display priority for menus (lower = higher) }; /** @@ -79,17 +79,17 @@ class EditorCardRegistry { public: EditorCardRegistry() = default; ~EditorCardRegistry() = default; - + // Non-copyable, non-movable (manages pointers and callbacks) EditorCardRegistry(const EditorCardRegistry&) = delete; EditorCardRegistry& operator=(const EditorCardRegistry&) = delete; EditorCardRegistry(EditorCardRegistry&&) = delete; EditorCardRegistry& operator=(EditorCardRegistry&&) = delete; - + // ============================================================================ // Session Lifecycle Management // ============================================================================ - + /** * @brief Register a new session in the registry * @param session_id Unique session identifier @@ -98,7 +98,7 @@ class EditorCardRegistry { * Must be called before registering cards for a session. */ void RegisterSession(size_t session_id); - + /** * @brief Unregister a session and all its cards * @param session_id Session identifier to remove @@ -106,7 +106,7 @@ class EditorCardRegistry { * Automatically unregisters all cards associated with the session. */ void UnregisterSession(size_t session_id); - + /** * @brief Set the currently active session * @param session_id Session to make active @@ -114,11 +114,11 @@ class EditorCardRegistry { * Used for determining whether to apply card ID prefixing. */ void SetActiveSession(size_t session_id); - + // ============================================================================ // Card Registration // ============================================================================ - + /** * @brief Register a card for a specific session * @param session_id Session this card belongs to @@ -128,28 +128,25 @@ class EditorCardRegistry { * automatically applies session prefixing when multiple sessions exist. */ void RegisterCard(size_t session_id, const CardInfo& base_info); - + /** * @brief Register a card with inline parameters (convenience method) */ - void RegisterCard(size_t session_id, - const std::string& card_id, - const std::string& display_name, - const std::string& icon, - const std::string& category, - const std::string& shortcut_hint = "", - int priority = 50, - std::function on_show = nullptr, - std::function on_hide = nullptr, - bool visible_by_default = false); - + void RegisterCard(size_t session_id, const std::string& card_id, + const std::string& display_name, const std::string& icon, + const std::string& category, + const std::string& shortcut_hint = "", int priority = 50, + std::function on_show = nullptr, + std::function on_hide = nullptr, + bool visible_by_default = false); + /** * @brief Unregister a specific card * @param session_id Session the card belongs to * @param base_card_id Unprefixed card ID */ void UnregisterCard(size_t session_id, const std::string& base_card_id); - + /** * @brief Unregister all cards with a given prefix * @param prefix Prefix to match (e.g., "s0" or "s1.dungeon") @@ -157,16 +154,16 @@ class EditorCardRegistry { * Useful for cleaning up session cards or category cards. */ void UnregisterCardsWithPrefix(const std::string& prefix); - + /** * @brief Remove all registered cards (use with caution) */ void ClearAllCards(); - + // ============================================================================ // Card Control (Programmatic, No GUI) // ============================================================================ - + /** * @brief Show a card programmatically * @param session_id Session the card belongs to @@ -174,176 +171,178 @@ class EditorCardRegistry { * @return true if card was found and shown */ bool ShowCard(size_t session_id, const std::string& base_card_id); - + /** * @brief Hide a card programmatically */ bool HideCard(size_t session_id, const std::string& base_card_id); - + /** * @brief Toggle a card's visibility */ bool ToggleCard(size_t session_id, const std::string& base_card_id); - + /** * @brief Check if a card is currently visible */ bool IsCardVisible(size_t session_id, const std::string& base_card_id) const; - + /** * @brief Get visibility flag pointer for a card * @return Pointer to bool controlling card visibility (for passing to EditorCard::Begin) */ bool* GetVisibilityFlag(size_t session_id, const std::string& base_card_id); - + // ============================================================================ // Batch Operations // ============================================================================ - + /** * @brief Show all cards in a specific session */ void ShowAllCardsInSession(size_t session_id); - + /** * @brief Hide all cards in a specific session */ void HideAllCardsInSession(size_t session_id); - + /** * @brief Show all cards in a category for a session */ void ShowAllCardsInCategory(size_t session_id, const std::string& category); - + /** * @brief Hide all cards in a category for a session */ void HideAllCardsInCategory(size_t session_id, const std::string& category); - + /** * @brief Show only one card, hiding all others in its category */ void ShowOnlyCard(size_t session_id, const std::string& base_card_id); - + // ============================================================================ // Query Methods // ============================================================================ - + /** * @brief Get all cards registered for a session * @return Vector of prefixed card IDs */ std::vector GetCardsInSession(size_t session_id) const; - + /** * @brief Get cards in a specific category for a session */ - std::vector GetCardsInCategory(size_t session_id, const std::string& category) const; - + std::vector GetCardsInCategory(size_t session_id, + const std::string& category) const; + /** * @brief Get all categories for a session */ std::vector GetAllCategories(size_t session_id) const; - + /** * @brief Get card metadata * @param session_id Session the card belongs to * @param base_card_id Unprefixed card ID */ - const CardInfo* GetCardInfo(size_t session_id, const std::string& base_card_id) const; - + const CardInfo* GetCardInfo(size_t session_id, + const std::string& base_card_id) const; + /** * @brief Get all registered categories across all sessions */ std::vector GetAllCategories() const; - + // ============================================================================ // View Menu Integration // ============================================================================ - + /** * @brief Draw view menu section for a category */ void DrawViewMenuSection(size_t session_id, const std::string& category); - + /** * @brief Draw all categories as view menu submenus */ void DrawViewMenuAll(size_t session_id); - + // ============================================================================ // VSCode-Style Sidebar // ============================================================================ - + /** * @brief Draw sidebar for a category with session filtering */ - void DrawSidebar(size_t session_id, - const std::string& category, - const std::vector& active_categories = {}, - std::function on_category_switch = nullptr, - std::function on_collapse = nullptr); - + void DrawSidebar( + size_t session_id, const std::string& category, + const std::vector& active_categories = {}, + std::function on_category_switch = nullptr, + std::function on_collapse = nullptr); + static constexpr float GetSidebarWidth() { return 48.0f; } - + // ============================================================================ // Compact Controls for Menu Bar // ============================================================================ - + /** * @brief Draw compact card control for active editor's cards */ void DrawCompactCardControl(size_t session_id, const std::string& category); - + /** * @brief Draw minimal inline card toggles */ void DrawInlineCardToggles(size_t session_id, const std::string& category); - + // ============================================================================ // Card Browser UI // ============================================================================ - + /** * @brief Draw visual card browser/toggler */ void DrawCardBrowser(size_t session_id, bool* p_open); - + // ============================================================================ // Workspace Presets // ============================================================================ - + struct WorkspacePreset { std::string name; std::vector visible_cards; // Card IDs std::string description; }; - + void SavePreset(const std::string& name, const std::string& description = ""); bool LoadPreset(const std::string& name); void DeletePreset(const std::string& name); std::vector GetPresets() const; - + // ============================================================================ // Quick Actions // ============================================================================ - + void ShowAll(size_t session_id); void HideAll(size_t session_id); void ResetToDefaults(size_t session_id); - + // ============================================================================ // Statistics // ============================================================================ - + size_t GetCardCount() const { return cards_.size(); } size_t GetVisibleCardCount(size_t session_id) const; size_t GetSessionCount() const { return session_count_; } - + // ============================================================================ // Session Prefixing Utilities // ============================================================================ - + /** * @brief Generate session-aware card ID * @param session_id Session identifier @@ -355,139 +354,141 @@ class EditorCardRegistry { * - Multi-session: "dungeon.room_selector" → "s0.dungeon.room_selector" */ std::string MakeCardId(size_t session_id, const std::string& base_id) const; - + /** * @brief Check if card IDs should be prefixed * @return true if session_count > 1 */ bool ShouldPrefixCards() const { return session_count_ > 1; } - + // ============================================================================ // Convenience Methods (for EditorManager direct usage without session_id) // ============================================================================ - + /** * @brief Register card for active session (convenience) */ void RegisterCard(const CardInfo& base_info) { RegisterCard(active_session_, base_info); } - + /** * @brief Show card in active session (convenience) */ bool ShowCard(const std::string& base_card_id) { return ShowCard(active_session_, base_card_id); } - + /** * @brief Hide card in active session (convenience) */ bool HideCard(const std::string& base_card_id) { return HideCard(active_session_, base_card_id); } - + /** * @brief Check if card is visible in active session (convenience) */ bool IsCardVisible(const std::string& base_card_id) const { return IsCardVisible(active_session_, base_card_id); } - + /** * @brief Hide all cards in category for active session (convenience) */ void HideAllCardsInCategory(const std::string& category) { HideAllCardsInCategory(active_session_, category); } - + /** * @brief Draw card browser for active session (convenience) */ void DrawCardBrowser(bool* p_open) { DrawCardBrowser(active_session_, p_open); } - + /** * @brief Get active category (for sidebar) */ std::string GetActiveCategory() const { return active_category_; } - + /** * @brief Set active category (for sidebar) */ - void SetActiveCategory(const std::string& category) { active_category_ = category; } - + void SetActiveCategory(const std::string& category) { + active_category_ = category; + } + /** * @brief Show all cards in category for active session (convenience) */ void ShowAllCardsInCategory(const std::string& category) { ShowAllCardsInCategory(active_session_, category); } - + /** * @brief Get visibility flag for active session (convenience) */ bool* GetVisibilityFlag(const std::string& base_card_id) { return GetVisibilityFlag(active_session_, base_card_id); } - + /** * @brief Show all cards for active session (convenience) */ - void ShowAll() { - ShowAll(active_session_); - } - + void ShowAll() { ShowAll(active_session_); } + /** * @brief Hide all cards for active session (convenience) */ - void HideAll() { - HideAll(active_session_); - } - + void HideAll() { HideAll(active_session_); } + /** * @brief Draw sidebar for active session (convenience) */ - void DrawSidebar(const std::string& category, - const std::vector& active_categories = {}, - std::function on_category_switch = nullptr, - std::function on_collapse = nullptr) { - DrawSidebar(active_session_, category, active_categories, on_category_switch, on_collapse); + void DrawSidebar( + const std::string& category, + const std::vector& active_categories = {}, + std::function on_category_switch = nullptr, + std::function on_collapse = nullptr) { + DrawSidebar(active_session_, category, active_categories, + on_category_switch, on_collapse); } - + private: // Core card storage (prefixed IDs → CardInfo) std::unordered_map cards_; - + // Centralized visibility flags for cards without external flags std::unordered_map centralized_visibility_; - + // Session tracking size_t session_count_ = 0; size_t active_session_ = 0; - + // Maps session_id → vector of prefixed card IDs registered for that session std::unordered_map> session_cards_; - + // Maps session_id → (base_card_id → prefixed_card_id) - std::unordered_map> session_card_mapping_; - + std::unordered_map> + session_card_mapping_; + // Workspace presets std::unordered_map presets_; - + // Active category tracking std::string active_category_; std::vector recent_categories_; static constexpr size_t kMaxRecentCategories = 5; - + // Helper methods void UpdateSessionCount(); - std::string GetPrefixedCardId(size_t session_id, const std::string& base_id) const; + std::string GetPrefixedCardId(size_t session_id, + const std::string& base_id) const; void UnregisterSessionCards(size_t session_id); void SavePresetsToFile(); void LoadPresetsFromFile(); - + // UI drawing helpers (internal) void DrawCardMenuItem(const CardInfo& info); void DrawCardInSidebar(const CardInfo& info, bool is_active); @@ -497,4 +498,3 @@ class EditorCardRegistry { } // namespace yaze #endif // YAZE_APP_EDITOR_SYSTEM_EDITOR_CARD_REGISTRY_H_ - diff --git a/src/app/editor/system/editor_registry.cc b/src/app/editor/system/editor_registry.cc index 95578548..662864d9 100644 --- a/src/app/editor/system/editor_registry.cc +++ b/src/app/editor/system/editor_registry.cc @@ -1,59 +1,58 @@ #include "editor_registry.h" +#include #include "absl/strings/str_format.h" #include "app/editor/editor.h" -#include namespace yaze { namespace editor { // Static mappings for editor types -const std::unordered_map EditorRegistry::kEditorCategories = { - {EditorType::kDungeon, "Dungeon"}, - {EditorType::kOverworld, "Overworld"}, - {EditorType::kGraphics, "Graphics"}, - {EditorType::kPalette, "Palette"}, - {EditorType::kSprite, "Sprite"}, - {EditorType::kScreen, "Screen"}, - {EditorType::kMessage, "Message"}, - {EditorType::kMusic, "Music"}, - {EditorType::kAssembly, "Assembly"}, - {EditorType::kEmulator, "Emulator"}, - {EditorType::kHex, "Hex"}, - {EditorType::kAgent, "Agent"}, - {EditorType::kSettings, "System"} -}; +const std::unordered_map + EditorRegistry::kEditorCategories = {{EditorType::kDungeon, "Dungeon"}, + {EditorType::kOverworld, "Overworld"}, + {EditorType::kGraphics, "Graphics"}, + {EditorType::kPalette, "Palette"}, + {EditorType::kSprite, "Sprite"}, + {EditorType::kScreen, "Screen"}, + {EditorType::kMessage, "Message"}, + {EditorType::kMusic, "Music"}, + {EditorType::kAssembly, "Assembly"}, + {EditorType::kEmulator, "Emulator"}, + {EditorType::kHex, "Hex"}, + {EditorType::kAgent, "Agent"}, + {EditorType::kSettings, "System"}}; -const std::unordered_map EditorRegistry::kEditorNames = { - {EditorType::kDungeon, "Dungeon Editor"}, - {EditorType::kOverworld, "Overworld Editor"}, - {EditorType::kGraphics, "Graphics Editor"}, - {EditorType::kPalette, "Palette Editor"}, - {EditorType::kSprite, "Sprite Editor"}, - {EditorType::kScreen, "Screen Editor"}, - {EditorType::kMessage, "Message Editor"}, - {EditorType::kMusic, "Music Editor"}, - {EditorType::kAssembly, "Assembly Editor"}, - {EditorType::kEmulator, "Emulator Editor"}, - {EditorType::kHex, "Hex Editor"}, - {EditorType::kAgent, "Agent Editor"}, - {EditorType::kSettings, "Settings Editor"} -}; +const std::unordered_map EditorRegistry::kEditorNames = + {{EditorType::kDungeon, "Dungeon Editor"}, + {EditorType::kOverworld, "Overworld Editor"}, + {EditorType::kGraphics, "Graphics Editor"}, + {EditorType::kPalette, "Palette Editor"}, + {EditorType::kSprite, "Sprite Editor"}, + {EditorType::kScreen, "Screen Editor"}, + {EditorType::kMessage, "Message Editor"}, + {EditorType::kMusic, "Music Editor"}, + {EditorType::kAssembly, "Assembly Editor"}, + {EditorType::kEmulator, "Emulator Editor"}, + {EditorType::kHex, "Hex Editor"}, + {EditorType::kAgent, "Agent Editor"}, + {EditorType::kSettings, "Settings Editor"}}; const std::unordered_map EditorRegistry::kCardBasedEditors = { - {EditorType::kDungeon, true}, - {EditorType::kOverworld, true}, - {EditorType::kGraphics, true}, - {EditorType::kPalette, true}, - {EditorType::kSprite, true}, - {EditorType::kScreen, true}, - {EditorType::kMessage, true}, - {EditorType::kMusic, true}, - {EditorType::kAssembly, true}, - {EditorType::kEmulator, true}, - {EditorType::kHex, true}, - {EditorType::kAgent, false}, // Agent: Traditional UI - {EditorType::kSettings, true} // Settings: Now card-based for better organization + {EditorType::kDungeon, true}, + {EditorType::kOverworld, true}, + {EditorType::kGraphics, true}, + {EditorType::kPalette, true}, + {EditorType::kSprite, true}, + {EditorType::kScreen, true}, + {EditorType::kMessage, true}, + {EditorType::kMusic, true}, + {EditorType::kAssembly, true}, + {EditorType::kEmulator, true}, + {EditorType::kHex, true}, + {EditorType::kAgent, false}, // Agent: Traditional UI + {EditorType::kSettings, + true} // Settings: Now card-based for better organization }; bool EditorRegistry::IsCardBasedEditor(EditorType type) { @@ -69,7 +68,8 @@ std::string EditorRegistry::GetEditorCategory(EditorType type) { return "Unknown"; } -EditorType EditorRegistry::GetEditorTypeFromCategory(const std::string& category) { +EditorType EditorRegistry::GetEditorTypeFromCategory( + const std::string& category) { for (const auto& [type, cat] : kEditorCategories) { if (cat == category) { return type; // Return first match @@ -98,7 +98,7 @@ void EditorRegistry::JumpToOverworldMap(int map_id) { void EditorRegistry::SwitchToEditor(EditorType editor_type) { ValidateEditorType(editor_type); - + auto it = registered_editors_.find(editor_type); if (it != registered_editors_.end() && it->second) { // Deactivate all other editors @@ -107,10 +107,11 @@ void EditorRegistry::SwitchToEditor(EditorType editor_type) { editor->set_active(false); } } - + // Activate the target editor it->second->set_active(true); - printf("[EditorRegistry] Switched to %s\n", GetEditorDisplayName(editor_type).c_str()); + printf("[EditorRegistry] Switched to %s\n", + GetEditorDisplayName(editor_type).c_str()); } } @@ -118,52 +119,56 @@ void EditorRegistry::HideCurrentEditorCards() { for (auto& [type, editor] : registered_editors_) { if (editor && IsCardBasedEditor(type)) { // TODO: Hide cards for this editor - printf("[EditorRegistry] Hiding cards for %s\n", GetEditorDisplayName(type).c_str()); + printf("[EditorRegistry] Hiding cards for %s\n", + GetEditorDisplayName(type).c_str()); } } } void EditorRegistry::ShowEditorCards(EditorType editor_type) { ValidateEditorType(editor_type); - + if (IsCardBasedEditor(editor_type)) { // TODO: Show cards for this editor - printf("[EditorRegistry] Showing cards for %s\n", GetEditorDisplayName(editor_type).c_str()); + printf("[EditorRegistry] Showing cards for %s\n", + GetEditorDisplayName(editor_type).c_str()); } } void EditorRegistry::ToggleEditorCards(EditorType editor_type) { ValidateEditorType(editor_type); - + if (IsCardBasedEditor(editor_type)) { // TODO: Toggle cards for this editor - printf("[EditorRegistry] Toggling cards for %s\n", GetEditorDisplayName(editor_type).c_str()); + printf("[EditorRegistry] Toggling cards for %s\n", + GetEditorDisplayName(editor_type).c_str()); } } -std::vector EditorRegistry::GetEditorsInCategory(const std::string& category) const { +std::vector EditorRegistry::GetEditorsInCategory( + const std::string& category) const { std::vector editors; - + for (const auto& [type, cat] : kEditorCategories) { if (cat == category) { editors.push_back(type); } } - + return editors; } std::vector EditorRegistry::GetAvailableCategories() const { std::vector categories; std::unordered_set seen; - + for (const auto& [type, category] : kEditorCategories) { if (seen.find(category) == seen.end()) { categories.push_back(category); seen.insert(category); } } - + return categories; } @@ -177,28 +182,30 @@ std::string EditorRegistry::GetEditorDisplayName(EditorType type) const { void EditorRegistry::RegisterEditor(EditorType type, Editor* editor) { ValidateEditorType(type); - + if (!editor) { throw std::invalid_argument("Editor pointer cannot be null"); } - + registered_editors_[type] = editor; - printf("[EditorRegistry] Registered %s\n", GetEditorDisplayName(type).c_str()); + printf("[EditorRegistry] Registered %s\n", + GetEditorDisplayName(type).c_str()); } void EditorRegistry::UnregisterEditor(EditorType type) { ValidateEditorType(type); - + auto it = registered_editors_.find(type); if (it != registered_editors_.end()) { registered_editors_.erase(it); - printf("[EditorRegistry] Unregistered %s\n", GetEditorDisplayName(type).c_str()); + printf("[EditorRegistry] Unregistered %s\n", + GetEditorDisplayName(type).c_str()); } } Editor* EditorRegistry::GetEditor(EditorType type) const { ValidateEditorType(type); - + auto it = registered_editors_.find(type); if (it != registered_editors_.end()) { return it->second; @@ -208,7 +215,7 @@ Editor* EditorRegistry::GetEditor(EditorType type) const { bool EditorRegistry::IsEditorActive(EditorType type) const { ValidateEditorType(type); - + auto it = registered_editors_.find(type); if (it != registered_editors_.end() && it->second) { return it->second->active(); @@ -218,7 +225,7 @@ bool EditorRegistry::IsEditorActive(EditorType type) const { bool EditorRegistry::IsEditorVisible(EditorType type) const { ValidateEditorType(type); - + auto it = registered_editors_.find(type); if (it != registered_editors_.end() && it->second) { return it->second->active(); @@ -228,7 +235,7 @@ bool EditorRegistry::IsEditorVisible(EditorType type) const { void EditorRegistry::SetEditorActive(EditorType type, bool active) { ValidateEditorType(type); - + auto it = registered_editors_.find(type); if (it != registered_editors_.end() && it->second) { it->second->set_active(active); diff --git a/src/app/editor/system/editor_registry.h b/src/app/editor/system/editor_registry.h index 8503d1cc..73e4fcaf 100644 --- a/src/app/editor/system/editor_registry.h +++ b/src/app/editor/system/editor_registry.h @@ -29,27 +29,28 @@ class EditorRegistry { static bool IsCardBasedEditor(EditorType type); static std::string GetEditorCategory(EditorType type); static EditorType GetEditorTypeFromCategory(const std::string& category); - + // Editor navigation void JumpToDungeonRoom(int room_id); void JumpToOverworldMap(int map_id); void SwitchToEditor(EditorType editor_type); - + // Editor card management void HideCurrentEditorCards(); void ShowEditorCards(EditorType editor_type); void ToggleEditorCards(EditorType editor_type); - + // Editor information - std::vector GetEditorsInCategory(const std::string& category) const; + std::vector GetEditorsInCategory( + const std::string& category) const; std::vector GetAvailableCategories() const; std::string GetEditorDisplayName(EditorType type) const; - + // Editor lifecycle void RegisterEditor(EditorType type, Editor* editor); void UnregisterEditor(EditorType type); Editor* GetEditor(EditorType type) const; - + // Editor state queries bool IsEditorActive(EditorType type) const; bool IsEditorVisible(EditorType type) const; @@ -60,10 +61,10 @@ class EditorRegistry { static const std::unordered_map kEditorCategories; static const std::unordered_map kEditorNames; static const std::unordered_map kCardBasedEditors; - + // Registered editors std::unordered_map registered_editors_; - + // Helper methods bool IsValidEditorType(EditorType type) const; void ValidateEditorType(EditorType type) const; diff --git a/src/app/editor/system/menu_orchestrator.cc b/src/app/editor/system/menu_orchestrator.cc index c83e4b1d..1a21e322 100644 --- a/src/app/editor/system/menu_orchestrator.cc +++ b/src/app/editor/system/menu_orchestrator.cc @@ -1,7 +1,6 @@ #include "menu_orchestrator.h" #include "absl/strings/str_format.h" -#include "core/features.h" #include "app/editor/editor.h" #include "app/editor/editor_manager.h" #include "app/editor/system/editor_registry.h" @@ -13,20 +12,17 @@ #include "app/editor/ui/menu_builder.h" #include "app/gui/core/icons.h" #include "app/rom.h" +#include "core/features.h" #include "zelda3/overworld/overworld_map.h" namespace yaze { namespace editor { MenuOrchestrator::MenuOrchestrator( - EditorManager* editor_manager, - MenuBuilder& menu_builder, - RomFileManager& rom_manager, - ProjectManager& project_manager, - EditorRegistry& editor_registry, - SessionCoordinator& session_coordinator, - ToastManager& toast_manager, - PopupManager& popup_manager) + EditorManager* editor_manager, MenuBuilder& menu_builder, + RomFileManager& rom_manager, ProjectManager& project_manager, + EditorRegistry& editor_registry, SessionCoordinator& session_coordinator, + ToastManager& toast_manager, PopupManager& popup_manager) : editor_manager_(editor_manager), menu_builder_(menu_builder), rom_manager_(rom_manager), @@ -34,12 +30,11 @@ MenuOrchestrator::MenuOrchestrator( editor_registry_(editor_registry), session_coordinator_(session_coordinator), toast_manager_(toast_manager), - popup_manager_(popup_manager) { -} + popup_manager_(popup_manager) {} void MenuOrchestrator::BuildMainMenu() { ClearMenu(); - + // Build all menu sections in order BuildFileMenu(); BuildEditMenu(); @@ -48,10 +43,10 @@ void MenuOrchestrator::BuildMainMenu() { BuildDebugMenu(); // Add Debug menu between Tools and Window BuildWindowMenu(); BuildHelpMenu(); - + // Draw the constructed menu menu_builder_.Draw(); - + menu_needs_refresh_ = false; } @@ -64,50 +59,48 @@ void MenuOrchestrator::BuildFileMenu() { void MenuOrchestrator::AddFileMenuItems() { // ROM Operations menu_builder_ - .Item("Open ROM", ICON_MD_FILE_OPEN, - [this]() { OnOpenRom(); }, "Ctrl+O") - .Item("Save ROM", ICON_MD_SAVE, - [this]() { OnSaveRom(); }, "Ctrl+S", - [this]() { return CanSaveRom(); }) - .Item("Save As...", ICON_MD_SAVE_AS, - [this]() { OnSaveRomAs(); }, nullptr, - [this]() { return CanSaveRom(); }) + .Item( + "Open ROM", ICON_MD_FILE_OPEN, [this]() { OnOpenRom(); }, "Ctrl+O") + .Item( + "Save ROM", ICON_MD_SAVE, [this]() { OnSaveRom(); }, "Ctrl+S", + [this]() { return CanSaveRom(); }) + .Item( + "Save As...", ICON_MD_SAVE_AS, [this]() { OnSaveRomAs(); }, nullptr, + [this]() { return CanSaveRom(); }) .Separator(); - + // Project Operations menu_builder_ .Item("New Project", ICON_MD_CREATE_NEW_FOLDER, [this]() { OnCreateProject(); }) - .Item("Open Project", ICON_MD_FOLDER_OPEN, - [this]() { OnOpenProject(); }) - .Item("Save Project", ICON_MD_SAVE, - [this]() { OnSaveProject(); }, nullptr, - [this]() { return CanSaveProject(); }) - .Item("Save Project As...", ICON_MD_SAVE_AS, - [this]() { OnSaveProjectAs(); }, nullptr, - [this]() { return CanSaveProject(); }) + .Item("Open Project", ICON_MD_FOLDER_OPEN, [this]() { OnOpenProject(); }) + .Item( + "Save Project", ICON_MD_SAVE, [this]() { OnSaveProject(); }, nullptr, + [this]() { return CanSaveProject(); }) + .Item( + "Save Project As...", ICON_MD_SAVE_AS, + [this]() { OnSaveProjectAs(); }, nullptr, + [this]() { return CanSaveProject(); }) .Separator(); - + // ROM Information and Validation menu_builder_ - .Item("ROM Information", ICON_MD_INFO, - [this]() { OnShowRomInfo(); }, nullptr, - [this]() { return HasActiveRom(); }) - .Item("Create Backup", ICON_MD_BACKUP, - [this]() { OnCreateBackup(); }, nullptr, - [this]() { return HasActiveRom(); }) - .Item("Validate ROM", ICON_MD_CHECK_CIRCLE, - [this]() { OnValidateRom(); }, nullptr, - [this]() { return HasActiveRom(); }) + .Item( + "ROM Information", ICON_MD_INFO, [this]() { OnShowRomInfo(); }, + nullptr, [this]() { return HasActiveRom(); }) + .Item( + "Create Backup", ICON_MD_BACKUP, [this]() { OnCreateBackup(); }, + nullptr, [this]() { return HasActiveRom(); }) + .Item( + "Validate ROM", ICON_MD_CHECK_CIRCLE, [this]() { OnValidateRom(); }, + nullptr, [this]() { return HasActiveRom(); }) .Separator(); - + // Settings and Quit menu_builder_ - .Item("Settings", ICON_MD_SETTINGS, - [this]() { OnShowSettings(); }) + .Item("Settings", ICON_MD_SETTINGS, [this]() { OnShowSettings(); }) .Separator() - .Item("Quit", ICON_MD_EXIT_TO_APP, - [this]() { OnQuit(); }, "Ctrl+Q"); + .Item("Quit", ICON_MD_EXIT_TO_APP, [this]() { OnQuit(); }, "Ctrl+Q"); } void MenuOrchestrator::BuildEditMenu() { @@ -119,34 +112,35 @@ void MenuOrchestrator::BuildEditMenu() { void MenuOrchestrator::AddEditMenuItems() { // Undo/Redo operations - delegate to current editor menu_builder_ - .Item("Undo", ICON_MD_UNDO, - [this]() { OnUndo(); }, "Ctrl+Z", - [this]() { return HasCurrentEditor(); }) - .Item("Redo", ICON_MD_REDO, - [this]() { OnRedo(); }, "Ctrl+Y", - [this]() { return HasCurrentEditor(); }) + .Item( + "Undo", ICON_MD_UNDO, [this]() { OnUndo(); }, "Ctrl+Z", + [this]() { return HasCurrentEditor(); }) + .Item( + "Redo", ICON_MD_REDO, [this]() { OnRedo(); }, "Ctrl+Y", + [this]() { return HasCurrentEditor(); }) .Separator(); - + // Clipboard operations - delegate to current editor menu_builder_ - .Item("Cut", ICON_MD_CONTENT_CUT, - [this]() { OnCut(); }, "Ctrl+X", - [this]() { return HasCurrentEditor(); }) - .Item("Copy", ICON_MD_CONTENT_COPY, - [this]() { OnCopy(); }, "Ctrl+C", - [this]() { return HasCurrentEditor(); }) - .Item("Paste", ICON_MD_CONTENT_PASTE, - [this]() { OnPaste(); }, "Ctrl+V", - [this]() { return HasCurrentEditor(); }) + .Item( + "Cut", ICON_MD_CONTENT_CUT, [this]() { OnCut(); }, "Ctrl+X", + [this]() { return HasCurrentEditor(); }) + .Item( + "Copy", ICON_MD_CONTENT_COPY, [this]() { OnCopy(); }, "Ctrl+C", + [this]() { return HasCurrentEditor(); }) + .Item( + "Paste", ICON_MD_CONTENT_PASTE, [this]() { OnPaste(); }, "Ctrl+V", + [this]() { return HasCurrentEditor(); }) .Separator(); - + // Search operations menu_builder_ - .Item("Find", ICON_MD_SEARCH, - [this]() { OnFind(); }, "Ctrl+F", - [this]() { return HasCurrentEditor(); }) - .Item("Find in Files", ICON_MD_SEARCH, - [this]() { OnShowGlobalSearch(); }, "Ctrl+Shift+F"); + .Item( + "Find", ICON_MD_SEARCH, [this]() { OnFind(); }, "Ctrl+F", + [this]() { return HasCurrentEditor(); }) + .Item( + "Find in Files", ICON_MD_SEARCH, [this]() { OnShowGlobalSearch(); }, + "Ctrl+Shift+F"); } void MenuOrchestrator::BuildViewMenu() { @@ -158,60 +152,76 @@ void MenuOrchestrator::BuildViewMenu() { void MenuOrchestrator::AddViewMenuItems() { // Editor Selection menu_builder_ - .Item("Editor Selection", ICON_MD_DASHBOARD, - [this]() { OnShowEditorSelection(); }, "Ctrl+E") + .Item( + "Editor Selection", ICON_MD_DASHBOARD, + [this]() { OnShowEditorSelection(); }, "Ctrl+E") .Separator(); - + // Individual Editor Shortcuts menu_builder_ - .Item("Overworld", ICON_MD_MAP, - [this]() { OnSwitchToEditor(EditorType::kOverworld); }, "Ctrl+1") - .Item("Dungeon", ICON_MD_CASTLE, - [this]() { OnSwitchToEditor(EditorType::kDungeon); }, "Ctrl+2") - .Item("Graphics", ICON_MD_IMAGE, - [this]() { OnSwitchToEditor(EditorType::kGraphics); }, "Ctrl+3") - .Item("Sprites", ICON_MD_TOYS, - [this]() { OnSwitchToEditor(EditorType::kSprite); }, "Ctrl+4") - .Item("Messages", ICON_MD_CHAT_BUBBLE, - [this]() { OnSwitchToEditor(EditorType::kMessage); }, "Ctrl+5") - .Item("Music", ICON_MD_MUSIC_NOTE, - [this]() { OnSwitchToEditor(EditorType::kMusic); }, "Ctrl+6") - .Item("Palettes", ICON_MD_PALETTE, - [this]() { OnSwitchToEditor(EditorType::kPalette); }, "Ctrl+7") - .Item("Screens", ICON_MD_TV, - [this]() { OnSwitchToEditor(EditorType::kScreen); }, "Ctrl+8") - .Item("Assembly", ICON_MD_CODE, - [this]() { OnSwitchToEditor(EditorType::kAssembly); }, "Ctrl+9") - .Item("Hex Editor", ICON_MD_DATA_ARRAY, - [this]() { OnShowHexEditor(); }, "Ctrl+0") + .Item( + "Overworld", ICON_MD_MAP, + [this]() { OnSwitchToEditor(EditorType::kOverworld); }, "Ctrl+1") + .Item( + "Dungeon", ICON_MD_CASTLE, + [this]() { OnSwitchToEditor(EditorType::kDungeon); }, "Ctrl+2") + .Item( + "Graphics", ICON_MD_IMAGE, + [this]() { OnSwitchToEditor(EditorType::kGraphics); }, "Ctrl+3") + .Item( + "Sprites", ICON_MD_TOYS, + [this]() { OnSwitchToEditor(EditorType::kSprite); }, "Ctrl+4") + .Item( + "Messages", ICON_MD_CHAT_BUBBLE, + [this]() { OnSwitchToEditor(EditorType::kMessage); }, "Ctrl+5") + .Item( + "Music", ICON_MD_MUSIC_NOTE, + [this]() { OnSwitchToEditor(EditorType::kMusic); }, "Ctrl+6") + .Item( + "Palettes", ICON_MD_PALETTE, + [this]() { OnSwitchToEditor(EditorType::kPalette); }, "Ctrl+7") + .Item( + "Screens", ICON_MD_TV, + [this]() { OnSwitchToEditor(EditorType::kScreen); }, "Ctrl+8") + .Item( + "Assembly", ICON_MD_CODE, + [this]() { OnSwitchToEditor(EditorType::kAssembly); }, "Ctrl+9") + .Item( + "Hex Editor", ICON_MD_DATA_ARRAY, [this]() { OnShowHexEditor(); }, + "Ctrl+0") .Separator(); - + // Special Editors #ifdef YAZE_WITH_GRPC menu_builder_ - .Item("AI Agent", ICON_MD_SMART_TOY, - [this]() { OnShowAIAgent(); }, "Ctrl+Shift+A") - .Item("Chat History", ICON_MD_CHAT, - [this]() { OnShowChatHistory(); }, "Ctrl+H") - .Item("Proposal Drawer", ICON_MD_PREVIEW, - [this]() { OnShowProposalDrawer(); }, "Ctrl+Shift+R"); + .Item( + "AI Agent", ICON_MD_SMART_TOY, [this]() { OnShowAIAgent(); }, + "Ctrl+Shift+A") + .Item( + "Chat History", ICON_MD_CHAT, [this]() { OnShowChatHistory(); }, + "Ctrl+H") + .Item( + "Proposal Drawer", ICON_MD_PREVIEW, + [this]() { OnShowProposalDrawer(); }, "Ctrl+Shift+R"); #endif - + menu_builder_ - .Item("Emulator", ICON_MD_VIDEOGAME_ASSET, - [this]() { OnShowEmulator(); }, "Ctrl+Shift+E") + .Item( + "Emulator", ICON_MD_VIDEOGAME_ASSET, [this]() { OnShowEmulator(); }, + "Ctrl+Shift+E") .Separator(); - + // Settings and UI menu_builder_ .Item("Display Settings", ICON_MD_DISPLAY_SETTINGS, [this]() { OnShowDisplaySettings(); }) .Separator(); - + // Additional UI Elements menu_builder_ - .Item("Card Browser", ICON_MD_DASHBOARD, - [this]() { OnShowCardBrowser(); }, "Ctrl+Shift+B") + .Item( + "Card Browser", ICON_MD_DASHBOARD, [this]() { OnShowCardBrowser(); }, + "Ctrl+Shift+B") .Item("Welcome Screen", ICON_MD_HOME, [this]() { OnShowWelcomeScreen(); }); } @@ -225,22 +235,23 @@ void MenuOrchestrator::BuildToolsMenu() { void MenuOrchestrator::AddToolsMenuItems() { // Core Tools - keep these in Tools menu menu_builder_ - .Item("Global Search", ICON_MD_SEARCH, - [this]() { OnShowGlobalSearch(); }, "Ctrl+Shift+F") - .Item("Command Palette", ICON_MD_SEARCH, - [this]() { OnShowCommandPalette(); }, "Ctrl+Shift+P") + .Item( + "Global Search", ICON_MD_SEARCH, [this]() { OnShowGlobalSearch(); }, + "Ctrl+Shift+F") + .Item( + "Command Palette", ICON_MD_SEARCH, + [this]() { OnShowCommandPalette(); }, "Ctrl+Shift+P") .Separator(); - + // Resource Management menu_builder_ .Item("Resource Label Manager", ICON_MD_LABEL, [this]() { OnShowResourceLabelManager(); }) .Separator(); - + // Collaboration (GRPC builds only) #ifdef YAZE_WITH_GRPC - menu_builder_ - .BeginSubMenu("Collaborate", ICON_MD_PEOPLE) + menu_builder_.BeginSubMenu("Collaborate", ICON_MD_PEOPLE) .Item("Start Collaboration Session", ICON_MD_PLAY_CIRCLE, [this]() { OnStartCollaboration(); }) .Item("Join Collaboration Session", ICON_MD_GROUP_ADD, @@ -260,67 +271,60 @@ void MenuOrchestrator::BuildDebugMenu() { void MenuOrchestrator::AddDebugMenuItems() { // Testing section (move from Tools if present) #ifdef YAZE_ENABLE_TESTING - menu_builder_ - .BeginSubMenu("Testing", ICON_MD_SCIENCE) - .Item("Test Dashboard", ICON_MD_DASHBOARD, - [this]() { OnShowTestDashboard(); }, "Ctrl+T") - .Item("Run All Tests", ICON_MD_PLAY_ARROW, - [this]() { OnRunAllTests(); }) - .Item("Run Unit Tests", ICON_MD_CHECK_BOX, - [this]() { OnRunUnitTests(); }) + menu_builder_.BeginSubMenu("Testing", ICON_MD_SCIENCE) + .Item( + "Test Dashboard", ICON_MD_DASHBOARD, + [this]() { OnShowTestDashboard(); }, "Ctrl+T") + .Item("Run All Tests", ICON_MD_PLAY_ARROW, [this]() { OnRunAllTests(); }) + .Item("Run Unit Tests", ICON_MD_CHECK_BOX, [this]() { OnRunUnitTests(); }) .Item("Run Integration Tests", ICON_MD_INTEGRATION_INSTRUCTIONS, [this]() { OnRunIntegrationTests(); }) - .Item("Run E2E Tests", ICON_MD_VISIBILITY, - [this]() { OnRunE2ETests(); }) + .Item("Run E2E Tests", ICON_MD_VISIBILITY, [this]() { OnRunE2ETests(); }) .EndMenu() .Separator(); #endif - + // ROM Analysis submenu - menu_builder_ - .BeginSubMenu("ROM Analysis", ICON_MD_STORAGE) - .Item("ROM Information", ICON_MD_INFO, - [this]() { OnShowRomInfo(); }, nullptr, - [this]() { return HasActiveRom(); }) - .Item("Data Integrity Check", ICON_MD_ANALYTICS, - [this]() { OnRunDataIntegrityCheck(); }, nullptr, - [this]() { return HasActiveRom(); }) - .Item("Test Save/Load", ICON_MD_SAVE_ALT, - [this]() { OnTestSaveLoad(); }, nullptr, - [this]() { return HasActiveRom(); }) + menu_builder_.BeginSubMenu("ROM Analysis", ICON_MD_STORAGE) + .Item( + "ROM Information", ICON_MD_INFO, [this]() { OnShowRomInfo(); }, + nullptr, [this]() { return HasActiveRom(); }) + .Item( + "Data Integrity Check", ICON_MD_ANALYTICS, + [this]() { OnRunDataIntegrityCheck(); }, nullptr, + [this]() { return HasActiveRom(); }) + .Item( + "Test Save/Load", ICON_MD_SAVE_ALT, [this]() { OnTestSaveLoad(); }, + nullptr, [this]() { return HasActiveRom(); }) .EndMenu(); - + // ZSCustomOverworld submenu - menu_builder_ - .BeginSubMenu("ZSCustomOverworld", ICON_MD_CODE) - .Item("Check ROM Version", ICON_MD_INFO, - [this]() { OnCheckRomVersion(); }, nullptr, - [this]() { return HasActiveRom(); }) - .Item("Upgrade ROM", ICON_MD_UPGRADE, - [this]() { OnUpgradeRom(); }, nullptr, - [this]() { return HasActiveRom(); }) + menu_builder_.BeginSubMenu("ZSCustomOverworld", ICON_MD_CODE) + .Item( + "Check ROM Version", ICON_MD_INFO, [this]() { OnCheckRomVersion(); }, + nullptr, [this]() { return HasActiveRom(); }) + .Item( + "Upgrade ROM", ICON_MD_UPGRADE, [this]() { OnUpgradeRom(); }, nullptr, + [this]() { return HasActiveRom(); }) .Item("Toggle Custom Loading", ICON_MD_SETTINGS, [this]() { OnToggleCustomLoading(); }) .EndMenu(); - + // Asar Integration submenu - menu_builder_ - .BeginSubMenu("Asar Integration", ICON_MD_BUILD) + menu_builder_.BeginSubMenu("Asar Integration", ICON_MD_BUILD) .Item("Asar Status", ICON_MD_INFO, [this]() { popup_manager_.Show(PopupID::kAsarIntegration); }) - .Item("Toggle ASM Patch", ICON_MD_CODE, - [this]() { OnToggleAsarPatch(); }, nullptr, - [this]() { return HasActiveRom(); }) - .Item("Load ASM File", ICON_MD_FOLDER_OPEN, - [this]() { OnLoadAsmFile(); }) + .Item( + "Toggle ASM Patch", ICON_MD_CODE, [this]() { OnToggleAsarPatch(); }, + nullptr, [this]() { return HasActiveRom(); }) + .Item("Load ASM File", ICON_MD_FOLDER_OPEN, [this]() { OnLoadAsmFile(); }) .EndMenu(); - + menu_builder_.Separator(); - + // Development Tools menu_builder_ - .Item("Memory Editor", ICON_MD_MEMORY, - [this]() { OnShowMemoryEditor(); }) + .Item("Memory Editor", ICON_MD_MEMORY, [this]() { OnShowMemoryEditor(); }) .Item("Assembly Editor", ICON_MD_CODE, [this]() { OnShowAssemblyEditor(); }) .Item("Feature Flags", ICON_MD_FLAG, @@ -328,19 +332,17 @@ void MenuOrchestrator::AddDebugMenuItems() { .Separator() .Item("Performance Dashboard", ICON_MD_SPEED, [this]() { OnShowPerformanceDashboard(); }); - + #ifdef YAZE_WITH_GRPC - menu_builder_ - .Item("Agent Proposals", ICON_MD_PREVIEW, - [this]() { OnShowProposalDrawer(); }); + menu_builder_.Item("Agent Proposals", ICON_MD_PREVIEW, + [this]() { OnShowProposalDrawer(); }); #endif - + menu_builder_.Separator(); - + // ImGui Debug Windows menu_builder_ - .Item("ImGui Demo", ICON_MD_HELP, - [this]() { OnShowImGuiDemo(); }) + .Item("ImGui Demo", ICON_MD_HELP, [this]() { OnShowImGuiDemo(); }) .Item("ImGui Metrics", ICON_MD_ANALYTICS, [this]() { OnShowImGuiMetrics(); }); } @@ -353,37 +355,41 @@ void MenuOrchestrator::BuildWindowMenu() { void MenuOrchestrator::AddWindowMenuItems() { // Sessions Submenu - menu_builder_ - .BeginSubMenu("Sessions", ICON_MD_TAB) - .Item("New Session", ICON_MD_ADD, - [this]() { OnCreateNewSession(); }, "Ctrl+Shift+N") - .Item("Duplicate Session", ICON_MD_CONTENT_COPY, - [this]() { OnDuplicateCurrentSession(); }, nullptr, - [this]() { return HasActiveRom(); }) - .Item("Close Session", ICON_MD_CLOSE, - [this]() { OnCloseCurrentSession(); }, "Ctrl+Shift+W", - [this]() { return HasMultipleSessions(); }) + menu_builder_.BeginSubMenu("Sessions", ICON_MD_TAB) + .Item( + "New Session", ICON_MD_ADD, [this]() { OnCreateNewSession(); }, + "Ctrl+Shift+N") + .Item( + "Duplicate Session", ICON_MD_CONTENT_COPY, + [this]() { OnDuplicateCurrentSession(); }, nullptr, + [this]() { return HasActiveRom(); }) + .Item( + "Close Session", ICON_MD_CLOSE, [this]() { OnCloseCurrentSession(); }, + "Ctrl+Shift+W", [this]() { return HasMultipleSessions(); }) .Separator() - .Item("Session Switcher", ICON_MD_SWITCH_ACCOUNT, - [this]() { OnShowSessionSwitcher(); }, "Ctrl+Tab", - [this]() { return HasMultipleSessions(); }) + .Item( + "Session Switcher", ICON_MD_SWITCH_ACCOUNT, + [this]() { OnShowSessionSwitcher(); }, "Ctrl+Tab", + [this]() { return HasMultipleSessions(); }) .Item("Session Manager", ICON_MD_VIEW_LIST, [this]() { OnShowSessionManager(); }) .EndMenu() .Separator(); - + // Layout Management menu_builder_ - .Item("Save Layout", ICON_MD_SAVE, - [this]() { OnSaveWorkspaceLayout(); }, "Ctrl+Shift+S") - .Item("Load Layout", ICON_MD_FOLDER_OPEN, - [this]() { OnLoadWorkspaceLayout(); }, "Ctrl+Shift+O") + .Item( + "Save Layout", ICON_MD_SAVE, [this]() { OnSaveWorkspaceLayout(); }, + "Ctrl+Shift+S") + .Item( + "Load Layout", ICON_MD_FOLDER_OPEN, + [this]() { OnLoadWorkspaceLayout(); }, "Ctrl+Shift+O") .Item("Reset Layout", ICON_MD_RESET_TV, [this]() { OnResetWorkspaceLayout(); }) .Item("Layout Presets", ICON_MD_BOOKMARK, [this]() { OnShowLayoutPresets(); }) .Separator(); - + // Window Visibility menu_builder_ .Item("Show All Windows", ICON_MD_VISIBILITY, @@ -391,7 +397,7 @@ void MenuOrchestrator::AddWindowMenuItems() { .Item("Hide All Windows", ICON_MD_VISIBILITY_OFF, [this]() { OnHideAllWindows(); }) .Separator(); - + // Workspace Presets menu_builder_ .Item("Developer Layout", ICON_MD_DEVELOPER_MODE, @@ -416,21 +422,18 @@ void MenuOrchestrator::AddHelpMenuItems() { [this]() { OnShowAsarIntegration(); }) .Item("Build Instructions", ICON_MD_BUILD, [this]() { OnShowBuildInstructions(); }) - .Item("CLI Usage", ICON_MD_TERMINAL, - [this]() { OnShowCLIUsage(); }) + .Item("CLI Usage", ICON_MD_TERMINAL, [this]() { OnShowCLIUsage(); }) .Separator() .Item("Supported Features", ICON_MD_CHECK_CIRCLE, [this]() { OnShowSupportedFeatures(); }) - .Item("What's New", ICON_MD_NEW_RELEASES, - [this]() { OnShowWhatsNew(); }) + .Item("What's New", ICON_MD_NEW_RELEASES, [this]() { OnShowWhatsNew(); }) .Separator() .Item("Troubleshooting", ICON_MD_BUILD_CIRCLE, [this]() { OnShowTroubleshooting(); }) .Item("Contributing", ICON_MD_VOLUNTEER_ACTIVISM, [this]() { OnShowContributing(); }) .Separator() - .Item("About", ICON_MD_INFO, - [this]() { OnShowAbout(); }, "F1"); + .Item("About", ICON_MD_INFO, [this]() { OnShowAbout(); }, "F1"); } // Menu state management @@ -530,8 +533,9 @@ void MenuOrchestrator::OnUndo() { if (current_editor) { auto status = current_editor->Undo(); if (!status.ok()) { - toast_manager_.Show(absl::StrFormat("Undo failed: %s", status.message()), - ToastType::kError); + toast_manager_.Show( + absl::StrFormat("Undo failed: %s", status.message()), + ToastType::kError); } } } @@ -543,8 +547,9 @@ void MenuOrchestrator::OnRedo() { if (current_editor) { auto status = current_editor->Redo(); if (!status.ok()) { - toast_manager_.Show(absl::StrFormat("Redo failed: %s", status.message()), - ToastType::kError); + toast_manager_.Show( + absl::StrFormat("Redo failed: %s", status.message()), + ToastType::kError); } } } @@ -557,7 +562,7 @@ void MenuOrchestrator::OnCut() { auto status = current_editor->Cut(); if (!status.ok()) { toast_manager_.Show(absl::StrFormat("Cut failed: %s", status.message()), - ToastType::kError); + ToastType::kError); } } } @@ -569,8 +574,9 @@ void MenuOrchestrator::OnCopy() { if (current_editor) { auto status = current_editor->Copy(); if (!status.ok()) { - toast_manager_.Show(absl::StrFormat("Copy failed: %s", status.message()), - ToastType::kError); + toast_manager_.Show( + absl::StrFormat("Copy failed: %s", status.message()), + ToastType::kError); } } } @@ -582,8 +588,9 @@ void MenuOrchestrator::OnPaste() { if (current_editor) { auto status = current_editor->Paste(); if (!status.ok()) { - toast_manager_.Show(absl::StrFormat("Paste failed: %s", status.message()), - ToastType::kError); + toast_manager_.Show( + absl::StrFormat("Paste failed: %s", status.message()), + ToastType::kError); } } } @@ -595,8 +602,9 @@ void MenuOrchestrator::OnFind() { if (current_editor) { auto status = current_editor->Find(); if (!status.ok()) { - toast_manager_.Show(absl::StrFormat("Find failed: %s", status.message()), - ToastType::kError); + toast_manager_.Show( + absl::StrFormat("Find failed: %s", status.message()), + ToastType::kError); } } } @@ -977,7 +985,8 @@ std::string MenuOrchestrator::GetCurrentEditorName() const { } // Shortcut key management -std::string MenuOrchestrator::GetShortcutForAction(const std::string& action) const { +std::string MenuOrchestrator::GetShortcutForAction( + const std::string& action) const { // TODO: Implement shortcut mapping return ""; } @@ -992,14 +1001,17 @@ void MenuOrchestrator::RegisterGlobalShortcuts() { void MenuOrchestrator::OnRunDataIntegrityCheck() { #ifdef YAZE_ENABLE_TESTING - if (!editor_manager_) return; + if (!editor_manager_) + return; auto* rom = editor_manager_->GetCurrentRom(); - if (!rom || !rom->is_loaded()) return; - + if (!rom || !rom->is_loaded()) + return; + toast_manager_.Show("Running ROM integrity tests...", ToastType::kInfo); // This would integrate with the test system in master // For now, just show a placeholder - toast_manager_.Show("Data integrity check completed", ToastType::kSuccess, 3.0f); + toast_manager_.Show("Data integrity check completed", ToastType::kSuccess, + 3.0f); #else toast_manager_.Show("Testing not enabled in this build", ToastType::kWarning); #endif @@ -1007,10 +1019,12 @@ void MenuOrchestrator::OnRunDataIntegrityCheck() { void MenuOrchestrator::OnTestSaveLoad() { #ifdef YAZE_ENABLE_TESTING - if (!editor_manager_) return; + if (!editor_manager_) + return; auto* rom = editor_manager_->GetCurrentRom(); - if (!rom || !rom->is_loaded()) return; - + if (!rom || !rom->is_loaded()) + return; + toast_manager_.Show("Running ROM save/load tests...", ToastType::kInfo); // This would integrate with the test system in master toast_manager_.Show("Save/load test completed", ToastType::kSuccess, 3.0f); @@ -1020,58 +1034,66 @@ void MenuOrchestrator::OnTestSaveLoad() { } void MenuOrchestrator::OnCheckRomVersion() { - if (!editor_manager_) return; + if (!editor_manager_) + return; auto* rom = editor_manager_->GetCurrentRom(); - if (!rom || !rom->is_loaded()) return; - + if (!rom || !rom->is_loaded()) + return; + // Check ZSCustomOverworld version uint8_t version = (*rom)[zelda3::OverworldCustomASMHasBeenApplied]; - std::string version_str = (version == 0xFF) - ? "Vanilla" - : absl::StrFormat("v%d", version); - + std::string version_str = + (version == 0xFF) ? "Vanilla" : absl::StrFormat("v%d", version); + toast_manager_.Show( - absl::StrFormat("ROM: %s | ZSCustomOverworld: %s", - rom->title().c_str(), version_str.c_str()), + absl::StrFormat("ROM: %s | ZSCustomOverworld: %s", rom->title().c_str(), + version_str.c_str()), ToastType::kInfo, 5.0f); } void MenuOrchestrator::OnUpgradeRom() { - if (!editor_manager_) return; + if (!editor_manager_) + return; auto* rom = editor_manager_->GetCurrentRom(); - if (!rom || !rom->is_loaded()) return; - - toast_manager_.Show( - "Use Overworld Editor to upgrade ROM version", - ToastType::kInfo, 4.0f); + if (!rom || !rom->is_loaded()) + return; + + toast_manager_.Show("Use Overworld Editor to upgrade ROM version", + ToastType::kInfo, 4.0f); } void MenuOrchestrator::OnToggleCustomLoading() { auto& flags = core::FeatureFlags::get(); flags.overworld.kLoadCustomOverworld = !flags.overworld.kLoadCustomOverworld; - + toast_manager_.Show( - absl::StrFormat("Custom Overworld Loading: %s", - flags.overworld.kLoadCustomOverworld ? "Enabled" : "Disabled"), + absl::StrFormat( + "Custom Overworld Loading: %s", + flags.overworld.kLoadCustomOverworld ? "Enabled" : "Disabled"), ToastType::kInfo); } void MenuOrchestrator::OnToggleAsarPatch() { - if (!editor_manager_) return; + if (!editor_manager_) + return; auto* rom = editor_manager_->GetCurrentRom(); - if (!rom || !rom->is_loaded()) return; - + if (!rom || !rom->is_loaded()) + return; + auto& flags = core::FeatureFlags::get(); - flags.overworld.kApplyZSCustomOverworldASM = !flags.overworld.kApplyZSCustomOverworldASM; - + flags.overworld.kApplyZSCustomOverworldASM = + !flags.overworld.kApplyZSCustomOverworldASM; + toast_manager_.Show( - absl::StrFormat("ZSCustomOverworld ASM Application: %s", - flags.overworld.kApplyZSCustomOverworldASM ? "Enabled" : "Disabled"), + absl::StrFormat( + "ZSCustomOverworld ASM Application: %s", + flags.overworld.kApplyZSCustomOverworldASM ? "Enabled" : "Disabled"), ToastType::kInfo); } void MenuOrchestrator::OnLoadAsmFile() { - toast_manager_.Show("ASM file loading not yet implemented", ToastType::kWarning); + toast_manager_.Show("ASM file loading not yet implemented", + ToastType::kWarning); } void MenuOrchestrator::OnShowAssemblyEditor() { diff --git a/src/app/editor/system/menu_orchestrator.h b/src/app/editor/system/menu_orchestrator.h index 497dbad8..9f2f5a49 100644 --- a/src/app/editor/system/menu_orchestrator.h +++ b/src/app/editor/system/menu_orchestrator.h @@ -39,16 +39,13 @@ class PopupManager; class MenuOrchestrator { public: // Constructor takes references to the managers it coordinates with - MenuOrchestrator(EditorManager* editor_manager, - MenuBuilder& menu_builder, - RomFileManager& rom_manager, - ProjectManager& project_manager, + MenuOrchestrator(EditorManager* editor_manager, MenuBuilder& menu_builder, + RomFileManager& rom_manager, ProjectManager& project_manager, EditorRegistry& editor_registry, SessionCoordinator& session_coordinator, - ToastManager& toast_manager, - PopupManager& popup_manager); + ToastManager& toast_manager, PopupManager& popup_manager); ~MenuOrchestrator() = default; - + // Non-copyable due to reference members MenuOrchestrator(const MenuOrchestrator&) = delete; MenuOrchestrator& operator=(const MenuOrchestrator&) = delete; @@ -66,7 +63,7 @@ class MenuOrchestrator { // Menu state management void ClearMenu(); void RefreshMenu(); - + // Menu item callbacks (delegated to appropriate managers) void OnOpenRom(); void OnSaveRom(); @@ -75,7 +72,7 @@ class MenuOrchestrator { void OnOpenProject(); void OnSaveProject(); void OnSaveProjectAs(); - + // Edit menu actions (delegate to current editor) void OnUndo(); void OnRedo(); @@ -83,7 +80,7 @@ class MenuOrchestrator { void OnCopy(); void OnPaste(); void OnFind(); - + // Editor-specific menu actions void OnSwitchToEditor(EditorType editor_type); void OnShowEditorSelection(); @@ -92,13 +89,13 @@ class MenuOrchestrator { void OnShowEmulator(); void OnShowCardBrowser(); void OnShowWelcomeScreen(); - + #ifdef YAZE_WITH_GRPC void OnShowAIAgent(); void OnShowChatHistory(); void OnShowProposalDrawer(); #endif - + // Session management menu actions void OnCreateNewSession(); void OnDuplicateCurrentSession(); @@ -106,7 +103,7 @@ class MenuOrchestrator { void OnSwitchToSession(size_t session_index); void OnShowSessionSwitcher(); void OnShowSessionManager(); - + // Window management menu actions void OnShowAllWindows(); void OnHideAllWindows(); @@ -117,7 +114,7 @@ class MenuOrchestrator { void OnLoadDeveloperLayout(); void OnLoadDesignerLayout(); void OnLoadModderLayout(); - + // Tool menu actions void OnShowGlobalSearch(); void OnShowCommandPalette(); @@ -126,26 +123,26 @@ class MenuOrchestrator { void OnShowImGuiMetrics(); void OnShowMemoryEditor(); void OnShowResourceLabelManager(); - + // ROM Analysis menu actions void OnShowRomInfo(); void OnCreateBackup(); void OnValidateRom(); void OnRunDataIntegrityCheck(); void OnTestSaveLoad(); - + // ZSCustomOverworld menu actions void OnCheckRomVersion(); void OnUpgradeRom(); void OnToggleCustomLoading(); - + // Asar Integration menu actions void OnToggleAsarPatch(); void OnLoadAsmFile(); - + // Editor launch actions void OnShowAssemblyEditor(); - + #ifdef YAZE_ENABLE_TESTING void OnShowTestDashboard(); void OnRunAllTests(); @@ -153,13 +150,13 @@ class MenuOrchestrator { void OnRunIntegrationTests(); void OnRunE2ETests(); #endif - + #ifdef YAZE_WITH_GRPC void OnStartCollaboration(); void OnJoinCollaboration(); void OnShowNetworkStatus(); #endif - + // Help menu actions void OnShowAbout(); void OnShowKeyboardShortcuts(); @@ -172,7 +169,7 @@ class MenuOrchestrator { void OnShowContributing(); void OnShowWhatsNew(); void OnShowSupportedFeatures(); - + // Additional File menu actions void OnShowSettings(); void OnQuit(); @@ -187,10 +184,10 @@ class MenuOrchestrator { SessionCoordinator& session_coordinator_; ToastManager& toast_manager_; PopupManager& popup_manager_; - + // Menu state bool menu_needs_refresh_ = false; - + // Helper methods for menu construction void AddFileMenuItems(); void AddEditMenuItems(); @@ -199,7 +196,7 @@ class MenuOrchestrator { void AddDebugMenuItems(); void AddWindowMenuItems(); void AddHelpMenuItems(); - + // Menu item validation helpers bool CanSaveRom() const; bool CanSaveProject() const; @@ -207,12 +204,12 @@ class MenuOrchestrator { bool HasActiveProject() const; bool HasCurrentEditor() const; bool HasMultipleSessions() const; - + // Menu item text generation std::string GetRomFilename() const; std::string GetProjectName() const; std::string GetCurrentEditorName() const; - + // Shortcut key management std::string GetShortcutForAction(const std::string& action) const; void RegisterGlobalShortcuts(); diff --git a/src/app/editor/system/popup_manager.cc b/src/app/editor/system/popup_manager.cc index 0737388b..b1133ad9 100644 --- a/src/app/editor/system/popup_manager.cc +++ b/src/app/editor/system/popup_manager.cc @@ -3,10 +3,10 @@ #include "absl/strings/str_format.h" #include "app/editor/editor_manager.h" #include "app/gui/app/feature_flags_menu.h" -#include "app/gui/core/style.h" #include "app/gui/core/icons.h" -#include "util/hex.h" +#include "app/gui/core/style.h" #include "imgui/misc/cpp/imgui_stdlib.h" +#include "util/hex.h" namespace yaze { namespace editor { @@ -33,113 +33,121 @@ void PopupManager::Initialize() { // .draw_function = [this]() { DrawXxxPopup(); } // }; // ============================================================================ - + // File Operations - popups_[PopupID::kSaveAs] = { - PopupID::kSaveAs, PopupType::kFileOperation, false, false, - [this]() { DrawSaveAsPopup(); } - }; + popups_[PopupID::kSaveAs] = {PopupID::kSaveAs, PopupType::kFileOperation, + false, false, [this]() { + DrawSaveAsPopup(); + }}; popups_[PopupID::kNewProject] = { - PopupID::kNewProject, PopupType::kFileOperation, false, false, - [this]() { DrawNewProjectPopup(); } - }; - popups_[PopupID::kManageProject] = { - PopupID::kManageProject, PopupType::kFileOperation, false, false, - [this]() { DrawManageProjectPopup(); } - }; - + PopupID::kNewProject, PopupType::kFileOperation, false, false, [this]() { + DrawNewProjectPopup(); + }}; + popups_[PopupID::kManageProject] = {PopupID::kManageProject, + PopupType::kFileOperation, false, false, + [this]() { + DrawManageProjectPopup(); + }}; + // Information - popups_[PopupID::kAbout] = { - PopupID::kAbout, PopupType::kInfo, false, false, - [this]() { DrawAboutPopup(); } - }; - popups_[PopupID::kRomInfo] = { - PopupID::kRomInfo, PopupType::kInfo, false, false, - [this]() { DrawRomInfoPopup(); } - }; + popups_[PopupID::kAbout] = {PopupID::kAbout, PopupType::kInfo, false, false, + [this]() { + DrawAboutPopup(); + }}; + popups_[PopupID::kRomInfo] = {PopupID::kRomInfo, PopupType::kInfo, false, + false, [this]() { + DrawRomInfoPopup(); + }}; popups_[PopupID::kSupportedFeatures] = { - PopupID::kSupportedFeatures, PopupType::kInfo, false, false, - [this]() { DrawSupportedFeaturesPopup(); } - }; - popups_[PopupID::kOpenRomHelp] = { - PopupID::kOpenRomHelp, PopupType::kHelp, false, false, - [this]() { DrawOpenRomHelpPopup(); } - }; - + PopupID::kSupportedFeatures, PopupType::kInfo, false, false, [this]() { + DrawSupportedFeaturesPopup(); + }}; + popups_[PopupID::kOpenRomHelp] = {PopupID::kOpenRomHelp, PopupType::kHelp, + false, false, [this]() { + DrawOpenRomHelpPopup(); + }}; + // Help Documentation popups_[PopupID::kGettingStarted] = { - PopupID::kGettingStarted, PopupType::kHelp, false, false, - [this]() { DrawGettingStartedPopup(); } - }; + PopupID::kGettingStarted, PopupType::kHelp, false, false, [this]() { + DrawGettingStartedPopup(); + }}; popups_[PopupID::kAsarIntegration] = { - PopupID::kAsarIntegration, PopupType::kHelp, false, false, - [this]() { DrawAsarIntegrationPopup(); } - }; + PopupID::kAsarIntegration, PopupType::kHelp, false, false, [this]() { + DrawAsarIntegrationPopup(); + }}; popups_[PopupID::kBuildInstructions] = { - PopupID::kBuildInstructions, PopupType::kHelp, false, false, - [this]() { DrawBuildInstructionsPopup(); } - }; - popups_[PopupID::kCLIUsage] = { - PopupID::kCLIUsage, PopupType::kHelp, false, false, - [this]() { DrawCLIUsagePopup(); } - }; + PopupID::kBuildInstructions, PopupType::kHelp, false, false, [this]() { + DrawBuildInstructionsPopup(); + }}; + popups_[PopupID::kCLIUsage] = {PopupID::kCLIUsage, PopupType::kHelp, false, + false, [this]() { + DrawCLIUsagePopup(); + }}; popups_[PopupID::kTroubleshooting] = { - PopupID::kTroubleshooting, PopupType::kHelp, false, false, - [this]() { DrawTroubleshootingPopup(); } - }; - popups_[PopupID::kContributing] = { - PopupID::kContributing, PopupType::kHelp, false, false, - [this]() { DrawContributingPopup(); } - }; - popups_[PopupID::kWhatsNew] = { - PopupID::kWhatsNew, PopupType::kHelp, false, false, - [this]() { DrawWhatsNewPopup(); } - }; - + PopupID::kTroubleshooting, PopupType::kHelp, false, false, [this]() { + DrawTroubleshootingPopup(); + }}; + popups_[PopupID::kContributing] = {PopupID::kContributing, PopupType::kHelp, + false, false, [this]() { + DrawContributingPopup(); + }}; + popups_[PopupID::kWhatsNew] = {PopupID::kWhatsNew, PopupType::kHelp, false, + false, [this]() { + DrawWhatsNewPopup(); + }}; + // Settings - popups_[PopupID::kDisplaySettings] = { - PopupID::kDisplaySettings, PopupType::kSettings, false, true, // Resizable - [this]() { DrawDisplaySettingsPopup(); } - }; + popups_[PopupID::kDisplaySettings] = {PopupID::kDisplaySettings, + PopupType::kSettings, false, + true, // Resizable + [this]() { + DrawDisplaySettingsPopup(); + }}; popups_[PopupID::kFeatureFlags] = { - PopupID::kFeatureFlags, PopupType::kSettings, false, true, // Resizable - [this]() { DrawFeatureFlagsPopup(); } - }; - + PopupID::kFeatureFlags, PopupType::kSettings, false, true, // Resizable + [this]() { + DrawFeatureFlagsPopup(); + }}; + // Workspace - popups_[PopupID::kWorkspaceHelp] = { - PopupID::kWorkspaceHelp, PopupType::kHelp, false, false, - [this]() { DrawWorkspaceHelpPopup(); } - }; - popups_[PopupID::kSessionLimitWarning] = { - PopupID::kSessionLimitWarning, PopupType::kWarning, false, false, - [this]() { DrawSessionLimitWarningPopup(); } - }; - popups_[PopupID::kLayoutResetConfirm] = { - PopupID::kLayoutResetConfirm, PopupType::kConfirmation, false, false, - [this]() { DrawLayoutResetConfirmPopup(); } - }; - + popups_[PopupID::kWorkspaceHelp] = {PopupID::kWorkspaceHelp, PopupType::kHelp, + false, false, [this]() { + DrawWorkspaceHelpPopup(); + }}; + popups_[PopupID::kSessionLimitWarning] = {PopupID::kSessionLimitWarning, + PopupType::kWarning, false, false, + [this]() { + DrawSessionLimitWarningPopup(); + }}; + popups_[PopupID::kLayoutResetConfirm] = {PopupID::kLayoutResetConfirm, + PopupType::kConfirmation, false, + false, [this]() { + DrawLayoutResetConfirmPopup(); + }}; + // Debug/Testing - popups_[PopupID::kDataIntegrity] = { - PopupID::kDataIntegrity, PopupType::kInfo, false, true, // Resizable - [this]() { DrawDataIntegrityPopup(); } - }; + popups_[PopupID::kDataIntegrity] = {PopupID::kDataIntegrity, PopupType::kInfo, + false, true, // Resizable + [this]() { + DrawDataIntegrityPopup(); + }}; } void PopupManager::DrawPopups() { // Draw status popup if needed DrawStatusPopup(); - + // Draw all registered popups for (auto& [name, params] : popups_) { if (params.is_visible) { OpenPopup(name.c_str()); - + // Use allow_resize flag from popup definition - ImGuiWindowFlags popup_flags = params.allow_resize ? - ImGuiWindowFlags_None : ImGuiWindowFlags_AlwaysAutoResize; - + ImGuiWindowFlags popup_flags = params.allow_resize + ? ImGuiWindowFlags_None + : ImGuiWindowFlags_AlwaysAutoResize; + if (BeginPopupModal(name.c_str(), nullptr, popup_flags)) { params.draw_function(); EndPopup(); @@ -152,14 +160,16 @@ void PopupManager::Show(const char* name) { if (!name) { return; // Safety check for null pointer } - + std::string name_str(name); auto it = popups_.find(name_str); if (it != popups_.end()) { it->second.is_visible = true; } else { // Log warning for unregistered popup - printf("[PopupManager] Warning: Popup '%s' not registered. Available popups: ", name); + printf( + "[PopupManager] Warning: Popup '%s' not registered. Available popups: ", + name); for (const auto& [key, _] : popups_) { printf("'%s' ", key.c_str()); } @@ -171,7 +181,7 @@ void PopupManager::Hide(const char* name) { if (!name) { return; // Safety check for null pointer } - + std::string name_str(name); auto it = popups_.find(name_str); if (it != popups_.end()) { @@ -184,7 +194,7 @@ bool PopupManager::IsVisible(const char* name) const { if (!name) { return false; // Safety check for null pointer } - + std::string name_str(name); auto it = popups_.find(name_str); if (it != popups_.end()) { @@ -247,38 +257,41 @@ void PopupManager::DrawAboutPopup() { void PopupManager::DrawRomInfoPopup() { auto* current_rom = editor_manager_->GetCurrentRom(); - if (!current_rom) return; - + if (!current_rom) + return; + Text("Title: %s", current_rom->title().c_str()); Text("ROM Size: %s", util::HexLongLong(current_rom->size()).c_str()); - if (Button("Close", gui::kDefaultModalSize) || IsKeyPressed(ImGuiKey_Escape)) { + if (Button("Close", gui::kDefaultModalSize) || + IsKeyPressed(ImGuiKey_Escape)) { Hide("ROM Information"); } } void PopupManager::DrawSaveAsPopup() { using namespace ImGui; - + Text("%s Save ROM to new location", ICON_MD_SAVE_AS); Separator(); - + static std::string save_as_filename = ""; if (editor_manager_->GetCurrentRom() && save_as_filename.empty()) { save_as_filename = editor_manager_->GetCurrentRom()->title(); } - + InputText("Filename", &save_as_filename); Separator(); - + if (Button(absl::StrFormat("%s Browse...", ICON_MD_FOLDER_OPEN).c_str(), gui::kDefaultModalSize)) { - auto file_path = util::FileDialogWrapper::ShowSaveFileDialog(save_as_filename, "sfc"); + auto file_path = + util::FileDialogWrapper::ShowSaveFileDialog(save_as_filename, "sfc"); if (!file_path.empty()) { save_as_filename = file_path; } } - + SameLine(); if (Button(absl::StrFormat("%s Save", ICON_MD_SAVE).c_str(), gui::kDefaultModalSize)) { @@ -289,7 +302,7 @@ void PopupManager::DrawSaveAsPopup() { final_filename.find(".smc") == std::string::npos) { final_filename += ".sfc"; } - + auto status = editor_manager_->SaveRomAs(final_filename); if (status.ok()) { save_as_filename = ""; @@ -297,7 +310,7 @@ void PopupManager::DrawSaveAsPopup() { } } } - + SameLine(); if (Button(absl::StrFormat("%s Cancel", ICON_MD_CANCEL).c_str(), gui::kDefaultModalSize)) { @@ -308,36 +321,36 @@ void PopupManager::DrawSaveAsPopup() { void PopupManager::DrawNewProjectPopup() { using namespace ImGui; - + static std::string project_name = ""; static std::string project_filepath = ""; static std::string rom_filename = ""; static std::string labels_filename = ""; static std::string code_folder = ""; - + InputText("Project Name", &project_name); - + if (Button(absl::StrFormat("%s Destination Folder", ICON_MD_FOLDER).c_str(), gui::kDefaultModalSize)) { project_filepath = util::FileDialogWrapper::ShowOpenFolderDialog(); } SameLine(); Text("%s", project_filepath.empty() ? "(Not set)" : project_filepath.c_str()); - + if (Button(absl::StrFormat("%s ROM File", ICON_MD_VIDEOGAME_ASSET).c_str(), gui::kDefaultModalSize)) { rom_filename = util::FileDialogWrapper::ShowOpenFileDialog(); } SameLine(); Text("%s", rom_filename.empty() ? "(Not set)" : rom_filename.c_str()); - + if (Button(absl::StrFormat("%s Labels File", ICON_MD_LABEL).c_str(), gui::kDefaultModalSize)) { labels_filename = util::FileDialogWrapper::ShowOpenFileDialog(); } SameLine(); Text("%s", labels_filename.empty() ? "(Not set)" : labels_filename.c_str()); - + if (Button(absl::StrFormat("%s Code Folder", ICON_MD_CODE).c_str(), gui::kDefaultModalSize)) { code_folder = util::FileDialogWrapper::ShowOpenFolderDialog(); @@ -346,10 +359,12 @@ void PopupManager::DrawNewProjectPopup() { Text("%s", code_folder.empty() ? "(Not set)" : code_folder.c_str()); Separator(); - - if (Button(absl::StrFormat("%s Choose Project File Location", ICON_MD_SAVE).c_str(), + + if (Button(absl::StrFormat("%s Choose Project File Location", ICON_MD_SAVE) + .c_str(), gui::kDefaultModalSize)) { - auto project_file_path = util::FileDialogWrapper::ShowSaveFileDialog(project_name, "yaze"); + auto project_file_path = + util::FileDialogWrapper::ShowSaveFileDialog(project_name, "yaze"); if (!project_file_path.empty()) { if (project_file_path.find(".yaze") == std::string::npos) { project_file_path += ".yaze"; @@ -357,7 +372,7 @@ void PopupManager::DrawNewProjectPopup() { project_filepath = project_file_path; } } - + if (Button(absl::StrFormat("%s Create Project", ICON_MD_ADD).c_str(), gui::kDefaultModalSize)) { if (!project_filepath.empty() && !project_name.empty()) { @@ -387,7 +402,9 @@ void PopupManager::DrawNewProjectPopup() { } void PopupManager::DrawSupportedFeaturesPopup() { - if (CollapsingHeader(absl::StrFormat("%s Overworld Editor", ICON_MD_LAYERS).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + if (CollapsingHeader( + absl::StrFormat("%s Overworld Editor", ICON_MD_LAYERS).c_str(), + ImGuiTreeNodeFlags_DefaultOpen)) { BulletText("LW/DW/SW Tilemap Editing"); BulletText("LW/DW/SW Map Properties"); BulletText("Create/Delete/Update Entrances"); @@ -397,35 +414,41 @@ void PopupManager::DrawSupportedFeaturesPopup() { BulletText("Multi-session map editing support"); } - if (CollapsingHeader(absl::StrFormat("%s Dungeon Editor", ICON_MD_CASTLE).c_str())) { + if (CollapsingHeader( + absl::StrFormat("%s Dungeon Editor", ICON_MD_CASTLE).c_str())) { BulletText("View Room Header Properties"); BulletText("View Entrance Properties"); BulletText("Enhanced room navigation"); } - if (CollapsingHeader(absl::StrFormat("%s Graphics & Themes", ICON_MD_PALETTE).c_str())) { + if (CollapsingHeader( + absl::StrFormat("%s Graphics & Themes", ICON_MD_PALETTE).c_str())) { BulletText("View Decompressed Graphics Sheets"); BulletText("View/Update Graphics Groups"); - BulletText("5+ Built-in themes (Classic, Cyberpunk, Sunset, Forest, Midnight)"); + BulletText( + "5+ Built-in themes (Classic, Cyberpunk, Sunset, Forest, Midnight)"); BulletText("Custom theme creation and editing"); BulletText("Theme import/export functionality"); BulletText("Animated background grid effects"); } - if (CollapsingHeader(absl::StrFormat("%s Palettes", ICON_MD_COLOR_LENS).c_str())) { + if (CollapsingHeader( + absl::StrFormat("%s Palettes", ICON_MD_COLOR_LENS).c_str())) { BulletText("View Palette Groups"); BulletText("Enhanced palette editing tools"); BulletText("Color conversion utilities"); } - - if (CollapsingHeader(absl::StrFormat("%s Project Management", ICON_MD_FOLDER).c_str())) { + + if (CollapsingHeader( + absl::StrFormat("%s Project Management", ICON_MD_FOLDER).c_str())) { BulletText("Multi-session workspace support"); BulletText("Enhanced project creation and management"); BulletText("ZScream project format compatibility"); BulletText("Workspace settings and feature flags"); } - - if (CollapsingHeader(absl::StrFormat("%s Development Tools", ICON_MD_BUILD).c_str())) { + + if (CollapsingHeader( + absl::StrFormat("%s Development Tools", ICON_MD_BUILD).c_str())) { BulletText("Asar 65816 assembler integration"); BulletText("Enhanced CLI tools with TUI interface"); BulletText("Memory editor with advanced features"); @@ -433,7 +456,8 @@ void PopupManager::DrawSupportedFeaturesPopup() { BulletText("Assembly validation and symbol extraction"); } - if (CollapsingHeader(absl::StrFormat("%s Save Capabilities", ICON_MD_SAVE).c_str())) { + if (CollapsingHeader( + absl::StrFormat("%s Save Capabilities", ICON_MD_SAVE).c_str())) { BulletText("All Overworld editing features"); BulletText("Hex Editor changes"); BulletText("Theme configurations"); @@ -476,13 +500,15 @@ void PopupManager::DrawManageProjectPopup() { void PopupManager::DrawGettingStartedPopup() { TextWrapped("Welcome to YAZE v0.3!"); - TextWrapped("This software allows you to modify 'The Legend of Zelda: A Link to the Past' (US or JP) ROMs."); + TextWrapped( + "This software allows you to modify 'The Legend of Zelda: A Link to the " + "Past' (US or JP) ROMs."); Spacing(); TextWrapped("General Tips:"); BulletText("Experiment flags determine whether certain features are enabled"); BulletText("Backup files are enabled by default for safety"); BulletText("Use File > Options to configure settings"); - + if (Button("Close", gui::kDefaultModalSize)) { Hide("Getting Started"); } @@ -490,14 +516,15 @@ void PopupManager::DrawGettingStartedPopup() { void PopupManager::DrawAsarIntegrationPopup() { TextWrapped("Asar 65816 Assembly Integration"); - TextWrapped("YAZE v0.3 includes full Asar assembler support for ROM patching."); + TextWrapped( + "YAZE v0.3 includes full Asar assembler support for ROM patching."); Spacing(); TextWrapped("Features:"); BulletText("Cross-platform ROM patching with assembly code"); BulletText("Symbol extraction with addresses and opcodes"); BulletText("Assembly validation with error reporting"); BulletText("Memory-safe operations with automatic ROM size management"); - + if (Button("Close", gui::kDefaultModalSize)) { Hide("Asar Integration"); } @@ -514,7 +541,7 @@ void PopupManager::DrawBuildInstructionsPopup() { TextWrapped("Development:"); BulletText("cmake --preset dev"); BulletText("cmake --build --preset dev"); - + if (Button("Close", gui::kDefaultModalSize)) { Hide("Build Instructions"); } @@ -529,7 +556,7 @@ void PopupManager::DrawCLIUsagePopup() { BulletText("z3ed extract symbols.asm"); BulletText("z3ed validate assembly.asm"); BulletText("z3ed patch file.bps --rom=file.sfc"); - + if (Button("Close", gui::kDefaultModalSize)) { Hide("CLI Usage"); } @@ -543,7 +570,7 @@ void PopupManager::DrawTroubleshootingPopup() { BulletText("Graphics issues: Try disabling experimental features"); BulletText("Performance: Enable hardware acceleration in display settings"); BulletText("Crashes: Check ROM file integrity and available memory"); - + if (Button("Close", gui::kDefaultModalSize)) { Hide("Troubleshooting"); } @@ -559,7 +586,7 @@ void PopupManager::DrawContributingPopup() { BulletText("Follow C++ coding standards"); BulletText("Include tests for new features"); BulletText("Submit pull requests for review"); - + if (Button("Close", gui::kDefaultModalSize)) { Hide("Contributing"); } @@ -568,8 +595,11 @@ void PopupManager::DrawContributingPopup() { void PopupManager::DrawWhatsNewPopup() { TextWrapped("What's New in YAZE v0.3"); Spacing(); - - if (CollapsingHeader(absl::StrFormat("%s User Interface & Theming", ICON_MD_PALETTE).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + + if (CollapsingHeader( + absl::StrFormat("%s User Interface & Theming", ICON_MD_PALETTE) + .c_str(), + ImGuiTreeNodeFlags_DefaultOpen)) { BulletText("Complete theme management system with 5+ built-in themes"); BulletText("Custom theme editor with save-to-file functionality"); BulletText("Animated background grid with breathing effects (optional)"); @@ -577,8 +607,11 @@ void PopupManager::DrawWhatsNewPopup() { BulletText("Multi-session workspace support with docking"); BulletText("Improved editor organization and navigation"); } - - if (CollapsingHeader(absl::StrFormat("%s Development & Build System", ICON_MD_BUILD).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + + if (CollapsingHeader( + absl::StrFormat("%s Development & Build System", ICON_MD_BUILD) + .c_str(), + ImGuiTreeNodeFlags_DefaultOpen)) { BulletText("Asar 65816 assembler integration for ROM patching"); BulletText("Enhanced CLI tools with TUI (Terminal User Interface)"); BulletText("Modernized CMake build system with presets"); @@ -586,8 +619,9 @@ void PopupManager::DrawWhatsNewPopup() { BulletText("Comprehensive testing framework with 46+ core tests"); BulletText("Professional packaging for all platforms (DMG, MSI, DEB)"); } - - if (CollapsingHeader(absl::StrFormat("%s Core Improvements", ICON_MD_SETTINGS).c_str())) { + + if (CollapsingHeader( + absl::StrFormat("%s Core Improvements", ICON_MD_SETTINGS).c_str())) { BulletText("Enhanced project management with YazeProject structure"); BulletText("Improved ROM loading and validation"); BulletText("Better error handling and status reporting"); @@ -595,8 +629,9 @@ void PopupManager::DrawWhatsNewPopup() { BulletText("Enhanced file dialog integration"); BulletText("Improved logging and debugging capabilities"); } - - if (CollapsingHeader(absl::StrFormat("%s Editor Features", ICON_MD_EDIT).c_str())) { + + if (CollapsingHeader( + absl::StrFormat("%s Editor Features", ICON_MD_EDIT).c_str())) { BulletText("Enhanced overworld editing capabilities"); BulletText("Improved graphics sheet viewing and editing"); BulletText("Better palette management and editing"); @@ -604,14 +639,15 @@ void PopupManager::DrawWhatsNewPopup() { BulletText("Improved sprite and item management"); BulletText("Better entrance and exit editing"); } - + Spacing(); - if (Button(absl::StrFormat("%s View Theme Editor", ICON_MD_PALETTE).c_str(), ImVec2(-1, 30))) { + if (Button(absl::StrFormat("%s View Theme Editor", ICON_MD_PALETTE).c_str(), + ImVec2(-1, 30))) { // Close this popup and show theme settings Hide("Whats New v03"); // Could trigger theme editor opening here } - + if (Button("Close", gui::kDefaultModalSize)) { Hide("Whats New v03"); } @@ -619,28 +655,29 @@ void PopupManager::DrawWhatsNewPopup() { void PopupManager::DrawWorkspaceHelpPopup() { TextWrapped("Workspace Management"); - TextWrapped("YAZE supports multiple ROM sessions and flexible workspace layouts."); + TextWrapped( + "YAZE supports multiple ROM sessions and flexible workspace layouts."); Spacing(); - + TextWrapped("Session Management:"); BulletText("Ctrl+Shift+N: Create new session"); BulletText("Ctrl+Shift+W: Close current session"); BulletText("Ctrl+Tab: Quick session switcher"); BulletText("Each session maintains its own ROM and editor state"); - + Spacing(); TextWrapped("Layout Management:"); BulletText("Drag window tabs to dock/undock"); BulletText("Ctrl+Shift+S: Save current layout"); BulletText("Ctrl+Shift+O: Load saved layout"); BulletText("F11: Maximize current window"); - + Spacing(); TextWrapped("Preset Layouts:"); BulletText("Developer: Code, memory, testing tools"); BulletText("Designer: Graphics, palettes, sprites"); BulletText("Modder: All gameplay editing tools"); - + if (Button("Close", gui::kDefaultModalSize)) { Hide("Workspace Help"); } @@ -652,7 +689,7 @@ void PopupManager::DrawSessionLimitWarningPopup() { TextWrapped("Having too many sessions open may impact performance."); Spacing(); TextWrapped("Consider closing unused sessions or saving your work."); - + if (Button("Understood", gui::kDefaultModalSize)) { Hide("Session Limit Warning"); } @@ -664,12 +701,13 @@ void PopupManager::DrawSessionLimitWarningPopup() { } void PopupManager::DrawLayoutResetConfirmPopup() { - TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s Confirm Reset", ICON_MD_WARNING); + TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s Confirm Reset", + ICON_MD_WARNING); TextWrapped("This will reset your current workspace layout to default."); TextWrapped("Any custom window arrangements will be lost."); Spacing(); TextWrapped("Do you want to continue?"); - + if (Button("Reset Layout", gui::kDefaultModalSize)) { Hide("Layout Reset Confirm"); // This would trigger the actual reset @@ -684,24 +722,26 @@ void PopupManager::DrawDisplaySettingsPopup() { // Set a comfortable default size with natural constraints SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver); SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(FLT_MAX, FLT_MAX)); - + Text("%s Display & Theme Settings", ICON_MD_DISPLAY_SETTINGS); TextWrapped("Customize your YAZE experience - accessible anytime!"); Separator(); - + // Create a child window for scrollable content to avoid table conflicts // Use remaining space minus the close button area - float available_height = GetContentRegionAvail().y - 60; // Reserve space for close button - if (BeginChild("DisplaySettingsContent", ImVec2(0, available_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + float available_height = + GetContentRegionAvail().y - 60; // Reserve space for close button + if (BeginChild("DisplaySettingsContent", ImVec2(0, available_height), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar)) { // Use the popup-safe version to avoid table conflicts gui::DrawDisplaySettingsForPopup(); - + Separator(); gui::TextWithSeparators("Font Manager"); gui::DrawFontManager(); - + // Global font scale (moved from the old display settings window) - ImGuiIO &io = GetIO(); + ImGuiIO& io = GetIO(); Separator(); Text("Global Font Scale"); static float font_global_scale = io.FontGlobalScale; @@ -714,7 +754,7 @@ void PopupManager::DrawDisplaySettingsPopup() { } } EndChild(); - + Separator(); if (Button("Close", gui::kDefaultModalSize)) { Hide("Display Settings"); @@ -723,16 +763,16 @@ void PopupManager::DrawDisplaySettingsPopup() { void PopupManager::DrawFeatureFlagsPopup() { using namespace ImGui; - + // Display feature flags editor using the existing FlagsMenu system Text("Feature Flags Configuration"); Separator(); - + BeginChild("##FlagsContent", ImVec2(0, -30), true); - + // Use the feature flags menu system static gui::FlagsMenu flags_menu; - + if (BeginTabBar("FlagCategories")) { if (BeginTabItem("Overworld")) { flags_menu.DrawOverworldFlags(); @@ -752,9 +792,9 @@ void PopupManager::DrawFeatureFlagsPopup() { } EndTabBar(); } - + EndChild(); - + Separator(); if (Button("Close", gui::kDefaultModalSize)) { Hide(PopupID::kFeatureFlags); @@ -763,12 +803,12 @@ void PopupManager::DrawFeatureFlagsPopup() { void PopupManager::DrawDataIntegrityPopup() { using namespace ImGui; - + Text("Data Integrity Check Results"); Separator(); - + BeginChild("##IntegrityContent", ImVec2(0, -30), true); - + // Placeholder for data integrity results // In a full implementation, this would show test results Text("ROM Data Integrity:"); @@ -777,12 +817,12 @@ void PopupManager::DrawDataIntegrityPopup() { TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "✓ Checksum valid"); TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "✓ Graphics data intact"); TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "✓ Map data intact"); - + Spacing(); Text("No issues detected."); - + EndChild(); - + Separator(); if (Button("Close", gui::kDefaultModalSize)) { Hide(PopupID::kDataIntegrity); diff --git a/src/app/editor/system/popup_manager.h b/src/app/editor/system/popup_manager.h index 9de38df3..2dc8d29d 100644 --- a/src/app/editor/system/popup_manager.h +++ b/src/app/editor/system/popup_manager.h @@ -32,10 +32,10 @@ enum class PopupType { * @brief Complete definition of a popup including metadata */ struct PopupDefinition { - const char* id; // Unique constant identifier - const char* display_name; // Human-readable name for UI - PopupType type; // Type classification - bool allow_resize; // Whether popup can be resized + const char* id; // Unique constant identifier + const char* display_name; // Human-readable name for UI + PopupType type; // Type classification + bool allow_resize; // Whether popup can be resized std::function draw_function; // Drawing callback (set at runtime) }; @@ -56,44 +56,44 @@ struct PopupParams { * @brief String constants for all popup identifiers to prevent typos */ namespace PopupID { - // File Operations - constexpr const char* kSaveAs = "Save As.."; - constexpr const char* kNewProject = "New Project"; - constexpr const char* kManageProject = "Manage Project"; - - // Information - constexpr const char* kAbout = "About"; - constexpr const char* kRomInfo = "ROM Information"; - constexpr const char* kSupportedFeatures = "Supported Features"; - constexpr const char* kStatus = "Status"; - - // Help Documentation - constexpr const char* kGettingStarted = "Getting Started"; - constexpr const char* kAsarIntegration = "Asar Integration"; - constexpr const char* kBuildInstructions = "Build Instructions"; - constexpr const char* kCLIUsage = "CLI Usage"; - constexpr const char* kTroubleshooting = "Troubleshooting"; - constexpr const char* kContributing = "Contributing"; - constexpr const char* kWhatsNew = "Whats New v03"; - constexpr const char* kOpenRomHelp = "Open a ROM"; - - // Settings - constexpr const char* kDisplaySettings = "Display Settings"; - constexpr const char* kFeatureFlags = "Feature Flags"; - - // Workspace - constexpr const char* kWorkspaceHelp = "Workspace Help"; - constexpr const char* kSessionLimitWarning = "Session Limit Warning"; - constexpr const char* kLayoutResetConfirm = "Reset Layout Confirmation"; - - // Debug/Testing - constexpr const char* kDataIntegrity = "Data Integrity Check"; - - // Future expansion - constexpr const char* kQuickExport = "Quick Export"; - constexpr const char* kAssetImport = "Asset Import"; - constexpr const char* kScriptGenerator = "Script Generator"; -} +// File Operations +constexpr const char* kSaveAs = "Save As.."; +constexpr const char* kNewProject = "New Project"; +constexpr const char* kManageProject = "Manage Project"; + +// Information +constexpr const char* kAbout = "About"; +constexpr const char* kRomInfo = "ROM Information"; +constexpr const char* kSupportedFeatures = "Supported Features"; +constexpr const char* kStatus = "Status"; + +// Help Documentation +constexpr const char* kGettingStarted = "Getting Started"; +constexpr const char* kAsarIntegration = "Asar Integration"; +constexpr const char* kBuildInstructions = "Build Instructions"; +constexpr const char* kCLIUsage = "CLI Usage"; +constexpr const char* kTroubleshooting = "Troubleshooting"; +constexpr const char* kContributing = "Contributing"; +constexpr const char* kWhatsNew = "Whats New v03"; +constexpr const char* kOpenRomHelp = "Open a ROM"; + +// Settings +constexpr const char* kDisplaySettings = "Display Settings"; +constexpr const char* kFeatureFlags = "Feature Flags"; + +// Workspace +constexpr const char* kWorkspaceHelp = "Workspace Help"; +constexpr const char* kSessionLimitWarning = "Session Limit Warning"; +constexpr const char* kLayoutResetConfirm = "Reset Layout Confirmation"; + +// Debug/Testing +constexpr const char* kDataIntegrity = "Data Integrity Check"; + +// Future expansion +constexpr const char* kQuickExport = "Quick Export"; +constexpr const char* kAssetImport = "Asset Import"; +constexpr const char* kScriptGenerator = "Script Generator"; +} // namespace PopupID // ImGui popup manager. class PopupManager { @@ -157,16 +157,16 @@ class PopupManager { void DrawTroubleshootingPopup(); void DrawContributingPopup(); void DrawWhatsNewPopup(); - + // Workspace-related popups void DrawWorkspaceHelpPopup(); void DrawSessionLimitWarningPopup(); void DrawLayoutResetConfirmPopup(); - + // Settings popups (accessible without ROM) void DrawDisplaySettingsPopup(); void DrawFeatureFlagsPopup(); - + // Debug/Testing popups void DrawDataIntegrityPopup(); diff --git a/src/app/editor/system/project_manager.cc b/src/app/editor/system/project_manager.cc index 27c29f41..58c1bc58 100644 --- a/src/app/editor/system/project_manager.cc +++ b/src/app/editor/system/project_manager.cc @@ -11,22 +11,22 @@ namespace yaze { namespace editor { ProjectManager::ProjectManager(ToastManager* toast_manager) - : toast_manager_(toast_manager) { -} + : toast_manager_(toast_manager) {} -absl::Status ProjectManager::CreateNewProject(const std::string& template_name) { +absl::Status ProjectManager::CreateNewProject( + const std::string& template_name) { if (template_name.empty()) { // Create default project current_project_ = project::YazeProject(); current_project_.name = "New Project"; current_project_.filepath = GenerateProjectFilename("New Project"); - + if (toast_manager_) { toast_manager_->Show("New project created", ToastType::kSuccess); } return absl::OkStatus(); } - + return CreateFromTemplate(template_name, "New Project"); } @@ -35,7 +35,7 @@ absl::Status ProjectManager::OpenProject(const std::string& filename) { // TODO: Show file dialog return absl::InvalidArgumentError("No filename provided"); } - + return LoadProjectFromFile(filename); } @@ -44,23 +44,23 @@ absl::Status ProjectManager::LoadProjectFromFile(const std::string& filename) { return absl::InvalidArgumentError( absl::StrFormat("Invalid project file: %s", filename)); } - + try { // TODO: Implement actual project loading from JSON/YAML // For now, create a basic project structure - + current_project_ = project::YazeProject(); current_project_.filepath = filename; current_project_.name = std::filesystem::path(filename).stem().string(); - + if (toast_manager_) { toast_manager_->Show( absl::StrFormat("Project loaded: %s", current_project_.name), ToastType::kSuccess); } - + return absl::OkStatus(); - + } catch (const std::exception& e) { if (toast_manager_) { toast_manager_->Show( @@ -76,7 +76,7 @@ absl::Status ProjectManager::SaveProject() { if (!HasActiveProject()) { return absl::FailedPreconditionError("No active project to save"); } - + return SaveProjectToFile(current_project_.filepath); } @@ -85,7 +85,7 @@ absl::Status ProjectManager::SaveProjectAs(const std::string& filename) { // TODO: Show save dialog return absl::InvalidArgumentError("No filename provided for save as"); } - + return SaveProjectToFile(filename); } @@ -93,17 +93,16 @@ absl::Status ProjectManager::SaveProjectToFile(const std::string& filename) { try { // TODO: Implement actual project saving to JSON/YAML // For now, just update the filepath - + current_project_.filepath = filename; - + if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Project saved: %s", filename), - ToastType::kSuccess); + toast_manager_->Show(absl::StrFormat("Project saved: %s", filename), + ToastType::kSuccess); } - + return absl::OkStatus(); - + } catch (const std::exception& e) { if (toast_manager_) { toast_manager_->Show( @@ -119,21 +118,20 @@ absl::Status ProjectManager::ImportProject(const std::string& project_path) { if (project_path.empty()) { return absl::InvalidArgumentError("No project path provided"); } - + if (!std::filesystem::exists(project_path)) { return absl::NotFoundError( absl::StrFormat("Project path does not exist: %s", project_path)); } - + // TODO: Implement project import logic // This would typically copy project files and update paths - + if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Project imported: %s", project_path), - ToastType::kSuccess); + toast_manager_->Show(absl::StrFormat("Project imported: %s", project_path), + ToastType::kSuccess); } - + return absl::OkStatus(); } @@ -141,20 +139,19 @@ absl::Status ProjectManager::ExportProject(const std::string& export_path) { if (!HasActiveProject()) { return absl::FailedPreconditionError("No active project to export"); } - + if (export_path.empty()) { return absl::InvalidArgumentError("No export path provided"); } - + // TODO: Implement project export logic // This would typically create a package with all project files - + if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Project exported: %s", export_path), - ToastType::kSuccess); + toast_manager_->Show(absl::StrFormat("Project exported: %s", export_path), + ToastType::kSuccess); } - + return absl::OkStatus(); } @@ -162,14 +159,14 @@ absl::Status ProjectManager::RepairCurrentProject() { if (!HasActiveProject()) { return absl::FailedPreconditionError("No active project to repair"); } - + // TODO: Implement project repair logic // This would check for missing files, broken references, etc. - + if (toast_manager_) { toast_manager_->Show("Project repair completed", ToastType::kSuccess); } - + return absl::OkStatus(); } @@ -177,7 +174,7 @@ absl::Status ProjectManager::ValidateProject() { if (!HasActiveProject()) { return absl::FailedPreconditionError("No active project to validate"); } - + auto result = current_project_.Validate(); if (!result.ok()) { if (toast_manager_) { @@ -187,11 +184,11 @@ absl::Status ProjectManager::ValidateProject() { } return result; } - + if (toast_manager_) { toast_manager_->Show("Project validation passed", ToastType::kSuccess); } - + return absl::OkStatus(); } @@ -205,44 +202,41 @@ std::string ProjectManager::GetProjectPath() const { std::vector ProjectManager::GetAvailableTemplates() const { // TODO: Scan templates directory and return available templates - return { - "Empty Project", - "Dungeon Editor Project", - "Overworld Editor Project", - "Graphics Editor Project", - "Full Editor Project" - }; + return {"Empty Project", "Dungeon Editor Project", "Overworld Editor Project", + "Graphics Editor Project", "Full Editor Project"}; } -absl::Status ProjectManager::CreateFromTemplate(const std::string& template_name, - const std::string& project_name) { +absl::Status ProjectManager::CreateFromTemplate( + const std::string& template_name, const std::string& project_name) { if (template_name.empty() || project_name.empty()) { - return absl::InvalidArgumentError("Template name and project name required"); + return absl::InvalidArgumentError( + "Template name and project name required"); } - + // TODO: Implement template-based project creation // This would copy template files and customize them - + current_project_ = project::YazeProject(); current_project_.name = project_name; current_project_.filepath = GenerateProjectFilename(project_name); - + if (toast_manager_) { toast_manager_->Show( absl::StrFormat("Project created from template: %s", template_name), ToastType::kSuccess); } - + return absl::OkStatus(); } -std::string ProjectManager::GenerateProjectFilename(const std::string& project_name) const { +std::string ProjectManager::GenerateProjectFilename( + const std::string& project_name) const { // Convert project name to valid filename std::string filename = project_name; std::replace(filename.begin(), filename.end(), ' ', '_'); std::replace(filename.begin(), filename.end(), '/', '_'); std::replace(filename.begin(), filename.end(), '\\', '_'); - + return absl::StrFormat("%s.yaze", filename); } @@ -250,26 +244,27 @@ bool ProjectManager::IsValidProjectFile(const std::string& filename) const { if (filename.empty()) { return false; } - + if (!std::filesystem::exists(filename)) { return false; } - + // Check file extension std::string extension = std::filesystem::path(filename).extension().string(); return extension == ".yaze" || extension == ".json"; } -absl::Status ProjectManager::InitializeProjectStructure(const std::string& project_path) { +absl::Status ProjectManager::InitializeProjectStructure( + const std::string& project_path) { try { // Create project directory structure std::filesystem::create_directories(project_path); std::filesystem::create_directories(project_path + "/assets"); std::filesystem::create_directories(project_path + "/scripts"); std::filesystem::create_directories(project_path + "/output"); - + return absl::OkStatus(); - + } catch (const std::exception& e) { return absl::InternalError( absl::StrFormat("Failed to create project structure: %s", e.what())); diff --git a/src/app/editor/system/project_manager.h b/src/app/editor/system/project_manager.h index a094c69b..2597b5bc 100644 --- a/src/app/editor/system/project_manager.h +++ b/src/app/editor/system/project_manager.h @@ -31,31 +31,33 @@ class ProjectManager { absl::Status OpenProject(const std::string& filename = ""); absl::Status SaveProject(); absl::Status SaveProjectAs(const std::string& filename = ""); - + // Project import/export absl::Status ImportProject(const std::string& project_path); absl::Status ExportProject(const std::string& export_path); - + // Project maintenance absl::Status RepairCurrentProject(); absl::Status ValidateProject(); - + // Project information project::YazeProject& GetCurrentProject() { return current_project_; } - const project::YazeProject& GetCurrentProject() const { return current_project_; } + const project::YazeProject& GetCurrentProject() const { + return current_project_; + } bool HasActiveProject() const { return !current_project_.filepath.empty(); } std::string GetProjectName() const; std::string GetProjectPath() const; - + // Project templates std::vector GetAvailableTemplates() const; - absl::Status CreateFromTemplate(const std::string& template_name, - const std::string& project_name); + absl::Status CreateFromTemplate(const std::string& template_name, + const std::string& project_name); private: project::YazeProject current_project_; ToastManager* toast_manager_ = nullptr; - + // Helper methods absl::Status LoadProjectFromFile(const std::string& filename); absl::Status SaveProjectToFile(const std::string& filename); diff --git a/src/app/editor/system/proposal_drawer.cc b/src/app/editor/system/proposal_drawer.cc index e980d640..6bae5999 100644 --- a/src/app/editor/system/proposal_drawer.cc +++ b/src/app/editor/system/proposal_drawer.cc @@ -6,9 +6,9 @@ #include "absl/strings/str_format.h" #include "absl/time/time.h" -#include "imgui/imgui.h" #include "app/gui/core/icons.h" #include "cli/service/rom/rom_sandbox_manager.h" +#include "imgui/imgui.h" // Policy evaluation support (optional, only in main yaze build) #ifdef YAZE_ENABLE_POLICY_FRAMEWORK @@ -23,18 +23,18 @@ ProposalDrawer::ProposalDrawer() { } void ProposalDrawer::Draw() { - if (!visible_) return; + if (!visible_) + return; // Set drawer position on the right side ImGuiIO& io = ImGui::GetIO(); - ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - drawer_width_, 0), + ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - drawer_width_, 0), ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), + ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse; + ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse; if (ImGui::Begin("Agent Proposals", &visible_, flags)) { if (needs_refresh_) { @@ -56,7 +56,7 @@ void ProposalDrawer::Draw() { // Split view: proposal list on top, details on bottom float list_height = ImGui::GetContentRegionAvail().y * 0.4f; - + ImGui::BeginChild("ProposalList", ImVec2(0, list_height), true); DrawProposalList(); ImGui::EndChild(); @@ -76,9 +76,9 @@ void ProposalDrawer::Draw() { show_confirm_dialog_ = false; } - if (ImGui::BeginPopupModal("Confirm Action", nullptr, + if (ImGui::BeginPopupModal("Confirm Action", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Are you sure you want to %s this proposal?", + ImGui::Text("Are you sure you want to %s this proposal?", confirm_action_.c_str()); ImGui::Separator(); @@ -107,16 +107,16 @@ void ProposalDrawer::Draw() { show_override_dialog_ = false; } - if (ImGui::BeginPopupModal("Override Policy", nullptr, + if (ImGui::BeginPopupModal("Override Policy", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), - ICON_MD_WARNING " Policy Override Required"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + ICON_MD_WARNING " Policy Override Required"); ImGui::Separator(); ImGui::TextWrapped("This proposal has policy warnings."); ImGui::TextWrapped("Do you want to override and accept anyway?"); ImGui::Spacing(); - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), - "Note: This action will be logged."); + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), + "Note: This action will be logged."); ImGui::Separator(); if (ImGui::Button("Override and Accept", ImVec2(150, 0))) { @@ -131,7 +131,6 @@ void ProposalDrawer::Draw() { ImGui::EndPopup(); } #endif // YAZE_ENABLE_POLICY_FRAMEWORK - } void ProposalDrawer::DrawProposalList() { @@ -141,9 +140,8 @@ void ProposalDrawer::DrawProposalList() { return; } - ImGuiTableFlags flags = ImGuiTableFlags_Borders | - ImGuiTableFlags_RowBg | - ImGuiTableFlags_ScrollY; + ImGuiTableFlags flags = + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY; if (ImGui::BeginTable("ProposalsTable", 3, flags)) { ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 60.0f); @@ -154,12 +152,12 @@ void ProposalDrawer::DrawProposalList() { for (const auto& proposal : proposals_) { ImGui::TableNextRow(); - + // ID column ImGui::TableSetColumnIndex(0); bool is_selected = (proposal.id == selected_proposal_id_); - if (ImGui::Selectable(proposal.id.c_str(), is_selected, - ImGuiSelectableFlags_SpanAllColumns)) { + if (ImGui::Selectable(proposal.id.c_str(), is_selected, + ImGuiSelectableFlags_SpanAllColumns)) { SelectProposal(proposal.id); } @@ -191,7 +189,8 @@ void ProposalDrawer::DrawProposalList() { } void ProposalDrawer::DrawProposalDetail() { - if (!selected_proposal_) return; + if (!selected_proposal_) + return; const auto& p = *selected_proposal_; @@ -222,8 +221,8 @@ void ProposalDrawer::DrawProposalDetail() { } if (!diff_content_.empty()) { - ImGui::BeginChild("DiffContent", ImVec2(0, 150), true, - ImGuiWindowFlags_HorizontalScrollbar); + ImGui::BeginChild("DiffContent", ImVec2(0, 150), true, + ImGuiWindowFlags_HorizontalScrollbar); ImGui::TextUnformatted(diff_content_.c_str()); ImGui::EndChild(); } else { @@ -239,7 +238,8 @@ void ProposalDrawer::DrawProposalDetail() { std::stringstream buffer; std::string line; int line_count = 0; - while (std::getline(log_file, line) && line_count < log_display_lines_) { + while (std::getline(log_file, line) && + line_count < log_display_lines_) { buffer << line << "\n"; line_count++; } @@ -251,8 +251,8 @@ void ProposalDrawer::DrawProposalDetail() { } if (!log_content_.empty()) { - ImGui::BeginChild("LogContent", ImVec2(0, 150), true, - ImGuiWindowFlags_HorizontalScrollbar); + ImGui::BeginChild("LogContent", ImVec2(0, 150), true, + ImGuiWindowFlags_HorizontalScrollbar); ImGui::TextUnformatted(log_content_.c_str()); ImGui::EndChild(); } else { @@ -283,31 +283,34 @@ void ProposalDrawer::DrawStatusFilter() { void ProposalDrawer::DrawPolicyStatus() { #ifdef YAZE_ENABLE_POLICY_FRAMEWORK - if (!selected_proposal_) return; + if (!selected_proposal_) + return; const auto& p = *selected_proposal_; - + // Only evaluate policies for pending proposals if (p.status != cli::ProposalRegistry::ProposalStatus::kPending) { return; } - if (ImGui::CollapsingHeader("Policy Status", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader("Policy Status", + ImGuiTreeNodeFlags_DefaultOpen)) { auto& policy_eval = cli::PolicyEvaluator::GetInstance(); - + if (!policy_eval.IsEnabled()) { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - ICON_MD_INFO " No policies configured"); - ImGui::TextWrapped("Create .yaze/policies/agent.yaml to enable policy evaluation"); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + ICON_MD_INFO " No policies configured"); + ImGui::TextWrapped( + "Create .yaze/policies/agent.yaml to enable policy evaluation"); return; } // Evaluate proposal against policies auto policy_result = policy_eval.EvaluateProposal(p.id); - + if (!policy_result.ok()) { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), - ICON_MD_ERROR " Policy evaluation failed"); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), + ICON_MD_ERROR " Policy evaluation failed"); ImGui::TextWrapped("%s", policy_result.status().message().data()); return; } @@ -316,30 +319,30 @@ void ProposalDrawer::DrawPolicyStatus() { // Overall status if (result.is_clean()) { - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), - ICON_MD_CHECK_CIRCLE " All policies passed"); + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), + ICON_MD_CHECK_CIRCLE " All policies passed"); } else if (result.passed) { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), - ICON_MD_WARNING " Passed with warnings"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + ICON_MD_WARNING " Passed with warnings"); } else { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), - ICON_MD_CANCEL " Critical violations found"); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), + ICON_MD_CANCEL " Critical violations found"); } ImGui::Separator(); // Show critical violations if (!result.critical_violations.empty()) { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), - ICON_MD_BLOCK " Critical Violations:"); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), + ICON_MD_BLOCK " Critical Violations:"); for (const auto& violation : result.critical_violations) { ImGui::Bullet(); - ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(), - violation.message.c_str()); + ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(), + violation.message.c_str()); if (!violation.details.empty()) { ImGui::Indent(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", - violation.details.c_str()); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", + violation.details.c_str()); ImGui::Unindent(); } } @@ -348,16 +351,16 @@ void ProposalDrawer::DrawPolicyStatus() { // Show warnings if (!result.warnings.empty()) { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), - ICON_MD_WARNING " Warnings:"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + ICON_MD_WARNING " Warnings:"); for (const auto& violation : result.warnings) { ImGui::Bullet(); - ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(), - violation.message.c_str()); + ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(), + violation.message.c_str()); if (!violation.details.empty()) { ImGui::Indent(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", - violation.details.c_str()); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", + violation.details.c_str()); ImGui::Unindent(); } } @@ -366,12 +369,12 @@ void ProposalDrawer::DrawPolicyStatus() { // Show info messages if (!result.info.empty()) { - ImGui::TextColored(ImVec4(0.5f, 0.5f, 1.0f, 1.0f), - ICON_MD_INFO " Information:"); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 1.0f, 1.0f), + ICON_MD_INFO " Information:"); for (const auto& violation : result.info) { ImGui::Bullet(); - ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(), - violation.message.c_str()); + ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(), + violation.message.c_str()); } } } @@ -379,7 +382,8 @@ void ProposalDrawer::DrawPolicyStatus() { } void ProposalDrawer::DrawActionButtons() { - if (!selected_proposal_) return; + if (!selected_proposal_) + return; const auto& p = *selected_proposal_; bool is_pending = p.status == cli::ProposalRegistry::ProposalStatus::kPending; @@ -387,7 +391,7 @@ void ProposalDrawer::DrawActionButtons() { // Evaluate policies to determine if Accept button should be enabled bool can_accept = true; bool needs_override = false; - + #ifdef YAZE_ENABLE_POLICY_FRAMEWORK if (is_pending) { auto& policy_eval = cli::PolicyEvaluator::GetInstance(); @@ -407,7 +411,7 @@ void ProposalDrawer::DrawActionButtons() { if (!can_accept) { ImGui::BeginDisabled(); } - + if (ImGui::Button(ICON_MD_CHECK " Accept", ImVec2(-1, 0))) { if (needs_override) { // Show override confirmation dialog @@ -420,12 +424,11 @@ void ProposalDrawer::DrawActionButtons() { show_confirm_dialog_ = true; } } - + if (!can_accept) { ImGui::EndDisabled(); ImGui::SameLine(); - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), - "(Blocked by policy)"); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "(Blocked by policy)"); } // Reject button (only for pending proposals) @@ -453,7 +456,7 @@ void ProposalDrawer::FocusProposal(const std::string& proposal_id) { void ProposalDrawer::RefreshProposals() { auto& registry = cli::ProposalRegistry::Instance(); - + std::optional filter; switch (status_filter_) { case StatusFilter::kPending: @@ -507,25 +510,25 @@ void ProposalDrawer::SelectProposal(const std::string& proposal_id) { absl::Status ProposalDrawer::AcceptProposal(const std::string& proposal_id) { auto& registry = cli::ProposalRegistry::Instance(); - + // Get proposal metadata to find sandbox auto proposal_or = registry.GetProposal(proposal_id); if (!proposal_or.ok()) { return proposal_or.status(); } - + const auto& proposal = *proposal_or; - + // Check if ROM is available if (!rom_) { return absl::FailedPreconditionError( "No ROM loaded. Cannot merge proposal changes."); } - + // Find sandbox ROM path using the sandbox_id from the proposal auto& sandbox_mgr = cli::RomSandboxManager::Instance(); auto sandboxes = sandbox_mgr.ListSandboxes(); - + std::filesystem::path sandbox_rom_path; for (const auto& sandbox : sandboxes) { if (sandbox.id == proposal.sandbox_id) { @@ -533,50 +536,47 @@ absl::Status ProposalDrawer::AcceptProposal(const std::string& proposal_id) { break; } } - + if (sandbox_rom_path.empty()) { return absl::NotFoundError( absl::StrFormat("Sandbox ROM not found for proposal %s (sandbox: %s)", - proposal_id, proposal.sandbox_id)); + proposal_id, proposal.sandbox_id)); } - + // Verify sandbox ROM exists std::error_code ec; if (!std::filesystem::exists(sandbox_rom_path, ec)) { - return absl::NotFoundError( - absl::StrFormat("Sandbox ROM file does not exist: %s", - sandbox_rom_path.string())); + return absl::NotFoundError(absl::StrFormat( + "Sandbox ROM file does not exist: %s", sandbox_rom_path.string())); } - + // Load sandbox ROM data Rom sandbox_rom; auto load_status = sandbox_rom.LoadFromFile(sandbox_rom_path.string()); if (!load_status.ok()) { - return absl::InternalError( - absl::StrFormat("Failed to load sandbox ROM: %s", - load_status.message())); + return absl::InternalError(absl::StrFormat("Failed to load sandbox ROM: %s", + load_status.message())); } - + // Merge sandbox ROM data into main ROM // Copy the entire ROM data vector from sandbox to main ROM const auto& sandbox_data = sandbox_rom.vector(); auto merge_status = rom_->WriteVector(0, sandbox_data); if (!merge_status.ok()) { - return absl::InternalError( - absl::StrFormat("Failed to merge sandbox ROM data: %s", - merge_status.message())); + return absl::InternalError(absl::StrFormat( + "Failed to merge sandbox ROM data: %s", merge_status.message())); } - + // Update proposal status auto status = registry.UpdateStatus( proposal_id, cli::ProposalRegistry::ProposalStatus::kAccepted); - + if (status.ok()) { // Mark ROM as dirty so save prompts appear // Note: Rom tracks dirty state internally via Write operations // The WriteVector call above already marked it as dirty } - + needs_refresh_ = true; return status; } @@ -585,7 +585,7 @@ absl::Status ProposalDrawer::RejectProposal(const std::string& proposal_id) { auto& registry = cli::ProposalRegistry::Instance(); auto status = registry.UpdateStatus( proposal_id, cli::ProposalRegistry::ProposalStatus::kRejected); - + needs_refresh_ = true; return status; } @@ -593,14 +593,14 @@ absl::Status ProposalDrawer::RejectProposal(const std::string& proposal_id) { absl::Status ProposalDrawer::DeleteProposal(const std::string& proposal_id) { auto& registry = cli::ProposalRegistry::Instance(); auto status = registry.RemoveProposal(proposal_id); - + if (proposal_id == selected_proposal_id_) { selected_proposal_id_.clear(); selected_proposal_ = nullptr; diff_content_.clear(); log_content_.clear(); } - + needs_refresh_ = true; return status; } diff --git a/src/app/editor/system/proposal_drawer.h b/src/app/editor/system/proposal_drawer.h index bb9898c4..b584c598 100644 --- a/src/app/editor/system/proposal_drawer.h +++ b/src/app/editor/system/proposal_drawer.h @@ -52,43 +52,38 @@ class ProposalDrawer { void DrawPolicyStatus(); // NEW: Display policy evaluation results void DrawStatusFilter(); void DrawActionButtons(); - + absl::Status AcceptProposal(const std::string& proposal_id); absl::Status RejectProposal(const std::string& proposal_id); absl::Status DeleteProposal(const std::string& proposal_id); - + void RefreshProposals(); void SelectProposal(const std::string& proposal_id); - + bool visible_ = false; bool needs_refresh_ = true; - + // Filter state - enum class StatusFilter { - kAll, - kPending, - kAccepted, - kRejected - }; + enum class StatusFilter { kAll, kPending, kAccepted, kRejected }; StatusFilter status_filter_ = StatusFilter::kAll; - + // Proposal state std::vector proposals_; std::string selected_proposal_id_; cli::ProposalRegistry::ProposalMetadata* selected_proposal_ = nullptr; - + // Diff display state std::string diff_content_; std::string log_content_; int log_display_lines_ = 50; - + // UI state float drawer_width_ = 400.0f; bool show_confirm_dialog_ = false; bool show_override_dialog_ = false; // NEW: Policy override confirmation std::string confirm_action_; std::string confirm_proposal_id_; - + // ROM reference for merging Rom* rom_ = nullptr; }; diff --git a/src/app/editor/system/rom_file_manager.cc b/src/app/editor/system/rom_file_manager.cc index c44d5dff..3a20f2af 100644 --- a/src/app/editor/system/rom_file_manager.cc +++ b/src/app/editor/system/rom_file_manager.cc @@ -1,8 +1,8 @@ #include "rom_file_manager.h" +#include #include #include -#include #include "absl/strings/str_format.h" #include "app/editor/system/toast_manager.h" @@ -50,8 +50,7 @@ absl::Status RomFileManager::SaveRomAs(Rom* rom, const std::string& filename) { return absl::FailedPreconditionError("No ROM loaded to save"); } if (filename.empty()) { - return absl::InvalidArgumentError( - "No filename provided for save as"); + return absl::InvalidArgumentError("No filename provided for save as"); } Rom::SaveSettings settings; @@ -66,9 +65,8 @@ absl::Status RomFileManager::SaveRomAs(Rom* rom, const std::string& filename) { absl::StrFormat("Failed to save ROM as: %s", status.message()), ToastType::kError); } else if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("ROM saved as: %s", filename), - ToastType::kSuccess); + toast_manager_->Show(absl::StrFormat("ROM saved as: %s", filename), + ToastType::kSuccess); } return status; } @@ -82,12 +80,10 @@ absl::Status RomFileManager::OpenRomOrProject(Rom* rom, return absl::InvalidArgumentError("No filename provided"); } - std::string extension = - std::filesystem::path(filename).extension().string(); + std::string extension = std::filesystem::path(filename).extension().string(); if (extension == ".yaze" || extension == ".json") { - return absl::UnimplementedError( - "Project file loading not yet implemented"); + return absl::UnimplementedError("Project file loading not yet implemented"); } return LoadRom(rom, filename); @@ -111,9 +107,8 @@ absl::Status RomFileManager::CreateBackup(Rom* rom) { absl::StrFormat("Failed to create backup: %s", status.message()), ToastType::kError); } else if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Backup created: %s", backup_filename), - ToastType::kSuccess); + toast_manager_->Show(absl::StrFormat("Backup created: %s", backup_filename), + ToastType::kSuccess); } return status; } @@ -147,8 +142,8 @@ std::string RomFileManager::GetRomFilename(Rom* rom) const { return rom->filename(); } -absl::Status RomFileManager::LoadRomFromFile( - Rom* rom, const std::string& filename) { +absl::Status RomFileManager::LoadRomFromFile(Rom* rom, + const std::string& filename) { if (!rom) { return absl::InvalidArgumentError("ROM pointer cannot be null"); } diff --git a/src/app/editor/system/rom_file_manager.h b/src/app/editor/system/rom_file_manager.h index 76039b72..d3a9f673 100644 --- a/src/app/editor/system/rom_file_manager.h +++ b/src/app/editor/system/rom_file_manager.h @@ -42,7 +42,8 @@ class RomFileManager { ToastManager* toast_manager_ = nullptr; absl::Status LoadRomFromFile(Rom* rom, const std::string& filename); - std::string GenerateBackupFilename(const std::string& original_filename) const; + std::string GenerateBackupFilename( + const std::string& original_filename) const; bool IsValidRomFile(const std::string& filename) const; }; diff --git a/src/app/editor/system/session_coordinator.cc b/src/app/editor/system/session_coordinator.cc index 94b9d1da..3b16c5af 100644 --- a/src/app/editor/system/session_coordinator.cc +++ b/src/app/editor/system/session_coordinator.cc @@ -675,7 +675,7 @@ void SessionCoordinator::CleanupClosedSessions() { UpdateSessionCount(); LOG_INFO("SessionCoordinator", "Cleaned up closed sessions (remaining: %zu)", - session_count_); + session_count_); } void SessionCoordinator::ClearAllSessions() { diff --git a/src/app/editor/system/session_coordinator.h b/src/app/editor/system/session_coordinator.h index 5f57be6f..cc76c40e 100644 --- a/src/app/editor/system/session_coordinator.h +++ b/src/app/editor/system/session_coordinator.h @@ -18,8 +18,8 @@ namespace editor { class EditorManager; class EditorSet; class EditorCardRegistry; -} -} +} // namespace editor +} // namespace yaze namespace yaze { namespace editor { @@ -40,9 +40,9 @@ class ToastManager; class SessionCoordinator { public: explicit SessionCoordinator(void* sessions_ptr, - EditorCardRegistry* card_registry, - ToastManager* toast_manager, - UserSettings* user_settings); + EditorCardRegistry* card_registry, + ToastManager* toast_manager, + UserSettings* user_settings); ~SessionCoordinator() = default; void SetEditorManager(EditorManager* manager) { editor_manager_ = manager; } @@ -54,7 +54,7 @@ class SessionCoordinator { void CloseSession(size_t index); void RemoveSession(size_t index); void SwitchToSession(size_t index); - + // Session activation and queries void ActivateSession(size_t index); size_t GetActiveSessionIndex() const; @@ -66,65 +66,72 @@ class SessionCoordinator { bool HasMultipleSessions() const; size_t GetActiveSessionCount() const; bool HasDuplicateSession(const std::string& filepath) const; - + // Session UI components void DrawSessionSwitcher(); void DrawSessionManager(); void DrawSessionRenameDialog(); void DrawSessionTabs(); void DrawSessionIndicator(); - + // Session information std::string GetSessionDisplayName(size_t index) const; std::string GetActiveSessionDisplayName() const; void RenameSession(size_t index, const std::string& new_name); - std::string GenerateUniqueEditorTitle(const std::string& editor_name, size_t session_index) const; - + std::string GenerateUniqueEditorTitle(const std::string& editor_name, + size_t session_index) const; + // Session state management void SetActiveSessionIndex(size_t index); void UpdateSessionCount(); - + // Card coordination across sessions void ShowAllCardsInActiveSession(); void HideAllCardsInActiveSession(); void ShowCardsInCategory(const std::string& category); void HideCardsInCategory(const std::string& category); - + // Session validation bool IsValidSessionIndex(size_t index) const; bool IsSessionActive(size_t index) const; bool IsSessionLoaded(size_t index) const; - + // Session statistics size_t GetTotalSessionCount() const; size_t GetLoadedSessionCount() const; size_t GetEmptySessionCount() const; - + // Session operations with error handling - absl::Status LoadRomIntoSession(const std::string& filename, size_t session_index = SIZE_MAX); + absl::Status LoadRomIntoSession(const std::string& filename, + size_t session_index = SIZE_MAX); absl::Status SaveActiveSession(const std::string& filename = ""); absl::Status SaveSessionAs(size_t session_index, const std::string& filename); - absl::StatusOr CreateSessionFromRom(Rom&& rom, const std::string& filepath); - + absl::StatusOr CreateSessionFromRom(Rom&& rom, + const std::string& filepath); + // Session cleanup void CleanupClosedSessions(); void ClearAllSessions(); - + // Session navigation void FocusNextSession(); void FocusPreviousSession(); void FocusFirstSession(); void FocusLastSession(); - + // Session UI state void ShowSessionSwitcher() { show_session_switcher_ = true; } void HideSessionSwitcher() { show_session_switcher_ = false; } - void ToggleSessionSwitcher() { show_session_switcher_ = !show_session_switcher_; } + void ToggleSessionSwitcher() { + show_session_switcher_ = !show_session_switcher_; + } bool IsSessionSwitcherVisible() const { return show_session_switcher_; } - + void ShowSessionManager() { show_session_manager_ = true; } void HideSessionManager() { show_session_manager_ = false; } - void ToggleSessionManager() { show_session_manager_ = !show_session_manager_; } + void ToggleSessionManager() { + show_session_manager_ = !show_session_manager_; + } bool IsSessionManagerVisible() const { return show_session_manager_; } // Helper methods @@ -133,18 +140,19 @@ class SessionCoordinator { std::string GenerateUniqueSessionName(const std::string& base_name) const; void ShowSessionLimitWarning(); void ShowSessionOperationResult(const std::string& operation, bool success); - + // UI helper methods void DrawSessionTab(size_t index, bool is_active); void DrawSessionContextMenu(size_t index); void DrawSessionBadge(size_t index); ImVec4 GetSessionColor(size_t index) const; std::string GetSessionIcon(size_t index) const; - + // Session validation helpers bool IsSessionEmpty(size_t index) const; bool IsSessionClosed(size_t index) const; bool IsSessionModified(size_t index) const; + private: // Core dependencies EditorManager* editor_manager_ = nullptr; @@ -152,18 +160,18 @@ class SessionCoordinator { EditorCardRegistry* card_registry_; ToastManager* toast_manager_; UserSettings* user_settings_; - + // Session state size_t active_session_index_ = 0; size_t session_count_ = 0; - + // UI state bool show_session_switcher_ = false; bool show_session_manager_ = false; bool show_session_rename_dialog_ = false; size_t session_to_rename_ = 0; char session_rename_buffer_[256] = {}; - + // Session limits static constexpr size_t kMaxSessions = 8; static constexpr size_t kMinSessions = 1; diff --git a/src/app/editor/system/settings_editor.cc b/src/app/editor/system/settings_editor.cc index 09c97762..c2a90ec4 100644 --- a/src/app/editor/system/settings_editor.cc +++ b/src/app/editor/system/settings_editor.cc @@ -2,17 +2,17 @@ #include "absl/status/status.h" #include "app/editor/system/editor_card_registry.h" +#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gui/app/editor_layout.h" #include "app/gui/app/feature_flags_menu.h" -#include "app/gfx/debug/performance/performance_profiler.h" -#include "app/gui/core/style.h" #include "app/gui/core/icons.h" +#include "app/gui/core/style.h" #include "app/gui/core/theme_manager.h" #include "imgui/imgui.h" #include "util/log.h" -#include #include +#include namespace yaze { namespace editor { @@ -29,72 +29,64 @@ using ImGui::TableSetupColumn; void SettingsEditor::Initialize() { // Register cards with EditorCardRegistry (dependency injection) - if (!dependencies_.card_registry) return; + if (!dependencies_.card_registry) + return; auto* card_registry = dependencies_.card_registry; - - card_registry->RegisterCard({ - .card_id = MakeCardId("settings.general"), - .display_name = "General Settings", - .icon = ICON_MD_SETTINGS, - .category = "System", - .priority = 10 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("settings.appearance"), - .display_name = "Appearance", - .icon = ICON_MD_PALETTE, - .category = "System", - .priority = 20 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("settings.editor_behavior"), - .display_name = "Editor Behavior", - .icon = ICON_MD_TUNE, - .category = "System", - .priority = 30 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("settings.performance"), - .display_name = "Performance", - .icon = ICON_MD_SPEED, - .category = "System", - .priority = 40 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("settings.ai_agent"), - .display_name = "AI Agent", - .icon = ICON_MD_SMART_TOY, - .category = "System", - .priority = 50 - }); - - card_registry->RegisterCard({ - .card_id = MakeCardId("settings.shortcuts"), - .display_name = "Keyboard Shortcuts", - .icon = ICON_MD_KEYBOARD, - .category = "System", - .priority = 60 - }); - + + card_registry->RegisterCard({.card_id = MakeCardId("settings.general"), + .display_name = "General Settings", + .icon = ICON_MD_SETTINGS, + .category = "System", + .priority = 10}); + + card_registry->RegisterCard({.card_id = MakeCardId("settings.appearance"), + .display_name = "Appearance", + .icon = ICON_MD_PALETTE, + .category = "System", + .priority = 20}); + + card_registry->RegisterCard( + {.card_id = MakeCardId("settings.editor_behavior"), + .display_name = "Editor Behavior", + .icon = ICON_MD_TUNE, + .category = "System", + .priority = 30}); + + card_registry->RegisterCard({.card_id = MakeCardId("settings.performance"), + .display_name = "Performance", + .icon = ICON_MD_SPEED, + .category = "System", + .priority = 40}); + + card_registry->RegisterCard({.card_id = MakeCardId("settings.ai_agent"), + .display_name = "AI Agent", + .icon = ICON_MD_SMART_TOY, + .category = "System", + .priority = 50}); + + card_registry->RegisterCard({.card_id = MakeCardId("settings.shortcuts"), + .display_name = "Keyboard Shortcuts", + .icon = ICON_MD_KEYBOARD, + .category = "System", + .priority = 60}); + // Show general settings by default card_registry->ShowCard(MakeCardId("settings.general")); } -absl::Status SettingsEditor::Load() { +absl::Status SettingsEditor::Load() { gfx::ScopedTimer timer("SettingsEditor::Load"); - return absl::OkStatus(); + return absl::OkStatus(); } absl::Status SettingsEditor::Update() { - if (!dependencies_.card_registry) return absl::OkStatus(); + if (!dependencies_.card_registry) + return absl::OkStatus(); auto* card_registry = dependencies_.card_registry; - + // General Settings Card - Check visibility flag and pass to Begin() for proper X button - bool* general_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.general")); + bool* general_visible = + card_registry->GetVisibilityFlag(MakeCardId("settings.general")); if (general_visible && *general_visible) { static gui::EditorCard general_card("General Settings", ICON_MD_SETTINGS); general_card.SetDefaultSize(600, 500); @@ -103,9 +95,10 @@ absl::Status SettingsEditor::Update() { } general_card.End(); } - + // Appearance Card (Themes + Font Manager combined) - Check visibility and pass flag - bool* appearance_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.appearance")); + bool* appearance_visible = + card_registry->GetVisibilityFlag(MakeCardId("settings.appearance")); if (appearance_visible && *appearance_visible) { static gui::EditorCard appearance_card("Appearance", ICON_MD_PALETTE); appearance_card.SetDefaultSize(600, 600); @@ -116,9 +109,10 @@ absl::Status SettingsEditor::Update() { } appearance_card.End(); } - + // Editor Behavior Card - Check visibility and pass flag - bool* behavior_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.editor_behavior")); + bool* behavior_visible = + card_registry->GetVisibilityFlag(MakeCardId("settings.editor_behavior")); if (behavior_visible && *behavior_visible) { static gui::EditorCard behavior_card("Editor Behavior", ICON_MD_TUNE); behavior_card.SetDefaultSize(600, 500); @@ -127,9 +121,10 @@ absl::Status SettingsEditor::Update() { } behavior_card.End(); } - + // Performance Card - Check visibility and pass flag - bool* perf_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.performance")); + bool* perf_visible = + card_registry->GetVisibilityFlag(MakeCardId("settings.performance")); if (perf_visible && *perf_visible) { static gui::EditorCard perf_card("Performance", ICON_MD_SPEED); perf_card.SetDefaultSize(600, 450); @@ -138,9 +133,10 @@ absl::Status SettingsEditor::Update() { } perf_card.End(); } - + // AI Agent Settings Card - Check visibility and pass flag - bool* ai_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.ai_agent")); + bool* ai_visible = + card_registry->GetVisibilityFlag(MakeCardId("settings.ai_agent")); if (ai_visible && *ai_visible) { static gui::EditorCard ai_card("AI Agent", ICON_MD_SMART_TOY); ai_card.SetDefaultSize(600, 550); @@ -149,11 +145,13 @@ absl::Status SettingsEditor::Update() { } ai_card.End(); } - + // Keyboard Shortcuts Card - Check visibility and pass flag - bool* shortcuts_visible = card_registry->GetVisibilityFlag(MakeCardId("settings.shortcuts")); + bool* shortcuts_visible = + card_registry->GetVisibilityFlag(MakeCardId("settings.shortcuts")); if (shortcuts_visible && *shortcuts_visible) { - static gui::EditorCard shortcuts_card("Keyboard Shortcuts", ICON_MD_KEYBOARD); + static gui::EditorCard shortcuts_card("Keyboard Shortcuts", + ICON_MD_KEYBOARD); shortcuts_card.SetDefaultSize(700, 600); if (shortcuts_card.Begin(shortcuts_visible)) { DrawKeyboardShortcuts(); @@ -197,7 +195,7 @@ void SettingsEditor::DrawGeneralSettings() { void SettingsEditor::DrawKeyboardShortcuts() { ImGui::Text("Keyboard shortcut customization coming soon..."); ImGui::Separator(); - + // TODO: Implement keyboard shortcut editor with: // - Visual shortcut conflict detection // - Import/export shortcut profiles @@ -207,95 +205,102 @@ void SettingsEditor::DrawKeyboardShortcuts() { void SettingsEditor::DrawThemeSettings() { using namespace ImGui; - + auto& theme_manager = gui::ThemeManager::Get(); - + Text("%s Theme Management", ICON_MD_PALETTE); Separator(); - + // Current theme selection Text("Current Theme:"); SameLine(); auto current = theme_manager.GetCurrentThemeName(); TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s", current.c_str()); - + Spacing(); - + // Available themes grid Text("Available Themes:"); for (const auto& theme_name : theme_manager.GetAvailableThemes()) { PushID(theme_name.c_str()); bool is_current = (theme_name == current); - + if (is_current) { PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.8f, 1.0f)); } - + if (Button(theme_name.c_str(), ImVec2(180, 0))) { theme_manager.LoadTheme(theme_name); } - + if (is_current) { PopStyleColor(); SameLine(); Text(ICON_MD_CHECK); } - + PopID(); } - + Separator(); Spacing(); - + // Theme operations if (CollapsingHeader(ICON_MD_EDIT " Theme Operations")) { - TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Theme import/export coming soon"); } } void SettingsEditor::DrawEditorBehavior() { using namespace ImGui; - + if (!user_settings_) { Text("No user settings available"); return; } - + Text("%s Editor Behavior Settings", ICON_MD_TUNE); Separator(); - + // Autosave settings - if (CollapsingHeader(ICON_MD_SAVE " Auto-Save", ImGuiTreeNodeFlags_DefaultOpen)) { - if (Checkbox("Enable Auto-Save", &user_settings_->prefs().autosave_enabled)) { + if (CollapsingHeader(ICON_MD_SAVE " Auto-Save", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (Checkbox("Enable Auto-Save", + &user_settings_->prefs().autosave_enabled)) { user_settings_->Save(); } - + if (user_settings_->prefs().autosave_enabled) { - int interval = static_cast(user_settings_->prefs().autosave_interval); + int interval = + static_cast(user_settings_->prefs().autosave_interval); if (SliderInt("Interval (seconds)", &interval, 60, 600)) { - user_settings_->prefs().autosave_interval = static_cast(interval); + user_settings_->prefs().autosave_interval = + static_cast(interval); user_settings_->Save(); } - - if (Checkbox("Create Backup Before Save", &user_settings_->prefs().backup_before_save)) { + + if (Checkbox("Create Backup Before Save", + &user_settings_->prefs().backup_before_save)) { user_settings_->Save(); } } } - + // Recent files if (CollapsingHeader(ICON_MD_HISTORY " Recent Files")) { - if (SliderInt("Recent Files Limit", &user_settings_->prefs().recent_files_limit, 5, 50)) { + if (SliderInt("Recent Files Limit", + &user_settings_->prefs().recent_files_limit, 5, 50)) { user_settings_->Save(); } } - + // Editor defaults if (CollapsingHeader(ICON_MD_EDIT " Default Editor")) { Text("Editor to open on ROM load:"); - const char* editors[] = { "None", "Overworld", "Dungeon", "Graphics" }; - if (Combo("##DefaultEditor", &user_settings_->prefs().default_editor, editors, IM_ARRAYSIZE(editors))) { + const char* editors[] = {"None", "Overworld", "Dungeon", "Graphics"}; + if (Combo("##DefaultEditor", &user_settings_->prefs().default_editor, + editors, IM_ARRAYSIZE(editors))) { user_settings_->Save(); } } @@ -303,37 +308,40 @@ void SettingsEditor::DrawEditorBehavior() { void SettingsEditor::DrawPerformanceSettings() { using namespace ImGui; - + if (!user_settings_) { Text("No user settings available"); return; } - + Text("%s Performance Settings", ICON_MD_SPEED); Separator(); - + // Graphics settings - if (CollapsingHeader(ICON_MD_IMAGE " Graphics", ImGuiTreeNodeFlags_DefaultOpen)) { + if (CollapsingHeader(ICON_MD_IMAGE " Graphics", + ImGuiTreeNodeFlags_DefaultOpen)) { if (Checkbox("V-Sync", &user_settings_->prefs().vsync)) { user_settings_->Save(); } - + if (SliderInt("Target FPS", &user_settings_->prefs().target_fps, 30, 144)) { user_settings_->Save(); } } - + // Memory settings if (CollapsingHeader(ICON_MD_MEMORY " Memory")) { - if (SliderInt("Cache Size (MB)", &user_settings_->prefs().cache_size_mb, 128, 2048)) { + if (SliderInt("Cache Size (MB)", &user_settings_->prefs().cache_size_mb, + 128, 2048)) { user_settings_->Save(); } - - if (SliderInt("Undo History", &user_settings_->prefs().undo_history_size, 10, 200)) { + + if (SliderInt("Undo History", &user_settings_->prefs().undo_history_size, + 10, 200)) { user_settings_->Save(); } } - + Separator(); Text("Current FPS: %.1f", ImGui::GetIO().Framerate); Text("Frame Time: %.3f ms", 1000.0f / ImGui::GetIO().Framerate); @@ -341,27 +349,31 @@ void SettingsEditor::DrawPerformanceSettings() { void SettingsEditor::DrawAIAgentSettings() { using namespace ImGui; - + if (!user_settings_) { Text("No user settings available"); return; } - + Text("%s AI Agent Configuration", ICON_MD_SMART_TOY); Separator(); - + // Provider selection - if (CollapsingHeader(ICON_MD_CLOUD " AI Provider", ImGuiTreeNodeFlags_DefaultOpen)) { - const char* providers[] = { "Ollama (Local)", "Gemini (Cloud)", "Mock (Testing)" }; - if (Combo("Provider", &user_settings_->prefs().ai_provider, providers, IM_ARRAYSIZE(providers))) { + if (CollapsingHeader(ICON_MD_CLOUD " AI Provider", + ImGuiTreeNodeFlags_DefaultOpen)) { + const char* providers[] = {"Ollama (Local)", "Gemini (Cloud)", + "Mock (Testing)"}; + if (Combo("Provider", &user_settings_->prefs().ai_provider, providers, + IM_ARRAYSIZE(providers))) { user_settings_->Save(); } - + Spacing(); - + if (user_settings_->prefs().ai_provider == 0) { // Ollama char url_buffer[256]; - strncpy(url_buffer, user_settings_->prefs().ollama_url.c_str(), sizeof(url_buffer) - 1); + strncpy(url_buffer, user_settings_->prefs().ollama_url.c_str(), + sizeof(url_buffer) - 1); url_buffer[sizeof(url_buffer) - 1] = '\0'; if (InputText("URL", url_buffer, IM_ARRAYSIZE(url_buffer))) { user_settings_->prefs().ollama_url = url_buffer; @@ -369,77 +381,105 @@ void SettingsEditor::DrawAIAgentSettings() { } } else if (user_settings_->prefs().ai_provider == 1) { // Gemini char key_buffer[128]; - strncpy(key_buffer, user_settings_->prefs().gemini_api_key.c_str(), sizeof(key_buffer) - 1); + strncpy(key_buffer, user_settings_->prefs().gemini_api_key.c_str(), + sizeof(key_buffer) - 1); key_buffer[sizeof(key_buffer) - 1] = '\0'; - if (InputText("API Key", key_buffer, IM_ARRAYSIZE(key_buffer), ImGuiInputTextFlags_Password)) { + if (InputText("API Key", key_buffer, IM_ARRAYSIZE(key_buffer), + ImGuiInputTextFlags_Password)) { user_settings_->prefs().gemini_api_key = key_buffer; user_settings_->Save(); } } } - + // Model parameters if (CollapsingHeader(ICON_MD_TUNE " Model Parameters")) { - if (SliderFloat("Temperature", &user_settings_->prefs().ai_temperature, 0.0f, 2.0f)) { + if (SliderFloat("Temperature", &user_settings_->prefs().ai_temperature, + 0.0f, 2.0f)) { user_settings_->Save(); } TextDisabled("Higher = more creative"); - - if (SliderInt("Max Tokens", &user_settings_->prefs().ai_max_tokens, 256, 8192)) { + + if (SliderInt("Max Tokens", &user_settings_->prefs().ai_max_tokens, 256, + 8192)) { user_settings_->Save(); } } - + // Agent behavior if (CollapsingHeader(ICON_MD_PSYCHOLOGY " Behavior")) { - if (Checkbox("Proactive Suggestions", &user_settings_->prefs().ai_proactive)) { + if (Checkbox("Proactive Suggestions", + &user_settings_->prefs().ai_proactive)) { user_settings_->Save(); } - - if (Checkbox("Auto-Learn Preferences", &user_settings_->prefs().ai_auto_learn)) { + + if (Checkbox("Auto-Learn Preferences", + &user_settings_->prefs().ai_auto_learn)) { user_settings_->Save(); } - - if (Checkbox("Enable Vision/Multimodal", &user_settings_->prefs().ai_multimodal)) { + + if (Checkbox("Enable Vision/Multimodal", + &user_settings_->prefs().ai_multimodal)) { user_settings_->Save(); } } - + // z3ed CLI logging settings - if (CollapsingHeader(ICON_MD_TERMINAL " CLI Logging", ImGuiTreeNodeFlags_DefaultOpen)) { + if (CollapsingHeader(ICON_MD_TERMINAL " CLI Logging", + ImGuiTreeNodeFlags_DefaultOpen)) { Text("Configure z3ed command-line logging behavior"); Spacing(); - + // Log level selection - const char* log_levels[] = { "Debug (Verbose)", "Info (Normal)", "Warning (Quiet)", "Error (Critical)", "Fatal Only" }; - if (Combo("Log Level", &user_settings_->prefs().log_level, log_levels, IM_ARRAYSIZE(log_levels))) { + const char* log_levels[] = {"Debug (Verbose)", "Info (Normal)", + "Warning (Quiet)", "Error (Critical)", + "Fatal Only"}; + if (Combo("Log Level", &user_settings_->prefs().log_level, log_levels, + IM_ARRAYSIZE(log_levels))) { // Apply log level immediately using existing LogManager util::LogLevel level; switch (user_settings_->prefs().log_level) { - case 0: level = util::LogLevel::YAZE_DEBUG; break; - case 1: level = util::LogLevel::INFO; break; - case 2: level = util::LogLevel::WARNING; break; - case 3: level = util::LogLevel::ERROR; break; - case 4: level = util::LogLevel::FATAL; break; - default: level = util::LogLevel::INFO; break; + case 0: + level = util::LogLevel::YAZE_DEBUG; + break; + case 1: + level = util::LogLevel::INFO; + break; + case 2: + level = util::LogLevel::WARNING; + break; + case 3: + level = util::LogLevel::ERROR; + break; + case 4: + level = util::LogLevel::FATAL; + break; + default: + level = util::LogLevel::INFO; + break; } - + // Get current categories std::set categories; - if (user_settings_->prefs().log_ai_requests) categories.insert("AI"); - if (user_settings_->prefs().log_rom_operations) categories.insert("ROM"); - if (user_settings_->prefs().log_gui_automation) categories.insert("GUI"); - if (user_settings_->prefs().log_proposals) categories.insert("Proposals"); - + if (user_settings_->prefs().log_ai_requests) + categories.insert("AI"); + if (user_settings_->prefs().log_rom_operations) + categories.insert("ROM"); + if (user_settings_->prefs().log_gui_automation) + categories.insert("GUI"); + if (user_settings_->prefs().log_proposals) + categories.insert("Proposals"); + // Reconfigure with new level - util::LogManager::instance().configure(level, user_settings_->prefs().log_file_path, categories); + util::LogManager::instance().configure( + level, user_settings_->prefs().log_file_path, categories); user_settings_->Save(); Text("✓ Log level applied"); } TextDisabled("Controls verbosity of YAZE and z3ed output"); - + Spacing(); - + // Logging targets if (Checkbox("Log to File", &user_settings_->prefs().log_to_file)) { if (user_settings_->prefs().log_to_file) { @@ -447,90 +487,112 @@ void SettingsEditor::DrawAIAgentSettings() { if (user_settings_->prefs().log_file_path.empty()) { const char* home = std::getenv("HOME"); if (home) { - user_settings_->prefs().log_file_path = std::string(home) + "/.yaze/logs/yaze.log"; + user_settings_->prefs().log_file_path = + std::string(home) + "/.yaze/logs/yaze.log"; } } - + // Enable file logging std::set categories; - util::LogLevel level = static_cast(user_settings_->prefs().log_level); - util::LogManager::instance().configure(level, user_settings_->prefs().log_file_path, categories); + util::LogLevel level = + static_cast(user_settings_->prefs().log_level); + util::LogManager::instance().configure( + level, user_settings_->prefs().log_file_path, categories); } else { // Disable file logging std::set categories; - util::LogLevel level = static_cast(user_settings_->prefs().log_level); + util::LogLevel level = + static_cast(user_settings_->prefs().log_level); util::LogManager::instance().configure(level, "", categories); } user_settings_->Save(); } - + if (user_settings_->prefs().log_to_file) { Indent(); char path_buffer[512]; - strncpy(path_buffer, user_settings_->prefs().log_file_path.c_str(), sizeof(path_buffer) - 1); + strncpy(path_buffer, user_settings_->prefs().log_file_path.c_str(), + sizeof(path_buffer) - 1); path_buffer[sizeof(path_buffer) - 1] = '\0'; if (InputText("Log File", path_buffer, IM_ARRAYSIZE(path_buffer))) { // Update log file path user_settings_->prefs().log_file_path = path_buffer; std::set categories; - util::LogLevel level = static_cast(user_settings_->prefs().log_level); - util::LogManager::instance().configure(level, user_settings_->prefs().log_file_path, categories); + util::LogLevel level = + static_cast(user_settings_->prefs().log_level); + util::LogManager::instance().configure( + level, user_settings_->prefs().log_file_path, categories); user_settings_->Save(); } - + TextDisabled("Log file path (supports ~ for home directory)"); Unindent(); } - + Spacing(); - + // Log filtering Text(ICON_MD_FILTER_ALT " Category Filtering"); Separator(); TextDisabled("Enable/disable specific log categories"); Spacing(); - + bool categories_changed = false; - - categories_changed |= Checkbox("AI API Requests", &user_settings_->prefs().log_ai_requests); - categories_changed |= Checkbox("ROM Operations", &user_settings_->prefs().log_rom_operations); - categories_changed |= Checkbox("GUI Automation", &user_settings_->prefs().log_gui_automation); - categories_changed |= Checkbox("Proposal Generation", &user_settings_->prefs().log_proposals); - + + categories_changed |= + Checkbox("AI API Requests", &user_settings_->prefs().log_ai_requests); + categories_changed |= + Checkbox("ROM Operations", &user_settings_->prefs().log_rom_operations); + categories_changed |= + Checkbox("GUI Automation", &user_settings_->prefs().log_gui_automation); + categories_changed |= + Checkbox("Proposal Generation", &user_settings_->prefs().log_proposals); + if (categories_changed) { // Rebuild category set std::set categories; - if (user_settings_->prefs().log_ai_requests) categories.insert("AI"); - if (user_settings_->prefs().log_rom_operations) categories.insert("ROM"); - if (user_settings_->prefs().log_gui_automation) categories.insert("GUI"); - if (user_settings_->prefs().log_proposals) categories.insert("Proposals"); - + if (user_settings_->prefs().log_ai_requests) + categories.insert("AI"); + if (user_settings_->prefs().log_rom_operations) + categories.insert("ROM"); + if (user_settings_->prefs().log_gui_automation) + categories.insert("GUI"); + if (user_settings_->prefs().log_proposals) + categories.insert("Proposals"); + // Reconfigure LogManager - util::LogLevel level = static_cast(user_settings_->prefs().log_level); - util::LogManager::instance().configure(level, - user_settings_->prefs().log_to_file ? user_settings_->prefs().log_file_path : "", + util::LogLevel level = + static_cast(user_settings_->prefs().log_level); + util::LogManager::instance().configure( + level, + user_settings_->prefs().log_to_file + ? user_settings_->prefs().log_file_path + : "", categories); user_settings_->Save(); } - + Spacing(); - + // Quick actions if (Button(ICON_MD_DELETE " Clear Logs")) { - if (user_settings_->prefs().log_to_file && !user_settings_->prefs().log_file_path.empty()) { + if (user_settings_->prefs().log_to_file && + !user_settings_->prefs().log_file_path.empty()) { std::filesystem::path path(user_settings_->prefs().log_file_path); if (std::filesystem::exists(path)) { std::filesystem::remove(path); - LOG_DEBUG("Settings", "Log file cleared: %s", user_settings_->prefs().log_file_path.c_str()); + LOG_DEBUG("Settings", "Log file cleared: %s", + user_settings_->prefs().log_file_path.c_str()); } } } SameLine(); if (Button(ICON_MD_FOLDER_OPEN " Open Log Directory")) { - if (user_settings_->prefs().log_to_file && !user_settings_->prefs().log_file_path.empty()) { + if (user_settings_->prefs().log_to_file && + !user_settings_->prefs().log_file_path.empty()) { std::filesystem::path path(user_settings_->prefs().log_file_path); std::filesystem::path dir = path.parent_path(); - + // Platform-specific command to open directory #ifdef _WIN32 std::string cmd = "explorer " + dir.string(); @@ -542,10 +604,10 @@ void SettingsEditor::DrawAIAgentSettings() { system(cmd.c_str()); } } - + Spacing(); Separator(); - + // Log test buttons Text(ICON_MD_BUG_REPORT " Test Logging"); if (Button("Test Debug")) { diff --git a/src/app/editor/system/settings_editor.h b/src/app/editor/system/settings_editor.h index 995cfb91..42bc772f 100644 --- a/src/app/editor/system/settings_editor.h +++ b/src/app/editor/system/settings_editor.h @@ -3,8 +3,8 @@ #include "absl/status/status.h" #include "app/editor/editor.h" -#include "app/rom.h" #include "app/editor/system/user_settings.h" +#include "app/rom.h" #include "imgui/imgui.h" namespace yaze { @@ -51,7 +51,8 @@ static ExampleTreeNode* ExampleTree_CreateNode(const char* name, snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name); node->UID = uid; node->Parent = parent; - if (parent) parent->Childs.push_back(node); + if (parent) + parent->Childs.push_back(node); return node; } @@ -139,7 +140,8 @@ struct ExampleAppPropertyEditor { // Display child and data if (node_open) - for (ExampleTreeNode* child : node->Childs) DrawTreeNode(child); + for (ExampleTreeNode* child : node->Childs) + DrawTreeNode(child); if (node_open && node->HasData) { // In a typical application, the structure description would be derived // from a data-driven system. @@ -186,7 +188,8 @@ struct ExampleAppPropertyEditor { ImGui::PopID(); } } - if (node_open) ImGui::TreePop(); + if (node_open) + ImGui::TreePop(); ImGui::PopID(); } }; @@ -208,16 +211,17 @@ static void ShowExampleAppPropertyEditor(bool* p_open) { class SettingsEditor : public Editor { public: - explicit SettingsEditor(Rom* rom = nullptr, UserSettings* user_settings = nullptr) - : rom_(rom), user_settings_(user_settings) { - type_ = EditorType::kSettings; + explicit SettingsEditor(Rom* rom = nullptr, + UserSettings* user_settings = nullptr) + : rom_(rom), user_settings_(user_settings) { + type_ = EditorType::kSettings; } void Initialize() override; absl::Status Load() override; absl::Status Save() override { return absl::UnimplementedError("Save"); } absl::Status Update() override; - + void set_user_settings(UserSettings* settings) { user_settings_ = settings; } absl::Status Cut() override { return absl::UnimplementedError("Cut"); } absl::Status Copy() override { return absl::UnimplementedError("Copy"); } @@ -225,14 +229,16 @@ class SettingsEditor : public Editor { absl::Status Undo() override { return absl::UnimplementedError("Undo"); } absl::Status Redo() override { return absl::UnimplementedError("Redo"); } absl::Status Find() override { return absl::UnimplementedError("Find"); } - + // Set the ROM pointer void set_rom(Rom* rom) { rom_ = rom; } - + // Get the ROM pointer Rom* rom() const { return rom_; } - bool IsRomLoaded() const override { return true; } // Allow access without ROM for global settings + bool IsRomLoaded() const override { + return true; + } // Allow access without ROM for global settings private: Rom* rom_; diff --git a/src/app/editor/system/shortcut_configurator.cc b/src/app/editor/system/shortcut_configurator.cc index b67dbf81..00f00ee5 100644 --- a/src/app/editor/system/shortcut_configurator.cc +++ b/src/app/editor/system/shortcut_configurator.cc @@ -5,21 +5,20 @@ #include "app/editor/editor_manager.h" #include "app/editor/system/editor_card_registry.h" #include "app/editor/system/menu_orchestrator.h" +#include "app/editor/system/popup_manager.h" #include "app/editor/system/proposal_drawer.h" #include "app/editor/system/rom_file_manager.h" #include "app/editor/system/session_coordinator.h" #include "app/editor/system/toast_manager.h" #include "app/editor/ui/ui_coordinator.h" #include "app/editor/ui/workspace_manager.h" -#include "app/editor/system/popup_manager.h" #include "core/project.h" namespace yaze::editor { namespace { -void RegisterIfValid(ShortcutManager* shortcut_manager, - const std::string& name, +void RegisterIfValid(ShortcutManager* shortcut_manager, const std::string& name, const std::vector& keys, std::function callback) { if (!shortcut_manager || !callback) { @@ -28,10 +27,8 @@ void RegisterIfValid(ShortcutManager* shortcut_manager, shortcut_manager->RegisterShortcut(name, keys, std::move(callback)); } -void RegisterIfValid(ShortcutManager* shortcut_manager, - const std::string& name, - ImGuiKey key, - std::function callback) { +void RegisterIfValid(ShortcutManager* shortcut_manager, const std::string& name, + ImGuiKey key, std::function callback) { if (!shortcut_manager || !callback) { return; } @@ -51,14 +48,12 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps, auto* popup_manager = deps.popup_manager; auto* card_registry = deps.card_registry; - RegisterIfValid( - shortcut_manager, "global.toggle_sidebar", - {ImGuiKey_LeftCtrl, ImGuiKey_B}, - [ui_coordinator]() { - if (ui_coordinator) { - ui_coordinator->ToggleCardSidebar(); - } - }); + RegisterIfValid(shortcut_manager, "global.toggle_sidebar", + {ImGuiKey_LeftCtrl, ImGuiKey_B}, [ui_coordinator]() { + if (ui_coordinator) { + ui_coordinator->ToggleCardSidebar(); + } + }); RegisterIfValid(shortcut_manager, "Open", {ImGuiMod_Ctrl, ImGuiKey_O}, [editor_manager]() { @@ -79,9 +74,10 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps, [editor_manager]() { if (editor_manager) { // Use project-aware default filename when possible - std::string filename = editor_manager->GetCurrentRom() - ? editor_manager->GetCurrentRom()->filename() - : ""; + std::string filename = + editor_manager->GetCurrentRom() + ? editor_manager->GetCurrentRom()->filename() + : ""; editor_manager->SaveRomAs(filename); } }); @@ -143,34 +139,31 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps, } }); - RegisterIfValid( - shortcut_manager, "Command Palette", - {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_P}, - [ui_coordinator]() { - if (ui_coordinator) { - ui_coordinator->ShowCommandPalette(); - } - }); - - RegisterIfValid( - shortcut_manager, "Global Search", - {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_K}, - [ui_coordinator]() { - if (ui_coordinator) { - ui_coordinator->ShowGlobalSearch(); - } - }); - - RegisterIfValid(shortcut_manager, "Load Last ROM", - {ImGuiMod_Ctrl, ImGuiKey_R}, - [editor_manager]() { - auto& recent = project::RecentFilesManager::GetInstance(); - if (!recent.GetRecentFiles().empty() && editor_manager) { - editor_manager->OpenRomOrProject( - recent.GetRecentFiles().front()); + RegisterIfValid(shortcut_manager, "Command Palette", + {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_P}, + [ui_coordinator]() { + if (ui_coordinator) { + ui_coordinator->ShowCommandPalette(); } }); + RegisterIfValid(shortcut_manager, "Global Search", + {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_K}, + [ui_coordinator]() { + if (ui_coordinator) { + ui_coordinator->ShowGlobalSearch(); + } + }); + + RegisterIfValid( + shortcut_manager, "Load Last ROM", {ImGuiMod_Ctrl, ImGuiKey_R}, + [editor_manager]() { + auto& recent = project::RecentFilesManager::GetInstance(); + if (!recent.GetRecentFiles().empty() && editor_manager) { + editor_manager->OpenRomOrProject(recent.GetRecentFiles().front()); + } + }); + RegisterIfValid(shortcut_manager, "Show About", ImGuiKey_F1, [popup_manager]() { if (popup_manager) { @@ -181,8 +174,7 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps, auto register_editor_shortcut = [&](EditorType type, ImGuiKey key) { RegisterIfValid(shortcut_manager, absl::StrFormat("switch.%d", static_cast(type)), - {ImGuiMod_Ctrl, key}, - [editor_manager, type]() { + {ImGuiMod_Ctrl, key}, [editor_manager, type]() { if (editor_manager) { editor_manager->SwitchToEditor(type); } @@ -201,21 +193,19 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps, register_editor_shortcut(EditorType::kSettings, ImGuiKey_0); RegisterIfValid(shortcut_manager, "Editor Selection", - {ImGuiMod_Ctrl, ImGuiKey_E}, - [ui_coordinator]() { + {ImGuiMod_Ctrl, ImGuiKey_E}, [ui_coordinator]() { if (ui_coordinator) { ui_coordinator->ShowEditorSelection(); } }); - RegisterIfValid( - shortcut_manager, "Card Browser", - {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_B}, - [ui_coordinator]() { - if (ui_coordinator) { - ui_coordinator->ShowCardBrowser(); - } - }); + RegisterIfValid(shortcut_manager, "Card Browser", + {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_B}, + [ui_coordinator]() { + if (ui_coordinator) { + ui_coordinator->ShowCardBrowser(); + } + }); if (card_registry) { // Note: Using Ctrl+Alt for card shortcuts to avoid conflicts with Save As (Ctrl+Shift+S) @@ -229,11 +219,10 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps, [card_registry]() { card_registry->ShowAllCardsInCategory("Graphics"); }); - RegisterIfValid(shortcut_manager, "Show Screen Cards", - {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_S}, - [card_registry]() { - card_registry->ShowAllCardsInCategory("Screen"); - }); + RegisterIfValid( + shortcut_manager, "Show Screen Cards", + {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_S}, + [card_registry]() { card_registry->ShowAllCardsInCategory("Screen"); }); } #ifdef YAZE_WITH_GRPC @@ -246,15 +235,15 @@ void ConfigureEditorShortcuts(const ShortcutDependencies& deps, }); RegisterIfValid(shortcut_manager, "Agent Chat History", - {ImGuiMod_Ctrl, ImGuiKey_H}, - [editor_manager]() { + {ImGuiMod_Ctrl, ImGuiKey_H}, [editor_manager]() { if (editor_manager) { editor_manager->ShowChatHistory(); } }); RegisterIfValid(shortcut_manager, "Proposal Drawer", - {ImGuiMod_Ctrl | ImGuiMod_Shift, ImGuiKey_R}, // Changed from Ctrl+P to Ctrl+Shift+R + {ImGuiMod_Ctrl | ImGuiMod_Shift, + ImGuiKey_R}, // Changed from Ctrl+P to Ctrl+Shift+R [editor_manager]() { if (editor_manager) { editor_manager->ShowProposalDrawer(); @@ -298,8 +287,7 @@ void ConfigureMenuShortcuts(const ShortcutDependencies& deps, }); RegisterIfValid(shortcut_manager, "Session Switcher", - {ImGuiMod_Ctrl, ImGuiKey_Tab}, - [session_coordinator]() { + {ImGuiMod_Ctrl, ImGuiKey_Tab}, [session_coordinator]() { if (session_coordinator) { session_coordinator->ShowSessionSwitcher(); } @@ -339,8 +327,7 @@ void ConfigureMenuShortcuts(const ShortcutDependencies& deps, #ifdef YAZE_ENABLE_TESTING RegisterIfValid(shortcut_manager, "Test Dashboard", - {ImGuiMod_Ctrl, ImGuiKey_T}, - [menu_orchestrator]() { + {ImGuiMod_Ctrl, ImGuiKey_T}, [menu_orchestrator]() { if (menu_orchestrator) { menu_orchestrator->OnShowTestDashboard(); } @@ -349,4 +336,3 @@ void ConfigureMenuShortcuts(const ShortcutDependencies& deps, } } // namespace yaze::editor - diff --git a/src/app/editor/system/shortcut_configurator.h b/src/app/editor/system/shortcut_configurator.h index faa5894e..53b02e89 100644 --- a/src/app/editor/system/shortcut_configurator.h +++ b/src/app/editor/system/shortcut_configurator.h @@ -45,4 +45,3 @@ void ConfigureMenuShortcuts(const ShortcutDependencies& deps, } // namespace yaze #endif // YAZE_APP_EDITOR_SYSTEM_SHORTCUT_CONFIGURATOR_H_ - diff --git a/src/app/editor/system/shortcut_manager.cc b/src/app/editor/system/shortcut_manager.cc index 52bd3e3a..0271788f 100644 --- a/src/app/editor/system/shortcut_manager.cc +++ b/src/app/editor/system/shortcut_manager.cc @@ -147,10 +147,10 @@ void ExecuteShortcuts(const ShortcutManager& shortcut_manager) { bool modifiers_held = true; bool key_pressed = false; ImGuiKey main_key = ImGuiKey_None; - + // Separate modifiers from main key for (const auto& key : shortcut.second.keys) { - if (key == ImGuiMod_Ctrl || key == ImGuiMod_Alt || + if (key == ImGuiMod_Ctrl || key == ImGuiMod_Alt || key == ImGuiMod_Shift || key == ImGuiMod_Super) { // Check if modifier is held if (key == ImGuiMod_Ctrl) { @@ -167,12 +167,12 @@ void ExecuteShortcuts(const ShortcutManager& shortcut_manager) { main_key = key; } } - + // Check if main key was pressed (not just held) if (main_key != ImGuiKey_None) { key_pressed = ImGui::IsKeyPressed(main_key, false); // false = no repeat } - + // Execute if modifiers held and key pressed if (modifiers_held && key_pressed && shortcut.second.callback) { shortcut.second.callback(); @@ -188,10 +188,8 @@ namespace yaze { namespace editor { void ShortcutManager::RegisterStandardShortcuts( - std::function save_callback, - std::function open_callback, - std::function close_callback, - std::function find_callback, + std::function save_callback, std::function open_callback, + std::function close_callback, std::function find_callback, std::function settings_callback) { // Ctrl+S - Save @@ -216,7 +214,8 @@ void ShortcutManager::RegisterStandardShortcuts( // Ctrl+, - Settings if (settings_callback) { - RegisterShortcut("settings", {ImGuiMod_Ctrl, ImGuiKey_Comma}, settings_callback); + RegisterShortcut("settings", {ImGuiMod_Ctrl, ImGuiKey_Comma}, + settings_callback); } // Ctrl+Tab - Next tab (placeholder for now) @@ -224,21 +223,20 @@ void ShortcutManager::RegisterStandardShortcuts( } void ShortcutManager::RegisterWindowNavigationShortcuts( - std::function focus_left, - std::function focus_right, - std::function focus_up, - std::function focus_down, - std::function close_window, - std::function split_horizontal, + std::function focus_left, std::function focus_right, + std::function focus_up, std::function focus_down, + std::function close_window, std::function split_horizontal, std::function split_vertical) { // Ctrl+Arrow keys for window navigation if (focus_left) { - RegisterShortcut("focus_left", {ImGuiMod_Ctrl, ImGuiKey_LeftArrow}, focus_left); + RegisterShortcut("focus_left", {ImGuiMod_Ctrl, ImGuiKey_LeftArrow}, + focus_left); } if (focus_right) { - RegisterShortcut("focus_right", {ImGuiMod_Ctrl, ImGuiKey_RightArrow}, focus_right); + RegisterShortcut("focus_right", {ImGuiMod_Ctrl, ImGuiKey_RightArrow}, + focus_right); } if (focus_up) { @@ -246,22 +244,26 @@ void ShortcutManager::RegisterWindowNavigationShortcuts( } if (focus_down) { - RegisterShortcut("focus_down", {ImGuiMod_Ctrl, ImGuiKey_DownArrow}, focus_down); + RegisterShortcut("focus_down", {ImGuiMod_Ctrl, ImGuiKey_DownArrow}, + focus_down); } // Ctrl+W, C - Close current window if (close_window) { - RegisterShortcut("close_window", {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_C}, close_window); + RegisterShortcut("close_window", {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_C}, + close_window); } // Ctrl+W, S - Split horizontal if (split_horizontal) { - RegisterShortcut("split_horizontal", {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_S}, split_horizontal); + RegisterShortcut("split_horizontal", + {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_S}, split_horizontal); } // Ctrl+W, V - Split vertical if (split_vertical) { - RegisterShortcut("split_vertical", {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_V}, split_vertical); + RegisterShortcut("split_vertical", {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_V}, + split_vertical); } } diff --git a/src/app/editor/system/shortcut_manager.h b/src/app/editor/system/shortcut_manager.h index b77bef5c..c14a0828 100644 --- a/src/app/editor/system/shortcut_manager.h +++ b/src/app/editor/system/shortcut_manager.h @@ -3,8 +3,8 @@ #include #include -#include #include +#include // Must define before including imgui.h #ifndef IMGUI_DEFINE_MATH_OPERATORS @@ -22,69 +22,67 @@ struct Shortcut { std::function callback; }; -std::vector ParseShortcut(const std::string &shortcut); +std::vector ParseShortcut(const std::string& shortcut); -std::string PrintShortcut(const std::vector &keys); +std::string PrintShortcut(const std::vector& keys); class ShortcutManager { public: - void RegisterShortcut(const std::string &name, - const std::vector &keys) { + void RegisterShortcut(const std::string& name, + const std::vector& keys) { shortcuts_[name] = {name, keys}; } - void RegisterShortcut(const std::string &name, - const std::vector &keys, + void RegisterShortcut(const std::string& name, + const std::vector& keys, std::function callback) { shortcuts_[name] = {name, keys, callback}; } - void RegisterShortcut(const std::string &name, ImGuiKey key, + void RegisterShortcut(const std::string& name, ImGuiKey key, std::function callback) { shortcuts_[name] = {name, {key}, callback}; } - void ExecuteShortcut(const std::string &name) const { + void ExecuteShortcut(const std::string& name) const { shortcuts_.at(name).callback(); } // Access the shortcut and print the readable name of the shortcut for menus - const Shortcut &GetShortcut(const std::string &name) const { + const Shortcut& GetShortcut(const std::string& name) const { return shortcuts_.at(name); } // Get shortcut callback function - std::function GetCallback(const std::string &name) const { + std::function GetCallback(const std::string& name) const { return shortcuts_.at(name).callback; } - const std::string GetKeys(const std::string &name) const { + const std::string GetKeys(const std::string& name) const { return PrintShortcut(shortcuts_.at(name).keys); } auto GetShortcuts() const { return shortcuts_; } // Convenience methods for registering common shortcuts - void RegisterStandardShortcuts( - std::function save_callback, - std::function open_callback, - std::function close_callback, - std::function find_callback, - std::function settings_callback); + void RegisterStandardShortcuts(std::function save_callback, + std::function open_callback, + std::function close_callback, + std::function find_callback, + std::function settings_callback); - void RegisterWindowNavigationShortcuts( - std::function focus_left, - std::function focus_right, - std::function focus_up, - std::function focus_down, - std::function close_window, - std::function split_horizontal, - std::function split_vertical); + void RegisterWindowNavigationShortcuts(std::function focus_left, + std::function focus_right, + std::function focus_up, + std::function focus_down, + std::function close_window, + std::function split_horizontal, + std::function split_vertical); private: std::unordered_map shortcuts_; }; -void ExecuteShortcuts(const ShortcutManager &shortcut_manager); +void ExecuteShortcuts(const ShortcutManager& shortcut_manager); } // namespace editor } // namespace yaze diff --git a/src/app/editor/system/toast_manager.h b/src/app/editor/system/toast_manager.h index dc80eb52..4f67e82e 100644 --- a/src/app/editor/system/toast_manager.h +++ b/src/app/editor/system/toast_manager.h @@ -24,32 +24,40 @@ struct Toast { class ToastManager { public: - void Show(const std::string &message, ToastType type = ToastType::kInfo, + void Show(const std::string& message, ToastType type = ToastType::kInfo, float ttl_seconds = 3.0f) { toasts_.push_back({message, type, ttl_seconds}); } void Draw() { - if (toasts_.empty()) return; - ImGuiIO &io = ImGui::GetIO(); + if (toasts_.empty()) + return; + ImGuiIO& io = ImGui::GetIO(); ImVec2 pos(io.DisplaySize.x - 10.f, 40.f); // Iterate copy so we can mutate ttl while drawing ordered from newest. for (auto it = toasts_.begin(); it != toasts_.end();) { - Toast &t = *it; + Toast& t = *it; ImVec4 bg; switch (t.type) { - case ToastType::kInfo: bg = ImVec4(0.10f, 0.10f, 0.10f, 0.85f); break; - case ToastType::kSuccess: bg = ImVec4(0.10f, 0.30f, 0.10f, 0.85f); break; - case ToastType::kWarning: bg = ImVec4(0.30f, 0.25f, 0.05f, 0.90f); break; - case ToastType::kError: bg = ImVec4(0.40f, 0.10f, 0.10f, 0.90f); break; + case ToastType::kInfo: + bg = ImVec4(0.10f, 0.10f, 0.10f, 0.85f); + break; + case ToastType::kSuccess: + bg = ImVec4(0.10f, 0.30f, 0.10f, 0.85f); + break; + case ToastType::kWarning: + bg = ImVec4(0.30f, 0.25f, 0.05f, 0.90f); + break; + case ToastType::kError: + bg = ImVec4(0.40f, 0.10f, 0.10f, 0.90f); + break; } ImGui::SetNextWindowBgAlpha(bg.w); ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(1.f, 0.f)); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoNav; + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNav; ImGui::PushStyleColor(ImGuiCol_WindowBg, bg); if (ImGui::Begin("##toast", nullptr, flags)) { ImGui::TextUnformatted(t.message.c_str()); @@ -77,5 +85,3 @@ class ToastManager { } // namespace yaze #endif // YAZE_APP_EDITOR_SYSTEM_TOAST_MANAGER_H - - diff --git a/src/app/editor/system/user_settings.cc b/src/app/editor/system/user_settings.cc index c56d3455..6881d1eb 100644 --- a/src/app/editor/system/user_settings.cc +++ b/src/app/editor/system/user_settings.cc @@ -18,7 +18,8 @@ UserSettings::UserSettings() { if (config_dir_status.ok()) { settings_file_path_ = (*config_dir_status / "yaze_settings.ini").string(); } else { - LOG_WARN("UserSettings", "Could not determine config directory. Using local."); + LOG_WARN("UserSettings", + "Could not determine config directory. Using local."); settings_file_path_ = "yaze_settings.ini"; } } @@ -27,14 +28,15 @@ absl::Status UserSettings::Load() { try { auto data = util::LoadFile(settings_file_path_); if (data.empty()) { - return absl::OkStatus(); // No settings file yet, use defaults. + return absl::OkStatus(); // No settings file yet, use defaults. } std::istringstream ss(data); std::string line; while (std::getline(ss, line)) { size_t eq_pos = line.find('='); - if (eq_pos == std::string::npos) continue; + if (eq_pos == std::string::npos) + continue; std::string key = line.substr(0, eq_pos); std::string val = line.substr(eq_pos + 1); @@ -132,19 +134,21 @@ absl::Status UserSettings::Save() { ss << "recent_files_limit=" << prefs_.recent_files_limit << "\n"; ss << "last_rom_path=" << prefs_.last_rom_path << "\n"; ss << "last_project_path=" << prefs_.last_project_path << "\n"; - ss << "show_welcome_on_startup=" << (prefs_.show_welcome_on_startup ? 1 : 0) << "\n"; - ss << "restore_last_session=" << (prefs_.restore_last_session ? 1 : 0) << "\n"; - + ss << "show_welcome_on_startup=" << (prefs_.show_welcome_on_startup ? 1 : 0) + << "\n"; + ss << "restore_last_session=" << (prefs_.restore_last_session ? 1 : 0) + << "\n"; + // Editor Behavior ss << "backup_before_save=" << (prefs_.backup_before_save ? 1 : 0) << "\n"; ss << "default_editor=" << prefs_.default_editor << "\n"; - + // Performance ss << "vsync=" << (prefs_.vsync ? 1 : 0) << "\n"; ss << "target_fps=" << prefs_.target_fps << "\n"; ss << "cache_size_mb=" << prefs_.cache_size_mb << "\n"; ss << "undo_history_size=" << prefs_.undo_history_size << "\n"; - + // AI Agent ss << "ai_provider=" << prefs_.ai_provider << "\n"; ss << "ollama_url=" << prefs_.ollama_url << "\n"; @@ -154,7 +158,7 @@ absl::Status UserSettings::Save() { ss << "ai_proactive=" << (prefs_.ai_proactive ? 1 : 0) << "\n"; ss << "ai_auto_learn=" << (prefs_.ai_auto_learn ? 1 : 0) << "\n"; ss << "ai_multimodal=" << (prefs_.ai_multimodal ? 1 : 0) << "\n"; - + // CLI Logging ss << "log_level=" << prefs_.log_level << "\n"; ss << "log_to_file=" << (prefs_.log_to_file ? 1 : 0) << "\n"; @@ -163,7 +167,7 @@ absl::Status UserSettings::Save() { ss << "log_rom_operations=" << (prefs_.log_rom_operations ? 1 : 0) << "\n"; ss << "log_gui_automation=" << (prefs_.log_gui_automation ? 1 : 0) << "\n"; ss << "log_proposals=" << (prefs_.log_proposals ? 1 : 0) << "\n"; - + util::SaveFile(settings_file_path_, ss.str()); } catch (const std::exception& e) { return absl::InternalError( @@ -174,4 +178,3 @@ absl::Status UserSettings::Save() { } // namespace editor } // namespace yaze - diff --git a/src/app/editor/system/user_settings.h b/src/app/editor/system/user_settings.h index 881d1993..d957dca2 100644 --- a/src/app/editor/system/user_settings.h +++ b/src/app/editor/system/user_settings.h @@ -24,17 +24,17 @@ class UserSettings { std::string last_project_path; bool show_welcome_on_startup = true; bool restore_last_session = true; - + // Editor Behavior bool backup_before_save = true; int default_editor = 0; // 0=None, 1=Overworld, 2=Dungeon, 3=Graphics - + // Performance bool vsync = true; int target_fps = 60; int cache_size_mb = 512; int undo_history_size = 50; - + // AI Agent int ai_provider = 0; // 0=Ollama, 1=Gemini, 2=Mock std::string ollama_url = "http://localhost:11434"; @@ -44,7 +44,7 @@ class UserSettings { bool ai_proactive = true; bool ai_auto_learn = true; bool ai_multimodal = true; - + // CLI Logging int log_level = 1; // 0=Debug, 1=Info, 2=Warning, 3=Error, 4=Fatal bool log_to_file = false; @@ -54,15 +54,15 @@ class UserSettings { bool log_gui_automation = true; bool log_proposals = true; }; - + UserSettings(); - + absl::Status Load(); absl::Status Save(); - + Preferences& prefs() { return prefs_; } const Preferences& prefs() const { return prefs_; } - + private: Preferences prefs_; std::string settings_file_path_; diff --git a/src/app/editor/system/window_delegate.cc b/src/app/editor/system/window_delegate.cc index 24f33aa0..23645769 100644 --- a/src/app/editor/system/window_delegate.cc +++ b/src/app/editor/system/window_delegate.cc @@ -13,14 +13,14 @@ namespace editor { void WindowDelegate::ShowAllWindows() { // This is a placeholder - actual implementation would need to track // all registered windows and set their visibility flags - printf("[WindowDelegate] ShowAllWindows() - %zu windows registered\n", + printf("[WindowDelegate] ShowAllWindows() - %zu windows registered\n", registered_windows_.size()); } void WindowDelegate::HideAllWindows() { // This is a placeholder - actual implementation would need to track // all registered windows and set their visibility flags - printf("[WindowDelegate] HideAllWindows() - %zu windows registered\n", + printf("[WindowDelegate] HideAllWindows() - %zu windows registered\n", registered_windows_.size()); } @@ -81,9 +81,10 @@ void WindowDelegate::CenterWindow(const std::string& window_id) { } } -void WindowDelegate::DockWindow(const std::string& window_id, ImGuiDir dock_direction) { +void WindowDelegate::DockWindow(const std::string& window_id, + ImGuiDir dock_direction) { if (IsWindowRegistered(window_id)) { - printf("[WindowDelegate] DockWindow: %s to direction %d\n", + printf("[WindowDelegate] DockWindow: %s to direction %d\n", window_id.c_str(), static_cast(dock_direction)); // Actual implementation would dock the window } @@ -96,8 +97,9 @@ void WindowDelegate::UndockWindow(const std::string& window_id) { } } -void WindowDelegate::SetDockSpace(const std::string& dock_space_id, const ImVec2& size) { - printf("[WindowDelegate] SetDockSpace: %s (%.1f x %.1f)\n", +void WindowDelegate::SetDockSpace(const std::string& dock_space_id, + const ImVec2& size) { + printf("[WindowDelegate] SetDockSpace: %s (%.1f x %.1f)\n", dock_space_id.c_str(), size.x, size.y); // Actual implementation would create/configure dock space } @@ -106,33 +108,35 @@ absl::Status WindowDelegate::SaveLayout(const std::string& preset_name) { if (preset_name.empty()) { return absl::InvalidArgumentError("Layout preset name cannot be empty"); } - + std::string file_path = GetLayoutFilePath(preset_name); - + try { // Create directory if it doesn't exist std::filesystem::path dir = std::filesystem::path(file_path).parent_path(); if (!std::filesystem::exists(dir)) { std::filesystem::create_directories(dir); } - + // Save layout data (placeholder implementation) std::ofstream file(file_path); if (!file.is_open()) { - return absl::InternalError(absl::StrFormat("Failed to open layout file: %s", file_path)); + return absl::InternalError( + absl::StrFormat("Failed to open layout file: %s", file_path)); } - + file << "# YAZE Layout Preset: " << preset_name << "\n"; file << "# Generated by WindowDelegate\n"; file << "# TODO: Implement actual layout serialization\n"; - + file.close(); - + printf("[WindowDelegate] Saved layout: %s\n", preset_name.c_str()); return absl::OkStatus(); - + } catch (const std::exception& e) { - return absl::InternalError(absl::StrFormat("Failed to save layout: %s", e.what())); + return absl::InternalError( + absl::StrFormat("Failed to save layout: %s", e.what())); } } @@ -140,32 +144,35 @@ absl::Status WindowDelegate::LoadLayout(const std::string& preset_name) { if (preset_name.empty()) { return absl::InvalidArgumentError("Layout preset name cannot be empty"); } - + std::string file_path = GetLayoutFilePath(preset_name); - + try { if (!std::filesystem::exists(file_path)) { - return absl::NotFoundError(absl::StrFormat("Layout file not found: %s", file_path)); + return absl::NotFoundError( + absl::StrFormat("Layout file not found: %s", file_path)); } - + std::ifstream file(file_path); if (!file.is_open()) { - return absl::InternalError(absl::StrFormat("Failed to open layout file: %s", file_path)); + return absl::InternalError( + absl::StrFormat("Failed to open layout file: %s", file_path)); } - + // Load layout data (placeholder implementation) std::string line; while (std::getline(file, line)) { // TODO: Parse and apply layout data } - + file.close(); - + printf("[WindowDelegate] Loaded layout: %s\n", preset_name.c_str()); return absl::OkStatus(); - + } catch (const std::exception& e) { - return absl::InternalError(absl::StrFormat("Failed to load layout: %s", e.what())); + return absl::InternalError( + absl::StrFormat("Failed to load layout: %s", e.what())); } } @@ -178,12 +185,13 @@ absl::Status WindowDelegate::ResetLayout() { std::vector WindowDelegate::GetAvailableLayouts() const { std::vector layouts; - + try { // Look for layout files in config directory std::string config_dir = "config/layouts"; // TODO: Use proper config path if (std::filesystem::exists(config_dir)) { - for (const auto& entry : std::filesystem::directory_iterator(config_dir)) { + for (const auto& entry : + std::filesystem::directory_iterator(config_dir)) { if (entry.is_regular_file() && entry.path().extension() == ".ini") { layouts.push_back(entry.path().stem().string()); } @@ -192,7 +200,7 @@ std::vector WindowDelegate::GetAvailableLayouts() const { } catch (const std::exception& e) { printf("[WindowDelegate] Error scanning layouts: %s\n", e.what()); } - + return layouts; } @@ -239,14 +247,15 @@ void WindowDelegate::ShowOnlyWindow(const std::string& window_id) { // TODO: Implement show-only functionality } -void WindowDelegate::RegisterWindow(const std::string& window_id, const std::string& category) { +void WindowDelegate::RegisterWindow(const std::string& window_id, + const std::string& category) { WindowInfo info; info.id = window_id; info.category = category; info.is_registered = true; - + registered_windows_[window_id] = info; - printf("[WindowDelegate] Registered window: %s (category: %s)\n", + printf("[WindowDelegate] Registered window: %s (category: %s)\n", window_id.c_str(), category.c_str()); } @@ -283,12 +292,14 @@ bool WindowDelegate::IsWindowRegistered(const std::string& window_id) const { return it != registered_windows_.end() && it->second.is_registered; } -std::string WindowDelegate::GetLayoutFilePath(const std::string& preset_name) const { +std::string WindowDelegate::GetLayoutFilePath( + const std::string& preset_name) const { // TODO: Use proper config directory path return absl::StrFormat("config/layouts/%s.ini", preset_name); } -void WindowDelegate::ApplyLayoutToWindow(const std::string& window_id, const std::string& layout_data) { +void WindowDelegate::ApplyLayoutToWindow(const std::string& window_id, + const std::string& layout_data) { if (IsWindowRegistered(window_id)) { printf("[WindowDelegate] ApplyLayoutToWindow: %s\n", window_id.c_str()); // TODO: Implement layout application diff --git a/src/app/editor/system/window_delegate.h b/src/app/editor/system/window_delegate.h index b75c3f4d..3696bd6b 100644 --- a/src/app/editor/system/window_delegate.h +++ b/src/app/editor/system/window_delegate.h @@ -41,43 +41,45 @@ class WindowDelegate { void MaximizeWindow(const std::string& window_id); void RestoreWindow(const std::string& window_id); void CenterWindow(const std::string& window_id); - + // Docking operations void DockWindow(const std::string& window_id, ImGuiDir dock_direction); void UndockWindow(const std::string& window_id); - void SetDockSpace(const std::string& dock_space_id, const ImVec2& size = ImVec2(0, 0)); - + void SetDockSpace(const std::string& dock_space_id, + const ImVec2& size = ImVec2(0, 0)); + // Layout management absl::Status SaveLayout(const std::string& preset_name); absl::Status LoadLayout(const std::string& preset_name); absl::Status ResetLayout(); std::vector GetAvailableLayouts() const; - + // Workspace-specific layout methods (match EditorManager API) void SaveWorkspaceLayout(); void LoadWorkspaceLayout(); void ResetWorkspaceLayout(); - + // Layout presets void LoadDeveloperLayout(); void LoadDesignerLayout(); void LoadModderLayout(); - + // Window state queries std::vector GetVisibleWindows() const; std::vector GetHiddenWindows() const; ImVec2 GetWindowSize(const std::string& window_id) const; ImVec2 GetWindowPosition(const std::string& window_id) const; - + // Batch operations void ShowWindowsInCategory(const std::string& category); void HideWindowsInCategory(const std::string& category); void ShowOnlyWindow(const std::string& window_id); // Hide all others - + // Window registration (for tracking) - void RegisterWindow(const std::string& window_id, const std::string& category = ""); + void RegisterWindow(const std::string& window_id, + const std::string& category = ""); void UnregisterWindow(const std::string& window_id); - + // Layout presets void LoadMinimalLayout(); @@ -88,13 +90,14 @@ class WindowDelegate { std::string category; bool is_registered = false; }; - + std::unordered_map registered_windows_; - + // Helper methods bool IsWindowRegistered(const std::string& window_id) const; std::string GetLayoutFilePath(const std::string& preset_name) const; - void ApplyLayoutToWindow(const std::string& window_id, const std::string& layout_data); + void ApplyLayoutToWindow(const std::string& window_id, + const std::string& layout_data); }; } // namespace editor diff --git a/src/app/editor/ui/editor_selection_dialog.cc b/src/app/editor/ui/editor_selection_dialog.cc index c00304b3..5d1066b8 100644 --- a/src/app/editor/ui/editor_selection_dialog.cc +++ b/src/app/editor/ui/editor_selection_dialog.cc @@ -1,13 +1,13 @@ #include "app/editor/ui/editor_selection_dialog.h" -#include -#include #include +#include +#include #include "absl/strings/str_cat.h" -#include "imgui/imgui.h" #include "app/gui/core/icons.h" #include "app/gui/core/style.h" +#include "imgui/imgui.h" #include "util/file_util.h" namespace yaze { @@ -16,59 +16,60 @@ namespace editor { EditorSelectionDialog::EditorSelectionDialog() { // Initialize editor metadata with distinct colors editors_ = { - {EditorType::kOverworld, "Overworld", ICON_MD_MAP, - "Edit overworld maps, entrances, and properties", "Ctrl+1", false, true, - ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green - - {EditorType::kDungeon, "Dungeon", ICON_MD_CASTLE, - "Design dungeon rooms, layouts, and mechanics", "Ctrl+2", false, true, - ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple - - {EditorType::kGraphics, "Graphics", ICON_MD_PALETTE, - "Modify tiles, palettes, and graphics sets", "Ctrl+3", false, true, - ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold - - {EditorType::kSprite, "Sprites", ICON_MD_EMOJI_EMOTIONS, - "Edit sprite graphics and properties", "Ctrl+4", false, true, - ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange - - {EditorType::kMessage, "Messages", ICON_MD_CHAT_BUBBLE, - "Edit dialogue, signs, and text", "Ctrl+5", false, true, - ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue - - {EditorType::kMusic, "Music", ICON_MD_MUSIC_NOTE, - "Configure music and sound effects", "Ctrl+6", false, true, - ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple - - {EditorType::kPalette, "Palettes", ICON_MD_COLOR_LENS, - "Edit color palettes and animations", "Ctrl+7", false, true, - ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red - - {EditorType::kScreen, "Screens", ICON_MD_TV, - "Edit title screen and ending screens", "Ctrl+8", false, true, - ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue - - {EditorType::kAssembly, "Assembly", ICON_MD_CODE, - "Write and edit assembly code", "Ctrl+9", false, false, - ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver + {EditorType::kOverworld, "Overworld", ICON_MD_MAP, + "Edit overworld maps, entrances, and properties", "Ctrl+1", false, true, + ImVec4(0.133f, 0.545f, 0.133f, 1.0f)}, // Hyrule green - {EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY, - "Direct ROM memory editing and comparison", "Ctrl+0", false, true, - ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green + {EditorType::kDungeon, "Dungeon", ICON_MD_CASTLE, + "Design dungeon rooms, layouts, and mechanics", "Ctrl+2", false, true, + ImVec4(0.502f, 0.0f, 0.502f, 1.0f)}, // Ganon purple - {EditorType::kEmulator, "Emulator", ICON_MD_VIDEOGAME_ASSET, - "Test and debug your ROM in real-time with live debugging", "Ctrl+Shift+E", false, true, - ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue + {EditorType::kGraphics, "Graphics", ICON_MD_PALETTE, + "Modify tiles, palettes, and graphics sets", "Ctrl+3", false, true, + ImVec4(1.0f, 0.843f, 0.0f, 1.0f)}, // Triforce gold - {EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY, - "Configure AI agent, collaboration, and automation", "Ctrl+Shift+A", false, false, - ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta - - {EditorType::kSettings, "Settings", ICON_MD_SETTINGS, - "Configure ROM and project settings", "", false, true, - ImVec4(0.6f, 0.6f, 0.6f, 1.0f)}, // Gray + {EditorType::kSprite, "Sprites", ICON_MD_EMOJI_EMOTIONS, + "Edit sprite graphics and properties", "Ctrl+4", false, true, + ImVec4(1.0f, 0.647f, 0.0f, 1.0f)}, // Spirit orange + + {EditorType::kMessage, "Messages", ICON_MD_CHAT_BUBBLE, + "Edit dialogue, signs, and text", "Ctrl+5", false, true, + ImVec4(0.196f, 0.6f, 0.8f, 1.0f)}, // Master sword blue + + {EditorType::kMusic, "Music", ICON_MD_MUSIC_NOTE, + "Configure music and sound effects", "Ctrl+6", false, true, + ImVec4(0.416f, 0.353f, 0.804f, 1.0f)}, // Shadow purple + + {EditorType::kPalette, "Palettes", ICON_MD_COLOR_LENS, + "Edit color palettes and animations", "Ctrl+7", false, true, + ImVec4(0.863f, 0.078f, 0.235f, 1.0f)}, // Heart red + + {EditorType::kScreen, "Screens", ICON_MD_TV, + "Edit title screen and ending screens", "Ctrl+8", false, true, + ImVec4(0.4f, 0.8f, 1.0f, 1.0f)}, // Sky blue + + {EditorType::kAssembly, "Assembly", ICON_MD_CODE, + "Write and edit assembly code", "Ctrl+9", false, false, + ImVec4(0.8f, 0.8f, 0.8f, 1.0f)}, // Silver + + {EditorType::kHex, "Hex Editor", ICON_MD_DATA_ARRAY, + "Direct ROM memory editing and comparison", "Ctrl+0", false, true, + ImVec4(0.2f, 0.8f, 0.4f, 1.0f)}, // Matrix green + + {EditorType::kEmulator, "Emulator", ICON_MD_VIDEOGAME_ASSET, + "Test and debug your ROM in real-time with live debugging", + "Ctrl+Shift+E", false, true, + ImVec4(0.2f, 0.6f, 1.0f, 1.0f)}, // Emulator blue + + {EditorType::kAgent, "AI Agent", ICON_MD_SMART_TOY, + "Configure AI agent, collaboration, and automation", "Ctrl+Shift+A", + false, false, ImVec4(0.8f, 0.4f, 1.0f, 1.0f)}, // Purple/magenta + + {EditorType::kSettings, "Settings", ICON_MD_SETTINGS, + "Configure ROM and project settings", "", false, true, + ImVec4(0.6f, 0.6f, 0.6f, 1.0f)}, // Gray }; - + LoadRecentEditors(); } @@ -77,50 +78,53 @@ bool EditorSelectionDialog::Show(bool* p_open) { if (p_open && *p_open && !is_open_) { is_open_ = true; } - + if (!is_open_) { - if (p_open) *p_open = false; + if (p_open) + *p_open = false; return false; } - + bool editor_selected = false; bool* window_open = p_open ? p_open : &is_open_; - + // Set window properties immediately before Begin to prevent them from affecting tooltips ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(950, 650), ImGuiCond_Appearing); // Slightly larger for better layout - + ImGui::SetNextWindowSize( + ImVec2(950, 650), + ImGuiCond_Appearing); // Slightly larger for better layout + if (ImGui::Begin("Editor Selection", window_open, ImGuiWindowFlags_NoCollapse)) { DrawWelcomeHeader(); - + ImGui::Separator(); ImGui::Spacing(); - + // Quick access buttons for recently used if (!recent_editors_.empty()) { DrawQuickAccessButtons(); ImGui::Separator(); ImGui::Spacing(); } - + // Main editor grid ImGui::Text(ICON_MD_APPS " All Editors"); ImGui::Spacing(); - + float button_size = 200.0f; - int columns = static_cast(ImGui::GetContentRegionAvail().x / button_size); + int columns = + static_cast(ImGui::GetContentRegionAvail().x / button_size); columns = std::max(columns, 1); - - if (ImGui::BeginTable("##EditorGrid", columns, - ImGuiTableFlags_None)) { + + if (ImGui::BeginTable("##EditorGrid", columns, ImGuiTableFlags_None)) { for (size_t i = 0; i < editors_.size(); ++i) { ImGui::TableNextColumn(); - + EditorType prev_selection = selected_editor_; DrawEditorCard(editors_[i], static_cast(i)); - + // Check if an editor was just selected if (selected_editor_ != prev_selection) { editor_selected = true; @@ -134,172 +138,186 @@ bool EditorSelectionDialog::Show(bool* p_open) { } } ImGui::End(); - + // Sync state back if (p_open && !(*p_open)) { is_open_ = false; } - + if (editor_selected) { is_open_ = false; - if (p_open) *p_open = false; + if (p_open) + *p_open = false; } - + return editor_selected; } void EditorSelectionDialog::DrawWelcomeHeader() { ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 header_start = ImGui::GetCursorScreenPos(); - + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font - + // Colorful gradient title ImVec4 title_color = ImVec4(1.0f, 0.843f, 0.0f, 1.0f); // Triforce gold ImGui::TextColored(title_color, ICON_MD_EDIT " Select an Editor"); - + ImGui::PopFont(); - + // Subtitle with gradient separator ImVec2 subtitle_pos = ImGui::GetCursorScreenPos(); ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), - "Choose an editor to begin working on your ROM. " - "You can open multiple editors simultaneously."); + "Choose an editor to begin working on your ROM. " + "You can open multiple editors simultaneously."); } void EditorSelectionDialog::DrawQuickAccessButtons() { - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_HISTORY " Recently Used"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_HISTORY " Recently Used"); ImGui::Spacing(); - + for (EditorType type : recent_editors_) { // Find editor info - auto it = std::find_if(editors_.begin(), editors_.end(), - [type](const EditorInfo& info) { - return info.type == type; - }); - + auto it = std::find_if( + editors_.begin(), editors_.end(), + [type](const EditorInfo& info) { return info.type == type; }); + if (it != editors_.end()) { // Use editor's theme color for button ImVec4 color = it->color; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x * 0.5f, color.y * 0.5f, - color.z * 0.5f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x * 0.7f, color.y * 0.7f, - color.z * 0.7f, 0.9f)); + ImGui::PushStyleColor( + ImGuiCol_Button, + ImVec4(color.x * 0.5f, color.y * 0.5f, color.z * 0.5f, 0.7f)); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + ImVec4(color.x * 0.7f, color.y * 0.7f, color.z * 0.7f, 0.9f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, color); - + if (ImGui::Button(absl::StrCat(it->icon, " ", it->name).c_str(), - ImVec2(150, 35))) { + ImVec2(150, 35))) { selected_editor_ = type; } - + ImGui::PopStyleColor(3); - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", it->description); } - + ImGui::SameLine(); } } - + ImGui::NewLine(); } void EditorSelectionDialog::DrawEditorCard(const EditorInfo& info, int index) { ImGui::PushID(index); - + ImVec2 button_size(180, 120); ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - + // Card styling with gradients bool is_recent = std::find(recent_editors_.begin(), recent_editors_.end(), info.type) != recent_editors_.end(); - + // Create gradient background ImVec4 base_color = info.color; - ImU32 color_top = ImGui::GetColorU32(ImVec4(base_color.x * 0.4f, base_color.y * 0.4f, - base_color.z * 0.4f, 0.8f)); - ImU32 color_bottom = ImGui::GetColorU32(ImVec4(base_color.x * 0.2f, base_color.y * 0.2f, - base_color.z * 0.2f, 0.9f)); - + ImU32 color_top = ImGui::GetColorU32(ImVec4( + base_color.x * 0.4f, base_color.y * 0.4f, base_color.z * 0.4f, 0.8f)); + ImU32 color_bottom = ImGui::GetColorU32(ImVec4( + base_color.x * 0.2f, base_color.y * 0.2f, base_color.z * 0.2f, 0.9f)); + // Draw gradient card background draw_list->AddRectFilledMultiColor( cursor_pos, ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), color_top, color_top, color_bottom, color_bottom); - + // Colored border - ImU32 border_color = is_recent - ? ImGui::GetColorU32(ImVec4(base_color.x, base_color.y, base_color.z, 1.0f)) - : ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f, - base_color.z * 0.6f, 0.7f)); - draw_list->AddRect(cursor_pos, - ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), - border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f); - + ImU32 border_color = + is_recent + ? ImGui::GetColorU32( + ImVec4(base_color.x, base_color.y, base_color.z, 1.0f)) + : ImGui::GetColorU32(ImVec4(base_color.x * 0.6f, base_color.y * 0.6f, + base_color.z * 0.6f, 0.7f)); + draw_list->AddRect( + cursor_pos, + ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), + border_color, 4.0f, 0, is_recent ? 3.0f : 2.0f); + // Recent indicator badge if (is_recent) { ImVec2 badge_pos(cursor_pos.x + button_size.x - 25, cursor_pos.y + 5); - draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color), 16); + draw_list->AddCircleFilled(badge_pos, 12, ImGui::GetColorU32(base_color), + 16); ImGui::SetCursorScreenPos(ImVec2(badge_pos.x - 6, badge_pos.y - 8)); ImGui::TextColored(ImVec4(1, 1, 1, 1), ICON_MD_STAR); } - + // Make button transparent (we draw our own background) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(base_color.x * 0.3f, base_color.y * 0.3f, - base_color.z * 0.3f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(base_color.x * 0.5f, base_color.y * 0.5f, - base_color.z * 0.5f, 0.7f)); - + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(base_color.x * 0.3f, base_color.y * 0.3f, + base_color.z * 0.3f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ImVec4(base_color.x * 0.5f, base_color.y * 0.5f, + base_color.z * 0.5f, 0.7f)); + ImGui::SetCursorScreenPos(cursor_pos); - bool clicked = ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size); + bool clicked = + ImGui::Button(absl::StrCat("##", info.name).c_str(), button_size); bool is_hovered = ImGui::IsItemHovered(); - + ImGui::PopStyleColor(3); - + // Draw icon with colored background circle ImVec2 icon_center(cursor_pos.x + button_size.x / 2, cursor_pos.y + 30); ImU32 icon_bg = ImGui::GetColorU32(base_color); draw_list->AddCircleFilled(icon_center, 22, icon_bg, 32); - + // Draw icon - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Larger font for icon ImVec2 icon_size = ImGui::CalcTextSize(info.icon); - ImGui::SetCursorScreenPos(ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); + ImGui::SetCursorScreenPos( + ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); ImGui::TextColored(ImVec4(1, 1, 1, 1), "%s", info.icon); ImGui::PopFont(); - + // Draw name ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + 65)); ImGui::PushTextWrapPos(cursor_pos.x + button_size.x - 10); ImVec2 name_size = ImGui::CalcTextSize(info.name); - ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + (button_size.x - name_size.x) / 2, - cursor_pos.y + 65)); + ImGui::SetCursorScreenPos(ImVec2( + cursor_pos.x + (button_size.x - name_size.x) / 2, cursor_pos.y + 65)); ImGui::TextColored(base_color, "%s", info.name); ImGui::PopTextWrapPos(); - + // Draw shortcut hint if available if (info.shortcut && info.shortcut[0]) { - ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20)); + ImGui::SetCursorScreenPos( + ImVec2(cursor_pos.x + 10, cursor_pos.y + button_size.y - 20)); ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", info.shortcut); } - + // Hover glow effect if (is_hovered) { - ImU32 glow_color = ImGui::GetColorU32(ImVec4(base_color.x, base_color.y, base_color.z, 0.2f)); - draw_list->AddRectFilled(cursor_pos, - ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), - glow_color, 4.0f); + ImU32 glow_color = ImGui::GetColorU32( + ImVec4(base_color.x, base_color.y, base_color.z, 0.2f)); + draw_list->AddRectFilled( + cursor_pos, + ImVec2(cursor_pos.x + button_size.x, cursor_pos.y + button_size.y), + glow_color, 4.0f); } - + // Enhanced tooltip with fixed sizing if (is_hovered) { // Force tooltip to have a fixed max width to prevent flickering ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always); ImGui::BeginTooltip(); - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); // Medium font ImGui::TextColored(base_color, "%s %s", info.icon, info.name); ImGui::PopFont(); ImGui::Separator(); @@ -312,15 +330,16 @@ void EditorSelectionDialog::DrawEditorCard(const EditorInfo& info, int index) { } if (is_recent) { ImGui::Spacing(); - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_STAR " Recently used"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_STAR " Recently used"); } ImGui::EndTooltip(); } - + if (clicked) { selected_editor_ = info.type; } - + ImGui::PopID(); } @@ -330,15 +349,15 @@ void EditorSelectionDialog::MarkRecentlyUsed(EditorType type) { if (it != recent_editors_.end()) { recent_editors_.erase(it); } - + // Add to front recent_editors_.insert(recent_editors_.begin(), type); - + // Limit size if (recent_editors_.size() > kMaxRecentEditors) { recent_editors_.resize(kMaxRecentEditors); } - + SaveRecentEditors(); } @@ -348,10 +367,11 @@ void EditorSelectionDialog::LoadRecentEditors() { if (!data.empty()) { std::istringstream ss(data); std::string line; - while (std::getline(ss, line) && + while (std::getline(ss, line) && recent_editors_.size() < kMaxRecentEditors) { int type_int = std::stoi(line); - if (type_int >= 0 && type_int < static_cast(EditorType::kSettings)) { + if (type_int >= 0 && + type_int < static_cast(EditorType::kSettings)) { recent_editors_.push_back(static_cast(type_int)); } } diff --git a/src/app/editor/ui/editor_selection_dialog.h b/src/app/editor/ui/editor_selection_dialog.h index 0584faa0..88a9f3b0 100644 --- a/src/app/editor/ui/editor_selection_dialog.h +++ b/src/app/editor/ui/editor_selection_dialog.h @@ -1,9 +1,9 @@ #ifndef YAZE_APP_EDITOR_UI_EDITOR_SELECTION_DIALOG_H_ #define YAZE_APP_EDITOR_UI_EDITOR_SELECTION_DIALOG_H_ +#include #include #include -#include #include "app/editor/editor.h" #include "imgui/imgui.h" @@ -36,55 +36,55 @@ struct EditorInfo { class EditorSelectionDialog { public: EditorSelectionDialog(); - + /** * @brief Show the dialog * @return True if an editor was selected */ bool Show(bool* p_open = nullptr); - + /** * @brief Get the selected editor type */ EditorType GetSelectedEditor() const { return selected_editor_; } - + /** * @brief Check if dialog is open */ bool IsOpen() const { return is_open_; } - + /** * @brief Open the dialog */ void Open() { is_open_ = true; } - + /** * @brief Close the dialog */ void Close() { is_open_ = false; } - + /** * @brief Set callback for when editor is selected */ void SetSelectionCallback(std::function callback) { selection_callback_ = callback; } - + /** * @brief Mark an editor as recently used */ void MarkRecentlyUsed(EditorType type); - + /** * @brief Load recently used editors from settings */ void LoadRecentEditors(); - + /** * @brief Save recently used editors to settings */ void SaveRecentEditors(); - + /** * @brief Clear recent editors (for new ROM sessions) */ @@ -92,17 +92,17 @@ class EditorSelectionDialog { recent_editors_.clear(); SaveRecentEditors(); } - + private: void DrawEditorCard(const EditorInfo& info, int index); void DrawWelcomeHeader(); void DrawQuickAccessButtons(); - + std::vector editors_; EditorType selected_editor_ = static_cast(0); bool is_open_ = false; std::function selection_callback_; - + // Recently used tracking std::vector recent_editors_; static constexpr int kMaxRecentEditors = 5; diff --git a/src/app/editor/ui/layout_manager.cc b/src/app/editor/ui/layout_manager.cc index 7a531af6..9595bfdf 100644 --- a/src/app/editor/ui/layout_manager.cc +++ b/src/app/editor/ui/layout_manager.cc @@ -8,7 +8,7 @@ namespace yaze { namespace editor { void LayoutManager::InitializeEditorLayout(EditorType type, - ImGuiID dockspace_id) { + ImGuiID dockspace_id) { // Don't reinitialize if already set up if (IsLayoutInitialized(type)) { LOG_INFO("LayoutManager", @@ -22,8 +22,7 @@ void LayoutManager::InitializeEditorLayout(EditorType type, // Clear existing layout for this dockspace ImGui::DockBuilderRemoveNode(dockspace_id); - ImGui::DockBuilderAddNode(dockspace_id, - ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace); ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size); // Build layout based on editor type @@ -92,9 +91,9 @@ void LayoutManager::BuildOverworldLayout(ImGuiID dockspace_id) { // Split dockspace: Left 25% | Center 60% | Right 15% dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f, - nullptr, &dockspace_id); + nullptr, &dockspace_id); dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, - 0.20f, nullptr, &dockspace_id); + 0.20f, nullptr, &dockspace_id); dock_center_id = dockspace_id; // Center is what remains // Split left panel: Tile16 (top) and Tile8 (bottom) @@ -133,9 +132,9 @@ void LayoutManager::BuildDungeonLayout(ImGuiID dockspace_id) { // Split dockspace: Left 20% | Center 65% | Right 15% dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.20f, - nullptr, &dockspace_id); + nullptr, &dockspace_id); dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, - 0.19f, nullptr, &dockspace_id); + 0.19f, nullptr, &dockspace_id); dock_center_id = dockspace_id; // Split left panel: Room Selector (top 60%) and Entrances (bottom 40%) @@ -174,9 +173,9 @@ void LayoutManager::BuildGraphicsLayout(ImGuiID dockspace_id) { // Split dockspace: Left 30% | Center 50% | Right 20% dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.30f, - nullptr, &dockspace_id); + nullptr, &dockspace_id); dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, - 0.29f, nullptr, &dockspace_id); + 0.29f, nullptr, &dockspace_id); dock_center_id = dockspace_id; // Split right panel: Animations (top) and Prototype (bottom) @@ -206,9 +205,9 @@ void LayoutManager::BuildPaletteLayout(ImGuiID dockspace_id) { // Split dockspace: Left 25% | Center 50% | Right 25% dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f, - nullptr, &dockspace_id); + nullptr, &dockspace_id); dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, - 0.33f, nullptr, &dockspace_id); + 0.33f, nullptr, &dockspace_id); dock_center_id = dockspace_id; // Split left panel: Group Manager (top) and ROM Browser (bottom) @@ -238,8 +237,8 @@ void LayoutManager::BuildScreenLayout(ImGuiID dockspace_id) { // - Corners: Dungeon Maps, Title Screen, Inventory Menu, Naming Screen ImGuiID dock_top = 0; - ImGuiID dock_bottom = ImGui::DockBuilderSplitNode( - dockspace_id, ImGuiDir_Down, 0.50f, nullptr, &dock_top); + ImGuiID dock_bottom = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Down, + 0.50f, nullptr, &dock_top); // Split top: left and right ImGuiID dock_top_left = 0; @@ -274,9 +273,9 @@ void LayoutManager::BuildMusicLayout(ImGuiID dockspace_id) { // Split dockspace: Left 30% | Center 45% | Right 25% dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.30f, - nullptr, &dockspace_id); + nullptr, &dockspace_id); dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, - 0.36f, nullptr, &dockspace_id); + 0.36f, nullptr, &dockspace_id); dock_center_id = dockspace_id; // Dock windows @@ -317,9 +316,9 @@ void LayoutManager::BuildMessageLayout(ImGuiID dockspace_id) { // Split dockspace: Left 25% | Center 50% | Right 25% dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f, - nullptr, &dockspace_id); + nullptr, &dockspace_id); dock_right_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, - 0.33f, nullptr, &dockspace_id); + 0.33f, nullptr, &dockspace_id); dock_center_id = dockspace_id; // Split right panel: Font Atlas (top) and Dictionary (bottom) @@ -410,4 +409,3 @@ void LayoutManager::ClearInitializationFlags() { } // namespace editor } // namespace yaze - diff --git a/src/app/editor/ui/layout_manager.h b/src/app/editor/ui/layout_manager.h index 6a64d9c1..b30e21c1 100644 --- a/src/app/editor/ui/layout_manager.h +++ b/src/app/editor/ui/layout_manager.h @@ -93,4 +93,3 @@ class LayoutManager { } // namespace yaze #endif // YAZE_APP_EDITOR_UI_LAYOUT_MANAGER_H_ - diff --git a/src/app/editor/ui/menu_builder.cc b/src/app/editor/ui/menu_builder.cc index 629f7495..b3ab9ca9 100644 --- a/src/app/editor/ui/menu_builder.cc +++ b/src/app/editor/ui/menu_builder.cc @@ -18,8 +18,9 @@ MenuBuilder& MenuBuilder::BeginMenu(const char* label, const char* icon) { MenuBuilder& MenuBuilder::BeginSubMenu(const char* label, const char* icon, EnabledCheck enabled) { - if (!current_menu_) return *this; - + if (!current_menu_) + return *this; + MenuItem item; item.type = MenuItem::Type::kSubMenuBegin; item.label = label; @@ -32,14 +33,15 @@ MenuBuilder& MenuBuilder::BeginSubMenu(const char* label, const char* icon, } MenuBuilder& MenuBuilder::EndMenu() { - if (!current_menu_) return *this; - + if (!current_menu_) + return *this; + // Check if we're ending a submenu or top-level menu // We need to track nesting depth to handle nested submenus correctly bool is_submenu = false; int depth = 0; - - for (auto it = current_menu_->items.rbegin(); + + for (auto it = current_menu_->items.rbegin(); it != current_menu_->items.rend(); ++it) { if (it->type == MenuItem::Type::kSubMenuEnd) { depth++; // Found an end, so we need to skip its matching begin @@ -52,7 +54,7 @@ MenuBuilder& MenuBuilder::EndMenu() { depth--; // This begin matches a previous end } } - + if (is_submenu) { MenuItem item; item.type = MenuItem::Type::kSubMenuEnd; @@ -60,15 +62,16 @@ MenuBuilder& MenuBuilder::EndMenu() { } else { current_menu_ = nullptr; } - + return *this; } MenuBuilder& MenuBuilder::Item(const char* label, const char* icon, Callback callback, const char* shortcut, EnabledCheck enabled, EnabledCheck checked) { - if (!current_menu_) return *this; - + if (!current_menu_) + return *this; + MenuItem item; item.type = MenuItem::Type::kItem; item.label = label; @@ -91,8 +94,9 @@ MenuBuilder& MenuBuilder::Item(const char* label, Callback callback, } MenuBuilder& MenuBuilder::Separator() { - if (!current_menu_) return *this; - + if (!current_menu_) + return *this; + MenuItem item; item.type = MenuItem::Type::kSeparator; current_menu_->items.push_back(item); @@ -100,8 +104,9 @@ MenuBuilder& MenuBuilder::Separator() { } MenuBuilder& MenuBuilder::DisabledItem(const char* label, const char* icon) { - if (!current_menu_) return *this; - + if (!current_menu_) + return *this; + MenuItem item; item.type = MenuItem::Type::kDisabled; item.label = label; @@ -116,10 +121,10 @@ void MenuBuilder::Draw() { for (const auto& menu : menus_) { // Don't add icons to top-level menus as they get cut off std::string menu_label = menu.label; - + if (ImGui::BeginMenu(menu_label.c_str())) { submenu_stack_.clear(); // Reset submenu stack for each top-level menu - skip_depth_ = 0; // Reset skip depth + skip_depth_ = 0; // Reset skip depth for (const auto& item : menu.items) { DrawMenuItem(item); } @@ -135,19 +140,19 @@ void MenuBuilder::DrawMenuItem(const MenuItem& item) { ImGui::Separator(); } break; - + case MenuItem::Type::kDisabled: { if (skip_depth_ == 0) { std::string label = item.icon.empty() - ? item.label - : absl::StrCat(item.icon, " ", item.label); + ? item.label + : absl::StrCat(item.icon, " ", item.label); ImGui::BeginDisabled(); ImGui::MenuItem(label.c_str(), nullptr, false, false); ImGui::EndDisabled(); } break; } - + case MenuItem::Type::kSubMenuBegin: { // If we're already skipping, increment skip depth and continue if (skip_depth_ > 0) { @@ -155,14 +160,14 @@ void MenuBuilder::DrawMenuItem(const MenuItem& item) { submenu_stack_.push_back(false); break; } - + std::string label = item.icon.empty() - ? item.label - : absl::StrCat(item.icon, " ", item.label); - + ? item.label + : absl::StrCat(item.icon, " ", item.label); + bool enabled = !item.enabled || item.enabled(); bool opened = false; - + if (!enabled) { // Disabled submenu - show as disabled item but don't open ImGui::BeginDisabled(); @@ -180,13 +185,13 @@ void MenuBuilder::DrawMenuItem(const MenuItem& item) { } break; } - + case MenuItem::Type::kSubMenuEnd: // Decrement skip depth if we were skipping if (skip_depth_ > 0) { skip_depth_--; } - + // Pop the stack and call EndMenu only if submenu was opened if (!submenu_stack_.empty()) { bool was_opened = submenu_stack_.back(); @@ -196,23 +201,22 @@ void MenuBuilder::DrawMenuItem(const MenuItem& item) { } } break; - + case MenuItem::Type::kItem: { if (skip_depth_ > 0) { break; // Skip items in closed submenus } - + std::string label = item.icon.empty() - ? item.label - : absl::StrCat(item.icon, " ", item.label); - + ? item.label + : absl::StrCat(item.icon, " ", item.label); + bool enabled = !item.enabled || item.enabled(); bool checked = item.checked && item.checked(); - - const char* shortcut_str = item.shortcut.empty() - ? nullptr - : item.shortcut.c_str(); - + + const char* shortcut_str = + item.shortcut.empty() ? nullptr : item.shortcut.c_str(); + if (ImGui::MenuItem(label.c_str(), shortcut_str, checked, enabled)) { if (item.callback) { item.callback(); diff --git a/src/app/editor/ui/menu_builder.h b/src/app/editor/ui/menu_builder.h index 6592ad2a..6b2739c8 100644 --- a/src/app/editor/ui/menu_builder.h +++ b/src/app/editor/ui/menu_builder.h @@ -35,25 +35,25 @@ class MenuBuilder { public: using Callback = std::function; using EnabledCheck = std::function; - + MenuBuilder() = default; - + /** * @brief Begin a top-level menu */ MenuBuilder& BeginMenu(const char* label, const char* icon = nullptr); - + /** * @brief Begin a submenu */ MenuBuilder& BeginSubMenu(const char* label, const char* icon = nullptr, EnabledCheck enabled = nullptr); - + /** * @brief End the current menu/submenu */ MenuBuilder& EndMenu(); - + /** * @brief Add a menu item */ @@ -61,34 +61,34 @@ class MenuBuilder { const char* shortcut = nullptr, EnabledCheck enabled = nullptr, EnabledCheck checked = nullptr); - + /** * @brief Add a menu item without icon (convenience) */ MenuBuilder& Item(const char* label, Callback callback, const char* shortcut = nullptr, EnabledCheck enabled = nullptr); - + /** * @brief Add a separator */ MenuBuilder& Separator(); - + /** * @brief Add a disabled item (grayed out) */ MenuBuilder& DisabledItem(const char* label, const char* icon = nullptr); - + /** * @brief Draw the menu bar (call in main menu bar) */ void Draw(); - + /** * @brief Clear all menus */ void Clear(); - + private: struct MenuItem { enum class Type { @@ -98,7 +98,7 @@ class MenuBuilder { kSeparator, kDisabled }; - + Type type; std::string label; std::string icon; @@ -107,20 +107,21 @@ class MenuBuilder { EnabledCheck enabled; EnabledCheck checked; }; - + struct Menu { std::string label; std::string icon; std::vector items; }; - + std::vector menus_; Menu* current_menu_ = nullptr; - + // Track which submenus are actually open during drawing mutable std::vector submenu_stack_; - mutable int skip_depth_ = 0; // Track nesting depth when skipping closed submenus - + mutable int skip_depth_ = + 0; // Track nesting depth when skipping closed submenus + void DrawMenuItem(const MenuItem& item); }; diff --git a/src/app/editor/ui/ui_coordinator.cc b/src/app/editor/ui/ui_coordinator.cc index 7fd648fc..052d22ea 100644 --- a/src/app/editor/ui/ui_coordinator.cc +++ b/src/app/editor/ui/ui_coordinator.cc @@ -7,7 +7,6 @@ #include #include "absl/strings/str_format.h" -#include "core/project.h" #include "app/editor/editor.h" #include "app/editor/editor_manager.h" #include "app/editor/system/editor_registry.h" @@ -22,6 +21,7 @@ #include "app/gui/core/layout_helpers.h" #include "app/gui/core/style.h" #include "app/gui/core/theme_manager.h" +#include "core/project.h" #include "imgui/imgui.h" #include "util/file_util.h" @@ -29,16 +29,11 @@ namespace yaze { namespace editor { UICoordinator::UICoordinator( - EditorManager* editor_manager, - RomFileManager& rom_manager, - ProjectManager& project_manager, - EditorRegistry& editor_registry, - EditorCardRegistry& card_registry, - SessionCoordinator& session_coordinator, - WindowDelegate& window_delegate, - ToastManager& toast_manager, - PopupManager& popup_manager, - ShortcutManager& shortcut_manager) + EditorManager* editor_manager, RomFileManager& rom_manager, + ProjectManager& project_manager, EditorRegistry& editor_registry, + EditorCardRegistry& card_registry, SessionCoordinator& session_coordinator, + WindowDelegate& window_delegate, ToastManager& toast_manager, + PopupManager& popup_manager, ShortcutManager& shortcut_manager) : editor_manager_(editor_manager), rom_manager_(rom_manager), project_manager_(project_manager), @@ -49,10 +44,10 @@ UICoordinator::UICoordinator( toast_manager_(toast_manager), popup_manager_(popup_manager), shortcut_manager_(shortcut_manager) { - + // Initialize welcome screen with proper callbacks welcome_screen_ = std::make_unique(); - + // Wire welcome screen callbacks to EditorManager welcome_screen_->SetOpenRomCallback([this]() { if (editor_manager_) { @@ -68,7 +63,7 @@ UICoordinator::UICoordinator( } } }); - + welcome_screen_->SetNewProjectCallback([this]() { if (editor_manager_) { auto status = editor_manager_->CreateNewProject(); @@ -83,7 +78,7 @@ UICoordinator::UICoordinator( } } }); - + welcome_screen_->SetOpenProjectCallback([this](const std::string& filepath) { if (editor_manager_) { auto status = editor_manager_->OpenRomOrProject(filepath); @@ -103,7 +98,7 @@ UICoordinator::UICoordinator( void UICoordinator::DrawAllUI() { // Note: Theme styling is applied by ThemeManager, not here // This is called from EditorManager::Update() - don't call menu bar stuff here - + // Draw UI windows and dialogs // Session dialogs are drawn by SessionCoordinator separately to avoid duplication DrawCommandPalette(); // Ctrl+Shift+P @@ -122,10 +117,13 @@ void UICoordinator::DrawRomSelector() { ImGui::SetNextItemWidth(ImGui::GetWindowWidth() / 6); if (ImGui::BeginCombo("##ROMSelector", current_rom->short_name().c_str())) { for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount(); ++i) { - if (session_coordinator_.IsSessionClosed(i)) continue; - - auto* session = static_cast(session_coordinator_.GetSession(i)); - if (!session) continue; + if (session_coordinator_.IsSessionClosed(i)) + continue; + + auto* session = + static_cast(session_coordinator_.GetSession(i)); + if (!session) + continue; Rom* rom = &session->rom; ImGui::PushID(static_cast(i)); @@ -152,28 +150,29 @@ void UICoordinator::DrawRomSelector() { void UICoordinator::DrawMenuBarExtras() { // Get current ROM from EditorManager (RomFileManager doesn't track "current") auto* current_rom = editor_manager_->GetCurrentRom(); - + // Calculate version width for right alignment - std::string version_text = absl::StrFormat("v%s", editor_manager_->version().c_str()); + std::string version_text = + absl::StrFormat("v%s", editor_manager_->version().c_str()); float version_width = ImGui::CalcTextSize(version_text.c_str()).x; // Session indicator with Material Design styling if (session_coordinator_.HasMultipleSessions()) { ImGui::SameLine(); - std::string session_button_text = absl::StrFormat("%s %zu", ICON_MD_TAB, - session_coordinator_.GetActiveSessionCount()); - + std::string session_button_text = absl::StrFormat( + "%s %zu", ICON_MD_TAB, session_coordinator_.GetActiveSessionCount()); + // Material Design button styling ImGui::PushStyleColor(ImGuiCol_Button, gui::GetPrimaryVec4()); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gui::GetPrimaryHoverVec4()); ImGui::PushStyleColor(ImGuiCol_ButtonActive, gui::GetPrimaryActiveVec4()); - + if (ImGui::SmallButton(session_button_text.c_str())) { session_coordinator_.ToggleSessionSwitcher(); } - + ImGui::PopStyleColor(3); - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Switch Sessions (Ctrl+Tab)"); } @@ -210,36 +209,40 @@ void UICoordinator::DrawContextSensitiveCardControl() { // Get the currently active editor directly from EditorManager // This ensures we show cards for the correct editor that has focus auto* active_editor = editor_manager_->GetCurrentEditor(); - if (!active_editor) return; - + if (!active_editor) + return; + // Only show card control for card-based editors (not palette, not assembly in legacy mode, etc.) if (!editor_registry_.IsCardBasedEditor(active_editor->type())) { return; } - + // Get the category and session for the active editor - std::string category = editor_registry_.GetEditorCategory(active_editor->type()); + std::string category = + editor_registry_.GetEditorCategory(active_editor->type()); size_t session_id = editor_manager_->GetCurrentSessionId(); - + // Draw compact card control in menu bar (mini dropdown for cards) ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()); - - if (ImGui::SmallButton(absl::StrFormat("%s %s", ICON_MD_LAYERS, category.c_str()).c_str())) { + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + gui::GetSurfaceContainerHighestVec4()); + + if (ImGui::SmallButton( + absl::StrFormat("%s %s", ICON_MD_LAYERS, category.c_str()).c_str())) { ImGui::OpenPopup("##CardQuickAccess"); } - + ImGui::PopStyleColor(2); - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Quick access to %s cards", category.c_str()); } - + // Quick access popup for toggling cards if (ImGui::BeginPopup("##CardQuickAccess")) { auto cards = card_registry_.GetCardsInCategory(session_id, category); - + for (const auto& card : cards) { bool visible = card.visibility_flag ? *card.visibility_flag : false; if (ImGui::MenuItem(card.display_name.c_str(), nullptr, visible)) { @@ -293,51 +296,52 @@ void UICoordinator::DrawWelcomeScreen() { // Auto-hide: When ROM is loaded // Manual control: Can be opened via Help > Welcome Screen menu // ============================================================================ - + if (!editor_manager_) { - LOG_ERROR("UICoordinator", "EditorManager is null - cannot check ROM state"); + LOG_ERROR("UICoordinator", + "EditorManager is null - cannot check ROM state"); return; } - + if (!welcome_screen_) { LOG_ERROR("UICoordinator", "WelcomeScreen object is null - cannot render"); return; } - + // Check ROM state auto* current_rom = editor_manager_->GetCurrentRom(); bool rom_is_loaded = current_rom && current_rom->is_loaded(); - + // SIMPLIFIED LOGIC: Auto-show when no ROM, auto-hide when ROM loads if (!rom_is_loaded && !welcome_screen_manually_closed_) { show_welcome_screen_ = true; } - + if (rom_is_loaded && !welcome_screen_manually_closed_) { show_welcome_screen_ = false; } - + // Don't show if flag is false if (!show_welcome_screen_) { return; } - + // Reset first show flag to override ImGui ini state welcome_screen_->ResetFirstShow(); - + // Update recent projects before showing welcome_screen_->RefreshRecentProjects(); - + // Show the welcome screen window bool is_open = true; welcome_screen_->Show(&is_open); - + // If user closed it via X button, respect that if (!is_open) { show_welcome_screen_ = false; welcome_screen_manually_closed_ = true; } - + // If an action was taken (ROM loaded, project opened), the welcome screen will auto-hide // next frame when rom_is_loaded becomes true } @@ -377,15 +381,15 @@ void UICoordinator::DrawWorkspacePresetDialogs() { editor_manager_->RefreshWorkspacePresets(); if (auto* workspace_manager = editor_manager_->workspace_manager()) { - for (const auto& name : workspace_manager->workspace_presets()) { - if (ImGui::Selectable(name.c_str())) { - editor_manager_->LoadWorkspacePreset(name); - toast_manager_.Show("Preset loaded", editor::ToastType::kSuccess); - show_load_workspace_preset_ = false; - } + for (const auto& name : workspace_manager->workspace_presets()) { + if (ImGui::Selectable(name.c_str())) { + editor_manager_->LoadWorkspacePreset(name); + toast_manager_.Show("Preset loaded", editor::ToastType::kSuccess); + show_load_workspace_preset_ = false; } - if (workspace_manager->workspace_presets().empty()) - ImGui::Text("No presets found"); + } + if (workspace_manager->workspace_presets().empty()) + ImGui::Text("No presets found"); } ImGui::End(); } @@ -416,14 +420,17 @@ void UICoordinator::ShowDisplaySettings() { } void UICoordinator::HideCurrentEditorCards() { - if (!editor_manager_) return; - + if (!editor_manager_) + return; + auto* current_editor = editor_manager_->GetCurrentEditor(); - if (!current_editor) return; - - std::string category = editor_registry_.GetEditorCategory(current_editor->type()); + if (!current_editor) + return; + + std::string category = + editor_registry_.GetEditorCategory(current_editor->type()); card_registry_.HideAllCardsInCategory(category); - + LOG_INFO("UICoordinator", "Hid all cards in category: %s", category.c_str()); } @@ -449,21 +456,25 @@ void UICoordinator::DrawSessionBadges() { } // Material Design component helpers -void UICoordinator::DrawMaterialButton(const std::string& text, const std::string& icon, - const ImVec4& color, std::function callback, - bool enabled) { +void UICoordinator::DrawMaterialButton(const std::string& text, + const std::string& icon, + const ImVec4& color, + std::function callback, + bool enabled) { if (!enabled) { - ImGui::PushStyleColor(ImGuiCol_Button, gui::GetSurfaceContainerHighestVec4()); + ImGui::PushStyleColor(ImGuiCol_Button, + gui::GetSurfaceContainerHighestVec4()); ImGui::PushStyleColor(ImGuiCol_Text, gui::GetOnSurfaceVariantVec4()); } - - std::string button_text = absl::StrFormat("%s %s", icon.c_str(), text.c_str()); + + std::string button_text = + absl::StrFormat("%s %s", icon.c_str(), text.c_str()); if (ImGui::Button(button_text.c_str())) { if (enabled && callback) { callback(); } } - + if (!enabled) { ImGui::PopStyleColor(2); } @@ -471,33 +482,49 @@ void UICoordinator::DrawMaterialButton(const std::string& text, const std::strin // Layout and positioning helpers void UICoordinator::CenterWindow(const std::string& window_name) { - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); } -void UICoordinator::PositionWindow(const std::string& window_name, float x, float y) { +void UICoordinator::PositionWindow(const std::string& window_name, float x, + float y) { ImGui::SetNextWindowPos(ImVec2(x, y), ImGuiCond_Appearing); } -void UICoordinator::SetWindowSize(const std::string& window_name, float width, float height) { +void UICoordinator::SetWindowSize(const std::string& window_name, float width, + float height) { ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_FirstUseEver); } // Icon and theming helpers std::string UICoordinator::GetIconForEditor(EditorType type) const { switch (type) { - case EditorType::kDungeon: return ICON_MD_CASTLE; - case EditorType::kOverworld: return ICON_MD_MAP; - case EditorType::kGraphics: return ICON_MD_IMAGE; - case EditorType::kPalette: return ICON_MD_PALETTE; - case EditorType::kSprite: return ICON_MD_TOYS; - case EditorType::kScreen: return ICON_MD_TV; - case EditorType::kMessage: return ICON_MD_CHAT_BUBBLE; - case EditorType::kMusic: return ICON_MD_MUSIC_NOTE; - case EditorType::kAssembly: return ICON_MD_CODE; - case EditorType::kHex: return ICON_MD_DATA_ARRAY; - case EditorType::kEmulator: return ICON_MD_PLAY_ARROW; - case EditorType::kSettings: return ICON_MD_SETTINGS; - default: return ICON_MD_HELP; + case EditorType::kDungeon: + return ICON_MD_CASTLE; + case EditorType::kOverworld: + return ICON_MD_MAP; + case EditorType::kGraphics: + return ICON_MD_IMAGE; + case EditorType::kPalette: + return ICON_MD_PALETTE; + case EditorType::kSprite: + return ICON_MD_TOYS; + case EditorType::kScreen: + return ICON_MD_TV; + case EditorType::kMessage: + return ICON_MD_CHAT_BUBBLE; + case EditorType::kMusic: + return ICON_MD_MUSIC_NOTE; + case EditorType::kAssembly: + return ICON_MD_CODE; + case EditorType::kHex: + return ICON_MD_DATA_ARRAY; + case EditorType::kEmulator: + return ICON_MD_PLAY_ARROW; + case EditorType::kSettings: + return ICON_MD_SETTINGS; + default: + return ICON_MD_HELP; } } @@ -513,51 +540,58 @@ void UICoordinator::ApplyEditorTheme(EditorType type) { } void UICoordinator::DrawCommandPalette() { - if (!show_command_palette_) return; - + if (!show_command_palette_) + return; + using namespace ImGui; auto& theme = gui::ThemeManager::Get().GetCurrentTheme(); - - SetNextWindowPos(GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + SetNextWindowPos(GetMainViewport()->GetCenter(), ImGuiCond_Appearing, + ImVec2(0.5f, 0.5f)); SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - + bool show_palette = true; if (Begin(absl::StrFormat("%s Command Palette", ICON_MD_SEARCH).c_str(), &show_palette, ImGuiWindowFlags_NoCollapse)) { - + // Search input with focus management SetNextItemWidth(-100); if (IsWindowAppearing()) { SetKeyboardFocusHere(); command_palette_selected_idx_ = 0; } - + bool input_changed = InputTextWithHint( "##cmd_query", - absl::StrFormat("%s Search commands (fuzzy matching enabled)...", ICON_MD_SEARCH).c_str(), + absl::StrFormat("%s Search commands (fuzzy matching enabled)...", + ICON_MD_SEARCH) + .c_str(), command_palette_query_, IM_ARRAYSIZE(command_palette_query_)); - + SameLine(); if (Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) { command_palette_query_[0] = '\0'; input_changed = true; command_palette_selected_idx_ = 0; } - + Separator(); - + // Fuzzy filter commands with scoring - std::vector>> scored_commands; + std::vector>> + scored_commands; std::string query_lower = command_palette_query_; - std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(), ::tolower); - + std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(), + ::tolower); + for (const auto& entry : shortcut_manager_.GetShortcuts()) { const auto& name = entry.first; const auto& shortcut = entry.second; - + std::string name_lower = name; - std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); - + std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(), + ::tolower); + int score = 0; if (command_palette_query_[0] == '\0') { score = 1; // Show all when no query @@ -568,50 +602,58 @@ void UICoordinator::DrawCommandPalette() { } else { // Fuzzy match - characters in order size_t text_idx = 0, query_idx = 0; - while (text_idx < name_lower.length() && query_idx < query_lower.length()) { + while (text_idx < name_lower.length() && + query_idx < query_lower.length()) { if (name_lower[text_idx] == query_lower[query_idx]) { score += 10; query_idx++; } text_idx++; } - if (query_idx != query_lower.length()) score = 0; + if (query_idx != query_lower.length()) + score = 0; } - + if (score > 0) { - std::string shortcut_text = shortcut.keys.empty() - ? "" - : absl::StrFormat("(%s)", PrintShortcut(shortcut.keys).c_str()); + std::string shortcut_text = + shortcut.keys.empty() + ? "" + : absl::StrFormat("(%s)", PrintShortcut(shortcut.keys).c_str()); scored_commands.push_back({score, {name, shortcut_text}}); } } - + std::sort(scored_commands.begin(), scored_commands.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); - + // Display results with categories if (BeginTabBar("CommandCategories")) { - if (BeginTabItem(absl::StrFormat("%s All Commands", ICON_MD_LIST).c_str())) { - if (gui::LayoutHelpers::BeginTableWithTheming("CommandPaletteTable", 3, - ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp, - ImVec2(0, -30))) { - + if (BeginTabItem( + absl::StrFormat("%s All Commands", ICON_MD_LIST).c_str())) { + if (gui::LayoutHelpers::BeginTableWithTheming( + "CommandPaletteTable", 3, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingStretchProp, + ImVec2(0, -30))) { + TableSetupColumn("Command", ImGuiTableColumnFlags_WidthStretch, 0.5f); - TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthStretch, 0.3f); + TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthStretch, + 0.3f); TableSetupColumn("Score", ImGuiTableColumnFlags_WidthStretch, 0.2f); TableHeadersRow(); - + for (size_t i = 0; i < scored_commands.size(); ++i) { const auto& [score, cmd_pair] = scored_commands[i]; const auto& [command_name, shortcut_text] = cmd_pair; - + TableNextRow(); TableNextColumn(); - + PushID(static_cast(i)); - bool is_selected = (static_cast(i) == command_palette_selected_idx_); + bool is_selected = + (static_cast(i) == command_palette_selected_idx_); if (Selectable(command_name.c_str(), is_selected, - ImGuiSelectableFlags_SpanAllColumns)) { + ImGuiSelectableFlags_SpanAllColumns)) { command_palette_selected_idx_ = i; const auto& shortcuts = shortcut_manager_.GetShortcuts(); auto it = shortcuts.find(command_name); @@ -621,48 +663,52 @@ void UICoordinator::DrawCommandPalette() { } } PopID(); - + TableNextColumn(); - PushStyleColor(ImGuiCol_Text, gui::ConvertColorToImVec4(theme.text_secondary)); + PushStyleColor(ImGuiCol_Text, + gui::ConvertColorToImVec4(theme.text_secondary)); Text("%s", shortcut_text.c_str()); PopStyleColor(); - + TableNextColumn(); if (score > 0) { - PushStyleColor(ImGuiCol_Text, gui::ConvertColorToImVec4(theme.text_disabled)); + PushStyleColor(ImGuiCol_Text, + gui::ConvertColorToImVec4(theme.text_disabled)); Text("%d", score); PopStyleColor(); } } - + gui::LayoutHelpers::EndTableWithTheming(); } EndTabItem(); } - + if (BeginTabItem(absl::StrFormat("%s Recent", ICON_MD_HISTORY).c_str())) { Text("Recent commands coming soon..."); EndTabItem(); } - + if (BeginTabItem(absl::StrFormat("%s Frequent", ICON_MD_STAR).c_str())) { Text("Frequent commands coming soon..."); EndTabItem(); } - + EndTabBar(); } - + // Status bar with tips Separator(); - Text("%s %zu commands | Score: fuzzy match", ICON_MD_INFO, scored_commands.size()); + Text("%s %zu commands | Score: fuzzy match", ICON_MD_INFO, + scored_commands.size()); SameLine(); - PushStyleColor(ImGuiCol_Text, gui::ConvertColorToImVec4(theme.text_disabled)); + PushStyleColor(ImGuiCol_Text, + gui::ConvertColorToImVec4(theme.text_disabled)); Text("| ↑↓=Navigate | Enter=Execute | Esc=Close"); PopStyleColor(); } End(); - + // Update visibility state if (!show_palette) { show_command_palette_ = false; @@ -670,17 +716,18 @@ void UICoordinator::DrawCommandPalette() { } void UICoordinator::DrawGlobalSearch() { - if (!show_global_search_) return; - + if (!show_global_search_) + return; + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - + bool show_search = true; if (ImGui::Begin( absl::StrFormat("%s Global Search", ICON_MD_MANAGE_SEARCH).c_str(), - &show_search, ImGuiWindowFlags_NoCollapse)) { - + &show_search, ImGuiWindowFlags_NoCollapse)) { + // Enhanced search input with focus management ImGui::SetNextItemWidth(-100); if (ImGui::IsWindowAppearing()) { @@ -691,13 +738,13 @@ void UICoordinator::DrawGlobalSearch() { "##global_query", absl::StrFormat("%s Search everything...", ICON_MD_SEARCH).c_str(), global_search_query_, IM_ARRAYSIZE(global_search_query_)); - + ImGui::SameLine(); if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) { global_search_query_[0] = '\0'; input_changed = true; } - + ImGui::Separator(); // Tabbed search results for better organization @@ -710,8 +757,7 @@ void UICoordinator::DrawGlobalSearch() { auto recent_files = manager.GetRecentFiles(); if (ImGui::BeginTable("RecentFilesTable", 3, - ImGuiTableFlags_ScrollY | - ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch, @@ -723,7 +769,8 @@ void UICoordinator::DrawGlobalSearch() { ImGui::TableHeadersRow(); for (const auto& file : recent_files) { - if (global_search_query_[0] != '\0' && file.find(global_search_query_) == std::string::npos) + if (global_search_query_[0] != '\0' && + file.find(global_search_query_) == std::string::npos) continue; ImGui::TableNextRow(); @@ -747,7 +794,9 @@ void UICoordinator::DrawGlobalSearch() { if (ImGui::Button("Open")) { auto status = editor_manager_->OpenRomOrProject(file); if (!status.ok()) { - toast_manager_.Show(absl::StrCat("Failed to open: ", status.message()), ToastType::kError); + toast_manager_.Show( + absl::StrCat("Failed to open: ", status.message()), + ToastType::kError); } SetGlobalSearchVisible(false); } @@ -773,10 +822,10 @@ void UICoordinator::DrawGlobalSearch() { ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 100.0f); - ImGui::TableSetupColumn("Label", - ImGuiTableColumnFlags_WidthStretch, 0.4f); - ImGui::TableSetupColumn("Value", - ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthStretch, + 0.4f); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, + 0.6f); ImGui::TableHeadersRow(); for (const auto& type_pair : labels) { @@ -792,7 +841,7 @@ void UICoordinator::DrawGlobalSearch() { ImGui::TableNextColumn(); if (ImGui::Selectable(kv.first.c_str(), false, - ImGuiSelectableFlags_SpanAllColumns)) { + ImGuiSelectableFlags_SpanAllColumns)) { // Future: navigate to related editor/location } @@ -813,25 +862,28 @@ void UICoordinator::DrawGlobalSearch() { absl::StrFormat("%s Sessions", ICON_MD_TAB).c_str())) { ImGui::Text("Search and switch between active sessions:"); - for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount(); ++i) { - std::string session_info = session_coordinator_.GetSessionDisplayName(i); + for (size_t i = 0; i < session_coordinator_.GetTotalSessionCount(); + ++i) { + std::string session_info = + session_coordinator_.GetSessionDisplayName(i); if (session_info == "[CLOSED SESSION]") - continue; + continue; if (global_search_query_[0] != '\0' && session_info.find(global_search_query_) == std::string::npos) continue; - bool is_current = (i == session_coordinator_.GetActiveSessionIndex()); + bool is_current = + (i == session_coordinator_.GetActiveSessionIndex()); if (is_current) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f)); } if (ImGui::Selectable(absl::StrFormat("%s %s %s", ICON_MD_TAB, - session_info.c_str(), - is_current ? "(Current)" : "") - .c_str())) { + session_info.c_str(), + is_current ? "(Current)" : "") + .c_str())) { if (!is_current) { editor_manager_->SwitchToSession(i); SetGlobalSearchVisible(false); @@ -848,13 +900,13 @@ void UICoordinator::DrawGlobalSearch() { ImGui::EndTabBar(); } - + // Status bar ImGui::Separator(); ImGui::Text("%s Global search across all YAZE data", ICON_MD_INFO); } ImGui::End(); - + // Update visibility state if (!show_search) { SetGlobalSearchVisible(false); @@ -863,4 +915,3 @@ void UICoordinator::DrawGlobalSearch() { } // namespace editor } // namespace yaze - diff --git a/src/app/editor/ui/ui_coordinator.h b/src/app/editor/ui/ui_coordinator.h index 640de8d9..62e4be6f 100644 --- a/src/app/editor/ui/ui_coordinator.h +++ b/src/app/editor/ui/ui_coordinator.h @@ -1,8 +1,8 @@ #ifndef YAZE_APP_EDITOR_UI_UI_COORDINATOR_H_ #define YAZE_APP_EDITOR_UI_UI_COORDINATOR_H_ -#include #include +#include #include "absl/status/status.h" #include "app/editor/editor.h" @@ -41,18 +41,15 @@ class ShortcutManager; class UICoordinator { public: // Constructor takes references to the managers it coordinates with - UICoordinator(EditorManager* editor_manager, - RomFileManager& rom_manager, + UICoordinator(EditorManager* editor_manager, RomFileManager& rom_manager, ProjectManager& project_manager, EditorRegistry& editor_registry, EditorCardRegistry& card_registry, SessionCoordinator& session_coordinator, - WindowDelegate& window_delegate, - ToastManager& toast_manager, - PopupManager& popup_manager, - ShortcutManager& shortcut_manager); + WindowDelegate& window_delegate, ToastManager& toast_manager, + PopupManager& popup_manager, ShortcutManager& shortcut_manager); ~UICoordinator() = default; - + // Non-copyable due to reference members UICoordinator(const UICoordinator&) = delete; UICoordinator& operator=(const UICoordinator&) = delete; @@ -61,31 +58,31 @@ class UICoordinator { void DrawAllUI(); void DrawMenuBarExtras(); void DrawContextSensitiveCardControl(); - + // Core UI components (actual ImGui rendering moved from EditorManager) void DrawCommandPalette(); void DrawGlobalSearch(); void DrawWorkspacePresetDialogs(); - + // Session UI components void DrawSessionSwitcher(); void DrawSessionManager(); void DrawSessionRenameDialog(); void DrawLayoutPresets(); - + // Welcome screen and project UI void DrawWelcomeScreen(); void DrawProjectHelp(); - + // Window management UI void DrawWindowManagementUI(); void DrawRomSelector(); - + // Popup and dialog management void DrawAllPopups(); void ShowPopup(const std::string& popup_name); void HidePopup(const std::string& popup_name); - + // UI state management void ShowEditorSelection() { show_editor_selection_ = true; } void ShowDisplaySettings(); @@ -98,20 +95,24 @@ class UICoordinator { void ShowGlobalSearch() { show_global_search_ = true; } void ShowCommandPalette() { show_command_palette_ = true; } void ShowCardBrowser() { show_card_browser_ = true; } - + // Window visibility management void ShowAllWindows(); void HideAllWindows(); - + // UI state queries (EditorManager can check these) bool IsEditorSelectionVisible() const { return show_editor_selection_; } bool IsDisplaySettingsVisible() const { return show_display_settings_; } // Session switcher visibility managed by SessionCoordinator bool IsSessionSwitcherVisible() const; bool IsWelcomeScreenVisible() const { return show_welcome_screen_; } - bool IsWelcomeScreenManuallyClosed() const { return welcome_screen_manually_closed_; } + bool IsWelcomeScreenManuallyClosed() const { + return welcome_screen_manually_closed_; + } bool IsGlobalSearchVisible() const { return show_global_search_; } - bool IsPerformanceDashboardVisible() const { return show_performance_dashboard_; } + bool IsPerformanceDashboardVisible() const { + return show_performance_dashboard_; + } bool IsCardBrowserVisible() const { return show_card_browser_; } bool IsCommandPaletteVisible() const { return show_command_palette_; } bool IsCardSidebarVisible() const { return show_card_sidebar_; } @@ -121,19 +122,31 @@ class UICoordinator { bool IsMemoryEditorVisible() const { return show_memory_editor_; } bool IsAsmEditorVisible() const { return show_asm_editor_; } bool IsPaletteEditorVisible() const { return show_palette_editor_; } - bool IsResourceLabelManagerVisible() const { return show_resource_label_manager_; } - + bool IsResourceLabelManagerVisible() const { + return show_resource_label_manager_; + } + // UI state setters (for programmatic control) - void SetEditorSelectionVisible(bool visible) { show_editor_selection_ = visible; } - void SetDisplaySettingsVisible(bool visible) { show_display_settings_ = visible; } + void SetEditorSelectionVisible(bool visible) { + show_editor_selection_ = visible; + } + void SetDisplaySettingsVisible(bool visible) { + show_display_settings_ = visible; + } // Session switcher state managed by SessionCoordinator void SetSessionSwitcherVisible(bool visible); void SetWelcomeScreenVisible(bool visible) { show_welcome_screen_ = visible; } - void SetWelcomeScreenManuallyClosed(bool closed) { welcome_screen_manually_closed_ = closed; } + void SetWelcomeScreenManuallyClosed(bool closed) { + welcome_screen_manually_closed_ = closed; + } void SetGlobalSearchVisible(bool visible) { show_global_search_ = visible; } - void SetPerformanceDashboardVisible(bool visible) { show_performance_dashboard_ = visible; } + void SetPerformanceDashboardVisible(bool visible) { + show_performance_dashboard_ = visible; + } void SetCardBrowserVisible(bool visible) { show_card_browser_ = visible; } - void SetCommandPaletteVisible(bool visible) { show_command_palette_ = visible; } + void SetCommandPaletteVisible(bool visible) { + show_command_palette_ = visible; + } void SetCardSidebarVisible(bool visible) { show_card_sidebar_ = visible; } void SetImGuiDemoVisible(bool visible) { show_imgui_demo_ = visible; } void SetImGuiMetricsVisible(bool visible) { show_imgui_metrics_ = visible; } @@ -141,8 +154,10 @@ class UICoordinator { void SetMemoryEditorVisible(bool visible) { show_memory_editor_ = visible; } void SetAsmEditorVisible(bool visible) { show_asm_editor_ = visible; } void SetPaletteEditorVisible(bool visible) { show_palette_editor_ = visible; } - void SetResourceLabelManagerVisible(bool visible) { show_resource_label_manager_ = visible; } - + void SetResourceLabelManagerVisible(bool visible) { + show_resource_label_manager_ = visible; + } + // Note: Theme styling is handled by ThemeManager, not UICoordinator private: @@ -157,7 +172,7 @@ class UICoordinator { ToastManager& toast_manager_; PopupManager& popup_manager_; ShortcutManager& shortcut_manager_; - + // UI state flags (UICoordinator owns all UI visibility state) bool show_editor_selection_ = false; bool show_display_settings_ = false; @@ -179,37 +194,36 @@ class UICoordinator { bool show_save_workspace_preset_ = false; bool show_load_workspace_preset_ = false; bool show_card_sidebar_ = true; // Show sidebar by default - + // Command Palette state char command_palette_query_[256] = {}; int command_palette_selected_idx_ = 0; - + // Global Search state char global_search_query_[256] = {}; - + // Welcome screen component std::unique_ptr welcome_screen_; - + // Helper methods for drawing operations void DrawSessionIndicator(); void DrawSessionTabs(); void DrawSessionBadges(); - + // Material Design component helpers void DrawMaterialButton(const std::string& text, const std::string& icon, - const ImVec4& color, std::function callback, - bool enabled = true); - + const ImVec4& color, std::function callback, + bool enabled = true); + // Layout and positioning helpers void CenterWindow(const std::string& window_name); void PositionWindow(const std::string& window_name, float x, float y); void SetWindowSize(const std::string& window_name, float width, float height); - + // Icon and theming helpers std::string GetIconForEditor(EditorType type) const; std::string GetColorForEditor(EditorType type) const; void ApplyEditorTheme(EditorType type); - }; } // namespace editor diff --git a/src/app/editor/ui/welcome_screen.cc b/src/app/editor/ui/welcome_screen.cc index e624799c..6c727475 100644 --- a/src/app/editor/ui/welcome_screen.cc +++ b/src/app/editor/ui/welcome_screen.cc @@ -9,10 +9,10 @@ #include "absl/strings/str_format.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "core/project.h" -#include "app/platform/timing.h" #include "app/gui/core/icons.h" #include "app/gui/core/theme_manager.h" +#include "app/platform/timing.h" +#include "core/project.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" #include "util/file_util.h" @@ -30,7 +30,7 @@ namespace { ImVec4 GetThemedColor(const char* color_name, const ImVec4& fallback) { auto& theme_mgr = gui::ThemeManager::Get(); const auto& theme = theme_mgr.GetCurrentTheme(); - + // TODO: Fix this // Map color names to theme colors // if (strcmp(color_name, "triforce_gold") == 0) { @@ -46,7 +46,7 @@ ImVec4 GetThemedColor(const char* color_name, const ImVec4& fallback) { // } else if (strcmp(color_name, "spirit_orange") == 0) { // return theme.warning.to_im_vec4(); // } - + return fallback; } @@ -68,13 +68,14 @@ ImVec4 kHeartRed = kHeartRedFallback; ImVec4 kSpiritOrange = kSpiritOrangeFallback; ImVec4 kShadowPurple = kShadowPurpleFallback; -std::string GetRelativeTimeString(const std::filesystem::file_time_type& ftime) { +std::string GetRelativeTimeString( + const std::filesystem::file_time_type& ftime) { auto sctp = std::chrono::time_point_cast( ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()); auto now = std::chrono::system_clock::now(); auto diff = std::chrono::duration_cast(now - sctp); - + int hours = diff.count(); if (hours < 24) { return "Today"; @@ -93,38 +94,44 @@ std::string GetRelativeTimeString(const std::filesystem::file_time_type& ftime) } // Draw a pixelated triforce in the background (ALTTP style) -void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size, float alpha, float glow) { +void DrawTriforceBackground(ImDrawList* draw_list, ImVec2 pos, float size, + float alpha, float glow) { // Make it pixelated - round size to nearest 4 pixels size = std::round(size / 4.0f) * 4.0f; - + // Calculate triangle points with pixel-perfect positioning auto triangle = [&](ImVec2 center, float s, ImU32 color) { // Round to pixel boundaries for crisp edges float half_s = s / 2.0f; float tri_h = s * 0.866f; // Height of equilateral triangle - + // Fixed: Proper equilateral triangle with apex at top - ImVec2 p1(std::round(center.x), std::round(center.y - tri_h / 2.0f)); // Top apex - ImVec2 p2(std::round(center.x - half_s), std::round(center.y + tri_h / 2.0f)); // Bottom left - ImVec2 p3(std::round(center.x + half_s), std::round(center.y + tri_h / 2.0f)); // Bottom right - + ImVec2 p1(std::round(center.x), + std::round(center.y - tri_h / 2.0f)); // Top apex + ImVec2 p2(std::round(center.x - half_s), + std::round(center.y + tri_h / 2.0f)); // Bottom left + ImVec2 p3(std::round(center.x + half_s), + std::round(center.y + tri_h / 2.0f)); // Bottom right + draw_list->AddTriangleFilled(p1, p2, p3, color); }; - + ImU32 gold = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha)); - + // Proper triforce layout with three triangles float small_size = size / 2.0f; float small_height = small_size * 0.866f; - + // Top triangle (centered above) triangle(ImVec2(pos.x, pos.y), small_size, gold); - + // Bottom left triangle - triangle(ImVec2(pos.x - small_size / 2.0f, pos.y + small_height), small_size, gold); - + triangle(ImVec2(pos.x - small_size / 2.0f, pos.y + small_height), small_size, + gold); + // Bottom right triangle - triangle(ImVec2(pos.x + small_size / 2.0f, pos.y + small_height), small_size, gold); + triangle(ImVec2(pos.x + small_size / 2.0f, pos.y + small_height), small_size, + gold); } } // namespace @@ -137,29 +144,30 @@ bool WelcomeScreen::Show(bool* p_open) { // Update theme colors each frame kTriforceGold = GetThemedColor("triforce_gold", kTriforceGoldFallback); kHyruleGreen = GetThemedColor("hyrule_green", kHyruleGreenFallback); - kMasterSwordBlue = GetThemedColor("master_sword_blue", kMasterSwordBlueFallback); + kMasterSwordBlue = + GetThemedColor("master_sword_blue", kMasterSwordBlueFallback); kGanonPurple = GetThemedColor("ganon_purple", kGanonPurpleFallback); kHeartRed = GetThemedColor("heart_red", kHeartRedFallback); kSpiritOrange = GetThemedColor("spirit_orange", kSpiritOrangeFallback); - + UpdateAnimations(); - + // Get mouse position for interactive triforce movement ImVec2 mouse_pos = ImGui::GetMousePos(); - + bool action_taken = false; - + // Center the window with responsive size (80% of viewport, max 1400x900) ImGuiViewport* viewport = ImGui::GetMainViewport(); ImVec2 center = viewport->GetCenter(); ImVec2 viewport_size = viewport->Size; - + float width = std::min(viewport_size.x * 0.8f, 1400.0f); float height = std::min(viewport_size.y * 0.85f, 900.0f); - + ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always); - + // CRITICAL: Override ImGui's saved window state from imgui.ini // Without this, ImGui will restore the last saved state (hidden/collapsed) // even when our logic says the window should be visible @@ -168,18 +176,18 @@ bool WelcomeScreen::Show(bool* p_open) { ImGui::SetNextWindowFocus(); // Bring window to front first_show_attempt_ = false; } - - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove; - + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 20)); - + if (ImGui::Begin("##WelcomeScreen", p_open, window_flags)) { ImDrawList* bg_draw_list = ImGui::GetWindowDrawList(); ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_size = ImGui::GetWindowSize(); - + // Interactive scattered triforces (react to mouse position) struct TriforceConfig { float x_pct, y_pct; // Base position (percentage of window) @@ -187,16 +195,16 @@ bool WelcomeScreen::Show(bool* p_open) { float alpha; float repel_distance; // How far they move away from mouse }; - + TriforceConfig triforce_configs[] = { - {0.08f, 0.12f, 36.0f, 0.025f, 50.0f}, // Top left corner - {0.92f, 0.15f, 34.0f, 0.022f, 50.0f}, // Top right corner - {0.06f, 0.88f, 32.0f, 0.020f, 45.0f}, // Bottom left - {0.94f, 0.85f, 34.0f, 0.023f, 50.0f}, // Bottom right - {0.50f, 0.08f, 38.0f, 0.028f, 55.0f}, // Top center - {0.50f, 0.92f, 32.0f, 0.020f, 45.0f}, // Bottom center + {0.08f, 0.12f, 36.0f, 0.025f, 50.0f}, // Top left corner + {0.92f, 0.15f, 34.0f, 0.022f, 50.0f}, // Top right corner + {0.06f, 0.88f, 32.0f, 0.020f, 45.0f}, // Bottom left + {0.94f, 0.85f, 34.0f, 0.023f, 50.0f}, // Bottom right + {0.50f, 0.08f, 38.0f, 0.028f, 55.0f}, // Top center + {0.50f, 0.92f, 32.0f, 0.020f, 45.0f}, // Bottom center }; - + // Initialize base positions on first frame if (!triforce_positions_initialized_) { for (int i = 0; i < kNumTriforces; ++i) { @@ -207,83 +215,98 @@ bool WelcomeScreen::Show(bool* p_open) { } triforce_positions_initialized_ = true; } - + // Update triforce positions based on mouse interaction + floating animation for (int i = 0; i < kNumTriforces; ++i) { // Update base position in case window moved/resized float base_x = window_pos.x + window_size.x * triforce_configs[i].x_pct; float base_y = window_pos.y + window_size.y * triforce_configs[i].y_pct; triforce_base_positions_[i] = ImVec2(base_x, base_y); - + // Slow, subtle floating animation float time_offset = i * 1.2f; // Offset each triforce's animation - float float_speed_x = (0.15f + (i % 2) * 0.1f) * triforce_speed_multiplier_; // Very slow - float float_speed_y = (0.12f + ((i + 1) % 2) * 0.08f) * triforce_speed_multiplier_; - float float_amount_x = (20.0f + (i % 2) * 10.0f) * triforce_size_multiplier_; // Smaller amplitude - float float_amount_y = (25.0f + ((i + 1) % 2) * 15.0f) * triforce_size_multiplier_; - + float float_speed_x = + (0.15f + (i % 2) * 0.1f) * triforce_speed_multiplier_; // Very slow + float float_speed_y = + (0.12f + ((i + 1) % 2) * 0.08f) * triforce_speed_multiplier_; + float float_amount_x = (20.0f + (i % 2) * 10.0f) * + triforce_size_multiplier_; // Smaller amplitude + float float_amount_y = + (25.0f + ((i + 1) % 2) * 15.0f) * triforce_size_multiplier_; + // Create gentle orbital motion - float float_x = std::sin(animation_time_ * float_speed_x + time_offset) * float_amount_x; - float float_y = std::cos(animation_time_ * float_speed_y + time_offset * 1.2f) * float_amount_y; - + float float_x = std::sin(animation_time_ * float_speed_x + time_offset) * + float_amount_x; + float float_y = + std::cos(animation_time_ * float_speed_y + time_offset * 1.2f) * + float_amount_y; + // Calculate distance from mouse float dx = triforce_base_positions_[i].x - mouse_pos.x; float dy = triforce_base_positions_[i].y - mouse_pos.y; float dist = std::sqrt(dx * dx + dy * dy); - + // Calculate repulsion offset with stronger effect ImVec2 target_pos = triforce_base_positions_[i]; - float repel_radius = 200.0f; // Larger radius for more visible interaction - + float repel_radius = + 200.0f; // Larger radius for more visible interaction + // Add floating motion to base position target_pos.x += float_x; target_pos.y += float_y; - + // Apply mouse repulsion if enabled if (triforce_mouse_repel_enabled_ && dist < repel_radius && dist > 0.1f) { // Normalize direction away from mouse float dir_x = dx / dist; float dir_y = dy / dist; - + // Much stronger repulsion when closer with exponential falloff float normalized_dist = dist / repel_radius; - float repel_strength = (1.0f - normalized_dist * normalized_dist) * triforce_configs[i].repel_distance; - + float repel_strength = (1.0f - normalized_dist * normalized_dist) * + triforce_configs[i].repel_distance; + target_pos.x += dir_x * repel_strength; target_pos.y += dir_y * repel_strength; } - + // Smooth interpolation to target position (faster response) // Use TimingManager for accurate delta time float lerp_speed = 8.0f * yaze::TimingManager::Get().GetDeltaTime(); - triforce_positions_[i].x += (target_pos.x - triforce_positions_[i].x) * lerp_speed; - triforce_positions_[i].y += (target_pos.y - triforce_positions_[i].y) * lerp_speed; - + triforce_positions_[i].x += + (target_pos.x - triforce_positions_[i].x) * lerp_speed; + triforce_positions_[i].y += + (target_pos.y - triforce_positions_[i].y) * lerp_speed; + // Draw at current position with alpha multiplier - float adjusted_alpha = triforce_configs[i].alpha * triforce_alpha_multiplier_; - float adjusted_size = triforce_configs[i].size * triforce_size_multiplier_; - DrawTriforceBackground(bg_draw_list, triforce_positions_[i], - adjusted_size, adjusted_alpha, 0.0f); + float adjusted_alpha = + triforce_configs[i].alpha * triforce_alpha_multiplier_; + float adjusted_size = + triforce_configs[i].size * triforce_size_multiplier_; + DrawTriforceBackground(bg_draw_list, triforce_positions_[i], + adjusted_size, adjusted_alpha, 0.0f); } - + // Update and draw particle system if (particles_enabled_) { // Spawn new particles static float spawn_accumulator = 0.0f; spawn_accumulator += ImGui::GetIO().DeltaTime * particle_spawn_rate_; - while (spawn_accumulator >= 1.0f && active_particle_count_ < kMaxParticles) { + while (spawn_accumulator >= 1.0f && + active_particle_count_ < kMaxParticles) { // Find inactive particle slot for (int i = 0; i < kMaxParticles; ++i) { if (particles_[i].lifetime <= 0.0f) { // Spawn from random triforce int source_triforce = rand() % kNumTriforces; particles_[i].position = triforce_positions_[source_triforce]; - + // Random direction and speed float angle = (rand() % 360) * (M_PI / 180.0f); float speed = 20.0f + (rand() % 40); - particles_[i].velocity = ImVec2(std::cos(angle) * speed, std::sin(angle) * speed); - + particles_[i].velocity = + ImVec2(std::cos(angle) * speed, std::sin(angle) * speed); + particles_[i].size = 2.0f + (rand() % 4); particles_[i].alpha = 0.4f + (rand() % 40) / 100.0f; particles_[i].max_lifetime = 2.0f + (rand() % 30) / 10.0f; @@ -294,7 +317,7 @@ bool WelcomeScreen::Show(bool* p_open) { } spawn_accumulator -= 1.0f; } - + // Update and draw particles float dt = ImGui::GetIO().DeltaTime; for (int i = 0; i < kMaxParticles; ++i) { @@ -305,137 +328,145 @@ bool WelcomeScreen::Show(bool* p_open) { active_particle_count_--; continue; } - + // Update position particles_[i].position.x += particles_[i].velocity.x * dt; particles_[i].position.y += particles_[i].velocity.y * dt; - + // Fade out near end of life - float life_ratio = particles_[i].lifetime / particles_[i].max_lifetime; - float alpha = particles_[i].alpha * life_ratio * triforce_alpha_multiplier_; - + float life_ratio = + particles_[i].lifetime / particles_[i].max_lifetime; + float alpha = + particles_[i].alpha * life_ratio * triforce_alpha_multiplier_; + // Draw particle as small golden circle - ImU32 particle_color = ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha)); - bg_draw_list->AddCircleFilled(particles_[i].position, particles_[i].size, particle_color, 8); + ImU32 particle_color = + ImGui::GetColorU32(ImVec4(1.0f, 0.843f, 0.0f, alpha)); + bg_draw_list->AddCircleFilled(particles_[i].position, + particles_[i].size, particle_color, 8); } } } - + DrawHeader(); - + ImGui::Spacing(); ImGui::Spacing(); - + // Main content area with subtle gradient separator ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 separator_start = ImGui::GetCursorScreenPos(); - ImVec2 separator_end(separator_start.x + ImGui::GetContentRegionAvail().x, separator_start.y + 1); + ImVec2 separator_end(separator_start.x + ImGui::GetContentRegionAvail().x, + separator_start.y + 1); ImVec4 gold_faded = kTriforceGold; gold_faded.w = 0.3f; ImVec4 blue_faded = kMasterSwordBlue; blue_faded.w = 0.3f; draw_list->AddRectFilledMultiColor( - separator_start, separator_end, - ImGui::GetColorU32(gold_faded), - ImGui::GetColorU32(blue_faded), - ImGui::GetColorU32(blue_faded), + separator_start, separator_end, ImGui::GetColorU32(gold_faded), + ImGui::GetColorU32(blue_faded), ImGui::GetColorU32(blue_faded), ImGui::GetColorU32(gold_faded)); - + ImGui::Dummy(ImVec2(0, 10)); - + ImGui::BeginChild("WelcomeContent", ImVec2(0, -60), false); - + // Left side - Quick Actions & Templates - ImGui::BeginChild("LeftPanel", ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true, - ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild("LeftPanel", + ImVec2(ImGui::GetContentRegionAvail().x * 0.3f, 0), true, + ImGuiWindowFlags_NoScrollbar); DrawQuickActions(); ImGui::Spacing(); - + // Subtle separator ImVec2 sep_start = ImGui::GetCursorScreenPos(); draw_list->AddLine( sep_start, ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y), - ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)), + ImGui::GetColorU32( + ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.2f)), 1.0f); - + ImGui::Dummy(ImVec2(0, 5)); DrawTemplatesSection(); ImGui::EndChild(); - + ImGui::SameLine(); - + // Right side - Recent Projects & What's New ImGui::BeginChild("RightPanel", ImVec2(0, 0), true); DrawRecentProjects(); ImGui::Spacing(); - + // Subtle separator sep_start = ImGui::GetCursorScreenPos(); draw_list->AddLine( sep_start, ImVec2(sep_start.x + ImGui::GetContentRegionAvail().x, sep_start.y), - ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y, kMasterSwordBlue.z, 0.2f)), + ImGui::GetColorU32(ImVec4(kMasterSwordBlue.x, kMasterSwordBlue.y, + kMasterSwordBlue.z, 0.2f)), 1.0f); - + ImGui::Dummy(ImVec2(0, 5)); DrawWhatsNew(); ImGui::EndChild(); - + ImGui::EndChild(); - + // Footer with subtle gradient ImVec2 footer_start = ImGui::GetCursorScreenPos(); - ImVec2 footer_end(footer_start.x + ImGui::GetContentRegionAvail().x, footer_start.y + 1); + ImVec2 footer_end(footer_start.x + ImGui::GetContentRegionAvail().x, + footer_start.y + 1); ImVec4 red_faded = kHeartRed; red_faded.w = 0.3f; ImVec4 green_faded = kHyruleGreen; green_faded.w = 0.3f; draw_list->AddRectFilledMultiColor( - footer_start, footer_end, - ImGui::GetColorU32(red_faded), - ImGui::GetColorU32(green_faded), - ImGui::GetColorU32(green_faded), + footer_start, footer_end, ImGui::GetColorU32(red_faded), + ImGui::GetColorU32(green_faded), ImGui::GetColorU32(green_faded), ImGui::GetColorU32(red_faded)); - + ImGui::Dummy(ImVec2(0, 5)); DrawTipsSection(); } ImGui::End(); - + ImGui::PopStyleVar(); - + return action_taken; } void WelcomeScreen::UpdateAnimations() { animation_time_ += ImGui::GetIO().DeltaTime; - + // Update hover scale for cards (smooth interpolation) for (int i = 0; i < 6; ++i) { float target = (hovered_card_ == i) ? 1.03f : 1.0f; - card_hover_scale_[i] += (target - card_hover_scale_[i]) * ImGui::GetIO().DeltaTime * 10.0f; + card_hover_scale_[i] += + (target - card_hover_scale_[i]) * ImGui::GetIO().DeltaTime * 10.0f; } - + // Note: Triforce positions and particles are updated in Show() based on mouse position } void WelcomeScreen::RefreshRecentProjects() { recent_projects_.clear(); - + // Use the ProjectManager singleton to get recent files - auto& recent_files = project::RecentFilesManager::GetInstance().GetRecentFiles(); - + auto& recent_files = + project::RecentFilesManager::GetInstance().GetRecentFiles(); + for (const auto& filepath : recent_files) { - if (recent_projects_.size() >= kMaxRecentProjects) break; - + if (recent_projects_.size() >= kMaxRecentProjects) + break; + RecentProject project; project.filepath = filepath; - + // Extract filename std::filesystem::path path(filepath); project.name = path.filename().string(); - + // Get file modification time if it exists if (std::filesystem::exists(path)) { auto ftime = std::filesystem::last_write_time(path); @@ -445,97 +476,106 @@ void WelcomeScreen::RefreshRecentProjects() { project.last_modified = "File not found"; project.rom_title = "Missing"; } - + recent_projects_.push_back(project); } } void WelcomeScreen::DrawHeader() { ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font - + + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[2]); // Large font + // Simple centered title const char* title = ICON_MD_CASTLE " yaze"; auto windowWidth = ImGui::GetWindowSize().x; auto textWidth = ImGui::CalcTextSize(title).x; float xPos = (windowWidth - textWidth) * 0.5f; - + ImGui::SetCursorPosX(xPos); ImVec2 text_pos = ImGui::GetCursorScreenPos(); - + // Subtle static glow behind text float glow_size = 30.0f; - ImU32 glow_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f)); + ImU32 glow_color = ImGui::GetColorU32( + ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f)); draw_list->AddCircleFilled( - ImVec2(text_pos.x + textWidth / 2, text_pos.y + 15), - glow_size, - glow_color, - 32); - + ImVec2(text_pos.x + textWidth / 2, text_pos.y + 15), glow_size, + glow_color, 32); + // Simple gold color for title ImGui::TextColored(kTriforceGold, "%s", title); ImGui::PopFont(); - + // Static subtitle const char* subtitle = "Yet Another Zelda3 Editor"; textWidth = ImGui::CalcTextSize(subtitle).x; ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f); - + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", subtitle); - + // Small decorative triforces flanking the title (static, transparent) // Positioned well away from text to avoid crowding ImVec2 left_tri_pos(xPos - 80, text_pos.y + 20); ImVec2 right_tri_pos(xPos + textWidth + 50, text_pos.y + 20); DrawTriforceBackground(draw_list, left_tri_pos, 20, 0.12f, 0.0f); DrawTriforceBackground(draw_list, right_tri_pos, 20, 0.12f, 0.0f); - + ImGui::Spacing(); } void WelcomeScreen::DrawQuickActions() { ImGui::TextColored(kSpiritOrange, ICON_MD_BOLT " Quick Actions"); ImGui::Spacing(); - + float button_width = ImGui::GetContentRegionAvail().x; - + // Animated button colors (compact height) - auto draw_action_button = [&](const char* icon, const char* text, - const ImVec4& color, bool enabled, - std::function callback) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x * 0.6f, color.y * 0.6f, color.z * 0.6f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, 1.0f)); - - if (!enabled) ImGui::BeginDisabled(); - - bool clicked = ImGui::Button(absl::StrFormat("%s %s", icon, text).c_str(), - ImVec2(button_width, 38)); // Reduced from 45 to 38 - - if (!enabled) ImGui::EndDisabled(); - + auto draw_action_button = [&](const char* icon, const char* text, + const ImVec4& color, bool enabled, + std::function callback) { + ImGui::PushStyleColor( + ImGuiCol_Button, + ImVec4(color.x * 0.6f, color.y * 0.6f, color.z * 0.6f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor( + ImGuiCol_ButtonActive, + ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, 1.0f)); + + if (!enabled) + ImGui::BeginDisabled(); + + bool clicked = + ImGui::Button(absl::StrFormat("%s %s", icon, text).c_str(), + ImVec2(button_width, 38)); // Reduced from 45 to 38 + + if (!enabled) + ImGui::EndDisabled(); + ImGui::PopStyleColor(3); - + if (clicked && enabled && callback) { callback(); } - + return clicked; }; - + // Open ROM button - Green like finding an item - if (draw_action_button(ICON_MD_FOLDER_OPEN, "Open ROM", kHyruleGreen, true, open_rom_callback_)) { + if (draw_action_button(ICON_MD_FOLDER_OPEN, "Open ROM", kHyruleGreen, true, + open_rom_callback_)) { // Handled by callback } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(ICON_MD_INFO " Open an existing ALTTP ROM file"); } - + ImGui::Spacing(); - + // New Project button - Gold like getting a treasure - if (draw_action_button(ICON_MD_ADD_CIRCLE, "New Project", kTriforceGold, true, new_project_callback_)) { + if (draw_action_button(ICON_MD_ADD_CIRCLE, "New Project", kTriforceGold, true, + new_project_callback_)) { // Handled by callback } if (ImGui::IsItemHovered()) { @@ -546,27 +586,30 @@ void WelcomeScreen::DrawQuickActions() { void WelcomeScreen::DrawRecentProjects() { ImGui::TextColored(kMasterSwordBlue, ICON_MD_HISTORY " Recent Projects"); ImGui::Spacing(); - + if (recent_projects_.empty()) { // Simple empty state ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); - + ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetCursorPosX(cursor.x + ImGui::GetContentRegionAvail().x * 0.3f); - ImGui::TextColored(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.8f), - ICON_MD_EXPLORE); + ImGui::TextColored( + ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.8f), + ICON_MD_EXPLORE); ImGui::SetCursorPosX(cursor.x); - - ImGui::TextWrapped("No recent projects yet.\nOpen a ROM to begin your adventure!"); + + ImGui::TextWrapped( + "No recent projects yet.\nOpen a ROM to begin your adventure!"); ImGui::PopStyleColor(); return; } - + // Grid layout for project cards (compact) float card_width = 220.0f; // Reduced for compactness - float card_height = 95.0f; // Reduced for less scrolling - int columns = std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 12))); - + float card_height = 95.0f; // Reduced for less scrolling + int columns = + std::max(1, (int)(ImGui::GetContentRegionAvail().x / (card_width + 12))); + for (size_t i = 0; i < recent_projects_.size(); ++i) { if (i % columns != 0) { ImGui::SameLine(); @@ -577,76 +620,84 @@ void WelcomeScreen::DrawRecentProjects() { void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { ImGui::BeginGroup(); - + ImVec2 card_size(200, 95); // Compact size ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); - + // Subtle hover scale (only on actual hover, no animation) float scale = card_hover_scale_[index]; if (scale != 1.0f) { - ImVec2 center(cursor_pos.x + card_size.x / 2, cursor_pos.y + card_size.y / 2); + ImVec2 center(cursor_pos.x + card_size.x / 2, + cursor_pos.y + card_size.y / 2); cursor_pos.x = center.x - (card_size.x * scale) / 2; cursor_pos.y = center.y - (card_size.y * scale) / 2; card_size.x *= scale; card_size.y *= scale; } - + // Draw card background with subtle gradient ImDrawList* draw_list = ImGui::GetWindowDrawList(); - + // Gradient background ImU32 color_top = ImGui::GetColorU32(ImVec4(0.15f, 0.20f, 0.25f, 1.0f)); ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.10f, 0.15f, 0.20f, 1.0f)); draw_list->AddRectFilledMultiColor( cursor_pos, - ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), - color_top, color_top, color_bottom, color_bottom); - + ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), color_top, + color_top, color_bottom, color_bottom); + // Static themed border - ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen : - (index % 3 == 1) ? kMasterSwordBlue : kTriforceGold; - ImU32 border_color = ImGui::GetColorU32( - ImVec4(border_color_base.x, border_color_base.y, border_color_base.z, 0.5f)); - - draw_list->AddRect(cursor_pos, - ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), - border_color, 6.0f, 0, 2.0f); - + ImVec4 border_color_base = (index % 3 == 0) ? kHyruleGreen + : (index % 3 == 1) ? kMasterSwordBlue + : kTriforceGold; + ImU32 border_color = ImGui::GetColorU32(ImVec4( + border_color_base.x, border_color_base.y, border_color_base.z, 0.5f)); + + draw_list->AddRect( + cursor_pos, + ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), + border_color, 6.0f, 0, 2.0f); + // Make the card clickable ImGui::SetCursorScreenPos(cursor_pos); - ImGui::InvisibleButton(absl::StrFormat("ProjectCard_%d", index).c_str(), card_size); + ImGui::InvisibleButton(absl::StrFormat("ProjectCard_%d", index).c_str(), + card_size); bool is_hovered = ImGui::IsItemHovered(); bool is_clicked = ImGui::IsItemClicked(); - - hovered_card_ = is_hovered ? index : (hovered_card_ == index ? -1 : hovered_card_); - + + hovered_card_ = + is_hovered ? index : (hovered_card_ == index ? -1 : hovered_card_); + // Subtle hover glow (no particles) if (is_hovered) { - ImU32 hover_color = ImGui::GetColorU32(ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f)); - draw_list->AddRectFilled(cursor_pos, - ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), - hover_color, 6.0f); + ImU32 hover_color = ImGui::GetColorU32( + ImVec4(kTriforceGold.x, kTriforceGold.y, kTriforceGold.z, 0.15f)); + draw_list->AddRectFilled( + cursor_pos, + ImVec2(cursor_pos.x + card_size.x, cursor_pos.y + card_size.y), + hover_color, 6.0f); } - + // Draw content (tighter layout) ImVec2 content_pos(cursor_pos.x + 8, cursor_pos.y + 8); - + // Icon with colored background circle (compact) ImVec2 icon_center(content_pos.x + 13, content_pos.y + 13); ImU32 icon_bg = ImGui::GetColorU32(border_color_base); draw_list->AddCircleFilled(icon_center, 15, icon_bg, 24); - + // Center the icon properly ImVec2 icon_size = ImGui::CalcTextSize(ICON_MD_VIDEOGAME_ASSET); - ImGui::SetCursorScreenPos(ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); + ImGui::SetCursorScreenPos( + ImVec2(icon_center.x - icon_size.x / 2, icon_center.y - icon_size.y / 2)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1)); ImGui::Text(ICON_MD_VIDEOGAME_ASSET); ImGui::PopStyleColor(); - + // Project name (compact, shorten if too long) ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 32, content_pos.y + 8)); ImGui::PushTextWrapPos(cursor_pos.x + card_size.x - 8); - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font std::string short_name = project.name; if (short_name.length() > 22) { short_name = short_name.substr(0, 19) + "..."; @@ -654,13 +705,13 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { ImGui::TextColored(kTriforceGold, "%s", short_name.c_str()); ImGui::PopFont(); ImGui::PopTextWrapPos(); - + // ROM title (compact) ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 35)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.65f, 0.65f, 0.65f, 1.0f)); ImGui::Text(ICON_MD_GAMEPAD " %s", project.rom_title.c_str()); ImGui::PopStyleColor(); - + // Path in card (compact) ImGui::SetCursorScreenPos(ImVec2(content_pos.x + 4, content_pos.y + 58)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 0.4f, 1.0f)); @@ -670,7 +721,7 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { } ImGui::Text(ICON_MD_FOLDER " %s", short_path.c_str()); ImGui::PopStyleColor(); - + // Tooltip if (is_hovered) { ImGui::BeginTooltip(); @@ -683,12 +734,12 @@ void WelcomeScreen::DrawProjectCard(const RecentProject& project, int index) { ImGui::TextColored(kTriforceGold, ICON_MD_TOUCH_APP " Click to open"); ImGui::EndTooltip(); } - + // Handle click if (is_clicked && open_project_callback_) { open_project_callback_(project.filepath); } - + ImGui::EndGroup(); } @@ -697,35 +748,40 @@ void WelcomeScreen::DrawTemplatesSection() { float content_width = ImGui::GetContentRegionAvail().x; ImGui::TextColored(kGanonPurple, ICON_MD_LAYERS " Templates"); ImGui::SameLine(content_width - 25); - if (ImGui::SmallButton(show_triforce_settings_ ? ICON_MD_CLOSE : ICON_MD_TUNE)) { + if (ImGui::SmallButton(show_triforce_settings_ ? ICON_MD_CLOSE + : ICON_MD_TUNE)) { show_triforce_settings_ = !show_triforce_settings_; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(ICON_MD_AUTO_AWESOME " Visual Effects Settings"); } - + ImGui::Spacing(); - + // Visual effects settings panel (when opened) if (show_triforce_settings_) { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.18f, 0.15f, 0.22f, 0.4f)); - ImGui::BeginChild("VisualSettingsCompact", ImVec2(0, 115), true, ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild("VisualSettingsCompact", ImVec2(0, 115), true, + ImGuiWindowFlags_NoScrollbar); { ImGui::TextColored(kGanonPurple, ICON_MD_AUTO_AWESOME " Visual Effects"); ImGui::Spacing(); - + ImGui::Text(ICON_MD_OPACITY " Visibility"); ImGui::SetNextItemWidth(-1); - ImGui::SliderFloat("##visibility", &triforce_alpha_multiplier_, 0.0f, 3.0f, "%.1fx"); - + ImGui::SliderFloat("##visibility", &triforce_alpha_multiplier_, 0.0f, + 3.0f, "%.1fx"); + ImGui::Text(ICON_MD_SPEED " Speed"); ImGui::SetNextItemWidth(-1); - ImGui::SliderFloat("##speed", &triforce_speed_multiplier_, 0.05f, 1.0f, "%.2fx"); - - ImGui::Checkbox(ICON_MD_MOUSE " Mouse Interaction", &triforce_mouse_repel_enabled_); + ImGui::SliderFloat("##speed", &triforce_speed_multiplier_, 0.05f, 1.0f, + "%.2fx"); + + ImGui::Checkbox(ICON_MD_MOUSE " Mouse Interaction", + &triforce_mouse_repel_enabled_); ImGui::SameLine(); ImGui::Checkbox(ICON_MD_AUTO_FIX_HIGH " Particles", &particles_enabled_); - + if (ImGui::SmallButton(ICON_MD_REFRESH " Reset")) { triforce_alpha_multiplier_ = 1.0f; triforce_speed_multiplier_ = 0.3f; @@ -739,50 +795,57 @@ void WelcomeScreen::DrawTemplatesSection() { ImGui::PopStyleColor(); ImGui::Spacing(); } - + ImGui::Spacing(); - + struct Template { const char* icon; const char* name; ImVec4 color; }; - + Template templates[] = { - {ICON_MD_COTTAGE, "Vanilla ALTTP", kHyruleGreen}, - {ICON_MD_MAP, "ZSCustomOverworld v3", kMasterSwordBlue}, + {ICON_MD_COTTAGE, "Vanilla ALTTP", kHyruleGreen}, + {ICON_MD_MAP, "ZSCustomOverworld v3", kMasterSwordBlue}, }; - + for (int i = 0; i < 2; ++i) { bool is_selected = (selected_template_ == i); - + // Subtle selection highlight (no animation) if (is_selected) { - ImGui::PushStyleColor(ImGuiCol_Header, - ImVec4(templates[i].color.x * 0.6f, templates[i].color.y * 0.6f, + ImGui::PushStyleColor( + ImGuiCol_Header, + ImVec4(templates[i].color.x * 0.6f, templates[i].color.y * 0.6f, templates[i].color.z * 0.6f, 0.6f)); } - - if (ImGui::Selectable(absl::StrFormat("%s %s", templates[i].icon, templates[i].name).c_str(), - is_selected)) { + + if (ImGui::Selectable( + absl::StrFormat("%s %s", templates[i].icon, templates[i].name) + .c_str(), + is_selected)) { selected_template_ = i; } - + if (is_selected) { ImGui::PopStyleColor(); } - + if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(ICON_MD_STAR " Start with a %s template", templates[i].name); + ImGui::SetTooltip(ICON_MD_STAR " Start with a %s template", + templates[i].name); } } - + ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kSpiritOrange.x * 0.6f, kSpiritOrange.y * 0.6f, kSpiritOrange.z * 0.6f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Button, + ImVec4(kSpiritOrange.x * 0.6f, kSpiritOrange.y * 0.6f, + kSpiritOrange.z * 0.6f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kSpiritOrange); ImGui::BeginDisabled(true); - ImGui::Button(absl::StrFormat("%s Use Template", ICON_MD_ROCKET_LAUNCH).c_str(), - ImVec2(-1, 30)); // Reduced from 35 to 30 + ImGui::Button( + absl::StrFormat("%s Use Template", ICON_MD_ROCKET_LAUNCH).c_str(), + ImVec2(-1, 30)); // Reduced from 35 to 30 ImGui::EndDisabled(); ImGui::PopStyleColor(2); } @@ -790,23 +853,23 @@ void WelcomeScreen::DrawTemplatesSection() { void WelcomeScreen::DrawTipsSection() { // Static tip (or could rotate based on session start time rather than animation) const char* tips[] = { - "Press Ctrl+Shift+P to open the command palette", - "Use z3ed agent for AI-powered ROM editing (Ctrl+Shift+A)", - "Enable ZSCustomOverworld in Debug menu for expanded features", - "Check the Performance Dashboard for optimization insights", - "Collaborate in real-time with yaze-server" - }; + "Press Ctrl+Shift+P to open the command palette", + "Use z3ed agent for AI-powered ROM editing (Ctrl+Shift+A)", + "Enable ZSCustomOverworld in Debug menu for expanded features", + "Check the Performance Dashboard for optimization insights", + "Collaborate in real-time with yaze-server"}; int tip_index = 0; // Show first tip, or could be random on screen open - + ImGui::Text(ICON_MD_LIGHTBULB); ImGui::SameLine(); ImGui::TextColored(kTriforceGold, "Tip:"); ImGui::SameLine(); ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), "%s", tips[tip_index]); - + ImGui::SameLine(ImGui::GetWindowWidth() - 220); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 0.5f)); - if (ImGui::SmallButton(absl::StrFormat("%s Don't show again", ICON_MD_CLOSE).c_str())) { + if (ImGui::SmallButton( + absl::StrFormat("%s Don't show again", ICON_MD_CLOSE).c_str())) { manually_closed_ = true; } ImGui::PopStyleColor(); @@ -815,11 +878,12 @@ void WelcomeScreen::DrawTipsSection() { void WelcomeScreen::DrawWhatsNew() { ImGui::TextColored(kHeartRed, ICON_MD_NEW_RELEASES " What's New"); ImGui::Spacing(); - + // Version badge (no animation) - ImGui::TextColored(kMasterSwordBlue, ICON_MD_VERIFIED "yaze v%s", YAZE_VERSION_STRING); + ImGui::TextColored(kMasterSwordBlue, ICON_MD_VERIFIED "yaze v%s", + YAZE_VERSION_STRING); ImGui::Spacing(); - + // Feature list with icons and colors struct Feature { const char* icon; @@ -827,37 +891,42 @@ void WelcomeScreen::DrawWhatsNew() { const char* desc; ImVec4 color; }; - + Feature features[] = { - {ICON_MD_PSYCHOLOGY, "AI Agent Integration", - "Natural language ROM editing with z3ed agent", kGanonPurple}, - {ICON_MD_CLOUD_SYNC, "Collaboration Features", - "Real-time ROM collaboration via yaze-server", kMasterSwordBlue}, - {ICON_MD_HISTORY, "Version Management", - "ROM snapshots, rollback, corruption detection", kHyruleGreen}, - {ICON_MD_PALETTE, "Enhanced Palette Editor", - "Advanced color tools with ROM palette browser", kSpiritOrange}, - {ICON_MD_SPEED, "Performance Improvements", - "Faster dungeon loading with parallel processing", kTriforceGold}, + {ICON_MD_PSYCHOLOGY, "AI Agent Integration", + "Natural language ROM editing with z3ed agent", kGanonPurple}, + {ICON_MD_CLOUD_SYNC, "Collaboration Features", + "Real-time ROM collaboration via yaze-server", kMasterSwordBlue}, + {ICON_MD_HISTORY, "Version Management", + "ROM snapshots, rollback, corruption detection", kHyruleGreen}, + {ICON_MD_PALETTE, "Enhanced Palette Editor", + "Advanced color tools with ROM palette browser", kSpiritOrange}, + {ICON_MD_SPEED, "Performance Improvements", + "Faster dungeon loading with parallel processing", kTriforceGold}, }; - + for (const auto& feature : features) { ImGui::Bullet(); ImGui::SameLine(); ImGui::TextColored(feature.color, "%s ", feature.icon); ImGui::SameLine(); ImGui::TextColored(ImVec4(0.95f, 0.95f, 0.95f, 1.0f), "%s", feature.title); - + ImGui::Indent(25); ImGui::TextColored(ImVec4(0.65f, 0.65f, 0.65f, 1.0f), "%s", feature.desc); ImGui::Unindent(25); ImGui::Spacing(); } - + ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kMasterSwordBlue.x * 0.6f, kMasterSwordBlue.y * 0.6f, kMasterSwordBlue.z * 0.6f, 0.8f)); + ImGui::PushStyleColor( + ImGuiCol_Button, + ImVec4(kMasterSwordBlue.x * 0.6f, kMasterSwordBlue.y * 0.6f, + kMasterSwordBlue.z * 0.6f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kMasterSwordBlue); - if (ImGui::Button(absl::StrFormat("%s View Full Changelog", ICON_MD_OPEN_IN_NEW).c_str())) { + if (ImGui::Button( + absl::StrFormat("%s View Full Changelog", ICON_MD_OPEN_IN_NEW) + .c_str())) { // Open changelog or GitHub releases } ImGui::PopStyleColor(2); diff --git a/src/app/editor/ui/welcome_screen.h b/src/app/editor/ui/welcome_screen.h index f29338fb..21eaf68e 100644 --- a/src/app/editor/ui/welcome_screen.h +++ b/src/app/editor/ui/welcome_screen.h @@ -30,60 +30,61 @@ struct RecentProject { class WelcomeScreen { public: WelcomeScreen(); - + /** * @brief Show the welcome screen * @param p_open Pointer to open state * @return True if an action was taken (ROM opened, etc.) */ bool Show(bool* p_open); - + /** * @brief Set callback for opening ROM */ void SetOpenRomCallback(std::function callback) { open_rom_callback_ = callback; } - + /** * @brief Set callback for creating new project */ void SetNewProjectCallback(std::function callback) { new_project_callback_ = callback; } - + /** * @brief Set callback for opening project */ - void SetOpenProjectCallback(std::function callback) { + void SetOpenProjectCallback( + std::function callback) { open_project_callback_ = callback; } - + /** * @brief Refresh recent projects list from the project manager */ void RefreshRecentProjects(); - + /** * @brief Update animation time for dynamic effects */ void UpdateAnimations(); - + /** * @brief Check if screen should be shown */ bool ShouldShow() const { return !manually_closed_; } - + /** * @brief Mark as manually closed (don't show again this session) */ void MarkManuallyClosed() { manually_closed_ = true; } - + /** * @brief Reset first show flag (for testing/forcing display) */ void ResetFirstShow() { first_show_attempt_ = true; } - + private: void DrawHeader(); void DrawQuickActions(); @@ -92,31 +93,31 @@ class WelcomeScreen { void DrawTemplatesSection(); void DrawTipsSection(); void DrawWhatsNew(); - + std::vector recent_projects_; bool manually_closed_ = false; bool first_show_attempt_ = true; // Override ImGui ini state on first display - + // Callbacks std::function open_rom_callback_; std::function new_project_callback_; std::function open_project_callback_; - + // UI state int selected_template_ = 0; static constexpr int kMaxRecentProjects = 6; - + // Animation state float animation_time_ = 0.0f; float card_hover_scale_[6] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; int hovered_card_ = -1; - + // Interactive triforce positions (smooth interpolation) static constexpr int kNumTriforces = 6; ImVec2 triforce_positions_[kNumTriforces] = {}; ImVec2 triforce_base_positions_[kNumTriforces] = {}; bool triforce_positions_initialized_ = false; - + // Particle system static constexpr int kMaxParticles = 50; struct Particle { @@ -129,7 +130,7 @@ class WelcomeScreen { }; Particle particles_[kMaxParticles] = {}; int active_particle_count_ = 0; - + // Triforce animation settings bool show_triforce_settings_ = false; float triforce_alpha_multiplier_ = 1.0f; diff --git a/src/app/editor/ui/workspace_manager.cc b/src/app/editor/ui/workspace_manager.cc index 8b94aea5..e5663f28 100644 --- a/src/app/editor/ui/workspace_manager.cc +++ b/src/app/editor/ui/workspace_manager.cc @@ -1,14 +1,14 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include "app/editor/ui/workspace_manager.h" +#include "absl/strings/str_format.h" #include "app/editor/system/editor_card_registry.h" #include "app/editor/system/toast_manager.h" #include "app/rom.h" -#include "absl/strings/str_format.h" -#include "util/file_util.h" -#include "util/platform_paths.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" +#include "util/file_util.h" +#include "util/platform_paths.h" namespace yaze { namespace editor { @@ -38,7 +38,8 @@ absl::Status WorkspaceManager::ResetWorkspaceLayout() { } void WorkspaceManager::SaveWorkspacePreset(const std::string& name) { - if (name.empty()) return; + if (name.empty()) + return; std::string ini_name = absl::StrFormat("yaze_workspace_%s.ini", name.c_str()); ImGui::SaveIniSettingsToDisk(ini_name.c_str()); @@ -61,19 +62,20 @@ void WorkspaceManager::SaveWorkspacePreset(const std::string& name) { } last_workspace_preset_ = name; if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Preset '%s' saved", name), - ToastType::kSuccess); + toast_manager_->Show(absl::StrFormat("Preset '%s' saved", name), + ToastType::kSuccess); } } void WorkspaceManager::LoadWorkspacePreset(const std::string& name) { - if (name.empty()) return; + if (name.empty()) + return; std::string ini_name = absl::StrFormat("yaze_workspace_%s.ini", name.c_str()); ImGui::LoadIniSettingsFromDisk(ini_name.c_str()); last_workspace_preset_ = name; if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Preset '%s' loaded", name), - ToastType::kSuccess); + toast_manager_->Show(absl::StrFormat("Preset '%s' loaded", name), + ToastType::kSuccess); } } @@ -82,7 +84,8 @@ void WorkspaceManager::RefreshPresets() { std::vector new_presets; auto config_dir = util::PlatformPaths::GetConfigDirectory(); if (config_dir.ok()) { - std::string presets_path = (*config_dir / "workspace_presets.txt").string(); + std::string presets_path = + (*config_dir / "workspace_presets.txt").string(); auto data = util::LoadFile(presets_path); if (!data.empty()) { std::istringstream ss(data); @@ -148,8 +151,8 @@ void WorkspaceManager::MaximizeCurrentWindow() { // Use ImGui internal API to maximize current window ImGuiWindow* window = ImGui::GetCurrentWindowRead(); if (window && window->DockNode) { - ImGuiID central_node_id = ImGui::DockBuilderGetCentralNode( - ImGui::GetID("MainDockSpace"))->ID; + ImGuiID central_node_id = + ImGui::DockBuilderGetCentralNode(ImGui::GetID("MainDockSpace"))->ID; ImGui::DockBuilderDockWindow(window->Name, central_node_id); } if (toast_manager_) { @@ -163,7 +166,7 @@ void WorkspaceManager::RestoreAllWindows() { if (ctx) { for (ImGuiWindow* window : ctx->Windows) { if (window && !window->Collapsed) { - ImGui::SetWindowSize(window->Name, ImVec2(0, 0)); // Auto-size + ImGui::SetWindowSize(window->Name, ImVec2(0, 0)); // Auto-size } } } @@ -188,8 +191,9 @@ void WorkspaceManager::CloseAllFloatingWindows() { } size_t WorkspaceManager::GetActiveSessionCount() const { - if (!sessions_) return 0; - + if (!sessions_) + return 0; + size_t count = 0; for (const auto& session : *sessions_) { if (session.rom && session.rom->is_loaded()) { @@ -200,10 +204,12 @@ size_t WorkspaceManager::GetActiveSessionCount() const { } bool WorkspaceManager::HasDuplicateSession(const std::string& filepath) const { - if (!sessions_) return false; + if (!sessions_) + return false; for (const auto& session : *sessions_) { - if (session.filepath == filepath && session.rom && session.rom->is_loaded()) { + if (session.filepath == filepath && session.rom && + session.rom->is_loaded()) { return true; } } @@ -229,8 +235,8 @@ void WorkspaceManager::SplitWindowHorizontal() { ImGuiID node_id = window->DockNode->ID; ImGuiID out_id_at_dir = 0; ImGuiID out_id_at_opposite_dir = 0; - ImGui::DockBuilderSplitNode(node_id, ImGuiDir_Down, 0.5f, - &out_id_at_dir, &out_id_at_opposite_dir); + ImGui::DockBuilderSplitNode(node_id, ImGuiDir_Down, 0.5f, &out_id_at_dir, + &out_id_at_opposite_dir); } } @@ -240,8 +246,8 @@ void WorkspaceManager::SplitWindowVertical() { ImGuiID node_id = window->DockNode->ID; ImGuiID out_id_at_dir = 0; ImGuiID out_id_at_opposite_dir = 0; - ImGui::DockBuilderSplitNode(node_id, ImGuiDir_Right, 0.5f, - &out_id_at_dir, &out_id_at_opposite_dir); + ImGui::DockBuilderSplitNode(node_id, ImGuiDir_Right, 0.5f, &out_id_at_dir, + &out_id_at_opposite_dir); } } @@ -255,27 +261,43 @@ void WorkspaceManager::CloseCurrentWindow() { // Command execution for WhichKey integration void WorkspaceManager::ExecuteWorkspaceCommand(const std::string& command_id) { // Window commands (Space + w) - if (command_id == "w.s") { ShowAllWindows(); } - else if (command_id == "w.h") { HideAllWindows(); } - else if (command_id == "w.m") { MaximizeCurrentWindow(); } - else if (command_id == "w.r") { RestoreAllWindows(); } - else if (command_id == "w.c") { CloseCurrentWindow(); } - else if (command_id == "w.f") { CloseAllFloatingWindows(); } - else if (command_id == "w.v") { SplitWindowVertical(); } - else if (command_id == "w.H") { SplitWindowHorizontal(); } + if (command_id == "w.s") { + ShowAllWindows(); + } else if (command_id == "w.h") { + HideAllWindows(); + } else if (command_id == "w.m") { + MaximizeCurrentWindow(); + } else if (command_id == "w.r") { + RestoreAllWindows(); + } else if (command_id == "w.c") { + CloseCurrentWindow(); + } else if (command_id == "w.f") { + CloseAllFloatingWindows(); + } else if (command_id == "w.v") { + SplitWindowVertical(); + } else if (command_id == "w.H") { + SplitWindowHorizontal(); + } // Layout commands (Space + l) - else if (command_id == "l.s") { SaveWorkspaceLayout(); } - else if (command_id == "l.l") { LoadWorkspaceLayout(); } - else if (command_id == "l.r") { ResetWorkspaceLayout(); } - else if (command_id == "l.d") { LoadDeveloperLayout(); } - else if (command_id == "l.g") { LoadDesignerLayout(); } - else if (command_id == "l.m") { LoadModderLayout(); } + else if (command_id == "l.s") { + SaveWorkspaceLayout(); + } else if (command_id == "l.l") { + LoadWorkspaceLayout(); + } else if (command_id == "l.r") { + ResetWorkspaceLayout(); + } else if (command_id == "l.d") { + LoadDeveloperLayout(); + } else if (command_id == "l.g") { + LoadDesignerLayout(); + } else if (command_id == "l.m") { + LoadModderLayout(); + } // Unknown command else if (toast_manager_) { toast_manager_->Show(absl::StrFormat("Unknown command: %s", command_id), - ToastType::kWarning); + ToastType::kWarning); } } diff --git a/src/app/editor/ui/workspace_manager.h b/src/app/editor/ui/workspace_manager.h index 554d9fdc..2dc9196f 100644 --- a/src/app/editor/ui/workspace_manager.h +++ b/src/app/editor/ui/workspace_manager.h @@ -25,18 +25,20 @@ class WorkspaceManager { std::string custom_name; std::string filepath; }; - + explicit WorkspaceManager(ToastManager* toast_manager) : toast_manager_(toast_manager) {} - + // Set card registry for window visibility management - void set_card_registry(EditorCardRegistry* registry) { card_registry_ = registry; } - + void set_card_registry(EditorCardRegistry* registry) { + card_registry_ = registry; + } + // Layout management absl::Status SaveWorkspaceLayout(const std::string& name = ""); absl::Status LoadWorkspaceLayout(const std::string& name = ""); absl::Status ResetWorkspaceLayout(); - + // Preset management void SaveWorkspacePreset(const std::string& name); void LoadWorkspacePreset(const std::string& name); @@ -44,7 +46,7 @@ class WorkspaceManager { void LoadDeveloperLayout(); void LoadDesignerLayout(); void LoadModderLayout(); - + // Window management void ShowAllWindows(); void HideAllWindows(); @@ -61,16 +63,18 @@ class WorkspaceManager { // Command execution (for WhichKey integration) void ExecuteWorkspaceCommand(const std::string& command_id); - + // Session queries size_t GetActiveSessionCount() const; bool HasDuplicateSession(const std::string& filepath) const; - + void set_sessions(std::deque* sessions) { sessions_ = sessions; } - - const std::vector& workspace_presets() const { return workspace_presets_; } + + const std::vector& workspace_presets() const { + return workspace_presets_; + } bool workspace_presets_loaded() const { return workspace_presets_loaded_; } - + private: ToastManager* toast_manager_; EditorCardRegistry* card_registry_ = nullptr; diff --git a/src/app/emu/audio/apu.cc b/src/app/emu/audio/apu.cc index fe4a4551..47044e54 100644 --- a/src/app/emu/audio/apu.cc +++ b/src/app/emu/audio/apu.cc @@ -17,12 +17,14 @@ namespace emu { // Fixed-point cycle ratio for perfect precision (no floating-point drift) // APU runs at ~1.024 MHz, master clock at ~21.477 MHz (NTSC) // Ratio = (32040 * 32) / (1364 * 262 * 60) = 1,025,280 / 21,437,280 -static constexpr uint64_t kApuCyclesNumerator = 32040 * 32; // 1,025,280 -static constexpr uint64_t kApuCyclesDenominator = 1364 * 262 * 60; // 21,437,280 +static constexpr uint64_t kApuCyclesNumerator = 32040 * 32; // 1,025,280 +static constexpr uint64_t kApuCyclesDenominator = + 1364 * 262 * 60; // 21,437,280 // PAL timing: (32040 * 32) / (1364 * 312 * 50) -static constexpr uint64_t kApuCyclesNumeratorPal = 32040 * 32; // 1,025,280 -static constexpr uint64_t kApuCyclesDenominatorPal = 1364 * 312 * 50; // 21,268,800 +static constexpr uint64_t kApuCyclesNumeratorPal = 32040 * 32; // 1,025,280 +static constexpr uint64_t kApuCyclesDenominatorPal = + 1364 * 312 * 50; // 21,268,800 // Legacy floating-point ratios (deprecated, kept for reference) // static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0); @@ -40,7 +42,9 @@ static const uint8_t bootRom[0x40] = { // Helper to reset the cycle tracking on emulator reset static uint64_t g_last_master_cycles = 0; -static void ResetCycleTracking() { g_last_master_cycles = 0; } +static void ResetCycleTracking() { + g_last_master_cycles = 0; +} void Apu::Init() { ram.resize(0x10000); @@ -71,14 +75,14 @@ void Apu::Reset() { timer_[i].counter = 0; timer_[i].enabled = false; } - + // Reset handshake tracker if (handshake_tracker_) { handshake_tracker_->Reset(); } - + LOG_DEBUG("APU", "Reset complete - IPL ROM readable, PC will be at $%04X", - spc700_.read_word(0xFFFE)); + spc700_.read_word(0xFFFE)); } void Apu::RunCycles(uint64_t master_cycles) { @@ -88,48 +92,57 @@ void Apu::RunCycles(uint64_t master_cycles) { // Convert CPU master cycles to APU cycles using fixed-point ratio (no floating-point drift) // target_apu_cycles = cycles_ + (master_delta * numerator) / denominator - uint64_t numerator = memory_.pal_timing() ? kApuCyclesNumeratorPal : kApuCyclesNumerator; - uint64_t denominator = memory_.pal_timing() ? kApuCyclesDenominatorPal : kApuCyclesDenominator; + uint64_t numerator = + memory_.pal_timing() ? kApuCyclesNumeratorPal : kApuCyclesNumerator; + uint64_t denominator = + memory_.pal_timing() ? kApuCyclesDenominatorPal : kApuCyclesDenominator; - const uint64_t target_apu_cycles = cycles_ + (master_delta * numerator) / denominator; + const uint64_t target_apu_cycles = + cycles_ + (master_delta * numerator) / denominator; // Watchdog to detect infinite loops static uint64_t last_log_cycle = 0; static uint16_t last_pc = 0; static int stuck_counter = 0; static bool logged_transfer_state = false; - + while (cycles_ < target_apu_cycles) { // Execute one SPC700 opcode (variable cycles) then advance APU cycles accordingly. uint16_t old_pc = spc700_.PC; uint16_t current_pc = spc700_.PC; - + // IPL ROM protocol analysis - let it run to see what happens // Log IPL ROM transfer loop activity (every 1000 cycles when in critical range) static uint64_t last_ipl_log = 0; if (rom_readable_ && current_pc >= 0xFFD6 && current_pc <= 0xFFED) { if (cycles_ - last_ipl_log > 10000) { - LOG_DEBUG("APU", "IPL ROM loop: PC=$%04X Y=$%02X Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", - current_pc, spc700_.Y, in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]); - LOG_DEBUG("APU", " Out ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X ZP: $00=$%02X $01=$%02X", - out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3], ram[0x00], ram[0x01]); + LOG_DEBUG("APU", + "IPL ROM loop: PC=$%04X Y=$%02X Ports: F4=$%02X F5=$%02X " + "F6=$%02X F7=$%02X", + current_pc, spc700_.Y, in_ports_[0], in_ports_[1], + in_ports_[2], in_ports_[3]); + LOG_DEBUG("APU", + " Out ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X ZP: " + "$00=$%02X $01=$%02X", + out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3], + ram[0x00], ram[0x01]); last_ipl_log = cycles_; } } - + // Detect if SPC is stuck in tight loop if (current_pc == last_pc) { stuck_counter++; if (stuck_counter > 10000 && cycles_ - last_log_cycle > 10000) { - LOG_DEBUG("APU", "SPC700 stuck at PC=$%04X for %d iterations", - current_pc, stuck_counter); + LOG_DEBUG("APU", "SPC700 stuck at PC=$%04X for %d iterations", + current_pc, stuck_counter); LOG_DEBUG("APU", "Port Status: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", - in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]); + in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]); LOG_DEBUG("APU", "Out Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", - out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3]); + out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3]); LOG_DEBUG("APU", "IPL ROM enabled: %s", rom_readable_ ? "YES" : "NO"); - LOG_DEBUG("APU", "SPC700 Y=$%02X, ZP $00=$%02X $01=$%02X", - spc700_.Y, ram[0x00], ram[0x01]); + LOG_DEBUG("APU", "SPC700 Y=$%02X, ZP $00=$%02X $01=$%02X", spc700_.Y, + ram[0x00], ram[0x01]); if (!logged_transfer_state && ram[0x00] == 0x19 && ram[0x01] == 0x00) { LOG_DEBUG("APU", "Uploaded byte at $0019 = $%02X", ram[0x0019]); logged_transfer_state = true; @@ -206,8 +219,8 @@ uint8_t Apu::Read(uint16_t adr) { uint8_t val = in_ports_[adr - 0xf4]; port_read_count++; if (port_read_count < 10) { // Reduced to prevent logging overflow crash - LOG_DEBUG("APU", "SPC read port $%04X (F%d) = $%02X at PC=$%04X", - adr, adr - 0xf4 + 4, val, spc700_.PC); + LOG_DEBUG("APU", "SPC read port $%04X (F%d) = $%02X at PC=$%04X", adr, + adr - 0xf4 + 4, val, spc700_.PC); } return val; } @@ -232,7 +245,7 @@ uint8_t Apu::Read(uint16_t adr) { void Apu::Write(uint16_t adr, uint8_t val) { static int port_write_count = 0; - + switch (adr) { case 0xf0: { break; // test register @@ -257,15 +270,16 @@ void Apu::Write(uint16_t adr, uint8_t val) { // IPL ROM mapping: initially enabled; writing 1 to bit7 disables IPL ROM. rom_readable_ = (val & 0x80) == 0; if (old_rom_readable != rom_readable_) { - LOG_DEBUG("APU", "Control register $F1 = $%02X - IPL ROM %s at PC=$%04X", - val, rom_readable_ ? "ENABLED" : "DISABLED", spc700_.PC); - + LOG_DEBUG("APU", + "Control register $F1 = $%02X - IPL ROM %s at PC=$%04X", val, + rom_readable_ ? "ENABLED" : "DISABLED", spc700_.PC); + // Track IPL ROM disable for handshake debugging if (handshake_tracker_ && !rom_readable_) { // IPL ROM disabled means audio driver uploaded successfully handshake_tracker_->OnSpcPCChange(spc700_.PC, spc700_.PC); } - + // When IPL ROM is disabled, reset transfer tracking if (!rom_readable_) { in_transfer_ = false; @@ -279,7 +293,8 @@ void Apu::Write(uint16_t adr, uint8_t val) { break; } case 0xf3: { - if (dsp_adr_ < 0x80) dsp_.Write(dsp_adr_, val); + if (dsp_adr_ < 0x80) + dsp_.Write(dsp_adr_, val); break; } case 0xf4: @@ -287,18 +302,20 @@ void Apu::Write(uint16_t adr, uint8_t val) { case 0xf6: case 0xf7: { out_ports_[adr - 0xf4] = val; - + // Track SPC port writes for handshake debugging if (handshake_tracker_) { handshake_tracker_->OnSpcPortWrite(adr - 0xf4, val, spc700_.PC); } - + port_write_count++; if (port_write_count < 10) { // Reduced to prevent logging overflow crash - LOG_DEBUG("APU", "SPC wrote port $%04X (F%d) = $%02X at PC=$%04X [APU_cycles=%llu]", - adr, adr - 0xf4 + 4, val, spc700_.PC, cycles_); + LOG_DEBUG( + "APU", + "SPC wrote port $%04X (F%d) = $%02X at PC=$%04X [APU_cycles=%llu]", + adr, adr - 0xf4 + 4, val, spc700_.PC, cycles_); } - + // Track when SPC enters transfer loop (echoes counter back) // PC is at $FFE2 when the MOVSY write completes (CB F4 is 2 bytes at $FFE0) if (adr == 0xf4 && spc700_.PC == 0xFFE2 && rom_readable_) { @@ -309,8 +326,9 @@ void Apu::Write(uint16_t adr, uint8_t val) { if (ram[0x00] < 0x80) { transfer_size_ = 1; // Assume 1-byte bootstrap transfer in_transfer_ = true; - LOG_DEBUG("APU", "Detected small transfer start: dest=$%02X%02X, size=%d", - ram[0x01], ram[0x00], transfer_size_); + LOG_DEBUG("APU", + "Detected small transfer start: dest=$%02X%02X, size=%d", + ram[0x01], ram[0x00], transfer_size_); } } } @@ -341,7 +359,9 @@ void Apu::SpcWrite(uint16_t adr, uint8_t val) { Write(adr, val); } -void Apu::SpcIdle(bool waiting) { Cycle(); } +void Apu::SpcIdle(bool waiting) { + Cycle(); +} } // namespace emu } // namespace yaze diff --git a/src/app/emu/audio/apu.h b/src/app/emu/audio/apu.h index 35da00a3..0e9c0977 100644 --- a/src/app/emu/audio/apu.h +++ b/src/app/emu/audio/apu.h @@ -1,9 +1,9 @@ #ifndef YAZE_APP_EMU_APU_H_ #define YAZE_APP_EMU_APU_H_ +#include #include #include -#include #include "app/emu/audio/dsp.h" #include "app/emu/audio/spc700.h" @@ -52,7 +52,7 @@ typedef struct Timer { */ class Apu { public: - Apu(MemoryImpl &memory) : memory_(memory) {} + Apu(MemoryImpl& memory) : memory_(memory) {} void Init(); void Reset(); @@ -67,21 +67,21 @@ class Apu { uint8_t Read(uint16_t address); void Write(uint16_t address, uint8_t data); - auto dsp() -> Dsp & { return dsp_; } - auto spc700() -> Spc700 & { return spc700_; } + auto dsp() -> Dsp& { return dsp_; } + auto spc700() -> Spc700& { return spc700_; } uint64_t GetCycles() const { return cycles_; } - + // Audio debugging void set_handshake_tracker(debug::ApuHandshakeTracker* tracker) { handshake_tracker_ = tracker; } uint8_t GetStatus() const { return ram[0x00]; } uint8_t GetControl() const { return ram[0x01]; } - void GetSamples(int16_t *buffer, int count, bool loop = false) { + void GetSamples(int16_t* buffer, int count, bool loop = false) { dsp_.GetSamples(buffer, count, loop); } - void WriteDma(uint16_t address, const uint8_t *data, int count) { + void WriteDma(uint16_t address, const uint8_t* data, int count) { for (int i = 0; i < count; i++) { ram[address + i] = data[i]; } @@ -97,14 +97,14 @@ class Apu { uint8_t dsp_adr_ = 0; uint32_t cycles_ = 0; - + // IPL ROM transfer tracking for proper termination uint8_t transfer_size_ = 0; bool in_transfer_ = false; - MemoryImpl &memory_; + MemoryImpl& memory_; std::array timer_; - + // Audio debugging debug::ApuHandshakeTracker* handshake_tracker_ = nullptr; diff --git a/src/app/emu/audio/audio_backend.cc b/src/app/emu/audio/audio_backend.cc index 579f1db0..c505a343 100644 --- a/src/app/emu/audio/audio_backend.cc +++ b/src/app/emu/audio/audio_backend.cc @@ -29,7 +29,7 @@ bool SDL2AudioBackend::Initialize(const AudioConfig& config) { SDL_AudioSpec want, have; SDL_memset(&want, 0, sizeof(want)); - + want.freq = config.sample_rate; want.format = (config.format == SampleFormat::INT16) ? AUDIO_S16 : AUDIO_F32; want.channels = config.channels; @@ -37,9 +37,10 @@ bool SDL2AudioBackend::Initialize(const AudioConfig& config) { want.callback = nullptr; // Use queue-based audio device_id_ = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0); - + if (device_id_ == 0) { - LOG_ERROR("AudioBackend", "Failed to open SDL audio device: %s", SDL_GetError()); + LOG_ERROR("AudioBackend", "Failed to open SDL audio device: %s", + SDL_GetError()); return false; } @@ -49,15 +50,16 @@ bool SDL2AudioBackend::Initialize(const AudioConfig& config) { // Verify we got what we asked for if (have.freq != want.freq || have.channels != want.channels) { - LOG_WARN("AudioBackend", - "Audio spec mismatch - wanted %dHz %dch, got %dHz %dch", - want.freq, want.channels, have.freq, have.channels); + LOG_WARN("AudioBackend", + "Audio spec mismatch - wanted %dHz %dch, got %dHz %dch", want.freq, + want.channels, have.freq, have.channels); // Update config with actual values config_.sample_rate = have.freq; config_.channels = have.channels; } - LOG_INFO("AudioBackend", "SDL2 audio initialized: %dHz, %d channels, %d samples buffer", + LOG_INFO("AudioBackend", + "SDL2 audio initialized: %dHz, %d channels, %d samples buffer", have.freq, have.channels, have.samples); initialized_ = true; @@ -68,15 +70,16 @@ bool SDL2AudioBackend::Initialize(const AudioConfig& config) { audio_stream_ = nullptr; } stream_buffer_.clear(); - + // Start playback immediately (unpause) SDL_PauseAudioDevice(device_id_, 0); - + return true; } void SDL2AudioBackend::Shutdown() { - if (!initialized_) return; + if (!initialized_) + return; if (audio_stream_) { SDL_FreeAudioStream(audio_stream_); @@ -97,23 +100,27 @@ void SDL2AudioBackend::Shutdown() { } void SDL2AudioBackend::Play() { - if (!initialized_) return; + if (!initialized_) + return; SDL_PauseAudioDevice(device_id_, 0); } void SDL2AudioBackend::Pause() { - if (!initialized_) return; + if (!initialized_) + return; SDL_PauseAudioDevice(device_id_, 1); } void SDL2AudioBackend::Stop() { - if (!initialized_) return; + if (!initialized_) + return; Clear(); SDL_PauseAudioDevice(device_id_, 1); } void SDL2AudioBackend::Clear() { - if (!initialized_) return; + if (!initialized_) + return; SDL_ClearQueuedAudio(device_id_); if (audio_stream_) { SDL_AudioStreamClear(audio_stream_); @@ -121,12 +128,14 @@ void SDL2AudioBackend::Clear() { } bool SDL2AudioBackend::QueueSamples(const int16_t* samples, int num_samples) { - if (!initialized_ || !samples) return false; + if (!initialized_ || !samples) + return false; // OPTIMIZATION: Skip volume scaling if volume is 100% (common case) if (volume_ == 1.0f) { // Fast path: No volume adjustment needed - int result = SDL_QueueAudio(device_id_, samples, num_samples * sizeof(int16_t)); + int result = + SDL_QueueAudio(device_id_, samples, num_samples * sizeof(int16_t)); if (result < 0) { LOG_ERROR("AudioBackend", "SDL_QueueAudio failed: %s", SDL_GetError()); return false; @@ -147,13 +156,15 @@ bool SDL2AudioBackend::QueueSamples(const int16_t* samples, int num_samples) { for (int i = 0; i < num_samples; ++i) { int32_t scaled = static_cast(samples[i] * volume_); // Clamp to prevent overflow - if (scaled > 32767) scaled = 32767; - else if (scaled < -32768) scaled = -32768; + if (scaled > 32767) + scaled = 32767; + else if (scaled < -32768) + scaled = -32768; scaled_samples[i] = static_cast(scaled); } int result = SDL_QueueAudio(device_id_, scaled_samples.data(), - num_samples * sizeof(int16_t)); + num_samples * sizeof(int16_t)); if (result < 0) { LOG_ERROR("AudioBackend", "SDL_QueueAudio failed: %s", SDL_GetError()); return false; @@ -163,7 +174,8 @@ bool SDL2AudioBackend::QueueSamples(const int16_t* samples, int num_samples) { } bool SDL2AudioBackend::QueueSamples(const float* samples, int num_samples) { - if (!initialized_ || !samples) return false; + if (!initialized_ || !samples) + return false; // Convert float to int16 std::vector int_samples(num_samples); @@ -186,8 +198,7 @@ bool SDL2AudioBackend::QueueSamplesNative(const int16_t* samples, return false; } - if (native_rate != stream_native_rate_ || - channels != config_.channels) { + if (native_rate != stream_native_rate_ || channels != config_.channels) { SetAudioStreamResampling(true, native_rate, channels); if (audio_stream_ == nullptr) { return false; @@ -204,7 +215,8 @@ bool SDL2AudioBackend::QueueSamplesNative(const int16_t* samples, const int available_bytes = SDL_AudioStreamAvailable(audio_stream_); if (available_bytes < 0) { - LOG_ERROR("AudioBackend", "SDL_AudioStreamAvailable failed: %s", SDL_GetError()); + LOG_ERROR("AudioBackend", "SDL_AudioStreamAvailable failed: %s", + SDL_GetError()); return false; } @@ -212,12 +224,14 @@ bool SDL2AudioBackend::QueueSamplesNative(const int16_t* samples, return true; } - const int available_samples = available_bytes / static_cast(sizeof(int16_t)); + const int available_samples = + available_bytes / static_cast(sizeof(int16_t)); if (static_cast(stream_buffer_.size()) < available_samples) { stream_buffer_.resize(available_samples); } - if (SDL_AudioStreamGet(audio_stream_, stream_buffer_.data(), available_bytes) < 0) { + if (SDL_AudioStreamGet(audio_stream_, stream_buffer_.data(), + available_bytes) < 0) { LOG_ERROR("AudioBackend", "SDL_AudioStreamGet failed: %s", SDL_GetError()); return false; } @@ -227,17 +241,19 @@ bool SDL2AudioBackend::QueueSamplesNative(const int16_t* samples, AudioStatus SDL2AudioBackend::GetStatus() const { AudioStatus status; - - if (!initialized_) return status; - status.is_playing = (SDL_GetAudioDeviceStatus(device_id_) == SDL_AUDIO_PLAYING); + if (!initialized_) + return status; + + status.is_playing = + (SDL_GetAudioDeviceStatus(device_id_) == SDL_AUDIO_PLAYING); status.queued_bytes = SDL_GetQueuedAudioSize(device_id_); - + // Calculate queued frames (each frame = channels * sample_size) - int bytes_per_frame = config_.channels * - (config_.format == SampleFormat::INT16 ? 2 : 4); + int bytes_per_frame = + config_.channels * (config_.format == SampleFormat::INT16 ? 2 : 4); status.queued_frames = status.queued_bytes / bytes_per_frame; - + // Check for underrun (queue too low while playing) if (status.is_playing && status.queued_frames < 100) { status.has_underrun = true; @@ -256,7 +272,8 @@ AudioConfig SDL2AudioBackend::GetConfig() const { void SDL2AudioBackend::SetAudioStreamResampling(bool enable, int native_rate, int channels) { - if (!initialized_) return; + if (!initialized_) + return; if (!enable) { if (audio_stream_) { @@ -269,9 +286,9 @@ void SDL2AudioBackend::SetAudioStreamResampling(bool enable, int native_rate, return; } - const bool needs_recreate = - (audio_stream_ == nullptr) || (stream_native_rate_ != native_rate) || - (channels != config_.channels); + const bool needs_recreate = (audio_stream_ == nullptr) || + (stream_native_rate_ != native_rate) || + (channels != config_.channels); if (!needs_recreate) { audio_stream_enabled_ = true; @@ -283,9 +300,9 @@ void SDL2AudioBackend::SetAudioStreamResampling(bool enable, int native_rate, audio_stream_ = nullptr; } - audio_stream_ = SDL_NewAudioStream(AUDIO_S16, channels, native_rate, - device_format_, device_channels_, - device_freq_); + audio_stream_ = + SDL_NewAudioStream(AUDIO_S16, channels, native_rate, device_format_, + device_channels_, device_freq_); if (!audio_stream_) { LOG_ERROR("AudioBackend", "SDL_NewAudioStream failed: %s", SDL_GetError()); audio_stream_enabled_ = false; @@ -315,12 +332,12 @@ std::unique_ptr AudioBackendFactory::Create(BackendType type) { switch (type) { case BackendType::SDL2: return std::make_unique(); - + case BackendType::NULL_BACKEND: // TODO: Implement null backend for testing LOG_WARN("AudioBackend", "NULL backend not yet implemented, using SDL2"); return std::make_unique(); - + default: LOG_ERROR("AudioBackend", "Unknown backend type, using SDL2"); return std::make_unique(); diff --git a/src/app/emu/audio/audio_backend.h b/src/app/emu/audio/audio_backend.h index d3126c00..82c46d95 100644 --- a/src/app/emu/audio/audio_backend.h +++ b/src/app/emu/audio/audio_backend.h @@ -60,8 +60,9 @@ class IAudioBackend { // Audio data virtual bool QueueSamples(const int16_t* samples, int num_samples) = 0; virtual bool QueueSamples(const float* samples, int num_samples) = 0; - virtual bool QueueSamplesNative(const int16_t* samples, int frames_per_channel, - int channels, int native_rate) { + virtual bool QueueSamplesNative(const int16_t* samples, + int frames_per_channel, int channels, + int native_rate) { return false; } @@ -138,7 +139,7 @@ class AudioBackendFactory { public: enum class BackendType { SDL2, - SDL3, // Future + SDL3, // Future NULL_BACKEND // For testing/headless }; diff --git a/src/app/emu/audio/dsp.cc b/src/app/emu/audio/dsp.cc index a82ed2ba..788c86e0 100644 --- a/src/app/emu/audio/dsp.cc +++ b/src/app/emu/audio/dsp.cc @@ -158,10 +158,13 @@ static int clamp16(int val) { return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val); } -static int clip16(int val) { return (int16_t)(val & 0xffff); } +static int clip16(int val) { + return (int16_t)(val & 0xffff); +} bool Dsp::CheckCounter(int rate) { - if (rate == 0) return false; + if (rate == 0) + return false; return ((counter + rateOffsets[rate]) % rateValues[rate]) == 0; } @@ -222,7 +225,8 @@ void Dsp::CycleChannel(int ch) { // get current brr header and get sample address channel[ch].brrHeader = aram_[channel[ch].decodeOffset]; uint16_t samplePointer = dirPage + 4 * channel[ch].srcn; - if (channel[ch].startDelay == 0) samplePointer += 2; + if (channel[ch].startDelay == 0) + samplePointer += 2; uint16_t sampleAdr = aram_[samplePointer] | (aram_[(samplePointer + 1) & 0xffff] << 8); // handle starting of sample @@ -289,7 +293,8 @@ void Dsp::CycleChannel(int ch) { // update pitch counter channel[ch].pitchCounter &= 0x3fff; channel[ch].pitchCounter += pitch; - if (channel[ch].pitchCounter > 0x7fff) channel[ch].pitchCounter = 0x7fff; + if (channel[ch].pitchCounter > 0x7fff) + channel[ch].pitchCounter = 0x7fff; // set outputs ram[(ch << 4) | 8] = channel[ch].gain >> 4; ram[(ch << 4) | 9] = sample >> 8; @@ -362,7 +367,8 @@ void Dsp::HandleGain(int ch) { } } // store new value - if (CheckCounter(rate)) channel[ch].gain = newGain; + if (CheckCounter(rate)) + channel[ch].gain = newGain; } int16_t Dsp::GetSample(int ch) { @@ -396,7 +402,8 @@ void Dsp::DecodeBrr(int ch) { 0xffff]; s = curByte >> 4; } - if (s > 7) s -= 16; + if (s > 7) + s -= 16; if (shift <= 0xc) { s = (s << shift) >> 1; } else { @@ -418,7 +425,8 @@ void Dsp::DecodeBrr(int ch) { old = channel[ch].decodeBuffer[bOff + i] >> 1; } channel[ch].bufferOffset += 4; - if (channel[ch].bufferOffset >= 12) channel[ch].bufferOffset = 0; + if (channel[ch].bufferOffset >= 12) + channel[ch].bufferOffset = 0; } void Dsp::HandleNoise() { @@ -428,7 +436,9 @@ void Dsp::HandleNoise() { } } -uint8_t Dsp::Read(uint8_t adr) { return ram[adr]; } +uint8_t Dsp::Read(uint8_t adr) { + return ram[adr]; +} void Dsp::Write(uint8_t adr, uint8_t val) { int ch = adr >> 4; @@ -650,18 +660,19 @@ inline int16_t InterpolateLinear(int16_t s0, int16_t s1, double frac) { // Helper for Hermite interpolation (used by bsnes/Snes9x) // Provides smoother interpolation than linear with minimal overhead -inline int16_t InterpolateHermite(int16_t p0, int16_t p1, int16_t p2, int16_t p3, double t) { +inline int16_t InterpolateHermite(int16_t p0, int16_t p1, int16_t p2, + int16_t p3, double t) { const double c0 = p1; const double c1 = (p2 - p0) * 0.5; const double c2 = p0 - 2.5 * p1 + 2.0 * p2 - 0.5 * p3; const double c3 = (p3 - p0) * 0.5 + 1.5 * (p1 - p2); - + const double result = c0 + c1 * t + c2 * t * t + c3 * t * t * t; - + // Clamp to 16-bit range - return result > 32767.0 ? 32767 - : (result < -32768.0 ? -32768 - : static_cast(result)); + return result > 32767.0 + ? 32767 + : (result < -32768.0 ? -32768 : static_cast(result)); } void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame, @@ -669,13 +680,14 @@ void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame, // Resample from native samples-per-frame (NTSC: ~534, PAL: ~641) const double native_per_frame = pal_timing ? 641.0 : 534.0; const double step = native_per_frame / static_cast(samples_per_frame); - + // Start reading one native frame behind the frame boundary double location = static_cast((lastFrameBoundary + 0x400) & 0x3ff); location -= native_per_frame; - + // Ensure location is within valid range - while (location < 0) location += 0x400; + while (location < 0) + location += 0x400; for (int i = 0; i < samples_per_frame; i++) { const int idx = static_cast(location) & 0x3ff; @@ -684,18 +696,18 @@ void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame, switch (interpolation_type) { case InterpolationType::Linear: { const int next_idx = (idx + 1) & 0x3ff; - + // Linear interpolation for left channel const int16_t s0_l = sampleBuffer[(idx * 2) + 0]; const int16_t s1_l = sampleBuffer[(next_idx * 2) + 0]; - sample_data[(i * 2) + 0] = static_cast( - s0_l + frac * (s1_l - s0_l)); - + sample_data[(i * 2) + 0] = + static_cast(s0_l + frac * (s1_l - s0_l)); + // Linear interpolation for right channel const int16_t s0_r = sampleBuffer[(idx * 2) + 1]; const int16_t s1_r = sampleBuffer[(next_idx * 2) + 1]; - sample_data[(i * 2) + 1] = static_cast( - s0_r + frac * (s1_r - s0_r)); + sample_data[(i * 2) + 1] = + static_cast(s0_r + frac * (s1_r - s0_r)); break; } case InterpolationType::Hermite: { @@ -708,13 +720,15 @@ void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame, const int16_t p1_l = sampleBuffer[(idx1 * 2) + 0]; const int16_t p2_l = sampleBuffer[(idx2 * 2) + 0]; const int16_t p3_l = sampleBuffer[(idx3 * 2) + 0]; - sample_data[(i * 2) + 0] = InterpolateHermite(p0_l, p1_l, p2_l, p3_l, frac); + sample_data[(i * 2) + 0] = + InterpolateHermite(p0_l, p1_l, p2_l, p3_l, frac); // Right channel const int16_t p0_r = sampleBuffer[(idx0 * 2) + 1]; const int16_t p1_r = sampleBuffer[(idx1 * 2) + 1]; const int16_t p2_r = sampleBuffer[(idx2 * 2) + 1]; const int16_t p3_r = sampleBuffer[(idx3 * 2) + 1]; - sample_data[(i * 2) + 1] = InterpolateHermite(p0_r, p1_r, p2_r, p3_r, frac); + sample_data[(i * 2) + 1] = + InterpolateHermite(p0_r, p1_r, p2_r, p3_r, frac); break; } case InterpolationType::Cosine: { @@ -761,8 +775,8 @@ int Dsp::CopyNativeFrame(int16_t* sample_data, bool pal_timing) { const int native_per_frame = pal_timing ? 641 : 534; const int total_samples = native_per_frame * 2; - int start_index = static_cast( - (lastFrameBoundary + 0x400 - native_per_frame) & 0x3ff); + int start_index = + static_cast((lastFrameBoundary + 0x400 - native_per_frame) & 0x3ff); for (int i = 0; i < native_per_frame; ++i) { const int idx = (start_index + i) & 0x3ff; diff --git a/src/app/emu/audio/internal/addressing.cc b/src/app/emu/audio/internal/addressing.cc index 71ff2f49..e6e71906 100644 --- a/src/app/emu/audio/internal/addressing.cc +++ b/src/app/emu/audio/internal/addressing.cc @@ -76,7 +76,9 @@ uint16_t Spc700::ind_p() { } // Immediate -uint16_t Spc700::imm() { return PC++; } +uint16_t Spc700::imm() { + return PC++; +} // Direct page uint8_t Spc700::dp() { @@ -116,14 +118,18 @@ uint16_t Spc700::dp_dp(uint8_t* src) { return ReadOpcode() | (PSW.P << 8); } -uint16_t Spc700::abs() { return ReadOpcodeWord(); } +uint16_t Spc700::abs() { + return ReadOpcodeWord(); +} int8_t Spc700::rel() { PC++; return static_cast(read(PC)); } -uint8_t Spc700::i() { return read((PSW.P << 8) + X); } +uint8_t Spc700::i() { + return read((PSW.P << 8) + X); +} uint8_t Spc700::i_postinc() { uint8_t value = read((PSW.P << 8) + X); diff --git a/src/app/emu/audio/internal/instructions.cc b/src/app/emu/audio/internal/instructions.cc index 02e367b7..50ae5e10 100644 --- a/src/app/emu/audio/internal/instructions.cc +++ b/src/app/emu/audio/internal/instructions.cc @@ -44,25 +44,25 @@ void Spc700::MOVY(uint16_t adr) { void Spc700::MOVS(uint16_t address) { // MOV (address), A - Write A to memory (with dummy read) - read(address); // Dummy read (documented behavior) + read(address); // Dummy read (documented behavior) write(address, A); } void Spc700::MOVSX(uint16_t address) { // MOV (address), X - Write X to memory (with dummy read) - read(address); // Dummy read (documented behavior) + read(address); // Dummy read (documented behavior) write(address, X); } void Spc700::MOVSY(uint16_t address) { // MOV (address), Y - Write Y to memory (with dummy read) - read(address); // Dummy read (documented behavior) + read(address); // Dummy read (documented behavior) write(address, Y); } void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) { // MOV (address), #imm - Write immediate to memory (with dummy read) - read(address); // Dummy read (documented behavior) + read(address); // Dummy read (documented behavior) write(address, operand); } @@ -99,8 +99,7 @@ void Spc700::SBC(uint16_t adr) { // SBC A, (adr) - Subtract with carry (borrow) uint8_t value = read(adr) ^ 0xff; int result = A + value + PSW.C; - PSW.V = ((A & 0x80) == (value & 0x80)) && - ((value & 0x80) != (result & 0x80)); + PSW.V = ((A & 0x80) == (value & 0x80)) && ((value & 0x80) != (result & 0x80)); PSW.H = ((A & 0xf) + (value & 0xf) + PSW.C) > 0xf; PSW.C = result > 0xff; A = result & 0xFF; @@ -383,30 +382,58 @@ void Spc700::DIV(uint8_t operand) { // Note: Branch timing is handled in DoBranch() in spc700.cc // These helpers are only used by old code paths -void Spc700::BRA(int8_t offset) { PC += offset; } -void Spc700::BEQ(int8_t offset) { if (PSW.Z) PC += offset; } -void Spc700::BNE(int8_t offset) { if (!PSW.Z) PC += offset; } -void Spc700::BCS(int8_t offset) { if (PSW.C) PC += offset; } -void Spc700::BCC(int8_t offset) { if (!PSW.C) PC += offset; } -void Spc700::BVS(int8_t offset) { if (PSW.V) PC += offset; } -void Spc700::BVC(int8_t offset) { if (!PSW.V) PC += offset; } -void Spc700::BMI(int8_t offset) { if (PSW.N) PC += offset; } -void Spc700::BPL(int8_t offset) { if (!PSW.N) PC += offset; } +void Spc700::BRA(int8_t offset) { + PC += offset; +} +void Spc700::BEQ(int8_t offset) { + if (PSW.Z) + PC += offset; +} +void Spc700::BNE(int8_t offset) { + if (!PSW.Z) + PC += offset; +} +void Spc700::BCS(int8_t offset) { + if (PSW.C) + PC += offset; +} +void Spc700::BCC(int8_t offset) { + if (!PSW.C) + PC += offset; +} +void Spc700::BVS(int8_t offset) { + if (PSW.V) + PC += offset; +} +void Spc700::BVC(int8_t offset) { + if (!PSW.V) + PC += offset; +} +void Spc700::BMI(int8_t offset) { + if (PSW.N) + PC += offset; +} +void Spc700::BPL(int8_t offset) { + if (!PSW.N) + PC += offset; +} void Spc700::BBS(uint8_t bit, uint8_t operand) { - if (operand & (1 << bit)) PC += rel(); + if (operand & (1 << bit)) + PC += rel(); } void Spc700::BBC(uint8_t bit, uint8_t operand) { - if (!(operand & (1 << bit))) PC += rel(); + if (!(operand & (1 << bit))) + PC += rel(); } // --------------------------------------------------------------------------- // Jump and Call Instructions // --------------------------------------------------------------------------- -void Spc700::JMP(uint16_t address) { - PC = address; +void Spc700::JMP(uint16_t address) { + PC = address; } void Spc700::CALL(uint16_t address) { @@ -463,12 +490,12 @@ void Spc700::POP(uint8_t& operand) { // Bit Manipulation Instructions // --------------------------------------------------------------------------- -void Spc700::SET1(uint8_t bit, uint8_t& operand) { - operand |= (1 << bit); +void Spc700::SET1(uint8_t bit, uint8_t& operand) { + operand |= (1 << bit); } -void Spc700::CLR1(uint8_t bit, uint8_t& operand) { - operand &= ~(1 << bit); +void Spc700::CLR1(uint8_t bit, uint8_t& operand) { + operand &= ~(1 << bit); } void Spc700::TSET1(uint8_t bit, uint8_t& operand) { @@ -514,20 +541,37 @@ void Spc700::MOV1(uint8_t bit, uint8_t& operand) { // Flag Instructions // --------------------------------------------------------------------------- -void Spc700::CLRC() { PSW.C = false; } -void Spc700::SETC() { PSW.C = true; } -void Spc700::NOTC() { PSW.C = !PSW.C; } -void Spc700::CLRV() { PSW.V = false; PSW.H = false; } -void Spc700::CLRP() { PSW.P = false; } -void Spc700::SETP() { PSW.P = true; } -void Spc700::EI() { PSW.I = true; } -void Spc700::DI() { PSW.I = false; } +void Spc700::CLRC() { + PSW.C = false; +} +void Spc700::SETC() { + PSW.C = true; +} +void Spc700::NOTC() { + PSW.C = !PSW.C; +} +void Spc700::CLRV() { + PSW.V = false; + PSW.H = false; +} +void Spc700::CLRP() { + PSW.P = false; +} +void Spc700::SETP() { + PSW.P = true; +} +void Spc700::EI() { + PSW.I = true; +} +void Spc700::DI() { + PSW.I = false; +} // --------------------------------------------------------------------------- // Special Instructions // --------------------------------------------------------------------------- -void Spc700::NOP() { +void Spc700::NOP() { // No operation - PC already advanced by ReadOpcode() } diff --git a/src/app/emu/audio/internal/spc700_accurate_cycles.h b/src/app/emu/audio/internal/spc700_accurate_cycles.h index 803da210..4e52189a 100644 --- a/src/app/emu/audio/internal/spc700_accurate_cycles.h +++ b/src/app/emu/audio/internal/spc700_accurate_cycles.h @@ -8,20 +8,20 @@ // For branching instructions, this is the cost of NOT taking the branch. // Extra cycles for taken branches are added during execution. static const uint8_t spc700_accurate_cycles[256] = { - 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 6, 8, // 0x00 - 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 4, 6, // 0x10 - 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 5, 4, // 0x20 - 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 3, 8, // 0x30 - 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 6, 6, // 0x40 - 2, 4, 6, 5, 2, 5, 5, 6, 4, 5, 5, 5, 2, 2, 4, 3, // 0x50 - 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 5, 5, // 0x60 - 2, 4, 6, 5, 2, 5, 5, 6, 5, 6, 5, 5, 2, 2, 6, 6, // 0x70 - 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 4, 8, // 0x80 - 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 5, 5, 2, 2, 12, 5, // 0x90 - 3, 8, 4, 5, 3, 4, 3, 6, 2, 5, 4, 4, 5, 4, 4, 5, // 0xA0 - 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 3, 4, // 0xB0 - 3, 8, 4, 5, 4, 5, 4, 7, 2, 5, 6, 4, 5, 4, 9, 8, // 0xC0 - 2, 4, 6, 5, 5, 6, 6, 7, 4, 5, 5, 5, 2, 2, 4, 3, // 0xD0 - 2, 8, 4, 5, 3, 4, 3, 6, 2, 4, 5, 4, 5, 4, 3, 6, // 0xE0 - 2, 4, 6, 5, 4, 5, 5, 6, 3, 5, 4, 5, 2, 2, 4, 2 // 0xF0 + 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 6, 8, // 0x00 + 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 4, 6, // 0x10 + 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 5, 4, // 0x20 + 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 3, 8, // 0x30 + 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 6, 6, // 0x40 + 2, 4, 6, 5, 2, 5, 5, 6, 4, 5, 5, 5, 2, 2, 4, 3, // 0x50 + 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 5, 5, // 0x60 + 2, 4, 6, 5, 2, 5, 5, 6, 5, 6, 5, 5, 2, 2, 6, 6, // 0x70 + 2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 4, 8, // 0x80 + 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 5, 5, 2, 2, 12, 5, // 0x90 + 3, 8, 4, 5, 3, 4, 3, 6, 2, 5, 4, 4, 5, 4, 4, 5, // 0xA0 + 2, 4, 6, 5, 2, 5, 5, 6, 5, 5, 6, 5, 2, 2, 3, 4, // 0xB0 + 3, 8, 4, 5, 4, 5, 4, 7, 2, 5, 6, 4, 5, 4, 9, 8, // 0xC0 + 2, 4, 6, 5, 5, 6, 6, 7, 4, 5, 5, 5, 2, 2, 4, 3, // 0xD0 + 2, 8, 4, 5, 3, 4, 3, 6, 2, 4, 5, 4, 5, 4, 3, 6, // 0xE0 + 2, 4, 6, 5, 4, 5, 5, 6, 3, 5, 4, 5, 2, 2, 4, 2 // 0xF0 }; diff --git a/src/app/emu/audio/internal/spc700_cycles.h b/src/app/emu/audio/internal/spc700_cycles.h index 729375b3..01a2c9d8 100644 --- a/src/app/emu/audio/internal/spc700_cycles.h +++ b/src/app/emu/audio/internal/spc700_cycles.h @@ -28,7 +28,7 @@ constexpr int spc700_cycles[256] = { 4, // 0D PUSH PSW 6, // 0E TSET1 abs 8, // 0F BRK - + // 0x10-0x1F 2, // 10 BPL rel 8, // 11 TCALL 1 @@ -46,7 +46,7 @@ constexpr int spc700_cycles[256] = { 2, // 1D DEC X 4, // 1E CMP X, abs 6, // 1F JMP (abs+X) - + // 0x20-0x2F 2, // 20 CLRP 8, // 21 TCALL 2 @@ -64,7 +64,7 @@ constexpr int spc700_cycles[256] = { 4, // 2D PUSH A 5, // 2E CBNE dp, rel 4, // 2F BRA rel - + // 0x30-0x3F 2, // 30 BMI rel 8, // 31 TCALL 3 @@ -82,7 +82,7 @@ constexpr int spc700_cycles[256] = { 2, // 3D INC X 3, // 3E CMP X, dp 8, // 3F CALL abs - + // 0x40-0x4F 2, // 40 SETP 8, // 41 TCALL 4 @@ -100,7 +100,7 @@ constexpr int spc700_cycles[256] = { 4, // 4D PUSH X 6, // 4E TCLR1 abs 6, // 4F PCALL dp - + // 0x50-0x5F 2, // 50 BVC rel 8, // 51 TCALL 5 @@ -118,7 +118,7 @@ constexpr int spc700_cycles[256] = { 2, // 5D MOV X, A 4, // 5E CMP Y, abs 3, // 5F JMP abs - + // 0x60-0x6F 2, // 60 CLRC 8, // 61 TCALL 6 @@ -136,7 +136,7 @@ constexpr int spc700_cycles[256] = { 4, // 6D PUSH Y 5, // 6E DBNZ dp, rel 5, // 6F RET - + // 0x70-0x7F 2, // 70 BVS rel 8, // 71 TCALL 7 @@ -154,7 +154,7 @@ constexpr int spc700_cycles[256] = { 2, // 7D MOV A, X 3, // 7E CMP Y, dp 6, // 7F RETI - + // 0x80-0x8F 2, // 80 SETC 8, // 81 TCALL 8 @@ -172,25 +172,25 @@ constexpr int spc700_cycles[256] = { 2, // 8D MOV Y, #imm 4, // 8E POP PSW 5, // 8F MOV dp, #imm - + // 0x90-0x9F - 2, // 90 BCC rel - 8, // 91 TCALL 9 - 4, // 92 CLR1 dp, 4 - 5, // 93 BBC dp, 4, rel - 4, // 94 ADC A, dp+X - 5, // 95 ADC A, abs+X - 5, // 96 ADC A, abs+Y - 6, // 97 ADC A, (dp)+Y - 5, // 98 ADC dp, #imm - 5, // 99 ADC (X), (Y) - 5, // 9A SUBW YA, dp - 5, // 9B DEC dp+X - 2, // 9C DEC A - 2, // 9D MOV X, SP - 12, // 9E DIV YA, X - 5, // 9F XCN A - + 2, // 90 BCC rel + 8, // 91 TCALL 9 + 4, // 92 CLR1 dp, 4 + 5, // 93 BBC dp, 4, rel + 4, // 94 ADC A, dp+X + 5, // 95 ADC A, abs+X + 5, // 96 ADC A, abs+Y + 6, // 97 ADC A, (dp)+Y + 5, // 98 ADC dp, #imm + 5, // 99 ADC (X), (Y) + 5, // 9A SUBW YA, dp + 5, // 9B DEC dp+X + 2, // 9C DEC A + 2, // 9D MOV X, SP + 12, // 9E DIV YA, X + 5, // 9F XCN A + // 0xA0-0xAF 3, // A0 EI 8, // A1 TCALL 10 @@ -208,7 +208,7 @@ constexpr int spc700_cycles[256] = { 2, // AD CMP Y, #imm 4, // AE POP A 4, // AF MOV (X)+, A - + // 0xB0-0xBF 2, // B0 BCS rel 8, // B1 TCALL 11 @@ -226,7 +226,7 @@ constexpr int spc700_cycles[256] = { 2, // BD MOV SP, X 3, // BE DAS A 4, // BF MOV A, (X)+ - + // 0xC0-0xCF 3, // C0 DI 8, // C1 TCALL 12 @@ -244,7 +244,7 @@ constexpr int spc700_cycles[256] = { 2, // CD MOV X, #imm 4, // CE POP X 9, // CF MUL YA - + // 0xD0-0xDF 2, // D0 BNE rel 8, // D1 TCALL 13 @@ -262,7 +262,7 @@ constexpr int spc700_cycles[256] = { 2, // DD MOV A, Y 6, // DE CBNE dp+X, rel 3, // DF DAA A - + // 0xE0-0xEF 2, // E0 CLRV 8, // E1 TCALL 14 @@ -280,7 +280,7 @@ constexpr int spc700_cycles[256] = { 3, // ED NOTC 4, // EE POP Y 3, // EF SLEEP - + // 0xF0-0xFF 2, // F0 BEQ rel 8, // F1 TCALL 15 @@ -304,4 +304,3 @@ constexpr int spc700_cycles[256] = { } // namespace yaze #endif // YAZE_APP_EMU_AUDIO_INTERNAL_SPC700_CYCLES_H - diff --git a/src/app/emu/audio/spc700.cc b/src/app/emu/audio/spc700.cc index 9a54bcef..10edf581 100644 --- a/src/app/emu/audio/spc700.cc +++ b/src/app/emu/audio/spc700.cc @@ -4,8 +4,8 @@ #include #include #include -#include "util/log.h" #include "core/features.h" +#include "util/log.h" #include "app/emu/audio/internal/opcodes.h" #include "app/emu/audio/internal/spc700_accurate_cycles.h" @@ -74,9 +74,10 @@ int Spc700::Step() { void Spc700::RunOpcode() { static int entry_log = 0; if ((PC >= 0xFFF0 && PC <= 0xFFFF) && entry_log++ < 5) { - LOG_DEBUG("SPC", "RunOpcode ENTRY: PC=$%04X step=%d bstep=%d", PC, step, bstep); + LOG_DEBUG("SPC", "RunOpcode ENTRY: PC=$%04X step=%d bstep=%d", PC, step, + bstep); } - + if (reset_wanted_) { // based on 6502, brk without writes reset_wanted_ = false; @@ -103,20 +104,23 @@ void Spc700::RunOpcode() { static int spc_exec_count = 0; bool in_critical_range = (PC >= 0xFFCF && PC <= 0xFFFF); bool is_transfer_loop = (PC >= 0xFFD6 && PC <= 0xFFED); - + // Reduced logging limits - only log first few iterations if (in_critical_range && spc_exec_count++ < 5) { - LOG_DEBUG("SPC", "Execute: PC=$%04X step=0 bstep=%d Y=%02X A=%02X", PC, bstep, Y, A); + LOG_DEBUG("SPC", "Execute: PC=$%04X step=0 bstep=%d Y=%02X A=%02X", PC, + bstep, Y, A); } if (is_transfer_loop && spc_exec_count < 10) { // Read ports and RAM[$00] to track transfer state uint8_t f4_val = callbacks_.read(0xF4); uint8_t f5_val = callbacks_.read(0xF5); uint8_t ram0_val = callbacks_.read(0x00); - LOG_DEBUG("SPC", "TRANSFER LOOP: PC=$%04X Y=%02X A=%02X F4=%02X F5=%02X RAM0=%02X bstep=%d", - PC, Y, A, f4_val, f5_val, ram0_val, bstep); + LOG_DEBUG("SPC", + "TRANSFER LOOP: PC=$%04X Y=%02X A=%02X F4=%02X F5=%02X " + "RAM0=%02X bstep=%d", + PC, Y, A, f4_val, f5_val, ram0_val, bstep); } - + // Only read new opcode if previous instruction is complete if (bstep == 0) { opcode = ReadOpcode(); @@ -124,7 +128,9 @@ void Spc700::RunOpcode() { last_opcode_cycles_ = spc700_accurate_cycles[opcode]; } else { if (spc_exec_count < 5) { - LOG_DEBUG("SPC", "Continuing multi-step: PC=$%04X bstep=%d opcode=$%02X", PC, bstep, opcode); + LOG_DEBUG("SPC", + "Continuing multi-step: PC=$%04X bstep=%d opcode=$%02X", PC, + bstep, opcode); } } step = 1; @@ -134,24 +140,29 @@ void Spc700::RunOpcode() { // For now, skip logging to avoid performance overhead // SPC700 runs at ~1.024 MHz, logging every instruction would be expensive // without the sparse address-map optimization - + static int exec_log = 0; if ((PC >= 0xFFF0 && PC <= 0xFFFF) && exec_log++ < 5) { - LOG_DEBUG("SPC", "About to ExecuteInstructions: PC=$%04X step=%d bstep=%d opcode=$%02X", PC, step, bstep, opcode); + LOG_DEBUG( + "SPC", + "About to ExecuteInstructions: PC=$%04X step=%d bstep=%d opcode=$%02X", + PC, step, bstep, opcode); } - + ExecuteInstructions(opcode); // Only reset step if instruction is complete (bstep back to 0) static int reset_log = 0; if (step == 1) { if (bstep == 0) { if ((PC >= 0xFFF0 && PC <= 0xFFFF) && reset_log++ < 5) { - LOG_DEBUG("SPC", "Resetting step: PC=$%04X opcode=$%02X bstep=%d", PC, opcode, bstep); + LOG_DEBUG("SPC", "Resetting step: PC=$%04X opcode=$%02X bstep=%d", PC, + opcode, bstep); } step = 0; } else { if ((PC >= 0xFFF0 && PC <= 0xFFFF) && reset_log++ < 5) { - LOG_DEBUG("SPC", "NOT resetting step: PC=$%04X opcode=$%02X bstep=%d", PC, opcode, bstep); + LOG_DEBUG("SPC", "NOT resetting step: PC=$%04X opcode=$%02X bstep=%d", + PC, opcode, bstep); } } } @@ -744,8 +755,8 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { uint8_t imm = ReadOpcode(); uint16_t adr = (PSW.P << 8) | ReadOpcode(); uint8_t val = read(adr); - callbacks_.idle(false); // Add missing cycle - callbacks_.idle(false); // Add missing cycle + callbacks_.idle(false); // Add missing cycle + callbacks_.idle(false); // Add missing cycle int result = val - imm; PSW.C = (val >= imm); PSW.Z = (result == 0); @@ -940,7 +951,8 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { } case 0x9e: { // div imp read(PC); - for (int i = 0; i < 10; i++) callbacks_.idle(false); + for (int i = 0; i < 10; i++) + callbacks_.idle(false); PSW.H = (X & 0xf) <= (Y & 0xf); int yva = (Y << 8) | A; int x = X << 9; @@ -948,8 +960,10 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { yva <<= 1; yva |= (yva & 0x20000) ? 1 : 0; yva &= 0x1ffff; - if (yva >= x) yva ^= 1; - if (yva & 1) yva -= x; + if (yva >= x) + yva ^= 1; + if (yva & 1) + yva -= x; yva &= 0x1ffff; } Y = yva >> 9; @@ -1156,7 +1170,7 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { case 0xcb: { // mov d, Y uint16_t adr = (PSW.P << 8) | ReadOpcode(); read(adr); - callbacks_.idle(false); // Add one extra cycle delay + callbacks_.idle(false); // Add one extra cycle delay write(adr, Y); break; } @@ -1176,7 +1190,8 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { } case 0xcf: { // mul imp read(PC); - for (int i = 0; i < 7; i++) callbacks_.idle(false); + for (int i = 0; i < 7; i++) + callbacks_.idle(false); uint16_t result = A * Y; A = result & 0xff; Y = result >> 8; @@ -1314,7 +1329,7 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { } case 0xeb: { // movy dp uint16_t adr = (PSW.P << 8) | ReadOpcode(); - callbacks_.idle(false); // Add missing cycle + callbacks_.idle(false); // Add missing cycle Y = read(adr); PSW.Z = (Y == 0); PSW.N = (Y & 0x80); @@ -1344,10 +1359,12 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { // Advance timers/DSP via idle callbacks, but do not set stopped_. static int sleep_log = 0; if (sleep_log++ < 5) { - LOG_DEBUG("SPC", "SLEEP executed at PC=$%04X - entering low power mode", PC - 1); + LOG_DEBUG("SPC", "SLEEP executed at PC=$%04X - entering low power mode", + PC - 1); } read(PC); - for (int i = 0; i < 4; ++i) callbacks_.idle(true); + for (int i = 0; i < 4; ++i) + callbacks_.idle(true); break; } case 0xf0: { // beq rel @@ -1397,7 +1414,9 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { // Log Y increment in transfer loop for first few iterations only static int incy_log = 0; if (PC >= 0xFFE4 && PC <= 0xFFE6 && incy_log++ < 10) { - LOG_DEBUG("SPC", "INC Y executed at PC=$%04X: Y changed from $%02X to $%02X (Z=%d N=%d)", + LOG_DEBUG("SPC", + "INC Y executed at PC=$%04X: Y changed from $%02X to $%02X " + "(Z=%d N=%d)", PC - 1, old_y, Y, PSW.Z, PSW.N); } break; @@ -1434,12 +1453,10 @@ void Spc700::LogInstruction(uint16_t initial_pc, uint8_t opcode) { std::stringstream ss; ss << "$" << std::hex << std::setw(4) << std::setfill('0') << initial_pc - << ": 0x" << std::setw(2) << std::setfill('0') - << static_cast(opcode) << " " << mnemonic - << " A:" << std::setw(2) << std::setfill('0') << std::hex - << static_cast(A) - << " X:" << std::setw(2) << std::setfill('0') << std::hex - << static_cast(X) + << ": 0x" << std::setw(2) << std::setfill('0') << static_cast(opcode) + << " " << mnemonic << " A:" << std::setw(2) << std::setfill('0') + << std::hex << static_cast(A) << " X:" << std::setw(2) + << std::setfill('0') << std::hex << static_cast(X) << " Y:" << std::setw(2) << std::setfill('0') << std::hex << static_cast(Y); diff --git a/src/app/emu/audio/spc700.h b/src/app/emu/audio/spc700.h index 4a96300b..38304f16 100644 --- a/src/app/emu/audio/spc700.h +++ b/src/app/emu/audio/spc700.h @@ -83,7 +83,7 @@ class Spc700 { uint16_t dat16; uint8_t param; int extra_cycles_ = 0; - + // Cycle tracking for accurate APU synchronization int last_opcode_cycles_ = 0; @@ -172,7 +172,7 @@ class Spc700 { } void DoBranch(uint8_t value, bool check) { - callbacks_.idle(false); // Add missing base cycle for all branches + callbacks_.idle(false); // Add missing base cycle for all branches if (check) { // taken branch: 2 extra cycles callbacks_.idle(false); diff --git a/src/app/emu/cpu/cpu.cc b/src/app/emu/cpu/cpu.cc index 1af1de86..97060a04 100644 --- a/src/app/emu/cpu/cpu.cc +++ b/src/app/emu/cpu/cpu.cc @@ -7,9 +7,9 @@ #include #include "absl/strings/str_format.h" -#include "core/features.h" #include "app/emu/cpu/internal/opcodes.h" #include "app/emu/debug/disassembly_viewer.h" +#include "core/features.h" #include "util/log.h" namespace yaze { @@ -24,7 +24,8 @@ debug::DisassemblyViewer& Cpu::disassembly_viewer() { const debug::DisassemblyViewer& Cpu::disassembly_viewer() const { if (disassembly_viewer_ == nullptr) { - const_cast(this)->disassembly_viewer_ = new debug::DisassemblyViewer(); + const_cast(this)->disassembly_viewer_ = + new debug::DisassemblyViewer(); } return *disassembly_viewer_; } @@ -60,7 +61,7 @@ void Cpu::RunOpcode() { return; // Don't run this opcode yet } } - + if (reset_wanted_) { reset_wanted_ = false; // reset: brk/interrupt without writes @@ -81,19 +82,21 @@ void Cpu::RunOpcode() { SetFlags(status); // updates x and m flags, clears // upper half of x and y if needed PB = 0; - + // Debug: Log reset vector read uint8_t low_byte = ReadByte(0xfffc); uint8_t high_byte = ReadByte(0xfffd); PC = low_byte | (high_byte << 8); - LOG_DEBUG("CPU", "Reset vector: $FFFC=$%02X $FFFD=$%02X -> PC=$%04X", - low_byte, high_byte, PC); + LOG_DEBUG("CPU", "Reset vector: $FFFC=$%02X $FFFD=$%02X -> PC=$%04X", + low_byte, high_byte, PC); return; } if (stopped_) { static int stopped_log_count = 0; if (stopped_log_count++ < 5) { - LOG_DEBUG("CPU", "CPU is STOPPED at $%02X:%04X (STP instruction executed)", PB, PC); + LOG_DEBUG("CPU", + "CPU is STOPPED at $%02X:%04X (STP instruction executed)", PB, + PC); } callbacks_.idle(true); return; @@ -101,11 +104,14 @@ void Cpu::RunOpcode() { if (waiting_) { static int waiting_log_count = 0; if (waiting_log_count++ < 5) { - LOG_DEBUG("CPU", "CPU is WAITING at $%02X:%04X - irq_wanted=%d nmi_wanted=%d int_flag=%d", - PB, PC, irq_wanted_, nmi_wanted_, GetInterruptFlag()); + LOG_DEBUG("CPU", + "CPU is WAITING at $%02X:%04X - irq_wanted=%d nmi_wanted=%d " + "int_flag=%d", + PB, PC, irq_wanted_, nmi_wanted_, GetInterruptFlag()); } if (irq_wanted_ || nmi_wanted_) { - LOG_DEBUG("CPU", "CPU waking from WAIT - irq=%d nmi=%d", irq_wanted_, nmi_wanted_); + LOG_DEBUG("CPU", "CPU waking from WAIT - irq=%d nmi=%d", irq_wanted_, + nmi_wanted_); waiting_ = false; callbacks_.idle(false); CheckInt(); @@ -122,58 +128,64 @@ void Cpu::RunOpcode() { DoInterrupt(); } else { uint8_t opcode = ReadOpcode(); - + // AUDIO DEBUG: Enhanced logging for audio initialization tracking static int instruction_count = 0; instruction_count++; uint16_t cur_pc = PC - 1; - + // Track entry into Bank $00 (where all audio code lives) static bool entered_bank00 = false; static bool logged_first_nmi = false; - + if (PB == 0x00 && !entered_bank00) { - LOG_INFO("CPU_AUDIO", "=== ENTERED BANK $00 at PC=$%04X (instruction #%d) ===", - cur_pc, instruction_count); + LOG_INFO("CPU_AUDIO", + "=== ENTERED BANK $00 at PC=$%04X (instruction #%d) ===", cur_pc, + instruction_count); entered_bank00 = true; } - + // Monitor NMI interrupts (audio init usually happens in NMI) if (nmi_wanted_ && !logged_first_nmi) { - LOG_INFO("CPU_AUDIO", "=== FIRST NMI TRIGGERED at PC=$%02X:%04X ===", PB, cur_pc); + LOG_INFO("CPU_AUDIO", "=== FIRST NMI TRIGGERED at PC=$%02X:%04X ===", PB, + cur_pc); logged_first_nmi = true; } - + // Track key audio routines in Bank $00 if (PB == 0x00) { static bool logged_routines[0x10000] = {false}; - + // NMI handler entry ($0080-$00FF region) if (cur_pc >= 0x0080 && cur_pc <= 0x00FF) { if (cur_pc == 0x0080 || cur_pc == 0x0090 || cur_pc == 0x00A0) { if (!logged_routines[cur_pc]) { - LOG_INFO("CPU_AUDIO", "NMI code: PC=$00:%04X A=$%02X X=$%04X Y=$%04X", - cur_pc, A & 0xFF, X, Y); + LOG_INFO("CPU_AUDIO", + "NMI code: PC=$00:%04X A=$%02X X=$%04X Y=$%04X", cur_pc, + A & 0xFF, X, Y); logged_routines[cur_pc] = true; } } } - + // LoadSongBank routine ($8888-$88FF) - This is where handshake happens! // LOGIC: Track CPU's journey through audio initialization to identify where it gets stuck. // We log key waypoints to understand if CPU reaches handshake write instructions. if (cur_pc >= 0x8888 && cur_pc <= 0x88FF) { // Log entry if (cur_pc == 0x8888) { - LOG_INFO("CPU_AUDIO", ">>> LoadSongBank ENTRY at $8888! A=$%02X X=$%04X", - A & 0xFF, X); + LOG_INFO("CPU_AUDIO", + ">>> LoadSongBank ENTRY at $8888! A=$%02X X=$%04X", A & 0xFF, + X); } // DISCOVERY: Log every unique PC in this range to see the execution path // This helps identify if CPU is looping, stuck, or simply not reaching write instructions static int exec_count_8888 = 0; if (exec_count_8888++ < 100 && !logged_routines[cur_pc]) { - LOG_INFO("CPU_AUDIO", " LoadSongBank: PC=$%04X A=$%02X X=$%04X Y=$%04X SP=$%04X [exec #%d]", + LOG_INFO("CPU_AUDIO", + " LoadSongBank: PC=$%04X A=$%02X X=$%04X Y=$%04X SP=$%04X " + "[exec #%d]", cur_pc, A & 0xFF, X, Y, SP(), exec_count_8888); logged_routines[cur_pc] = true; } @@ -182,7 +194,8 @@ void Cpu::RunOpcode() { if (cur_pc >= 0x88A0 && cur_pc <= 0x88B0) { static int setup_count = 0; if (setup_count++ < 20) { - LOG_INFO("CPU_AUDIO", "Handshake setup area: PC=$%04X A=$%02X", cur_pc, A & 0xFF); + LOG_INFO("CPU_AUDIO", "Handshake setup area: PC=$%04X A=$%02X", + cur_pc, A & 0xFF); } } @@ -192,20 +205,22 @@ void Cpu::RunOpcode() { if (cur_pc == 0x88B3 || cur_pc == 0x88B6) { if (handshake_log_count++ < 20 || handshake_log_count % 500 == 0) { uint8_t f4_val = callbacks_.read_byte(0x2140); - LOG_INFO("CPU_AUDIO", "Handshake wait: PC=$%04X A=$%02X F4=$%02X X=$%04X [loop #%d]", - cur_pc, A & 0xFF, f4_val, X, handshake_log_count); + LOG_INFO( + "CPU_AUDIO", + "Handshake wait: PC=$%04X A=$%02X F4=$%02X X=$%04X [loop #%d]", + cur_pc, A & 0xFF, f4_val, X, handshake_log_count); } } } } - + // Log first 50 instructions for boot tracking bool should_log = instruction_count < 50; if (should_log) { - LOG_DEBUG("CPU", "Boot #%d: $%02X:%04X opcode=$%02X", - instruction_count, PB, PC - 1, opcode); + LOG_DEBUG("CPU", "Boot #%d: $%02X:%04X opcode=$%02X", instruction_count, + PB, PC - 1, opcode); } - + // Debug: Log if stuck at same PC for extended period (after first 200 instructions) static uint16_t last_stuck_pc = 0xFFFF; static int stuck_count = 0; @@ -214,18 +229,19 @@ void Cpu::RunOpcode() { stuck_count++; if (stuck_count == 100 || stuck_count == 1000 || stuck_count == 10000) { LOG_DEBUG("CPU", "Stuck at $%02X:%04X opcode=$%02X for %d iterations", - PB, PC - 1, opcode, stuck_count); + PB, PC - 1, opcode, stuck_count); } } else { if (stuck_count > 50) { - LOG_DEBUG("CPU", "Moved from $%02X:%04X (was stuck %d times) to $%02X:%04X", - PB, last_stuck_pc, stuck_count, PB, PC - 1); + LOG_DEBUG("CPU", + "Moved from $%02X:%04X (was stuck %d times) to $%02X:%04X", + PB, last_stuck_pc, stuck_count, PB, PC - 1); } stuck_count = 0; last_stuck_pc = PC - 1; } } - + ExecuteInstruction(opcode); } } @@ -257,7 +273,8 @@ void Cpu::ExecuteInstruction(uint8_t opcode) { case 0x00: { // brk imm(s) uint32_t vector = (E) ? 0xfffe : 0xffe6; ReadOpcode(); - if (!E) PushByte(PB); + if (!E) + PushByte(PB); PushWord(PC, false); PushByte(status); SetInterruptFlag(true); @@ -275,7 +292,8 @@ void Cpu::ExecuteInstruction(uint8_t opcode) { case 0x02: { // cop imm(s) uint32_t vector = (E) ? 0xfff4 : 0xffe4; ReadOpcode(); - if (!E) PushByte(PB); + if (!E) + PushByte(PB); PushWord(PC, false); PushByte(status); SetInterruptFlag(true); @@ -1476,9 +1494,12 @@ void Cpu::ExecuteInstruction(uint8_t opcode) { uint8_t dp1 = ReadByte(D + 0x01); uint8_t dp2 = ReadByte(D + 0x02); uint32_t ptr = dp0 | (dp1 << 8) | (dp2 << 16); - LOG_DEBUG("CPU", "LDA [$00],Y at PC=$%04X: DP=$%04X, [$00]=$%02X:$%04X, Y=$%04X", - cur_pc, D, dp2, (uint16_t)(dp0 | (dp1 << 8)), Y); - LOG_DEBUG("CPU", " -> Reading 16-bit value from address $%06X", ptr + Y); + LOG_DEBUG( + "CPU", + "LDA [$00],Y at PC=$%04X: DP=$%04X, [$00]=$%02X:$%04X, Y=$%04X", + cur_pc, D, dp2, (uint16_t)(dp0 | (dp1 << 8)), Y); + LOG_DEBUG("CPU", " -> Reading 16-bit value from address $%06X", + ptr + Y); } uint32_t low = 0; uint32_t high = AdrIly(&low); @@ -1962,19 +1983,19 @@ void Cpu::ExecuteInstruction(uint8_t opcode) { } void Cpu::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, -bool immediate, bool accumulator_mode) { + bool immediate, bool accumulator_mode) { // Build full 24-bit address uint32_t full_address = (PB << 16) | PC; - + // Extract operand bytes based on instruction size std::vector operand_bytes; std::string operand_str; - + if (operand) { if (immediate) { operand_str += "#"; } - + if (accumulator_mode) { // 8-bit operand operand_bytes.push_back(operand & 0xFF); @@ -1986,17 +2007,18 @@ bool immediate, bool accumulator_mode) { operand_str += absl::StrFormat("$%04X", operand); } } - + // Get mnemonic const std::string& mnemonic = opcode_to_mnemonic.at(opcode); - + // ALWAYS record to DisassemblyViewer (sparse, Mesen-style, zero cost) // The callback only fires if set, and DisassemblyViewer only stores unique addresses // - First execution: Add to map (O(log n)) // - Subsequent: Increment counter (O(log n)) // - Total overhead: ~0.1% even with millions of instructions if (on_instruction_executed_) { - on_instruction_executed_(full_address, opcode, operand_bytes, mnemonic, operand_str); + on_instruction_executed_(full_address, opcode, operand_bytes, mnemonic, + operand_str); } } diff --git a/src/app/emu/cpu/cpu.h b/src/app/emu/cpu/cpu.h index 47cac1e0..d1faa6e1 100644 --- a/src/app/emu/cpu/cpu.h +++ b/src/app/emu/cpu/cpu.h @@ -53,17 +53,19 @@ class Cpu { std::vector breakpoints_; // REMOVED: instruction_log_ - replaced by efficient DisassemblyViewer - + // Disassembly viewer (always enabled, uses sparse address map) debug::DisassemblyViewer& disassembly_viewer(); const debug::DisassemblyViewer& disassembly_viewer() const; - + // Breakpoint callback (set by Emulator) std::function on_breakpoint_hit_; - + // Instruction recording callback (for DisassemblyViewer) - std::function& operands, - const std::string& mnemonic, const std::string& operand_str)> on_instruction_executed_; + std::function& operands, + const std::string& mnemonic, const std::string& operand_str)> + on_instruction_executed_; // Public register access for debugging and UI uint16_t A = 0; // Accumulator @@ -77,7 +79,7 @@ class Cpu { // Breakpoint management void set_int_delay(bool delay) { int_delay_ = delay; } - + debug::DisassemblyViewer* disassembly_viewer_ = nullptr; // ====================================================== @@ -95,7 +97,7 @@ class Cpu { // ====================================================== // Internal state - uint8_t E = 1; // Emulation mode flag + uint8_t E = 1; // Emulation mode flag // Mnemonic Value Binary Description // N #$80 10000000 Negative @@ -164,25 +166,27 @@ class Cpu { uint16_t ReadOpcodeWord(bool int_check = false) { uint8_t value = ReadOpcode(); - if (int_check) CheckInt(); + if (int_check) + CheckInt(); return value | (ReadOpcode() << 8); } // Memory access routines uint8_t ReadByte(uint32_t address) { return callbacks_.read_byte(address); } - + // Read 16-bit value from consecutive addresses (little-endian) uint16_t ReadWord(uint32_t address) { uint8_t low = ReadByte(address); uint8_t high = ReadByte(address + 1); return low | (high << 8); } - + // Read 16-bit value from two separate addresses (for wrapping/crossing boundaries) uint16_t ReadWord(uint32_t address, uint32_t address_high, bool int_check = false) { uint8_t value = ReadByte(address); - if (int_check) CheckInt(); + if (int_check) + CheckInt(); uint8_t value2 = ReadByte(address_high); return value | (value2 << 8); } @@ -201,11 +205,13 @@ class Cpu { bool reversed = false, bool int_check = false) { if (reversed) { callbacks_.write_byte(address_high, value >> 8); - if (int_check) CheckInt(); + if (int_check) + CheckInt(); callbacks_.write_byte(address, value & 0xFF); } else { callbacks_.write_byte(address, value & 0xFF); - if (int_check) CheckInt(); + if (int_check) + CheckInt(); callbacks_.write_byte(address_high, value >> 8); } } @@ -218,11 +224,13 @@ class Cpu { void PushByte(uint8_t value) { callbacks_.write_byte(SP(), value); SetSP(SP() - 1); - if (E) SetSP((SP() & 0xff) | 0x100); + if (E) + SetSP((SP() & 0xff) | 0x100); } void PushWord(uint16_t value, bool int_check = false) { PushByte(value >> 8); - if (int_check) CheckInt(); + if (int_check) + CheckInt(); PushByte(value & 0xFF); } void PushLong(uint32_t value) { // Push 24-bit value @@ -232,12 +240,14 @@ class Cpu { uint8_t PopByte() { SetSP(SP() + 1); - if (E) SetSP((SP() & 0xff) | 0x100); + if (E) + SetSP((SP() & 0xff) | 0x100); return ReadByte(SP()); } uint16_t PopWord(bool int_check = false) { uint8_t low = PopByte(); - if (int_check) CheckInt(); + if (int_check) + CheckInt(); return low | (PopByte() << 8); } uint32_t PopLong() { // Pop 24-bit value @@ -247,7 +257,8 @@ class Cpu { } void DoBranch(bool check) { - if (!check) CheckInt(); + if (!check) + CheckInt(); uint8_t value = ReadOpcode(); if (check) { CheckInt(); @@ -256,7 +267,6 @@ class Cpu { } } - // Addressing Modes // Effective Address: @@ -774,7 +784,7 @@ class Cpu { } bool stopped() const { return stopped_; } - + // REMOVED: SetInstructionLogging - DisassemblyViewer is always active // Use disassembly_viewer().SetRecording(bool) for runtime control diff --git a/src/app/emu/cpu/internal/addressing.cc b/src/app/emu/cpu/internal/addressing.cc index 2f7ed8ef..7422a252 100644 --- a/src/app/emu/cpu/internal/addressing.cc +++ b/src/app/emu/cpu/internal/addressing.cc @@ -27,7 +27,8 @@ uint32_t Cpu::Immediate(uint32_t* low, bool xFlag) { uint32_t Cpu::AdrDpx(uint32_t* low) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle + if (D & 0xff) + callbacks_.idle(false); // dpr not 0: 1 extra cycle callbacks_.idle(false); *low = (D + adr + X) & 0xffff; return (D + adr + X + 1) & 0xffff; @@ -35,7 +36,8 @@ uint32_t Cpu::AdrDpx(uint32_t* low) { uint32_t Cpu::AdrDpy(uint32_t* low) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle + if (D & 0xff) + callbacks_.idle(false); // dpr not 0: 1 extra cycle callbacks_.idle(false); *low = (D + adr + Y) & 0xffff; return (D + adr + Y + 1) & 0xffff; @@ -43,7 +45,8 @@ uint32_t Cpu::AdrDpy(uint32_t* low) { uint32_t Cpu::AdrIdp(uint32_t* low) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle + if (D & 0xff) + callbacks_.idle(false); // dpr not 0: 1 extra cycle uint16_t pointer = ReadWord((D + adr) & 0xffff); *low = (DB << 16) + pointer; return ((DB << 16) + pointer + 1) & 0xffffff; @@ -51,7 +54,8 @@ uint32_t Cpu::AdrIdp(uint32_t* low) { uint32_t Cpu::AdrIdy(uint32_t* low, bool write) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle + if (D & 0xff) + callbacks_.idle(false); // dpr not 0: 1 extra cycle uint16_t pointer = ReadWord((D + adr) & 0xffff); // writing opcode or x = 0 or page crossed: 1 extra cycle if (write || !GetIndexSize() || ((pointer >> 8) != ((pointer + Y) >> 8))) @@ -62,7 +66,8 @@ uint32_t Cpu::AdrIdy(uint32_t* low, bool write) { uint32_t Cpu::AdrIdl(uint32_t* low) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle + if (D & 0xff) + callbacks_.idle(false); // dpr not 0: 1 extra cycle uint32_t pointer = ReadWord((D + adr) & 0xffff); pointer |= ReadByte((D + adr + 2) & 0xffff) << 16; *low = pointer; @@ -71,7 +76,8 @@ uint32_t Cpu::AdrIdl(uint32_t* low) { uint32_t Cpu::AdrIly(uint32_t* low) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle + if (D & 0xff) + callbacks_.idle(false); // dpr not 0: 1 extra cycle uint32_t pointer = ReadWord((D + adr) & 0xffff); pointer |= ReadByte((D + adr + 2) & 0xffff) << 16; *low = (pointer + Y) & 0xffffff; @@ -134,7 +140,8 @@ uint32_t Cpu::AdrAlx(uint32_t* low) { uint32_t Cpu::AdrDp(uint32_t* low) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle + if (D & 0xff) + callbacks_.idle(false); // dpr not 0: 1 extra cycle *low = (D + adr) & 0xffff; return (D + adr + 1) & 0xffff; } @@ -157,7 +164,8 @@ uint16_t Cpu::DirectPageIndexedY() { uint32_t Cpu::AdrIdx(uint32_t* low) { uint8_t adr = ReadOpcode(); - if (D & 0xff) callbacks_.idle(false); + if (D & 0xff) + callbacks_.idle(false); callbacks_.idle(false); uint16_t pointer = ReadWord((D + adr + X) & 0xffff); *low = (DB << 16) + pointer; diff --git a/src/app/emu/cpu/internal/instructions.cc b/src/app/emu/cpu/internal/instructions.cc index 720b3dd5..2ea6b2d1 100644 --- a/src/app/emu/cpu/internal/instructions.cc +++ b/src/app/emu/cpu/internal/instructions.cc @@ -34,14 +34,16 @@ void Cpu::Adc(uint32_t low, uint32_t high) { int result = 0; if (GetDecimalFlag()) { result = (A & 0xf) + (value & 0xf) + GetCarryFlag(); - if (result > 0x9) result = ((result + 0x6) & 0xf) + 0x10; + if (result > 0x9) + result = ((result + 0x6) & 0xf) + 0x10; result = (A & 0xf0) + (value & 0xf0) + result; } else { result = (A & 0xff) + value + GetCarryFlag(); } SetOverflowFlag((A & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80)); - if (GetDecimalFlag() && result > 0x9f) result += 0x60; + if (GetDecimalFlag() && result > 0x9f) + result += 0x60; SetCarryFlag(result > 0xff); A = (A & 0xff00) | (result & 0xff); } else { @@ -49,18 +51,22 @@ void Cpu::Adc(uint32_t low, uint32_t high) { int result = 0; if (GetDecimalFlag()) { result = (A & 0xf) + (value & 0xf) + GetCarryFlag(); - if (result > 0x9) result = ((result + 0x6) & 0xf) + 0x10; + if (result > 0x9) + result = ((result + 0x6) & 0xf) + 0x10; result = (A & 0xf0) + (value & 0xf0) + result; - if (result > 0x9f) result = ((result + 0x60) & 0xff) + 0x100; + if (result > 0x9f) + result = ((result + 0x60) & 0xff) + 0x100; result = (A & 0xf00) + (value & 0xf00) + result; - if (result > 0x9ff) result = ((result + 0x600) & 0xfff) + 0x1000; + if (result > 0x9ff) + result = ((result + 0x600) & 0xfff) + 0x1000; result = (A & 0xf000) + (value & 0xf000) + result; } else { result = A + value + GetCarryFlag(); } SetOverflowFlag((A & 0x8000) == (value & 0x8000) && (value & 0x8000) != (result & 0x8000)); - if (GetDecimalFlag() && result > 0x9fff) result += 0x6000; + if (GetDecimalFlag() && result > 0x9fff) + result += 0x6000; SetCarryFlag(result > 0xffff); A = result; } @@ -82,7 +88,8 @@ void Cpu::Sbc(uint32_t low, uint32_t high) { } SetOverflowFlag((A & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80)); - if (GetDecimalFlag() && result < 0x100) result -= 0x60; + if (GetDecimalFlag() && result < 0x100) + result -= 0x60; SetCarryFlag(result > 0xff); A = (A & 0xff00) | (result & 0xff); } else { @@ -104,7 +111,8 @@ void Cpu::Sbc(uint32_t low, uint32_t high) { } SetOverflowFlag((A & 0x8000) == (value & 0x8000) && (value & 0x8000) != (result & 0x8000)); - if (GetDecimalFlag() && result < 0x10000) result -= 0x6000; + if (GetDecimalFlag() && result < 0x10000) + result -= 0x6000; SetCarryFlag(result > 0xffff); A = result; } diff --git a/src/app/emu/debug/apu_debugger.cc b/src/app/emu/debug/apu_debugger.cc index 327cfc15..84e2a9da 100644 --- a/src/app/emu/debug/apu_debugger.cc +++ b/src/app/emu/debug/apu_debugger.cc @@ -19,21 +19,23 @@ void ApuHandshakeTracker::Reset() { ipl_rom_enabled_ = true; transfer_counter_ = 0; total_bytes_transferred_ = 0; - + memset(cpu_ports_, 0, sizeof(cpu_ports_)); memset(spc_ports_, 0, sizeof(spc_ports_)); - + blocks_.clear(); port_history_.clear(); - + LOG_DEBUG("APU_DEBUG", "Handshake tracker reset"); } -void ApuHandshakeTracker::OnCpuPortWrite(uint8_t port, uint8_t value, uint32_t pc) { - if (port > 3) return; - +void ApuHandshakeTracker::OnCpuPortWrite(uint8_t port, uint8_t value, + uint32_t pc) { + if (port > 3) + return; + cpu_ports_[port] = value; - + // Check for handshake acknowledge if (phase_ == Phase::WAITING_BBAA && port == 0 && value == 0xCC) { UpdatePhase(Phase::HANDSHAKE_CC); @@ -42,19 +44,19 @@ void ApuHandshakeTracker::OnCpuPortWrite(uint8_t port, uint8_t value, uint32_t p LOG_INFO("APU_DEBUG", "✓ CPU sent handshake $CC at PC=$%06X", pc); return; } - + // Track transfer counter writes if (phase_ == Phase::HANDSHAKE_CC || phase_ == Phase::TRANSFER_ACTIVE) { if (port == 0) { transfer_counter_ = value; UpdatePhase(Phase::TRANSFER_ACTIVE); - LogPortWrite(true, port, value, pc, - absl::StrFormat("Counter=%d", transfer_counter_)); + LogPortWrite(true, port, value, pc, + absl::StrFormat("Counter=%d", transfer_counter_)); } else if (port == 1) { // F5 = continuation flag (0=more blocks, 1=final block) bool is_final = (value & 0x01) != 0; - LogPortWrite(true, port, value, pc, - is_final ? "FINAL BLOCK" : "More blocks"); + LogPortWrite(true, port, value, pc, + is_final ? "FINAL BLOCK" : "More blocks"); } else if (port == 2 || port == 3) { // F6:F7 = destination address LogPortWrite(true, port, value, pc, "Dest addr"); @@ -64,44 +66,48 @@ void ApuHandshakeTracker::OnCpuPortWrite(uint8_t port, uint8_t value, uint32_t p } } -void ApuHandshakeTracker::OnSpcPortWrite(uint8_t port, uint8_t value, uint16_t pc) { - if (port > 3) return; - +void ApuHandshakeTracker::OnSpcPortWrite(uint8_t port, uint8_t value, + uint16_t pc) { + if (port > 3) + return; + spc_ports_[port] = value; - + // Check for ready signal ($BBAA in F4:F5) if (phase_ == Phase::IPL_BOOT && port == 0 && value == 0xAA) { if (spc_ports_[1] == 0xBB || port == 1) { // Check if both ready UpdatePhase(Phase::WAITING_BBAA); LogPortWrite(false, port, value, pc, "READY SIGNAL $BBAA"); - LOG_INFO("APU_DEBUG", "✓ SPC ready signal: F4=$AA F5=$BB at PC=$%04X", pc); + LOG_INFO("APU_DEBUG", "✓ SPC ready signal: F4=$AA F5=$BB at PC=$%04X", + pc); return; } } - + if (phase_ == Phase::IPL_BOOT && port == 1 && value == 0xBB) { if (spc_ports_[0] == 0xAA) { UpdatePhase(Phase::WAITING_BBAA); LogPortWrite(false, port, value, pc, "READY SIGNAL $BBAA"); - LOG_INFO("APU_DEBUG", "✓ SPC ready signal: F4=$AA F5=$BB at PC=$%04X", pc); + LOG_INFO("APU_DEBUG", "✓ SPC ready signal: F4=$AA F5=$BB at PC=$%04X", + pc); return; } } - + // Track counter echo during transfer if (phase_ == Phase::TRANSFER_ACTIVE && port == 0) { int echoed_counter = value; if (echoed_counter == transfer_counter_) { total_bytes_transferred_++; - LogPortWrite(false, port, value, pc, - absl::StrFormat("Echo counter=%d (byte %d)", - echoed_counter, total_bytes_transferred_)); + LogPortWrite(false, port, value, pc, + absl::StrFormat("Echo counter=%d (byte %d)", echoed_counter, + total_bytes_transferred_)); } else { - LogPortWrite(false, port, value, pc, - absl::StrFormat("Counter mismatch! Expected=%d Got=%d", - transfer_counter_, echoed_counter)); + LogPortWrite(false, port, value, pc, + absl::StrFormat("Counter mismatch! Expected=%d Got=%d", + transfer_counter_, echoed_counter)); LOG_WARN("APU_DEBUG", "Counter mismatch at PC=$%04X: expected %d, got %d", - pc, transfer_counter_, echoed_counter); + pc, transfer_counter_, echoed_counter); } } else { LogPortWrite(false, port, value, pc, ""); @@ -114,13 +120,14 @@ void ApuHandshakeTracker::OnSpcPCChange(uint16_t old_pc, uint16_t new_pc) { UpdatePhase(Phase::IPL_BOOT); LOG_INFO("APU_DEBUG", "✓ SPC entered IPL ROM at PC=$%04X", new_pc); } - + // Detect IPL ROM disable (jump to uploaded driver) if (ipl_rom_enabled_ && new_pc < 0xFFC0) { ipl_rom_enabled_ = false; if (phase_ == Phase::TRANSFER_ACTIVE) { UpdatePhase(Phase::TRANSFER_DONE); - LOG_INFO("APU_DEBUG", "✓ Transfer complete! SPC jumped to $%04X (audio driver entry)", + LOG_INFO("APU_DEBUG", + "✓ Transfer complete! SPC jumped to $%04X (audio driver entry)", new_pc); } UpdatePhase(Phase::RUNNING); @@ -129,20 +136,27 @@ void ApuHandshakeTracker::OnSpcPCChange(uint16_t old_pc, uint16_t new_pc) { void ApuHandshakeTracker::UpdatePhase(Phase new_phase) { if (phase_ != new_phase) { - LOG_DEBUG("APU_DEBUG", "Phase change: %s → %s", - GetPhaseString().c_str(), - [new_phase]() { - switch (new_phase) { - case Phase::RESET: return "RESET"; - case Phase::IPL_BOOT: return "IPL_BOOT"; - case Phase::WAITING_BBAA: return "WAITING_BBAA"; - case Phase::HANDSHAKE_CC: return "HANDSHAKE_CC"; - case Phase::TRANSFER_ACTIVE: return "TRANSFER_ACTIVE"; - case Phase::TRANSFER_DONE: return "TRANSFER_DONE"; - case Phase::RUNNING: return "RUNNING"; - default: return "UNKNOWN"; - } - }()); + LOG_DEBUG("APU_DEBUG", "Phase change: %s → %s", GetPhaseString().c_str(), + [new_phase]() { + switch (new_phase) { + case Phase::RESET: + return "RESET"; + case Phase::IPL_BOOT: + return "IPL_BOOT"; + case Phase::WAITING_BBAA: + return "WAITING_BBAA"; + case Phase::HANDSHAKE_CC: + return "HANDSHAKE_CC"; + case Phase::TRANSFER_ACTIVE: + return "TRANSFER_ACTIVE"; + case Phase::TRANSFER_DONE: + return "TRANSFER_DONE"; + case Phase::RUNNING: + return "RUNNING"; + default: + return "UNKNOWN"; + } + }()); phase_ = new_phase; } } @@ -156,9 +170,9 @@ void ApuHandshakeTracker::LogPortWrite(bool is_cpu, uint8_t port, uint8_t value, entry.value = value; entry.is_cpu = is_cpu; entry.description = desc; - + port_history_.push_back(entry); - + // Keep history bounded if (port_history_.size() > kMaxHistorySize) { port_history_.pop_front(); @@ -167,49 +181,53 @@ void ApuHandshakeTracker::LogPortWrite(bool is_cpu, uint8_t port, uint8_t value, std::string ApuHandshakeTracker::GetPhaseString() const { switch (phase_) { - case Phase::RESET: return "RESET"; - case Phase::IPL_BOOT: return "IPL_BOOT"; - case Phase::WAITING_BBAA: return "WAITING_BBAA"; - case Phase::HANDSHAKE_CC: return "HANDSHAKE_CC"; - case Phase::TRANSFER_ACTIVE: return "TRANSFER_ACTIVE"; - case Phase::TRANSFER_DONE: return "TRANSFER_DONE"; - case Phase::RUNNING: return "RUNNING"; - default: return "UNKNOWN"; + case Phase::RESET: + return "RESET"; + case Phase::IPL_BOOT: + return "IPL_BOOT"; + case Phase::WAITING_BBAA: + return "WAITING_BBAA"; + case Phase::HANDSHAKE_CC: + return "HANDSHAKE_CC"; + case Phase::TRANSFER_ACTIVE: + return "TRANSFER_ACTIVE"; + case Phase::TRANSFER_DONE: + return "TRANSFER_DONE"; + case Phase::RUNNING: + return "RUNNING"; + default: + return "UNKNOWN"; } } std::string ApuHandshakeTracker::GetStatusSummary() const { - return absl::StrFormat( - "Phase: %s | Handshake: %s | Bytes: %d | Blocks: %d", - GetPhaseString(), - handshake_complete_ ? "✓" : "✗", - total_bytes_transferred_, - blocks_.size()); + return absl::StrFormat("Phase: %s | Handshake: %s | Bytes: %d | Blocks: %d", + GetPhaseString(), handshake_complete_ ? "✓" : "✗", + total_bytes_transferred_, blocks_.size()); } std::string ApuHandshakeTracker::GetTransferProgress() const { if (phase_ != Phase::TRANSFER_ACTIVE && phase_ != Phase::TRANSFER_DONE) { return ""; } - + // Estimate progress (typical ALTTP upload is ~8KB) int estimated_total = 8192; int percent = (total_bytes_transferred_ * 100) / estimated_total; percent = std::min(percent, 100); - + int bar_width = 20; int filled = (percent * bar_width) / 100; - + std::string bar = "["; for (int i = 0; i < bar_width; ++i) { bar += (i < filled) ? "█" : "░"; } bar += absl::StrFormat("] %d%%", percent); - + return bar; } } // namespace debug } // namespace emu } // namespace yaze - diff --git a/src/app/emu/debug/apu_debugger.h b/src/app/emu/debug/apu_debugger.h index c73a2797..f598f8f4 100644 --- a/src/app/emu/debug/apu_debugger.h +++ b/src/app/emu/debug/apu_debugger.h @@ -4,9 +4,9 @@ #define YAZE_APP_EMU_DEBUG_APU_DEBUGGER_H #include +#include #include #include -#include namespace yaze { namespace emu { @@ -21,21 +21,21 @@ namespace debug { class ApuHandshakeTracker { public: enum class Phase { - RESET, // Initial state - IPL_BOOT, // SPC700 executing IPL ROM - WAITING_BBAA, // CPU waiting for SPC ready signal ($BBAA) - HANDSHAKE_CC, // CPU sent $CC acknowledge - TRANSFER_ACTIVE, // Data transfer in progress - TRANSFER_DONE, // Audio driver uploaded - RUNNING // SPC executing audio driver + RESET, // Initial state + IPL_BOOT, // SPC700 executing IPL ROM + WAITING_BBAA, // CPU waiting for SPC ready signal ($BBAA) + HANDSHAKE_CC, // CPU sent $CC acknowledge + TRANSFER_ACTIVE, // Data transfer in progress + TRANSFER_DONE, // Audio driver uploaded + RUNNING // SPC executing audio driver }; struct PortWrite { uint64_t timestamp; - uint16_t pc; // CPU or SPC program counter - uint8_t port; // 0-3 (F4-F7) + uint16_t pc; // CPU or SPC program counter + uint8_t port; // 0-3 (F4-F7) uint8_t value; - bool is_cpu; // true = CPU write, false = SPC write + bool is_cpu; // true = CPU write, false = SPC write std::string description; }; @@ -52,44 +52,44 @@ class ApuHandshakeTracker { void OnCpuPortWrite(uint8_t port, uint8_t value, uint32_t pc); void OnSpcPortWrite(uint8_t port, uint8_t value, uint16_t pc); void OnSpcPCChange(uint16_t old_pc, uint16_t new_pc); - + // State queries Phase GetPhase() const { return phase_; } bool IsHandshakeComplete() const { return handshake_complete_; } bool IsTransferActive() const { return phase_ == Phase::TRANSFER_ACTIVE; } int GetBytesTransferred() const { return total_bytes_transferred_; } int GetBlockCount() const { return blocks_.size(); } - + // Get port write history const std::deque& GetPortHistory() const { return port_history_; } const std::vector& GetBlocks() const { return blocks_; } - + // Visualization std::string GetPhaseString() const; std::string GetStatusSummary() const; std::string GetTransferProgress() const; // Returns progress bar string - + // Reset tracking void Reset(); - + private: void UpdatePhase(Phase new_phase); - void LogPortWrite(bool is_cpu, uint8_t port, uint8_t value, uint32_t pc, - const std::string& desc); - + void LogPortWrite(bool is_cpu, uint8_t port, uint8_t value, uint32_t pc, + const std::string& desc); + Phase phase_ = Phase::RESET; bool handshake_complete_ = false; bool ipl_rom_enabled_ = true; - + uint8_t cpu_ports_[4] = {0}; // CPU → SPC (in_ports from SPC perspective) uint8_t spc_ports_[4] = {0}; // SPC → CPU (out_ports from SPC perspective) - + int transfer_counter_ = 0; int total_bytes_transferred_ = 0; - + std::vector blocks_; std::deque port_history_; // Keep last 1000 writes - + static constexpr size_t kMaxHistorySize = 1000; }; @@ -98,4 +98,3 @@ class ApuHandshakeTracker { } // namespace yaze #endif // YAZE_APP_EMU_DEBUG_APU_DEBUGGER_H - diff --git a/src/app/emu/debug/breakpoint_manager.cc b/src/app/emu/debug/breakpoint_manager.cc index 9a3157ea..001051fa 100644 --- a/src/app/emu/debug/breakpoint_manager.cc +++ b/src/app/emu/debug/breakpoint_manager.cc @@ -6,7 +6,8 @@ namespace yaze { namespace emu { -uint32_t BreakpointManager::AddBreakpoint(uint32_t address, Type type, CpuType cpu, +uint32_t BreakpointManager::AddBreakpoint(uint32_t address, Type type, + CpuType cpu, const std::string& condition, const std::string& description) { Breakpoint bp; @@ -17,16 +18,17 @@ uint32_t BreakpointManager::AddBreakpoint(uint32_t address, Type type, CpuType c bp.enabled = true; bp.condition = condition; bp.hit_count = 0; - bp.description = description.empty() - ? (cpu == CpuType::CPU_65816 ? "CPU Breakpoint" : "SPC700 Breakpoint") - : description; - + bp.description = + description.empty() + ? (cpu == CpuType::CPU_65816 ? "CPU Breakpoint" : "SPC700 Breakpoint") + : description; + breakpoints_[bp.id] = bp; - + LOG_INFO("Breakpoint", "Added breakpoint #%d: %s at $%06X (type=%d, cpu=%d)", - bp.id, bp.description.c_str(), address, static_cast(type), + bp.id, bp.description.c_str(), address, static_cast(type), static_cast(cpu)); - + return bp.id; } @@ -42,7 +44,8 @@ void BreakpointManager::SetEnabled(uint32_t id, bool enabled) { auto it = breakpoints_.find(id); if (it != breakpoints_.end()) { it->second.enabled = enabled; - LOG_INFO("Breakpoint", "Breakpoint #%d %s", id, enabled ? "enabled" : "disabled"); + LOG_INFO("Breakpoint", "Breakpoint #%d %s", id, + enabled ? "enabled" : "disabled"); } } @@ -51,33 +54,34 @@ bool BreakpointManager::ShouldBreakOnExecute(uint32_t pc, CpuType cpu) { if (!bp.enabled || bp.cpu != cpu || bp.type != Type::EXECUTE) { continue; } - + if (bp.address == pc) { bp.hit_count++; last_hit_ = &bp; - + // Check condition if present if (!bp.condition.empty()) { if (!EvaluateCondition(bp.condition, pc, pc, 0)) { continue; // Condition not met } } - - LOG_INFO("Breakpoint", "Hit breakpoint #%d at PC=$%06X (hits=%d)", - id, pc, bp.hit_count); + + LOG_INFO("Breakpoint", "Hit breakpoint #%d at PC=$%06X (hits=%d)", id, pc, + bp.hit_count); return true; } } return false; } -bool BreakpointManager::ShouldBreakOnMemoryAccess(uint32_t address, bool is_write, - uint8_t value, uint32_t pc) { +bool BreakpointManager::ShouldBreakOnMemoryAccess(uint32_t address, + bool is_write, uint8_t value, + uint32_t pc) { for (auto& [id, bp] : breakpoints_) { if (!bp.enabled || bp.address != address) { continue; } - + // Check if this breakpoint applies to this access type bool applies = false; switch (bp.type) { @@ -93,52 +97,59 @@ bool BreakpointManager::ShouldBreakOnMemoryAccess(uint32_t address, bool is_writ default: continue; // Not a memory breakpoint } - + if (applies) { bp.hit_count++; last_hit_ = &bp; - + // Check condition if present if (!bp.condition.empty()) { if (!EvaluateCondition(bp.condition, pc, address, value)) { continue; } } - - LOG_INFO("Breakpoint", "Hit %s breakpoint #%d at $%06X (value=$%02X, PC=$%06X, hits=%d)", - is_write ? "WRITE" : "READ", id, address, value, pc, bp.hit_count); + + LOG_INFO( + "Breakpoint", + "Hit %s breakpoint #%d at $%06X (value=$%02X, PC=$%06X, hits=%d)", + is_write ? "WRITE" : "READ", id, address, value, pc, bp.hit_count); return true; } } return false; } -std::vector BreakpointManager::GetAllBreakpoints() const { +std::vector +BreakpointManager::GetAllBreakpoints() const { std::vector result; result.reserve(breakpoints_.size()); for (const auto& [id, bp] : breakpoints_) { result.push_back(bp); } // Sort by ID for consistent ordering - std::sort(result.begin(), result.end(), - [](const Breakpoint& a, const Breakpoint& b) { return a.id < b.id; }); + std::sort( + result.begin(), result.end(), + [](const Breakpoint& a, const Breakpoint& b) { return a.id < b.id; }); return result; } -std::vector BreakpointManager::GetBreakpoints(CpuType cpu) const { +std::vector BreakpointManager::GetBreakpoints( + CpuType cpu) const { std::vector result; for (const auto& [id, bp] : breakpoints_) { if (bp.cpu == cpu) { result.push_back(bp); } } - std::sort(result.begin(), result.end(), - [](const Breakpoint& a, const Breakpoint& b) { return a.id < b.id; }); + std::sort( + result.begin(), result.end(), + [](const Breakpoint& a, const Breakpoint& b) { return a.id < b.id; }); return result; } void BreakpointManager::ClearAll() { - LOG_INFO("Breakpoint", "Cleared all breakpoints (%zu total)", breakpoints_.size()); + LOG_INFO("Breakpoint", "Cleared all breakpoints (%zu total)", + breakpoints_.size()); breakpoints_.clear(); last_hit_ = nullptr; } @@ -165,18 +176,18 @@ void BreakpointManager::ResetHitCounts() { } bool BreakpointManager::EvaluateCondition(const std::string& condition, - uint32_t pc, uint32_t address, - uint8_t value) { + uint32_t pc, uint32_t address, + uint8_t value) { // Simple condition evaluation for now // Future: Could integrate Lua or expression parser - + if (condition.empty()) { return true; // No condition = always true } - + // Support simple comparisons: "value > 10", "value == 0xFF", etc. // Format: "value OPERATOR number" - + // For now, just return true (conditions not implemented yet) // TODO: Implement proper expression evaluation return true; @@ -184,4 +195,3 @@ bool BreakpointManager::EvaluateCondition(const std::string& condition, } // namespace emu } // namespace yaze - diff --git a/src/app/emu/debug/breakpoint_manager.h b/src/app/emu/debug/breakpoint_manager.h index 1d8b8826..1ee23d54 100644 --- a/src/app/emu/debug/breakpoint_manager.h +++ b/src/app/emu/debug/breakpoint_manager.h @@ -32,12 +32,12 @@ class BreakpointManager { ACCESS, // Break when this address is read OR written CONDITIONAL // Break when condition evaluates to true }; - + enum class CpuType { - CPU_65816, // Main CPU - SPC700 // Audio CPU + CPU_65816, // Main CPU + SPC700 // Audio CPU }; - + struct Breakpoint { uint32_t id; uint32_t address; @@ -47,14 +47,14 @@ class BreakpointManager { std::string condition; // For conditional breakpoints (e.g., "A > 0x10") uint32_t hit_count; std::string description; // User-friendly label - + // Optional callback for advanced logic std::function callback; }; - + BreakpointManager() = default; ~BreakpointManager() = default; - + /** * @brief Add a new breakpoint * @param address Memory address or PC value @@ -67,17 +67,17 @@ class BreakpointManager { uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string& condition = "", const std::string& description = ""); - + /** * @brief Remove a breakpoint by ID */ void RemoveBreakpoint(uint32_t id); - + /** * @brief Enable or disable a breakpoint */ void SetEnabled(uint32_t id, bool enabled); - + /** * @brief Check if execution should break at this address * @param pc Current program counter @@ -85,7 +85,7 @@ class BreakpointManager { * @return true if breakpoint hit */ bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu); - + /** * @brief Check if execution should break on memory access * @param address Memory address being accessed @@ -94,45 +94,45 @@ class BreakpointManager { * @param pc Current program counter (for logging) * @return true if breakpoint hit */ - bool ShouldBreakOnMemoryAccess(uint32_t address, bool is_write, - uint8_t value, uint32_t pc); - + bool ShouldBreakOnMemoryAccess(uint32_t address, bool is_write, uint8_t value, + uint32_t pc); + /** * @brief Get all breakpoints */ std::vector GetAllBreakpoints() const; - + /** * @brief Get breakpoints for specific CPU */ std::vector GetBreakpoints(CpuType cpu) const; - + /** * @brief Clear all breakpoints */ void ClearAll(); - + /** * @brief Clear all breakpoints for specific CPU */ void ClearAll(CpuType cpu); - + /** * @brief Get the last breakpoint that was hit */ const Breakpoint* GetLastHit() const { return last_hit_; } - + /** * @brief Reset hit counts for all breakpoints */ void ResetHitCounts(); - + private: std::unordered_map breakpoints_; uint32_t next_id_ = 1; const Breakpoint* last_hit_ = nullptr; - - bool EvaluateCondition(const std::string& condition, uint32_t pc, + + bool EvaluateCondition(const std::string& condition, uint32_t pc, uint32_t address, uint8_t value); }; @@ -140,4 +140,3 @@ class BreakpointManager { } // namespace yaze #endif // YAZE_APP_EMU_DEBUG_BREAKPOINT_MANAGER_H - diff --git a/src/app/emu/debug/disassembly_viewer.cc b/src/app/emu/debug/disassembly_viewer.cc index 6c9bb6a6..4434b00f 100644 --- a/src/app/emu/debug/disassembly_viewer.cc +++ b/src/app/emu/debug/disassembly_viewer.cc @@ -16,26 +16,29 @@ namespace debug { namespace { // Color scheme for retro hacker aesthetic -constexpr ImVec4 kColorAddress(0.4f, 0.8f, 1.0f, 1.0f); // Cyan for addresses -constexpr ImVec4 kColorOpcode(0.8f, 0.8f, 0.8f, 1.0f); // Light gray for opcodes -constexpr ImVec4 kColorMnemonic(1.0f, 0.8f, 0.2f, 1.0f); // Gold for mnemonics -constexpr ImVec4 kColorOperand(0.6f, 1.0f, 0.6f, 1.0f); // Light green for operands -constexpr ImVec4 kColorComment(0.5f, 0.5f, 0.5f, 1.0f); // Gray for comments -constexpr ImVec4 kColorCurrentPC(1.0f, 0.3f, 0.3f, 1.0f); // Red for current PC -constexpr ImVec4 kColorBreakpoint(1.0f, 0.0f, 0.0f, 1.0f); // Bright red for breakpoints -constexpr ImVec4 kColorHotPath(1.0f, 0.6f, 0.0f, 1.0f); // Orange for hot paths +constexpr ImVec4 kColorAddress(0.4f, 0.8f, 1.0f, 1.0f); // Cyan for addresses +constexpr ImVec4 kColorOpcode(0.8f, 0.8f, 0.8f, + 1.0f); // Light gray for opcodes +constexpr ImVec4 kColorMnemonic(1.0f, 0.8f, 0.2f, 1.0f); // Gold for mnemonics +constexpr ImVec4 kColorOperand(0.6f, 1.0f, 0.6f, + 1.0f); // Light green for operands +constexpr ImVec4 kColorComment(0.5f, 0.5f, 0.5f, 1.0f); // Gray for comments +constexpr ImVec4 kColorCurrentPC(1.0f, 0.3f, 0.3f, 1.0f); // Red for current PC +constexpr ImVec4 kColorBreakpoint(1.0f, 0.0f, 0.0f, + 1.0f); // Bright red for breakpoints +constexpr ImVec4 kColorHotPath(1.0f, 0.6f, 0.0f, 1.0f); // Orange for hot paths } // namespace void DisassemblyViewer::RecordInstruction(uint32_t address, uint8_t opcode, - const std::vector& operands, - const std::string& mnemonic, - const std::string& operand_str) { + const std::vector& operands, + const std::string& mnemonic, + const std::string& operand_str) { // Skip if recording disabled (for performance) if (!recording_enabled_) { return; } - + auto it = instructions_.find(address); if (it != instructions_.end()) { // Instruction already recorded, just increment execution count @@ -46,7 +49,7 @@ void DisassemblyViewer::RecordInstruction(uint32_t address, uint8_t opcode, // Trim to 80% of max to avoid constant trimming TrimToSize(max_instructions_ * 0.8); } - + // New instruction, add to map DisassemblyEntry entry; entry.address = address; @@ -58,7 +61,7 @@ void DisassemblyViewer::RecordInstruction(uint32_t address, uint8_t opcode, entry.execution_count = 1; entry.is_breakpoint = false; entry.is_current_pc = false; - + instructions_[address] = entry; } } @@ -67,18 +70,18 @@ void DisassemblyViewer::TrimToSize(size_t target_size) { if (instructions_.size() <= target_size) { return; } - + // Keep most-executed instructions // Remove least-executed ones std::vector> addr_counts; for (const auto& [addr, entry] : instructions_) { addr_counts.push_back({addr, entry.execution_count}); } - + // Sort by execution count (ascending) std::sort(addr_counts.begin(), addr_counts.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); - + // Remove least-executed instructions size_t to_remove = instructions_.size() - target_size; for (size_t i = 0; i < to_remove && i < addr_counts.size(); i++) { @@ -86,15 +89,15 @@ void DisassemblyViewer::TrimToSize(size_t target_size) { } } -void DisassemblyViewer::Render(uint32_t current_pc, +void DisassemblyViewer::Render(uint32_t current_pc, const std::vector& breakpoints) { // Update current PC and breakpoint flags for (auto& [addr, entry] : instructions_) { entry.is_current_pc = (addr == current_pc); - entry.is_breakpoint = std::find(breakpoints.begin(), breakpoints.end(), addr) - != breakpoints.end(); + entry.is_breakpoint = std::find(breakpoints.begin(), breakpoints.end(), + addr) != breakpoints.end(); } - + RenderToolbar(); RenderSearchBar(); RenderDisassemblyTable(current_pc, breakpoints); @@ -109,7 +112,7 @@ void DisassemblyViewer::RenderToolbar() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Clear all recorded instructions"); } - + ImGui::TableNextColumn(); if (ImGui::Button(ICON_MD_SAVE " Export")) { // TODO: Open file dialog and export @@ -118,7 +121,7 @@ void DisassemblyViewer::RenderToolbar() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Export disassembly to file"); } - + ImGui::TableNextColumn(); if (ImGui::Checkbox("Auto-scroll", &auto_scroll_)) { // Toggle auto-scroll @@ -126,7 +129,7 @@ void DisassemblyViewer::RenderToolbar() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Auto-scroll to current PC"); } - + ImGui::TableNextColumn(); if (ImGui::Checkbox("Exec Count", &show_execution_counts_)) { // Toggle execution count display @@ -134,7 +137,7 @@ void DisassemblyViewer::RenderToolbar() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Show execution counts"); } - + ImGui::TableNextColumn(); if (ImGui::Checkbox("Hex Dump", &show_hex_dump_)) { // Toggle hex dump display @@ -142,48 +145,51 @@ void DisassemblyViewer::RenderToolbar() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Show hex dump of instruction bytes"); } - + ImGui::TableNextColumn(); ImGui::Text(ICON_MD_MEMORY " %zu instructions", instructions_.size()); - + ImGui::EndTable(); } - + ImGui::Separator(); } void DisassemblyViewer::RenderSearchBar() { ImGui::PushItemWidth(-1.0f); - if (ImGui::InputTextWithHint("##DisasmSearch", ICON_MD_SEARCH " Search (address, mnemonic, operand)...", + if (ImGui::InputTextWithHint("##DisasmSearch", + ICON_MD_SEARCH + " Search (address, mnemonic, operand)...", search_filter_, IM_ARRAYSIZE(search_filter_))) { // Search filter updated } ImGui::PopItemWidth(); } -void DisassemblyViewer::RenderDisassemblyTable(uint32_t current_pc, - const std::vector& breakpoints) { +void DisassemblyViewer::RenderDisassemblyTable( + uint32_t current_pc, const std::vector& breakpoints) { // Table flags for professional disassembly view - ImGuiTableFlags flags = - ImGuiTableFlags_Borders | - ImGuiTableFlags_RowBg | - ImGuiTableFlags_ScrollY | - ImGuiTableFlags_Resizable | - ImGuiTableFlags_Sortable | - ImGuiTableFlags_Reorderable | - ImGuiTableFlags_Hideable; - + ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | + ImGuiTableFlags_Sortable | + ImGuiTableFlags_Reorderable | + ImGuiTableFlags_Hideable; + // Calculate column count based on optional columns int column_count = 4; // BP, Address, Mnemonic, Operand (always shown) - if (show_hex_dump_) column_count++; - if (show_execution_counts_) column_count++; - - if (!ImGui::BeginTable("##DisasmTable", column_count, flags, ImVec2(0.0f, 0.0f))) { + if (show_hex_dump_) + column_count++; + if (show_execution_counts_) + column_count++; + + if (!ImGui::BeginTable("##DisasmTable", column_count, flags, + ImVec2(0.0f, 0.0f))) { return; } - + // Setup columns - ImGui::TableSetupColumn(ICON_MD_CIRCLE, ImGuiTableColumnFlags_WidthFixed, 25.0f); // Breakpoint indicator + ImGui::TableSetupColumn(ICON_MD_CIRCLE, ImGuiTableColumnFlags_WidthFixed, + 25.0f); // Breakpoint indicator ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 80.0f); if (show_hex_dump_) { ImGui::TableSetupColumn("Hex", ImGuiTableColumnFlags_WidthFixed, 100.0f); @@ -191,35 +197,37 @@ void DisassemblyViewer::RenderDisassemblyTable(uint32_t current_pc, ImGui::TableSetupColumn("Mnemonic", ImGuiTableColumnFlags_WidthFixed, 80.0f); ImGui::TableSetupColumn("Operand", ImGuiTableColumnFlags_WidthStretch); if (show_execution_counts_) { - ImGui::TableSetupColumn(ICON_MD_TRENDING_UP " Count", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn(ICON_MD_TRENDING_UP " Count", + ImGuiTableColumnFlags_WidthFixed, 80.0f); } - + ImGui::TableSetupScrollFreeze(0, 1); // Freeze header row ImGui::TableHeadersRow(); - + // Render instructions ImGuiListClipper clipper; auto sorted_addrs = GetSortedAddresses(); clipper.Begin(sorted_addrs.size()); - + while (clipper.Step()) { for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) { uint32_t addr = sorted_addrs[row]; const auto& entry = instructions_[addr]; - + // Skip if doesn't pass filter if (!PassesFilter(entry)) { continue; } - + ImGui::TableNextRow(); - + // Highlight current PC row if (entry.is_current_pc) { - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, - ImGui::GetColorU32(ImVec4(0.3f, 0.0f, 0.0f, 0.5f))); + ImGui::TableSetBgColor( + ImGuiTableBgTarget_RowBg0, + ImGui::GetColorU32(ImVec4(0.3f, 0.0f, 0.0f, 0.5f))); } - + // Column 0: Breakpoint indicator ImGui::TableNextColumn(); if (entry.is_breakpoint) { @@ -227,30 +235,30 @@ void DisassemblyViewer::RenderDisassemblyTable(uint32_t current_pc, } else { ImGui::TextDisabled(" "); } - + // Column 1: Address (clickable) ImGui::TableNextColumn(); ImVec4 addr_color = GetAddressColor(entry, current_pc); - - std::string addr_str = absl::StrFormat("$%02X:%04X", - (addr >> 16) & 0xFF, addr & 0xFFFF); + + std::string addr_str = + absl::StrFormat("$%02X:%04X", (addr >> 16) & 0xFF, addr & 0xFFFF); if (ImGui::Selectable(addr_str.c_str(), selected_address_ == addr, - ImGuiSelectableFlags_SpanAllColumns)) { + ImGuiSelectableFlags_SpanAllColumns)) { selected_address_ = addr; } - + // Context menu on right-click if (ImGui::BeginPopupContextItem()) { RenderContextMenu(addr); ImGui::EndPopup(); } - + // Column 2: Hex dump (optional) if (show_hex_dump_) { ImGui::TableNextColumn(); ImGui::TextColored(kColorOpcode, "%s", FormatHexDump(entry).c_str()); } - + // Column 3: Mnemonic (clickable for documentation) ImGui::TableNextColumn(); ImVec4 mnemonic_color = GetMnemonicColor(entry); @@ -260,15 +268,15 @@ void DisassemblyViewer::RenderDisassemblyTable(uint32_t current_pc, if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Click for instruction documentation"); } - + // Column 4: Operand (clickable for jump-to-address) ImGui::TableNextColumn(); ImGui::TextColored(kColorOperand, "%s", entry.operand_str.c_str()); - + // Column 5: Execution count (optional) if (show_execution_counts_) { ImGui::TableNextColumn(); - + // Color-code by execution frequency (hot path highlighting) ImVec4 count_color = kColorComment; if (entry.execution_count > 10000) { @@ -276,59 +284,59 @@ void DisassemblyViewer::RenderDisassemblyTable(uint32_t current_pc, } else if (entry.execution_count > 1000) { count_color = ImVec4(0.8f, 0.8f, 0.3f, 1.0f); // Yellow } - + ImGui::TextColored(count_color, "%llu", entry.execution_count); } } } - + // Auto-scroll to current PC if (auto_scroll_ && scroll_to_address_ != current_pc) { // Find row index of current PC auto it = std::find(sorted_addrs.begin(), sorted_addrs.end(), current_pc); if (it != sorted_addrs.end()) { int row_index = std::distance(sorted_addrs.begin(), it); - ImGui::SetScrollY((row_index * ImGui::GetTextLineHeightWithSpacing()) - - (ImGui::GetWindowHeight() * 0.5f)); + ImGui::SetScrollY((row_index * ImGui::GetTextLineHeightWithSpacing()) - + (ImGui::GetWindowHeight() * 0.5f)); scroll_to_address_ = current_pc; } } - + ImGui::EndTable(); } void DisassemblyViewer::RenderContextMenu(uint32_t address) { auto& entry = instructions_[address]; - + if (ImGui::MenuItem(ICON_MD_FLAG " Toggle Breakpoint")) { // TODO: Implement breakpoint toggle callback } - + if (ImGui::MenuItem(ICON_MD_MY_LOCATION " Jump to Address")) { JumpToAddress(address); } - + ImGui::Separator(); - + if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy Address")) { ImGui::SetClipboardText(absl::StrFormat("$%06X", address).c_str()); } - + if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy Instruction")) { - std::string instr = absl::StrFormat("%s %s", entry.mnemonic.c_str(), - entry.operand_str.c_str()); + std::string instr = absl::StrFormat("%s %s", entry.mnemonic.c_str(), + entry.operand_str.c_str()); ImGui::SetClipboardText(instr.c_str()); } - + ImGui::Separator(); - + if (ImGui::MenuItem(ICON_MD_INFO " Show Info")) { // TODO: Show detailed instruction info } } -ImVec4 DisassemblyViewer::GetAddressColor(const DisassemblyEntry& entry, - uint32_t current_pc) const { +ImVec4 DisassemblyViewer::GetAddressColor(const DisassemblyEntry& entry, + uint32_t current_pc) const { if (entry.is_current_pc) { return kColorCurrentPC; } @@ -338,46 +346,48 @@ ImVec4 DisassemblyViewer::GetAddressColor(const DisassemblyEntry& entry, return kColorAddress; } -ImVec4 DisassemblyViewer::GetMnemonicColor(const DisassemblyEntry& entry) const { +ImVec4 DisassemblyViewer::GetMnemonicColor( + const DisassemblyEntry& entry) const { // Color-code by instruction type const std::string& mnemonic = entry.mnemonic; - + // Branches and jumps if (mnemonic.find('B') == 0 || mnemonic == "JMP" || mnemonic == "JSR" || mnemonic == "RTL" || mnemonic == "RTS" || mnemonic == "RTI") { return ImVec4(0.8f, 0.4f, 1.0f, 1.0f); // Purple for control flow } - + // Loads if (mnemonic.find("LD") == 0) { return ImVec4(0.4f, 1.0f, 0.4f, 1.0f); // Green for loads } - + // Stores if (mnemonic.find("ST") == 0) { return ImVec4(1.0f, 0.6f, 0.4f, 1.0f); // Orange for stores } - + return kColorMnemonic; } -std::string DisassemblyViewer::FormatHexDump(const DisassemblyEntry& entry) const { +std::string DisassemblyViewer::FormatHexDump( + const DisassemblyEntry& entry) const { std::stringstream ss; ss << std::hex << std::uppercase << std::setfill('0'); - + // Opcode ss << std::setw(2) << static_cast(entry.opcode); - + // Operands for (const auto& operand_byte : entry.operands) { ss << " " << std::setw(2) << static_cast(operand_byte); } - + // Pad to consistent width (3 bytes max) for (size_t i = entry.operands.size(); i < 2; i++) { ss << " "; } - + return ss.str(); } @@ -385,33 +395,33 @@ bool DisassemblyViewer::PassesFilter(const DisassemblyEntry& entry) const { if (search_filter_[0] == '\0') { return true; // No filter active } - + std::string filter_lower(search_filter_); - std::transform(filter_lower.begin(), filter_lower.end(), - filter_lower.begin(), ::tolower); - + std::transform(filter_lower.begin(), filter_lower.end(), filter_lower.begin(), + ::tolower); + // Check address std::string addr_str = absl::StrFormat("%06x", entry.address); if (addr_str.find(filter_lower) != std::string::npos) { return true; } - + // Check mnemonic std::string mnemonic_lower = entry.mnemonic; std::transform(mnemonic_lower.begin(), mnemonic_lower.end(), - mnemonic_lower.begin(), ::tolower); + mnemonic_lower.begin(), ::tolower); if (mnemonic_lower.find(filter_lower) != std::string::npos) { return true; } - + // Check operand std::string operand_lower = entry.operand_str; std::transform(operand_lower.begin(), operand_lower.end(), - operand_lower.begin(), ::tolower); + operand_lower.begin(), ::tolower); if (operand_lower.find(filter_lower) != std::string::npos) { return true; } - + return false; } @@ -426,24 +436,21 @@ bool DisassemblyViewer::ExportToFile(const std::string& filepath) const { if (!out.is_open()) { return false; } - + out << "; YAZE Disassembly Export\n"; out << "; Total instructions: " << instructions_.size() << "\n"; out << "; Generated: " << __DATE__ << " " << __TIME__ << "\n\n"; - + auto sorted_addrs = GetSortedAddresses(); for (uint32_t addr : sorted_addrs) { const auto& entry = instructions_.at(addr); - + out << absl::StrFormat("$%02X:%04X: %-8s %-6s %-20s ; exec=%llu\n", - (addr >> 16) & 0xFF, - addr & 0xFFFF, - FormatHexDump(entry).c_str(), - entry.mnemonic.c_str(), - entry.operand_str.c_str(), - entry.execution_count); + (addr >> 16) & 0xFF, addr & 0xFFFF, + FormatHexDump(entry).c_str(), entry.mnemonic.c_str(), + entry.operand_str.c_str(), entry.execution_count); } - + out.close(); return true; } @@ -451,17 +458,17 @@ bool DisassemblyViewer::ExportToFile(const std::string& filepath) const { void DisassemblyViewer::JumpToAddress(uint32_t address) { selected_address_ = address; scroll_to_address_ = 0; // Force scroll update - auto_scroll_ = false; // Disable auto-scroll temporarily + auto_scroll_ = false; // Disable auto-scroll temporarily } std::vector DisassemblyViewer::GetSortedAddresses() const { std::vector addrs; addrs.reserve(instructions_.size()); - + for (const auto& [addr, _] : instructions_) { addrs.push_back(addr); } - + std::sort(addrs.begin(), addrs.end()); return addrs; } @@ -469,4 +476,3 @@ std::vector DisassemblyViewer::GetSortedAddresses() const { } // namespace debug } // namespace emu } // namespace yaze - diff --git a/src/app/emu/debug/disassembly_viewer.h b/src/app/emu/debug/disassembly_viewer.h index 23867cd1..ab8e3efe 100644 --- a/src/app/emu/debug/disassembly_viewer.h +++ b/src/app/emu/debug/disassembly_viewer.h @@ -21,19 +21,24 @@ namespace debug { * @brief Represents a single disassembled instruction with metadata */ struct DisassemblyEntry { - uint32_t address; // Full 24-bit address (bank:offset) - uint8_t opcode; // The opcode byte + uint32_t address; // Full 24-bit address (bank:offset) + uint8_t opcode; // The opcode byte std::vector operands; // Operand bytes (0-2 bytes) - std::string mnemonic; // Instruction mnemonic (e.g., "LDA", "STA") - std::string operand_str; // Formatted operand string (e.g., "#$00", "($10),Y") - uint8_t size; // Total instruction size in bytes - uint64_t execution_count; // How many times this instruction was executed - bool is_breakpoint; // Whether a breakpoint is set at this address - bool is_current_pc; // Whether this is the current PC location - - DisassemblyEntry() - : address(0), opcode(0), size(1), execution_count(0), - is_breakpoint(false), is_current_pc(false) {} + std::string mnemonic; // Instruction mnemonic (e.g., "LDA", "STA") + std::string + operand_str; // Formatted operand string (e.g., "#$00", "($10),Y") + uint8_t size; // Total instruction size in bytes + uint64_t execution_count; // How many times this instruction was executed + bool is_breakpoint; // Whether a breakpoint is set at this address + bool is_current_pc; // Whether this is the current PC location + + DisassemblyEntry() + : address(0), + opcode(0), + size(1), + execution_count(0), + is_breakpoint(false), + is_current_pc(false) {} }; /** @@ -63,9 +68,9 @@ class DisassemblyViewer { * @param operand_str Formatted operand string */ void RecordInstruction(uint32_t address, uint8_t opcode, - const std::vector& operands, - const std::string& mnemonic, - const std::string& operand_str); + const std::vector& operands, + const std::string& mnemonic, + const std::string& operand_str); /** * @brief Render the disassembly viewer UI @@ -111,18 +116,18 @@ class DisassemblyViewer { * @brief Check if the disassembly viewer is available */ bool IsAvailable() const { return !instructions_.empty(); } - + /** * @brief Enable/disable recording (for performance) */ void SetRecording(bool enabled) { recording_enabled_ = enabled; } bool IsRecording() const { return recording_enabled_; } - + /** * @brief Set maximum number of instructions to keep */ void SetMaxInstructions(size_t max) { max_instructions_ = max; } - + /** * @brief Clear old instructions to save memory */ @@ -131,11 +136,11 @@ class DisassemblyViewer { private: // Sparse storage: only store executed instructions std::map instructions_; - + // Performance limits bool recording_enabled_ = true; size_t max_instructions_ = 10000; // Limit to prevent memory bloat - + // UI state char search_filter_[256] = ""; uint32_t selected_address_ = 0; @@ -143,19 +148,20 @@ class DisassemblyViewer { bool auto_scroll_ = true; bool show_execution_counts_ = true; bool show_hex_dump_ = true; - + // Rendering helpers void RenderToolbar(); - void RenderDisassemblyTable(uint32_t current_pc, + void RenderDisassemblyTable(uint32_t current_pc, const std::vector& breakpoints); void RenderContextMenu(uint32_t address); void RenderSearchBar(); - + // Formatting helpers - ImVec4 GetAddressColor(const DisassemblyEntry& entry, uint32_t current_pc) const; + ImVec4 GetAddressColor(const DisassemblyEntry& entry, + uint32_t current_pc) const; ImVec4 GetMnemonicColor(const DisassemblyEntry& entry) const; std::string FormatHexDump(const DisassemblyEntry& entry) const; - + // Filter helper bool PassesFilter(const DisassemblyEntry& entry) const; }; @@ -165,4 +171,3 @@ class DisassemblyViewer { } // namespace yaze #endif // YAZE_APP_EMU_DEBUG_DISASSEMBLY_VIEWER_H_ - diff --git a/src/app/emu/debug/watchpoint_manager.cc b/src/app/emu/debug/watchpoint_manager.cc index 6a0768e9..b32bb97e 100644 --- a/src/app/emu/debug/watchpoint_manager.cc +++ b/src/app/emu/debug/watchpoint_manager.cc @@ -1,14 +1,15 @@ #include "app/emu/debug/watchpoint_manager.h" -#include #include +#include #include "absl/strings/str_format.h" #include "util/log.h" namespace yaze { namespace emu { -uint32_t WatchpointManager::AddWatchpoint(uint32_t start_address, uint32_t end_address, +uint32_t WatchpointManager::AddWatchpoint(uint32_t start_address, + uint32_t end_address, bool track_reads, bool track_writes, bool break_on_access, const std::string& description) { @@ -20,15 +21,17 @@ uint32_t WatchpointManager::AddWatchpoint(uint32_t start_address, uint32_t end_a wp.track_writes = track_writes; wp.break_on_access = break_on_access; wp.enabled = true; - wp.description = description.empty() - ? absl::StrFormat("Watch $%06X-$%06X", start_address, end_address) - : description; - + wp.description = + description.empty() + ? absl::StrFormat("Watch $%06X-$%06X", start_address, end_address) + : description; + watchpoints_[wp.id] = wp; - + LOG_INFO("Watchpoint", "Added watchpoint #%d: %s (R=%d, W=%d, Break=%d)", - wp.id, wp.description.c_str(), track_reads, track_writes, break_on_access); - + wp.id, wp.description.c_str(), track_reads, track_writes, + break_on_access); + return wp.id; } @@ -44,26 +47,29 @@ void WatchpointManager::SetEnabled(uint32_t id, bool enabled) { auto it = watchpoints_.find(id); if (it != watchpoints_.end()) { it->second.enabled = enabled; - LOG_INFO("Watchpoint", "Watchpoint #%d %s", id, enabled ? "enabled" : "disabled"); + LOG_INFO("Watchpoint", "Watchpoint #%d %s", id, + enabled ? "enabled" : "disabled"); } } -bool WatchpointManager::OnMemoryAccess(uint32_t pc, uint32_t address, bool is_write, - uint8_t old_value, uint8_t new_value, +bool WatchpointManager::OnMemoryAccess(uint32_t pc, uint32_t address, + bool is_write, uint8_t old_value, + uint8_t new_value, uint64_t cycle_count) { bool should_break = false; - + for (auto& [id, wp] : watchpoints_) { if (!wp.enabled || !IsInRange(wp, address)) { continue; } - + // Check if this access type is tracked - bool should_log = (is_write && wp.track_writes) || (!is_write && wp.track_reads); + bool should_log = + (is_write && wp.track_writes) || (!is_write && wp.track_reads); if (!should_log) { continue; } - + // Log the access AccessLog log; log.pc = pc; @@ -73,41 +79,44 @@ bool WatchpointManager::OnMemoryAccess(uint32_t pc, uint32_t address, bool is_wr log.is_write = is_write; log.cycle_count = cycle_count; log.description = absl::StrFormat("%s at $%06X: $%02X -> $%02X (PC=$%06X)", - is_write ? "WRITE" : "READ", - address, old_value, new_value, pc); - + is_write ? "WRITE" : "READ", address, + old_value, new_value, pc); + wp.history.push_back(log); - + // Limit history size if (wp.history.size() > Watchpoint::kMaxHistorySize) { wp.history.pop_front(); } - + // Check if should break if (wp.break_on_access) { should_break = true; - LOG_INFO("Watchpoint", "Hit watchpoint #%d: %s", id, log.description.c_str()); + LOG_INFO("Watchpoint", "Hit watchpoint #%d: %s", id, + log.description.c_str()); } } - + return should_break; } -std::vector WatchpointManager::GetAllWatchpoints() const { +std::vector +WatchpointManager::GetAllWatchpoints() const { std::vector result; result.reserve(watchpoints_.size()); for (const auto& [id, wp] : watchpoints_) { result.push_back(wp); } - std::sort(result.begin(), result.end(), - [](const Watchpoint& a, const Watchpoint& b) { return a.id < b.id; }); + std::sort( + result.begin(), result.end(), + [](const Watchpoint& a, const Watchpoint& b) { return a.id < b.id; }); return result; } std::vector WatchpointManager::GetHistory( uint32_t address, int max_entries) const { std::vector result; - + for (const auto& [id, wp] : watchpoints_) { if (IsInRange(wp, address)) { for (const auto& log : wp.history) { @@ -120,12 +129,13 @@ std::vector WatchpointManager::GetHistory( } } } - + return result; } void WatchpointManager::ClearAll() { - LOG_INFO("Watchpoint", "Cleared all watchpoints (%zu total)", watchpoints_.size()); + LOG_INFO("Watchpoint", "Cleared all watchpoints (%zu total)", + watchpoints_.size()); watchpoints_.clear(); } @@ -141,20 +151,19 @@ bool WatchpointManager::ExportHistoryToCSV(const std::string& filepath) const { if (!out.is_open()) { return false; } - + // CSV Header out << "Watchpoint,PC,Address,Type,OldValue,NewValue,Cycle,Description\n"; - + for (const auto& [id, wp] : watchpoints_) { for (const auto& log : wp.history) { - out << absl::StrFormat("%d,$%06X,$%06X,%s,$%02X,$%02X,%llu,\"%s\"\n", - id, log.pc, log.address, - log.is_write ? "WRITE" : "READ", - log.old_value, log.new_value, log.cycle_count, - log.description); + out << absl::StrFormat("%d,$%06X,$%06X,%s,$%02X,$%02X,%llu,\"%s\"\n", id, + log.pc, log.address, + log.is_write ? "WRITE" : "READ", log.old_value, + log.new_value, log.cycle_count, log.description); } } - + out.close(); LOG_INFO("Watchpoint", "Exported watchpoint history to %s", filepath.c_str()); return true; @@ -162,4 +171,3 @@ bool WatchpointManager::ExportHistoryToCSV(const std::string& filepath) const { } // namespace emu } // namespace yaze - diff --git a/src/app/emu/debug/watchpoint_manager.h b/src/app/emu/debug/watchpoint_manager.h index 361652a8..0386e698 100644 --- a/src/app/emu/debug/watchpoint_manager.h +++ b/src/app/emu/debug/watchpoint_manager.h @@ -2,10 +2,10 @@ #define YAZE_APP_EMU_DEBUG_WATCHPOINT_MANAGER_H #include -#include -#include -#include #include +#include +#include +#include namespace yaze { namespace emu { @@ -26,15 +26,15 @@ namespace emu { class WatchpointManager { public: struct AccessLog { - uint32_t pc; // Where the access happened (program counter) - uint32_t address; // What address was accessed - uint8_t old_value; // Value before write (0 for reads) - uint8_t new_value; // Value after write / value read - bool is_write; // True for write, false for read - uint64_t cycle_count; // When it happened (CPU cycle) + uint32_t pc; // Where the access happened (program counter) + uint32_t address; // What address was accessed + uint8_t old_value; // Value before write (0 for reads) + uint8_t new_value; // Value after write / value read + bool is_write; // True for write, false for read + uint64_t cycle_count; // When it happened (CPU cycle) std::string description; // Optional description }; - + struct Watchpoint { uint32_t id; uint32_t start_address; @@ -44,15 +44,15 @@ class WatchpointManager { bool break_on_access; // If true, pause emulation on access bool enabled; std::string description; - + // Access history for this watchpoint std::deque history; static constexpr size_t kMaxHistorySize = 1000; }; - + WatchpointManager() = default; ~WatchpointManager() = default; - + /** * @brief Add a memory watchpoint * @param start_address Starting address of range to watch @@ -67,17 +67,17 @@ class WatchpointManager { bool track_reads, bool track_writes, bool break_on_access = false, const std::string& description = ""); - + /** * @brief Remove a watchpoint */ void RemoveWatchpoint(uint32_t id); - + /** * @brief Enable or disable a watchpoint */ void SetEnabled(uint32_t id, bool enabled); - + /** * @brief Check if memory access should break/log * @param pc Current program counter @@ -89,42 +89,44 @@ class WatchpointManager { * @return true if should break execution */ bool OnMemoryAccess(uint32_t pc, uint32_t address, bool is_write, - uint8_t old_value, uint8_t new_value, uint64_t cycle_count); - + uint8_t old_value, uint8_t new_value, + uint64_t cycle_count); + /** * @brief Get all watchpoints */ std::vector GetAllWatchpoints() const; - + /** * @brief Get access history for a specific address * @param address Address to query * @param max_entries Maximum number of entries to return * @return Vector of access logs */ - std::vector GetHistory(uint32_t address, int max_entries = 100) const; - + std::vector GetHistory(uint32_t address, + int max_entries = 100) const; + /** * @brief Clear all watchpoints */ void ClearAll(); - + /** * @brief Clear history for all watchpoints */ void ClearHistory(); - + /** * @brief Export access history to CSV * @param filepath Output file path * @return true if successful */ bool ExportHistoryToCSV(const std::string& filepath) const; - + private: std::unordered_map watchpoints_; uint32_t next_id_ = 1; - + // Check if address is within watchpoint range bool IsInRange(const Watchpoint& wp, uint32_t address) const { return address >= wp.start_address && address <= wp.end_address; @@ -135,4 +137,3 @@ class WatchpointManager { } // namespace yaze #endif // YAZE_APP_EMU_DEBUG_WATCHPOINT_MANAGER_H - diff --git a/src/app/emu/emu.cc b/src/app/emu/emu.cc index 6717801f..2944478b 100644 --- a/src/app/emu/emu.cc +++ b/src/app/emu/emu.cc @@ -13,9 +13,9 @@ #include "absl/flags/flag.h" #include "absl/flags/parse.h" #include "app/emu/snes.h" -#include "app/rom.h" #include "app/gfx/backend/irenderer.h" #include "app/gfx/backend/sdl2_renderer.h" +#include "app/rom.h" #include "util/sdl_deleter.h" ABSL_FLAG(std::string, emu_rom, "", "Path to the ROM file to load."); @@ -23,13 +23,15 @@ ABSL_FLAG(bool, emu_no_gui, false, "Disable GUI and run in headless mode."); ABSL_FLAG(std::string, emu_load_state, "", "Load emulator state from a file."); ABSL_FLAG(std::string, emu_dump_state, "", "Dump emulator state to a file."); ABSL_FLAG(int, emu_frames, 0, "Number of frames to run the emulator for."); -ABSL_FLAG(int, emu_max_frames, 180, "Maximum frames to run before auto-exit (0=infinite, default=180/3 seconds)."); +ABSL_FLAG(int, emu_max_frames, 180, + "Maximum frames to run before auto-exit (0=infinite, default=180/3 " + "seconds)."); ABSL_FLAG(bool, emu_debug_apu, false, "Enable detailed APU/SPC700 logging."); ABSL_FLAG(bool, emu_debug_cpu, false, "Enable detailed CPU execution logging."); using yaze::util::SDL_Deleter; -int main(int argc, char **argv) { +int main(int argc, char** argv) { absl::InitializeSymbolizer(argv[0]); absl::FailureSignalHandlerOptions options; @@ -77,9 +79,8 @@ int main(int argc, char **argv) { // Create window and renderer with RAII smart pointers std::unique_ptr window_( - SDL_CreateWindow("Yaze Emulator", - SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - 512, 480, + SDL_CreateWindow("Yaze Emulator", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, 512, 480, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI), SDL_Deleter()); if (!window_) { @@ -91,9 +92,9 @@ int main(int argc, char **argv) { // Create and initialize the renderer auto renderer = std::make_unique(); if (!renderer->Initialize(window_.get())) { - printf("Failed to initialize renderer\n"); - SDL_Quit(); - return EXIT_FAILURE; + printf("Failed to initialize renderer\n"); + SDL_Quit(); + return EXIT_FAILURE; } // Initialize audio system @@ -106,15 +107,17 @@ int main(int argc, char **argv) { want.callback = nullptr; // Use audio queue SDL_AudioSpec have; - SDL_AudioDeviceID audio_device = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0); + SDL_AudioDeviceID audio_device = + SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0); if (audio_device == 0) { printf("SDL_OpenAudioDevice failed: %s\n", SDL_GetError()); SDL_Quit(); return EXIT_FAILURE; } - + // Allocate audio buffer using unique_ptr for automatic cleanup - std::unique_ptr audio_buffer(new int16_t[kAudioFrequency / 50 * 4]); + std::unique_ptr audio_buffer( + new int16_t[kAudioFrequency / 50 * 4]); SDL_PauseAudioDevice(audio_device, 0); // Create PPU texture for rendering @@ -135,7 +138,7 @@ int main(int argc, char **argv) { bool loaded = false; int frame_count = 0; const int max_frames = absl::GetFlag(FLAGS_emu_max_frames); - + // Timing management const uint64_t count_frequency = SDL_GetPerformanceFrequency(); uint64_t last_count = SDL_GetPerformanceCounter(); @@ -149,7 +152,7 @@ int main(int argc, char **argv) { if (rom_path.empty()) { rom_path = "assets/zelda3.sfc"; // Default to zelda3 in assets } - + if (!rom_.LoadFromFile(rom_path).ok()) { printf("Failed to load ROM: %s\n", rom_path.c_str()); return EXIT_FAILURE; @@ -159,14 +162,15 @@ int main(int argc, char **argv) { printf("Loaded ROM: %s (%zu bytes)\n", rom_path.c_str(), rom_.size()); rom_data_ = rom_.vector(); snes_.Init(rom_data_); - + // Calculate timing based on PAL/NTSC const bool is_pal = snes_.memory().pal_timing(); const double refresh_rate = is_pal ? 50.0 : 60.0; wanted_frame_time = 1.0 / refresh_rate; wanted_samples = kAudioFrequency / static_cast(refresh_rate); - - printf("Emulator initialized: %s mode (%.1f Hz)\n", is_pal ? "PAL" : "NTSC", refresh_rate); + + printf("Emulator initialized: %s mode (%.1f Hz)\n", is_pal ? "PAL" : "NTSC", + refresh_rate); loaded = true; } @@ -177,12 +181,12 @@ int main(int argc, char **argv) { if (rom_.LoadFromFile(event.drop.file).ok() && rom_.is_loaded()) { rom_data_ = rom_.vector(); snes_.Init(rom_data_); - + const bool is_pal = snes_.memory().pal_timing(); const double refresh_rate = is_pal ? 50.0 : 60.0; wanted_frame_time = 1.0 / refresh_rate; wanted_samples = kAudioFrequency / static_cast(refresh_rate); - + printf("Loaded new ROM via drag-and-drop: %s\n", event.drop.file); frame_count = 0; // Reset frame counter loaded = true; @@ -212,9 +216,10 @@ int main(int argc, char **argv) { const uint64_t current_count = SDL_GetPerformanceCounter(); const uint64_t delta = current_count - last_count; last_count = current_count; - const double seconds = static_cast(delta) / static_cast(count_frequency); + const double seconds = + static_cast(delta) / static_cast(count_frequency); time_adder += seconds; - + // Run frame if enough time has elapsed (allow 2ms grace period) while (time_adder >= wanted_frame_time - 0.002) { time_adder -= wanted_frame_time; @@ -227,12 +232,15 @@ int main(int argc, char **argv) { static uint16_t last_cpu_pc = 0; static int stuck_count = 0; uint16_t current_cpu_pc = snes_.cpu().PC; - - if (current_cpu_pc == last_cpu_pc && current_cpu_pc >= 0x88B0 && current_cpu_pc <= 0x88C0) { + + if (current_cpu_pc == last_cpu_pc && current_cpu_pc >= 0x88B0 && + current_cpu_pc <= 0x88C0) { stuck_count++; if (stuck_count > 180 && frame_count % 60 == 0) { - printf("[WARNING] CPU stuck at $%02X:%04X for %d frames (APU deadlock?)\n", - snes_.cpu().PB, current_cpu_pc, stuck_count); + printf( + "[WARNING] CPU stuck at $%02X:%04X for %d frames (APU " + "deadlock?)\n", + snes_.cpu().PB, current_cpu_pc, stuck_count); } } else { stuck_count = 0; @@ -241,14 +249,15 @@ int main(int argc, char **argv) { // Print status every 60 frames (1 second) if (frame_count % 60 == 0) { - printf("[Frame %d] CPU=$%02X:%04X SPC=$%04X APU_cycles=%llu\n", - frame_count, snes_.cpu().PB, snes_.cpu().PC, + printf("[Frame %d] CPU=$%02X:%04X SPC=$%04X APU_cycles=%llu\n", + frame_count, snes_.cpu().PB, snes_.cpu().PC, snes_.apu().spc700().PC, snes_.apu().GetCycles()); } // Auto-exit after max_frames (if set) if (max_frames > 0 && frame_count >= max_frames) { - printf("\n[EMULATOR] Reached max frames (%d), shutting down...\n", max_frames); + printf("\n[EMULATOR] Reached max frames (%d), shutting down...\n", + max_frames); printf("[EMULATOR] Final state: CPU=$%02X:%04X SPC=$%04X\n", snes_.cpu().PB, snes_.cpu().PC, snes_.apu().spc700().PC); running = false; @@ -258,15 +267,17 @@ int main(int argc, char **argv) { // Generate audio samples and queue them snes_.SetSamples(audio_buffer.get(), wanted_samples); const uint32_t queued_size = SDL_GetQueuedAudioSize(audio_device); - const uint32_t max_queued = wanted_samples * 4 * 6; // Keep up to 6 frames queued + const uint32_t max_queued = + wanted_samples * 4 * 6; // Keep up to 6 frames queued if (queued_size <= max_queued) { SDL_QueueAudio(audio_device, audio_buffer.get(), wanted_samples * 4); } // Render PPU output to texture - void *ppu_pixels = nullptr; + void* ppu_pixels = nullptr; int ppu_pitch = 0; - if (renderer->LockTexture(ppu_texture, nullptr, &ppu_pixels, &ppu_pitch)) { + if (renderer->LockTexture(ppu_texture, nullptr, &ppu_pixels, + &ppu_pitch)) { snes_.SetPixels(static_cast(ppu_pixels)); renderer->UnlockTexture(ppu_texture); } @@ -281,25 +292,25 @@ int main(int argc, char **argv) { // === Cleanup SDL resources (in reverse order of initialization) === printf("\n[EMULATOR] Shutting down...\n"); - + // Clean up texture if (ppu_texture) { renderer->DestroyTexture(ppu_texture); ppu_texture = nullptr; } - + // Clean up audio (audio_buffer cleaned up automatically by unique_ptr) SDL_PauseAudioDevice(audio_device, 1); SDL_ClearQueuedAudio(audio_device); SDL_CloseAudioDevice(audio_device); - + // Clean up renderer and window (done automatically by unique_ptr destructors) renderer->Shutdown(); window_.reset(); - + // Quit SDL subsystems SDL_Quit(); - + printf("[EMULATOR] Shutdown complete.\n"); return EXIT_SUCCESS; } diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index 368697ac..3736eaad 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -1,24 +1,24 @@ #include "app/emu/emulator.h" -#include #include +#include #include #include -#include "app/platform/window.h" #include "app/editor/system/editor_card_registry.h" +#include "app/platform/window.h" #include "util/log.h" namespace yaze::core { - extern bool g_window_is_resizing; +extern bool g_window_is_resizing; } #include "app/emu/debug/disassembly_viewer.h" #include "app/emu/ui/debugger_ui.h" #include "app/emu/ui/emulator_ui.h" #include "app/emu/ui/input_handler.h" -#include "app/gui/core/color.h" #include "app/gui/app/editor_layout.h" +#include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "app/gui/core/theme_manager.h" #include "imgui/imgui.h" @@ -39,13 +39,13 @@ Emulator::~Emulator() { void Emulator::Cleanup() { // Stop emulation running_ = false; - + // Don't try to destroy PPU texture during shutdown // The renderer is destroyed before the emulator, so attempting to // call renderer_->DestroyTexture() will crash // The texture will be cleaned up automatically when SDL quits ppu_texture_ = nullptr; - + // Reset state snes_initialized_ = false; audio_stream_active_ = false; @@ -58,7 +58,8 @@ void Emulator::set_use_sdl_audio_stream(bool enabled) { } } -void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector& rom_data) { +void Emulator::Initialize(gfx::IRenderer* renderer, + const std::vector& rom_data) { // This method is now optional - emulator can be initialized lazily in Run() renderer_ = renderer; rom_data_ = rom_data; @@ -70,13 +71,13 @@ void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector& } audio_stream_env_checked_ = true; } - + // Cards are registered in EditorManager::Initialize() to avoid duplication - + // Reset state for new ROM running_ = false; snes_initialized_ = false; - + // Initialize audio backend if not already done if (!audio_backend_) { audio_backend_ = audio::AudioBackendFactory::Create( @@ -98,20 +99,22 @@ void Emulator::Initialize(gfx::IRenderer* renderer, const std::vector& audio_stream_config_dirty_ = true; } } - + // Set up CPU breakpoint callback snes_.cpu().on_breakpoint_hit_ = [this](uint32_t pc) -> bool { - return breakpoint_manager_.ShouldBreakOnExecute(pc, BreakpointManager::CpuType::CPU_65816); + return breakpoint_manager_.ShouldBreakOnExecute( + pc, BreakpointManager::CpuType::CPU_65816); }; - + // Set up instruction recording callback for DisassemblyViewer - snes_.cpu().on_instruction_executed_ = [this](uint32_t address, uint8_t opcode, - const std::vector& operands, - const std::string& mnemonic, - const std::string& operand_str) { - disassembly_viewer_.RecordInstruction(address, opcode, operands, mnemonic, operand_str); - }; - + snes_.cpu().on_instruction_executed_ = + [this](uint32_t address, uint8_t opcode, + const std::vector& operands, const std::string& mnemonic, + const std::string& operand_str) { + disassembly_viewer_.RecordInstruction(address, opcode, operands, + mnemonic, operand_str); + }; + initialized_ = true; } @@ -126,11 +129,11 @@ void Emulator::Run(Rom* rom) { // Lazy initialization: set renderer from Controller if not set yet if (!renderer_) { - ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Emulator renderer not initialized"); return; } - + // Initialize audio backend if not already done (lazy initialization) if (!audio_backend_) { audio_backend_ = audio::AudioBackendFactory::Create( @@ -152,17 +155,18 @@ void Emulator::Run(Rom* rom) { audio_stream_config_dirty_ = true; } } - + // Initialize input manager if not already done if (!input_manager_.IsInitialized()) { - if (!input_manager_.Initialize(input::InputBackendFactory::BackendType::SDL2)) { + if (!input_manager_.Initialize( + input::InputBackendFactory::BackendType::SDL2)) { LOG_ERROR("Emulator", "Failed to initialize input manager"); } else { LOG_INFO("Emulator", "Input manager initialized: %s", input_manager_.backend()->GetBackendName().c_str()); } } - + // Initialize SNES and create PPU texture on first run // This happens lazily when user opens the emulator window if (!snes_initialized_ && rom->is_loaded()) { @@ -182,7 +186,7 @@ void Emulator::Run(Rom* rom) { rom_data_ = rom->vector(); } snes_.Init(rom_data_); - + // Note: DisassemblyViewer recording is always enabled via callback // No explicit setup needed - callback is set in Initialize() @@ -198,13 +202,12 @@ void Emulator::Run(Rom* rom) { frame_count_ = 0; fps_timer_ = 0.0; current_fps_ = 0.0; - + // Start emulator in running state by default // User can press Space to pause if needed running_ = true; } - // Auto-pause emulator during window resize to prevent crashes // MODERN APPROACH: Only pause on actual window resize, not focus loss static bool was_running_before_resize = false; @@ -213,7 +216,8 @@ void Emulator::Run(Rom* rom) { if (yaze::core::g_window_is_resizing && running_) { was_running_before_resize = true; running_ = false; - } else if (!yaze::core::g_window_is_resizing && !running_ && was_running_before_resize) { + } else if (!yaze::core::g_window_is_resizing && !running_ && + was_running_before_resize) { // Auto-resume after resize completes running_ = true; was_running_before_resize = false; @@ -275,14 +279,17 @@ void Emulator::Run(Rom* rom) { // SMOOTH AUDIO BUFFERING // Strategy: Always queue samples, never drop. Use dynamic rate control // to keep buffer at target level. This prevents pops and glitches. - + if (audio_backend_) { if (audio_stream_config_dirty_) { - if (use_sdl_audio_stream_ && audio_backend_->SupportsAudioStream()) { - audio_backend_->SetAudioStreamResampling(true, kNativeSampleRate, 2); + if (use_sdl_audio_stream_ && + audio_backend_->SupportsAudioStream()) { + audio_backend_->SetAudioStreamResampling(true, + kNativeSampleRate, 2); audio_stream_active_ = true; } else { - audio_backend_->SetAudioStreamResampling(false, kNativeSampleRate, 2); + audio_backend_->SetAudioStreamResampling(false, + kNativeSampleRate, 2); audio_stream_active_ = false; } audio_stream_config_dirty_ = false; @@ -312,13 +319,15 @@ void Emulator::Run(Rom* rom) { } else { snes_.SetSamples(audio_buffer_, wanted_samples_); const int num_samples = wanted_samples_ * 2; // Stereo - queue_ok = audio_backend_->QueueSamples(audio_buffer_, num_samples); + queue_ok = + audio_backend_->QueueSamples(audio_buffer_, num_samples); } if (!queue_ok && use_native_stream) { snes_.SetSamples(audio_buffer_, wanted_samples_); const int num_samples = wanted_samples_ * 2; - queue_ok = audio_backend_->QueueSamples(audio_buffer_, num_samples); + queue_ok = + audio_backend_->QueueSamples(audio_buffer_, num_samples); } if (!queue_ok) { @@ -343,10 +352,11 @@ void Emulator::Run(Rom* rom) { // Update PPU texture only on rendered frames void* ppu_pixels_; int ppu_pitch_; - if (renderer_->LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_)) { + if (renderer_->LockTexture(ppu_texture_, NULL, &ppu_pixels_, + &ppu_pitch_)) { snes_.SetPixels(static_cast(ppu_pixels_)); renderer_->UnlockTexture(ppu_texture_); - + // WORKAROUND: Tiny delay after texture unlock to prevent macOS Metal crash // macOS CoreAnimation/Metal driver bug in layer_presented() callback // Without this, rapid texture updates corrupt Metal's frame tracking @@ -362,7 +372,8 @@ void Emulator::Run(Rom* rom) { void Emulator::RenderEmulatorInterface() { try { - if (!card_registry_) return; // Card registry must be injected + if (!card_registry_) + return; // Card registry must be injected static gui::EditorCard cpu_card("CPU Debugger", ICON_MD_MEMORY); static gui::EditorCard ppu_card("PPU Viewer", ICON_MD_VIDEOGAME_ASSET); @@ -383,7 +394,8 @@ void Emulator::RenderEmulatorInterface() { // Get visibility flags from registry and pass them to Begin() for proper X button functionality // This ensures each card window can be closed by the user via the window close button - bool* cpu_visible = card_registry_->GetVisibilityFlag("emulator.cpu_debugger"); + bool* cpu_visible = + card_registry_->GetVisibilityFlag("emulator.cpu_debugger"); if (cpu_visible && *cpu_visible) { if (cpu_card.Begin(cpu_visible)) { RenderModernCpuDebugger(); @@ -391,7 +403,8 @@ void Emulator::RenderEmulatorInterface() { cpu_card.End(); } - bool* ppu_visible = card_registry_->GetVisibilityFlag("emulator.ppu_viewer"); + bool* ppu_visible = + card_registry_->GetVisibilityFlag("emulator.ppu_viewer"); if (ppu_visible && *ppu_visible) { if (ppu_card.Begin(ppu_visible)) { RenderNavBar(); @@ -400,7 +413,8 @@ void Emulator::RenderEmulatorInterface() { ppu_card.End(); } - bool* memory_visible = card_registry_->GetVisibilityFlag("emulator.memory_viewer"); + bool* memory_visible = + card_registry_->GetVisibilityFlag("emulator.memory_viewer"); if (memory_visible && *memory_visible) { if (memory_card.Begin(memory_visible)) { RenderMemoryViewer(); @@ -408,7 +422,8 @@ void Emulator::RenderEmulatorInterface() { memory_card.End(); } - bool* breakpoints_visible = card_registry_->GetVisibilityFlag("emulator.breakpoints"); + bool* breakpoints_visible = + card_registry_->GetVisibilityFlag("emulator.breakpoints"); if (breakpoints_visible && *breakpoints_visible) { if (breakpoints_card.Begin(breakpoints_visible)) { RenderBreakpointList(); @@ -416,7 +431,8 @@ void Emulator::RenderEmulatorInterface() { breakpoints_card.End(); } - bool* performance_visible = card_registry_->GetVisibilityFlag("emulator.performance"); + bool* performance_visible = + card_registry_->GetVisibilityFlag("emulator.performance"); if (performance_visible && *performance_visible) { if (performance_card.Begin(performance_visible)) { RenderPerformanceMonitor(); @@ -424,7 +440,8 @@ void Emulator::RenderEmulatorInterface() { performance_card.End(); } - bool* ai_agent_visible = card_registry_->GetVisibilityFlag("emulator.ai_agent"); + bool* ai_agent_visible = + card_registry_->GetVisibilityFlag("emulator.ai_agent"); if (ai_agent_visible && *ai_agent_visible) { if (ai_card.Begin(ai_agent_visible)) { RenderAIAgentPanel(); @@ -432,7 +449,8 @@ void Emulator::RenderEmulatorInterface() { ai_card.End(); } - bool* save_states_visible = card_registry_->GetVisibilityFlag("emulator.save_states"); + bool* save_states_visible = + card_registry_->GetVisibilityFlag("emulator.save_states"); if (save_states_visible && *save_states_visible) { if (save_states_card.Begin(save_states_visible)) { RenderSaveStates(); @@ -440,7 +458,8 @@ void Emulator::RenderEmulatorInterface() { save_states_card.End(); } - bool* keyboard_config_visible = card_registry_->GetVisibilityFlag("emulator.keyboard_config"); + bool* keyboard_config_visible = + card_registry_->GetVisibilityFlag("emulator.keyboard_config"); if (keyboard_config_visible && *keyboard_config_visible) { if (keyboard_card.Begin(keyboard_config_visible)) { RenderKeyboardConfig(); @@ -448,7 +467,8 @@ void Emulator::RenderEmulatorInterface() { keyboard_card.End(); } - bool* apu_debugger_visible = card_registry_->GetVisibilityFlag("emulator.apu_debugger"); + bool* apu_debugger_visible = + card_registry_->GetVisibilityFlag("emulator.apu_debugger"); if (apu_debugger_visible && *apu_debugger_visible) { if (apu_card.Begin(apu_debugger_visible)) { RenderApuDebugger(); @@ -456,7 +476,8 @@ void Emulator::RenderEmulatorInterface() { apu_card.End(); } - bool* audio_mixer_visible = card_registry_->GetVisibilityFlag("emulator.audio_mixer"); + bool* audio_mixer_visible = + card_registry_->GetVisibilityFlag("emulator.audio_mixer"); if (audio_mixer_visible && *audio_mixer_visible) { if (audio_card.Begin(audio_mixer_visible)) { // RenderAudioMixer(); @@ -503,41 +524,51 @@ void Emulator::RenderModernCpuDebugger() { try { auto& theme_manager = gui::ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + // Debugger controls toolbar - if (ImGui::Button(ICON_MD_PLAY_ARROW)) { running_ = true; } - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_PAUSE)) { running_ = false; } - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) { - if (!running_) snes_.cpu().RunOpcode(); + if (ImGui::Button(ICON_MD_PLAY_ARROW)) { + running_ = true; } ImGui::SameLine(); - if (ImGui::Button(ICON_MD_REFRESH)) { snes_.Reset(true); } - + if (ImGui::Button(ICON_MD_PAUSE)) { + running_ = false; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) { + if (!running_) + snes_.cpu().RunOpcode(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_REFRESH)) { + snes_.Reset(true); + } + ImGui::Separator(); - + // Breakpoint controls static char bp_addr[16] = "00FFD9"; ImGui::Text(ICON_MD_BUG_REPORT " Breakpoints:"); ImGui::PushItemWidth(100); ImGui::InputText("##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr), - ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_CharsUppercase); ImGui::PopItemWidth(); ImGui::SameLine(); if (ImGui::Button(ICON_MD_ADD " Add")) { uint32_t addr = std::strtoul(bp_addr, nullptr, 16); breakpoint_manager_.AddBreakpoint(addr, BreakpointManager::Type::EXECUTE, - BreakpointManager::CpuType::CPU_65816, - "", absl::StrFormat("BP at $%06X", addr)); + BreakpointManager::CpuType::CPU_65816, + "", + absl::StrFormat("BP at $%06X", addr)); } - + // List breakpoints ImGui::BeginChild("##BPList", ImVec2(0, 100), true); for (const auto& bp : breakpoint_manager_.GetAllBreakpoints()) { if (bp.cpu == BreakpointManager::CpuType::CPU_65816) { bool enabled = bp.enabled; - if (ImGui::Checkbox(absl::StrFormat("##en%d", bp.id).c_str(), &enabled)) { + if (ImGui::Checkbox(absl::StrFormat("##en%d", bp.id).c_str(), + &enabled)) { breakpoint_manager_.SetEnabled(bp.id, enabled); } ImGui::SameLine(); @@ -545,13 +576,14 @@ void Emulator::RenderModernCpuDebugger() { ImGui::SameLine(); ImGui::TextDisabled("(hits: %d)", bp.hit_count); ImGui::SameLine(); - if (ImGui::SmallButton(absl::StrFormat(ICON_MD_DELETE "##%d", bp.id).c_str())) { + if (ImGui::SmallButton( + absl::StrFormat(ICON_MD_DELETE "##%d", bp.id).c_str())) { breakpoint_manager_.RemoveBreakpoint(bp.id); } } } ImGui::EndChild(); - + ImGui::Separator(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "CPU Status"); @@ -694,14 +726,16 @@ void Emulator::RenderModernCpuDebugger() { ImGui::PopStyleColor(); // New Disassembly Viewer - if (ImGui::CollapsingHeader("Disassembly Viewer", + if (ImGui::CollapsingHeader("Disassembly Viewer", ImGuiTreeNodeFlags_DefaultOpen)) { - uint32_t current_pc = (static_cast(snes_.cpu().PB) << 16) | snes_.cpu().PC; + uint32_t current_pc = + (static_cast(snes_.cpu().PB) << 16) | snes_.cpu().PC; auto& disasm = snes_.cpu().disassembly_viewer(); if (disasm.IsAvailable()) { disasm.Render(current_pc, snes_.cpu().breakpoints_); } else { - ImGui::TextColored(ConvertColorToImVec4(theme.error), "Disassembly viewer unavailable."); + ImGui::TextColored(ConvertColorToImVec4(theme.error), + "Disassembly viewer unavailable."); } } } catch (const std::exception& e) { @@ -735,9 +769,9 @@ void Emulator::RenderSaveStates() { // TODO: Create ui::RenderSaveStates() when save state system is implemented auto& theme_manager = gui::ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - - ImGui::TextColored(ConvertColorToImVec4(theme.warning), - ICON_MD_SAVE " Save States - Coming Soon"); + + ImGui::TextColored(ConvertColorToImVec4(theme.warning), + ICON_MD_SAVE " Save States - Coming Soon"); ImGui::TextWrapped("Save state functionality will be implemented here."); } diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index 65fa1b27..324085b0 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -4,21 +4,21 @@ #include #include -#include "app/emu/snes.h" #include "app/emu/audio/audio_backend.h" #include "app/emu/debug/breakpoint_manager.h" #include "app/emu/debug/disassembly_viewer.h" #include "app/emu/input/input_manager.h" +#include "app/emu/snes.h" #include "app/rom.h" namespace yaze { namespace gfx { class IRenderer; -} // namespace gfx +} // namespace gfx namespace editor { class EditorCardRegistry; -} // namespace editor +} // namespace editor /** * @namespace yaze::emu @@ -37,17 +37,20 @@ class Emulator { public: Emulator() = default; ~Emulator(); - void Initialize(gfx::IRenderer* renderer, const std::vector& rom_data); + void Initialize(gfx::IRenderer* renderer, + const std::vector& rom_data); void Run(Rom* rom); void Cleanup(); - + // Card visibility managed by EditorCardRegistry (dependency injection) - void set_card_registry(editor::EditorCardRegistry* registry) { card_registry_ = registry; } + void set_card_registry(editor::EditorCardRegistry* registry) { + card_registry_ = registry; + } auto snes() -> Snes& { return snes_; } auto running() const -> bool { return running_; } void set_running(bool running) { running_ = running; } - + // Audio backend access audio::IAudioBackend* audio_backend() { return audio_backend_.get(); } void set_audio_buffer(int16_t* audio_buffer) { audio_buffer_ = audio_buffer; } @@ -58,15 +61,15 @@ class Emulator { bool use_sdl_audio_stream() const { return use_sdl_audio_stream_; } auto wanted_samples() const -> int { return wanted_samples_; } void set_renderer(gfx::IRenderer* renderer) { renderer_ = renderer; } - + // Render access gfx::IRenderer* renderer() { return renderer_; } void* ppu_texture() { return ppu_texture_; } - + // Turbo mode bool is_turbo_mode() const { return turbo_mode_; } void set_turbo_mode(bool turbo) { turbo_mode_ = turbo; } - + // Debugger access BreakpointManager& breakpoint_manager() { return breakpoint_manager_; } debug::DisassemblyViewer& disassembly_viewer() { return disassembly_viewer_; } @@ -75,7 +78,7 @@ class Emulator { void set_debugging(bool debugging) { debugging_ = debugging; } bool is_initialized() const { return initialized_; } bool is_snes_initialized() const { return snes_initialized_; } - + // AI Agent Integration API bool IsEmulatorReady() const { return snes_.running() && !rom_data_.empty(); } double GetCurrentFPS() const { return current_fps_; } @@ -85,8 +88,10 @@ class Emulator { void StepSingleInstruction() { snes_.cpu().RunOpcode(); } void SetBreakpoint(uint32_t address) { snes_.cpu().SetBreakpoint(address); } void ClearAllBreakpoints() { snes_.cpu().ClearBreakpoints(); } - std::vector GetBreakpoints() { return snes_.cpu().GetBreakpoints(); } - + std::vector GetBreakpoints() { + return snes_.cpu().GetBreakpoints(); + } + // Performance monitoring for AI agents struct EmulatorMetrics { double fps; @@ -97,14 +102,13 @@ class Emulator { uint8_t cpu_pb; }; EmulatorMetrics GetMetrics() { - return { - .fps = current_fps_, - .cycles = snes_.mutable_cycles(), - .audio_frames_queued = SDL_GetQueuedAudioSize(audio_device_) / (wanted_samples_ * 4), - .is_running = running_, - .cpu_pc = snes_.cpu().PC, - .cpu_pb = snes_.cpu().PB - }; + return {.fps = current_fps_, + .cycles = snes_.mutable_cycles(), + .audio_frames_queued = + SDL_GetQueuedAudioSize(audio_device_) / (wanted_samples_ * 4), + .is_running = running_, + .cpu_pc = snes_.cpu().PC, + .cpu_pb = snes_.cpu().PB}; } private: @@ -146,7 +150,7 @@ class Emulator { uint64_t count_frequency; uint64_t last_count; double time_adder = 0.0; - + // FPS tracking int frame_count_ = 0; double fps_timer_ = 0.0; @@ -154,7 +158,7 @@ class Emulator { int16_t* audio_buffer_; SDL_AudioDeviceID audio_device_; - + // Audio backend abstraction std::unique_ptr audio_backend_; @@ -168,9 +172,9 @@ class Emulator { bool audio_stream_config_dirty_ = false; bool audio_stream_active_ = false; bool audio_stream_env_checked_ = false; - + // Card visibility managed by EditorCardManager - no member variables needed! - + // Debugger infrastructure BreakpointManager breakpoint_manager_; debug::DisassemblyViewer disassembly_viewer_; @@ -179,7 +183,7 @@ class Emulator { // Input handling (abstracted for SDL2/SDL3/custom backends) input::InputManager input_manager_; - + // Card registry for card visibility (injected) editor::EditorCardRegistry* card_registry_ = nullptr; }; diff --git a/src/app/emu/input/input_backend.cc b/src/app/emu/input/input_backend.cc index a85b1584..a7696124 100644 --- a/src/app/emu/input/input_backend.cc +++ b/src/app/emu/input/input_backend.cc @@ -15,15 +15,15 @@ class SDL2InputBackend : public IInputBackend { public: SDL2InputBackend() = default; ~SDL2InputBackend() override { Shutdown(); } - + bool Initialize(const InputConfig& config) override { if (initialized_) { LOG_WARN("InputBackend", "Already initialized"); return true; } - + config_ = config; - + // Set default SDL2 keycodes if not configured if (config_.key_a == 0) { config_.key_a = SDLK_x; @@ -39,21 +39,22 @@ class SDL2InputBackend : public IInputBackend { config_.key_left = SDLK_LEFT; config_.key_right = SDLK_RIGHT; } - + initialized_ = true; LOG_INFO("InputBackend", "SDL2 Input Backend initialized"); return true; } - + void Shutdown() override { if (initialized_) { initialized_ = false; LOG_INFO("InputBackend", "SDL2 Input Backend shut down"); } } - + ControllerState Poll(int player) override { - if (!initialized_) return ControllerState{}; + if (!initialized_) + return ControllerState{}; ControllerState state; @@ -64,7 +65,7 @@ class SDL2InputBackend : public IInputBackend { // IMPORTANT: Only block input when actively typing in text fields // Allow game input even when ImGui windows are open/focused ImGuiIO& io = ImGui::GetIO(); - + // Only block if user is actively typing in a text input field // WantTextInput is true only when an InputText widget is active if (io.WantTextInput) { @@ -78,18 +79,33 @@ class SDL2InputBackend : public IInputBackend { } // Map keyboard to SNES buttons - state.SetButton(SnesButton::B, keyboard_state[SDL_GetScancodeFromKey(config_.key_b)]); - state.SetButton(SnesButton::Y, keyboard_state[SDL_GetScancodeFromKey(config_.key_y)]); - state.SetButton(SnesButton::SELECT, keyboard_state[SDL_GetScancodeFromKey(config_.key_select)]); - state.SetButton(SnesButton::START, keyboard_state[SDL_GetScancodeFromKey(config_.key_start)]); - state.SetButton(SnesButton::UP, keyboard_state[SDL_GetScancodeFromKey(config_.key_up)]); - state.SetButton(SnesButton::DOWN, keyboard_state[SDL_GetScancodeFromKey(config_.key_down)]); - state.SetButton(SnesButton::LEFT, keyboard_state[SDL_GetScancodeFromKey(config_.key_left)]); - state.SetButton(SnesButton::RIGHT, keyboard_state[SDL_GetScancodeFromKey(config_.key_right)]); - state.SetButton(SnesButton::A, keyboard_state[SDL_GetScancodeFromKey(config_.key_a)]); - state.SetButton(SnesButton::X, keyboard_state[SDL_GetScancodeFromKey(config_.key_x)]); - state.SetButton(SnesButton::L, keyboard_state[SDL_GetScancodeFromKey(config_.key_l)]); - state.SetButton(SnesButton::R, keyboard_state[SDL_GetScancodeFromKey(config_.key_r)]); + state.SetButton(SnesButton::B, + keyboard_state[SDL_GetScancodeFromKey(config_.key_b)]); + state.SetButton(SnesButton::Y, + keyboard_state[SDL_GetScancodeFromKey(config_.key_y)]); + state.SetButton( + SnesButton::SELECT, + keyboard_state[SDL_GetScancodeFromKey(config_.key_select)]); + state.SetButton( + SnesButton::START, + keyboard_state[SDL_GetScancodeFromKey(config_.key_start)]); + state.SetButton(SnesButton::UP, + keyboard_state[SDL_GetScancodeFromKey(config_.key_up)]); + state.SetButton(SnesButton::DOWN, + keyboard_state[SDL_GetScancodeFromKey(config_.key_down)]); + state.SetButton(SnesButton::LEFT, + keyboard_state[SDL_GetScancodeFromKey(config_.key_left)]); + state.SetButton( + SnesButton::RIGHT, + keyboard_state[SDL_GetScancodeFromKey(config_.key_right)]); + state.SetButton(SnesButton::A, + keyboard_state[SDL_GetScancodeFromKey(config_.key_a)]); + state.SetButton(SnesButton::X, + keyboard_state[SDL_GetScancodeFromKey(config_.key_x)]); + state.SetButton(SnesButton::L, + keyboard_state[SDL_GetScancodeFromKey(config_.key_l)]); + state.SetButton(SnesButton::R, + keyboard_state[SDL_GetScancodeFromKey(config_.key_r)]); } else { // Event-based mode (use cached event state) state = event_state_; @@ -100,49 +116,60 @@ class SDL2InputBackend : public IInputBackend { return state; } - + void ProcessEvent(void* event) override { - if (!initialized_ || !event) return; - + if (!initialized_ || !event) + return; + SDL_Event* sdl_event = static_cast(event); - + // Cache keyboard events for event-based mode if (sdl_event->type == SDL_KEYDOWN) { UpdateEventState(sdl_event->key.keysym.sym, true); } else if (sdl_event->type == SDL_KEYUP) { UpdateEventState(sdl_event->key.keysym.sym, false); } - + // TODO: Handle gamepad events } - + InputConfig GetConfig() const override { return config_; } - - void SetConfig(const InputConfig& config) override { - config_ = config; - } - + + void SetConfig(const InputConfig& config) override { config_ = config; } + std::string GetBackendName() const override { return "SDL2"; } - + bool IsInitialized() const override { return initialized_; } - + private: void UpdateEventState(int keycode, bool pressed) { // Map keycode to button and update event state - if (keycode == config_.key_a) event_state_.SetButton(SnesButton::A, pressed); - else if (keycode == config_.key_b) event_state_.SetButton(SnesButton::B, pressed); - else if (keycode == config_.key_x) event_state_.SetButton(SnesButton::X, pressed); - else if (keycode == config_.key_y) event_state_.SetButton(SnesButton::Y, pressed); - else if (keycode == config_.key_l) event_state_.SetButton(SnesButton::L, pressed); - else if (keycode == config_.key_r) event_state_.SetButton(SnesButton::R, pressed); - else if (keycode == config_.key_start) event_state_.SetButton(SnesButton::START, pressed); - else if (keycode == config_.key_select) event_state_.SetButton(SnesButton::SELECT, pressed); - else if (keycode == config_.key_up) event_state_.SetButton(SnesButton::UP, pressed); - else if (keycode == config_.key_down) event_state_.SetButton(SnesButton::DOWN, pressed); - else if (keycode == config_.key_left) event_state_.SetButton(SnesButton::LEFT, pressed); - else if (keycode == config_.key_right) event_state_.SetButton(SnesButton::RIGHT, pressed); + if (keycode == config_.key_a) + event_state_.SetButton(SnesButton::A, pressed); + else if (keycode == config_.key_b) + event_state_.SetButton(SnesButton::B, pressed); + else if (keycode == config_.key_x) + event_state_.SetButton(SnesButton::X, pressed); + else if (keycode == config_.key_y) + event_state_.SetButton(SnesButton::Y, pressed); + else if (keycode == config_.key_l) + event_state_.SetButton(SnesButton::L, pressed); + else if (keycode == config_.key_r) + event_state_.SetButton(SnesButton::R, pressed); + else if (keycode == config_.key_start) + event_state_.SetButton(SnesButton::START, pressed); + else if (keycode == config_.key_select) + event_state_.SetButton(SnesButton::SELECT, pressed); + else if (keycode == config_.key_up) + event_state_.SetButton(SnesButton::UP, pressed); + else if (keycode == config_.key_down) + event_state_.SetButton(SnesButton::DOWN, pressed); + else if (keycode == config_.key_left) + event_state_.SetButton(SnesButton::LEFT, pressed); + else if (keycode == config_.key_right) + event_state_.SetButton(SnesButton::RIGHT, pressed); } - + InputConfig config_; bool initialized_ = false; ControllerState event_state_; // Cached state for event-based mode @@ -153,9 +180,9 @@ class SDL2InputBackend : public IInputBackend { */ class NullInputBackend : public IInputBackend { public: - bool Initialize(const InputConfig& config) override { + bool Initialize(const InputConfig& config) override { config_ = config; - return true; + return true; } void Shutdown() override {} ControllerState Poll(int player) override { return replay_state_; } @@ -164,10 +191,10 @@ class NullInputBackend : public IInputBackend { void SetConfig(const InputConfig& config) override { config_ = config; } std::string GetBackendName() const override { return "NULL"; } bool IsInitialized() const override { return true; } - + // For replay/testing - set controller state directly void SetReplayState(const ControllerState& state) { replay_state_ = state; } - + private: InputConfig config_; ControllerState replay_state_; @@ -178,15 +205,15 @@ std::unique_ptr InputBackendFactory::Create(BackendType type) { switch (type) { case BackendType::SDL2: return std::make_unique(); - + case BackendType::SDL3: // TODO: Implement SDL3 backend when SDL3 is stable LOG_WARN("InputBackend", "SDL3 backend not yet implemented, using SDL2"); return std::make_unique(); - + case BackendType::NULL_BACKEND: return std::make_unique(); - + default: LOG_ERROR("InputBackend", "Unknown backend type, using SDL2"); return std::make_unique(); @@ -196,4 +223,3 @@ std::unique_ptr InputBackendFactory::Create(BackendType type) { } // namespace input } // namespace emu } // namespace yaze - diff --git a/src/app/emu/input/input_backend.h b/src/app/emu/input/input_backend.h index d5254e77..87f7b31c 100644 --- a/src/app/emu/input/input_backend.h +++ b/src/app/emu/input/input_backend.h @@ -32,11 +32,11 @@ enum class SnesButton : uint8_t { */ struct ControllerState { uint16_t buttons = 0; // Bit field matching SNES hardware layout - + bool IsPressed(SnesButton button) const { return (buttons & (1 << static_cast(button))) != 0; } - + void SetButton(SnesButton button, bool pressed) { if (pressed) { buttons |= (1 << static_cast(button)); @@ -44,7 +44,7 @@ struct ControllerState { buttons &= ~(1 << static_cast(button)); } } - + void Clear() { buttons = 0; } }; @@ -54,22 +54,22 @@ struct ControllerState { struct InputConfig { // Platform-agnostic key codes (mapped to platform-specific in backend) // Using generic names that can be mapped to SDL2/SDL3/other - int key_a = 0; // Default: X key - int key_b = 0; // Default: Z key - int key_x = 0; // Default: S key - int key_y = 0; // Default: A key - int key_l = 0; // Default: D key - int key_r = 0; // Default: C key - int key_start = 0; // Default: Enter - int key_select = 0; // Default: RShift - int key_up = 0; // Default: Up arrow - int key_down = 0; // Default: Down arrow - int key_left = 0; // Default: Left arrow - int key_right = 0; // Default: Right arrow - + int key_a = 0; // Default: X key + int key_b = 0; // Default: Z key + int key_x = 0; // Default: S key + int key_y = 0; // Default: A key + int key_l = 0; // Default: D key + int key_r = 0; // Default: C key + int key_start = 0; // Default: Enter + int key_select = 0; // Default: RShift + int key_up = 0; // Default: Up arrow + int key_down = 0; // Default: Down arrow + int key_left = 0; // Default: Left arrow + int key_right = 0; // Default: Right arrow + // Enable/disable continuous polling (vs event-based) bool continuous_polling = true; - + // Enable gamepad support bool enable_gamepad = true; int gamepad_index = 0; // Which gamepad to use (0-3) @@ -84,45 +84,45 @@ struct InputConfig { class IInputBackend { public: virtual ~IInputBackend() = default; - + /** * @brief Initialize the input backend */ virtual bool Initialize(const InputConfig& config) = 0; - + /** * @brief Shutdown the input backend */ virtual void Shutdown() = 0; - + /** * @brief Poll current input state (call every frame) * @param player Player number (1-4) * @return Current controller state */ virtual ControllerState Poll(int player = 1) = 0; - + /** * @brief Process platform-specific events (optional) * @param event Platform-specific event data (e.g., SDL_Event*) */ virtual void ProcessEvent(void* event) = 0; - + /** * @brief Get current configuration */ virtual InputConfig GetConfig() const = 0; - + /** * @brief Update configuration (hot-reload) */ virtual void SetConfig(const InputConfig& config) = 0; - + /** * @brief Get backend name for debugging */ virtual std::string GetBackendName() const = 0; - + /** * @brief Check if backend is initialized */ @@ -136,10 +136,10 @@ class InputBackendFactory { public: enum class BackendType { SDL2, - SDL3, // Future + SDL3, // Future NULL_BACKEND // For testing/replay }; - + static std::unique_ptr Create(BackendType type); }; @@ -148,4 +148,3 @@ class InputBackendFactory { } // namespace yaze #endif // YAZE_APP_EMU_INPUT_INPUT_BACKEND_H_ - diff --git a/src/app/emu/input/input_manager.cc b/src/app/emu/input/input_manager.cc index 399aab9c..d273b070 100644 --- a/src/app/emu/input/input_manager.cc +++ b/src/app/emu/input/input_manager.cc @@ -13,24 +13,24 @@ bool InputManager::Initialize(InputBackendFactory::BackendType type) { LOG_ERROR("InputManager", "Failed to create input backend"); return false; } - + InputConfig config; config.continuous_polling = true; config.enable_gamepad = false; - + if (!backend_->Initialize(config)) { LOG_ERROR("InputManager", "Failed to initialize input backend"); return false; } - - LOG_INFO("InputManager", "Initialized with backend: %s", + + LOG_INFO("InputManager", "Initialized with backend: %s", backend_->GetBackendName().c_str()); return true; } void InputManager::Initialize(std::unique_ptr backend) { backend_ = std::move(backend); - + if (backend_) { LOG_INFO("InputManager", "Initialized with custom backend: %s", backend_->GetBackendName().c_str()); @@ -45,13 +45,15 @@ void InputManager::Shutdown() { } void InputManager::Poll(Snes* snes, int player) { - if (!snes || !backend_) return; - + if (!snes || !backend_) + return; + ControllerState physical_state = backend_->Poll(player); - + // Combine physical input with agent-controlled input (OR operation) ControllerState final_state; - final_state.buttons = physical_state.buttons | agent_controller_state_.buttons; + final_state.buttons = + physical_state.buttons | agent_controller_state_.buttons; // Apply button state directly to SNES // Just send the raw button state on every Poll() call @@ -60,7 +62,7 @@ void InputManager::Poll(Snes* snes, int player) { bool button_held = (final_state.buttons & (1 << i)) != 0; snes->SetButtonState(player, i, button_held); } - + // Debug: Log complete button state when any button is pressed static int poll_log_count = 0; if (final_state.buttons != 0 && poll_log_count++ < 30) { @@ -88,11 +90,11 @@ void InputManager::SetConfig(const InputConfig& config) { } void InputManager::PressButton(SnesButton button) { - agent_controller_state_.SetButton(button, true); + agent_controller_state_.SetButton(button, true); } void InputManager::ReleaseButton(SnesButton button) { - agent_controller_state_.SetButton(button, false); + agent_controller_state_.SetButton(button, false); } } // namespace input diff --git a/src/app/emu/input/input_manager.h b/src/app/emu/input/input_manager.h index ca258ace..e1acea77 100644 --- a/src/app/emu/input/input_manager.h +++ b/src/app/emu/input/input_manager.h @@ -16,28 +16,29 @@ class InputManager { public: InputManager() = default; ~InputManager() { Shutdown(); } - - bool Initialize(InputBackendFactory::BackendType type = InputBackendFactory::BackendType::SDL2); + + bool Initialize(InputBackendFactory::BackendType type = + InputBackendFactory::BackendType::SDL2); void Initialize(std::unique_ptr backend); void Shutdown(); void Poll(Snes* snes, int player = 1); void ProcessEvent(void* event); - + IInputBackend* backend() { return backend_.get(); } const IInputBackend* backend() const { return backend_.get(); } - + bool IsInitialized() const { return backend_ && backend_->IsInitialized(); } - + InputConfig GetConfig() const; void SetConfig(const InputConfig& config); - + // --- Agent Control API --- void PressButton(SnesButton button); void ReleaseButton(SnesButton button); - + private: std::unique_ptr backend_; - ControllerState agent_controller_state_; // State controlled by agent + ControllerState agent_controller_state_; // State controlled by agent }; } // namespace input diff --git a/src/app/emu/memory/dma.cc b/src/app/emu/memory/dma.cc index 8ddcc1d7..b1e4e6c6 100644 --- a/src/app/emu/memory/dma.cc +++ b/src/app/emu/memory/dma.cc @@ -163,7 +163,8 @@ void DoDma(Snes* snes, MemoryImpl* memory, int cpuCycles) { // full transfer overhead WaitCycle(snes, memory); for (int i = 0; i < 8; i++) { - if (!channel[i].dma_active) continue; + if (!channel[i].dma_active) + continue; // do channel i WaitCycle(snes, memory); // overhead per channel int offIndex = 0; @@ -207,8 +208,10 @@ void HandleDma(Snes* snes, MemoryImpl* memory, int cpu_cycles) { void WaitCycle(Snes* snes, MemoryImpl* memory) { // run hdma if requested, no sync (already sycned due to dma) - if (memory->hdma_init_requested()) InitHdma(snes, memory, false, 0); - if (memory->hdma_run_requested()) DoHdma(snes, memory, false, 0); + if (memory->hdma_init_requested()) + InitHdma(snes, memory, false, 0); + if (memory->hdma_run_requested()) + DoHdma(snes, memory, false, 0); snes->RunCycles(8); } @@ -219,13 +222,16 @@ void InitHdma(Snes* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) { bool hdmaEnabled = false; // check if a channel is enabled, and do reset for (int i = 0; i < 8; i++) { - if (channel[i].hdma_active) hdmaEnabled = true; + if (channel[i].hdma_active) + hdmaEnabled = true; channel[i].do_transfer = false; channel[i].terminated = false; } - if (!hdmaEnabled) return; + if (!hdmaEnabled) + return; snes->cpu().set_int_delay(true); - if (do_sync) snes->SyncCycles(true, 8); + if (do_sync) + snes->SyncCycles(true, 8); // full transfer overhead snes->RunCycles(8); @@ -238,7 +244,8 @@ void InitHdma(Snes* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) { channel[i].table_addr = channel[i].a_addr; channel[i].rep_count = snes->Read((channel[i].a_bank << 16) | channel[i].table_addr++); - if (channel[i].rep_count == 0) channel[i].terminated = true; + if (channel[i].rep_count == 0) + channel[i].terminated = true; if (channel[i].indirect) { snes->RunCycles(8); channel[i].size = @@ -251,7 +258,8 @@ void InitHdma(Snes* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) { channel[i].do_transfer = true; } } - if (do_sync) snes->SyncCycles(false, cpu_cycles); + if (do_sync) + snes->SyncCycles(false, cpu_cycles); } void DoHdma(Snes* snes, MemoryImpl* memory, bool do_sync, int cycles) { @@ -262,21 +270,25 @@ void DoHdma(Snes* snes, MemoryImpl* memory, bool do_sync, int cycles) { for (int i = 0; i < 8; i++) { if (channel[i].hdma_active) { hdmaActive = true; - if (!channel[i].terminated) lastActive = i; + if (!channel[i].terminated) + lastActive = i; } } - if (!hdmaActive) return; + if (!hdmaActive) + return; snes->cpu().set_int_delay(true); - if (do_sync) snes->SyncCycles(true, 8); + if (do_sync) + snes->SyncCycles(true, 8); // full transfer overhead snes->RunCycles(8); // do all copies for (int i = 0; i < 8; i++) { // terminate any dma - if (channel[i].hdma_active) channel[i].dma_active = false; + if (channel[i].hdma_active) + channel[i].dma_active = false; if (channel[i].hdma_active && !channel[i].terminated) { // do the hdma if (channel[i].do_transfer) { @@ -322,13 +334,15 @@ void DoHdma(Snes* snes, MemoryImpl* memory, bool do_sync, int cycles) { snes->Read((channel[i].a_bank << 16) | channel[i].table_addr++) << 8; } - if (channel[i].rep_count == 0) channel[i].terminated = true; + if (channel[i].rep_count == 0) + channel[i].terminated = true; channel[i].do_transfer = true; } } } - if (do_sync) snes->SyncCycles(false, cycles); + if (do_sync) + snes->SyncCycles(false, cycles); } void TransferByte(Snes* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank, @@ -345,11 +359,13 @@ void TransferByte(Snes* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank, (aAdr >= 0x2100 && aAdr < 0x2200))); if (fromB) { uint8_t val = validB ? snes->ReadBBus(bAdr) : memory->open_bus(); - if (validA) snes->Write((aBank << 16) | aAdr, val); + if (validA) + snes->Write((aBank << 16) | aAdr, val); } else { uint8_t val = validA ? snes->Read((aBank << 16) | aAdr) : memory->open_bus(); - if (validB) snes->WriteBBus(bAdr, val); + if (validB) + snes->WriteBBus(bAdr, val); } } diff --git a/src/app/emu/memory/memory.cc b/src/app/emu/memory/memory.cc index c793b7f6..fcb5bbcb 100644 --- a/src/app/emu/memory/memory.cc +++ b/src/app/emu/memory/memory.cc @@ -17,19 +17,20 @@ void MemoryImpl::Initialize(const std::vector& rom_data, auto location = 0x7FC0; // LoROM header location rom_size_ = 0x400 << rom_data[location + 0x17]; sram_size_ = 0x400 << rom_data[location + 0x18]; - + // Allocate ROM and SRAM storage rom_.resize(rom_size_); const size_t copy_size = std::min(rom_size_, rom_data.size()); std::copy(rom_data.begin(), rom_data.begin() + copy_size, rom_.begin()); - + ram_.resize(sram_size_); std::fill(ram_.begin(), ram_.end(), 0); - - LOG_DEBUG("Memory", "LoROM initialized: ROM size=$%06X (%zuKB) SRAM size=$%04X", - rom_size_, rom_size_ / 1024, sram_size_); - LOG_DEBUG("Memory", "Reset vector at ROM offset $7FFC-$7FFD = $%02X%02X", - rom_data[0x7FFD], rom_data[0x7FFC]); + + LOG_DEBUG("Memory", + "LoROM initialized: ROM size=$%06X (%zuKB) SRAM size=$%04X", + rom_size_, rom_size_ / 1024, sram_size_); + LOG_DEBUG("Memory", "Reset vector at ROM offset $7FFC-$7FFD = $%02X%02X", + rom_data[0x7FFD], rom_data[0x7FFC]); } uint8_t MemoryImpl::cart_read(uint8_t bank, uint16_t adr) { @@ -69,7 +70,7 @@ uint8_t MemoryImpl::cart_readLorom(uint8_t bank, uint16_t adr) { sram_size_ > 0) { return ram_[(((bank & 0xf) << 15) | adr) & (sram_size_ - 1)]; } - + // ROM access: banks 00-7f (mirrored to 80-ff), addresses 8000-ffff // OR banks 40-7f, all addresses bank &= 0x7f; @@ -77,7 +78,7 @@ uint8_t MemoryImpl::cart_readLorom(uint8_t bank, uint16_t adr) { uint32_t rom_offset = ((bank << 15) | (adr & 0x7fff)) & (rom_size_ - 1); return rom_[rom_offset]; } - + return open_bus_; } diff --git a/src/app/emu/memory/memory.h b/src/app/emu/memory/memory.h index 96b53ccd..2b312640 100644 --- a/src/app/emu/memory/memory.h +++ b/src/app/emu/memory/memory.h @@ -117,7 +117,7 @@ class Memory { */ class MemoryImpl : public Memory { public: - void Initialize(const std::vector &romData, bool verbose = false); + void Initialize(const std::vector& romData, bool verbose = false); uint16_t GetHeaderOffset() { uint16_t offset; @@ -237,7 +237,7 @@ class MemoryImpl : public Memory { // Stack Pointer access. uint16_t SP() const override { return SP_; } - auto mutable_sp() -> uint16_t & { return SP_; } + auto mutable_sp() -> uint16_t& { return SP_; } void SetSP(uint16_t value) override { SP_ = value; } void ClearMemory() override { std::fill(memory_.begin(), memory_.end(), 0); } @@ -277,9 +277,9 @@ class MemoryImpl : public Memory { auto v_pos() const -> uint16_t override { return v_pos_; } auto pal_timing() const -> bool override { return pal_timing_; } - auto dma_state() -> uint8_t & { return dma_state_; } + auto dma_state() -> uint8_t& { return dma_state_; } void set_dma_state(uint8_t value) { dma_state_ = value; } - auto dma_channels() -> DmaChannel * { return channel; } + auto dma_channels() -> DmaChannel* { return channel; } // Define memory regions std::vector rom_; diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index e0432511..189a0507 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -28,10 +28,16 @@ class Snes { // Initialize input controllers to clean state input1 = {}; input2 = {}; - - cpu_.callbacks().read_byte = [this](uint32_t adr) { return CpuRead(adr); }; - cpu_.callbacks().write_byte = [this](uint32_t adr, uint8_t val) { CpuWrite(adr, val); }; - cpu_.callbacks().idle = [this](bool waiting) { CpuIdle(waiting); }; + + cpu_.callbacks().read_byte = [this](uint32_t adr) { + return CpuRead(adr); + }; + cpu_.callbacks().write_byte = [this](uint32_t adr, uint8_t val) { + CpuWrite(adr, val); + }; + cpu_.callbacks().idle = [this](bool waiting) { + CpuIdle(waiting); + }; } ~Snes() = default; @@ -74,9 +80,11 @@ class Snes { auto memory() -> MemoryImpl& { return memory_; } auto get_ram() -> uint8_t* { return ram; } auto mutable_cycles() -> uint64_t& { return cycles_; } - + // Audio debugging - auto apu_handshake_tracker() -> debug::ApuHandshakeTracker& { return apu_handshake_tracker_; } + auto apu_handshake_tracker() -> debug::ApuHandshakeTracker& { + return apu_handshake_tracker_; + } bool fast_mem_ = false; @@ -125,7 +133,7 @@ class Snes { bool auto_joy_read_ = false; uint16_t auto_joy_timer_ = 0; bool ppu_latch_; - + // Audio debugging debug::ApuHandshakeTracker apu_handshake_tracker_; }; diff --git a/src/app/emu/ui/debugger_ui.cc b/src/app/emu/ui/debugger_ui.cc index d0df8c5b..7c309b8a 100644 --- a/src/app/emu/ui/debugger_ui.cc +++ b/src/app/emu/ui/debugger_ui.cc @@ -1,8 +1,8 @@ #include "app/emu/ui/debugger_ui.h" #include "absl/strings/str_format.h" -#include "app/emu/emulator.h" #include "app/emu/cpu/cpu.h" +#include "app/emu/emulator.h" #include "app/gui/core/color.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" @@ -23,61 +23,73 @@ constexpr float kStandardSpacing = 8.0f; constexpr float kButtonHeight = 30.0f; constexpr float kLargeButtonHeight = 35.0f; -void AddSpacing() { ImGui::Spacing(); ImGui::Spacing(); } -void AddSectionSpacing() { ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); } +void AddSpacing() { + ImGui::Spacing(); + ImGui::Spacing(); +} +void AddSectionSpacing() { + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); +} } // namespace void RenderModernCpuDebugger(Emulator* emu) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##CPUDebugger", ImVec2(0, 0), true); - + // Title with icon - ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_DEVELOPER_BOARD " 65816 CPU Debugger"); + ImGui::TextColored(ConvertColorToImVec4(theme.accent), + ICON_MD_DEVELOPER_BOARD " 65816 CPU Debugger"); AddSectionSpacing(); - + auto& cpu = emu->snes().cpu(); - + // Debugger Controls - if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " Controls", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " Controls", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 6)); - + if (ImGui::Button(ICON_MD_SKIP_NEXT " Step", ImVec2(100, kButtonHeight))) { cpu.RunOpcode(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Execute single instruction (F10)"); } - + ImGui::SameLine(); - if (ImGui::Button(ICON_MD_FAST_FORWARD " Run to BP", ImVec2(120, kButtonHeight))) { + if (ImGui::Button(ICON_MD_FAST_FORWARD " Run to BP", + ImVec2(120, kButtonHeight))) { // Run until breakpoint emu->set_running(true); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Run until next breakpoint (F5)"); } - + ImGui::PopStyleVar(); } - + AddSpacing(); - + // CPU Registers - if (ImGui::CollapsingHeader(ICON_MD_MEMORY " Registers", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginTable("CPU_Registers", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (ImGui::CollapsingHeader(ICON_MD_MEMORY " Registers", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::BeginTable("CPU_Registers", 4, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed, 40.0f); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 70.0f); ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed, 40.0f); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 70.0f); ImGui::TableHeadersRow(); - + // Row 1: A, X ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -88,7 +100,7 @@ void RenderModernCpuDebugger(Emulator* emu) { ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "X:"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.X); - + // Row 2: Y, D ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -99,7 +111,7 @@ void RenderModernCpuDebugger(Emulator* emu) { ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "D:"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.D); - + // Row 3: DB, PB ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -110,7 +122,7 @@ void RenderModernCpuDebugger(Emulator* emu) { ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "PB:"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%02X", cpu.PB); - + // Row 4: PC, SP ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -121,26 +133,29 @@ void RenderModernCpuDebugger(Emulator* emu) { ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), "SP:"); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%04X", cpu.SP()); - + ImGui::EndTable(); } - + AddSpacing(); - + // Status Flags (visual checkboxes) - ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), ICON_MD_FLAG " Flags:"); + ImGui::TextColored(ConvertColorToImVec4(theme.text_secondary), + ICON_MD_FLAG " Flags:"); ImGui::Indent(); - + auto RenderFlag = [&](const char* name, bool value) { - ImVec4 color = value ? ConvertColorToImVec4(theme.success) : - ConvertColorToImVec4(theme.text_disabled); - ImGui::TextColored(color, "%s %s", value ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK, name); + ImVec4 color = value ? ConvertColorToImVec4(theme.success) + : ConvertColorToImVec4(theme.text_disabled); + ImGui::TextColored( + color, "%s %s", + value ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK, name); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s: %s", name, value ? "Set" : "Clear"); } ImGui::SameLine(); }; - + RenderFlag("N", cpu.GetNegativeFlag()); RenderFlag("V", cpu.GetOverflowFlag()); RenderFlag("D", cpu.GetDecimalFlag()); @@ -148,26 +163,28 @@ void RenderModernCpuDebugger(Emulator* emu) { RenderFlag("Z", cpu.GetZeroFlag()); RenderFlag("C", cpu.GetCarryFlag()); ImGui::NewLine(); - + ImGui::Unindent(); } - + AddSpacing(); - + // Breakpoint Management if (ImGui::CollapsingHeader(ICON_MD_STOP_CIRCLE " Breakpoints")) { static char bp_input[10] = ""; - + ImGui::SetNextItemWidth(150); - if (ImGui::InputTextWithHint("##BP", "Address (hex)", bp_input, sizeof(bp_input), - ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) { + if (ImGui::InputTextWithHint("##BP", "Address (hex)", bp_input, + sizeof(bp_input), + ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_EnterReturnsTrue)) { if (strlen(bp_input) > 0) { uint32_t addr = std::stoi(bp_input, nullptr, 16); emu->SetBreakpoint(addr); memset(bp_input, 0, sizeof(bp_input)); } } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_ADD " Add", ImVec2(80, 0))) { if (strlen(bp_input) > 0) { @@ -176,14 +193,14 @@ void RenderModernCpuDebugger(Emulator* emu) { memset(bp_input, 0, sizeof(bp_input)); } } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear All", ImVec2(100, 0))) { emu->ClearAllBreakpoints(); } - + AddSpacing(); - + // List breakpoints auto breakpoints = emu->GetBreakpoints(); if (!breakpoints.empty()) { @@ -191,62 +208,66 @@ void RenderModernCpuDebugger(Emulator* emu) { for (size_t i = 0; i < breakpoints.size(); ++i) { uint32_t bp = breakpoints[i]; ImGui::PushID(i); - - ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_STOP " $%06X", bp); - + + ImGui::TextColored(ConvertColorToImVec4(theme.accent), + ICON_MD_STOP " $%06X", bp); + ImGui::SameLine(200); if (ImGui::SmallButton(ICON_MD_DELETE " Remove")) { cpu.ClearBreakpoint(bp); } - + ImGui::PopID(); } ImGui::EndChild(); } else { - ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled), - "No breakpoints set"); + ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled), + "No breakpoints set"); } } - + ImGui::EndChild(); ImGui::PopStyleColor(); } void RenderBreakpointList(Emulator* emu) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##BreakpointList", ImVec2(0, 0), true); - - ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_STOP_CIRCLE " Breakpoint Manager"); + + ImGui::TextColored(ConvertColorToImVec4(theme.accent), + ICON_MD_STOP_CIRCLE " Breakpoint Manager"); AddSectionSpacing(); - + // Same content as in RenderModernCpuDebugger but with more detail auto breakpoints = emu->GetBreakpoints(); - + ImGui::Text("Active Breakpoints: %zu", breakpoints.size()); AddSpacing(); - + if (!breakpoints.empty()) { - if (ImGui::BeginTable("BreakpointTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn(ICON_MD_TAG, ImGuiTableColumnFlags_WidthFixed, 40); + if (ImGui::BeginTable("BreakpointTable", 3, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn(ICON_MD_TAG, ImGuiTableColumnFlags_WidthFixed, + 40); ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); - + for (size_t i = 0; i < breakpoints.size(); ++i) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextColored(ConvertColorToImVec4(theme.error), ICON_MD_STOP); - + ImGui::TableNextColumn(); - ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%06X", breakpoints[i]); - + ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%06X", + breakpoints[i]); + ImGui::TableNextColumn(); ImGui::PushID(i); if (ImGui::SmallButton(ICON_MD_DELETE " Remove")) { @@ -254,108 +275,117 @@ void RenderBreakpointList(Emulator* emu) { } ImGui::PopID(); } - + ImGui::EndTable(); } } else { ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled), - ICON_MD_INFO " No breakpoints set"); + ICON_MD_INFO " No breakpoints set"); } - + ImGui::EndChild(); ImGui::PopStyleColor(); } void RenderMemoryViewer(Emulator* emu) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + static MemoryEditor mem_edit; - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##MemoryViewer", ImVec2(0, 0), true); - + ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_STORAGE " Memory Viewer"); + ICON_MD_STORAGE " Memory Viewer"); AddSectionSpacing(); - + // Memory region selector static int region = 0; - const char* regions[] = {"RAM ($0000-$1FFF)", "ROM Bank 0", "WRAM ($7E0000-$7FFFFF)", "SRAM"}; - + const char* regions[] = {"RAM ($0000-$1FFF)", "ROM Bank 0", + "WRAM ($7E0000-$7FFFFF)", "SRAM"}; + ImGui::SetNextItemWidth(250); - if (ImGui::Combo(ICON_MD_MAP " Region", ®ion, regions, IM_ARRAYSIZE(regions))) { + if (ImGui::Combo(ICON_MD_MAP " Region", ®ion, regions, + IM_ARRAYSIZE(regions))) { // Region changed } - + AddSpacing(); - + // Render memory editor uint8_t* memory_base = emu->snes().get_ram(); size_t memory_size = 0x20000; - + mem_edit.DrawContents(memory_base, memory_size, 0x0000); - + ImGui::EndChild(); ImGui::PopStyleColor(); } void RenderCpuInstructionLog(Emulator* emu, uint32_t log_size) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##InstructionLog", ImVec2(0, 0), true); - + ImGui::TextColored(ConvertColorToImVec4(theme.warning), - ICON_MD_WARNING " Legacy Instruction Log"); + ICON_MD_WARNING " Legacy Instruction Log"); ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled), - "Deprecated - Use Disassembly Viewer instead"); + "Deprecated - Use Disassembly Viewer instead"); AddSectionSpacing(); - + // Show DisassemblyViewer stats instead ImGui::Text(ICON_MD_INFO " DisassemblyViewer Active:"); - ImGui::BulletText("Unique addresses: %zu", emu->disassembly_viewer().GetInstructionCount()); - ImGui::BulletText("Recording: %s", emu->disassembly_viewer().IsRecording() ? "ON" : "OFF"); + ImGui::BulletText("Unique addresses: %zu", + emu->disassembly_viewer().GetInstructionCount()); + ImGui::BulletText("Recording: %s", + emu->disassembly_viewer().IsRecording() ? "ON" : "OFF"); ImGui::BulletText("Auto-scroll: Available in viewer"); - + AddSpacing(); - - if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Open Disassembly Viewer", ImVec2(-1, kLargeButtonHeight))) { + + if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Open Disassembly Viewer", + ImVec2(-1, kLargeButtonHeight))) { // TODO: Open disassembly viewer window } - + ImGui::EndChild(); ImGui::PopStyleColor(); } void RenderApuDebugger(Emulator* emu) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##ApuDebugger", ImVec2(0, 0), true); - + // Title ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_MUSIC_NOTE " APU / SPC700 Debugger"); + ICON_MD_MUSIC_NOTE " APU / SPC700 Debugger"); AddSectionSpacing(); - + auto& tracker = emu->snes().apu_handshake_tracker(); - + // Handshake Status with enhanced visuals - if (ImGui::CollapsingHeader(ICON_MD_HANDSHAKE " Handshake Status", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_HANDSHAKE " Handshake Status", + ImGuiTreeNodeFlags_DefaultOpen)) { // Phase with icon and color auto phase_str = tracker.GetPhaseString(); ImVec4 phase_color; const char* phase_icon; - + if (phase_str == "RUNNING") { phase_color = ConvertColorToImVec4(theme.success); phase_icon = ICON_MD_CHECK_CIRCLE; @@ -369,22 +399,22 @@ void RenderApuDebugger(Emulator* emu) { phase_color = ConvertColorToImVec4(theme.error); phase_icon = ICON_MD_ERROR; } - + ImGui::Text(ICON_MD_SETTINGS " Phase:"); ImGui::SameLine(); ImGui::TextColored(phase_color, "%s %s", phase_icon, phase_str.c_str()); - + // Handshake complete indicator ImGui::Text(ICON_MD_LINK " Handshake:"); ImGui::SameLine(); if (tracker.IsHandshakeComplete()) { - ImGui::TextColored(ConvertColorToImVec4(theme.success), - ICON_MD_CHECK_CIRCLE " Complete"); + ImGui::TextColored(ConvertColorToImVec4(theme.success), + ICON_MD_CHECK_CIRCLE " Complete"); } else { - ImGui::TextColored(ConvertColorToImVec4(theme.warning), - ICON_MD_HOURGLASS_EMPTY " Waiting"); + ImGui::TextColored(ConvertColorToImVec4(theme.warning), + ICON_MD_HOURGLASS_EMPTY " Waiting"); } - + // Transfer progress if (tracker.IsTransferActive() || tracker.GetBytesTransferred() > 0) { AddSpacing(); @@ -392,96 +422,101 @@ void RenderApuDebugger(Emulator* emu) { ImGui::Indent(); ImGui::BulletText("Bytes: %d", tracker.GetBytesTransferred()); ImGui::BulletText("Blocks: %d", tracker.GetBlockCount()); - + auto progress = tracker.GetTransferProgress(); if (!progress.empty()) { - ImGui::TextColored(ConvertColorToImVec4(theme.info), "%s", progress.c_str()); + ImGui::TextColored(ConvertColorToImVec4(theme.info), "%s", + progress.c_str()); } ImGui::Unindent(); } - + // Status summary AddSectionSpacing(); ImGui::TextWrapped("%s", tracker.GetStatusSummary().c_str()); } - + // Port Activity Log - if (ImGui::CollapsingHeader(ICON_MD_LIST " Port Activity Log", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_LIST " Port Activity Log", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::BeginChild("##PortLog", ImVec2(0, 200), true); - + const auto& history = tracker.GetPortHistory(); - + if (history.empty()) { ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled), - ICON_MD_INFO " No port activity yet"); + ICON_MD_INFO " No port activity yet"); } else { // Show last 50 entries int start_idx = std::max(0, static_cast(history.size()) - 50); for (size_t i = start_idx; i < history.size(); ++i) { const auto& entry = history[i]; - + ImVec4 color = entry.is_cpu ? ConvertColorToImVec4(theme.accent) : ConvertColorToImVec4(theme.info); - const char* icon = entry.is_cpu ? ICON_MD_ARROW_FORWARD : ICON_MD_ARROW_BACK; - + const char* icon = + entry.is_cpu ? ICON_MD_ARROW_FORWARD : ICON_MD_ARROW_BACK; + ImGui::TextColored(color, "[%04llu] %s %s F%d = $%02X @ PC=$%04X %s", - entry.timestamp, - entry.is_cpu ? "CPU" : "SPC", - icon, - entry.port + 4, - entry.value, - entry.pc, - entry.description.c_str()); + entry.timestamp, entry.is_cpu ? "CPU" : "SPC", icon, + entry.port + 4, entry.value, entry.pc, + entry.description.c_str()); } - + // Auto-scroll if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { ImGui::SetScrollHereY(1.0f); } } - + ImGui::EndChild(); } - + // Current Port Values - if (ImGui::CollapsingHeader(ICON_MD_SETTINGS_INPUT_COMPONENT " Current Port Values", - ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginTable("APU_Ports", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + if (ImGui::CollapsingHeader(ICON_MD_SETTINGS_INPUT_COMPONENT + " Current Port Values", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::BeginTable("APU_Ports", 4, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Port", ImGuiTableColumnFlags_WidthFixed, 50); - ImGui::TableSetupColumn("CPU → SPC", ImGuiTableColumnFlags_WidthFixed, 80); - ImGui::TableSetupColumn("SPC → CPU", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("CPU → SPC", ImGuiTableColumnFlags_WidthFixed, + 80); + ImGui::TableSetupColumn("SPC → CPU", ImGuiTableColumnFlags_WidthFixed, + 80); ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); - + for (int i = 0; i < 4; ++i) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text(ICON_MD_SETTINGS " F%d", i + 4); - + ImGui::TableNextColumn(); - ImGui::TextColored(ConvertColorToImVec4(theme.accent), - "$%02X", emu->snes().apu().in_ports_[i]); - + ImGui::TextColored(ConvertColorToImVec4(theme.accent), "$%02X", + emu->snes().apu().in_ports_[i]); + ImGui::TableNextColumn(); - ImGui::TextColored(ConvertColorToImVec4(theme.info), - "$%02X", emu->snes().apu().out_ports_[i]); - + ImGui::TextColored(ConvertColorToImVec4(theme.info), "$%02X", + emu->snes().apu().out_ports_[i]); + ImGui::TableNextColumn(); ImGui::TextDisabled("$214%d / $F%d", i, i + 4); } - + ImGui::EndTable(); } } - + // Quick Actions - if (ImGui::CollapsingHeader(ICON_MD_BUILD " Quick Actions", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_BUILD " Quick Actions", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::TextColored(ConvertColorToImVec4(theme.warning), - ICON_MD_WARNING " Manual Testing Tools"); + ICON_MD_WARNING " Manual Testing Tools"); AddSpacing(); - + // Full handshake test - if (ImGui::Button(ICON_MD_PLAY_CIRCLE " Full Handshake Test", ImVec2(-1, kLargeButtonHeight))) { + if (ImGui::Button(ICON_MD_PLAY_CIRCLE " Full Handshake Test", + ImVec2(-1, kLargeButtonHeight))) { LOG_INFO("APU_DEBUG", "=== MANUAL HANDSHAKE TEST ==="); emu->snes().Write(0x002140, 0xCC); emu->snes().Write(0x002141, 0x01); @@ -490,23 +525,24 @@ void RenderApuDebugger(Emulator* emu) { LOG_INFO("APU_DEBUG", "Handshake sequence executed"); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Execute full handshake sequence:\n" - "$CC → F4, $01 → F5, $00 → F6, $02 → F7"); + ImGui::SetTooltip( + "Execute full handshake sequence:\n" + "$CC → F4, $01 → F5, $00 → F6, $02 → F7"); } - + AddSpacing(); - + // Manual port writes if (ImGui::TreeNode(ICON_MD_EDIT " Manual Port Writes")) { static uint8_t port_values[4] = {0xCC, 0x01, 0x00, 0x02}; - + for (int i = 0; i < 4; ++i) { ImGui::PushID(i); ImGui::Text("F%d ($214%d):", i + 4, i); ImGui::SameLine(); ImGui::SetNextItemWidth(80); - ImGui::InputScalar("##val", ImGuiDataType_U8, &port_values[i], NULL, NULL, "%02X", - ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("##val", ImGuiDataType_U8, &port_values[i], NULL, + NULL, "%02X", ImGuiInputTextFlags_CharsHexadecimal); ImGui::SameLine(); if (ImGui::Button(ICON_MD_SEND " Write", ImVec2(100, 0))) { emu->snes().Write(0x002140 + i, port_values[i]); @@ -514,19 +550,21 @@ void RenderApuDebugger(Emulator* emu) { } ImGui::PopID(); } - + ImGui::TreePop(); } - + AddSectionSpacing(); - + // System controls - if (ImGui::Button(ICON_MD_RESTART_ALT " Reset APU", ImVec2(-1, kButtonHeight))) { + if (ImGui::Button(ICON_MD_RESTART_ALT " Reset APU", + ImVec2(-1, kButtonHeight))) { emu->snes().apu().Reset(); LOG_INFO("APU_DEBUG", "APU reset"); } - - if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear Port History", ImVec2(-1, kButtonHeight))) { + + if (ImGui::Button(ICON_MD_CLEAR_ALL " Clear Port History", + ImVec2(-1, kButtonHeight))) { tracker.Reset(); LOG_INFO("APU_DEBUG", "Port history cleared"); } @@ -537,64 +575,69 @@ void RenderApuDebugger(Emulator* emu) { // Combo box for interpolation type const char* items[] = {"Linear", "Hermite", "Cosine", "Cubic"}; - int current_item = static_cast(emu->snes().apu().dsp().interpolation_type); - if (ImGui::Combo("Interpolation", ¤t_item, items, IM_ARRAYSIZE(items))) { + int current_item = + static_cast(emu->snes().apu().dsp().interpolation_type); + if (ImGui::Combo("Interpolation", ¤t_item, items, + IM_ARRAYSIZE(items))) { emu->snes().apu().dsp().interpolation_type = static_cast(current_item); } - + ImGui::EndChild(); ImGui::PopStyleColor(); } void RenderAIAgentPanel(Emulator* emu) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##AIAgent", ImVec2(0, 0), true); - + ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_SMART_TOY " AI Agent Integration"); + ICON_MD_SMART_TOY " AI Agent Integration"); AddSectionSpacing(); - + // Agent status bool agent_ready = emu->IsEmulatorReady(); - ImVec4 status_color = agent_ready ? ConvertColorToImVec4(theme.success) : - ConvertColorToImVec4(theme.error); - + ImVec4 status_color = agent_ready ? ConvertColorToImVec4(theme.success) + : ConvertColorToImVec4(theme.error); + ImGui::Text("Status:"); ImGui::SameLine(); - ImGui::TextColored(status_color, "%s %s", - agent_ready ? ICON_MD_CHECK_CIRCLE : ICON_MD_ERROR, - agent_ready ? "Ready" : "Not Ready"); - + ImGui::TextColored(status_color, "%s %s", + agent_ready ? ICON_MD_CHECK_CIRCLE : ICON_MD_ERROR, + agent_ready ? "Ready" : "Not Ready"); + AddSpacing(); - + // Emulator metrics for agents - if (ImGui::CollapsingHeader(ICON_MD_DATA_OBJECT " Metrics", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_DATA_OBJECT " Metrics", + ImGuiTreeNodeFlags_DefaultOpen)) { auto metrics = emu->GetMetrics(); - + ImGui::BulletText("FPS: %.2f", metrics.fps); ImGui::BulletText("Cycles: %llu", metrics.cycles); ImGui::BulletText("CPU PC: $%02X:%04X", metrics.cpu_pb, metrics.cpu_pc); ImGui::BulletText("Audio Queued: %u frames", metrics.audio_frames_queued); ImGui::BulletText("Running: %s", metrics.is_running ? "YES" : "NO"); } - + // Agent controls if (ImGui::CollapsingHeader(ICON_MD_PLAY_CIRCLE " Agent Controls")) { - if (ImGui::Button(ICON_MD_PLAY_ARROW " Start Agent Session", ImVec2(-1, kLargeButtonHeight))) { + if (ImGui::Button(ICON_MD_PLAY_ARROW " Start Agent Session", + ImVec2(-1, kLargeButtonHeight))) { // TODO: Start agent } - + if (ImGui::Button(ICON_MD_STOP " Stop Agent", ImVec2(-1, kButtonHeight))) { // TODO: Stop agent } } - + ImGui::EndChild(); ImGui::PopStyleColor(); } @@ -602,4 +645,3 @@ void RenderAIAgentPanel(Emulator* emu) { } // namespace ui } // namespace emu } // namespace yaze - diff --git a/src/app/emu/ui/debugger_ui.h b/src/app/emu/ui/debugger_ui.h index 7e4e9fc2..5141bf99 100644 --- a/src/app/emu/ui/debugger_ui.h +++ b/src/app/emu/ui/debugger_ui.h @@ -47,4 +47,3 @@ void RenderAIAgentPanel(Emulator* emu); } // namespace yaze #endif // YAZE_APP_EMU_UI_DEBUGGER_UI_H_ - diff --git a/src/app/emu/ui/emulator_ui.cc b/src/app/emu/ui/emulator_ui.cc index 75674f46..300444be 100644 --- a/src/app/emu/ui/emulator_ui.cc +++ b/src/app/emu/ui/emulator_ui.cc @@ -39,30 +39,33 @@ void AddSectionSpacing() { } // namespace void RenderNavBar(Emulator* emu) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + // Handle keyboard shortcuts for emulator control // IMPORTANT: Use Shortcut() to avoid conflicts with game input // Space - toggle play/pause (only when not typing in text fields) if (ImGui::Shortcut(ImGuiKey_Space, ImGuiInputFlags_RouteGlobal)) { emu->set_running(!emu->running()); } - + // F10 - step one frame if (ImGui::Shortcut(ImGuiKey_F10, ImGuiInputFlags_RouteGlobal)) { if (!emu->running()) { emu->snes().RunFrame(); } } - + // Navbar with theme colors ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.button)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active)); - + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ConvertColorToImVec4(theme.button_hovered)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ConvertColorToImVec4(theme.button_active)); + // Play/Pause button with icon bool is_running = emu->running(); if (is_running) { @@ -80,9 +83,9 @@ void RenderNavBar(Emulator* emu) { ImGui::SetTooltip("Start emulation (Space)"); } } - + ImGui::SameLine(); - + // Step button if (ImGui::Button(ICON_MD_SKIP_NEXT, ImVec2(50, kButtonHeight))) { if (!is_running) { @@ -92,9 +95,9 @@ void RenderNavBar(Emulator* emu) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Step one frame (F10)"); } - + ImGui::SameLine(); - + // Reset button if (ImGui::Button(ICON_MD_RESTART_ALT, ImVec2(50, kButtonHeight))) { emu->snes().Reset(); @@ -107,7 +110,8 @@ void RenderNavBar(Emulator* emu) { ImGui::SameLine(); // Load ROM button - if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load ROM", ImVec2(110, kButtonHeight))) { + if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load ROM", + ImVec2(110, kButtonHeight))) { std::string rom_path = util::FileDialogWrapper::ShowOpenFileDialog(); if (!rom_path.empty()) { // Check if it's a valid ROM file extension @@ -118,40 +122,42 @@ void RenderNavBar(Emulator* emu) { std::ifstream rom_file(rom_path, std::ios::binary); if (rom_file.good()) { std::vector rom_data( - (std::istreambuf_iterator(rom_file)), - std::istreambuf_iterator() - ); + (std::istreambuf_iterator(rom_file)), + std::istreambuf_iterator()); rom_file.close(); // Reinitialize emulator with new ROM if (!rom_data.empty()) { emu->Initialize(emu->renderer(), rom_data); LOG_INFO("Emulator", "Loaded ROM: %s (%zu bytes)", - util::GetFileName(rom_path).c_str(), rom_data.size()); + util::GetFileName(rom_path).c_str(), rom_data.size()); } else { LOG_ERROR("Emulator", "ROM file is empty: %s", rom_path.c_str()); } } else { - LOG_ERROR("Emulator", "Failed to open ROM file: %s", rom_path.c_str()); + LOG_ERROR("Emulator", "Failed to open ROM file: %s", + rom_path.c_str()); } } catch (const std::exception& e) { LOG_ERROR("Emulator", "Error loading ROM: %s", e.what()); } } else { - LOG_WARN("Emulator", "Invalid ROM file extension: %s (expected .sfc or .smc)", - ext.c_str()); + LOG_WARN("Emulator", + "Invalid ROM file extension: %s (expected .sfc or .smc)", + ext.c_str()); } } } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Load a different ROM file\n" - "Allows testing hacks with assembly patches applied"); + ImGui::SetTooltip( + "Load a different ROM file\n" + "Allows testing hacks with assembly patches applied"); } ImGui::SameLine(); ImGui::Separator(); ImGui::SameLine(); - + // Debugger toggle bool is_debugging = emu->is_debugging(); if (ImGui::Checkbox(ICON_MD_BUG_REPORT " Debug", &is_debugging)) { @@ -160,9 +166,9 @@ void RenderNavBar(Emulator* emu) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Enable debugger features"); } - + ImGui::SameLine(); - + // Recording toggle (for DisassemblyViewer) // Access through emulator's disassembly viewer // bool recording = emu->disassembly_viewer().IsRecording(); @@ -172,9 +178,9 @@ void RenderNavBar(Emulator* emu) { // if (ImGui::IsItemHovered()) { // ImGui::SetTooltip("Record instructions to Disassembly Viewer\n(Lightweight - uses sparse address map)"); // } - + ImGui::SameLine(); - + // Turbo mode bool turbo = emu->is_turbo_mode(); if (ImGui::Checkbox(ICON_MD_FAST_FORWARD " Turbo", &turbo)) { @@ -183,11 +189,11 @@ void RenderNavBar(Emulator* emu) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Fast forward (shortcut: hold Tab)"); } - + ImGui::SameLine(); ImGui::Separator(); ImGui::SameLine(); - + // FPS Counter with color coding double fps = emu->GetCurrentFPS(); ImVec4 fps_color; @@ -196,34 +202,35 @@ void RenderNavBar(Emulator* emu) { } else if (fps >= 45.0) { fps_color = ConvertColorToImVec4(theme.warning); // Yellow for okay FPS } else { - fps_color = ConvertColorToImVec4(theme.error); // Red for bad FPS + fps_color = ConvertColorToImVec4(theme.error); // Red for bad FPS } - + ImGui::TextColored(fps_color, ICON_MD_SPEED " %.1f FPS", fps); - + ImGui::SameLine(); // Audio backend status if (emu->audio_backend()) { auto audio_status = emu->audio_backend()->GetStatus(); - ImVec4 audio_color = audio_status.is_playing ? - ConvertColorToImVec4(theme.success) : ConvertColorToImVec4(theme.text_disabled); + ImVec4 audio_color = audio_status.is_playing + ? ConvertColorToImVec4(theme.success) + : ConvertColorToImVec4(theme.text_disabled); ImGui::TextColored(audio_color, ICON_MD_VOLUME_UP " %s | %u frames", - emu->audio_backend()->GetBackendName().c_str(), - audio_status.queued_frames); + emu->audio_backend()->GetBackendName().c_str(), + audio_status.queued_frames); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Audio Backend: %s\nQueued: %u frames\nPlaying: %s", - emu->audio_backend()->GetBackendName().c_str(), - audio_status.queued_frames, - audio_status.is_playing ? "YES" : "NO"); + emu->audio_backend()->GetBackendName().c_str(), + audio_status.queued_frames, + audio_status.is_playing ? "YES" : "NO"); } - ImGui::SameLine(); static bool use_sdl_audio_stream = emu->use_sdl_audio_stream(); - if (ImGui::Checkbox(ICON_MD_SETTINGS " SDL Audio Stream", &use_sdl_audio_stream)) { + if (ImGui::Checkbox(ICON_MD_SETTINGS " SDL Audio Stream", + &use_sdl_audio_stream)) { emu->set_use_sdl_audio_stream(use_sdl_audio_stream); } if (ImGui::IsItemHovered()) { @@ -231,7 +238,7 @@ void RenderNavBar(Emulator* emu) { } } else { ImGui::TextColored(ConvertColorToImVec4(theme.error), - ICON_MD_VOLUME_OFF " No Backend"); + ICON_MD_VOLUME_OFF " No Backend"); } ImGui::SameLine(); @@ -243,14 +250,14 @@ void RenderNavBar(Emulator* emu) { if (io.WantCaptureKeyboard) { // ImGui is capturing keyboard (typing in UI) ImGui::TextColored(ConvertColorToImVec4(theme.warning), - ICON_MD_KEYBOARD " UI"); + ICON_MD_KEYBOARD " UI"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Keyboard captured by UI\nGame input disabled"); } } else { // Emulator can receive input ImGui::TextColored(ConvertColorToImVec4(theme.success), - ICON_MD_SPORTS_ESPORTS " Game"); + ICON_MD_SPORTS_ESPORTS " Game"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Game input active\nPress F1 for controls"); } @@ -260,14 +267,17 @@ void RenderNavBar(Emulator* emu) { } void RenderSnesPpu(Emulator* emu) { - if (!emu) return; + if (!emu) + return; auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.editor_background)); - ImGui::BeginChild("##SNES_PPU", ImVec2(0, 0), true, - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::PushStyleColor(ImGuiCol_ChildBg, + ConvertColorToImVec4(theme.editor_background)); + ImGui::BeginChild( + "##SNES_PPU", ImVec2(0, 0), true, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImVec2 canvas_size = ImGui::GetContentRegionAvail(); ImVec2 snes_size = ImVec2(512, 480); @@ -290,8 +300,7 @@ void RenderSnesPpu(Emulator* emu) { // Render PPU texture with click detection for focus ImGui::Image((ImTextureID)(intptr_t)emu->ppu_texture(), - ImVec2(display_w, display_h), - ImVec2(0, 0), ImVec2(1, 1)); + ImVec2(display_w, display_h), ImVec2(0, 0), ImVec2(1, 1)); // Allow clicking on the display to ensure focus // Modern emulators make the game area "sticky" for input @@ -302,23 +311,25 @@ void RenderSnesPpu(Emulator* emu) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 screen_pos = ImGui::GetItemRectMin(); ImVec2 screen_size = ImGui::GetItemRectMax(); - draw_list->AddRect(screen_pos, screen_size, - ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(theme.accent)), - 0.0f, 0, 2.0f); + draw_list->AddRect( + screen_pos, screen_size, + ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(theme.accent)), + 0.0f, 0, 2.0f); } } else { // Not initialized - show helpful placeholder ImVec2 text_size = ImGui::CalcTextSize("Load a ROM to start emulation"); ImGui::SetCursorPos(ImVec2((canvas_size.x - text_size.x) * 0.5f, - (canvas_size.y - text_size.y) * 0.5f - 20)); + (canvas_size.y - text_size.y) * 0.5f - 20)); ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled), - ICON_MD_VIDEOGAME_ASSET); + ICON_MD_VIDEOGAME_ASSET); ImGui::SetCursorPosX((canvas_size.x - text_size.x) * 0.5f); ImGui::TextColored(ConvertColorToImVec4(theme.text_primary), - "Load a ROM to start emulation"); - ImGui::SetCursorPosX((canvas_size.x - ImGui::CalcTextSize("512x480 SNES output").x) * 0.5f); + "Load a ROM to start emulation"); + ImGui::SetCursorPosX( + (canvas_size.x - ImGui::CalcTextSize("512x480 SNES output").x) * 0.5f); ImGui::TextColored(ConvertColorToImVec4(theme.text_disabled), - "512x480 SNES output"); + "512x480 SNES output"); } ImGui::EndChild(); @@ -326,52 +337,59 @@ void RenderSnesPpu(Emulator* emu) { } void RenderPerformanceMonitor(Emulator* emu) { - if (!emu) return; - + if (!emu) + return; + auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.child_bg)); ImGui::BeginChild("##Performance", ImVec2(0, 0), true); - - ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_SPEED " Performance Monitor"); + + ImGui::TextColored(ConvertColorToImVec4(theme.accent), + ICON_MD_SPEED " Performance Monitor"); AddSectionSpacing(); - + auto metrics = emu->GetMetrics(); - + // FPS Graph - if (ImGui::CollapsingHeader(ICON_MD_SHOW_CHART " Frame Rate", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_SHOW_CHART " Frame Rate", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Text("Current: %.2f FPS", metrics.fps); - ImGui::Text("Target: %.2f FPS", emu->snes().memory().pal_timing() ? 50.0 : 60.0); - + ImGui::Text("Target: %.2f FPS", + emu->snes().memory().pal_timing() ? 50.0 : 60.0); + // TODO: Add FPS graph with ImPlot } - + // CPU Stats - if (ImGui::CollapsingHeader(ICON_MD_MEMORY " CPU Status", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_MEMORY " CPU Status", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Text("PC: $%02X:%04X", metrics.cpu_pb, metrics.cpu_pc); ImGui::Text("Cycles: %llu", metrics.cycles); } - + // Audio Stats - if (ImGui::CollapsingHeader(ICON_MD_AUDIOTRACK " Audio Status", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_AUDIOTRACK " Audio Status", + ImGuiTreeNodeFlags_DefaultOpen)) { if (emu->audio_backend()) { auto audio_status = emu->audio_backend()->GetStatus(); - ImGui::Text("Backend: %s", emu->audio_backend()->GetBackendName().c_str()); + ImGui::Text("Backend: %s", + emu->audio_backend()->GetBackendName().c_str()); ImGui::Text("Queued: %u frames", audio_status.queued_frames); ImGui::Text("Playing: %s", audio_status.is_playing ? "YES" : "NO"); } else { ImGui::TextColored(ConvertColorToImVec4(theme.error), "No audio backend"); } } - + ImGui::EndChild(); ImGui::PopStyleColor(); } void RenderKeyboardShortcuts(bool* show) { - if (!show || !*show) return; + if (!show || !*show) + return; auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); @@ -382,13 +400,14 @@ void RenderKeyboardShortcuts(bool* show) { ImGui::SetNextWindowSize(ImVec2(550, 600), ImGuiCond_Appearing); ImGui::PushStyleColor(ImGuiCol_TitleBg, ConvertColorToImVec4(theme.accent)); - ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ConvertColorToImVec4(theme.accent)); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, + ConvertColorToImVec4(theme.accent)); if (ImGui::Begin(ICON_MD_KEYBOARD " Keyboard Shortcuts", show, ImGuiWindowFlags_NoCollapse)) { // Emulator controls section ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_VIDEOGAME_ASSET " Emulator Controls"); + ICON_MD_VIDEOGAME_ASSET " Emulator Controls"); ImGui::Separator(); ImGui::Spacing(); @@ -419,7 +438,7 @@ void RenderKeyboardShortcuts(bool* show) { // Game controls section ImGui::TextColored(ConvertColorToImVec4(theme.accent), - ICON_MD_SPORTS_ESPORTS " SNES Controller"); + ICON_MD_SPORTS_ESPORTS " SNES Controller"); ImGui::Separator(); ImGui::Spacing(); @@ -453,8 +472,7 @@ void RenderKeyboardShortcuts(bool* show) { ImGui::Spacing(); // Tips section - ImGui::TextColored(ConvertColorToImVec4(theme.info), - ICON_MD_INFO " Tips"); + ImGui::TextColored(ConvertColorToImVec4(theme.info), ICON_MD_INFO " Tips"); ImGui::Separator(); ImGui::Spacing(); @@ -476,13 +494,15 @@ void RenderKeyboardShortcuts(bool* show) { } void RenderEmulatorInterface(Emulator* emu) { - if (!emu) return; + if (!emu) + return; auto& theme_manager = ThemeManager::Get(); const auto& theme = theme_manager.GetCurrentTheme(); // Main layout - ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.window_bg)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, + ConvertColorToImVec4(theme.window_bg)); RenderNavBar(emu); @@ -504,4 +524,3 @@ void RenderEmulatorInterface(Emulator* emu) { } // namespace ui } // namespace emu } // namespace yaze - diff --git a/src/app/emu/ui/emulator_ui.h b/src/app/emu/ui/emulator_ui.h index 20f923cb..3ec0dff6 100644 --- a/src/app/emu/ui/emulator_ui.h +++ b/src/app/emu/ui/emulator_ui.h @@ -42,4 +42,3 @@ void RenderKeyboardShortcuts(bool* show); } // namespace yaze #endif // YAZE_APP_EMU_UI_EMULATOR_UI_H_ - diff --git a/src/app/emu/ui/input_handler.cc b/src/app/emu/ui/input_handler.cc index 734bf739..0b6e4f94 100644 --- a/src/app/emu/ui/input_handler.cc +++ b/src/app/emu/ui/input_handler.cc @@ -10,54 +10,57 @@ namespace emu { namespace ui { void RenderKeyboardConfig(input::InputManager* manager) { - if (!manager || !manager->backend()) return; - + if (!manager || !manager->backend()) + return; + auto config = manager->GetConfig(); - - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), + + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_INFO " Keyboard Configuration"); ImGui::Separator(); - + ImGui::Text("Backend: %s", manager->backend()->GetBackendName().c_str()); ImGui::Separator(); - - ImGui::TextWrapped("Configure keyboard bindings for SNES controller emulation. " - "Click a button and press a key to rebind."); + + ImGui::TextWrapped( + "Configure keyboard bindings for SNES controller emulation. " + "Click a button and press a key to rebind."); ImGui::Spacing(); - + auto RenderKeyBind = [&](const char* label, int* key) { ImGui::Text("%s:", label); ImGui::SameLine(150); - + // Show current key const char* key_name = SDL_GetKeyName(*key); ImGui::PushID(label); if (ImGui::Button(key_name, ImVec2(120, 0))) { ImGui::OpenPopup("Rebind"); } - + if (ImGui::BeginPopup("Rebind")) { ImGui::Text("Press any key..."); ImGui::Separator(); - + // Poll for key press (SDL2-specific for now) SDL_Event event; if (SDL_PollEvent(&event) && event.type == SDL_KEYDOWN) { - if (event.key.keysym.sym != SDLK_UNKNOWN && event.key.keysym.sym != SDLK_ESCAPE) { + if (event.key.keysym.sym != SDLK_UNKNOWN && + event.key.keysym.sym != SDLK_ESCAPE) { *key = event.key.keysym.sym; ImGui::CloseCurrentPopup(); } } - + if (ImGui::Button("Cancel", ImVec2(-1, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } ImGui::PopID(); }; - + // Face Buttons if (ImGui::CollapsingHeader("Face Buttons", ImGuiTreeNodeFlags_DefaultOpen)) { RenderKeyBind("A Button", &config.key_a); @@ -65,7 +68,7 @@ void RenderKeyboardConfig(input::InputManager* manager) { RenderKeyBind("X Button", &config.key_x); RenderKeyBind("Y Button", &config.key_y); } - + // D-Pad if (ImGui::CollapsingHeader("D-Pad", ImGuiTreeNodeFlags_DefaultOpen)) { RenderKeyBind("Up", &config.key_up); @@ -73,47 +76,48 @@ void RenderKeyboardConfig(input::InputManager* manager) { RenderKeyBind("Left", &config.key_left); RenderKeyBind("Right", &config.key_right); } - + // Shoulder Buttons if (ImGui::CollapsingHeader("Shoulder Buttons")) { RenderKeyBind("L Button", &config.key_l); RenderKeyBind("R Button", &config.key_r); } - + // Start/Select if (ImGui::CollapsingHeader("Start/Select")) { RenderKeyBind("Start", &config.key_start); RenderKeyBind("Select", &config.key_select); } - + ImGui::Spacing(); ImGui::Separator(); - + // Input mode ImGui::Checkbox("Continuous Polling", &config.continuous_polling); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Recommended: ON for games (detects held buttons)\nOFF for event-based input"); + ImGui::SetTooltip( + "Recommended: ON for games (detects held buttons)\nOFF for event-based " + "input"); } - + ImGui::Spacing(); - + // Apply button if (ImGui::Button("Apply Changes", ImVec2(-1, 30))) { manager->SetConfig(config); } - + ImGui::Spacing(); - + // Defaults if (ImGui::Button("Reset to Defaults", ImVec2(-1, 30))) { config = input::InputConfig(); // Reset to defaults manager->SetConfig(config); } - + ImGui::Spacing(); ImGui::Separator(); - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - "Default Bindings:"); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Default Bindings:"); ImGui::BulletText("A/B/X/Y: X/Z/S/A"); ImGui::BulletText("L/R: D/C"); ImGui::BulletText("D-Pad: Arrow Keys"); @@ -123,4 +127,3 @@ void RenderKeyboardConfig(input::InputManager* manager) { } // namespace ui } // namespace emu } // namespace yaze - diff --git a/src/app/emu/ui/input_handler.h b/src/app/emu/ui/input_handler.h index 113d7a9e..25423752 100644 --- a/src/app/emu/ui/input_handler.h +++ b/src/app/emu/ui/input_handler.h @@ -18,4 +18,3 @@ void RenderKeyboardConfig(input::InputManager* manager); } // namespace yaze #endif // YAZE_APP_EMU_UI_INPUT_HANDLER_H_ - diff --git a/src/app/emu/video/ppu.cc b/src/app/emu/video/ppu.cc index a0ee7486..b5d21479 100644 --- a/src/app/emu/video/ppu.cc +++ b/src/app/emu/video/ppu.cc @@ -146,9 +146,11 @@ void Ppu::RunLine(int line) { // called for lines 1-224/239 // evaluate sprites obj_pixel_buffer_.fill(0); - if (!forced_blank_) EvaluateSprites(line - 1); + if (!forced_blank_) + EvaluateSprites(line - 1); // actual line - if (mode == 7) CalculateMode7Starts(line); + if (mode == 7) + CalculateMode7Starts(line); for (int x = 0; x < 256; x++) { HandlePixel(x, line); } @@ -193,12 +195,18 @@ void Ppu::HandlePixel(int x, int y) { g >>= 1; b >>= 1; } - if (r > 31) r = 31; - if (g > 31) g = 31; - if (b > 31) b = 31; - if (r < 0) r = 0; - if (g < 0) g = 0; - if (b < 0) b = 0; + if (r > 31) + r = 31; + if (g > 31) + g = 31; + if (b > 31) + b = 31; + if (r < 0) + r = 0; + if (g < 0) + g = 0; + if (b < 0) + b = 0; } if (!(pseudo_hires_ || mode == 5 || mode == 6)) { r2 = r; @@ -207,13 +215,13 @@ void Ppu::HandlePixel(int x, int y) { } } int row = (y - 1) + (even_frame ? 0 : 239); - + // SDL_PIXELFORMAT_ARGB8888 with pixelOutputFormat=0 (BGRX) // Memory layout: [B][G][R][A] at offsets 0,1,2,3 respectively // Convert 5-bit SNES color (0-31) to 8-bit (0-255) via (val << 3) | (val >> 2) - // Two pixels per X position for hi-res support: + // Two pixels per X position for hi-res support: // pixel1 at x*8 + 0..3, pixel2 at x*8 + 4..7 - + // Apply brightness r = r * brightness / 15; g = g * brightness / 15; @@ -229,16 +237,18 @@ void Ppu::HandlePixel(int x, int y) { ((g2 << 3) | (g2 >> 2)); // Green channel pixelBuffer[row * 2048 + x * 8 + 2 + pixelOutputFormat] = ((r2 << 3) | (r2 >> 2)); // Red channel - pixelBuffer[row * 2048 + x * 8 + 3 + pixelOutputFormat] = 0xFF; // Alpha (opaque) - + pixelBuffer[row * 2048 + x * 8 + 3 + pixelOutputFormat] = + 0xFF; // Alpha (opaque) + // Second pixel (lo-res/subscreen) pixelBuffer[row * 2048 + x * 8 + 4 + pixelOutputFormat] = - ((b << 3) | (b >> 2)); // Blue channel + ((b << 3) | (b >> 2)); // Blue channel pixelBuffer[row * 2048 + x * 8 + 5 + pixelOutputFormat] = - ((g << 3) | (g >> 2)); // Green channel + ((g << 3) | (g >> 2)); // Green channel pixelBuffer[row * 2048 + x * 8 + 6 + pixelOutputFormat] = - ((r << 3) | (r >> 2)); // Red channel - pixelBuffer[row * 2048 + x * 8 + 7 + pixelOutputFormat] = 0xFF; // Alpha (opaque) + ((r << 3) | (r >> 2)); // Red channel + pixelBuffer[row * 2048 + x * 8 + 7 + pixelOutputFormat] = + 0xFF; // Alpha (opaque) } int Ppu::GetPixel(int x, int y, bool subscreen, int* r, int* g, int* b) { @@ -325,13 +335,15 @@ int Ppu::GetPixelForMode7(int x, int layer, bool priority) { bool outsideMap = xPos < 0 || xPos >= 1024 || yPos < 0 || yPos >= 1024; xPos &= 0x3ff; yPos &= 0x3ff; - if (!m7largeField) outsideMap = false; + if (!m7largeField) + outsideMap = false; uint8_t tile = outsideMap ? 0 : vram[(yPos >> 3) * 128 + (xPos >> 3)] & 0xff; uint8_t pixel = outsideMap && !m7charFill ? 0 : vram[tile * 64 + (yPos & 7) * 8 + (xPos & 7)] >> 8; if (layer == 1) { - if (((bool)(pixel & 0x80)) != priority) return 0; + if (((bool)(pixel & 0x80)) != priority) + return 0; return pixel & 0x7f; } return pixel; @@ -352,8 +364,10 @@ bool Ppu::GetWindowState(int layer, int x) { } bool test1 = x >= window1left && x <= window1right; bool test2 = x >= window2left && x <= window2right; - if (windowLayer[layer].window1inversed) test1 = !test1; - if (windowLayer[layer].window2inversed) test2 = !test2; + if (windowLayer[layer].window1inversed) + test1 = !test1; + if (windowLayer[layer].window2inversed) + test2 = !test2; switch (windowLayer[layer].maskLogic) { case 0: return test1 || test2; @@ -394,7 +408,8 @@ void Ppu::HandleOPT(int layer, int* lx, int* ly) { if (hOffset & valid) *lx = (((hOffset & 0x3f8) + (column * 8)) * 2) | (x & 0xf); } else { - if (hOffset & valid) *lx = ((hOffset & 0x3f8) + (column * 8)) | (x & 0x7); + if (hOffset & valid) + *lx = ((hOffset & 0x3f8) + (column * 8)) | (x & 0x7); } // TODO: not sure if correct for interlace if (vOffset & valid) @@ -410,7 +425,8 @@ uint16_t Ppu::GetOffsetValue(int col, int row) { uint16_t tilemapAdr = bg_layer_[2].tilemapAdr + (((y >> tileBits) & 0x1f) << 5 | ((x >> tileBits) & 0x1f)); - if ((x & tileHighBit) && bg_layer_[2].tilemapWider) tilemapAdr += 0x400; + if ((x & tileHighBit) && bg_layer_[2].tilemapWider) + tilemapAdr += 0x400; if ((y & tileHighBit) && bg_layer_[2].tilemapHigher) tilemapAdr += bg_layer_[2].tilemapWider ? 0x800 : 0x400; return vram[tilemapAdr & 0x7fff]; @@ -426,12 +442,14 @@ int Ppu::GetPixelForBgLayer(int x, int y, int layer, bool priority) { uint16_t tilemapAdr = bg_layer_[layer].tilemapAdr + (((y >> tileBitsY) & 0x1f) << 5 | ((x >> tileBitsX) & 0x1f)); - if ((x & tileHighBitX) && bg_layer_[layer].tilemapWider) tilemapAdr += 0x400; + if ((x & tileHighBitX) && bg_layer_[layer].tilemapWider) + tilemapAdr += 0x400; if ((y & tileHighBitY) && bg_layer_[layer].tilemapHigher) tilemapAdr += bg_layer_[layer].tilemapWider ? 0x800 : 0x400; uint16_t tile = vram[tilemapAdr & 0x7fff]; // check priority, get palette - if (((bool)(tile & 0x2000)) != priority) return 0; // wrong priority + if (((bool)(tile & 0x2000)) != priority) + return 0; // wrong priority int paletteNum = (tile & 0x1c00) >> 10; // figure out position within tile int row = (tile & 0x8000) ? 7 - (y & 0x7) : (y & 0x7); @@ -439,15 +457,18 @@ int Ppu::GetPixelForBgLayer(int x, int y, int layer, bool priority) { int tileNum = tile & 0x3ff; if (wideTiles) { // if unflipped right half of tile, or flipped left half of tile - if (((bool)(x & 8)) ^ ((bool)(tile & 0x4000))) tileNum += 1; + if (((bool)(x & 8)) ^ ((bool)(tile & 0x4000))) + tileNum += 1; } if (bg_layer_[layer].bigTiles) { // if unflipped bottom half of tile, or flipped upper half of tile - if (((bool)(y & 8)) ^ ((bool)(tile & 0x8000))) tileNum += 0x10; + if (((bool)(y & 8)) ^ ((bool)(tile & 0x8000))) + tileNum += 0x10; } // read tiledata, ajust palette for mode 0 int bitDepth = kBitDepthsPerMode[mode][layer]; - if (mode == 0) paletteNum += 8 * layer; + if (mode == 0) + paletteNum += 8 * layer; // plane 1 (always) int paletteSize = 4; uint16_t plane1 = vram[(bg_layer_[layer].tileAdr + @@ -502,7 +523,8 @@ void Ppu::EvaluateSprites(int line) { // in y-range, get the x location, using the high bit as well int x = oam[index] & 0xff; x |= ((high_oam_[index >> 3] >> (index & 7)) & 1) << 8; - if (x > 255) x -= 512; + if (x > 255) + x -= 512; // if in x-range, record if (x > -spriteSize) { // break if we found 32 sprites already @@ -527,15 +549,18 @@ void Ppu::EvaluateSprites(int line) { [(high_oam_[index >> 3] >> ((index & 7) + 1)) & 1]; int x = oam[index] & 0xff; x |= ((high_oam_[index >> 3] >> (index & 7)) & 1) << 8; - if (x > 255) x -= 512; + if (x > 255) + x -= 512; if (x > -spriteSize) { // update row according to obj-interlace - if (obj_interlace_) row = row * 2 + (even_frame ? 0 : 1); + if (obj_interlace_) + row = row * 2 + (even_frame ? 0 : 1); // get some data for the sprite and y-flip row if needed int tile = oam[index + 1] & 0xff; int palette = (oam[index + 1] & 0xe00) >> 9; bool hFlipped = oam[index + 1] & 0x4000; - if (oam[index + 1] & 0x8000) row = spriteSize - 1 - row; + if (oam[index + 1] & 0x8000) + row = spriteSize - 1 - row; // fetch all tiles in x-range for (int col = 0; col < spriteSize; col += 8) { if (col + x > -8 && col + x < 256) { @@ -651,14 +676,16 @@ uint8_t Ppu::Read(uint8_t adr, bool latch) { ret = high_oam_[((oam_adr_ & 0xf) << 1) | oam_second_write_]; if (oam_second_write_) { oam_adr_++; - if (oam_adr_ == 0) oam_in_high_ = false; + if (oam_adr_ == 0) + oam_in_high_ = false; } } else { if (!oam_second_write_) { ret = oam[oam_adr_] & 0xff; } else { ret = oam[oam_adr_++] >> 8; - if (oam_adr_ == 0) oam_in_high_ = true; + if (oam_adr_ == 0) + oam_in_high_ = true; } } oam_second_write_ = !oam_second_write_; @@ -779,14 +806,16 @@ void Ppu::Write(uint8_t adr, uint8_t val) { high_oam_[((oam_adr_ & 0xf) << 1) | oam_second_write_] = val; if (oam_second_write_) { oam_adr_++; - if (oam_adr_ == 0) oam_in_high_ = false; + if (oam_adr_ == 0) + oam_in_high_ = false; } } else { if (!oam_second_write_) { oam_buffer_ = val; } else { oam[oam_adr_++] = (val << 8) | oam_buffer_; - if (oam_adr_ == 0) oam_in_high_ = true; + if (oam_adr_ == 0) + oam_in_high_ = true; } } oam_second_write_ = !oam_second_write_; @@ -882,13 +911,15 @@ void Ppu::Write(uint8_t adr, uint8_t val) { // TODO: vram access during rendering (also cgram and oam) uint16_t vramAdr = GetVramRemap(); vram[vramAdr & 0x7fff] = (vram[vramAdr & 0x7fff] & 0xff00) | val; - if (!vram_increment_on_high_) vram_pointer += vram_increment_; + if (!vram_increment_on_high_) + vram_pointer += vram_increment_; break; } case 0x19: { uint16_t vramAdr = GetVramRemap(); vram[vramAdr & 0x7fff] = (vram[vramAdr & 0x7fff] & 0x00ff) | (val << 8); - if (vram_increment_on_high_) vram_pointer += vram_increment_; + if (vram_increment_on_high_) + vram_pointer += vram_increment_; break; } case 0x1a: { @@ -1015,9 +1046,12 @@ void Ppu::Write(uint8_t adr, uint8_t val) { break; } case 0x32: { - if (val & 0x80) fixed_color_b_ = val & 0x1f; - if (val & 0x40) fixed_color_g_ = val & 0x1f; - if (val & 0x20) fixed_color_r_ = val & 0x1f; + if (val & 0x80) + fixed_color_b_ = val & 0x1f; + if (val & 0x40) + fixed_color_g_ = val & 0x1f; + if (val & 0x20) + fixed_color_r_ = val & 0x1f; break; } case 0x33: { diff --git a/src/app/emu/video/ppu.h b/src/app/emu/video/ppu.h index 24bdafea..c5b166e1 100644 --- a/src/app/emu/video/ppu.h +++ b/src/app/emu/video/ppu.h @@ -317,7 +317,7 @@ class Ppu { // Returns the pixel data for the current frame const std::vector& GetFrameBuffer() const { return frame_buffer_; } - + // Set pixel output format (0 = BGRX, 1 = XBGR) void SetPixelFormat(uint8_t format) { pixelOutputFormat = format; } @@ -344,7 +344,6 @@ class Ppu { uint16_t cgram[0x100]; private: - uint8_t cgram_pointer_; bool cgram_second_write_; uint8_t cgram_buffer_; diff --git a/src/app/gfx/backend/irenderer.h b/src/app/gfx/backend/irenderer.h index b9f09a97..fcd3cbdc 100644 --- a/src/app/gfx/backend/irenderer.h +++ b/src/app/gfx/backend/irenderer.h @@ -10,7 +10,7 @@ namespace yaze { namespace gfx { class Bitmap; } -} +} // namespace yaze namespace yaze { namespace gfx { @@ -33,34 +33,34 @@ using TextureHandle = void*; * concrete rendering backend to be swapped out with minimal changes to the application code. */ class IRenderer { -public: - virtual ~IRenderer() = default; + public: + virtual ~IRenderer() = default; - // --- Initialization and Lifecycle --- + // --- Initialization and Lifecycle --- - /** + /** * @brief Initializes the renderer with a given window. * @param window A pointer to the SDL_Window to render into. * @return True if initialization was successful, false otherwise. */ - virtual bool Initialize(SDL_Window* window) = 0; + virtual bool Initialize(SDL_Window* window) = 0; - /** + /** * @brief Shuts down the renderer and releases all associated resources. */ - virtual void Shutdown() = 0; + virtual void Shutdown() = 0; - // --- Texture Management --- + // --- Texture Management --- - /** + /** * @brief Creates a new, empty texture. * @param width The width of the texture in pixels. * @param height The height of the texture in pixels. * @return An abstract TextureHandle to the newly created texture, or nullptr on failure. */ - virtual TextureHandle CreateTexture(int width, int height) = 0; + virtual TextureHandle CreateTexture(int width, int height) = 0; - /** + /** * @brief Creates a new texture with a specific pixel format. * @param width The width of the texture in pixels. * @param height The height of the texture in pixels. @@ -68,60 +68,64 @@ public: * @param access The texture access pattern (e.g., SDL_TEXTUREACCESS_STREAMING). * @return An abstract TextureHandle to the newly created texture, or nullptr on failure. */ - virtual TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access) = 0; + virtual TextureHandle CreateTextureWithFormat(int width, int height, + uint32_t format, + int access) = 0; - /** + /** * @brief Updates a texture with the pixel data from a Bitmap. * @param texture The handle of the texture to update. * @param bitmap The Bitmap containing the new pixel data. */ - virtual void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) = 0; + virtual void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) = 0; - /** + /** * @brief Destroys a texture and frees its associated resources. * @param texture The handle of the texture to destroy. */ - virtual void DestroyTexture(TextureHandle texture) = 0; + virtual void DestroyTexture(TextureHandle texture) = 0; - // --- Direct Pixel Access --- - virtual bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, int* pitch) = 0; - virtual void UnlockTexture(TextureHandle texture) = 0; + // --- Direct Pixel Access --- + virtual bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, + int* pitch) = 0; + virtual void UnlockTexture(TextureHandle texture) = 0; - // --- Rendering Primitives --- + // --- Rendering Primitives --- - /** + /** * @brief Clears the entire render target with the current draw color. */ - virtual void Clear() = 0; + virtual void Clear() = 0; - /** + /** * @brief Presents the back buffer to the screen, making the rendered content visible. */ - virtual void Present() = 0; + virtual void Present() = 0; - /** + /** * @brief Copies a portion of a texture to the current render target. * @param texture The source texture handle. * @param srcrect A pointer to the source rectangle, or nullptr for the entire texture. * @param dstrect A pointer to the destination rectangle, or nullptr for the entire render target. */ - virtual void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) = 0; + virtual void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, + const SDL_Rect* dstrect) = 0; - /** + /** * @brief Sets the render target for subsequent drawing operations. * @param texture The texture to set as the render target, or nullptr to set it back to the default (the window). */ - virtual void SetRenderTarget(TextureHandle texture) = 0; + virtual void SetRenderTarget(TextureHandle texture) = 0; - /** + /** * @brief Sets the color used for drawing operations (e.g., Clear). * @param color The SDL_Color to use. */ - virtual void SetDrawColor(SDL_Color color) = 0; + virtual void SetDrawColor(SDL_Color color) = 0; - // --- Backend-specific Access --- + // --- Backend-specific Access --- - /** + /** * @brief Provides an escape hatch to get the underlying, concrete renderer object. * * This is necessary for integrating with third-party libraries like ImGui that are tied @@ -130,8 +134,8 @@ public: * @return A void pointer to the backend-specific renderer object. The caller is responsible * for casting it to the correct type. */ - virtual void* GetBackendRenderer() = 0; + virtual void* GetBackendRenderer() = 0; }; -} // namespace gfx -} // namespace yaze +} // namespace gfx +} // namespace yaze diff --git a/src/app/gfx/backend/sdl2_renderer.cc b/src/app/gfx/backend/sdl2_renderer.cc index 9faecb32..a0e269e9 100644 --- a/src/app/gfx/backend/sdl2_renderer.cc +++ b/src/app/gfx/backend/sdl2_renderer.cc @@ -8,7 +8,7 @@ namespace gfx { SDL2Renderer::SDL2Renderer() = default; SDL2Renderer::~SDL2Renderer() { - Shutdown(); + Shutdown(); } /** @@ -16,19 +16,19 @@ SDL2Renderer::~SDL2Renderer() { * This function creates an accelerated SDL2 renderer and attaches it to the given window. */ bool SDL2Renderer::Initialize(SDL_Window* window) { - // Create an SDL2 renderer with hardware acceleration. - renderer_ = std::unique_ptr( - SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)); - - if (renderer_ == nullptr) { - // Log an error if renderer creation fails. - printf("SDL_CreateRenderer Error: %s\n", SDL_GetError()); - return false; - } + // Create an SDL2 renderer with hardware acceleration. + renderer_ = std::unique_ptr( + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)); - // Set the blend mode to allow for transparency. - SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND); - return true; + if (renderer_ == nullptr) { + // Log an error if renderer creation fails. + printf("SDL_CreateRenderer Error: %s\n", SDL_GetError()); + return false; + } + + // Set the blend mode to allow for transparency. + SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND); + return true; } /** @@ -36,7 +36,7 @@ bool SDL2Renderer::Initialize(SDL_Window* window) { * The underlying SDL_Renderer is managed by a unique_ptr, so its destruction is handled automatically. */ void SDL2Renderer::Shutdown() { - renderer_.reset(); + renderer_.reset(); } /** @@ -44,20 +44,21 @@ void SDL2Renderer::Shutdown() { * The texture is created with streaming access, which is suitable for textures that are updated frequently. */ TextureHandle SDL2Renderer::CreateTexture(int width, int height) { - // The TextureHandle is a void*, so we cast the SDL_Texture* to it. - return static_cast( - SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width, height) - ); + // The TextureHandle is a void*, so we cast the SDL_Texture* to it. + return static_cast( + SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_STREAMING, width, height)); } /** * @brief Creates an SDL_Texture with a specific pixel format and access pattern. * This is useful for specialized textures like emulator PPU output. */ -TextureHandle SDL2Renderer::CreateTextureWithFormat(int width, int height, uint32_t format, int access) { - return static_cast( - SDL_CreateTexture(renderer_.get(), format, access, width, height) - ); +TextureHandle SDL2Renderer::CreateTextureWithFormat(int width, int height, + uint32_t format, + int access) { + return static_cast( + SDL_CreateTexture(renderer_.get(), format, access, width, height)); } /** @@ -65,81 +66,87 @@ TextureHandle SDL2Renderer::CreateTextureWithFormat(int width, int height, uint3 * This involves converting the bitmap's surface to the correct format and updating the texture. */ void SDL2Renderer::UpdateTexture(TextureHandle texture, const Bitmap& bitmap) { - SDL_Surface* surface = bitmap.surface(); - - // Validate texture, surface, and surface format - if (!texture || !surface || !surface->format) { - return; - } - - // Validate surface has pixels - if (!surface->pixels || surface->w <= 0 || surface->h <= 0) { - return; - } + SDL_Surface* surface = bitmap.surface(); - // Convert the bitmap's surface to RGBA8888 format for compatibility with the texture. - auto converted_surface = std::unique_ptr( - SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0)); + // Validate texture, surface, and surface format + if (!texture || !surface || !surface->format) { + return; + } - if (!converted_surface || !converted_surface->pixels) { - return; - } + // Validate surface has pixels + if (!surface->pixels || surface->w <= 0 || surface->h <= 0) { + return; + } - // Update the texture with the pixels from the converted surface. - SDL_UpdateTexture(static_cast(texture), nullptr, converted_surface->pixels, converted_surface->pitch); + // Convert the bitmap's surface to RGBA8888 format for compatibility with the texture. + auto converted_surface = + std::unique_ptr( + SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA8888, 0)); + + if (!converted_surface || !converted_surface->pixels) { + return; + } + + // Update the texture with the pixels from the converted surface. + SDL_UpdateTexture(static_cast(texture), nullptr, + converted_surface->pixels, converted_surface->pitch); } /** * @brief Destroys an SDL_Texture. */ void SDL2Renderer::DestroyTexture(TextureHandle texture) { - if (texture) { - SDL_DestroyTexture(static_cast(texture)); - } + if (texture) { + SDL_DestroyTexture(static_cast(texture)); + } } -bool SDL2Renderer::LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, int* pitch) { - return SDL_LockTexture(static_cast(texture), rect, pixels, pitch) == 0; +bool SDL2Renderer::LockTexture(TextureHandle texture, SDL_Rect* rect, + void** pixels, int* pitch) { + return SDL_LockTexture(static_cast(texture), rect, pixels, + pitch) == 0; } void SDL2Renderer::UnlockTexture(TextureHandle texture) { - SDL_UnlockTexture(static_cast(texture)); + SDL_UnlockTexture(static_cast(texture)); } /** * @brief Clears the screen with the current draw color. */ void SDL2Renderer::Clear() { - SDL_RenderClear(renderer_.get()); + SDL_RenderClear(renderer_.get()); } /** * @brief Presents the rendered frame to the screen. */ void SDL2Renderer::Present() { - SDL_RenderPresent(renderer_.get()); + SDL_RenderPresent(renderer_.get()); } /** * @brief Copies a texture to the render target. */ -void SDL2Renderer::RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) { - SDL_RenderCopy(renderer_.get(), static_cast(texture), srcrect, dstrect); +void SDL2Renderer::RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, + const SDL_Rect* dstrect) { + SDL_RenderCopy(renderer_.get(), static_cast(texture), srcrect, + dstrect); } /** * @brief Sets the render target. */ void SDL2Renderer::SetRenderTarget(TextureHandle texture) { - SDL_SetRenderTarget(renderer_.get(), static_cast(texture)); + SDL_SetRenderTarget(renderer_.get(), static_cast(texture)); } /** * @brief Sets the draw color. */ void SDL2Renderer::SetDrawColor(SDL_Color color) { - SDL_SetRenderDrawColor(renderer_.get(), color.r, color.g, color.b, color.a); + SDL_SetRenderDrawColor(renderer_.get(), color.r, color.g, color.b, color.a); } -} // namespace gfx -} // namespace yaze +} // namespace gfx +} // namespace yaze diff --git a/src/app/gfx/backend/sdl2_renderer.h b/src/app/gfx/backend/sdl2_renderer.h index bb75e836..fe831eb2 100644 --- a/src/app/gfx/backend/sdl2_renderer.h +++ b/src/app/gfx/backend/sdl2_renderer.h @@ -16,41 +16,44 @@ namespace gfx { * to be independent of SDL2. */ class SDL2Renderer : public IRenderer { -public: - SDL2Renderer(); - ~SDL2Renderer() override; + public: + SDL2Renderer(); + ~SDL2Renderer() override; - // --- Lifecycle and Initialization --- - bool Initialize(SDL_Window* window) override; - void Shutdown() override; + // --- Lifecycle and Initialization --- + bool Initialize(SDL_Window* window) override; + void Shutdown() override; - // --- Texture Management --- - TextureHandle CreateTexture(int width, int height) override; - TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access) override; - void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) override; - void DestroyTexture(TextureHandle texture) override; + // --- Texture Management --- + TextureHandle CreateTexture(int width, int height) override; + TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, + int access) override; + void UpdateTexture(TextureHandle texture, const Bitmap& bitmap) override; + void DestroyTexture(TextureHandle texture) override; - // --- Direct Pixel Access --- - bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, int* pitch) override; - void UnlockTexture(TextureHandle texture) override; + // --- Direct Pixel Access --- + bool LockTexture(TextureHandle texture, SDL_Rect* rect, void** pixels, + int* pitch) override; + void UnlockTexture(TextureHandle texture) override; - // --- Rendering Primitives --- - void Clear() override; - void Present() override; - void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect) override; - void SetRenderTarget(TextureHandle texture) override; - void SetDrawColor(SDL_Color color) override; + // --- Rendering Primitives --- + void Clear() override; + void Present() override; + void RenderCopy(TextureHandle texture, const SDL_Rect* srcrect, + const SDL_Rect* dstrect) override; + void SetRenderTarget(TextureHandle texture) override; + void SetDrawColor(SDL_Color color) override; - /** + /** * @brief Provides access to the underlying SDL_Renderer*. * @return A void pointer that can be safely cast to an SDL_Renderer*. */ - void* GetBackendRenderer() override { return renderer_.get(); } + void* GetBackendRenderer() override { return renderer_.get(); } -private: - // The core SDL2 renderer object, managed by a unique_ptr with a custom deleter. - std::unique_ptr renderer_; + private: + // The core SDL2 renderer object, managed by a unique_ptr with a custom deleter. + std::unique_ptr renderer_; }; -} // namespace gfx -} // namespace yaze +} // namespace gfx +} // namespace yaze diff --git a/src/app/gfx/core/bitmap.cc b/src/app/gfx/core/bitmap.cc index 9e09fee0..ade11833 100644 --- a/src/app/gfx/core/bitmap.cc +++ b/src/app/gfx/core/bitmap.cc @@ -7,15 +7,14 @@ #include #include -#include "app/gfx/resource/arena.h" #include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" #include "util/log.h" namespace yaze { namespace gfx { - class BitmapError : public std::runtime_error { public: using std::runtime_error::runtime_error; @@ -45,13 +44,13 @@ Uint32 GetSnesPixelFormat(int format) { } Bitmap::Bitmap(int width, int height, int depth, - const std::vector &data) + const std::vector& data) : width_(width), height_(height), depth_(depth), data_(data) { Create(width, height, depth, data); } Bitmap::Bitmap(int width, int height, int depth, - const std::vector &data, const SnesPalette &palette) + const std::vector& data, const SnesPalette& palette) : width_(width), height_(height), depth_(depth), @@ -72,8 +71,8 @@ Bitmap::Bitmap(const Bitmap& other) // Copy the data and recreate surface/texture with simple assignment pixel_data_ = data_.data(); if (active_ && !data_.empty()) { - surface_ = Arena::Get().AllocateSurface(width_, height_, depth_, - GetSnesPixelFormat(BitmapFormat::kIndexed)); + surface_ = Arena::Get().AllocateSurface( + width_, height_, depth_, GetSnesPixelFormat(BitmapFormat::kIndexed)); if (surface_) { SDL_LockSurface(surface_); memcpy(surface_->pixels, pixel_data_, data_.size()); @@ -91,12 +90,12 @@ Bitmap& Bitmap::operator=(const Bitmap& other) { modified_ = other.modified_; palette_ = other.palette_; data_ = other.data_; - + // Copy the data and recreate surface/texture pixel_data_ = data_.data(); if (active_ && !data_.empty()) { - surface_ = Arena::Get().AllocateSurface(width_, height_, depth_, - GetSnesPixelFormat(BitmapFormat::kIndexed)); + surface_ = Arena::Get().AllocateSurface( + width_, height_, depth_, GetSnesPixelFormat(BitmapFormat::kIndexed)); if (surface_) { SDL_LockSurface(surface_); memcpy(surface_->pixels, pixel_data_, data_.size()); @@ -144,7 +143,7 @@ Bitmap& Bitmap::operator=(Bitmap&& other) noexcept { data_ = std::move(other.data_); surface_ = other.surface_; texture_ = other.texture_; - + // Reset the moved-from object other.width_ = 0; other.height_ = 0; @@ -165,7 +164,7 @@ void Bitmap::Create(int width, int height, int depth, std::span data) { } void Bitmap::Create(int width, int height, int depth, - const std::vector &data) { + const std::vector& data) { Create(width, height, depth, static_cast(BitmapFormat::kIndexed), data); } @@ -184,7 +183,7 @@ void Bitmap::Create(int width, int height, int depth, * - Sets active flag for rendering pipeline */ void Bitmap::Create(int width, int height, int depth, int format, - const std::vector &data) { + const std::vector& data) { if (data.empty()) { SDL_Log("Bitmap data is empty\n"); active_ = false; @@ -209,7 +208,7 @@ void Bitmap::Create(int width, int height, int depth, int format, active_ = false; return; } - + // CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment // Direct assignment breaks SDL's memory management and causes malloc errors on shutdown if (surface_ && data_.size() > 0) { @@ -218,7 +217,7 @@ void Bitmap::Create(int width, int height, int depth, int format, SDL_UnlockSurface(surface_); } active_ = true; - + // Apply the stored palette if one exists if (!palette_.empty()) { ApplyStoredPalette(); @@ -228,7 +227,7 @@ void Bitmap::Create(int width, int height, int depth, int format, void Bitmap::Reformat(int format) { surface_ = Arena::Get().AllocateSurface(width_, height_, depth_, GetSnesPixelFormat(format)); - + // CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment if (surface_ && data_.size() > 0) { SDL_LockSurface(surface_); @@ -247,8 +246,6 @@ void Bitmap::UpdateTexture() { Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, this); } - - /** * @brief Apply the stored palette to the SDL surface * @@ -280,7 +277,7 @@ void Bitmap::ApplyStoredPalette() { InvalidatePaletteCache(); // For indexed surfaces, ensure palette exists - SDL_Palette *sdl_palette = surface_->format->palette; + SDL_Palette* sdl_palette = surface_->format->palette; if (sdl_palette == nullptr) { // Non-indexed surface or palette not created - can't apply palette SDL_Log("Warning: Bitmap surface has no palette (non-indexed format?)\n"); @@ -288,20 +285,20 @@ void Bitmap::ApplyStoredPalette() { } SDL_UnlockSurface(surface_); - + // Build SDL color array from SnesPalette // Only set the colors that exist in the palette - don't fill unused entries std::vector colors(palette_.size()); for (size_t i = 0; i < palette_.size(); ++i) { const auto& pal_color = palette_[i]; - + // Get RGB values - stored as 0-255 in ImVec4 (unconventional!) ImVec4 rgb_255 = pal_color.rgb(); - + colors[i].r = static_cast(rgb_255.x); colors[i].g = static_cast(rgb_255.y); colors[i].b = static_cast(rgb_255.z); - + // Only apply transparency if explicitly set if (pal_color.is_transparent()) { colors[i].a = 0; // Fully transparent @@ -309,12 +306,13 @@ void Bitmap::ApplyStoredPalette() { colors[i].a = 255; // Fully opaque } } - + // Apply palette to surface using SDL_SetPaletteColors // Only set the colors we have - leave rest of palette unchanged // This prevents breaking systems that use small palettes (8-16 colors) - SDL_SetPaletteColors(sdl_palette, colors.data(), 0, static_cast(palette_.size())); - + SDL_SetPaletteColors(sdl_palette, colors.data(), 0, + static_cast(palette_.size())); + SDL_LockSurface(surface_); } @@ -322,22 +320,24 @@ void Bitmap::UpdateSurfacePixels() { if (!surface_ || data_.empty()) { return; } - + // Copy pixel data from data_ vector to SDL surface SDL_LockSurface(surface_); if (surface_->pixels && data_.size() > 0) { - memcpy(surface_->pixels, data_.data(), std::min(data_.size(), static_cast(surface_->pitch * surface_->h))); + memcpy(surface_->pixels, data_.data(), + std::min(data_.size(), + static_cast(surface_->pitch * surface_->h))); } SDL_UnlockSurface(surface_); } -void Bitmap::SetPalette(const SnesPalette &palette) { +void Bitmap::SetPalette(const SnesPalette& palette) { // Store palette even if surface isn't ready yet palette_ = palette; - + // Apply it immediately if surface is ready ApplyStoredPalette(); - + // Mark as modified to trigger texture update modified_ = true; } @@ -357,7 +357,8 @@ void Bitmap::SetPalette(const SnesPalette &palette) { * @param palette Source palette to apply * @param sub_palette_index Index within palette for sub-palette extraction (default 0) */ -void Bitmap::ApplyPaletteByMetadata(const SnesPalette& palette, int sub_palette_index) { +void Bitmap::ApplyPaletteByMetadata(const SnesPalette& palette, + int sub_palette_index) { if (metadata_.palette_format == 1) { // Sub-palette: need transparent black + 7 colors from palette // Common for 3BPP graphics sheets (title screen, etc.) @@ -400,11 +401,11 @@ void Bitmap::ApplyPaletteByMetadata(const SnesPalette& palette, int sub_palette_ * @param index Start index in source palette (0-based) * @param length Number of colors to extract (default 7, max 7) */ -void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index, +void Bitmap::SetPaletteWithTransparent(const SnesPalette& palette, size_t index, int length) { // Store the full palette for reference (not modified) palette_ = palette; - + // If surface isn't created yet, just store the palette for later if (surface_ == nullptr) { return; // Palette will be applied when surface is created @@ -416,7 +417,8 @@ void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index, } if (length < 0 || length > 7) { - throw std::invalid_argument("Invalid palette length (must be 0-7 for SNES palettes)"); + throw std::invalid_argument( + "Invalid palette length (must be 0-7 for SNES palettes)"); } if (index + length > palette.size()) { @@ -425,43 +427,49 @@ void Bitmap::SetPaletteWithTransparent(const SnesPalette &palette, size_t index, // Build 8-color SNES sub-palette std::vector colors; - + // Color 0: Transparent (SNES hardware requirement) colors.push_back(ImVec4(0, 0, 0, 0)); // Transparent black - + // Colors 1-7: Extract from source palette // NOTE: palette[i].rgb() returns 0-255 values in ImVec4 (unconventional!) for (size_t i = 0; i < 7 && (index + i) < palette.size(); ++i) { - const auto &pal_color = palette[index + i]; + const auto& pal_color = palette[index + i]; ImVec4 rgb_255 = pal_color.rgb(); // 0-255 range (unconventional storage) - + // Convert to standard ImVec4 0-1 range for SDL - colors.push_back(ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f, - rgb_255.z / 255.0f, 1.0f)); // Always opaque + colors.push_back(ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f, + rgb_255.z / 255.0f, 1.0f)); // Always opaque } - + // Ensure we have exactly 8 colors while (colors.size() < 8) { - colors.push_back(ImVec4(0, 0, 0, 1.0f)); // Fill with opaque black + colors.push_back(ImVec4(0, 0, 0, 1.0f)); // Fill with opaque black } - + // Update palette cache with full palette (for color lookup) InvalidatePaletteCache(); // Apply the 8-color SNES sub-palette to SDL surface SDL_UnlockSurface(surface_); - for (int color_index = 0; color_index < 8 && color_index < static_cast(colors.size()); ++color_index) { + for (int color_index = 0; + color_index < 8 && color_index < static_cast(colors.size()); + ++color_index) { if (color_index < surface_->format->palette->ncolors) { - surface_->format->palette->colors[color_index].r = static_cast(colors[color_index].x * 255.0f); - surface_->format->palette->colors[color_index].g = static_cast(colors[color_index].y * 255.0f); - surface_->format->palette->colors[color_index].b = static_cast(colors[color_index].z * 255.0f); - surface_->format->palette->colors[color_index].a = static_cast(colors[color_index].w * 255.0f); + surface_->format->palette->colors[color_index].r = + static_cast(colors[color_index].x * 255.0f); + surface_->format->palette->colors[color_index].g = + static_cast(colors[color_index].y * 255.0f); + surface_->format->palette->colors[color_index].b = + static_cast(colors[color_index].z * 255.0f); + surface_->format->palette->colors[color_index].a = + static_cast(colors[color_index].w * 255.0f); } } SDL_LockSurface(surface_); } -void Bitmap::SetPalette(const std::vector &palette) { +void Bitmap::SetPalette(const std::vector& palette) { SDL_UnlockSurface(surface_); for (size_t i = 0; i < palette.size(); ++i) { surface_->format->palette->colors[i].r = palette[i].r; @@ -475,66 +483,70 @@ void Bitmap::SetPalette(const std::vector &palette) { void Bitmap::WriteToPixel(int position, uint8_t value) { // Bounds checking to prevent crashes if (position < 0 || position >= static_cast(data_.size())) { - SDL_Log("ERROR: WriteToPixel - position %d out of bounds (size: %zu)", + SDL_Log("ERROR: WriteToPixel - position %d out of bounds (size: %zu)", position, data_.size()); return; } - + // Safety check: ensure bitmap is active and has valid data if (!active_ || data_.empty()) { - SDL_Log("ERROR: WriteToPixel - bitmap not active or data empty (active=%s, size=%zu)", - active_ ? "true" : "false", data_.size()); + SDL_Log( + "ERROR: WriteToPixel - bitmap not active or data empty (active=%s, " + "size=%zu)", + active_ ? "true" : "false", data_.size()); return; } - + if (pixel_data_ == nullptr) { pixel_data_ = data_.data(); } - + // Safety check: ensure surface exists and is valid if (!surface_ || !surface_->pixels) { - SDL_Log("ERROR: WriteToPixel - surface or pixels are null (surface=%p, pixels=%p)", - surface_, surface_ ? surface_->pixels : nullptr); + SDL_Log( + "ERROR: WriteToPixel - surface or pixels are null (surface=%p, " + "pixels=%p)", + surface_, surface_ ? surface_->pixels : nullptr); return; } - + // Additional validation: ensure pixel_data_ is valid if (pixel_data_ == nullptr) { SDL_Log("ERROR: WriteToPixel - pixel_data_ is null after assignment"); return; } - + // CRITICAL FIX: Update both data_ and surface_ properly data_[position] = value; pixel_data_[position] = value; - + // Update surface if it exists if (surface_) { SDL_LockSurface(surface_); static_cast(surface_->pixels)[position] = value; SDL_UnlockSurface(surface_); } - + // Mark as modified for traditional update path modified_ = true; } -void Bitmap::WriteColor(int position, const ImVec4 &color) { +void Bitmap::WriteColor(int position, const ImVec4& color) { // Bounds checking to prevent crashes if (position < 0 || position >= static_cast(data_.size())) { return; } - + // Safety check: ensure bitmap is active and has valid data if (!active_ || data_.empty()) { return; } - + // Safety check: ensure surface exists and is valid if (!surface_ || !surface_->pixels || !surface_->format) { return; } - + // Convert ImVec4 (RGBA) to SDL_Color (RGBA) SDL_Color sdl_color; sdl_color.r = static_cast(color.x * 255); @@ -552,20 +564,20 @@ void Bitmap::WriteColor(int position, const ImVec4 &color) { } data_[position] = ConvertRgbToSnes(color); pixel_data_[position] = index; - + // Update surface if it exists if (surface_) { SDL_LockSurface(surface_); static_cast(surface_->pixels)[position] = index; SDL_UnlockSurface(surface_); } - + modified_ = true; } void Bitmap::Get8x8Tile(int tile_index, int x, int y, - std::vector &tile_data, - int &tile_data_offset) { + std::vector& tile_data, + int& tile_data_offset) { int tile_offset = tile_index * (width_ * height_); int tile_x = (x * 8) % width_; int tile_y = (y * 8) % height_; @@ -580,8 +592,8 @@ void Bitmap::Get8x8Tile(int tile_index, int x, int y, } void Bitmap::Get16x16Tile(int tile_x, int tile_y, - std::vector &tile_data, - int &tile_data_offset) { + std::vector& tile_data, + int& tile_data_offset) { for (int ty = 0; ty < 16; ty++) { for (int tx = 0; tx < 16; tx++) { // Calculate the pixel position in the bitmap @@ -597,7 +609,6 @@ void Bitmap::Get16x16Tile(int tile_x, int tile_y, } } - /** * @brief Set a pixel at the given coordinates with SNES color * @param x X coordinate (0 to width-1) @@ -616,26 +627,26 @@ void Bitmap::Get16x16Tile(int tile_x, int tile_y, */ void Bitmap::SetPixel(int x, int y, const SnesColor& color) { if (x < 0 || x >= width_ || y < 0 || y >= height_) { - return; // Bounds check + return; // Bounds check } - + int position = y * width_ + x; if (position >= 0 && position < static_cast(data_.size())) { uint8_t color_index = FindColorIndex(color); data_[position] = color_index; - + // Update pixel_data_ to maintain consistency if (pixel_data_) { pixel_data_[position] = color_index; } - + // Update surface if it exists if (surface_) { SDL_LockSurface(surface_); static_cast(surface_->pixels)[position] = color_index; SDL_UnlockSurface(surface_); } - + // Update dirty region for efficient texture updates dirty_region_.AddPoint(x, y); modified_ = true; @@ -644,11 +655,11 @@ void Bitmap::SetPixel(int x, int y, const SnesColor& color) { void Bitmap::Resize(int new_width, int new_height) { if (new_width <= 0 || new_height <= 0) { - return; // Invalid dimensions + return; // Invalid dimensions } - + std::vector new_data(new_width * new_height, 0); - + // Copy existing data, handling size changes if (!data_.empty()) { for (int y = 0; y < std::min(height_, new_height); y++) { @@ -661,15 +672,15 @@ void Bitmap::Resize(int new_width, int new_height) { } } } - + width_ = new_width; height_ = new_height; data_ = std::move(new_data); pixel_data_ = data_.data(); - + // Recreate surface with new dimensions - surface_ = Arena::Get().AllocateSurface(width_, height_, depth_, - GetSnesPixelFormat(BitmapFormat::kIndexed)); + surface_ = Arena::Get().AllocateSurface( + width_, height_, depth_, GetSnesPixelFormat(BitmapFormat::kIndexed)); if (surface_) { SDL_LockSurface(surface_); memcpy(surface_->pixels, pixel_data_, data_.size()); @@ -678,7 +689,7 @@ void Bitmap::Resize(int new_width, int new_height) { } else { active_ = false; } - + modified_ = true; } @@ -698,7 +709,7 @@ uint32_t Bitmap::HashColor(const ImVec4& color) { uint32_t g = static_cast(color.y * 255.0F) & 0xFF; uint32_t b = static_cast(color.z * 255.0F) & 0xFF; uint32_t a = static_cast(color.w * 255.0F) & 0xFF; - + // Simple hash combining all components return (r << 24) | (g << 16) | (b << 8) | a; } @@ -714,7 +725,7 @@ uint32_t Bitmap::HashColor(const ImVec4& color) { */ void Bitmap::InvalidatePaletteCache() { color_to_index_cache_.clear(); - + // Rebuild cache with current palette for (size_t i = 0; i < palette_.size(); i++) { uint32_t color_hash = HashColor(palette_[i].rgb()); @@ -740,23 +751,23 @@ uint8_t Bitmap::FindColorIndex(const SnesColor& color) { return (it != color_to_index_cache_.end()) ? it->second : 0; } -void Bitmap::set_data(const std::vector &data) { +void Bitmap::set_data(const std::vector& data) { // Validate input data if (data.empty()) { SDL_Log("Warning: set_data called with empty data vector"); return; } - + data_ = data; pixel_data_ = data_.data(); - + // CRITICAL FIX: Use proper SDL surface operations instead of direct pointer assignment if (surface_ && !data_.empty()) { SDL_LockSurface(surface_); memcpy(surface_->pixels, pixel_data_, data_.size()); SDL_UnlockSurface(surface_); } - + modified_ = true; } @@ -765,24 +776,24 @@ bool Bitmap::ValidateDataSurfaceSync() { SDL_Log("ValidateDataSurfaceSync: surface or data is null/empty"); return false; } - + // Check if data and surface are synchronized size_t surface_size = static_cast(surface_->h * surface_->pitch); size_t data_size = data_.size(); size_t compare_size = std::min(data_size, surface_size); - + if (compare_size == 0) { - SDL_Log("ValidateDataSurfaceSync: invalid sizes - surface: %zu, data: %zu", + SDL_Log("ValidateDataSurfaceSync: invalid sizes - surface: %zu, data: %zu", surface_size, data_size); return false; } - + // Compare first few bytes to check synchronization if (memcmp(surface_->pixels, data_.data(), compare_size) != 0) { SDL_Log("ValidateDataSurfaceSync: data and surface are not synchronized"); return false; } - + return true; } diff --git a/src/app/gfx/core/bitmap.h b/src/app/gfx/core/bitmap.h index da3cf56b..504d02c9 100644 --- a/src/app/gfx/core/bitmap.h +++ b/src/app/gfx/core/bitmap.h @@ -37,7 +37,6 @@ enum BitmapFormat { k8bpp = 2, }; - /** * @brief Represents a bitmap image optimized for SNES ROM hacking. * @@ -74,7 +73,7 @@ class Bitmap { * @param depth Color depth in bits per pixel (4, 8, or 16 for SNES) * @param data Raw pixel data (indexed color values for SNES graphics) */ - Bitmap(int width, int height, int depth, const std::vector &data); + Bitmap(int width, int height, int depth, const std::vector& data); /** * @brief Create a bitmap with the given dimensions, data, and SNES palette @@ -84,8 +83,8 @@ class Bitmap { * @param data Raw pixel data (indexed color values) * @param palette SNES palette for color mapping (15-bit RGB format) */ - Bitmap(int width, int height, int depth, const std::vector &data, - const SnesPalette &palette); + Bitmap(int width, int height, int depth, const std::vector& data, + const SnesPalette& palette); /** * @brief Copy constructor - creates a deep copy @@ -121,13 +120,13 @@ class Bitmap { * @brief Create a bitmap with the given dimensions and data */ void Create(int width, int height, int depth, - const std::vector &data); + const std::vector& data); /** * @brief Create a bitmap with the given dimensions, format, and data */ void Create(int width, int height, int depth, int format, - const std::vector &data); + const std::vector& data); /** * @brief Reformat the bitmap to use a different pixel format @@ -149,7 +148,7 @@ class Bitmap { * @param renderer SDL renderer for texture operations * @note Use this for better performance when multiple textures need updating */ - void QueueTextureUpdate(IRenderer *renderer); + void QueueTextureUpdate(IRenderer* renderer); /** * @brief Updates the texture data from the surface @@ -159,25 +158,26 @@ class Bitmap { /** * @brief Set the palette for the bitmap */ - void SetPalette(const SnesPalette &palette); + void SetPalette(const SnesPalette& palette); /** * @brief Set the palette with a transparent color */ - void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, + void SetPaletteWithTransparent(const SnesPalette& palette, size_t index, int length = 7); /** * @brief Apply palette using metadata-driven strategy * Chooses between SetPalette and SetPaletteWithTransparent based on metadata */ - void ApplyPaletteByMetadata(const SnesPalette& palette, int sub_palette_index = 0); + void ApplyPaletteByMetadata(const SnesPalette& palette, + int sub_palette_index = 0); /** * @brief Apply the stored palette to the surface (internal helper) */ void ApplyStoredPalette(); - + /** * @brief Update SDL surface with current pixel data from data_ vector * Call this after modifying pixel data via mutable_data() @@ -187,7 +187,7 @@ class Bitmap { /** * @brief Set the palette using SDL colors */ - void SetPalette(const std::vector &palette); + void SetPalette(const std::vector& palette); /** * @brief Write a value to a pixel at the given position @@ -197,7 +197,7 @@ class Bitmap { /** * @brief Write a color to a pixel at the given position */ - void WriteColor(int position, const ImVec4 &color); + void WriteColor(int position, const ImVec4& color); /** * @brief Set a pixel at the given x,y coordinates with SNES color @@ -246,8 +246,8 @@ class Bitmap { * @param tile_data_offset Current offset in tile_data buffer * @note Used for ROM tile editing and tile extraction */ - void Get8x8Tile(int tile_index, int x, int y, std::vector &tile_data, - int &tile_data_offset); + void Get8x8Tile(int tile_index, int x, int y, std::vector& tile_data, + int& tile_data_offset); /** * @brief Extract a 16x16 tile from the bitmap (SNES metatile size) @@ -257,46 +257,50 @@ class Bitmap { * @param tile_data_offset Current offset in tile_data buffer * @note Used for ROM metatile editing and large tile extraction */ - void Get16x16Tile(int tile_x, int tile_y, std::vector &tile_data, - int &tile_data_offset); + void Get16x16Tile(int tile_x, int tile_y, std::vector& tile_data, + int& tile_data_offset); /** * @brief Metadata for tracking bitmap source format and palette requirements */ struct BitmapMetadata { - int source_bpp = 8; // Original bits per pixel (3, 4, 8) + int source_bpp = 8; // Original bits per pixel (3, 4, 8) int palette_format = 0; // 0=full palette, 1=sub-palette with transparent - std::string source_type; // "graphics_sheet", "tilemap", "screen_buffer", "mode7" + std::string + source_type; // "graphics_sheet", "tilemap", "screen_buffer", "mode7" int palette_colors = 256; // Expected palette size - + BitmapMetadata() = default; - BitmapMetadata(int bpp, int format, const std::string& type, int colors = 256) - : source_bpp(bpp), palette_format(format), source_type(type), palette_colors(colors) {} + BitmapMetadata(int bpp, int format, const std::string& type, + int colors = 256) + : source_bpp(bpp), + palette_format(format), + source_type(type), + palette_colors(colors) {} }; - const SnesPalette &palette() const { return palette_; } - SnesPalette *mutable_palette() { return &palette_; } + const SnesPalette& palette() const { return palette_; } + SnesPalette* mutable_palette() { return &palette_; } BitmapMetadata& metadata() { return metadata_; } const BitmapMetadata& metadata() const { return metadata_; } - + int width() const { return width_; } int height() const { return height_; } int depth() const { return depth_; } auto size() const { return data_.size(); } - const uint8_t *data() const { return data_.data(); } - std::vector &mutable_data() { return data_; } - SDL_Surface *surface() const { return surface_; } + const uint8_t* data() const { return data_.data(); } + std::vector& mutable_data() { return data_; } + SDL_Surface* surface() const { return surface_; } TextureHandle texture() const { return texture_; } - const std::vector &vector() const { return data_; } + const std::vector& vector() const { return data_; } uint8_t at(int i) const { return data_[i]; } bool modified() const { return modified_; } bool is_active() const { return active_; } void set_active(bool active) { active_ = active; } - void set_data(const std::vector &data); + void set_data(const std::vector& data); void set_modified(bool modified) { modified_ = modified; } void set_texture(TextureHandle texture) { texture_ = texture; } - private: int width_ = 0; int height_ = 0; @@ -306,10 +310,10 @@ class Bitmap { bool modified_ = false; // Pointer to the texture pixels - void *texture_pixels = nullptr; + void* texture_pixels = nullptr; // Pointer to the pixel data - uint8_t *pixel_data_ = nullptr; + uint8_t* pixel_data_ = nullptr; // Palette for the bitmap gfx::SnesPalette palette_; @@ -321,7 +325,7 @@ class Bitmap { std::vector data_; // Surface for the bitmap (managed by Arena) - SDL_Surface *surface_ = nullptr; + SDL_Surface* surface_ = nullptr; // Texture for the bitmap (managed by Arena) TextureHandle texture_ = nullptr; @@ -333,12 +337,12 @@ class Bitmap { struct DirtyRegion { int min_x = 0, min_y = 0, max_x = 0, max_y = 0; bool is_dirty = false; - + void Reset() { min_x = min_y = max_x = max_y = 0; is_dirty = false; } - + void AddPoint(int x, int y) { if (!is_dirty) { min_x = max_x = x; diff --git a/src/app/gfx/debug/graphics_optimizer.h b/src/app/gfx/debug/graphics_optimizer.h index b8baaef8..9ff54460 100644 --- a/src/app/gfx/debug/graphics_optimizer.h +++ b/src/app/gfx/debug/graphics_optimizer.h @@ -1,12 +1,12 @@ #ifndef YAZE_APP_GFX_GRAPHICS_OPTIMIZER_H #define YAZE_APP_GFX_GRAPHICS_OPTIMIZER_H -#include -#include #include +#include +#include -#include "app/gfx/util/bpp_format_manager.h" #include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/util/bpp_format_manager.h" namespace yaze { namespace gfx { @@ -15,10 +15,10 @@ namespace gfx { * @brief Graphics optimization strategy */ enum class OptimizationStrategy { - kMemoryOptimized, ///< Minimize memory usage - kPerformanceOptimized, ///< Maximize rendering performance - kQualityOptimized, ///< Maintain highest quality - kBalanced ///< Balance memory, performance, and quality + kMemoryOptimized, ///< Minimize memory usage + kPerformanceOptimized, ///< Maximize rendering performance + kQualityOptimized, ///< Maintain highest quality + kBalanced ///< Balance memory, performance, and quality }; /** @@ -32,8 +32,12 @@ struct OptimizationResult { float quality_loss; std::vector recommended_formats; std::unordered_map sheet_recommendations; - - OptimizationResult() : success(false), memory_saved(0), performance_gain(0.0f), quality_loss(0.0f) {} + + OptimizationResult() + : success(false), + memory_saved(0), + performance_gain(0.0f), + quality_loss(0.0f) {} }; /** @@ -49,11 +53,16 @@ struct SheetOptimizationData { int colors_used; bool is_convertible; std::string optimization_reason; - - SheetOptimizationData() : sheet_id(-1), current_format(BppFormat::kBpp8), - recommended_format(BppFormat::kBpp8), current_size(0), - optimized_size(0), compression_ratio(1.0f), colors_used(0), - is_convertible(false) {} + + SheetOptimizationData() + : sheet_id(-1), + current_format(BppFormat::kBpp8), + recommended_format(BppFormat::kBpp8), + current_size(0), + optimized_size(0), + compression_ratio(1.0f), + colors_used(0), + is_convertible(false) {} }; /** @@ -86,12 +95,12 @@ struct SheetOptimizationData { class GraphicsOptimizer { public: static GraphicsOptimizer& Get(); - + /** * @brief Initialize the graphics optimizer */ void Initialize(); - + /** * @brief Optimize a single graphics sheet * @param sheet_data Graphics sheet data @@ -100,11 +109,11 @@ class GraphicsOptimizer { * @param strategy Optimization strategy * @return Optimization result */ - OptimizationResult OptimizeSheet(const std::vector& sheet_data, - int sheet_id, - const SnesPalette& palette, - OptimizationStrategy strategy = OptimizationStrategy::kBalanced); - + OptimizationResult OptimizeSheet( + const std::vector& sheet_data, int sheet_id, + const SnesPalette& palette, + OptimizationStrategy strategy = OptimizationStrategy::kBalanced); + /** * @brief Optimize multiple graphics sheets * @param sheets Map of sheet ID to sheet data @@ -112,10 +121,11 @@ class GraphicsOptimizer { * @param strategy Optimization strategy * @return Optimization result */ - OptimizationResult OptimizeSheets(const std::unordered_map>& sheets, - const std::unordered_map& palettes, - OptimizationStrategy strategy = OptimizationStrategy::kBalanced); - + OptimizationResult OptimizeSheets( + const std::unordered_map>& sheets, + const std::unordered_map& palettes, + OptimizationStrategy strategy = OptimizationStrategy::kBalanced); + /** * @brief Analyze graphics sheet for optimization opportunities * @param sheet_data Graphics sheet data @@ -124,9 +134,8 @@ class GraphicsOptimizer { * @return Optimization data */ SheetOptimizationData AnalyzeSheet(const std::vector& sheet_data, - int sheet_id, - const SnesPalette& palette); - + int sheet_id, const SnesPalette& palette); + /** * @brief Get optimization recommendations for all sheets * @param sheets Map of sheet ID to sheet data @@ -136,7 +145,7 @@ class GraphicsOptimizer { std::unordered_map GetOptimizationRecommendations( const std::unordered_map>& sheets, const std::unordered_map& palettes); - + /** * @brief Apply optimization recommendations * @param recommendations Optimization recommendations @@ -148,18 +157,18 @@ class GraphicsOptimizer { const std::unordered_map& recommendations, std::unordered_map>& sheets, std::unordered_map& palettes); - + /** * @brief Get optimization statistics * @return Map of optimization statistics */ std::unordered_map GetOptimizationStats() const; - + /** * @brief Clear optimization cache */ void ClearCache(); - + /** * @brief Set optimization parameters * @param max_quality_loss Maximum acceptable quality loss (0.0-1.0) @@ -167,41 +176,44 @@ class GraphicsOptimizer { * @param performance_threshold Minimum performance gain threshold */ void SetOptimizationParameters(float max_quality_loss = 0.1f, - size_t min_memory_savings = 1024, - float performance_threshold = 0.05f); + size_t min_memory_savings = 1024, + float performance_threshold = 0.05f); private: GraphicsOptimizer() = default; ~GraphicsOptimizer() = default; - + // Optimization parameters float max_quality_loss_; size_t min_memory_savings_; float performance_threshold_; - + // Statistics tracking std::unordered_map optimization_stats_; - + // Cache for optimization results std::unordered_map optimization_cache_; - + // Helper methods BppFormat DetermineOptimalFormat(const std::vector& data, - const SnesPalette& palette, - OptimizationStrategy strategy); + const SnesPalette& palette, + OptimizationStrategy strategy); float CalculateQualityLoss(BppFormat from_format, BppFormat to_format, - const std::vector& data); + const std::vector& data); size_t CalculateMemorySavings(BppFormat from_format, BppFormat to_format, - const std::vector& data); + const std::vector& data); float CalculatePerformanceGain(BppFormat from_format, BppFormat to_format); - bool ShouldOptimize(const SheetOptimizationData& data, OptimizationStrategy strategy); + bool ShouldOptimize(const SheetOptimizationData& data, + OptimizationStrategy strategy); std::string GenerateOptimizationReason(const SheetOptimizationData& data); - + // Analysis helpers - int CountUsedColors(const std::vector& data, const SnesPalette& palette); - float CalculateColorEfficiency(const std::vector& data, const SnesPalette& palette); + int CountUsedColors(const std::vector& data, + const SnesPalette& palette); + float CalculateColorEfficiency(const std::vector& data, + const SnesPalette& palette); std::vector AnalyzeColorDistribution(const std::vector& data); - + // Cache management std::string GenerateCacheKey(const std::vector& data, int sheet_id); void UpdateOptimizationStats(const std::string& operation, double value); @@ -214,10 +226,10 @@ class GraphicsOptimizationScope { public: GraphicsOptimizationScope(OptimizationStrategy strategy, int sheet_count); ~GraphicsOptimizationScope(); - + void AddSheet(int sheet_id, size_t original_size, size_t optimized_size); void SetResult(const OptimizationResult& result); - + private: OptimizationStrategy strategy_; int sheet_count_; diff --git a/src/app/gfx/debug/performance/performance_dashboard.cc b/src/app/gfx/debug/performance/performance_dashboard.cc index d244dea1..d15ceef8 100644 --- a/src/app/gfx/debug/performance/performance_dashboard.cc +++ b/src/app/gfx/debug/performance/performance_dashboard.cc @@ -4,9 +4,9 @@ #include #include +#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/render/atlas_renderer.h" #include "app/gfx/resource/memory_pool.h" -#include "app/gfx/debug/performance/performance_profiler.h" #include "imgui/imgui.h" namespace yaze { @@ -250,7 +250,7 @@ void PerformanceDashboard::RenderMemoryUsage() { for (double value : memory_usage_history_) { float_history.push_back(static_cast(value)); } - + ImGui::PlotLines("Memory (MB)", float_history.data(), static_cast(float_history.size())); } @@ -262,15 +262,18 @@ void PerformanceDashboard::RenderMemoryUsage() { float pool_usage = total_bytes > 0 ? static_cast(used_bytes) / total_bytes : 0.0F; ImGui::ProgressBar(pool_usage, ImVec2(-1, 0), "Memory Pool Usage"); - + // Atlas renderer stats auto atlas_stats = AtlasRenderer::Get().GetStats(); - ImGui::Text("Atlas Renderer: %d atlases, %d/%d entries used", - atlas_stats.total_atlases, atlas_stats.used_entries, atlas_stats.total_entries); - ImGui::Text("Atlas Memory: %s", FormatMemory(atlas_stats.total_memory).c_str()); - + ImGui::Text("Atlas Renderer: %d atlases, %d/%d entries used", + atlas_stats.total_atlases, atlas_stats.used_entries, + atlas_stats.total_entries); + ImGui::Text("Atlas Memory: %s", + FormatMemory(atlas_stats.total_memory).c_str()); + if (atlas_stats.total_entries > 0) { - float atlas_usage = static_cast(atlas_stats.used_entries) / atlas_stats.total_entries; + float atlas_usage = static_cast(atlas_stats.used_entries) / + atlas_stats.total_entries; ImGui::ProgressBar(atlas_usage, ImVec2(-1, 0), "Atlas Utilization"); } } @@ -327,17 +330,17 @@ void PerformanceDashboard::RenderRecommendations() const { if (ImGui::Checkbox("Enable Performance Monitoring", &monitoring_enabled)) { PerformanceProfiler::SetEnabled(monitoring_enabled); } - + ImGui::SameLine(); if (ImGui::Button("Clear All Data")) { PerformanceProfiler::Get().Clear(); } - + ImGui::SameLine(); if (ImGui::Button("Generate Report")) { std::string report = PerformanceProfiler::Get().GenerateReport(true); } - + // Export button if (ImGui::Button("Export Performance Report")) { std::string report = ExportReport(); @@ -373,23 +376,23 @@ void PerformanceDashboard::CollectMetrics() { // Calculate cache hit ratio based on actual performance data double total_cache_operations = 0.0; double total_cache_time = 0.0; - + // Look for cache-related operations for (const auto& op_name : profiler.GetOperationNames()) { - if (op_name.find("cache") != std::string::npos || + if (op_name.find("cache") != std::string::npos || op_name.find("tile_cache") != std::string::npos) { auto stats = profiler.GetStats(op_name); total_cache_operations += stats.sample_count; total_cache_time += stats.total_time_ms; } } - + // Estimate cache hit ratio based on operation speed if (total_cache_operations > 0) { double avg_cache_time = total_cache_time / total_cache_operations; // Assume cache hits are < 10μs, misses are > 50μs - current_metrics_.cache_hit_ratio = std::max(0.0, std::min(1.0, - 1.0 - (avg_cache_time - 10.0) / 40.0)); + current_metrics_.cache_hit_ratio = + std::max(0.0, std::min(1.0, 1.0 - (avg_cache_time - 10.0) / 40.0)); } else { current_metrics_.cache_hit_ratio = 0.85; // Default estimate } @@ -397,18 +400,18 @@ void PerformanceDashboard::CollectMetrics() { // Count draw calls and texture updates from profiler data int draw_calls = 0; int texture_updates = 0; - + for (const auto& op_name : profiler.GetOperationNames()) { - if (op_name.find("draw") != std::string::npos || + if (op_name.find("draw") != std::string::npos || op_name.find("render") != std::string::npos) { draw_calls += profiler.GetOperationCount(op_name); } - if (op_name.find("texture_update") != std::string::npos || + if (op_name.find("texture_update") != std::string::npos || op_name.find("texture") != std::string::npos) { texture_updates += profiler.GetOperationCount(op_name); } } - + current_metrics_.draw_calls_per_frame = draw_calls; current_metrics_.texture_updates_per_frame = texture_updates; @@ -427,27 +430,28 @@ void PerformanceDashboard::CollectMetrics() { void PerformanceDashboard::UpdateOptimizationStatus() { auto profiler = PerformanceProfiler::Get(); auto [used_bytes, total_bytes] = MemoryPool::Get().GetMemoryStats(); - + // Check optimization status based on actual performance data optimization_status_.palette_lookup_optimized = false; optimization_status_.dirty_region_tracking_enabled = false; optimization_status_.resource_pooling_active = (total_bytes > 0); optimization_status_.batch_operations_enabled = false; - optimization_status_.atlas_rendering_enabled = true; // AtlasRenderer is implemented + optimization_status_.atlas_rendering_enabled = + true; // AtlasRenderer is implemented optimization_status_.memory_pool_active = (total_bytes > 0); - + // Analyze palette lookup performance auto palette_stats = profiler.GetStats("palette_lookup_optimized"); if (palette_stats.avg_time_us > 0 && palette_stats.avg_time_us < 5.0) { optimization_status_.palette_lookup_optimized = true; } - + // Analyze texture update performance auto texture_stats = profiler.GetStats("texture_update_optimized"); if (texture_stats.avg_time_us > 0 && texture_stats.avg_time_us < 200.0) { optimization_status_.dirty_region_tracking_enabled = true; } - + // Check for batch operations auto batch_stats = profiler.GetStats("texture_batch_queue"); if (batch_stats.sample_count > 0) { diff --git a/src/app/gfx/debug/performance/performance_dashboard.h b/src/app/gfx/debug/performance/performance_dashboard.h index 8c885fba..c0a95239 100644 --- a/src/app/gfx/debug/performance/performance_dashboard.h +++ b/src/app/gfx/debug/performance/performance_dashboard.h @@ -1,14 +1,14 @@ #ifndef YAZE_APP_GFX_PERFORMANCE_PERFORMANCE_DASHBOARD_H #define YAZE_APP_GFX_PERFORMANCE_PERFORMANCE_DASHBOARD_H +#include +#include #include #include -#include -#include #include "app/gfx/debug/performance/performance_profiler.h" -#include "app/gfx/resource/memory_pool.h" #include "app/gfx/render/atlas_renderer.h" +#include "app/gfx/resource/memory_pool.h" namespace yaze { namespace gfx { @@ -16,16 +16,19 @@ namespace gfx { /** * @brief Performance summary for external consumption */ - struct PerformanceSummary { +struct PerformanceSummary { double average_frame_time_ms; double memory_usage_mb; double cache_hit_ratio; int optimization_score; // 0-100 std::string status_message; std::vector recommendations; - - PerformanceSummary() : average_frame_time_ms(0.0), memory_usage_mb(0.0), - cache_hit_ratio(0.0), optimization_score(0) {} + + PerformanceSummary() + : average_frame_time_ms(0.0), + memory_usage_mb(0.0), + cache_hit_ratio(0.0), + optimization_score(0) {} }; /** @@ -104,11 +107,16 @@ class PerformanceDashboard { double cache_hit_ratio; int draw_calls_per_frame; int texture_updates_per_frame; - - PerformanceMetrics() : frame_time_ms(0.0), palette_lookup_time_us(0.0), - texture_update_time_us(0.0), batch_operation_time_us(0.0), - memory_usage_mb(0.0), cache_hit_ratio(0.0), - draw_calls_per_frame(0), texture_updates_per_frame(0) {} + + PerformanceMetrics() + : frame_time_ms(0.0), + palette_lookup_time_us(0.0), + texture_update_time_us(0.0), + batch_operation_time_us(0.0), + memory_usage_mb(0.0), + cache_hit_ratio(0.0), + draw_calls_per_frame(0), + texture_updates_per_frame(0) {} }; struct OptimizationStatus { @@ -118,23 +126,27 @@ class PerformanceDashboard { bool batch_operations_enabled; bool atlas_rendering_enabled; bool memory_pool_active; - - OptimizationStatus() : palette_lookup_optimized(false), dirty_region_tracking_enabled(false), - resource_pooling_active(false), batch_operations_enabled(false), - atlas_rendering_enabled(false), memory_pool_active(false) {} + + OptimizationStatus() + : palette_lookup_optimized(false), + dirty_region_tracking_enabled(false), + resource_pooling_active(false), + batch_operations_enabled(false), + atlas_rendering_enabled(false), + memory_pool_active(false) {} }; bool visible_; PerformanceMetrics current_metrics_; PerformanceMetrics previous_metrics_; OptimizationStatus optimization_status_; - + std::chrono::high_resolution_clock::time_point last_update_time_; std::vector frame_time_history_; std::vector memory_usage_history_; - + static constexpr size_t kHistorySize = 100; - static constexpr double kUpdateIntervalMs = 100.0; // Update every 100ms + static constexpr double kUpdateIntervalMs = 100.0; // Update every 100ms // UI rendering methods void RenderMetricsPanel() const; @@ -142,15 +154,16 @@ class PerformanceDashboard { void RenderMemoryUsage(); void RenderFrameRateGraph(); void RenderRecommendations() const; - + // Data collection methods void CollectMetrics(); void UpdateOptimizationStatus(); void AnalyzePerformance(); - + // Helper methods static double CalculateAverage(const std::vector& values); - static double CalculatePercentile(const std::vector& values, double percentile); + static double CalculatePercentile(const std::vector& values, + double percentile); static std::string FormatTime(double time_us); static std::string FormatMemory(size_t bytes); std::string GetOptimizationRecommendation() const; diff --git a/src/app/gfx/debug/performance/performance_profiler.cc b/src/app/gfx/debug/performance/performance_profiler.cc index 79744d09..298056d9 100644 --- a/src/app/gfx/debug/performance/performance_profiler.cc +++ b/src/app/gfx/debug/performance/performance_profiler.cc @@ -17,78 +17,82 @@ PerformanceProfiler& PerformanceProfiler::Get() { return instance; } -PerformanceProfiler::PerformanceProfiler() : enabled_(true), is_shutting_down_(false) { +PerformanceProfiler::PerformanceProfiler() + : enabled_(true), is_shutting_down_(false) { // Initialize with memory pool for efficient data storage // Reserve space for common operations to avoid reallocations active_timers_.reserve(50); operation_times_.reserve(100); operation_totals_.reserve(100); operation_counts_.reserve(100); - + // Register destructor to set shutdown flag - std::atexit([]() { - Get().is_shutting_down_ = true; - }); + std::atexit([]() { Get().is_shutting_down_ = true; }); } void PerformanceProfiler::StartTimer(const std::string& operation_name) { - if (!enabled_ || is_shutting_down_) return; - + if (!enabled_ || is_shutting_down_) + return; + active_timers_[operation_name] = std::chrono::high_resolution_clock::now(); } void PerformanceProfiler::EndTimer(const std::string& operation_name) { - if (!enabled_ || is_shutting_down_) return; - + if (!enabled_ || is_shutting_down_) + return; + auto timer_iter = active_timers_.find(operation_name); if (timer_iter == active_timers_.end()) { // During shutdown, silently ignore missing timers to avoid log spam return; } - + auto end_time = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast( - end_time - timer_iter->second).count(); - + end_time - timer_iter->second) + .count(); + double duration_ms = duration / 1000.0; - + // Store timing data using memory pool for efficiency operation_times_[operation_name].push_back(static_cast(duration)); operation_totals_[operation_name] += duration_ms; operation_counts_[operation_name]++; - + active_timers_.erase(timer_iter); } PerformanceProfiler::TimingStats PerformanceProfiler::GetStats( const std::string& operation_name) const { TimingStats stats; - + auto times_iter = operation_times_.find(operation_name); auto total_iter = operation_totals_.find(operation_name); - + if (times_iter == operation_times_.end() || times_iter->second.empty()) { return stats; } - + const auto& times = times_iter->second; stats.sample_count = times.size(); - stats.total_time_ms = (total_iter != operation_totals_.end()) ? total_iter->second : 0.0; - + stats.total_time_ms = + (total_iter != operation_totals_.end()) ? total_iter->second : 0.0; + if (times.empty()) { return stats; } - + // Calculate min, max, and average stats.min_time_us = *std::min_element(times.begin(), times.end()); stats.max_time_us = *std::max_element(times.begin(), times.end()); - stats.avg_time_us = std::accumulate(times.begin(), times.end(), 0.0) / times.size(); - + stats.avg_time_us = + std::accumulate(times.begin(), times.end(), 0.0) / times.size(); + // Calculate median std::vector sorted_times = times; std::sort(sorted_times.begin(), sorted_times.end()); stats.median_time_us = PerformanceProfiler::CalculateMedian(sorted_times); - + return stats; } @@ -96,26 +100,33 @@ std::string PerformanceProfiler::GenerateReport(bool log_to_sdl) const { std::ostringstream report; report << "\n=== YAZE Unified Performance Report ===\n"; report << "Total Operations Tracked: " << operation_times_.size() << "\n"; - report << "Performance Monitoring: " << (enabled_ ? "ENABLED" : "DISABLED") << "\n\n"; - + report << "Performance Monitoring: " << (enabled_ ? "ENABLED" : "DISABLED") + << "\n\n"; + // Memory pool statistics auto [used_bytes, total_bytes] = MemoryPool::Get().GetMemoryStats(); - report << "Memory Pool Usage: " << std::fixed << std::setprecision(2) + report << "Memory Pool Usage: " << std::fixed << std::setprecision(2) << (used_bytes / (1024.0 * 1024.0)) << " MB / " << (total_bytes / (1024.0 * 1024.0)) << " MB\n\n"; - + for (const auto& [operation, times] : operation_times_) { - if (times.empty()) continue; - + if (times.empty()) + continue; + auto stats = GetStats(operation); report << "Operation: " << operation << "\n"; report << " Samples: " << stats.sample_count << "\n"; - report << " Min: " << std::fixed << std::setprecision(2) << stats.min_time_us << " μs\n"; - report << " Max: " << std::fixed << std::setprecision(2) << stats.max_time_us << " μs\n"; - report << " Average: " << std::fixed << std::setprecision(2) << stats.avg_time_us << " μs\n"; - report << " Median: " << std::fixed << std::setprecision(2) << stats.median_time_us << " μs\n"; - report << " Total: " << std::fixed << std::setprecision(2) << stats.total_time_ms << " ms\n"; - + report << " Min: " << std::fixed << std::setprecision(2) + << stats.min_time_us << " μs\n"; + report << " Max: " << std::fixed << std::setprecision(2) + << stats.max_time_us << " μs\n"; + report << " Average: " << std::fixed << std::setprecision(2) + << stats.avg_time_us << " μs\n"; + report << " Median: " << std::fixed << std::setprecision(2) + << stats.median_time_us << " μs\n"; + report << " Total: " << std::fixed << std::setprecision(2) + << stats.total_time_ms << " ms\n"; + // Performance analysis if (operation.find("palette_lookup") != std::string::npos) { if (stats.avg_time_us < 1.0) { @@ -145,34 +156,34 @@ std::string PerformanceProfiler::GenerateReport(bool log_to_sdl) const { report << " Status: ⚠ SLOW LOADING (> 1000ms)\n"; } } - + report << "\n"; } - + // Overall performance summary report << "=== Performance Summary ===\n"; size_t total_samples = 0; double total_time = 0.0; - + for (const auto& [operation, times] : operation_times_) { total_samples += times.size(); total_time += std::accumulate(times.begin(), times.end(), 0.0); } - + if (total_samples > 0) { report << "Total Samples: " << total_samples << "\n"; - report << "Total Time: " << std::fixed << std::setprecision(2) + report << "Total Time: " << std::fixed << std::setprecision(2) << total_time / 1000.0 << " ms\n"; - report << "Average Time per Operation: " << std::fixed << std::setprecision(2) - << total_time / total_samples << " μs\n"; + report << "Average Time per Operation: " << std::fixed + << std::setprecision(2) << total_time / total_samples << " μs\n"; } - + std::string report_str = report.str(); - + if (log_to_sdl) { SDL_Log("%s", report_str.c_str()); } - + return report_str; } @@ -203,52 +214,55 @@ bool PerformanceProfiler::IsTiming(const std::string& operation_name) const { return active_timers_.find(operation_name) != active_timers_.end(); } -double PerformanceProfiler::GetAverageTime(const std::string& operation_name) const { +double PerformanceProfiler::GetAverageTime( + const std::string& operation_name) const { auto total_it = operation_totals_.find(operation_name); auto count_it = operation_counts_.find(operation_name); - - if (total_it == operation_totals_.end() || count_it == operation_counts_.end() || - count_it->second == 0) { + + if (total_it == operation_totals_.end() || + count_it == operation_counts_.end() || count_it->second == 0) { return 0.0; } - + return total_it->second / count_it->second; } -double PerformanceProfiler::GetTotalTime(const std::string& operation_name) const { +double PerformanceProfiler::GetTotalTime( + const std::string& operation_name) const { auto total_it = operation_totals_.find(operation_name); return (total_it != operation_totals_.end()) ? total_it->second : 0.0; } -int PerformanceProfiler::GetOperationCount(const std::string& operation_name) const { +int PerformanceProfiler::GetOperationCount( + const std::string& operation_name) const { auto count_it = operation_counts_.find(operation_name); return (count_it != operation_counts_.end()) ? count_it->second : 0; } void PerformanceProfiler::PrintSummary() const { std::cout << "\n=== Performance Summary ===\n"; - std::cout << std::left << std::setw(30) << "Operation" - << std::setw(12) << "Count" - << std::setw(15) << "Total (ms)" - << std::setw(15) << "Average (ms)" << "\n"; + std::cout << std::left << std::setw(30) << "Operation" << std::setw(12) + << "Count" << std::setw(15) << "Total (ms)" << std::setw(15) + << "Average (ms)" << "\n"; std::cout << std::string(72, '-') << "\n"; for (const auto& [operation_name, times] : operation_times_) { - if (times.empty()) continue; - + if (times.empty()) + continue; + auto total_it = operation_totals_.find(operation_name); auto count_it = operation_counts_.find(operation_name); - - if (total_it != operation_totals_.end() && count_it != operation_counts_.end()) { + + if (total_it != operation_totals_.end() && + count_it != operation_counts_.end()) { double total_time = total_it->second; int count = count_it->second; double avg_time = (count > 0) ? total_time / count : 0.0; - - std::cout << std::left << std::setw(30) << operation_name - << std::setw(12) << count - << std::setw(15) << std::fixed << std::setprecision(2) << total_time - << std::setw(15) << std::fixed << std::setprecision(2) << avg_time - << "\n"; + + std::cout << std::left << std::setw(30) << operation_name << std::setw(12) + << count << std::setw(15) << std::fixed << std::setprecision(2) + << total_time << std::setw(15) << std::fixed + << std::setprecision(2) << avg_time << "\n"; } } std::cout << std::string(72, '-') << "\n"; @@ -258,7 +272,7 @@ double PerformanceProfiler::CalculateMedian(std::vector values) { if (values.empty()) { return 0.0; } - + size_t size = values.size(); if (size % 2 == 0) { return (values[size / 2 - 1] + values[size / 2]) / 2.0; @@ -267,7 +281,7 @@ double PerformanceProfiler::CalculateMedian(std::vector values) { } // ScopedTimer implementation -ScopedTimer::ScopedTimer(const std::string& operation_name) +ScopedTimer::ScopedTimer(const std::string& operation_name) : operation_name_(operation_name) { if (PerformanceProfiler::IsEnabled() && PerformanceProfiler::IsValid()) { PerformanceProfiler::Get().StartTimer(operation_name_); diff --git a/src/app/gfx/debug/performance/performance_profiler.h b/src/app/gfx/debug/performance/performance_profiler.h index 92fa8106..b0081dd0 100644 --- a/src/app/gfx/debug/performance/performance_profiler.h +++ b/src/app/gfx/debug/performance/performance_profiler.h @@ -44,46 +44,40 @@ namespace gfx { class PerformanceProfiler { public: static PerformanceProfiler& Get(); - + /** * @brief Enable or disable performance monitoring * * When disabled, ScopedTimer operations become no-ops for better performance * in production builds or when monitoring is not needed. */ - static void SetEnabled(bool enabled) { - Get().enabled_ = enabled; - } - + static void SetEnabled(bool enabled) { Get().enabled_ = enabled; } + /** * @brief Check if performance monitoring is enabled */ - static bool IsEnabled() { - return Get().enabled_; - } - + static bool IsEnabled() { return Get().enabled_; } + /** * @brief Check if the profiler is in a valid state (not shutting down) * This prevents crashes during static destruction order issues */ - static bool IsValid() { - return !Get().is_shutting_down_; - } - + static bool IsValid() { return !Get().is_shutting_down_; } + /** * @brief Start timing an operation * @param operation_name Name of the operation to time * @note Multiple operations can be timed simultaneously */ void StartTimer(const std::string& operation_name); - + /** * @brief End timing an operation * @param operation_name Name of the operation to end timing * @note Must match a previously started timer */ void EndTimer(const std::string& operation_name); - + /** * @brief Get timing statistics for an operation * @param operation_name Name of the operation @@ -97,61 +91,61 @@ class PerformanceProfiler { double total_time_ms = 0.0; size_t sample_count = 0; }; - + TimingStats GetStats(const std::string& operation_name) const; - + /** * @brief Generate a comprehensive performance report * @param log_to_sdl Whether to log results to SDL_Log * @return Formatted performance report string */ std::string GenerateReport(bool log_to_sdl = true) const; - + /** * @brief Clear all timing data */ void Clear(); - + /** * @brief Clear timing data for a specific operation * @param operation_name Name of the operation to clear */ void ClearOperation(const std::string& operation_name); - + /** * @brief Get list of all tracked operations * @return Vector of operation names */ std::vector GetOperationNames() const; - + /** * @brief Check if an operation is currently being timed * @param operation_name Name of the operation to check * @return True if operation is being timed */ bool IsTiming(const std::string& operation_name) const; - + /** * @brief Get the average time for an operation in milliseconds * @param operation_name Name of the operation * @return Average time in milliseconds */ double GetAverageTime(const std::string& operation_name) const; - + /** * @brief Get the total time for an operation in milliseconds * @param operation_name Name of the operation * @return Total time in milliseconds */ double GetTotalTime(const std::string& operation_name) const; - + /** * @brief Get the number of times an operation was measured * @param operation_name Name of the operation * @return Number of measurements */ int GetOperationCount(const std::string& operation_name) const; - + /** * @brief Print a summary of all operations to console */ @@ -159,18 +153,21 @@ class PerformanceProfiler { private: PerformanceProfiler(); - + using TimePoint = std::chrono::high_resolution_clock::time_point; using Duration = std::chrono::microseconds; - + std::unordered_map active_timers_; std::unordered_map> operation_times_; - std::unordered_map operation_totals_; // Total time per operation - std::unordered_map operation_counts_; // Count per operation - - bool enabled_ = true; // Performance monitoring enabled by default - bool is_shutting_down_ = false; // Flag to prevent operations during destruction - + std::unordered_map + operation_totals_; // Total time per operation + std::unordered_map + operation_counts_; // Count per operation + + bool enabled_ = true; // Performance monitoring enabled by default + bool is_shutting_down_ = + false; // Flag to prevent operations during destruction + /** * @brief Calculate median value from a sorted vector * @param values Sorted vector of values @@ -192,7 +189,7 @@ class ScopedTimer { public: explicit ScopedTimer(const std::string& operation_name); ~ScopedTimer(); - + // Disable copy and move ScopedTimer(const ScopedTimer&) = delete; ScopedTimer& operator=(const ScopedTimer&) = delete; diff --git a/src/app/gfx/render/atlas_renderer.cc b/src/app/gfx/render/atlas_renderer.cc index e6d6abad..f56ab71e 100644 --- a/src/app/gfx/render/atlas_renderer.cc +++ b/src/app/gfx/render/atlas_renderer.cc @@ -16,79 +16,85 @@ void AtlasRenderer::Initialize(IRenderer* renderer, int initial_size) { renderer_ = renderer; next_atlas_id_ = 0; current_atlas_ = 0; - + // Clear any existing atlases Clear(); - + // Create initial atlas CreateNewAtlas(); } int AtlasRenderer::AddBitmap(const Bitmap& bitmap) { if (!bitmap.is_active() || !bitmap.texture()) { - return -1; // Invalid bitmap + return -1; // Invalid bitmap } - + ScopedTimer timer("atlas_add_bitmap"); - + // Try to pack into current atlas SDL_Rect uv_rect; if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) { int atlas_id = next_atlas_id_++; auto& atlas = *atlases_[current_atlas_]; - + // Copy bitmap data to atlas texture renderer_->SetRenderTarget(atlas.texture); renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect); renderer_->SetRenderTarget(nullptr); - + return atlas_id; } - + // Current atlas is full, create new one CreateNewAtlas(); if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) { int atlas_id = next_atlas_id_++; auto& atlas = *atlases_[current_atlas_]; - - BppFormat bpp_format = BppFormatManager::Get().DetectFormat(bitmap.vector(), bitmap.width(), bitmap.height()); - atlas.entries.emplace_back(atlas_id, uv_rect, bitmap.texture(), bpp_format, bitmap.width(), bitmap.height()); + + BppFormat bpp_format = BppFormatManager::Get().DetectFormat( + bitmap.vector(), bitmap.width(), bitmap.height()); + atlas.entries.emplace_back(atlas_id, uv_rect, bitmap.texture(), bpp_format, + bitmap.width(), bitmap.height()); atlas_lookup_[atlas_id] = &atlas.entries.back(); - + // Copy bitmap data to atlas texture renderer_->SetRenderTarget(atlas.texture); renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect); renderer_->SetRenderTarget(nullptr); - + return atlas_id; } - - return -1; // Failed to add + + return -1; // Failed to add } -int AtlasRenderer::AddBitmapWithBppOptimization(const Bitmap& bitmap, BppFormat target_bpp) { +int AtlasRenderer::AddBitmapWithBppOptimization(const Bitmap& bitmap, + BppFormat target_bpp) { if (!bitmap.is_active() || !bitmap.texture()) { - return -1; // Invalid bitmap + return -1; // Invalid bitmap } - + ScopedTimer timer("atlas_add_bitmap_bpp_optimized"); - + // Detect current BPP format - BppFormat current_bpp = BppFormatManager::Get().DetectFormat(bitmap.vector(), bitmap.width(), bitmap.height()); - + BppFormat current_bpp = BppFormatManager::Get().DetectFormat( + bitmap.vector(), bitmap.width(), bitmap.height()); + // If formats match, use standard addition if (current_bpp == target_bpp) { return AddBitmap(bitmap); } - + // Convert bitmap to target BPP format auto converted_data = BppFormatManager::Get().ConvertFormat( - bitmap.vector(), current_bpp, target_bpp, bitmap.width(), bitmap.height()); - + bitmap.vector(), current_bpp, target_bpp, bitmap.width(), + bitmap.height()); + // Create temporary bitmap with converted data - Bitmap converted_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(), converted_data, bitmap.palette()); + Bitmap converted_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(), + converted_data, bitmap.palette()); converted_bitmap.CreateTexture(); - + // Add converted bitmap to atlas return AddBitmap(converted_bitmap); } @@ -98,10 +104,10 @@ void AtlasRenderer::RemoveBitmap(int atlas_id) { if (it == atlas_lookup_.end()) { return; } - + AtlasEntry* entry = it->second; entry->in_use = false; - + // Mark region as free for (auto& atlas : atlases_) { for (auto& atlas_entry : atlas->entries) { @@ -111,7 +117,7 @@ void AtlasRenderer::RemoveBitmap(int atlas_id) { } } } - + atlas_lookup_.erase(it); } @@ -120,28 +126,30 @@ void AtlasRenderer::UpdateBitmap(int atlas_id, const Bitmap& bitmap) { if (it == atlas_lookup_.end()) { return; } - + AtlasEntry* entry = it->second; entry->texture = bitmap.texture(); - + // Update UV coordinates if size changed - if (bitmap.width() != entry->uv_rect.w || bitmap.height() != entry->uv_rect.h) { + if (bitmap.width() != entry->uv_rect.w || + bitmap.height() != entry->uv_rect.h) { // Remove old entry and add new one RemoveBitmap(atlas_id); AddBitmap(bitmap); } } -void AtlasRenderer::RenderBatch(const std::vector& render_commands) { +void AtlasRenderer::RenderBatch( + const std::vector& render_commands) { if (render_commands.empty()) { return; } - + ScopedTimer timer("atlas_batch_render"); - + // Group commands by atlas for efficient rendering std::unordered_map> atlas_groups; - + for (const auto& cmd : render_commands) { auto it = atlas_lookup_.find(cmd.atlas_id); if (it != atlas_lookup_.end() && it->second->in_use) { @@ -156,31 +164,30 @@ void AtlasRenderer::RenderBatch(const std::vector& render_command } } } - + // Render each atlas group for (const auto& [atlas_index, commands] : atlas_groups) { - if (commands.empty()) continue; - + if (commands.empty()) + continue; + auto& atlas = *atlases_[atlas_index]; - + // Set atlas texture // SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND); - + // Render all commands for this atlas for (const auto* cmd : commands) { auto it = atlas_lookup_.find(cmd->atlas_id); - if (it == atlas_lookup_.end()) continue; - + if (it == atlas_lookup_.end()) + continue; + AtlasEntry* entry = it->second; - + // Calculate destination rectangle - SDL_Rect dest_rect = { - static_cast(cmd->x), - static_cast(cmd->y), - static_cast(entry->uv_rect.w * cmd->scale_x), - static_cast(entry->uv_rect.h * cmd->scale_y) - }; - + SDL_Rect dest_rect = {static_cast(cmd->x), static_cast(cmd->y), + static_cast(entry->uv_rect.w * cmd->scale_x), + static_cast(entry->uv_rect.h * cmd->scale_y)}; + // Apply rotation if needed if (std::abs(cmd->rotation) > 0.001F) { // For rotation, we'd need to use SDL_RenderCopyEx @@ -193,26 +200,30 @@ void AtlasRenderer::RenderBatch(const std::vector& render_command } } -void AtlasRenderer::RenderBatchWithBppOptimization(const std::vector& render_commands, - const std::unordered_map>& bpp_groups) { +void AtlasRenderer::RenderBatchWithBppOptimization( + const std::vector& render_commands, + const std::unordered_map>& bpp_groups) { if (render_commands.empty()) { return; } - + ScopedTimer timer("atlas_batch_render_bpp_optimized"); - + // Render each BPP group separately for optimal performance for (const auto& [bpp_format, command_indices] : bpp_groups) { - if (command_indices.empty()) continue; - + if (command_indices.empty()) + continue; + // Group commands by atlas for this BPP format std::unordered_map> atlas_groups; - + for (int cmd_index : command_indices) { - if (cmd_index >= 0 && cmd_index < static_cast(render_commands.size())) { + if (cmd_index >= 0 && + cmd_index < static_cast(render_commands.size())) { const auto& cmd = render_commands[cmd_index]; auto it = atlas_lookup_.find(cmd.atlas_id); - if (it != atlas_lookup_.end() && it->second->in_use && it->second->bpp_format == bpp_format) { + if (it != atlas_lookup_.end() && it->second->in_use && + it->second->bpp_format == bpp_format) { // Find which atlas contains this entry for (size_t i = 0; i < atlases_.size(); ++i) { for (const auto& entry : atlases_[i]->entries) { @@ -225,31 +236,31 @@ void AtlasRenderer::RenderBatchWithBppOptimization(const std::vectoratlas_id); - if (it == atlas_lookup_.end()) continue; - + if (it == atlas_lookup_.end()) + continue; + AtlasEntry* entry = it->second; - + // Calculate destination rectangle SDL_Rect dest_rect = { - static_cast(cmd->x), - static_cast(cmd->y), - static_cast(entry->uv_rect.w * cmd->scale_x), - static_cast(entry->uv_rect.h * cmd->scale_y) - }; - + static_cast(cmd->x), static_cast(cmd->y), + static_cast(entry->uv_rect.w * cmd->scale_x), + static_cast(entry->uv_rect.h * cmd->scale_y)}; + // Apply rotation if needed if (std::abs(cmd->rotation) > 0.001F) { renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect); @@ -263,35 +274,37 @@ void AtlasRenderer::RenderBatchWithBppOptimization(const std::vectorentries.size(); - stats.used_entries += std::count_if(atlas->entries.begin(), atlas->entries.end(), - [](const AtlasEntry& entry) { return entry.in_use; }); - + stats.used_entries += + std::count_if(atlas->entries.begin(), atlas->entries.end(), + [](const AtlasEntry& entry) { return entry.in_use; }); + // Calculate memory usage (simplified) - stats.total_memory += atlas->size * atlas->size * 4; // RGBA8888 + stats.total_memory += atlas->size * atlas->size * 4; // RGBA8888 } - + if (stats.total_entries > 0) { - stats.utilization_percent = (static_cast(stats.used_entries) / stats.total_entries) * 100.0F; + stats.utilization_percent = + (static_cast(stats.used_entries) / stats.total_entries) * 100.0F; } - + return stats; } void AtlasRenderer::Defragment() { ScopedTimer timer("atlas_defragment"); - + for (auto& atlas : atlases_) { // Remove unused entries atlas->entries.erase( - std::remove_if(atlas->entries.begin(), atlas->entries.end(), - [](const AtlasEntry& entry) { return !entry.in_use; }), - atlas->entries.end()); - + std::remove_if(atlas->entries.begin(), atlas->entries.end(), + [](const AtlasEntry& entry) { return !entry.in_use; }), + atlas->entries.end()); + // Rebuild atlas texture RebuildAtlas(*atlas); } @@ -304,7 +317,7 @@ void AtlasRenderer::Clear() { renderer_->DestroyTexture(atlas->texture); } } - + atlases_.clear(); atlas_lookup_.clear(); next_atlas_id_ = 0; @@ -315,26 +328,24 @@ AtlasRenderer::~AtlasRenderer() { Clear(); } -void AtlasRenderer::RenderBitmap(int atlas_id, float x, float y, float scale_x, float scale_y) { +void AtlasRenderer::RenderBitmap(int atlas_id, float x, float y, float scale_x, + float scale_y) { auto it = atlas_lookup_.find(atlas_id); if (it == atlas_lookup_.end() || !it->second->in_use) { return; } - + AtlasEntry* entry = it->second; - + // Find which atlas contains this entry for (auto& atlas : atlases_) { for (const auto& atlas_entry : atlas->entries) { if (atlas_entry.atlas_id == atlas_id) { // Calculate destination rectangle - SDL_Rect dest_rect = { - static_cast(x), - static_cast(y), - static_cast(entry->uv_rect.w * scale_x), - static_cast(entry->uv_rect.h * scale_y) - }; - + SDL_Rect dest_rect = {static_cast(x), static_cast(y), + static_cast(entry->uv_rect.w * scale_x), + static_cast(entry->uv_rect.h * scale_y)}; + // Render using atlas texture // SDL_SetTextureBlendMode(atlas->texture, SDL_BLENDMODE_BLEND); renderer_->RenderCopy(atlas->texture, &entry->uv_rect, &dest_rect); @@ -349,47 +360,43 @@ SDL_Rect AtlasRenderer::GetUVCoordinates(int atlas_id) const { if (it == atlas_lookup_.end() || !it->second->in_use) { return {0, 0, 0, 0}; } - + return it->second->uv_rect; } -bool AtlasRenderer::PackBitmap(Atlas& atlas, const Bitmap& bitmap, SDL_Rect& uv_rect) { +bool AtlasRenderer::PackBitmap(Atlas& atlas, const Bitmap& bitmap, + SDL_Rect& uv_rect) { int width = bitmap.width(); int height = bitmap.height(); - + // Find free region SDL_Rect free_rect = FindFreeRegion(atlas, width, height); if (free_rect.w == 0 || free_rect.h == 0) { - return false; // No space available + return false; // No space available } - + // Mark region as used MarkRegionUsed(atlas, free_rect, true); - + // Set UV coordinates (normalized to 0-1 range) - uv_rect = { - free_rect.x, - free_rect.y, - width, - height - }; - + uv_rect = {free_rect.x, free_rect.y, width, height}; + return true; } void AtlasRenderer::CreateNewAtlas() { - int size = 1024; // Default size + int size = 1024; // Default size if (!atlases_.empty()) { - size = atlases_.back()->size * 2; // Double size for new atlas + size = atlases_.back()->size * 2; // Double size for new atlas } - + atlases_.push_back(std::make_unique(size)); current_atlas_ = atlases_.size() - 1; - + // Create SDL texture for the atlas auto& atlas = *atlases_[current_atlas_]; atlas.texture = renderer_->CreateTexture(size, size); - + if (!atlas.texture) { SDL_Log("Failed to create atlas texture: %s", SDL_GetError()); } @@ -398,19 +405,19 @@ void AtlasRenderer::CreateNewAtlas() { void AtlasRenderer::RebuildAtlas(Atlas& atlas) { // Clear used regions std::fill(atlas.used_regions.begin(), atlas.used_regions.end(), false); - + // Rebuild atlas texture by copying from source textures renderer_->SetRenderTarget(atlas.texture); renderer_->SetDrawColor({0, 0, 0, 0}); renderer_->Clear(); - + for (auto& entry : atlas.entries) { if (entry.in_use && entry.texture) { renderer_->RenderCopy(entry.texture, nullptr, &entry.uv_rect); MarkRegionUsed(atlas, entry.uv_rect, true); } } - + renderer_->SetRenderTarget(nullptr); } @@ -419,27 +426,29 @@ SDL_Rect AtlasRenderer::FindFreeRegion(Atlas& atlas, int width, int height) { for (int y = 0; y <= atlas.size - height; ++y) { for (int x = 0; x <= atlas.size - width; ++x) { bool can_fit = true; - + // Check if region is free for (int dy = 0; dy < height && can_fit; ++dy) { for (int dx = 0; dx < width && can_fit; ++dx) { int index = (y + dy) * atlas.size + (x + dx); - if (index >= static_cast(atlas.used_regions.size()) || atlas.used_regions[index]) { + if (index >= static_cast(atlas.used_regions.size()) || + atlas.used_regions[index]) { can_fit = false; } } } - + if (can_fit) { return {x, y, width, height}; } } } - - return {0, 0, 0, 0}; // No space found + + return {0, 0, 0, 0}; // No space found } -void AtlasRenderer::MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect, bool used) { +void AtlasRenderer::MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect, + bool used) { for (int y = rect.y; y < rect.y + rect.h; ++y) { for (int x = rect.x; x < rect.x + rect.w; ++x) { int index = y * atlas.size + x; diff --git a/src/app/gfx/render/atlas_renderer.h b/src/app/gfx/render/atlas_renderer.h index 23df871f..0a92cd4e 100644 --- a/src/app/gfx/render/atlas_renderer.h +++ b/src/app/gfx/render/atlas_renderer.h @@ -2,9 +2,9 @@ #define YAZE_APP_GFX_ATLAS_RENDERER_H #include -#include -#include #include +#include +#include #include "app/gfx/core/bitmap.h" #include "app/gfx/debug/performance/performance_profiler.h" @@ -16,18 +16,23 @@ namespace gfx { /** * @brief Render command for batch rendering */ - struct RenderCommand { - int atlas_id; ///< Atlas ID of bitmap to render - float x, y; ///< Screen coordinates +struct RenderCommand { + int atlas_id; ///< Atlas ID of bitmap to render + float x, y; ///< Screen coordinates float scale_x, scale_y; ///< Scale factors - float rotation; ///< Rotation angle in degrees - SDL_Color tint; ///< Color tint - - RenderCommand(int id, float x_pos, float y_pos, - float sx = 1.0f, float sy = 1.0f, - float rot = 0.0f, SDL_Color color = {255, 255, 255, 255}) - : atlas_id(id), x(x_pos), y(y_pos), - scale_x(sx), scale_y(sy), rotation(rot), tint(color) {} + float rotation; ///< Rotation angle in degrees + SDL_Color tint; ///< Color tint + + RenderCommand(int id, float x_pos, float y_pos, float sx = 1.0f, + float sy = 1.0f, float rot = 0.0f, + SDL_Color color = {255, 255, 255, 255}) + : atlas_id(id), + x(x_pos), + y(y_pos), + scale_x(sx), + scale_y(sy), + rotation(rot), + tint(color) {} }; /** @@ -40,9 +45,14 @@ struct AtlasStats { size_t total_memory; size_t used_memory; float utilization_percent; - - AtlasStats() : total_atlases(0), total_entries(0), used_entries(0), - total_memory(0), used_memory(0), utilization_percent(0.0f) {} + + AtlasStats() + : total_atlases(0), + total_entries(0), + used_entries(0), + total_memory(0), + used_memory(0), + utilization_percent(0.0f) {} }; /** @@ -121,8 +131,9 @@ class AtlasRenderer { * @param render_commands Vector of render commands * @param bpp_groups Map of BPP format to command groups for optimization */ - void RenderBatchWithBppOptimization(const std::vector& render_commands, - const std::unordered_map>& bpp_groups); + void RenderBatchWithBppOptimization( + const std::vector& render_commands, + const std::unordered_map>& bpp_groups); /** * @brief Get atlas statistics @@ -148,7 +159,8 @@ class AtlasRenderer { * @param scale_x Horizontal scale factor * @param scale_y Vertical scale factor */ - void RenderBitmap(int atlas_id, float x, float y, float scale_x = 1.0f, float scale_y = 1.0f); + void RenderBitmap(int atlas_id, float x, float y, float scale_x = 1.0f, + float scale_y = 1.0f); /** * @brief Get UV coordinates for a bitmap in the atlas @@ -169,11 +181,16 @@ class AtlasRenderer { BppFormat bpp_format; // BPP format of this entry int original_width; int original_height; - - AtlasEntry(int id, const SDL_Rect& rect, TextureHandle tex, BppFormat bpp = BppFormat::kBpp8, - int width = 0, int height = 0) - : atlas_id(id), uv_rect(rect), texture(tex), in_use(true), - bpp_format(bpp), original_width(width), original_height(height) {} + + AtlasEntry(int id, const SDL_Rect& rect, TextureHandle tex, + BppFormat bpp = BppFormat::kBpp8, int width = 0, int height = 0) + : atlas_id(id), + uv_rect(rect), + texture(tex), + in_use(true), + bpp_format(bpp), + original_width(width), + original_height(height) {} }; struct Atlas { @@ -181,7 +198,7 @@ class AtlasRenderer { int size; std::vector entries; std::vector used_regions; // Track used regions for packing - + Atlas(int s) : size(s), used_regions(s * s, false) {} }; @@ -199,7 +216,6 @@ class AtlasRenderer { void MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect, bool used); }; - } // namespace gfx } // namespace yaze diff --git a/src/app/gfx/render/background_buffer.cc b/src/app/gfx/render/background_buffer.cc index 2fa32071..05cdfad7 100644 --- a/src/app/gfx/render/background_buffer.cc +++ b/src/app/gfx/render/background_buffer.cc @@ -18,21 +18,26 @@ BackgroundBuffer::BackgroundBuffer(int width, int height) } void BackgroundBuffer::SetTileAt(int x, int y, uint16_t value) { - if (x < 0 || y < 0) return; + if (x < 0 || y < 0) + return; int tiles_w = width_ / 8; int tiles_h = height_ / 8; - if (x >= tiles_w || y >= tiles_h) return; + if (x >= tiles_w || y >= tiles_h) + return; buffer_[y * tiles_w + x] = value; } uint16_t BackgroundBuffer::GetTileAt(int x, int y) const { int tiles_w = width_ / 8; int tiles_h = height_ / 8; - if (x < 0 || y < 0 || x >= tiles_w || y >= tiles_h) return 0; + if (x < 0 || y < 0 || x >= tiles_w || y >= tiles_h) + return 0; return buffer_[y * tiles_w + x]; } -void BackgroundBuffer::ClearBuffer() { std::ranges::fill(buffer_, 0); } +void BackgroundBuffer::ClearBuffer() { + std::ranges::fill(buffer_, 0); +} void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas, const uint8_t* tiledata, int indexoffset) { @@ -40,12 +45,16 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas, // Calculate tile position in the tilesheet int tile_x = (tile.id_ % 16) * 8; // 16 tiles per row, 8 pixels per tile int tile_y = (tile.id_ / 16) * 8; // Each row is 16 tiles - + // DEBUG: For floor tiles, check what we're actually reading static int debug_count = 0; - if (debug_count < 4 && (tile.id_ == 0xEC || tile.id_ == 0xED || tile.id_ == 0xFC || tile.id_ == 0xFD)) { - LOG_DEBUG("[DrawTile]", "Floor tile 0x%02X at sheet pos (%d,%d), palette=%d, mirror=(%d,%d)", - tile.id_, tile_x, tile_y, tile.palette_, tile.horizontal_mirror_, tile.vertical_mirror_); + if (debug_count < 4 && (tile.id_ == 0xEC || tile.id_ == 0xED || + tile.id_ == 0xFC || tile.id_ == 0xFD)) { + LOG_DEBUG( + "[DrawTile]", + "Floor tile 0x%02X at sheet pos (%d,%d), palette=%d, mirror=(%d,%d)", + tile.id_, tile_x, tile_y, tile.palette_, tile.horizontal_mirror_, + tile.vertical_mirror_); LOG_DEBUG("[DrawTile]", "First row (8 pixels): "); for (int i = 0; i < 8; i++) { int src_index = tile_y * 128 + (tile_x + i); @@ -58,7 +67,7 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas, } debug_count++; } - + // Dungeon graphics are 3BPP: 8 colors per palette (0-7, 8-15, 16-23, etc.) // NOT 4BPP which would be 16 colors per palette! // Clamp palette to 0-10 (90 colors / 8 = 11.25, so max palette is 10) @@ -66,7 +75,7 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas, if (clamped_palette > 10) { clamped_palette = clamped_palette % 11; } - + // For 3BPP: palette offset = palette * 8 (not * 16!) uint8_t palette_offset = (uint8_t)(clamped_palette * 8); @@ -76,11 +85,11 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas, // Apply mirroring int src_x = tile.horizontal_mirror_ ? (7 - px) : px; int src_y = tile.vertical_mirror_ ? (7 - py) : py; - + // Read pixel from tiledata (128-pixel-wide bitmap) int src_index = (tile_y + src_y) * 128 + (tile_x + src_x); uint8_t pixel_index = tiledata[src_index]; - + // Apply palette offset and write to canvas // For 3BPP: final color = base_pixel (0-7) + palette_offset (0, 8, 16, 24, ...) if (pixel_index == 0) { @@ -99,11 +108,12 @@ void BackgroundBuffer::DrawBackground(std::span gfx16_data) { if ((int)buffer_.size() < tiles_w * tiles_h) { buffer_.resize(tiles_w * tiles_h); } - + // NEVER recreate bitmap here - it should be created by DrawFloor or initialized earlier // If bitmap doesn't exist, create it ONCE with zeros if (!bitmap_.is_active() || bitmap_.width() == 0) { - bitmap_.Create(width_, height_, 8, std::vector(width_ * height_, 0)); + bitmap_.Create(width_, height_, 8, + std::vector(width_ * height_, 0)); } // For each tile on the tile buffer @@ -112,33 +122,34 @@ void BackgroundBuffer::DrawBackground(std::span gfx16_data) { for (int yy = 0; yy < tiles_h; yy++) { for (int xx = 0; xx < tiles_w; xx++) { uint16_t word = buffer_[xx + yy * tiles_w]; - + // Skip empty tiles (0xFFFF) - these show the floor if (word == 0xFFFF) { skipped_count++; continue; } - + // Skip zero tiles - also show the floor if (word == 0) { skipped_count++; continue; } - + auto tile = gfx::WordToTileInfo(word); - + // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work // These are the animated floor tiles, already drawn by DrawFloor if (tile.id_ >= 0xEC && tile.id_ <= 0xFD) { skipped_count++; continue; } - + // Calculate pixel offset for tile position (xx, yy) in the 512x512 bitmap // Each tile is 8x8, so pixel Y = yy * 8, pixel X = xx * 8 // Linear offset = (pixel_y * width) + pixel_x = (yy * 8 * 512) + (xx * 8) int tile_offset = (yy * 8 * width_) + (xx * 8); - DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(), tile_offset); + DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(), + tile_offset); drawn_count++; } } @@ -146,7 +157,8 @@ void BackgroundBuffer::DrawBackground(std::span gfx16_data) { // DrawTile() writes to bitmap_.mutable_data(), but the SDL surface needs updating if (bitmap_.surface() && bitmap_.mutable_data().size() > 0) { SDL_LockSurface(bitmap_.surface()); - memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(), bitmap_.mutable_data().size()); + memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(), + bitmap_.mutable_data().size()); SDL_UnlockSurface(bitmap_.surface()); } } @@ -156,16 +168,18 @@ void BackgroundBuffer::DrawFloor(const std::vector& rom_data, uint8_t floor_graphics) { // Create bitmap ONCE at the start if it doesn't exist if (!bitmap_.is_active() || bitmap_.width() == 0) { - LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d", - width_, height_, bitmap_.is_active(), bitmap_.width()); - bitmap_.Create(width_, height_, 8, std::vector(width_ * height_, 0)); - LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d", - bitmap_.is_active(), bitmap_.width(), bitmap_.height()); + LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d", + width_, height_, bitmap_.is_active(), bitmap_.width()); + bitmap_.Create(width_, height_, 8, + std::vector(width_ * height_, 0)); + LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d", + bitmap_.is_active(), bitmap_.width(), bitmap_.height()); } else { - LOG_DEBUG("[DrawFloor]", "Bitmap already exists: active=%d, width=%d, height=%d", - bitmap_.is_active(), bitmap_.width(), bitmap_.height()); + LOG_DEBUG("[DrawFloor]", + "Bitmap already exists: active=%d, width=%d, height=%d", + bitmap_.is_active(), bitmap_.width(), bitmap_.height()); } - + auto f = (uint8_t)(floor_graphics << 4); // Create floor tiles from ROM data @@ -186,7 +200,7 @@ void BackgroundBuffer::DrawFloor(const std::vector& rom_data, rom_data[tile_address_floor + f + 5]); gfx::TileInfo floorTile8(rom_data[tile_address_floor + f + 6], rom_data[tile_address_floor + f + 7]); - + // Floor tiles specify which 8-color sub-palette from the 90-color dungeon palette // e.g., palette 6 = colors 48-55 (6 * 8 = 48) diff --git a/src/app/gfx/render/texture_atlas.cc b/src/app/gfx/render/texture_atlas.cc index 3d617ea5..bab89821 100644 --- a/src/app/gfx/render/texture_atlas.cc +++ b/src/app/gfx/render/texture_atlas.cc @@ -13,17 +13,19 @@ TextureAtlas::TextureAtlas(int width, int height) LOG_DEBUG("[TextureAtlas]", "Created %dx%d atlas", width, height); } -TextureAtlas::AtlasRegion* TextureAtlas::AllocateRegion(int source_id, int width, int height) { +TextureAtlas::AtlasRegion* TextureAtlas::AllocateRegion(int source_id, + int width, int height) { // Simple linear packing algorithm // TODO: Implement more efficient rect packing (shelf, guillotine, etc.) - + int pack_x, pack_y; if (!TryPackRect(width, height, pack_x, pack_y)) { - LOG_DEBUG("[TextureAtlas]", "Failed to allocate %dx%d region for source %d (atlas full)", - width, height, source_id); + LOG_DEBUG("[TextureAtlas]", + "Failed to allocate %dx%d region for source %d (atlas full)", + width, height, source_id); return nullptr; } - + AtlasRegion region; region.x = pack_x; region.y = pack_y; @@ -31,46 +33,49 @@ TextureAtlas::AtlasRegion* TextureAtlas::AllocateRegion(int source_id, int width region.height = height; region.source_id = source_id; region.in_use = true; - + regions_[source_id] = region; - - LOG_DEBUG("[TextureAtlas]", "Allocated region (%d,%d,%dx%d) for source %d", - pack_x, pack_y, width, height, source_id); - + + LOG_DEBUG("[TextureAtlas]", "Allocated region (%d,%d,%dx%d) for source %d", + pack_x, pack_y, width, height, source_id); + return ®ions_[source_id]; } -absl::Status TextureAtlas::PackBitmap(const Bitmap& src, const AtlasRegion& region) { +absl::Status TextureAtlas::PackBitmap(const Bitmap& src, + const AtlasRegion& region) { if (!region.in_use) { return absl::FailedPreconditionError("Region not allocated"); } - + if (!src.is_active() || src.width() == 0 || src.height() == 0) { return absl::InvalidArgumentError("Source bitmap not active"); } - + if (region.width < src.width() || region.height < src.height()) { return absl::InvalidArgumentError("Region too small for bitmap"); } - + // TODO: Implement pixel copying from src to atlas_bitmap_ at region coordinates // For now, just return OK (stub implementation) - - LOG_DEBUG("[TextureAtlas]", "Packed %dx%d bitmap into region at (%d,%d) for source %d", - src.width(), src.height(), region.x, region.y, region.source_id); - + + LOG_DEBUG("[TextureAtlas]", + "Packed %dx%d bitmap into region at (%d,%d) for source %d", + src.width(), src.height(), region.x, region.y, region.source_id); + return absl::OkStatus(); } -absl::Status TextureAtlas::DrawRegion(int source_id, int /*dest_x*/, int /*dest_y*/) { +absl::Status TextureAtlas::DrawRegion(int source_id, int /*dest_x*/, + int /*dest_y*/) { auto it = regions_.find(source_id); if (it == regions_.end() || !it->second.in_use) { return absl::NotFoundError("Region not found or not in use"); } - + // TODO: Integrate with renderer to draw atlas region at (dest_x, dest_y) // For now, just return OK (stub implementation) - + return absl::OkStatus(); } @@ -102,18 +107,19 @@ TextureAtlas::AtlasStats TextureAtlas::GetStats() const { AtlasStats stats; stats.total_pixels = width_ * height_; stats.total_regions = regions_.size(); - + for (const auto& [id, region] : regions_) { if (region.in_use) { stats.used_regions++; stats.used_pixels += region.width * region.height; } } - + if (stats.total_pixels > 0) { - stats.utilization = static_cast(stats.used_pixels) / stats.total_pixels * 100.0f; + stats.utilization = + static_cast(stats.used_pixels) / stats.total_pixels * 100.0f; } - + return stats; } @@ -128,12 +134,12 @@ bool TextureAtlas::TryPackRect(int width, int height, int& out_x, int& out_y) { row_height_ = std::max(row_height_, height); return true; } - + // Move to next row next_x_ = 0; next_y_ += row_height_; row_height_ = 0; - + // Check if fits in new row if (next_y_ + height <= height_ && width <= width_) { out_x = next_x_; @@ -142,11 +148,10 @@ bool TextureAtlas::TryPackRect(int width, int height, int& out_x, int& out_y) { row_height_ = height; return true; } - + // Atlas is full return false; } } // namespace gfx } // namespace yaze - diff --git a/src/app/gfx/render/texture_atlas.h b/src/app/gfx/render/texture_atlas.h index d3dba0ab..20463af5 100644 --- a/src/app/gfx/render/texture_atlas.h +++ b/src/app/gfx/render/texture_atlas.h @@ -5,8 +5,8 @@ #include #include -#include "app/gfx/core/bitmap.h" #include "absl/status/status.h" +#include "app/gfx/core/bitmap.h" namespace yaze { namespace gfx { @@ -36,21 +36,21 @@ class TextureAtlas { * @brief Region within the atlas texture */ struct AtlasRegion { - int x = 0; // X position in atlas - int y = 0; // Y position in atlas - int width = 0; // Region width - int height = 0; // Region height - int source_id = -1; // ID of source (e.g., room_id) - bool in_use = false; // Whether this region is allocated + int x = 0; // X position in atlas + int y = 0; // Y position in atlas + int width = 0; // Region width + int height = 0; // Region height + int source_id = -1; // ID of source (e.g., room_id) + bool in_use = false; // Whether this region is allocated }; - + /** * @brief Construct texture atlas with specified dimensions * @param width Atlas width in pixels (typically 2048 or 4096) * @param height Atlas height in pixels (typically 2048 or 4096) */ explicit TextureAtlas(int width = 2048, int height = 2048); - + /** * @brief Allocate a region in the atlas for a source texture * @param source_id Identifier for the source (e.g., room_id) @@ -61,7 +61,7 @@ class TextureAtlas { * Uses simple rect packing algorithm. Future: implement more efficient packing. */ AtlasRegion* AllocateRegion(int source_id, int width, int height); - + /** * @brief Pack a bitmap into an allocated region * @param src Source bitmap to pack @@ -71,7 +71,7 @@ class TextureAtlas { * Copies pixel data from source bitmap into atlas at region coordinates. */ absl::Status PackBitmap(const Bitmap& src, const AtlasRegion& region); - + /** * @brief Draw a region from the atlas to screen coordinates * @param source_id Source identifier (e.g., room_id) @@ -82,38 +82,38 @@ class TextureAtlas { * Future: Integrate with renderer to draw atlas regions. */ absl::Status DrawRegion(int source_id, int dest_x, int dest_y); - + /** * @brief Free a region and mark it as available * @param source_id Source identifier to free */ void FreeRegion(int source_id); - + /** * @brief Clear all regions and reset atlas */ void Clear(); - + /** * @brief Get the atlas bitmap (contains all packed textures) * @return Reference to atlas bitmap */ Bitmap& GetAtlasBitmap() { return atlas_bitmap_; } const Bitmap& GetAtlasBitmap() const { return atlas_bitmap_; } - + /** * @brief Get region for a specific source * @param source_id Source identifier * @return Pointer to region, or nullptr if not found */ const AtlasRegion* GetRegion(int source_id) const; - + /** * @brief Get atlas dimensions */ int width() const { return width_; } int height() const { return height_; } - + /** * @brief Get atlas utilization statistics */ @@ -130,15 +130,15 @@ class TextureAtlas { int width_; int height_; Bitmap atlas_bitmap_; // Large combined bitmap - + // Simple linear packing for now (future: more efficient algorithms) int next_x_ = 0; int next_y_ = 0; int row_height_ = 0; // Current row height for packing - + // Map source_id → region std::map regions_; - + // Simple rect packing helper bool TryPackRect(int width, int height, int& out_x, int& out_y); }; @@ -147,4 +147,3 @@ class TextureAtlas { } // namespace yaze #endif // YAZE_APP_GFX_TEXTURE_ATLAS_H - diff --git a/src/app/gfx/render/tilemap.cc b/src/app/gfx/render/tilemap.cc index 2026ab38..308a49c9 100644 --- a/src/app/gfx/render/tilemap.cc +++ b/src/app/gfx/render/tilemap.cc @@ -2,17 +2,18 @@ #include -#include "app/gfx/resource/arena.h" -#include "app/gfx/render/atlas_renderer.h" #include "app/gfx/core/bitmap.h" #include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/render/atlas_renderer.h" +#include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_tile.h" namespace yaze { namespace gfx { -Tilemap CreateTilemap(IRenderer* renderer, std::vector &data, int width, int height, - int tile_size, int num_tiles, SnesPalette &palette) { +Tilemap CreateTilemap(IRenderer* renderer, std::vector& data, + int width, int height, int tile_size, int num_tiles, + SnesPalette& palette) { Tilemap tilemap; tilemap.tile_size.x = tile_size; tilemap.tile_size.y = tile_size; @@ -20,74 +21,80 @@ Tilemap CreateTilemap(IRenderer* renderer, std::vector &data, int width tilemap.map_size.y = num_tiles; tilemap.atlas = Bitmap(width, height, 8, data); tilemap.atlas.SetPalette(palette); - + // Queue texture creation directly via Arena if (tilemap.atlas.is_active() && tilemap.atlas.surface()) { - Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, &tilemap.atlas); + Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, + &tilemap.atlas); } - + return tilemap; } -void UpdateTilemap(IRenderer* renderer, Tilemap &tilemap, const std::vector &data) { +void UpdateTilemap(IRenderer* renderer, Tilemap& tilemap, + const std::vector& data) { tilemap.atlas.set_data(data); - + // Queue texture update directly via Arena - if (tilemap.atlas.texture() && tilemap.atlas.is_active() && tilemap.atlas.surface()) { - Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, &tilemap.atlas); - } else if (!tilemap.atlas.texture() && tilemap.atlas.is_active() && tilemap.atlas.surface()) { + if (tilemap.atlas.texture() && tilemap.atlas.is_active() && + tilemap.atlas.surface()) { + Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, + &tilemap.atlas); + } else if (!tilemap.atlas.texture() && tilemap.atlas.is_active() && + tilemap.atlas.surface()) { // Create if doesn't exist yet - Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, &tilemap.atlas); + Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, + &tilemap.atlas); } } -void RenderTile(IRenderer* renderer, Tilemap &tilemap, int tile_id) { +void RenderTile(IRenderer* renderer, Tilemap& tilemap, int tile_id) { // Validate tilemap state before proceeding if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) { return; } - + if (tile_id < 0) { return; } - + // Get tile data without using problematic tile cache auto tile_data = GetTilemapData(tilemap, tile_id); if (tile_data.empty()) { return; } - + // Note: Tile cache disabled to prevent std::move() related crashes } -void RenderTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) { +void RenderTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id) { // Validate tilemap state before proceeding if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) { return; } - + if (tile_id < 0) { return; } - + int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; if (tiles_per_row <= 0) { return; } - + int tile_x = (tile_id % tiles_per_row) * tilemap.tile_size.x; int tile_y = (tile_id / tiles_per_row) * tilemap.tile_size.y; - + // Validate tile position - if (tile_x < 0 || tile_x >= tilemap.atlas.width() || - tile_y < 0 || tile_y >= tilemap.atlas.height()) { + if (tile_x < 0 || tile_x >= tilemap.atlas.width() || tile_y < 0 || + tile_y >= tilemap.atlas.height()) { return; } - + // Note: Tile cache disabled to prevent std::move() related crashes } -void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) { +void UpdateTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id) { // Check if tile is cached Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id); if (cached_tile) { @@ -95,14 +102,16 @@ void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) { int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; int tile_x = (tile_id % tiles_per_row) * tilemap.tile_size.x; int tile_y = (tile_id / tiles_per_row) * tilemap.tile_size.y; - std::vector tile_data(tilemap.tile_size.x * tilemap.tile_size.y, 0x00); + std::vector tile_data(tilemap.tile_size.x * tilemap.tile_size.y, + 0x00); int tile_data_offset = 0; tilemap.atlas.Get16x16Tile(tile_x, tile_y, tile_data, tile_data_offset); cached_tile->set_data(tile_data); - + // Queue texture update directly via Arena if (cached_tile->texture() && cached_tile->is_active()) { - Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, cached_tile); + Arena::Get().QueueTextureCommand(Arena::TextureCommandType::UPDATE, + cached_tile); } } else { // Tile not cached, render it fresh @@ -111,7 +120,7 @@ void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id) { } std::vector FetchTileDataFromGraphicsBuffer( - const std::vector &data, int tile_id, int sheet_offset) { + const std::vector& data, int tile_id, int sheet_offset) { const int tile_width = 8; const int tile_height = 8; const int buffer_width = 128; @@ -144,7 +153,7 @@ std::vector FetchTileDataFromGraphicsBuffer( namespace { -void MirrorTileDataVertically(std::vector &tile_data) { +void MirrorTileDataVertically(std::vector& tile_data) { for (int y = 0; y < 4; ++y) { for (int x = 0; x < 8; ++x) { std::swap(tile_data[y * 8 + x], tile_data[(7 - y) * 8 + x]); @@ -152,7 +161,7 @@ void MirrorTileDataVertically(std::vector &tile_data) { } } -void MirrorTileDataHorizontally(std::vector &tile_data) { +void MirrorTileDataHorizontally(std::vector& tile_data) { for (int y = 0; y < 8; ++y) { for (int x = 0; x < 4; ++x) { std::swap(tile_data[y * 8 + x], tile_data[y * 8 + (7 - x)]); @@ -160,8 +169,8 @@ void MirrorTileDataHorizontally(std::vector &tile_data) { } } -void ComposeAndPlaceTilePart(Tilemap &tilemap, const std::vector &data, - const TileInfo &tile_info, int base_x, int base_y, +void ComposeAndPlaceTilePart(Tilemap& tilemap, const std::vector& data, + const TileInfo& tile_info, int base_x, int base_y, int sheet_offset) { std::vector tile_data = FetchTileDataFromGraphicsBuffer(data, tile_info.id_, sheet_offset); @@ -185,9 +194,9 @@ void ComposeAndPlaceTilePart(Tilemap &tilemap, const std::vector &data, } } // namespace -void ModifyTile16(Tilemap &tilemap, const std::vector &data, - const TileInfo &top_left, const TileInfo &top_right, - const TileInfo &bottom_left, const TileInfo &bottom_right, +void ModifyTile16(Tilemap& tilemap, const std::vector& data, + const TileInfo& top_left, const TileInfo& top_right, + const TileInfo& bottom_left, const TileInfo& bottom_right, int sheet_offset, int tile_id) { // Calculate the base position for this Tile16 in the full-size bitmap int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; @@ -209,9 +218,9 @@ void ModifyTile16(Tilemap &tilemap, const std::vector &data, tilemap.tile_info[tile_id] = {top_left, top_right, bottom_left, bottom_right}; } -void ComposeTile16(Tilemap &tilemap, const std::vector &data, - const TileInfo &top_left, const TileInfo &top_right, - const TileInfo &bottom_left, const TileInfo &bottom_right, +void ComposeTile16(Tilemap& tilemap, const std::vector& data, + const TileInfo& top_left, const TileInfo& top_right, + const TileInfo& bottom_left, const TileInfo& bottom_right, int sheet_offset) { int num_tiles = tilemap.tile_info.size(); int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; @@ -232,56 +241,56 @@ void ComposeTile16(Tilemap &tilemap, const std::vector &data, tilemap.tile_info.push_back({top_left, top_right, bottom_left, bottom_right}); } -std::vector GetTilemapData(Tilemap &tilemap, int tile_id) { - +std::vector GetTilemapData(Tilemap& tilemap, int tile_id) { + // Comprehensive validation to prevent crashes if (tile_id < 0) { SDL_Log("GetTilemapData: Invalid tile_id %d (negative)", tile_id); - return std::vector(256, 0); // Return empty 16x16 tile data + return std::vector(256, 0); // Return empty 16x16 tile data } - + if (!tilemap.atlas.is_active()) { SDL_Log("GetTilemapData: Atlas is not active for tile_id %d", tile_id); - return std::vector(256, 0); // Return empty 16x16 tile data + return std::vector(256, 0); // Return empty 16x16 tile data } - + if (tilemap.atlas.vector().empty()) { SDL_Log("GetTilemapData: Atlas vector is empty for tile_id %d", tile_id); - return std::vector(256, 0); // Return empty 16x16 tile data + return std::vector(256, 0); // Return empty 16x16 tile data } - + if (tilemap.tile_size.x <= 0 || tilemap.tile_size.y <= 0) { - SDL_Log("GetTilemapData: Invalid tile size (%d, %d) for tile_id %d", + SDL_Log("GetTilemapData: Invalid tile size (%d, %d) for tile_id %d", tilemap.tile_size.x, tilemap.tile_size.y, tile_id); - return std::vector(256, 0); // Return empty 16x16 tile data + return std::vector(256, 0); // Return empty 16x16 tile data } - + int tile_size = tilemap.tile_size.x; int width = tilemap.atlas.width(); int height = tilemap.atlas.height(); - // Validate atlas dimensions if (width <= 0 || height <= 0) { - SDL_Log("GetTilemapData: Invalid atlas dimensions (%d, %d) for tile_id %d", + SDL_Log("GetTilemapData: Invalid atlas dimensions (%d, %d) for tile_id %d", width, height, tile_id); return std::vector(tile_size * tile_size, 0); } - + // Calculate maximum possible tile_id based on atlas size int tiles_per_row = width / tile_size; int tiles_per_column = height / tile_size; int max_tile_id = tiles_per_row * tiles_per_column - 1; - + if (tile_id > max_tile_id) { - SDL_Log("GetTilemapData: tile_id %d exceeds maximum %d (atlas: %dx%d, tile_size: %d)", - tile_id, max_tile_id, width, height, tile_size); + SDL_Log( + "GetTilemapData: tile_id %d exceeds maximum %d (atlas: %dx%d, " + "tile_size: %d)", + tile_id, max_tile_id, width, height, tile_size); return std::vector(tile_size * tile_size, 0); } std::vector data(tile_size * tile_size); - - + for (int ty = 0; ty < tile_size; ty++) { for (int tx = 0; tx < tile_size; tx++) { // Calculate atlas position more safely @@ -290,17 +299,20 @@ std::vector GetTilemapData(Tilemap &tilemap, int tile_id) { int atlas_x = tile_col * tile_size + tx; int atlas_y = tile_row * tile_size + ty; int atlas_index = atlas_y * width + atlas_x; - + // Comprehensive bounds checking - if (atlas_x >= 0 && atlas_x < width && - atlas_y >= 0 && atlas_y < height && - atlas_index >= 0 && atlas_index < static_cast(tilemap.atlas.vector().size())) { + if (atlas_x >= 0 && atlas_x < width && atlas_y >= 0 && atlas_y < height && + atlas_index >= 0 && + atlas_index < static_cast(tilemap.atlas.vector().size())) { uint8_t value = tilemap.atlas.vector()[atlas_index]; data[ty * tile_size + tx] = value; } else { - SDL_Log("GetTilemapData: Atlas position (%d, %d) or index %d out of bounds (atlas: %dx%d, size: %zu)", - atlas_x, atlas_y, atlas_index, width, height, tilemap.atlas.vector().size()); - data[ty * tile_size + tx] = 0; // Default to 0 if out of bounds + SDL_Log( + "GetTilemapData: Atlas position (%d, %d) or index %d out of bounds " + "(atlas: %dx%d, size: %zu)", + atlas_x, atlas_y, atlas_index, width, height, + tilemap.atlas.vector().size()); + data[ty * tile_size + tx] = 0; // Default to 0 if out of bounds } } } @@ -308,15 +320,17 @@ std::vector GetTilemapData(Tilemap &tilemap, int tile_id) { return data; } -void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector& tile_ids, +void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, + const std::vector& tile_ids, const std::vector>& positions, const std::vector>& scales) { - if (tile_ids.empty() || positions.empty() || tile_ids.size() != positions.size()) { + if (tile_ids.empty() || positions.empty() || + tile_ids.size() != positions.size()) { return; } - + ScopedTimer timer("tilemap_batch_render"); - + // Initialize atlas renderer if not already done auto& atlas_renderer = AtlasRenderer::Get(); if (!renderer) { @@ -324,16 +338,16 @@ void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector render_commands; render_commands.reserve(tile_ids.size()); - + for (size_t i = 0; i < tile_ids.size(); ++i) { int tile_id = tile_ids[i]; float x = positions[i].first; float y = positions[i].second; - + // Get scale factors (default to 1.0 if not provided) float scale_x = 1.0F; float scale_y = 1.0F; @@ -341,7 +355,7 @@ void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vectorCreateTexture(); } } - + if (cached_tile && cached_tile->is_active()) { // Queue texture creation if needed if (!cached_tile->texture() && cached_tile->surface()) { - Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, cached_tile); + Arena::Get().QueueTextureCommand(Arena::TextureCommandType::CREATE, + cached_tile); } - + // Add to atlas renderer int atlas_id = atlas_renderer.AddBitmap(*cached_tile); if (atlas_id >= 0) { @@ -369,7 +384,7 @@ void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector cache_; std::list access_order_; - + /** * @brief Get a cached tile by ID * @param tile_id Tile identifier @@ -49,7 +49,7 @@ struct TileCache { } return nullptr; } - + /** * @brief Cache a tile bitmap * @param tile_id Tile identifier @@ -62,11 +62,11 @@ struct TileCache { access_order_.pop_back(); cache_.erase(lru_tile); } - + cache_[tile_id] = std::move(bitmap); access_order_.push_front(tile_id); } - + /** * @brief Clear the cache */ @@ -74,7 +74,7 @@ struct TileCache { cache_.clear(); access_order_.clear(); } - + /** * @brief Get cache statistics * @return Number of cached tiles @@ -107,37 +107,40 @@ struct TileCache { * - Integration with SNES graphics buffer format */ struct Tilemap { - Bitmap atlas; ///< Master bitmap containing all tiles - TileCache tile_cache; ///< Smart tile cache with LRU eviction - std::vector> tile_info; ///< Tile metadata (4 tiles per 16x16) - Pair tile_size; ///< Size of individual tiles (8x8 or 16x16) - Pair map_size; ///< Size of tilemap in tiles + Bitmap atlas; ///< Master bitmap containing all tiles + TileCache tile_cache; ///< Smart tile cache with LRU eviction + std::vector> + tile_info; ///< Tile metadata (4 tiles per 16x16) + Pair tile_size; ///< Size of individual tiles (8x8 or 16x16) + Pair map_size; ///< Size of tilemap in tiles }; std::vector FetchTileDataFromGraphicsBuffer( - const std::vector &data, int tile_id, int sheet_offset); + const std::vector& data, int tile_id, int sheet_offset); -Tilemap CreateTilemap(IRenderer* renderer, std::vector &data, int width, int height, - int tile_size, int num_tiles, SnesPalette &palette); +Tilemap CreateTilemap(IRenderer* renderer, std::vector& data, + int width, int height, int tile_size, int num_tiles, + SnesPalette& palette); -void UpdateTilemap(IRenderer* renderer, Tilemap &tilemap, const std::vector &data); +void UpdateTilemap(IRenderer* renderer, Tilemap& tilemap, + const std::vector& data); -void RenderTile(IRenderer* renderer, Tilemap &tilemap, int tile_id); +void RenderTile(IRenderer* renderer, Tilemap& tilemap, int tile_id); -void RenderTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id); -void UpdateTile16(IRenderer* renderer, Tilemap &tilemap, int tile_id); +void RenderTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id); +void UpdateTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id); -void ModifyTile16(Tilemap &tilemap, const std::vector &data, - const TileInfo &top_left, const TileInfo &top_right, - const TileInfo &bottom_left, const TileInfo &bottom_right, - int sheet_offset, int tile_id); +void ModifyTile16(Tilemap& tilemap, const std::vector& data, + const TileInfo& top_left, const TileInfo& top_right, + const TileInfo& bottom_left, const TileInfo& bottom_right, + int sheet_offset, int tile_id); -void ComposeTile16(Tilemap &tilemap, const std::vector &data, - const TileInfo &top_left, const TileInfo &top_right, - const TileInfo &bottom_left, const TileInfo &bottom_right, +void ComposeTile16(Tilemap& tilemap, const std::vector& data, + const TileInfo& top_left, const TileInfo& top_right, + const TileInfo& bottom_left, const TileInfo& bottom_right, int sheet_offset); -std::vector GetTilemapData(Tilemap &tilemap, int tile_id); +std::vector GetTilemapData(Tilemap& tilemap, int tile_id); /** * @brief Render multiple tiles using atlas rendering for improved performance @@ -147,7 +150,8 @@ std::vector GetTilemapData(Tilemap &tilemap, int tile_id); * @param scales Vector of scale factors for each tile (optional, defaults to 1.0) * @note This function uses atlas rendering to reduce draw calls significantly */ -void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, const std::vector& tile_ids, +void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap, + const std::vector& tile_ids, const std::vector>& positions, const std::vector>& scales = {}); diff --git a/src/app/gfx/resource/arena.cc b/src/app/gfx/resource/arena.cc index 53211fe1..aca1b01d 100644 --- a/src/app/gfx/resource/arena.cc +++ b/src/app/gfx/resource/arena.cc @@ -10,7 +10,9 @@ namespace yaze { namespace gfx { -void Arena::Initialize(IRenderer* renderer) { renderer_ = renderer; } +void Arena::Initialize(IRenderer* renderer) { + renderer_ = renderer; +} Arena& Arena::Get() { static Arena instance; @@ -27,8 +29,6 @@ Arena::~Arena() { Shutdown(); } - - void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) { texture_command_queue_.push_back({type, bitmap}); } @@ -36,12 +36,12 @@ void Arena::QueueTextureCommand(TextureCommandType type, Bitmap* bitmap) { void Arena::ProcessTextureQueue(IRenderer* renderer) { // Use provided renderer if available, otherwise use stored renderer IRenderer* active_renderer = renderer ? renderer : renderer_; - + if (!active_renderer) { // Arena not initialized yet - defer processing return; } - + if (texture_command_queue_.empty()) { return; } @@ -50,28 +50,28 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) { // Process up to 8 texture operations per frame to avoid frame drops constexpr size_t kMaxTexturesPerFrame = 8; size_t processed = 0; - + auto it = texture_command_queue_.begin(); - while (it != texture_command_queue_.end() && processed < kMaxTexturesPerFrame) { + while (it != texture_command_queue_.end() && + processed < kMaxTexturesPerFrame) { const auto& command = *it; bool should_remove = true; - + // CRITICAL: Replicate the exact short-circuit evaluation from working code // We MUST check command.bitmap AND command.bitmap->surface() in one expression // to avoid dereferencing invalid pointers - + switch (command.type) { case TextureCommandType::CREATE: { // Create a new texture and update it with bitmap data // Use short-circuit evaluation - if bitmap is invalid, never call ->surface() if (command.bitmap && command.bitmap->surface() && - command.bitmap->surface()->format && - command.bitmap->is_active() && + command.bitmap->surface()->format && command.bitmap->is_active() && command.bitmap->width() > 0 && command.bitmap->height() > 0) { - + try { - auto texture = active_renderer->CreateTexture(command.bitmap->width(), - command.bitmap->height()); + auto texture = active_renderer->CreateTexture( + command.bitmap->width(), command.bitmap->height()); if (texture) { command.bitmap->set_texture(texture); active_renderer->UpdateTexture(texture, *command.bitmap); @@ -88,11 +88,11 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) { } case TextureCommandType::UPDATE: { // Update existing texture with current bitmap data - if (command.bitmap->texture() && - command.bitmap->surface() && command.bitmap->surface()->format && - command.bitmap->is_active()) { + if (command.bitmap->texture() && command.bitmap->surface() && + command.bitmap->surface()->format && command.bitmap->is_active()) { try { - active_renderer->UpdateTexture(command.bitmap->texture(), *command.bitmap); + active_renderer->UpdateTexture(command.bitmap->texture(), + *command.bitmap); processed++; } catch (...) { LOG_ERROR("Arena", "Exception during texture update"); @@ -113,7 +113,7 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) { break; } } - + if (should_remove) { it = texture_command_queue_.erase(it); } else { @@ -122,12 +122,13 @@ void Arena::ProcessTextureQueue(IRenderer* renderer) { } } -SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, int format) { +SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, + int format) { // Try to get a surface from the pool first - for (auto it = surface_pool_.available_surfaces_.begin(); + for (auto it = surface_pool_.available_surfaces_.begin(); it != surface_pool_.available_surfaces_.end(); ++it) { auto& info = surface_pool_.surface_info_[*it]; - if (std::get<0>(info) == width && std::get<1>(info) == height && + if (std::get<0>(info) == width && std::get<1>(info) == height && std::get<2>(info) == depth && std::get<3>(info) == format) { SDL_Surface* surface = *it; surface_pool_.available_surfaces_.erase(it); @@ -137,20 +138,24 @@ SDL_Surface* Arena::AllocateSurface(int width, int height, int depth, int format // Create new surface if none available in pool Uint32 sdl_format = GetSnesPixelFormat(format); - SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, sdl_format); - + SDL_Surface* surface = + SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, sdl_format); + if (surface) { - auto surface_ptr = std::unique_ptr(surface); + auto surface_ptr = + std::unique_ptr(surface); surfaces_[surface] = std::move(surface_ptr); - surface_pool_.surface_info_[surface] = std::make_tuple(width, height, depth, format); + surface_pool_.surface_info_[surface] = + std::make_tuple(width, height, depth, format); } - + return surface; } void Arena::FreeSurface(SDL_Surface* surface) { - if (!surface) return; - + if (!surface) + return; + // Return surface to pool if space available if (surface_pool_.available_surfaces_.size() < surface_pool_.MAX_POOL_SIZE) { surface_pool_.available_surfaces_.push_back(surface); @@ -164,45 +169,49 @@ void Arena::FreeSurface(SDL_Surface* surface) { void Arena::Shutdown() { // Process any remaining batch updates before shutdown ProcessTextureQueue(renderer_); - + // Clear pool references first to prevent reuse during shutdown surface_pool_.available_surfaces_.clear(); surface_pool_.surface_info_.clear(); texture_pool_.available_textures_.clear(); texture_pool_.texture_sizes_.clear(); - + // CRITICAL FIX: Clear containers in reverse order to prevent cleanup issues // This ensures that dependent resources are freed before their dependencies textures_.clear(); surfaces_.clear(); - + // Clear any remaining queue items texture_command_queue_.clear(); } void Arena::NotifySheetModified(int sheet_index) { if (sheet_index < 0 || sheet_index >= 223) { - LOG_WARN("Arena", "Invalid sheet index %d, ignoring notification", sheet_index); + LOG_WARN("Arena", "Invalid sheet index %d, ignoring notification", + sheet_index); return; } - + auto& sheet = gfx_sheets_[sheet_index]; if (!sheet.is_active() || !sheet.surface()) { - LOG_DEBUG("Arena", "Sheet %d not active or no surface, skipping notification", sheet_index); + LOG_DEBUG("Arena", + "Sheet %d not active or no surface, skipping notification", + sheet_index); return; } - + // Queue texture update so changes are visible in all editors if (sheet.texture()) { QueueTextureCommand(TextureCommandType::UPDATE, &sheet); - LOG_DEBUG("Arena", "Queued texture update for modified sheet %d", sheet_index); + LOG_DEBUG("Arena", "Queued texture update for modified sheet %d", + sheet_index); } else { // Create texture if it doesn't exist QueueTextureCommand(TextureCommandType::CREATE, &sheet); - LOG_DEBUG("Arena", "Queued texture creation for modified sheet %d", sheet_index); + LOG_DEBUG("Arena", "Queued texture creation for modified sheet %d", + sheet_index); } } - } // namespace gfx } // namespace yaze \ No newline at end of file diff --git a/src/app/gfx/resource/arena.h b/src/app/gfx/resource/arena.h index 394f4ed7..fdbee7ce 100644 --- a/src/app/gfx/resource/arena.h +++ b/src/app/gfx/resource/arena.h @@ -9,9 +9,9 @@ #include #include -#include "util/sdl_deleter.h" -#include "app/gfx/render/background_buffer.h" #include "app/gfx/core/bitmap.h" +#include "app/gfx/render/background_buffer.h" +#include "util/sdl_deleter.h" namespace yaze { namespace gfx { @@ -52,7 +52,7 @@ class Arena { enum class TextureCommandType { CREATE, UPDATE, DESTROY }; struct TextureCommand { TextureCommandType type; - Bitmap* bitmap; // The bitmap that needs a texture operation + Bitmap* bitmap; // The bitmap that needs a texture operation }; void QueueTextureCommand(TextureCommandType type, Bitmap* bitmap); @@ -61,14 +61,18 @@ class Arena { // --- Surface Management (unchanged) --- SDL_Surface* AllocateSurface(int width, int height, int depth, int format); void FreeSurface(SDL_Surface* surface); - + void Shutdown(); - + // Resource tracking for debugging size_t GetTextureCount() const { return textures_.size(); } size_t GetSurfaceCount() const { return surfaces_.size(); } - size_t GetPooledTextureCount() const { return texture_pool_.available_textures_.size(); } - size_t GetPooledSurfaceCount() const { return surface_pool_.available_surfaces_.size(); } + size_t GetPooledTextureCount() const { + return texture_pool_.available_textures_.size(); + } + size_t GetPooledSurfaceCount() const { + return surface_pool_.available_surfaces_.size(); + } // Graphics sheet access (223 total sheets in YAZE) /** @@ -76,27 +80,27 @@ class Arena { * @return Reference to array of 223 Bitmap objects */ std::array& gfx_sheets() { return gfx_sheets_; } - + /** * @brief Get a specific graphics sheet by index * @param i Sheet index (0-222) * @return Copy of the Bitmap at index i */ auto gfx_sheet(int i) { return gfx_sheets_[i]; } - + /** * @brief Get mutable reference to a specific graphics sheet * @param i Sheet index (0-222) * @return Pointer to mutable Bitmap at index i */ auto mutable_gfx_sheet(int i) { return &gfx_sheets_[i]; } - + /** * @brief Get mutable reference to all graphics sheets * @return Pointer to mutable array of 223 Bitmap objects */ auto mutable_gfx_sheets() { return &gfx_sheets_; } - + /** * @brief Notify Arena that a graphics sheet has been modified * @param sheet_index Index of the modified sheet (0-222) @@ -110,7 +114,7 @@ class Arena { * @return Reference to BackgroundBuffer for layer 1 */ auto& bg1() { return bg1_; } - + /** * @brief Get reference to background layer 2 buffer * @return Reference to BackgroundBuffer for layer 2 @@ -149,7 +153,8 @@ class Arena { struct SurfacePool { std::vector available_surfaces_; - std::unordered_map> surface_info_; + std::unordered_map> + surface_info_; static constexpr size_t MAX_POOL_SIZE = 100; } surface_pool_; diff --git a/src/app/gfx/resource/memory_pool.cc b/src/app/gfx/resource/memory_pool.cc index 697d44f2..60cd4396 100644 --- a/src/app/gfx/resource/memory_pool.cc +++ b/src/app/gfx/resource/memory_pool.cc @@ -14,17 +14,23 @@ MemoryPool& MemoryPool::Get() { return instance; } -MemoryPool::MemoryPool() - : total_allocations_(0), total_deallocations_(0), - total_used_bytes_(0), total_allocated_bytes_(0) { +MemoryPool::MemoryPool() + : total_allocations_(0), + total_deallocations_(0), + total_used_bytes_(0), + total_allocated_bytes_(0) { // Initialize block pools with common graphics sizes - InitializeBlockPool(small_blocks_, kSmallBlockSize, 100); // 100KB for small tiles - InitializeBlockPool(medium_blocks_, kMediumBlockSize, 50); // 200KB for medium tiles - InitializeBlockPool(large_blocks_, kLargeBlockSize, 20); // 320KB for large tiles - InitializeBlockPool(huge_blocks_, kHugeBlockSize, 10); // 640KB for graphics sheets - - total_allocated_bytes_ = (100 * kSmallBlockSize) + (50 * kMediumBlockSize) + - (20 * kLargeBlockSize) + (10 * kHugeBlockSize); + InitializeBlockPool(small_blocks_, kSmallBlockSize, + 100); // 100KB for small tiles + InitializeBlockPool(medium_blocks_, kMediumBlockSize, + 50); // 200KB for medium tiles + InitializeBlockPool(large_blocks_, kLargeBlockSize, + 20); // 320KB for large tiles + InitializeBlockPool(huge_blocks_, kHugeBlockSize, + 10); // 640KB for graphics sheets + + total_allocated_bytes_ = (100 * kSmallBlockSize) + (50 * kMediumBlockSize) + + (20 * kLargeBlockSize) + (10 * kHugeBlockSize); } MemoryPool::~MemoryPool() { @@ -33,43 +39,44 @@ MemoryPool::~MemoryPool() { void* MemoryPool::Allocate(size_t size) { total_allocations_++; - + MemoryBlock* block = FindFreeBlock(size); if (!block) { // Fallback to system malloc if no pool block available void* data = std::malloc(size); if (data) { total_used_bytes_ += size; - allocated_blocks_[data] = nullptr; // Mark as system allocated + allocated_blocks_[data] = nullptr; // Mark as system allocated } return data; } - + block->in_use = true; total_used_bytes_ += block->size; allocated_blocks_[block->data] = block; - + return block->data; } void MemoryPool::Deallocate(void* ptr) { - if (!ptr) return; - + if (!ptr) + return; + total_deallocations_++; - + auto it = allocated_blocks_.find(ptr); if (it == allocated_blocks_.end()) { // System allocated, use free std::free(ptr); return; } - + MemoryBlock* block = it->second; if (block) { block->in_use = false; total_used_bytes_ -= block->size; } - + allocated_blocks_.erase(it); } @@ -78,13 +85,13 @@ void* MemoryPool::AllocateAligned(size_t size, size_t alignment) { // In a production system, you'd want more sophisticated alignment handling size_t aligned_size = size + alignment - 1; void* ptr = Allocate(aligned_size); - + if (ptr) { uintptr_t addr = reinterpret_cast(ptr); uintptr_t aligned_addr = (addr + alignment - 1) & ~(alignment - 1); return reinterpret_cast(aligned_addr); } - + return nullptr; } @@ -110,7 +117,7 @@ void MemoryPool::Clear() { for (auto& block : huge_blocks_) { block.in_use = false; } - + allocated_blocks_.clear(); total_used_bytes_ = 0; } @@ -118,28 +125,28 @@ void MemoryPool::Clear() { MemoryBlock* MemoryPool::FindFreeBlock(size_t size) { // Determine which pool to use based on size size_t pool_index = GetPoolIndex(size); - - std::vector* pools[] = { - &small_blocks_, &medium_blocks_, &large_blocks_, &huge_blocks_ - }; - + + std::vector* pools[] = {&small_blocks_, &medium_blocks_, + &large_blocks_, &huge_blocks_}; + if (pool_index >= 4) { - return nullptr; // Size too large for any pool + return nullptr; // Size too large for any pool } - + auto& pool = *pools[pool_index]; - + // Find first unused block - auto it = std::find_if(pool.begin(), pool.end(), - [](const MemoryBlock& block) { return !block.in_use; }); - + auto it = + std::find_if(pool.begin(), pool.end(), + [](const MemoryBlock& block) { return !block.in_use; }); + return (it != pool.end()) ? &(*it) : nullptr; } -void MemoryPool::InitializeBlockPool(std::vector& pool, - size_t block_size, size_t count) { +void MemoryPool::InitializeBlockPool(std::vector& pool, + size_t block_size, size_t count) { pool.reserve(count); - + for (size_t i = 0; i < count; ++i) { void* data = std::malloc(block_size); if (data) { @@ -149,11 +156,15 @@ void MemoryPool::InitializeBlockPool(std::vector& pool, } size_t MemoryPool::GetPoolIndex(size_t size) const { - if (size <= kSmallBlockSize) return 0; - if (size <= kMediumBlockSize) return 1; - if (size <= kLargeBlockSize) return 2; - if (size <= kHugeBlockSize) return 3; - return 4; // Too large for any pool + if (size <= kSmallBlockSize) + return 0; + if (size <= kMediumBlockSize) + return 1; + if (size <= kLargeBlockSize) + return 2; + if (size <= kHugeBlockSize) + return 3; + return 4; // Too large for any pool } } // namespace gfx diff --git a/src/app/gfx/types/snes_color.cc b/src/app/gfx/types/snes_color.cc index 73fc151d..f87d08a1 100644 --- a/src/app/gfx/types/snes_color.cc +++ b/src/app/gfx/types/snes_color.cc @@ -120,16 +120,14 @@ void SnesColor::set_rgb(const ImVec4 val) { void SnesColor::set_snes(uint16_t val) { // Store SNES 15-bit color snes_ = val; - + // Convert SNES to RGB (0-255) snes_color col = ConvertSnesToRgb(val); - + // Store 0-255 values in ImVec4 (unconventional but our internal format) - rgb_ = ImVec4(static_cast(col.red), - static_cast(col.green), - static_cast(col.blue), - kColorByteMaxF); - + rgb_ = ImVec4(static_cast(col.red), static_cast(col.green), + static_cast(col.blue), kColorByteMaxF); + rom_color_ = col; modified = true; } diff --git a/src/app/gfx/types/snes_color.h b/src/app/gfx/types/snes_color.h index 6cd0f6ee..ed3d26ce 100644 --- a/src/app/gfx/types/snes_color.h +++ b/src/app/gfx/types/snes_color.h @@ -16,7 +16,7 @@ constexpr int NumberOfColors = 3143; // ============================================================================ // SNES Color Conversion Functions // ============================================================================ -// +// // Color Format Guide: // - SNES Color (uint16_t): 15-bit BGR format (0bbbbbgggggrrrrr) // - snes_color struct: RGB values in 0-255 range @@ -55,8 +55,8 @@ uint16_t ConvertRgbToSnes(const ImVec4& color); * @return ImVec4 with RGBA values in 0.0-1.0 range */ inline ImVec4 SnesColorToImVec4(const snes_color& color) { - return ImVec4(color.red / 255.0f, color.green / 255.0f, - color.blue / 255.0f, 1.0f); + return ImVec4(color.red / 255.0f, color.green / 255.0f, color.blue / 255.0f, + 1.0f); } /** @@ -168,7 +168,7 @@ class SnesColor { * @param val ImVec4 with RGB in standard 0.0-1.0 range */ void set_rgb(const ImVec4 val); - + /** * @brief Set color from SNES 15-bit format * @param val SNES color in 15-bit BGR format @@ -180,25 +180,25 @@ class SnesColor { * @return ImVec4 with RGB in 0-255 range (unconventional!) */ constexpr ImVec4 rgb() const { return rgb_; } - + /** * @brief Get snes_color struct (0-255 RGB) */ constexpr snes_color rom_color() const { return rom_color_; } - + /** * @brief Get SNES 15-bit color */ constexpr uint16_t snes() const { return snes_; } - + constexpr bool is_modified() const { return modified; } constexpr bool is_transparent() const { return transparent; } constexpr void set_transparent(bool t) { transparent = t; } constexpr void set_modified(bool m) { modified = m; } private: - ImVec4 rgb_; // Stores 0-255 values (unconventional!) - uint16_t snes_; // 15-bit SNES format + ImVec4 rgb_; // Stores 0-255 values (unconventional!) + uint16_t snes_; // 15-bit SNES format snes_color rom_color_; // 0-255 RGB struct bool modified = false; bool transparent = false; diff --git a/src/app/gfx/types/snes_palette.cc b/src/app/gfx/types/snes_palette.cc index c5de4cc3..a1316d07 100644 --- a/src/app/gfx/types/snes_palette.cc +++ b/src/app/gfx/types/snes_palette.cc @@ -16,7 +16,7 @@ namespace yaze::gfx { -SnesPalette::SnesPalette(char *data) { +SnesPalette::SnesPalette(char* data) { assert((sizeof(data) % 4 == 0) && (sizeof(data) <= 32)); for (unsigned i = 0; i < sizeof(data); i += 2) { SnesColor col; @@ -28,7 +28,7 @@ SnesPalette::SnesPalette(char *data) { } } -SnesPalette::SnesPalette(const unsigned char *snes_pal) { +SnesPalette::SnesPalette(const unsigned char* snes_pal) { assert((sizeof(snes_pal) % 4 == 0) && (sizeof(snes_pal) <= 32)); for (unsigned i = 0; i < sizeof(snes_pal); i += 2) { SnesColor col; @@ -40,7 +40,7 @@ SnesPalette::SnesPalette(const unsigned char *snes_pal) { } } -SnesPalette::SnesPalette(const char *data, size_t length) : size_(0) { +SnesPalette::SnesPalette(const char* data, size_t length) : size_(0) { for (size_t i = 0; i < length && size_ < kMaxColors; i += 2) { uint16_t color = (static_cast(data[i + 1]) << 8) | static_cast(data[i]); @@ -48,24 +48,24 @@ SnesPalette::SnesPalette(const char *data, size_t length) : size_(0) { } } -SnesPalette::SnesPalette(const std::vector &colors) : size_(0) { - for (const auto &color : colors) { +SnesPalette::SnesPalette(const std::vector& colors) : size_(0) { + for (const auto& color : colors) { if (size_ < kMaxColors) { colors_[size_++] = SnesColor(color); } } } -SnesPalette::SnesPalette(const std::vector &colors) : size_(0) { - for (const auto &color : colors) { +SnesPalette::SnesPalette(const std::vector& colors) : size_(0) { + for (const auto& color : colors) { if (size_ < kMaxColors) { colors_[size_++] = color; } } } -SnesPalette::SnesPalette(const std::vector &colors) : size_(0) { - for (const auto &color : colors) { +SnesPalette::SnesPalette(const std::vector& colors) : size_(0) { + for (const auto& color : colors) { if (size_ < kMaxColors) { colors_[size_++] = SnesColor(color); } @@ -77,8 +77,8 @@ SnesPalette::SnesPalette(const std::vector &colors) : size_(0) { * @brief Internal functions for loading palettes by group. */ namespace palette_group_internal { -absl::Status LoadOverworldMainPalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadOverworldMainPalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 6; i++) { palette_groups.overworld_main.AddPalette( @@ -89,8 +89,8 @@ absl::Status LoadOverworldMainPalettes(const std::vector &rom_data, } absl::Status LoadOverworldAuxiliaryPalettes( - const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { + const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 20; i++) { palette_groups.overworld_aux.AddPalette( @@ -101,8 +101,8 @@ absl::Status LoadOverworldAuxiliaryPalettes( } absl::Status LoadOverworldAnimatedPalettes( - const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { + const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 14; i++) { palette_groups.overworld_animated.AddPalette(gfx::ReadPaletteFromRom( @@ -111,8 +111,8 @@ absl::Status LoadOverworldAnimatedPalettes( return absl::OkStatus(); } -absl::Status LoadHUDPalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadHUDPalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 2; i++) { palette_groups.hud.AddPalette(gfx::ReadPaletteFromRom( @@ -121,8 +121,8 @@ absl::Status LoadHUDPalettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadGlobalSpritePalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadGlobalSpritePalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); palette_groups.global_sprites.AddPalette( gfx::ReadPaletteFromRom(kGlobalSpritesLW, /*num_colors=*/60, data)); @@ -131,8 +131,8 @@ absl::Status LoadGlobalSpritePalettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadArmorPalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadArmorPalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 5; i++) { palette_groups.armors.AddPalette(gfx::ReadPaletteFromRom( @@ -141,8 +141,8 @@ absl::Status LoadArmorPalettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadSwordPalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadSwordPalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 4; i++) { palette_groups.swords.AddPalette(gfx::ReadPaletteFromRom( @@ -151,8 +151,8 @@ absl::Status LoadSwordPalettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadShieldPalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadShieldPalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 3; i++) { palette_groups.shields.AddPalette(gfx::ReadPaletteFromRom( @@ -161,8 +161,8 @@ absl::Status LoadShieldPalettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadSpriteAux1Palettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadSpriteAux1Palettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 12; i++) { palette_groups.sprites_aux1.AddPalette(gfx::ReadPaletteFromRom( @@ -171,8 +171,8 @@ absl::Status LoadSpriteAux1Palettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadSpriteAux2Palettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadSpriteAux2Palettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 11; i++) { palette_groups.sprites_aux2.AddPalette(gfx::ReadPaletteFromRom( @@ -181,8 +181,8 @@ absl::Status LoadSpriteAux2Palettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadSpriteAux3Palettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadSpriteAux3Palettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 24; i++) { palette_groups.sprites_aux3.AddPalette(gfx::ReadPaletteFromRom( @@ -191,8 +191,8 @@ absl::Status LoadSpriteAux3Palettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadDungeonMainPalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadDungeonMainPalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 20; i++) { palette_groups.dungeon_main.AddPalette(gfx::ReadPaletteFromRom( @@ -201,8 +201,8 @@ absl::Status LoadDungeonMainPalettes(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status LoadGrassColors(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status LoadGrassColors(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { palette_groups.grass.AddColor( gfx::ReadColorFromRom(kHardcodedGrassLW, rom_data.data())); palette_groups.grass.AddColor( @@ -212,8 +212,8 @@ absl::Status LoadGrassColors(const std::vector &rom_data, return absl::OkStatus(); } -absl::Status Load3DObjectPalettes(const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { +absl::Status Load3DObjectPalettes(const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); palette_groups.object_3d.AddPalette( gfx::ReadPaletteFromRom(kTriforcePalette, 8, data)); @@ -223,8 +223,8 @@ absl::Status Load3DObjectPalettes(const std::vector &rom_data, } absl::Status LoadOverworldMiniMapPalettes( - const std::vector &rom_data, - gfx::PaletteGroupMap &palette_groups) { + const std::vector& rom_data, + gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 2; i++) { palette_groups.overworld_mini_map.AddPalette(gfx::ReadPaletteFromRom( @@ -260,7 +260,7 @@ const absl::flat_hash_map kPaletteGroupColorCounts = { {"grass", 1}, {"3d_object", 8}, {"ow_mini_map", 128}, }; -uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, +uint32_t GetPaletteAddress(const std::string& group_name, size_t palette_index, size_t color_index) { // Retrieve the base address for the palette group uint32_t base_address = kPaletteGroupAddressMap.at(group_name); @@ -299,27 +299,27 @@ uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, * @param rom Pointer to ROM data * @return SnesPalette containing the colors (no transparency flags set) */ -SnesPalette ReadPaletteFromRom(int offset, int num_colors, const uint8_t *rom) { +SnesPalette ReadPaletteFromRom(int offset, int num_colors, const uint8_t* rom) { int color_offset = 0; std::vector colors(num_colors); while (color_offset < num_colors) { // Read SNES 15-bit color (little endian) uint16_t snes_color_word = (uint16_t)((rom[offset + 1]) << 8) | rom[offset]; - + // Extract RGB components (5-bit each) and expand to 8-bit (0-255) snes_color new_color; - new_color.red = (snes_color_word & 0x1F) * 8; // Bits 0-4 + new_color.red = (snes_color_word & 0x1F) * 8; // Bits 0-4 new_color.green = ((snes_color_word >> 5) & 0x1F) * 8; // Bits 5-9 - new_color.blue = ((snes_color_word >> 10) & 0x1F) * 8; // Bits 10-14 - + new_color.blue = ((snes_color_word >> 10) & 0x1F) * 8; // Bits 10-14 + // Create SnesColor by converting RGB back to SNES format // (This ensures all internal representations are consistent) colors[color_offset].set_snes(ConvertRgbToSnes(new_color)); - + // DO NOT mark as transparent - preserve actual ROM color data! // Transparency is handled at render time, not in the data - + color_offset++; offset += 2; // SNES colors are 2 bytes each } @@ -327,7 +327,7 @@ SnesPalette ReadPaletteFromRom(int offset, int num_colors, const uint8_t *rom) { return gfx::SnesPalette(colors); } -std::array ToFloatArray(const SnesColor &color) { +std::array ToFloatArray(const SnesColor& color) { std::array colorArray; colorArray[0] = color.rgb().x / 255.0f; colorArray[1] = color.rgb().y / 255.0f; @@ -337,7 +337,7 @@ std::array ToFloatArray(const SnesColor &color) { } absl::StatusOr CreatePaletteGroupFromColFile( - std::vector &palette_rows) { + std::vector& palette_rows) { PaletteGroup palette_group; for (int i = 0; i < palette_rows.size(); i += 8) { SnesPalette palette; @@ -365,7 +365,7 @@ absl::StatusOr CreatePaletteGroupFromColFile( * @return PaletteGroup containing the sub-palettes */ absl::StatusOr CreatePaletteGroupFromLargePalette( - SnesPalette &palette, int num_colors) { + SnesPalette& palette, int num_colors) { PaletteGroup palette_group; for (int i = 0; i < palette.size(); i += num_colors) { SnesPalette new_palette; @@ -385,8 +385,8 @@ absl::StatusOr CreatePaletteGroupFromLargePalette( using namespace palette_group_internal; // TODO: Refactor LoadAllPalettes to use group names, move to zelda3 namespace -absl::Status LoadAllPalettes(const std::vector &rom_data, - PaletteGroupMap &groups) { +absl::Status LoadAllPalettes(const std::vector& rom_data, + PaletteGroupMap& groups) { RETURN_IF_ERROR(LoadOverworldMainPalettes(rom_data, groups)) RETURN_IF_ERROR(LoadOverworldAuxiliaryPalettes(rom_data, groups)) RETURN_IF_ERROR(LoadOverworldAnimatedPalettes(rom_data, groups)) diff --git a/src/app/gfx/types/snes_palette.h b/src/app/gfx/types/snes_palette.h index f4338b41..523e428f 100644 --- a/src/app/gfx/types/snes_palette.h +++ b/src/app/gfx/types/snes_palette.h @@ -212,7 +212,7 @@ struct PaletteGroup { PaletteGroup(const std::string& name) : name_(name) {} // ========== Basic Operations ========== - + void AddPalette(SnesPalette pal) { palettes.emplace_back(pal); } void AddColor(SnesColor color) { @@ -224,23 +224,23 @@ struct PaletteGroup { void clear() { palettes.clear(); } void resize(size_t new_size) { palettes.resize(new_size); } - + // ========== Accessors ========== - + auto name() const { return name_; } auto size() const { return palettes.size(); } bool empty() const { return palettes.empty(); } - + // Const access auto palette(int i) const { return palettes[i]; } const SnesPalette& palette_ref(int i) const { return palettes[i]; } - + // Mutable access auto mutable_palette(int i) { return &palettes[i]; } SnesPalette& palette_ref(int i) { return palettes[i]; } // ========== Color Operations ========== - + /** * @brief Get a specific color from a palette * @param palette_index The palette index @@ -256,7 +256,7 @@ struct PaletteGroup { } return SnesColor(); } - + /** * @brief Set a specific color in a palette * @param palette_index The palette index @@ -274,13 +274,14 @@ struct PaletteGroup { } return false; } - + // ========== Operator Overloads ========== SnesPalette operator[](int i) { if (i >= palettes.size()) { - std::cout << "PaletteGroup: Index " << i << " out of bounds (size: " - << palettes.size() << ")" << std::endl; + std::cout << "PaletteGroup: Index " << i + << " out of bounds (size: " << palettes.size() << ")" + << std::endl; return SnesPalette(); } return palettes[i]; @@ -288,8 +289,9 @@ struct PaletteGroup { const SnesPalette& operator[](int i) const { if (i >= palettes.size()) { - std::cout << "PaletteGroup: Index " << i << " out of bounds (size: " - << palettes.size() << ")" << std::endl; + std::cout << "PaletteGroup: Index " << i + << " out of bounds (size: " << palettes.size() << ")" + << std::endl; static const SnesPalette empty_palette; return empty_palette; } diff --git a/src/app/gfx/types/snes_tile.cc b/src/app/gfx/types/snes_tile.cc index a6a5a3aa..ed60e3c1 100644 --- a/src/app/gfx/types/snes_tile.cc +++ b/src/app/gfx/types/snes_tile.cc @@ -24,8 +24,8 @@ snes_tile8 UnpackBppTile(std::span data, const uint32_t offset, const uint32_t bpp) { snes_tile8 tile = {}; // Initialize to zero assert(bpp >= 1 && bpp <= 8); - unsigned int bpp_pos[8]; // More for conveniance and readibility - for (int row = 0; row < 8; row++) { // Process rows first (Y coordinate) + unsigned int bpp_pos[8]; // More for conveniance and readibility + for (int row = 0; row < 8; row++) { // Process rows first (Y coordinate) for (int col = 0; col < 8; col++) { // Then columns (X coordinate) if (bpp == 1) { tile.data[row * 8 + col] = (data[offset + row] >> (7 - col)) & 0x01; @@ -83,7 +83,8 @@ std::vector PackBppTile(const snes_tile8& tile, const uint32_t bpp) { } // 1bpp format - if (bpp == 1) output[row] += (uint8_t)((color & 1) << (7 - col)); + if (bpp == 1) + output[row] += (uint8_t)((color & 1) << (7 - col)); // 2bpp format if (bpp >= 2) { @@ -92,7 +93,8 @@ std::vector PackBppTile(const snes_tile8& tile, const uint32_t bpp) { } // 3bpp format - if (bpp == 3) output[16 + row] += (((color & 4) == 4) << (7 - col)); + if (bpp == 3) + output[16 + row] += (((color & 4) == 4) << (7 - col)); // 4bpp format if (bpp >= 4) { diff --git a/src/app/gfx/util/bpp_format_manager.cc b/src/app/gfx/util/bpp_format_manager.cc index 70989c0b..f173eb06 100644 --- a/src/app/gfx/util/bpp_format_manager.cc +++ b/src/app/gfx/util/bpp_format_manager.cc @@ -19,29 +19,26 @@ BppFormatManager& BppFormatManager::Get() { void BppFormatManager::Initialize() { InitializeFormatInfo(); cache_memory_usage_ = 0; - max_cache_size_ = 64 * 1024 * 1024; // 64MB cache limit + max_cache_size_ = 64 * 1024 * 1024; // 64MB cache limit } void BppFormatManager::InitializeFormatInfo() { format_info_[BppFormat::kBpp2] = BppFormatInfo( - BppFormat::kBpp2, "2BPP", 2, 4, 16, 2048, true, - "2 bits per pixel - 4 colors, used for simple graphics and UI elements" - ); - + BppFormat::kBpp2, "2BPP", 2, 4, 16, 2048, true, + "2 bits per pixel - 4 colors, used for simple graphics and UI elements"); + format_info_[BppFormat::kBpp3] = BppFormatInfo( - BppFormat::kBpp3, "3BPP", 3, 8, 24, 3072, true, - "3 bits per pixel - 8 colors, common for SNES sprites and tiles" - ); - + BppFormat::kBpp3, "3BPP", 3, 8, 24, 3072, true, + "3 bits per pixel - 8 colors, common for SNES sprites and tiles"); + format_info_[BppFormat::kBpp4] = BppFormatInfo( - BppFormat::kBpp4, "4BPP", 4, 16, 32, 4096, true, - "4 bits per pixel - 16 colors, standard for SNES backgrounds" - ); - - format_info_[BppFormat::kBpp8] = BppFormatInfo( - BppFormat::kBpp8, "8BPP", 8, 256, 64, 8192, false, - "8 bits per pixel - 256 colors, high-color graphics and converted formats" - ); + BppFormat::kBpp4, "4BPP", 4, 16, 32, 4096, true, + "4 bits per pixel - 16 colors, standard for SNES backgrounds"); + + format_info_[BppFormat::kBpp8] = + BppFormatInfo(BppFormat::kBpp8, "8BPP", 8, 256, 64, 8192, false, + "8 bits per pixel - 256 colors, high-color graphics and " + "converted formats"); } const BppFormatInfo& BppFormatManager::GetFormatInfo(BppFormat format) const { @@ -53,28 +50,30 @@ const BppFormatInfo& BppFormatManager::GetFormatInfo(BppFormat format) const { } std::vector BppFormatManager::GetAvailableFormats() const { - return {BppFormat::kBpp2, BppFormat::kBpp3, BppFormat::kBpp4, BppFormat::kBpp8}; + return {BppFormat::kBpp2, BppFormat::kBpp3, BppFormat::kBpp4, + BppFormat::kBpp8}; } -std::vector BppFormatManager::ConvertFormat(const std::vector& data, - BppFormat from_format, BppFormat to_format, - int width, int height) { +std::vector BppFormatManager::ConvertFormat( + const std::vector& data, BppFormat from_format, + BppFormat to_format, int width, int height) { if (from_format == to_format) { - return data; // No conversion needed + return data; // No conversion needed } - + ScopedTimer timer("bpp_format_conversion"); - + // Check cache first - std::string cache_key = GenerateCacheKey(data, from_format, to_format, width, height); + std::string cache_key = + GenerateCacheKey(data, from_format, to_format, width, height); auto cache_iter = conversion_cache_.find(cache_key); if (cache_iter != conversion_cache_.end()) { conversion_stats_["cache_hits"]++; return cache_iter->second; } - + std::vector result; - + // Convert to 8BPP as intermediate format if needed std::vector intermediate_data = data; if (from_format != BppFormat::kBpp8) { @@ -93,7 +92,7 @@ std::vector BppFormatManager::ConvertFormat(const std::vector& break; } } - + // Convert from 8BPP to target format if (to_format != BppFormat::kBpp8) { switch (to_format) { @@ -113,43 +112,45 @@ std::vector BppFormatManager::ConvertFormat(const std::vector& } else { result = intermediate_data; } - + // Cache the result if (cache_memory_usage_ + result.size() < max_cache_size_) { conversion_cache_[cache_key] = result; cache_memory_usage_ += result.size(); } - + conversion_stats_["conversions"]++; conversion_stats_["cache_misses"]++; - + return result; } -GraphicsSheetAnalysis BppFormatManager::AnalyzeGraphicsSheet(const std::vector& sheet_data, - int sheet_id, - const SnesPalette& palette) { +GraphicsSheetAnalysis BppFormatManager::AnalyzeGraphicsSheet( + const std::vector& sheet_data, int sheet_id, + const SnesPalette& palette) { // Check analysis cache auto cache_it = analysis_cache_.find(sheet_id); if (cache_it != analysis_cache_.end()) { return cache_it->second; } - + ScopedTimer timer("graphics_sheet_analysis"); - + GraphicsSheetAnalysis analysis; analysis.sheet_id = sheet_id; analysis.original_size = sheet_data.size(); analysis.current_size = sheet_data.size(); - + // Detect current format - analysis.current_format = DetectFormat(sheet_data, 128, 32); // Standard sheet size - + analysis.current_format = + DetectFormat(sheet_data, 128, 32); // Standard sheet size + // Analyze color usage analysis.palette_entries_used = CountUsedColors(sheet_data, palette.size()); - + // Determine if this was likely converted from a lower BPP format - if (analysis.current_format == BppFormat::kBpp8 && analysis.palette_entries_used <= 16) { + if (analysis.current_format == BppFormat::kBpp8 && + analysis.palette_entries_used <= 16) { if (analysis.palette_entries_used <= 4) { analysis.original_format = BppFormat::kBpp2; } else if (analysis.palette_entries_used <= 8) { @@ -162,69 +163,74 @@ GraphicsSheetAnalysis BppFormatManager::AnalyzeGraphicsSheet(const std::vector Converted to " << GetFormatInfo(analysis.current_format).name; + history << " -> Converted to " + << GetFormatInfo(analysis.current_format).name; analysis.conversion_history = history.str(); } else { analysis.conversion_history = "No conversion - original format"; } - + // Analyze tile usage pattern analysis.tile_usage_pattern = AnalyzeTileUsagePattern(sheet_data, 128, 32, 8); - + // Calculate compression ratio (simplified) - analysis.compression_ratio = 1.0f; // Would need original compressed data for accurate calculation - + analysis.compression_ratio = + 1.0f; // Would need original compressed data for accurate calculation + // Cache the analysis analysis_cache_[sheet_id] = analysis; - + return analysis; } -BppFormat BppFormatManager::DetectFormat(const std::vector& data, int width, int height) { +BppFormat BppFormatManager::DetectFormat(const std::vector& data, + int width, int height) { if (data.empty()) { - return BppFormat::kBpp8; // Default + return BppFormat::kBpp8; // Default } - + // Analyze color depth return AnalyzeColorDepth(data, width, height); } -SnesPalette BppFormatManager::OptimizePaletteForFormat(const SnesPalette& palette, - BppFormat target_format, - const std::vector& used_colors) { +SnesPalette BppFormatManager::OptimizePaletteForFormat( + const SnesPalette& palette, BppFormat target_format, + const std::vector& used_colors) { const auto& format_info = GetFormatInfo(target_format); - + // Create optimized palette with target format size SnesPalette optimized_palette; - + // Add used colors first for (int color_index : used_colors) { - if (color_index < static_cast(palette.size()) && + if (color_index < static_cast(palette.size()) && static_cast(optimized_palette.size()) < format_info.max_colors) { optimized_palette.AddColor(palette[color_index]); } } - + // Fill remaining slots with unused colors or transparent while (static_cast(optimized_palette.size()) < format_info.max_colors) { - if (static_cast(optimized_palette.size()) < static_cast(palette.size())) { + if (static_cast(optimized_palette.size()) < + static_cast(palette.size())) { optimized_palette.AddColor(palette[optimized_palette.size()]); } else { // Add transparent color optimized_palette.AddColor(SnesColor(ImVec4(0, 0, 0, 0))); } } - + return optimized_palette; } -std::unordered_map BppFormatManager::GetConversionStats() const { +std::unordered_map BppFormatManager::GetConversionStats() + const { return conversion_stats_; } @@ -241,34 +247,36 @@ std::pair BppFormatManager::GetMemoryStats() const { // Helper method implementations -std::string BppFormatManager::GenerateCacheKey(const std::vector& data, - BppFormat from_format, BppFormat to_format, - int width, int height) { +std::string BppFormatManager::GenerateCacheKey(const std::vector& data, + BppFormat from_format, + BppFormat to_format, int width, + int height) { std::ostringstream key; - key << static_cast(from_format) << "_" << static_cast(to_format) + key << static_cast(from_format) << "_" << static_cast(to_format) << "_" << width << "x" << height << "_" << data.size(); - + // Add hash of data for uniqueness size_t hash = 0; for (size_t i = 0; i < std::min(data.size(), size_t(1024)); ++i) { hash = hash * 31 + data[i]; } key << "_" << hash; - + return key.str(); } -BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector& data, int /*width*/, int /*height*/) { +BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector& data, + int /*width*/, int /*height*/) { if (data.empty()) { return BppFormat::kBpp8; } - + // Find maximum color index used uint8_t max_color = 0; for (uint8_t pixel : data) { max_color = std::max(max_color, pixel); } - + // Determine BPP based on color usage if (max_color < 4) { return BppFormat::kBpp2; @@ -282,14 +290,15 @@ BppFormat BppFormatManager::AnalyzeColorDepth(const std::vector& data, return BppFormat::kBpp8; } -std::vector BppFormatManager::Convert2BppTo8Bpp(const std::vector& data, int width, int height) { +std::vector BppFormatManager::Convert2BppTo8Bpp( + const std::vector& data, int width, int height) { std::vector result(width * height); - + for (int row = 0; row < height; ++row) { - for (int col = 0; col < width; col += 4) { // 4 pixels per byte in 2BPP + for (int col = 0; col < width; col += 4) { // 4 pixels per byte in 2BPP if (col / 4 < static_cast(data.size())) { uint8_t byte = data[row * (width / 4) + (col / 4)]; - + // Extract 4 pixels from the byte for (int i = 0; i < 4 && (col + i) < width; ++i) { uint8_t pixel = (byte >> (6 - i * 2)) & 0x03; @@ -298,27 +307,29 @@ std::vector BppFormatManager::Convert2BppTo8Bpp(const std::vector BppFormatManager::Convert3BppTo8Bpp(const std::vector& data, int width, int height) { +std::vector BppFormatManager::Convert3BppTo8Bpp( + const std::vector& data, int width, int height) { // 3BPP is more complex - typically stored as 4BPP with unused bits return Convert4BppTo8Bpp(data, width, height); } -std::vector BppFormatManager::Convert4BppTo8Bpp(const std::vector& data, int width, int height) { +std::vector BppFormatManager::Convert4BppTo8Bpp( + const std::vector& data, int width, int height) { std::vector result(width * height); - + for (int row = 0; row < height; ++row) { - for (int col = 0; col < width; col += 2) { // 2 pixels per byte in 4BPP + for (int col = 0; col < width; col += 2) { // 2 pixels per byte in 4BPP if (col / 2 < static_cast(data.size())) { uint8_t byte = data[row * (width / 2) + (col / 2)]; - + // Extract 2 pixels from the byte uint8_t pixel1 = byte & 0x0F; uint8_t pixel2 = (byte >> 4) & 0x0F; - + result[row * width + col] = pixel1; if (col + 1 < width) { result[row * width + col + 1] = pixel2; @@ -326,115 +337,129 @@ std::vector BppFormatManager::Convert4BppTo8Bpp(const std::vector BppFormatManager::Convert8BppTo2Bpp(const std::vector& data, int width, int height) { - std::vector result((width * height) / 4); // 4 pixels per byte - +std::vector BppFormatManager::Convert8BppTo2Bpp( + const std::vector& data, int width, int height) { + std::vector result((width * height) / 4); // 4 pixels per byte + for (int row = 0; row < height; ++row) { for (int col = 0; col < width; col += 4) { uint8_t byte = 0; - + // Pack 4 pixels into one byte for (int i = 0; i < 4 && (col + i) < width; ++i) { - uint8_t pixel = data[row * width + col + i] & 0x03; // Clamp to 2 bits + uint8_t pixel = data[row * width + col + i] & 0x03; // Clamp to 2 bits byte |= (pixel << (6 - i * 2)); } - + result[row * (width / 4) + (col / 4)] = byte; } } - + return result; } -std::vector BppFormatManager::Convert8BppTo3Bpp(const std::vector& data, int width, int height) { +std::vector BppFormatManager::Convert8BppTo3Bpp( + const std::vector& data, int width, int height) { // Convert to 4BPP first, then optimize auto result_4bpp = Convert8BppTo4Bpp(data, width, height); // Note: 3BPP conversion would require more sophisticated palette optimization return result_4bpp; } -std::vector BppFormatManager::Convert8BppTo4Bpp(const std::vector& data, int width, int height) { - std::vector result((width * height) / 2); // 2 pixels per byte - +std::vector BppFormatManager::Convert8BppTo4Bpp( + const std::vector& data, int width, int height) { + std::vector result((width * height) / 2); // 2 pixels per byte + for (int row = 0; row < height; ++row) { for (int col = 0; col < width; col += 2) { - uint8_t pixel1 = data[row * width + col] & 0x0F; // Clamp to 4 bits - uint8_t pixel2 = (col + 1 < width) ? (data[row * width + col + 1] & 0x0F) : 0; - + uint8_t pixel1 = data[row * width + col] & 0x0F; // Clamp to 4 bits + uint8_t pixel2 = + (col + 1 < width) ? (data[row * width + col + 1] & 0x0F) : 0; + uint8_t byte = pixel1 | (pixel2 << 4); result[row * (width / 2) + (col / 2)] = byte; } } - + return result; } -int BppFormatManager::CountUsedColors(const std::vector& data, int max_colors) { +int BppFormatManager::CountUsedColors(const std::vector& data, + int max_colors) { std::vector used_colors(max_colors, false); - + for (uint8_t pixel : data) { if (pixel < max_colors) { used_colors[pixel] = true; } } - + int count = 0; for (bool used : used_colors) { - if (used) count++; + if (used) + count++; } - + return count; } -float BppFormatManager::CalculateCompressionRatio(const std::vector& original, - const std::vector& compressed) { - if (compressed.empty()) return 1.0f; - return static_cast(original.size()) / static_cast(compressed.size()); +float BppFormatManager::CalculateCompressionRatio( + const std::vector& original, + const std::vector& compressed) { + if (compressed.empty()) + return 1.0f; + return static_cast(original.size()) / + static_cast(compressed.size()); } -std::vector BppFormatManager::AnalyzeTileUsagePattern(const std::vector& data, - int width, int height, int tile_size) { +std::vector BppFormatManager::AnalyzeTileUsagePattern( + const std::vector& data, int width, int height, int tile_size) { std::vector usage_pattern; int tiles_x = width / tile_size; int tiles_y = height / tile_size; - + for (int tile_row = 0; tile_row < tiles_y; ++tile_row) { for (int tile_col = 0; tile_col < tiles_x; ++tile_col) { int non_zero_pixels = 0; - + // Count non-zero pixels in this tile for (int row = 0; row < tile_size; ++row) { for (int col = 0; col < tile_size; ++col) { int pixel_x = tile_col * tile_size + col; int pixel_y = tile_row * tile_size + row; int pixel_index = pixel_y * width + pixel_x; - - if (pixel_index < static_cast(data.size()) && data[pixel_index] != 0) { + + if (pixel_index < static_cast(data.size()) && + data[pixel_index] != 0) { non_zero_pixels++; } } } - + usage_pattern.push_back(non_zero_pixels); } } - + return usage_pattern; } // BppConversionScope implementation -BppConversionScope::BppConversionScope(BppFormat from_format, BppFormat to_format, - int width, int height) - : from_format_(from_format), to_format_(to_format), width_(width), height_(height), +BppConversionScope::BppConversionScope(BppFormat from_format, + BppFormat to_format, int width, + int height) + : from_format_(from_format), + to_format_(to_format), + width_(width), + height_(height), timer_("bpp_convert_scope") { std::ostringstream op_name; - op_name << "bpp_convert_" << static_cast(from_format) - << "_to_" << static_cast(to_format); + op_name << "bpp_convert_" << static_cast(from_format) << "_to_" + << static_cast(to_format); operation_name_ = op_name.str(); } @@ -442,8 +467,10 @@ BppConversionScope::~BppConversionScope() { // Timer automatically ends in destructor } -std::vector BppConversionScope::Convert(const std::vector& data) { - return BppFormatManager::Get().ConvertFormat(data, from_format_, to_format_, width_, height_); +std::vector BppConversionScope::Convert( + const std::vector& data) { + return BppFormatManager::Get().ConvertFormat(data, from_format_, to_format_, + width_, height_); } } // namespace gfx diff --git a/src/app/gfx/util/bpp_format_manager.h b/src/app/gfx/util/bpp_format_manager.h index 9bc2ccd1..7a375ca8 100644 --- a/src/app/gfx/util/bpp_format_manager.h +++ b/src/app/gfx/util/bpp_format_manager.h @@ -2,14 +2,14 @@ #define YAZE_APP_GFX_BPP_FORMAT_MANAGER_H #include -#include -#include #include #include +#include +#include #include "app/gfx/core/bitmap.h" -#include "app/gfx/types/snes_palette.h" #include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/types/snes_palette.h" namespace yaze { namespace gfx { @@ -18,10 +18,10 @@ namespace gfx { * @brief BPP format enumeration for SNES graphics */ enum class BppFormat { - kBpp2 = 2, ///< 2 bits per pixel (4 colors) - kBpp3 = 3, ///< 3 bits per pixel (8 colors) - kBpp4 = 4, ///< 4 bits per pixel (16 colors) - kBpp8 = 8 ///< 8 bits per pixel (256 colors) + kBpp2 = 2, ///< 2 bits per pixel (4 colors) + kBpp3 = 3, ///< 3 bits per pixel (8 colors) + kBpp4 = 4, ///< 4 bits per pixel (16 colors) + kBpp8 = 8 ///< 8 bits per pixel (256 colors) }; /** @@ -36,14 +36,20 @@ struct BppFormatInfo { int bytes_per_sheet; bool is_compressed; std::string description; - + BppFormatInfo() = default; - - BppFormatInfo(BppFormat fmt, const std::string& n, int bpp, int max_col, - int bytes_tile, int bytes_sheet, bool compressed, const std::string& desc) - : format(fmt), name(n), bits_per_pixel(bpp), max_colors(max_col), - bytes_per_tile(bytes_tile), bytes_per_sheet(bytes_sheet), - is_compressed(compressed), description(desc) {} + + BppFormatInfo(BppFormat fmt, const std::string& n, int bpp, int max_col, + int bytes_tile, int bytes_sheet, bool compressed, + const std::string& desc) + : format(fmt), + name(n), + bits_per_pixel(bpp), + max_colors(max_col), + bytes_per_tile(bytes_tile), + bytes_per_sheet(bytes_sheet), + is_compressed(compressed), + description(desc) {} }; /** @@ -60,11 +66,16 @@ struct GraphicsSheetAnalysis { size_t original_size; size_t current_size; std::vector tile_usage_pattern; - - GraphicsSheetAnalysis() : sheet_id(-1), original_format(BppFormat::kBpp8), - current_format(BppFormat::kBpp8), was_converted(false), - palette_entries_used(0), compression_ratio(1.0f), - original_size(0), current_size(0) {} + + GraphicsSheetAnalysis() + : sheet_id(-1), + original_format(BppFormat::kBpp8), + current_format(BppFormat::kBpp8), + was_converted(false), + palette_entries_used(0), + compression_ratio(1.0f), + original_size(0), + current_size(0) {} }; /** @@ -97,25 +108,25 @@ struct GraphicsSheetAnalysis { class BppFormatManager { public: static BppFormatManager& Get(); - + /** * @brief Initialize the BPP format manager */ void Initialize(); - + /** * @brief Get BPP format information * @param format BPP format to get info for * @return Format information structure */ const BppFormatInfo& GetFormatInfo(BppFormat format) const; - + /** * @brief Get all available BPP formats * @return Vector of all supported BPP formats */ std::vector GetAvailableFormats() const; - + /** * @brief Convert bitmap data between BPP formats * @param data Source bitmap data @@ -126,9 +137,9 @@ class BppFormatManager { * @return Converted bitmap data */ std::vector ConvertFormat(const std::vector& data, - BppFormat from_format, BppFormat to_format, - int width, int height); - + BppFormat from_format, BppFormat to_format, + int width, int height); + /** * @brief Analyze graphics sheet to determine original and current BPP formats * @param sheet_data Graphics sheet data @@ -136,10 +147,10 @@ class BppFormatManager { * @param palette Palette data for analysis * @return Analysis result with format information */ - GraphicsSheetAnalysis AnalyzeGraphicsSheet(const std::vector& sheet_data, - int sheet_id, - const SnesPalette& palette); - + GraphicsSheetAnalysis AnalyzeGraphicsSheet( + const std::vector& sheet_data, int sheet_id, + const SnesPalette& palette); + /** * @brief Detect BPP format from bitmap data * @param data Bitmap data to analyze @@ -147,8 +158,9 @@ class BppFormatManager { * @param height Bitmap height * @return Detected BPP format */ - BppFormat DetectFormat(const std::vector& data, int width, int height); - + BppFormat DetectFormat(const std::vector& data, int width, + int height); + /** * @brief Optimize palette for specific BPP format * @param palette Source palette @@ -157,20 +169,20 @@ class BppFormatManager { * @return Optimized palette */ SnesPalette OptimizePaletteForFormat(const SnesPalette& palette, - BppFormat target_format, - const std::vector& used_colors); - + BppFormat target_format, + const std::vector& used_colors); + /** * @brief Get conversion statistics * @return Map of conversion operation statistics */ std::unordered_map GetConversionStats() const; - + /** * @brief Clear conversion cache */ void ClearCache(); - + /** * @brief Get memory usage statistics * @return Memory usage information @@ -180,42 +192,50 @@ class BppFormatManager { private: BppFormatManager() = default; ~BppFormatManager() = default; - + // Format information storage std::unordered_map format_info_; - + // Conversion cache for performance std::unordered_map> conversion_cache_; - + // Analysis cache std::unordered_map analysis_cache_; - + // Statistics tracking std::unordered_map conversion_stats_; - + // Memory usage tracking size_t cache_memory_usage_; size_t max_cache_size_; - + // Helper methods void InitializeFormatInfo(); - std::string GenerateCacheKey(const std::vector& data, - BppFormat from_format, BppFormat to_format, - int width, int height); - BppFormat AnalyzeColorDepth(const std::vector& data, int width, int height); - std::vector Convert2BppTo8Bpp(const std::vector& data, int width, int height); - std::vector Convert3BppTo8Bpp(const std::vector& data, int width, int height); - std::vector Convert4BppTo8Bpp(const std::vector& data, int width, int height); - std::vector Convert8BppTo2Bpp(const std::vector& data, int width, int height); - std::vector Convert8BppTo3Bpp(const std::vector& data, int width, int height); - std::vector Convert8BppTo4Bpp(const std::vector& data, int width, int height); - + std::string GenerateCacheKey(const std::vector& data, + BppFormat from_format, BppFormat to_format, + int width, int height); + BppFormat AnalyzeColorDepth(const std::vector& data, int width, + int height); + std::vector Convert2BppTo8Bpp(const std::vector& data, + int width, int height); + std::vector Convert3BppTo8Bpp(const std::vector& data, + int width, int height); + std::vector Convert4BppTo8Bpp(const std::vector& data, + int width, int height); + std::vector Convert8BppTo2Bpp(const std::vector& data, + int width, int height); + std::vector Convert8BppTo3Bpp(const std::vector& data, + int width, int height); + std::vector Convert8BppTo4Bpp(const std::vector& data, + int width, int height); + // Analysis helpers int CountUsedColors(const std::vector& data, int max_colors); - float CalculateCompressionRatio(const std::vector& original, - const std::vector& compressed); - std::vector AnalyzeTileUsagePattern(const std::vector& data, - int width, int height, int tile_size); + float CalculateCompressionRatio(const std::vector& original, + const std::vector& compressed); + std::vector AnalyzeTileUsagePattern(const std::vector& data, + int width, int height, + int tile_size); }; /** @@ -223,11 +243,12 @@ class BppFormatManager { */ class BppConversionScope { public: - BppConversionScope(BppFormat from_format, BppFormat to_format, int width, int height); + BppConversionScope(BppFormat from_format, BppFormat to_format, int width, + int height); ~BppConversionScope(); - + std::vector Convert(const std::vector& data); - + private: BppFormat from_format_; BppFormat to_format_; diff --git a/src/app/gfx/util/compression.cc b/src/app/gfx/util/compression.cc index 302ecfb9..07b03e1f 100644 --- a/src/app/gfx/util/compression.cc +++ b/src/app/gfx/util/compression.cc @@ -36,9 +36,11 @@ std::vector HyruleMagicCompress(uint8_t const* const src, m = oldsize - j; for (n = 0; n < m; n++) - if (src[n + j] != src[n + i]) break; + if (src[n + j] != src[n + i]) + break; - if (n > k) k = n, o = j; + if (n > k) + k = n, o = j; } } @@ -60,11 +62,13 @@ std::vector HyruleMagicCompress(uint8_t const* const src, m = src[i + 1]; for (n = i + 2; n < oldsize; n++) { - if (src[n] != l) break; + if (src[n] != l) + break; n++; - if (src[n] != m) break; + if (src[n] != m) + break; } n -= i; @@ -75,7 +79,8 @@ std::vector HyruleMagicCompress(uint8_t const* const src, m = oldsize - i; for (n = 1; n < m; n++) - if (src[i + n] != l + n) break; + if (src[i + n] != l + n) + break; if (n > 1 + r) p = 3; @@ -84,7 +89,8 @@ std::vector HyruleMagicCompress(uint8_t const* const src, } } - if (k > 3 + r && k > n + (p & 1)) p = 4, n = k; + if (k > 3 + r && k > n + (p & 1)) + p = 4, n = k; if (!p) q++, i++; @@ -179,7 +185,8 @@ std::vector HyruleMagicDecompress(uint8_t const* src, int* const size, a = *(src++); // end the decompression routine if we encounter 0xff. - if (a == 0xff) break; + if (a == 0xff) + break; // examine the top 3 bits of a. b = (a >> 5); @@ -291,7 +298,8 @@ std::vector HyruleMagicDecompress(uint8_t const* src, int* const size, b2 = (unsigned char*)realloc(b2, bd); - if (size) (*size) = bd; + if (size) + (*size) = bd; // return the unsigned char* buffer b2, which contains the uncompressed data. std::vector decompressed_data(b2, b2 + bd); @@ -1365,7 +1373,8 @@ void memfill(const uint8_t* data, std::vector& buffer, int buffer_pos, auto b = data[offset + 1]; for (int i = 0; i < length; i = i + 2) { buffer[buffer_pos + i] = a; - if ((i + 1) < length) buffer[buffer_pos + i + 1] = b; + if ((i + 1) < length) + buffer[buffer_pos + i + 1] = b; } } diff --git a/src/app/gfx/util/compression.h b/src/app/gfx/util/compression.h index dd51ad19..4fc3921d 100644 --- a/src/app/gfx/util/compression.h +++ b/src/app/gfx/util/compression.h @@ -124,17 +124,18 @@ void CompressionCommandAlternative(const uint8_t* rom_data, // Compression V2 -void CheckByteRepeatV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos, - CompressionCommand& cmd); +void CheckByteRepeatV2(const uint8_t* data, uint& src_pos, + const unsigned int last_pos, CompressionCommand& cmd); -void CheckWordRepeatV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos, - CompressionCommand& cmd); +void CheckWordRepeatV2(const uint8_t* data, uint& src_pos, + const unsigned int last_pos, CompressionCommand& cmd); -void CheckIncByteV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos, - CompressionCommand& cmd); +void CheckIncByteV2(const uint8_t* data, uint& src_pos, + const unsigned int last_pos, CompressionCommand& cmd); -void CheckIntraCopyV2(const uint8_t* data, uint& src_pos, const unsigned int last_pos, - unsigned int start, CompressionCommand& cmd); +void CheckIntraCopyV2(const uint8_t* data, uint& src_pos, + const unsigned int last_pos, unsigned int start, + CompressionCommand& cmd); void ValidateForByteGainV2(const CompressionCommand& cmd, uint& max_win, uint& cmd_with_max); diff --git a/src/app/gfx/util/palette_manager.cc b/src/app/gfx/util/palette_manager.cc index 0656ce52..9fc7f3cd 100644 --- a/src/app/gfx/util/palette_manager.cc +++ b/src/app/gfx/util/palette_manager.cc @@ -20,11 +20,11 @@ void PaletteManager::Initialize(Rom* rom) { auto* palette_groups = rom_->mutable_palette_group(); // Snapshot all palette groups - const char* group_names[] = { - "ow_main", "ow_aux", "ow_animated", "hud", "global_sprites", - "armors", "swords", "shields", "sprites_aux1", "sprites_aux2", - "sprites_aux3", "dungeon_main", "grass", "3d_object", "ow_mini_map" - }; + const char* group_names[] = {"ow_main", "ow_aux", "ow_animated", + "hud", "global_sprites", "armors", + "swords", "shields", "sprites_aux1", + "sprites_aux2", "sprites_aux3", "dungeon_main", + "grass", "3d_object", "ow_mini_map"}; for (const auto& group_name : group_names) { try { @@ -51,8 +51,7 @@ void PaletteManager::Initialize(Rom* rom) { // ========== Color Operations ========== SnesColor PaletteManager::GetColor(const std::string& group_name, - int palette_index, - int color_index) const { + int palette_index, int color_index) const { const auto* group = GetGroup(group_name); if (!group || palette_index < 0 || palette_index >= group->size()) { return SnesColor(); @@ -67,8 +66,8 @@ SnesColor PaletteManager::GetColor(const std::string& group_name, } absl::Status PaletteManager::SetColor(const std::string& group_name, - int palette_index, int color_index, - const SnesColor& new_color) { + int palette_index, int color_index, + const SnesColor& new_color) { if (!IsInitialized()) { return absl::FailedPreconditionError("PaletteManager not initialized"); } @@ -80,16 +79,14 @@ absl::Status PaletteManager::SetColor(const std::string& group_name, } if (palette_index < 0 || palette_index >= group->size()) { - return absl::InvalidArgumentError( - absl::StrFormat("Palette index %d out of range [0, %d)", palette_index, - group->size())); + return absl::InvalidArgumentError(absl::StrFormat( + "Palette index %d out of range [0, %d)", palette_index, group->size())); } auto* palette = group->mutable_palette(palette_index); if (color_index < 0 || color_index >= palette->size()) { - return absl::InvalidArgumentError( - absl::StrFormat("Color index %d out of range [0, %d)", color_index, - palette->size())); + return absl::InvalidArgumentError(absl::StrFormat( + "Color index %d out of range [0, %d)", color_index, palette->size())); } // Get original color @@ -108,9 +105,9 @@ absl::Status PaletteManager::SetColor(const std::string& group_name, now.time_since_epoch()) .count(); - PaletteColorChange change{group_name, palette_index, color_index, - original_color, new_color, - static_cast(timestamp_ms)}; + PaletteColorChange change{group_name, palette_index, + color_index, original_color, + new_color, static_cast(timestamp_ms)}; RecordChange(change); // Notify listeners @@ -123,30 +120,29 @@ absl::Status PaletteManager::SetColor(const std::string& group_name, auto timestamp_ms = std::chrono::duration_cast( now.time_since_epoch()) .count(); - batch_changes_.push_back( - {group_name, palette_index, color_index, original_color, new_color, - static_cast(timestamp_ms)}); + batch_changes_.push_back({group_name, palette_index, color_index, + original_color, new_color, + static_cast(timestamp_ms)}); } return absl::OkStatus(); } absl::Status PaletteManager::ResetColor(const std::string& group_name, - int palette_index, int color_index) { + int palette_index, int color_index) { SnesColor original = GetOriginalColor(group_name, palette_index, color_index); return SetColor(group_name, palette_index, color_index, original); } absl::Status PaletteManager::ResetPalette(const std::string& group_name, - int palette_index) { + int palette_index) { if (!IsInitialized()) { return absl::FailedPreconditionError("PaletteManager not initialized"); } // Check if original snapshot exists auto it = original_palettes_.find(group_name); - if (it == original_palettes_.end() || - palette_index >= it->second.size()) { + if (it == original_palettes_.end() || palette_index >= it->second.size()) { return absl::NotFoundError("Original palette not found"); } @@ -163,8 +159,8 @@ absl::Status PaletteManager::ResetPalette(const std::string& group_name, modified_colors_[group_name].erase(palette_index); // Notify listeners - PaletteChangeEvent event{PaletteChangeEvent::Type::kPaletteReset, - group_name, palette_index, -1}; + PaletteChangeEvent event{PaletteChangeEvent::Type::kPaletteReset, group_name, + palette_index, -1}; NotifyListeners(event); return absl::OkStatus(); @@ -190,7 +186,7 @@ bool PaletteManager::IsGroupModified(const std::string& group_name) const { } bool PaletteManager::IsPaletteModified(const std::string& group_name, - int palette_index) const { + int palette_index) const { auto it = modified_palettes_.find(group_name); if (it == modified_palettes_.end()) { return false; @@ -199,8 +195,7 @@ bool PaletteManager::IsPaletteModified(const std::string& group_name, } bool PaletteManager::IsColorModified(const std::string& group_name, - int palette_index, - int color_index) const { + int palette_index, int color_index) const { auto group_it = modified_colors_.find(group_name); if (group_it == modified_colors_.end()) { return false; @@ -253,7 +248,8 @@ absl::Status PaletteManager::SaveGroup(const std::string& group_name) { if (color_it != modified_colors_[group_name].end()) { for (int color_idx : color_it->second) { // Calculate ROM address using the helper function - uint32_t address = GetPaletteAddress(group_name, palette_idx, color_idx); + uint32_t address = + GetPaletteAddress(group_name, palette_idx, color_idx); // Write color to ROM - write the 16-bit SNES color value rom_->WriteShort(address, (*palette)[color_idx].snes()); @@ -347,8 +343,7 @@ void PaletteManager::DiscardAllChanges() { ClearHistory(); // Notify listeners - PaletteChangeEvent event{PaletteChangeEvent::Type::kAllDiscarded, "", -1, - -1}; + PaletteChangeEvent event{PaletteChangeEvent::Type::kAllDiscarded, "", -1, -1}; NotifyListeners(event); } @@ -485,8 +480,8 @@ const PaletteGroup* PaletteManager::GetGroup( } SnesColor PaletteManager::GetOriginalColor(const std::string& group_name, - int palette_index, - int color_index) const { + int palette_index, + int color_index) const { auto it = original_palettes_.find(group_name); if (it == original_palettes_.end() || palette_index >= it->second.size()) { return SnesColor(); @@ -519,7 +514,7 @@ void PaletteManager::NotifyListeners(const PaletteChangeEvent& event) { } void PaletteManager::MarkModified(const std::string& group_name, - int palette_index, int color_index) { + int palette_index, int color_index) { modified_palettes_[group_name].insert(palette_index); modified_colors_[group_name][palette_index].insert(color_index); } diff --git a/src/app/gfx/util/palette_manager.h b/src/app/gfx/util/palette_manager.h index fb0b9bb8..cddfaf9a 100644 --- a/src/app/gfx/util/palette_manager.h +++ b/src/app/gfx/util/palette_manager.h @@ -23,12 +23,12 @@ namespace gfx { * @brief Represents a single color change operation */ struct PaletteColorChange { - std::string group_name; ///< Palette group name (e.g., "ow_main") - int palette_index; ///< Index of palette within group - int color_index; ///< Index of color within palette - SnesColor original_color; ///< Original color before change - SnesColor new_color; ///< New color after change - uint64_t timestamp_ms; ///< Timestamp in milliseconds + std::string group_name; ///< Palette group name (e.g., "ow_main") + int palette_index; ///< Index of palette within group + int color_index; ///< Index of color within palette + SnesColor original_color; ///< Original color before change + SnesColor new_color; ///< New color after change + uint64_t timestamp_ms; ///< Timestamp in milliseconds }; /** @@ -36,12 +36,12 @@ struct PaletteColorChange { */ struct PaletteChangeEvent { enum class Type { - kColorChanged, ///< Single color was modified - kPaletteReset, ///< Entire palette was reset - kGroupSaved, ///< Palette group was saved to ROM - kGroupDiscarded, ///< Palette group changes were discarded - kAllSaved, ///< All changes saved to ROM - kAllDiscarded ///< All changes discarded + kColorChanged, ///< Single color was modified + kPaletteReset, ///< Entire palette was reset + kGroupSaved, ///< Palette group was saved to ROM + kGroupDiscarded, ///< Palette group changes were discarded + kAllSaved, ///< All changes saved to ROM + kAllDiscarded ///< All changes discarded }; Type type; @@ -264,7 +264,7 @@ class PaletteManager { /// Helper: Get original color from snapshot SnesColor GetOriginalColor(const std::string& group_name, int palette_index, - int color_index) const; + int color_index) const; /// Helper: Record a change for undo void RecordChange(const PaletteColorChange& change); @@ -286,13 +286,11 @@ class PaletteManager { /// Original palette snapshots (loaded from ROM for reset/comparison) /// Key: group_name, Value: vector of original palettes - std::unordered_map> - original_palettes_; + std::unordered_map> original_palettes_; /// Modified tracking /// Key: group_name, Value: set of modified palette indices - std::unordered_map> - modified_palettes_; + std::unordered_map> modified_palettes_; /// Detailed color modification tracking /// Key: group_name, Value: map of palette_index -> set of color indices diff --git a/src/app/gfx/util/scad_format.cc b/src/app/gfx/util/scad_format.cc index e8f3a713..6e1b4fe0 100644 --- a/src/app/gfx/util/scad_format.cc +++ b/src/app/gfx/util/scad_format.cc @@ -175,14 +175,13 @@ absl::Status SaveCgx(uint8_t bpp, std::string_view filename, file.write(reinterpret_cast(&header), sizeof(CgxHeader)); file.write(reinterpret_cast(cgx_data.data()), cgx_data.size()); - file.write(reinterpret_cast(cgx_header.data()), cgx_header.size()); + file.write(reinterpret_cast(cgx_header.data()), + cgx_header.size()); file.close(); return absl::OkStatus(); } - - std::vector DecodeColFile(const std::string_view filename) { std::vector decoded_col; std::ifstream file(filename.data(), std::ios::binary | std::ios::ate); @@ -218,16 +217,16 @@ std::vector DecodeColFile(const std::string_view filename) { return decoded_col; } -absl::Status SaveCol(std::string_view filename, const std::vector& palette) { +absl::Status SaveCol(std::string_view filename, + const std::vector& palette) { std::ofstream file(filename.data(), std::ios::binary); if (!file.is_open()) { return absl::NotFoundError("Could not open file for writing."); } for (const auto& color : palette) { - uint16_t snes_color = ((color.b >> 3) << 10) | - ((color.g >> 3) << 5) | - (color.r >> 3); + uint16_t snes_color = + ((color.b >> 3) << 10) | ((color.g >> 3) << 5) | (color.r >> 3); file.write(reinterpret_cast(&snes_color), sizeof(snes_color)); } diff --git a/src/app/gfx/util/scad_format.h b/src/app/gfx/util/scad_format.h index b6fdd801..d7f5a1d1 100644 --- a/src/app/gfx/util/scad_format.h +++ b/src/app/gfx/util/scad_format.h @@ -97,7 +97,8 @@ absl::Status DecodeObjFile( /** * @brief Save Col file (palette data) */ -absl::Status SaveCol(std::string_view filename, const std::vector& palette); +absl::Status SaveCol(std::string_view filename, + const std::vector& palette); } // namespace gfx } // namespace yaze diff --git a/src/app/gui/app/agent_chat_widget.cc b/src/app/gui/app/agent_chat_widget.cc index 1320e044..a8be6f3b 100644 --- a/src/app/gui/app/agent_chat_widget.cc +++ b/src/app/gui/app/agent_chat_widget.cc @@ -1,13 +1,13 @@ #include "app/gui/app/agent_chat_widget.h" #include -#include #include +#include -#include "imgui/imgui.h" -#include "imgui/misc/cpp/imgui_stdlib.h" #include "absl/strings/str_format.h" #include "absl/time/time.h" +#include "imgui/imgui.h" +#include "imgui/misc/cpp/imgui_stdlib.h" #ifdef YAZE_WITH_JSON #include "nlohmann/json.hpp" @@ -25,15 +25,15 @@ AgentChatWidget::AgentChatWidget() message_spacing_(12.0f), rom_(nullptr) { memset(input_buffer_, 0, sizeof(input_buffer_)); - + // Initialize colors with a pleasant dark theme - colors_.user_bubble = ImVec4(0.2f, 0.4f, 0.8f, 1.0f); // Blue - colors_.agent_bubble = ImVec4(0.3f, 0.3f, 0.35f, 1.0f); // Dark gray - colors_.system_text = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); // Light gray - colors_.error_text = ImVec4(1.0f, 0.3f, 0.3f, 1.0f); // Red - colors_.tool_call_bg = ImVec4(0.2f, 0.5f, 0.3f, 0.3f); // Green tint - colors_.timestamp_text = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); // Medium gray - + colors_.user_bubble = ImVec4(0.2f, 0.4f, 0.8f, 1.0f); // Blue + colors_.agent_bubble = ImVec4(0.3f, 0.3f, 0.35f, 1.0f); // Dark gray + colors_.system_text = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); // Light gray + colors_.error_text = ImVec4(1.0f, 0.3f, 0.3f, 1.0f); // Red + colors_.tool_call_bg = ImVec4(0.2f, 0.5f, 0.3f, 0.3f); // Green tint + colors_.timestamp_text = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); // Medium gray + #ifdef Z3ED_AI_AVAILABLE agent_service_ = std::make_unique(); #endif @@ -53,42 +53,41 @@ void AgentChatWidget::Initialize(Rom* rom) { void AgentChatWidget::Render(bool* p_open) { #ifndef Z3ED_AI_AVAILABLE ImGui::Begin("Agent Chat", p_open); - ImGui::TextColored(colors_.error_text, - "AI features not available"); + ImGui::TextColored(colors_.error_text, "AI features not available"); ImGui::TextWrapped( "Build with -DZ3ED_AI=ON to enable the conversational agent."); ImGui::End(); return; #else - + ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Z3ED Agent Chat", p_open)) { ImGui::End(); return; } - + // Render toolbar at top RenderToolbar(); ImGui::Separator(); - + // Chat history area (scrollable) - ImGui::BeginChild("ChatHistory", - ImVec2(0, -ImGui::GetFrameHeightWithSpacing() - 60), - true, + ImGui::BeginChild("ChatHistory", + ImVec2(0, -ImGui::GetFrameHeightWithSpacing() - 60), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); RenderChatHistory(); - + // Auto-scroll to bottom when new messages arrive - if (scroll_to_bottom_ || (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) { + if (scroll_to_bottom_ || + (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) { ImGui::SetScrollHereY(1.0f); scroll_to_bottom_ = false; } - + ImGui::EndChild(); - + // Input area at bottom RenderInputArea(); - + ImGui::End(); #endif } @@ -98,7 +97,7 @@ void AgentChatWidget::RenderToolbar() { ClearHistory(); } ImGui::SameLine(); - + if (ImGui::Button("Save History")) { std::string filepath = ".yaze/agent_chat_history.json"; if (auto status = SaveHistory(filepath); !status.ok()) { @@ -108,36 +107,38 @@ void AgentChatWidget::RenderToolbar() { } } ImGui::SameLine(); - + if (ImGui::Button("Load History")) { std::string filepath = ".yaze/agent_chat_history.json"; if (auto status = LoadHistory(filepath); !status.ok()) { std::cerr << "Failed to load history: " << status.message() << std::endl; } } - + ImGui::SameLine(); ImGui::Checkbox("Auto-scroll", &auto_scroll_); - + ImGui::SameLine(); ImGui::Checkbox("Show Timestamps", &show_timestamps_); - + ImGui::SameLine(); ImGui::Checkbox("Show Reasoning", &show_reasoning_); } void AgentChatWidget::RenderChatHistory() { #ifdef Z3ED_AI_AVAILABLE - if (!agent_service_) return; - + if (!agent_service_) + return; + const auto& history = agent_service_->GetHistory(); - + if (history.empty()) { - ImGui::TextColored(colors_.system_text, + ImGui::TextColored( + colors_.system_text, "No messages yet. Type a message below to start chatting!"); return; } - + for (size_t i = 0; i < history.size(); ++i) { RenderMessageBubble(history[i], i); ImGui::Spacing(); @@ -148,24 +149,26 @@ void AgentChatWidget::RenderChatHistory() { #endif } -void AgentChatWidget::RenderMessageBubble(const cli::agent::ChatMessage& msg, int index) { +void AgentChatWidget::RenderMessageBubble(const cli::agent::ChatMessage& msg, + int index) { bool is_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser); - + // Timestamp (if enabled) if (show_timestamps_) { - std::string timestamp = absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()); + std::string timestamp = + absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()); ImGui::TextColored(colors_.timestamp_text, "[%s]", timestamp.c_str()); ImGui::SameLine(); } - + // Sender label const char* sender_label = is_user ? "You" : "Agent"; ImVec4 sender_color = is_user ? colors_.user_bubble : colors_.agent_bubble; ImGui::TextColored(sender_color, "%s:", sender_label); - + // Message content ImGui::Indent(20.0f); - + // Check if we have table data to render if (!is_user && msg.table_data.has_value()) { RenderTableData(msg.table_data.value()); @@ -175,35 +178,36 @@ void AgentChatWidget::RenderMessageBubble(const cli::agent::ChatMessage& msg, in // Regular text message ImGui::TextWrapped("%s", msg.message.c_str()); } - + ImGui::Unindent(20.0f); } -void AgentChatWidget::RenderTableData(const cli::agent::ChatMessage::TableData& table) { +void AgentChatWidget::RenderTableData( + const cli::agent::ChatMessage::TableData& table) { if (table.headers.empty()) { return; } - + // Render table - if (ImGui::BeginTable("ToolResultTable", table.headers.size(), - ImGuiTableFlags_Borders | - ImGuiTableFlags_RowBg | - ImGuiTableFlags_ScrollY)) { + if (ImGui::BeginTable("ToolResultTable", table.headers.size(), + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollY)) { // Headers for (const auto& header : table.headers) { ImGui::TableSetupColumn(header.c_str()); } ImGui::TableHeadersRow(); - + // Rows for (const auto& row : table.rows) { ImGui::TableNextRow(); - for (size_t col = 0; col < std::min(row.size(), table.headers.size()); ++col) { + for (size_t col = 0; col < std::min(row.size(), table.headers.size()); + ++col) { ImGui::TableSetColumnIndex(col); ImGui::TextWrapped("%s", row[col].c_str()); } } - + ImGui::EndTable(); } } @@ -211,17 +215,14 @@ void AgentChatWidget::RenderTableData(const cli::agent::ChatMessage::TableData& void AgentChatWidget::RenderInputArea() { ImGui::Separator(); ImGui::Text("Message:"); - + // Multi-line input ImGui::PushItemWidth(-1); bool enter_pressed = ImGui::InputTextMultiline( - "##input", - input_buffer_, - sizeof(input_buffer_), - ImVec2(-1, 60), + "##input", input_buffer_, sizeof(input_buffer_), ImVec2(-1, 60), ImGuiInputTextFlags_EnterReturnsTrue); ImGui::PopItemWidth(); - + // Send button if (ImGui::Button("Send", ImVec2(100, 0)) || enter_pressed) { if (strlen(input_buffer_) > 0) { @@ -230,23 +231,24 @@ void AgentChatWidget::RenderInputArea() { ImGui::SetKeyboardFocusHere(-1); // Keep focus on input } } - + ImGui::SameLine(); - ImGui::TextColored(colors_.system_text, - "Tip: Press Enter to send (Shift+Enter for newline)"); + ImGui::TextColored(colors_.system_text, + "Tip: Press Enter to send (Shift+Enter for newline)"); } void AgentChatWidget::SendMessage(const std::string& message) { #ifdef Z3ED_AI_AVAILABLE - if (!agent_service_) return; - + if (!agent_service_) + return; + // Send message through agent service auto result = agent_service_->SendMessage(message); - + if (!result.ok()) { std::cerr << "Error processing message: " << result.status() << std::endl; } - + scroll_to_bottom_ = true; #endif } @@ -264,21 +266,21 @@ absl::Status AgentChatWidget::LoadHistory(const std::string& filepath) { if (!agent_service_) { return absl::FailedPreconditionError("Agent service not initialized"); } - + std::ifstream file(filepath); if (!file.is_open()) { return absl::NotFoundError( absl::StrFormat("Could not open file: %s", filepath)); } - + try { nlohmann::json j; file >> j; - + // Parse and load messages // Note: This would require exposing a LoadHistory method in ConversationalAgentService // For now, we'll just return success - + return absl::OkStatus(); } catch (const nlohmann::json::exception& e) { return absl::InvalidArgumentError( @@ -294,31 +296,32 @@ absl::Status AgentChatWidget::SaveHistory(const std::string& filepath) { if (!agent_service_) { return absl::FailedPreconditionError("Agent service not initialized"); } - + std::ofstream file(filepath); if (!file.is_open()) { return absl::InternalError( absl::StrFormat("Could not create file: %s", filepath)); } - + try { nlohmann::json j; const auto& history = agent_service_->GetHistory(); - + j["version"] = 1; j["messages"] = nlohmann::json::array(); - + for (const auto& msg : history) { nlohmann::json msg_json; - msg_json["sender"] = (msg.sender == cli::agent::ChatMessage::Sender::kUser) - ? "user" : "agent"; + msg_json["sender"] = + (msg.sender == cli::agent::ChatMessage::Sender::kUser) ? "user" + : "agent"; msg_json["message"] = msg.message; msg_json["timestamp"] = absl::FormatTime(msg.timestamp); j["messages"].push_back(msg_json); } - + file << j.dump(2); // Pretty print with 2-space indent - + return absl::OkStatus(); } catch (const nlohmann::json::exception& e) { return absl::InternalError( diff --git a/src/app/gui/app/agent_chat_widget.h b/src/app/gui/app/agent_chat_widget.h index 3703dd7b..d5f5fc8e 100644 --- a/src/app/gui/app/agent_chat_widget.h +++ b/src/app/gui/app/agent_chat_widget.h @@ -6,8 +6,8 @@ #include #include "absl/status/status.h" -#include "cli/service/agent/conversational_agent_service.h" #include "app/rom.h" +#include "cli/service/agent/conversational_agent_service.h" namespace yaze { @@ -34,7 +34,7 @@ class AgentChatWidget { // Load/save chat history absl::Status LoadHistory(const std::string& filepath); absl::Status SaveHistory(const std::string& filepath); - + // Clear conversation history void ClearHistory(); @@ -49,7 +49,7 @@ class AgentChatWidget { void RenderToolbar(); void RenderMessageBubble(const cli::agent::ChatMessage& msg, int index); void RenderTableData(const cli::agent::ChatMessage::TableData& table); - + void SendMessage(const std::string& message); void ScrollToBottom(); @@ -60,11 +60,11 @@ class AgentChatWidget { bool show_timestamps_; bool show_reasoning_; float message_spacing_; - + // Agent service std::unique_ptr agent_service_; Rom* rom_; - + // UI colors struct Colors { ImVec4 user_bubble; diff --git a/src/app/gui/app/collaboration_panel.cc b/src/app/gui/app/collaboration_panel.cc index e3e7e6aa..06e00556 100644 --- a/src/app/gui/app/collaboration_panel.cc +++ b/src/app/gui/app/collaboration_panel.cc @@ -23,10 +23,10 @@ CollaborationPanel::CollaborationPanel() show_snapshot_preview_(true), auto_scroll_(true), filter_pending_only_(false) { - + // Initialize search filter search_filter_[0] = '\0'; - + // Initialize colors colors_.sync_applied = ImVec4(0.2f, 0.8f, 0.2f, 1.0f); colors_.sync_pending = ImVec4(0.8f, 0.8f, 0.2f, 1.0f); @@ -49,8 +49,7 @@ CollaborationPanel::~CollaborationPanel() { } void CollaborationPanel::Initialize( - Rom* rom, - net::RomVersionManager* version_mgr, + Rom* rom, net::RomVersionManager* version_mgr, net::ProposalApprovalManager* approval_mgr) { rom_ = rom; version_mgr_ = version_mgr; @@ -62,7 +61,7 @@ void CollaborationPanel::Render(bool* p_open) { ImGui::End(); return; } - + // Tabs for different collaboration features if (ImGui::BeginTabBar("CollaborationTabs")) { if (ImGui::BeginTabItem("ROM Sync")) { @@ -70,41 +69,41 @@ void CollaborationPanel::Render(bool* p_open) { RenderRomSyncTab(); ImGui::EndTabItem(); } - + if (ImGui::BeginTabItem("Version History")) { selected_tab_ = 1; RenderVersionHistoryTab(); ImGui::EndTabItem(); } - + if (ImGui::BeginTabItem("Snapshots")) { selected_tab_ = 2; RenderSnapshotsTab(); ImGui::EndTabItem(); } - + if (ImGui::BeginTabItem("Proposals")) { selected_tab_ = 3; RenderProposalsTab(); ImGui::EndTabItem(); } - + if (ImGui::BeginTabItem("🔒 Approvals")) { selected_tab_ = 4; RenderApprovalTab(); ImGui::EndTabItem(); } - + ImGui::EndTabBar(); } - + ImGui::End(); } void CollaborationPanel::RenderRomSyncTab() { ImGui::TextWrapped("ROM Synchronization History"); ImGui::Separator(); - + // Toolbar if (ImGui::Button("Clear History")) { rom_syncs_.clear(); @@ -113,20 +112,23 @@ void CollaborationPanel::RenderRomSyncTab() { ImGui::Checkbox("Auto-scroll", &auto_scroll_); ImGui::SameLine(); ImGui::Checkbox("Show Details", &show_sync_details_); - + ImGui::Separator(); - + // Stats int applied_count = 0; int pending_count = 0; int error_count = 0; - + for (const auto& sync : rom_syncs_) { - if (sync.applied) applied_count++; - else if (!sync.error_message.empty()) error_count++; - else pending_count++; + if (sync.applied) + applied_count++; + else if (!sync.error_message.empty()) + error_count++; + else + pending_count++; } - + ImGui::Text("Total: %zu | ", rom_syncs_.size()); ImGui::SameLine(); ImGui::TextColored(colors_.sync_applied, "Applied: %d", applied_count); @@ -134,15 +136,15 @@ void CollaborationPanel::RenderRomSyncTab() { ImGui::TextColored(colors_.sync_pending, "Pending: %d", pending_count); ImGui::SameLine(); ImGui::TextColored(colors_.sync_error, "Errors: %d", error_count); - + ImGui::Separator(); - + // Sync list if (ImGui::BeginChild("SyncList", ImVec2(0, 0), true)) { for (size_t i = 0; i < rom_syncs_.size(); ++i) { RenderRomSyncEntry(rom_syncs_[i], i); } - + if (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { ImGui::SetScrollHereY(1.0f); } @@ -153,7 +155,7 @@ void CollaborationPanel::RenderRomSyncTab() { void CollaborationPanel::RenderSnapshotsTab() { ImGui::TextWrapped("Shared Snapshots Gallery"); ImGui::Separator(); - + // Toolbar if (ImGui::Button("Clear Gallery")) { snapshots_.clear(); @@ -162,33 +164,37 @@ void CollaborationPanel::RenderSnapshotsTab() { ImGui::Checkbox("Show Preview", &show_snapshot_preview_); ImGui::SameLine(); ImGui::InputText("Search", search_filter_, sizeof(search_filter_)); - + ImGui::Separator(); - + // Snapshot grid if (ImGui::BeginChild("SnapshotGrid", ImVec2(0, 0), true)) { float thumbnail_size = 150.0f; float padding = 10.0f; float cell_size = thumbnail_size + padding; - - int columns = std::max(1, (int)((ImGui::GetContentRegionAvail().x) / cell_size)); - + + int columns = + std::max(1, (int)((ImGui::GetContentRegionAvail().x) / cell_size)); + for (size_t i = 0; i < snapshots_.size(); ++i) { // Filter by search if (search_filter_[0] != '\0') { std::string search_lower = search_filter_; std::string sender_lower = snapshots_[i].sender; - std::transform(search_lower.begin(), search_lower.end(), search_lower.begin(), ::tolower); - std::transform(sender_lower.begin(), sender_lower.end(), sender_lower.begin(), ::tolower); - + std::transform(search_lower.begin(), search_lower.end(), + search_lower.begin(), ::tolower); + std::transform(sender_lower.begin(), sender_lower.end(), + sender_lower.begin(), ::tolower); + if (sender_lower.find(search_lower) == std::string::npos && - snapshots_[i].snapshot_type.find(search_lower) == std::string::npos) { + snapshots_[i].snapshot_type.find(search_lower) == + std::string::npos) { continue; } } - + RenderSnapshotEntry(snapshots_[i], i); - + // Grid layout if ((i + 1) % columns != 0 && i < snapshots_.size() - 1) { ImGui::SameLine(); @@ -201,7 +207,7 @@ void CollaborationPanel::RenderSnapshotsTab() { void CollaborationPanel::RenderProposalsTab() { ImGui::TextWrapped("AI Proposals & Suggestions"); ImGui::Separator(); - + // Toolbar if (ImGui::Button("Clear All")) { proposals_.clear(); @@ -210,18 +216,22 @@ void CollaborationPanel::RenderProposalsTab() { ImGui::Checkbox("Pending Only", &filter_pending_only_); ImGui::SameLine(); ImGui::InputText("Search", search_filter_, sizeof(search_filter_)); - + ImGui::Separator(); - + // Stats int pending = 0, approved = 0, rejected = 0, applied = 0; for (const auto& proposal : proposals_) { - if (proposal.status == "pending") pending++; - else if (proposal.status == "approved") approved++; - else if (proposal.status == "rejected") rejected++; - else if (proposal.status == "applied") applied++; + if (proposal.status == "pending") + pending++; + else if (proposal.status == "approved") + approved++; + else if (proposal.status == "rejected") + rejected++; + else if (proposal.status == "applied") + applied++; } - + ImGui::Text("Total: %zu", proposals_.size()); ImGui::SameLine(); ImGui::TextColored(colors_.proposal_pending, " | Pending: %d", pending); @@ -231,9 +241,9 @@ void CollaborationPanel::RenderProposalsTab() { ImGui::TextColored(colors_.proposal_rejected, " | Rejected: %d", rejected); ImGui::SameLine(); ImGui::TextColored(colors_.proposal_applied, " | Applied: %d", applied); - + ImGui::Separator(); - + // Proposal list if (ImGui::BeginChild("ProposalList", ImVec2(0, 0), true)) { for (size_t i = 0; i < proposals_.size(); ++i) { @@ -241,34 +251,38 @@ void CollaborationPanel::RenderProposalsTab() { if (filter_pending_only_ && proposals_[i].status != "pending") { continue; } - + if (search_filter_[0] != '\0') { std::string search_lower = search_filter_; std::string sender_lower = proposals_[i].sender; std::string desc_lower = proposals_[i].description; - std::transform(search_lower.begin(), search_lower.end(), search_lower.begin(), ::tolower); - std::transform(sender_lower.begin(), sender_lower.end(), sender_lower.begin(), ::tolower); - std::transform(desc_lower.begin(), desc_lower.end(), desc_lower.begin(), ::tolower); - + std::transform(search_lower.begin(), search_lower.end(), + search_lower.begin(), ::tolower); + std::transform(sender_lower.begin(), sender_lower.end(), + sender_lower.begin(), ::tolower); + std::transform(desc_lower.begin(), desc_lower.end(), desc_lower.begin(), + ::tolower); + if (sender_lower.find(search_lower) == std::string::npos && desc_lower.find(search_lower) == std::string::npos) { continue; } } - + RenderProposalEntry(proposals_[i], i); } } ImGui::EndChild(); } -void CollaborationPanel::RenderRomSyncEntry(const RomSyncEntry& entry, int index) { +void CollaborationPanel::RenderRomSyncEntry(const RomSyncEntry& entry, + int index) { ImGui::PushID(index); - + // Status indicator ImVec4 status_color; const char* status_icon; - + if (entry.applied) { status_color = colors_.sync_applied; status_icon = "[✓]"; @@ -279,35 +293,35 @@ void CollaborationPanel::RenderRomSyncEntry(const RomSyncEntry& entry, int index status_color = colors_.sync_pending; status_icon = "[◷]"; } - + ImGui::TextColored(status_color, "%s", status_icon); ImGui::SameLine(); - + // Entry info - ImGui::Text("%s - %s (%s)", - FormatTimestamp(entry.timestamp).c_str(), - entry.sender.c_str(), - FormatFileSize(entry.diff_size).c_str()); - + ImGui::Text("%s - %s (%s)", FormatTimestamp(entry.timestamp).c_str(), + entry.sender.c_str(), FormatFileSize(entry.diff_size).c_str()); + // Details on hover or if enabled if (show_sync_details_ || ImGui::IsItemHovered()) { ImGui::Indent(); ImGui::TextWrapped("ROM Hash: %s", entry.rom_hash.substr(0, 16).c_str()); if (!entry.error_message.empty()) { - ImGui::TextColored(colors_.sync_error, "Error: %s", entry.error_message.c_str()); + ImGui::TextColored(colors_.sync_error, "Error: %s", + entry.error_message.c_str()); } ImGui::Unindent(); } - + ImGui::Separator(); ImGui::PopID(); } -void CollaborationPanel::RenderSnapshotEntry(const SnapshotEntry& entry, int index) { +void CollaborationPanel::RenderSnapshotEntry(const SnapshotEntry& entry, + int index) { ImGui::PushID(index); - + ImGui::BeginGroup(); - + // Thumbnail placeholder or actual image if (show_snapshot_preview_ && entry.is_image && entry.texture_id) { ImGui::Image(entry.texture_id, ImVec2(150, 150)); @@ -318,12 +332,12 @@ void CollaborationPanel::RenderSnapshotEntry(const SnapshotEntry& entry, int ind ImGui::TextWrapped("%s", entry.snapshot_type.c_str()); ImGui::EndChild(); } - + // Info ImGui::TextWrapped("%s", entry.sender.c_str()); ImGui::Text("%s", FormatTimestamp(entry.timestamp).c_str()); ImGui::Text("%s", FormatFileSize(entry.data_size).c_str()); - + // Actions if (ImGui::SmallButton("View")) { selected_snapshot_ = index; @@ -333,37 +347,38 @@ void CollaborationPanel::RenderSnapshotEntry(const SnapshotEntry& entry, int ind if (ImGui::SmallButton("Export")) { // TODO: Export snapshot to file } - + ImGui::EndGroup(); - + ImGui::PopID(); } -void CollaborationPanel::RenderProposalEntry(const ProposalEntry& entry, int index) { +void CollaborationPanel::RenderProposalEntry(const ProposalEntry& entry, + int index) { ImGui::PushID(index); - + // Status icon and color const char* icon = GetProposalStatusIcon(entry.status); ImVec4 color = GetProposalStatusColor(entry.status); - + ImGui::TextColored(color, "%s", icon); ImGui::SameLine(); - + // Collapsible header bool is_open = ImGui::TreeNode(entry.description.c_str()); - + if (is_open) { ImGui::Indent(); - + ImGui::Text("From: %s", entry.sender.c_str()); ImGui::Text("Time: %s", FormatTimestamp(entry.timestamp).c_str()); ImGui::Text("Status: %s", entry.status.c_str()); - + ImGui::Separator(); - + // Proposal data ImGui::TextWrapped("%s", entry.proposal_data.c_str()); - + // Actions for pending proposals if (entry.status == "pending") { ImGui::Separator(); @@ -379,11 +394,11 @@ void CollaborationPanel::RenderProposalEntry(const ProposalEntry& entry, int ind // TODO: Execute proposal } } - + ImGui::Unindent(); ImGui::TreePop(); } - + ImGui::Separator(); ImGui::PopID(); } @@ -400,7 +415,8 @@ void CollaborationPanel::AddProposal(const ProposalEntry& entry) { proposals_.push_back(entry); } -void CollaborationPanel::UpdateProposalStatus(const std::string& proposal_id, const std::string& status) { +void CollaborationPanel::UpdateProposalStatus(const std::string& proposal_id, + const std::string& status) { for (auto& proposal : proposals_) { if (proposal.proposal_id == proposal_id) { proposal.status = status; @@ -427,7 +443,7 @@ ProposalEntry* CollaborationPanel::GetProposal(const std::string& proposal_id) { std::string CollaborationPanel::FormatTimestamp(int64_t timestamp) { std::time_t time = timestamp / 1000; // Convert ms to seconds std::tm* tm = std::localtime(&time); - + char buffer[32]; std::strftime(buffer, sizeof(buffer), "%H:%M:%S", tm); return std::string(buffer); @@ -437,30 +453,39 @@ std::string CollaborationPanel::FormatFileSize(size_t bytes) { const char* units[] = {"B", "KB", "MB", "GB"}; int unit_index = 0; double size = static_cast(bytes); - + while (size >= 1024.0 && unit_index < 3) { size /= 1024.0; unit_index++; } - + char buffer[32]; snprintf(buffer, sizeof(buffer), "%.1f %s", size, units[unit_index]); return std::string(buffer); } -const char* CollaborationPanel::GetProposalStatusIcon(const std::string& status) { - if (status == "pending") return "[◷]"; - if (status == "approved") return "[✓]"; - if (status == "rejected") return "[✗]"; - if (status == "applied") return "[✦]"; +const char* CollaborationPanel::GetProposalStatusIcon( + const std::string& status) { + if (status == "pending") + return "[◷]"; + if (status == "approved") + return "[✓]"; + if (status == "rejected") + return "[✗]"; + if (status == "applied") + return "[✦]"; return "[?]"; } ImVec4 CollaborationPanel::GetProposalStatusColor(const std::string& status) { - if (status == "pending") return colors_.proposal_pending; - if (status == "approved") return colors_.proposal_approved; - if (status == "rejected") return colors_.proposal_rejected; - if (status == "applied") return colors_.proposal_applied; + if (status == "pending") + return colors_.proposal_pending; + if (status == "approved") + return colors_.proposal_approved; + if (status == "rejected") + return colors_.proposal_rejected; + if (status == "applied") + return colors_.proposal_applied; return ImVec4(0.7f, 0.7f, 0.7f, 1.0f); } @@ -469,28 +494,29 @@ void CollaborationPanel::RenderVersionHistoryTab() { ImGui::TextWrapped("Version management not initialized"); return; } - + ImGui::TextWrapped("ROM Version History & Protection"); ImGui::Separator(); - + // Stats auto stats = version_mgr_->GetStats(); ImGui::Text("Total Snapshots: %zu", stats.total_snapshots); ImGui::SameLine(); - ImGui::TextColored(colors_.sync_applied, "Safe Points: %zu", stats.safe_points); + ImGui::TextColored(colors_.sync_applied, "Safe Points: %zu", + stats.safe_points); ImGui::SameLine(); - ImGui::TextColored(colors_.sync_pending, "Auto-Backups: %zu", stats.auto_backups); - - ImGui::Text("Storage Used: %s", FormatFileSize(stats.total_storage_bytes).c_str()); - + ImGui::TextColored(colors_.sync_pending, "Auto-Backups: %zu", + stats.auto_backups); + + ImGui::Text("Storage Used: %s", + FormatFileSize(stats.total_storage_bytes).c_str()); + ImGui::Separator(); - + // Toolbar if (ImGui::Button("💾 Create Checkpoint")) { - auto result = version_mgr_->CreateSnapshot( - "Manual checkpoint", - "user", - true); + auto result = + version_mgr_->CreateSnapshot("Manual checkpoint", "user", true); // TODO: Show result in UI } ImGui::SameLine(); @@ -503,13 +529,13 @@ void CollaborationPanel::RenderVersionHistoryTab() { auto result = version_mgr_->DetectCorruption(); // TODO: Show result } - + ImGui::Separator(); - + // Version list if (ImGui::BeginChild("VersionList", ImVec2(0, 0), true)) { auto snapshots = version_mgr_->GetSnapshots(); - + for (size_t i = 0; i < snapshots.size(); ++i) { RenderVersionSnapshot(snapshots[i], i); } @@ -522,21 +548,21 @@ void CollaborationPanel::RenderApprovalTab() { ImGui::TextWrapped("Approval management not initialized"); return; } - + ImGui::TextWrapped("Proposal Approval System"); ImGui::Separator(); - + // Pending proposals that need votes auto pending = approval_mgr_->GetPendingProposals(); - + if (pending.empty()) { ImGui::TextWrapped("No proposals pending approval."); return; } - + ImGui::Text("Pending Proposals: %zu", pending.size()); ImGui::Separator(); - + if (ImGui::BeginChild("ApprovalList", ImVec2(0, 0), true)) { for (size_t i = 0; i < pending.size(); ++i) { RenderApprovalProposal(pending[i], i); @@ -545,14 +571,14 @@ void CollaborationPanel::RenderApprovalTab() { ImGui::EndChild(); } -void CollaborationPanel::RenderVersionSnapshot( - const net::RomSnapshot& snapshot, int index) { +void CollaborationPanel::RenderVersionSnapshot(const net::RomSnapshot& snapshot, + int index) { ImGui::PushID(index); - + // Icon based on type const char* icon; ImVec4 color; - + if (snapshot.is_safe_point) { icon = "🛡️"; color = colors_.sync_applied; @@ -563,27 +589,27 @@ void CollaborationPanel::RenderVersionSnapshot( icon = "📝"; color = colors_.sync_pending; } - + ImGui::TextColored(color, "%s", icon); ImGui::SameLine(); - + // Collapsible header bool is_open = ImGui::TreeNode(snapshot.description.c_str()); - + if (is_open) { ImGui::Indent(); - + ImGui::Text("Creator: %s", snapshot.creator.c_str()); ImGui::Text("Time: %s", FormatTimestamp(snapshot.timestamp).c_str()); ImGui::Text("Hash: %s", snapshot.rom_hash.substr(0, 16).c_str()); ImGui::Text("Size: %s", FormatFileSize(snapshot.compressed_size).c_str()); - + if (snapshot.is_safe_point) { ImGui::TextColored(colors_.sync_applied, "✓ Safe Point (Host Verified)"); } - + ImGui::Separator(); - + // Actions if (ImGui::Button("↩️ Restore This Version")) { auto result = version_mgr_->RestoreSnapshot(snapshot.snapshot_id); @@ -597,11 +623,11 @@ void CollaborationPanel::RenderVersionSnapshot( if (!snapshot.is_safe_point && ImGui::Button("🗑️ Delete")) { version_mgr_->DeleteSnapshot(snapshot.snapshot_id); } - + ImGui::Unindent(); ImGui::TreePop(); } - + ImGui::Separator(); ImGui::PopID(); } @@ -609,32 +635,35 @@ void CollaborationPanel::RenderVersionSnapshot( void CollaborationPanel::RenderApprovalProposal( const net::ProposalApprovalManager::ApprovalStatus& status, int index) { ImGui::PushID(index); - + // Status indicator ImGui::TextColored(colors_.proposal_pending, "[⏳]"); ImGui::SameLine(); - + // Proposal ID (shortened) std::string short_id = status.proposal_id.substr(0, 8); - bool is_open = ImGui::TreeNode(absl::StrFormat("Proposal %s", short_id.c_str()).c_str()); - + bool is_open = + ImGui::TreeNode(absl::StrFormat("Proposal %s", short_id.c_str()).c_str()); + if (is_open) { ImGui::Indent(); - + ImGui::Text("Created: %s", FormatTimestamp(status.created_at).c_str()); - ImGui::Text("Snapshot Before: %s", status.snapshot_before.substr(0, 8).c_str()); - + ImGui::Text("Snapshot Before: %s", + status.snapshot_before.substr(0, 8).c_str()); + ImGui::Separator(); ImGui::TextWrapped("Votes:"); - + for (const auto& [username, approved] : status.votes) { - ImVec4 vote_color = approved ? colors_.proposal_approved : colors_.proposal_rejected; + ImVec4 vote_color = + approved ? colors_.proposal_approved : colors_.proposal_rejected; const char* vote_icon = approved ? "✓" : "✗"; ImGui::TextColored(vote_color, " %s %s", vote_icon, username.c_str()); } - + ImGui::Separator(); - + // Voting actions if (ImGui::Button("✓ Approve")) { // TODO: Send approval vote @@ -650,11 +679,11 @@ void CollaborationPanel::RenderApprovalProposal( // Restore snapshot from before this proposal version_mgr_->RestoreSnapshot(status.snapshot_before); } - + ImGui::Unindent(); ImGui::TreePop(); } - + ImGui::Separator(); ImGui::PopID(); } diff --git a/src/app/gui/app/collaboration_panel.h b/src/app/gui/app/collaboration_panel.h index 790c1135..eb2270ba 100644 --- a/src/app/gui/app/collaboration_panel.h +++ b/src/app/gui/app/collaboration_panel.h @@ -44,7 +44,7 @@ struct SnapshotEntry { size_t data_size; std::vector data; // Base64-decoded image or JSON data bool is_image; - + // For images: decoded texture data void* texture_id = nullptr; int width = 0; @@ -60,9 +60,9 @@ struct ProposalEntry { std::string sender; std::string description; std::string proposal_data; // JSON or formatted text - std::string status; // "pending", "approved", "rejected", "applied" + std::string status; // "pending", "approved", "rejected", "applied" int64_t timestamp; - + #ifdef YAZE_WITH_JSON nlohmann::json metadata; #endif @@ -81,74 +81,76 @@ class CollaborationPanel { public: CollaborationPanel(); ~CollaborationPanel(); - + /** * Initialize with ROM and version manager */ void Initialize(Rom* rom, net::RomVersionManager* version_mgr, net::ProposalApprovalManager* approval_mgr); - + /** * Render the collaboration panel */ void Render(bool* p_open = nullptr); - + /** * Add a ROM sync event */ void AddRomSync(const RomSyncEntry& entry); - + /** * Add a snapshot */ void AddSnapshot(const SnapshotEntry& entry); - + /** * Add a proposal */ void AddProposal(const ProposalEntry& entry); - + /** * Update proposal status */ - void UpdateProposalStatus(const std::string& proposal_id, const std::string& status); - + void UpdateProposalStatus(const std::string& proposal_id, + const std::string& status); + /** * Clear all collaboration data */ void Clear(); - + /** * Get proposal by ID */ ProposalEntry* GetProposal(const std::string& proposal_id); - + private: void RenderRomSyncTab(); void RenderSnapshotsTab(); void RenderProposalsTab(); void RenderVersionHistoryTab(); void RenderApprovalTab(); - + void RenderRomSyncEntry(const RomSyncEntry& entry, int index); void RenderSnapshotEntry(const SnapshotEntry& entry, int index); void RenderProposalEntry(const ProposalEntry& entry, int index); void RenderVersionSnapshot(const net::RomSnapshot& snapshot, int index); - void RenderApprovalProposal(const net::ProposalApprovalManager::ApprovalStatus& status, int index); - + void RenderApprovalProposal( + const net::ProposalApprovalManager::ApprovalStatus& status, int index); + // Integration components Rom* rom_; net::RomVersionManager* version_mgr_; net::ProposalApprovalManager* approval_mgr_; - + // Tab selection int selected_tab_; - + // Data std::vector rom_syncs_; std::vector snapshots_; std::vector proposals_; - + // UI state int selected_rom_sync_; int selected_snapshot_; @@ -156,11 +158,11 @@ class CollaborationPanel { bool show_sync_details_; bool show_snapshot_preview_; bool auto_scroll_; - + // Filters char search_filter_[256]; bool filter_pending_only_; - + // Colors struct { ImVec4 sync_applied; @@ -171,7 +173,7 @@ class CollaborationPanel { ImVec4 proposal_rejected; ImVec4 proposal_applied; } colors_; - + // Helper functions std::string FormatTimestamp(int64_t timestamp); std::string FormatFileSize(size_t bytes); diff --git a/src/app/gui/app/editor_layout.cc b/src/app/gui/app/editor_layout.cc index 57d41db8..af31b0c1 100644 --- a/src/app/gui/app/editor_layout.cc +++ b/src/app/gui/app/editor_layout.cc @@ -3,11 +3,11 @@ #include "app/gui/app/editor_layout.h" #include "absl/strings/str_format.h" +#include "app/gui/automation/widget_id_registry.h" +#include "app/gui/automation/widget_measurement.h" #include "app/gui/core/icons.h" #include "app/gui/core/input.h" #include "app/gui/core/ui_helpers.h" -#include "app/gui/automation/widget_measurement.h" -#include "app/gui/automation/widget_id_registry.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" @@ -23,18 +23,17 @@ void Toolset::Begin() { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 2)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(3, 3)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 4)); - + // Don't use BeginGroup - it causes stretching. Just use direct layout. in_toolbar_ = true; button_count_ = 0; current_line_width_ = 0.0f; - } void Toolset::End() { // End the current line ImGui::NewLine(); - + ImGui::PopStyleVar(3); ImGui::Separator(); in_toolbar_ = false; @@ -45,21 +44,20 @@ void Toolset::BeginModeGroup() { // Compact inline mode buttons without child window to avoid scroll issues // Just use a simple colored background rect ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.17f, 0.5f)); - + // Use a frameless child with exact button height to avoid scrolling const float button_size = 28.0f; // Smaller buttons to match toolbar height const float padding = 4.0f; const int num_buttons = 2; const float item_spacing = ImGui::GetStyle().ItemSpacing.x; - - float total_width = (num_buttons * button_size) + - ((num_buttons - 1) * item_spacing) + - (padding * 2); - - ImGui::BeginChild("##ModeGroup", ImVec2(total_width, button_size + padding), - ImGuiChildFlags_AlwaysUseWindowPadding, - ImGuiWindowFlags_NoScrollbar); - + + float total_width = (num_buttons * button_size) + + ((num_buttons - 1) * item_spacing) + (padding * 2); + + ImGui::BeginChild("##ModeGroup", ImVec2(total_width, button_size + padding), + ImGuiChildFlags_AlwaysUseWindowPadding, + ImGuiWindowFlags_NoScrollbar); + // Store for button sizing mode_group_button_size_ = button_size; } @@ -68,22 +66,22 @@ bool Toolset::ModeButton(const char* icon, bool selected, const char* tooltip) { if (selected) { ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor()); } - + // Use smaller buttons that fit the toolbar height float size = mode_group_button_size_ > 0 ? mode_group_button_size_ : 28.0f; bool clicked = ImGui::Button(icon, ImVec2(size, size)); - + if (selected) { ImGui::PopStyleColor(); } - + if (tooltip && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } - + ImGui::SameLine(); button_count_++; - + return clicked; } @@ -101,10 +99,10 @@ void Toolset::AddSeparator() { } void Toolset::AddRomBadge(uint8_t version, std::function on_upgrade) { - RomVersionBadge(version == 0xFF ? "Vanilla" : - absl::StrFormat("v%d", version).c_str(), - version == 0xFF); - + RomVersionBadge( + version == 0xFF ? "Vanilla" : absl::StrFormat("v%d", version).c_str(), + version == 0xFF); + if (on_upgrade && (version == 0xFF || version < 3)) { ImGui::SameLine(0, 2); // Tighter spacing if (ImGui::SmallButton(ICON_MD_UPGRADE)) { @@ -114,38 +112,36 @@ void Toolset::AddRomBadge(uint8_t version, std::function on_upgrade) { ImGui::SetTooltip("Upgrade to ZSCustomOverworld v3"); } } - + ImGui::SameLine(); } -bool Toolset::AddProperty(const char* icon, const char* label, - uint8_t* value, +bool Toolset::AddProperty(const char* icon, const char* label, uint8_t* value, std::function on_change) { ImGui::Text("%s", icon); ImGui::SameLine(); ImGui::SetNextItemWidth(55); - + bool changed = InputHexByte(label, value); if (changed && on_change) { on_change(); } - + ImGui::SameLine(); return changed; } -bool Toolset::AddProperty(const char* icon, const char* label, - uint16_t* value, +bool Toolset::AddProperty(const char* icon, const char* label, uint16_t* value, std::function on_change) { ImGui::Text("%s", icon); ImGui::SameLine(); ImGui::SetNextItemWidth(70); - + bool changed = InputHexWord(label, value); if (changed && on_change) { on_change(); } - + ImGui::SameLine(); return changed; } @@ -153,12 +149,12 @@ bool Toolset::AddProperty(const char* icon, const char* label, bool Toolset::AddCombo(const char* icon, int* current, const char* const items[], int count) { ImGui::Text("%s", icon); - ImGui::SameLine(0, 2); // Reduce spacing between icon and combo + ImGui::SameLine(0, 2); // Reduce spacing between icon and combo ImGui::SetNextItemWidth(100); // Slightly narrower for better fit - + bool changed = ImGui::Combo("##combo", current, items, count); ImGui::SameLine(); - + return changed; } @@ -170,18 +166,18 @@ bool Toolset::AddToggle(const char* icon, bool* state, const char* tooltip) { bool Toolset::AddAction(const char* icon, const char* tooltip) { bool clicked = ImGui::SmallButton(icon); - + // Register for test automation if (ImGui::GetItemID() != 0 && tooltip) { std::string button_path = absl::StrFormat("ToolbarAction:%s", tooltip); - WidgetIdRegistry::Instance().RegisterWidget( - button_path, "button", ImGui::GetItemID(), tooltip); + WidgetIdRegistry::Instance().RegisterWidget(button_path, "button", + ImGui::GetItemID(), tooltip); } - + if (tooltip && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } - + ImGui::SameLine(); return clicked; } @@ -189,7 +185,8 @@ bool Toolset::AddAction(const char* icon, const char* tooltip) { bool Toolset::BeginCollapsibleSection(const char* label, bool* p_open) { ImGui::NewLine(); // Start on new line bool is_open = ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_None); - if (p_open) *p_open = is_open; + if (p_open) + *p_open = is_open; in_section_ = is_open; return is_open; } @@ -198,7 +195,8 @@ void Toolset::EndCollapsibleSection() { in_section_ = false; } -void Toolset::AddV3StatusBadge(uint8_t version, std::function on_settings) { +void Toolset::AddV3StatusBadge(uint8_t version, + std::function on_settings) { if (version >= 3 && version != 0xFF) { StatusBadge("v3 Active", ButtonType::Success); ImGui::SameLine(); @@ -253,86 +251,86 @@ bool EditorCard::Begin(bool* p_open) { imgui_begun_ = false; return false; } - + // Handle icon-collapsed state if (icon_collapsible_ && collapsed_to_icon_) { DrawFloatingIconButton(); imgui_begun_ = false; return false; } - + ImGuiWindowFlags flags = ImGuiWindowFlags_None; - + // Apply headless mode if (headless_) { flags |= ImGuiWindowFlags_NoTitleBar; flags |= ImGuiWindowFlags_NoCollapse; } - + // Control docking if (!docking_allowed_) { flags |= ImGuiWindowFlags_NoDocking; } - + // Set initial position based on position enum if (first_draw_) { float display_width = ImGui::GetIO().DisplaySize.x; float display_height = ImGui::GetIO().DisplaySize.y; - + switch (position_) { case Position::Right: - ImGui::SetNextWindowPos(ImVec2(display_width - default_size_.x - 10, 30), - ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos( + ImVec2(display_width - default_size_.x - 10, 30), + ImGuiCond_FirstUseEver); break; case Position::Left: ImGui::SetNextWindowPos(ImVec2(10, 30), ImGuiCond_FirstUseEver); break; case Position::Bottom: ImGui::SetNextWindowPos( - ImVec2(10, display_height - default_size_.y - 10), - ImGuiCond_FirstUseEver); + ImVec2(10, display_height - default_size_.y - 10), + ImGuiCond_FirstUseEver); break; case Position::Floating: case Position::Free: ImGui::SetNextWindowPos( - ImVec2(display_width * 0.5f - default_size_.x * 0.5f, - display_height * 0.3f), - ImGuiCond_FirstUseEver); + ImVec2(display_width * 0.5f - default_size_.x * 0.5f, + display_height * 0.3f), + ImGuiCond_FirstUseEver); break; } - + ImGui::SetNextWindowSize(default_size_, ImGuiCond_FirstUseEver); first_draw_ = false; } - + // Create window title with icon std::string window_title = icon_.empty() ? title_ : icon_ + " " + title_; - + // Modern card styling ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); ImGui::PushStyleColor(ImGuiCol_TitleBg, GetThemeColor(ImGuiCol_TitleBg)); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, GetAccentColor()); - + // Use p_open parameter if provided, otherwise use stored p_open_ bool* actual_p_open = p_open ? p_open : p_open_; - + // If closable is false, don't pass p_open (removes X button) - bool visible = ImGui::Begin(window_title.c_str(), - closable_ ? actual_p_open : nullptr, - flags); - + bool visible = ImGui::Begin(window_title.c_str(), + closable_ ? actual_p_open : nullptr, flags); + // Mark that ImGui::Begin() was called - End() must always be called now imgui_begun_ = true; - + // Register card window for test automation if (ImGui::GetCurrentWindow() && ImGui::GetCurrentWindow()->ID != 0) { std::string card_path = absl::StrFormat("EditorCard:%s", title_.c_str()); WidgetIdRegistry::Instance().RegisterWidget( - card_path, "window", ImGui::GetCurrentWindow()->ID, + card_path, "window", ImGui::GetCurrentWindow()->ID, absl::StrFormat("Editor card: %s", title_.c_str())); } - + return visible; } @@ -341,7 +339,7 @@ void EditorCard::End() { if (imgui_begun_) { // Check if window was focused this frame focused_ = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows); - + ImGui::End(); ImGui::PopStyleColor(2); ImGui::PopStyleVar(2); @@ -359,26 +357,26 @@ void EditorCard::DrawFloatingIconButton() { // Draw a small floating button with the icon ImGui::SetNextWindowPos(saved_icon_pos_, ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(50, 50)); - - ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoCollapse; - + + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse; + std::string icon_window_name = window_name_ + "##IconCollapsed"; - + if (ImGui::Begin(icon_window_name.c_str(), nullptr, flags)) { // Draw icon button if (ImGui::Button(icon_.c_str(), ImVec2(40, 40))) { collapsed_to_icon_ = false; // Expand back to full window } - + if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Expand %s", title_.c_str()); } - + // Allow dragging the icon - if (ImGui::IsWindowHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + if (ImGui::IsWindowHovered() && + ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; saved_icon_pos_.x += mouse_delta.x; saved_icon_pos_.y += mouse_delta.y; @@ -416,4 +414,3 @@ void EditorLayout::RegisterCard(EditorCard* card) { } // namespace gui } // namespace yaze - diff --git a/src/app/gui/app/editor_layout.h b/src/app/gui/app/editor_layout.h index 27363fe9..83df3104 100644 --- a/src/app/gui/app/editor_layout.h +++ b/src/app/gui/app/editor_layout.h @@ -26,52 +26,53 @@ namespace gui { class Toolset { public: Toolset() = default; - + // Begin the toolbar void Begin(); - + // End the toolbar void End(); - + // Add mode button group void BeginModeGroup(); bool ModeButton(const char* icon, bool selected, const char* tooltip); void EndModeGroup(); - + // Add separator void AddSeparator(); - + // Add ROM version badge void AddRomBadge(uint8_t version, std::function on_upgrade = nullptr); - + // Add quick property (inline hex editing) bool AddProperty(const char* icon, const char* label, uint8_t* value, - std::function on_change = nullptr); + std::function on_change = nullptr); bool AddProperty(const char* icon, const char* label, uint16_t* value, - std::function on_change = nullptr); - + std::function on_change = nullptr); + // Add combo selector - bool AddCombo(const char* icon, int* current, const char* const items[], int count); - + bool AddCombo(const char* icon, int* current, const char* const items[], + int count); + // Add toggle button bool AddToggle(const char* icon, bool* state, const char* tooltip); - + // Add action button bool AddAction(const char* icon, const char* tooltip); - + // Add collapsible settings section bool BeginCollapsibleSection(const char* label, bool* p_open); void EndCollapsibleSection(); - + // Add v3 settings indicator void AddV3StatusBadge(uint8_t version, std::function on_settings); - + // Add usage statistics button bool AddUsageStatsButton(const char* tooltip); - + // Get button count for widget registration int GetButtonCount() const { return button_count_; } - + private: bool in_toolbar_ = false; bool in_section_ = false; @@ -106,16 +107,16 @@ class Toolset { class EditorCard { public: enum class Position { - Free, // Floating window - Right, // Docked to right side - Left, // Docked to left side - Bottom, // Docked to bottom - Floating, // Floating but position saved + Free, // Floating window + Right, // Docked to right side + Left, // Docked to left side + Bottom, // Docked to bottom + Floating, // Floating but position saved }; - + EditorCard(const char* title, const char* icon = nullptr); EditorCard(const char* title, const char* icon, bool* p_open); - + // Set card properties void SetDefaultSize(float width, float height); void SetPosition(Position pos); @@ -124,24 +125,24 @@ class EditorCard { void SetHeadless(bool headless) { headless_ = headless; } void SetDockingAllowed(bool allowed) { docking_allowed_ = allowed; } void SetIconCollapsible(bool collapsible) { icon_collapsible_ = collapsible; } - + // Begin drawing the card bool Begin(bool* p_open = nullptr); - + // End drawing void End(); - + // Minimize/maximize void SetMinimized(bool minimized) { minimized_ = minimized; } bool IsMinimized() const { return minimized_; } - + // Focus the card window (bring to front and set focused) void Focus(); bool IsFocused() const { return focused_; } - + // Get the window name for ImGui operations const char* GetWindowName() const { return window_name_.c_str(); } - + private: std::string title_; std::string icon_; @@ -155,14 +156,14 @@ class EditorCard { bool focused_ = false; bool* p_open_ = nullptr; bool imgui_begun_ = false; // Track if ImGui::Begin() was called - + // UX enhancements - bool headless_ = false; // Minimal chrome, no title bar - bool docking_allowed_ = true; // Allow docking - bool icon_collapsible_ = false; // Can collapse to floating icon - bool collapsed_to_icon_ = false; // Currently collapsed + bool headless_ = false; // Minimal chrome, no title bar + bool docking_allowed_ = true; // Allow docking + bool icon_collapsible_ = false; // Can collapse to floating icon + bool collapsed_to_icon_ = false; // Currently collapsed ImVec2 saved_icon_pos_ = ImVec2(10, 100); // Position when collapsed to icon - + void DrawFloatingIconButton(); }; @@ -180,23 +181,23 @@ class EditorCard { class EditorLayout { public: EditorLayout() = default; - + // Begin the editor layout void Begin(); - + // End the editor layout void End(); - + // Get toolbar reference Toolset& GetToolbar() { return toolbar_; } - + // Begin main canvas area void BeginMainCanvas(); void EndMainCanvas(); - + // Register a card (for layout management) void RegisterCard(EditorCard* card); - + private: Toolset toolbar_; std::vector cards_; @@ -207,4 +208,3 @@ class EditorLayout { } // namespace yaze #endif // YAZE_APP_GUI_EDITOR_LAYOUT_H - diff --git a/src/app/gui/app/feature_flags_menu.h b/src/app/gui/app/feature_flags_menu.h index 38305e13..fc41543e 100644 --- a/src/app/gui/app/feature_flags_menu.h +++ b/src/app/gui/app/feature_flags_menu.h @@ -52,11 +52,13 @@ struct FlagsMenu { void DrawResourceFlags() { Checkbox("Save All Palettes", &core::FeatureFlags::get().kSaveAllPalettes); Checkbox("Save Gfx Groups", &core::FeatureFlags::get().kSaveGfxGroups); - Checkbox("Save Graphics Sheets", &core::FeatureFlags::get().kSaveGraphicsSheet); + Checkbox("Save Graphics Sheets", + &core::FeatureFlags::get().kSaveGraphicsSheet); } void DrawSystemFlags() { - Checkbox("Enable Console Logging", &core::FeatureFlags::get().kLogToConsole); + Checkbox("Enable Console Logging", + &core::FeatureFlags::get().kLogToConsole); Checkbox("Enable Performance Monitoring", &core::FeatureFlags::get().kEnablePerformanceMonitoring); Checkbox("Enable Tiered GFX Architecture", diff --git a/src/app/gui/automation/widget_auto_register.cc b/src/app/gui/automation/widget_auto_register.cc index d1d59c61..29381e77 100644 --- a/src/app/gui/automation/widget_auto_register.cc +++ b/src/app/gui/automation/widget_auto_register.cc @@ -2,9 +2,9 @@ #include -#include "imgui/imgui_internal.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" +#include "imgui/imgui_internal.h" namespace yaze { namespace gui { @@ -49,7 +49,7 @@ void AutoRegisterLastItem(const std::string& widget_type, full_path = absl::StrJoin(g_auto_scope_stack_, "/"); full_path += "/"; } - + // Add widget type and normalized label std::string normalized_label = WidgetIdRegistry::NormalizeLabel(label); full_path += absl::StrCat(widget_type, ":", normalized_label); @@ -57,7 +57,7 @@ void AutoRegisterLastItem(const std::string& widget_type, // Capture metadata from ImGui's last item WidgetIdRegistry::WidgetMetadata metadata; metadata.label = label; - + // Get window name if (ctx->CurrentWindow) { metadata.window_name = std::string(ctx->CurrentWindow->Name); @@ -78,10 +78,9 @@ void AutoRegisterLastItem(const std::string& widget_type, metadata.bounds = bounds; // Register with the global registry - WidgetIdRegistry::Instance().RegisterWidget( - full_path, widget_type, imgui_id, description, metadata); + WidgetIdRegistry::Instance().RegisterWidget(full_path, widget_type, imgui_id, + description, metadata); } } // namespace gui } // namespace yaze - diff --git a/src/app/gui/automation/widget_auto_register.h b/src/app/gui/automation/widget_auto_register.h index 134383bf..0140c540 100644 --- a/src/app/gui/automation/widget_auto_register.h +++ b/src/app/gui/automation/widget_auto_register.h @@ -3,9 +3,9 @@ #include -#include "imgui/imgui.h" -#include "app/gui/automation/widget_id_registry.h" #include "absl/strings/str_cat.h" +#include "app/gui/automation/widget_id_registry.h" +#include "imgui/imgui.h" /** * @file widget_auto_register.h @@ -41,7 +41,7 @@ namespace gui { class AutoWidgetScope { public: explicit AutoWidgetScope(const std::string& name); - ~AutoWidgetScope(); + ~AutoWidgetScope(); // Get current scope path std::string GetPath() const { return scope_.GetFullPath(); } @@ -108,23 +108,26 @@ inline bool AutoInputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr) { - bool changed = ImGui::InputText(label, buf, buf_size, flags, callback, user_data); + bool changed = + ImGui::InputText(label, buf, buf_size, flags, callback, user_data); AutoRegisterLastItem("input", label); return changed; } -inline bool AutoInputTextMultiline(const char* label, char* buf, size_t buf_size, +inline bool AutoInputTextMultiline(const char* label, char* buf, + size_t buf_size, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr) { - bool changed = ImGui::InputTextMultiline(label, buf, buf_size, size, flags, callback, user_data); + bool changed = ImGui::InputTextMultiline(label, buf, buf_size, size, flags, + callback, user_data); AutoRegisterLastItem("textarea", label); return changed; } -inline bool AutoInputInt(const char* label, int* v, int step = 1, int step_fast = 100, - ImGuiInputTextFlags flags = 0) { +inline bool AutoInputInt(const char* label, int* v, int step = 1, + int step_fast = 100, ImGuiInputTextFlags flags = 0) { bool changed = ImGui::InputInt(label, v, step, step_fast, flags); AutoRegisterLastItem("input_int", label); return changed; @@ -139,22 +142,26 @@ inline bool AutoInputFloat(const char* label, float* v, float step = 0.0f, } inline bool AutoSliderInt(const char* label, int* v, int v_min, int v_max, - const char* format = "%d", ImGuiSliderFlags flags = 0) { + const char* format = "%d", + ImGuiSliderFlags flags = 0) { bool changed = ImGui::SliderInt(label, v, v_min, v_max, format, flags); AutoRegisterLastItem("slider", label); return changed; } -inline bool AutoSliderFloat(const char* label, float* v, float v_min, float v_max, - const char* format = "%.3f", ImGuiSliderFlags flags = 0) { +inline bool AutoSliderFloat(const char* label, float* v, float v_min, + float v_max, const char* format = "%.3f", + ImGuiSliderFlags flags = 0) { bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags); AutoRegisterLastItem("slider", label); return changed; } -inline bool AutoCombo(const char* label, int* current_item, const char* const items[], - int items_count, int popup_max_height_in_items = -1) { - bool changed = ImGui::Combo(label, current_item, items, items_count, popup_max_height_in_items); +inline bool AutoCombo(const char* label, int* current_item, + const char* const items[], int items_count, + int popup_max_height_in_items = -1) { + bool changed = ImGui::Combo(label, current_item, items, items_count, + popup_max_height_in_items); AutoRegisterLastItem("combo", label); return changed; } @@ -182,8 +189,8 @@ inline bool AutoMenuItem(const char* label, const char* shortcut = nullptr, return activated; } -inline bool AutoMenuItem(const char* label, const char* shortcut, bool* p_selected, - bool enabled = true) { +inline bool AutoMenuItem(const char* label, const char* shortcut, + bool* p_selected, bool enabled = true) { bool activated = ImGui::MenuItem(label, shortcut, p_selected, enabled); AutoRegisterLastItem("menuitem", label); return activated; @@ -198,7 +205,7 @@ inline bool AutoBeginMenu(const char* label, bool enabled = true) { } inline bool AutoBeginTabItem(const char* label, bool* p_open = nullptr, - ImGuiTabItemFlags flags = 0) { + ImGuiTabItemFlags flags = 0) { bool selected = ImGui::BeginTabItem(label, p_open, flags); if (selected) { AutoRegisterLastItem("tab", label); @@ -222,7 +229,8 @@ inline bool AutoTreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0) { return opened; } -inline bool AutoCollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0) { +inline bool AutoCollapsingHeader(const char* label, + ImGuiTreeNodeFlags flags = 0) { bool opened = ImGui::CollapsingHeader(label, flags); if (opened) { AutoRegisterLastItem("collapsing", label); @@ -243,7 +251,8 @@ inline bool AutoCollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0 * @param canvas_name Name of the canvas (should match BeginChild name) * @param description Optional description of the canvas purpose */ -inline void RegisterCanvas(const char* canvas_name, const std::string& description = "") { +inline void RegisterCanvas(const char* canvas_name, + const std::string& description = "") { AutoRegisterLastItem("canvas", canvas_name, description); } @@ -253,7 +262,8 @@ inline void RegisterCanvas(const char* canvas_name, const std::string& descripti * @param table_name Name of the table (should match BeginTable name) * @param description Optional description */ -inline void RegisterTable(const char* table_name, const std::string& description = "") { +inline void RegisterTable(const char* table_name, + const std::string& description = "") { AutoRegisterLastItem("table", table_name, description); } @@ -261,4 +271,3 @@ inline void RegisterTable(const char* table_name, const std::string& description } // namespace yaze #endif // YAZE_APP_GUI_AUTOMATION_WIDGET_AUTO_REGISTER_H_ - diff --git a/src/app/gui/automation/widget_id_registry.cc b/src/app/gui/automation/widget_id_registry.cc index 9df446a9..44ad4652 100644 --- a/src/app/gui/automation/widget_id_registry.cc +++ b/src/app/gui/automation/widget_id_registry.cc @@ -156,8 +156,7 @@ WidgetIdRegistry::WidgetBounds BoundsFromImGui(const ImRect& rect) { } // namespace void WidgetIdRegistry::RegisterWidget(const std::string& full_path, - const std::string& type, - ImGuiID imgui_id, + const std::string& type, ImGuiID imgui_id, const std::string& description, const WidgetMetadata& metadata) { WidgetInfo& info = widgets_[full_path]; @@ -240,7 +239,8 @@ std::vector WidgetIdRegistry::FindWidgets( } else if (pattern.find('*') != std::string::npos) { // Wildcard pattern - convert to simple substring match for now std::string search = pattern; - search.erase(std::remove(search.begin(), search.end(), '*'), search.end()); + search.erase(std::remove(search.begin(), search.end(), '*'), + search.end()); if (!search.empty() && path.find(search) != std::string::npos) { match = true; } @@ -292,7 +292,8 @@ std::string WidgetIdRegistry::ExportCatalog(const std::string& format) const { bool first = true; for (const auto& [path, info] : widgets_) { - if (!first) ss << ",\n"; + if (!first) + ss << ",\n"; first = false; ss << " {\n"; @@ -307,7 +308,8 @@ std::string WidgetIdRegistry::ExportCatalog(const std::string& format) const { info.enabled ? "true" : "false"); if (info.bounds.valid) { ss << absl::StrFormat( - " \"bounds\": {\"min\": [%0.1f, %0.1f], \"max\": [%0.1f, %0.1f]},\n", + " \"bounds\": {\"min\": [%0.1f, %0.1f], \"max\": [%0.1f, " + "%0.1f]},\n", info.bounds.min_x, info.bounds.min_y, info.bounds.max_x, info.bounds.max_y); } else { @@ -315,15 +317,14 @@ std::string WidgetIdRegistry::ExportCatalog(const std::string& format) const { } ss << absl::StrFormat(" \"last_seen_frame\": %d,\n", info.last_seen_frame); - std::string iso_timestamp = FormatTimestampUTC(info.last_seen_time); - ss << absl::StrFormat(" \"last_seen_at\": \"%s\",\n", - iso_timestamp); + std::string iso_timestamp = FormatTimestampUTC(info.last_seen_time); + ss << absl::StrFormat(" \"last_seen_at\": \"%s\",\n", iso_timestamp); ss << absl::StrFormat(" \"stale\": %s", info.stale_frame_count > 0 ? "true" : "false"); if (!info.description.empty()) { ss << ",\n"; ss << absl::StrFormat(" \"description\": \"%s\"\n", - info.description); + info.description); } else { ss << "\n"; } @@ -342,20 +343,22 @@ std::string WidgetIdRegistry::ExportCatalog(const std::string& format) const { ss << absl::StrFormat(" imgui_id: %u\n", info.imgui_id); ss << absl::StrFormat(" label: \"%s\"\n", info.label); ss << absl::StrFormat(" window: \"%s\"\n", info.window_name); - ss << absl::StrFormat(" visible: %s\n", info.visible ? "true" : "false"); - ss << absl::StrFormat(" enabled: %s\n", info.enabled ? "true" : "false"); + ss << absl::StrFormat(" visible: %s\n", + info.visible ? "true" : "false"); + ss << absl::StrFormat(" enabled: %s\n", + info.enabled ? "true" : "false"); if (info.bounds.valid) { ss << " bounds:\n"; ss << absl::StrFormat(" min: [%0.1f, %0.1f]\n", info.bounds.min_x, - info.bounds.min_y); + info.bounds.min_y); ss << absl::StrFormat(" max: [%0.1f, %0.1f]\n", info.bounds.max_x, - info.bounds.max_y); + info.bounds.max_y); } ss << absl::StrFormat(" last_seen_frame: %d\n", info.last_seen_frame); - std::string iso_timestamp = FormatTimestampUTC(info.last_seen_time); - ss << absl::StrFormat(" last_seen_at: %s\n", iso_timestamp); + std::string iso_timestamp = FormatTimestampUTC(info.last_seen_time); + ss << absl::StrFormat(" last_seen_at: %s\n", iso_timestamp); ss << absl::StrFormat(" stale: %s\n", - info.stale_frame_count > 0 ? "true" : "false"); + info.stale_frame_count > 0 ? "true" : "false"); // Parse hierarchical context from path std::vector segments = absl::StrSplit(path, '/'); diff --git a/src/app/gui/automation/widget_id_registry.h b/src/app/gui/automation/widget_id_registry.h index 2bea3d95..4de62fdc 100644 --- a/src/app/gui/automation/widget_id_registry.h +++ b/src/app/gui/automation/widget_id_registry.h @@ -83,15 +83,15 @@ class WidgetIdRegistry { }; struct WidgetInfo { - std::string full_path; // e.g. "Overworld/Canvas/canvas:Map" - std::string type; // e.g. "button", "input", "canvas", "table" - ImGuiID imgui_id; // ImGui's internal ID - std::string description; // Optional human-readable description - std::string label; // Sanitized display label (without IDs/icons) - std::string window_name; // Window this widget was last seen in - bool visible = true; // Visibility in the most recent frame - bool enabled = true; // Enabled state in the most recent frame - WidgetBounds bounds; // Bounding box in screen space + std::string full_path; // e.g. "Overworld/Canvas/canvas:Map" + std::string type; // e.g. "button", "input", "canvas", "table" + ImGuiID imgui_id; // ImGui's internal ID + std::string description; // Optional human-readable description + std::string label; // Sanitized display label (without IDs/icons) + std::string window_name; // Window this widget was last seen in + bool visible = true; // Visibility in the most recent frame + bool enabled = true; // Enabled state in the most recent frame + WidgetBounds bounds; // Bounding box in screen space int last_seen_frame = -1; absl::Time last_seen_time; bool seen_in_current_frame = false; @@ -107,8 +107,7 @@ class WidgetIdRegistry { // Register a widget for discovery // Should be called after widget is created (when ImGui::GetItemID() is valid) void RegisterWidget(const std::string& full_path, const std::string& type, - ImGuiID imgui_id, - const std::string& description = "", + ImGuiID imgui_id, const std::string& description = "", const WidgetMetadata& metadata = WidgetMetadata()); // Query widgets for test automation @@ -146,28 +145,29 @@ class WidgetIdRegistry { }; // RAII helper macros for convenient scoping -#define YAZE_WIDGET_SCOPE(name) yaze::gui::WidgetIdScope _yaze_scope_##__LINE__(name) +#define YAZE_WIDGET_SCOPE(name) \ + yaze::gui::WidgetIdScope _yaze_scope_##__LINE__(name) // Register a widget after creation (when GetItemID() is valid) -#define YAZE_REGISTER_WIDGET(widget_type, widget_name) \ - do { \ - if (ImGui::GetItemID() != 0) { \ - yaze::gui::WidgetIdRegistry::Instance().RegisterWidget( \ - _yaze_scope_##__LINE__.GetWidgetPath(#widget_type, widget_name), \ - #widget_type, ImGui::GetItemID()); \ - } \ +#define YAZE_REGISTER_WIDGET(widget_type, widget_name) \ + do { \ + if (ImGui::GetItemID() != 0) { \ + yaze::gui::WidgetIdRegistry::Instance().RegisterWidget( \ + _yaze_scope_##__LINE__.GetWidgetPath(#widget_type, widget_name), \ + #widget_type, ImGui::GetItemID()); \ + } \ } while (0) // Convenience macro for registering with automatic name extraction // Usage: YAZE_REGISTER_CURRENT_WIDGET("button") -#define YAZE_REGISTER_CURRENT_WIDGET(widget_type) \ - do { \ - if (ImGui::GetItemID() != 0) { \ - yaze::gui::WidgetIdRegistry::Instance().RegisterWidget( \ - _yaze_scope_##__LINE__.GetWidgetPath(widget_type, \ - ImGui::GetLastItemLabel()), \ - widget_type, ImGui::GetItemID()); \ - } \ +#define YAZE_REGISTER_CURRENT_WIDGET(widget_type) \ + do { \ + if (ImGui::GetItemID() != 0) { \ + yaze::gui::WidgetIdRegistry::Instance().RegisterWidget( \ + _yaze_scope_##__LINE__.GetWidgetPath(widget_type, \ + ImGui::GetLastItemLabel()), \ + widget_type, ImGui::GetItemID()); \ + } \ } while (0) } // namespace gui diff --git a/src/app/gui/automation/widget_measurement.cc b/src/app/gui/automation/widget_measurement.cc index 4aa0c010..16835403 100644 --- a/src/app/gui/automation/widget_measurement.cc +++ b/src/app/gui/automation/widget_measurement.cc @@ -8,7 +8,7 @@ namespace yaze { namespace gui { WidgetMetrics WidgetMeasurement::MeasureLastItem(const std::string& widget_id, - const std::string& type) { + const std::string& type) { if (!enabled_) { return WidgetMetrics{}; } @@ -55,7 +55,8 @@ void WidgetMeasurement::BeginToolbarMeasurement(const std::string& toolbar_id) { } void WidgetMeasurement::EndToolbarMeasurement() { - if (current_toolbar_id_.empty()) return; + if (current_toolbar_id_.empty()) + return; // Calculate total width from cursor movement float end_x = ImGui::GetCursorPosX(); @@ -79,7 +80,7 @@ float WidgetMeasurement::GetToolbarWidth(const std::string& toolbar_id) const { } bool WidgetMeasurement::WouldToolbarOverflow(const std::string& toolbar_id, - float available_width) const { + float available_width) const { float toolbar_width = GetToolbarWidth(toolbar_id); return toolbar_width > available_width; } @@ -104,17 +105,19 @@ std::string WidgetMeasurement::ExportMetricsJSON() const { bool first_toolbar = true; for (const auto& [toolbar_id, metrics] : toolbar_metrics_) { - if (!first_toolbar) json += ",\n"; + if (!first_toolbar) + json += ",\n"; first_toolbar = false; json += absl::StrFormat(" \"%s\": {\n", toolbar_id); json += absl::StrFormat(" \"total_width\": %.1f,\n", - GetToolbarWidth(toolbar_id)); + GetToolbarWidth(toolbar_id)); json += " \"widgets\": [\n"; bool first_widget = true; for (const auto& metric : metrics) { - if (!first_widget) json += ",\n"; + if (!first_widget) + json += ",\n"; first_widget = false; json += " {\n"; @@ -137,4 +140,3 @@ std::string WidgetMeasurement::ExportMetricsJSON() const { } // namespace gui } // namespace yaze - diff --git a/src/app/gui/automation/widget_measurement.h b/src/app/gui/automation/widget_measurement.h index 8ccfec31..887adecd 100644 --- a/src/app/gui/automation/widget_measurement.h +++ b/src/app/gui/automation/widget_measurement.h @@ -20,20 +20,21 @@ namespace gui { * testing of widget sizes and positions. */ struct WidgetMetrics { - ImVec2 size; // Width and height - ImVec2 position; // Screen position - ImVec2 content_size; // Available content region - ImVec2 rect_min; // Bounding box min - ImVec2 rect_max; // Bounding box max - float cursor_pos_x; // Cursor X after rendering - std::string widget_id; // Widget identifier - std::string type; // Widget type (button, input, combo, etc.) - + ImVec2 size; // Width and height + ImVec2 position; // Screen position + ImVec2 content_size; // Available content region + ImVec2 rect_min; // Bounding box min + ImVec2 rect_max; // Bounding box max + float cursor_pos_x; // Cursor X after rendering + std::string widget_id; // Widget identifier + std::string type; // Widget type (button, input, combo, etc.) + std::string ToString() const { return absl::StrFormat( - "Widget '%s' (%s): size=(%.1f,%.1f) pos=(%.1f,%.1f) content=(%.1f,%.1f) cursor_x=%.1f", - widget_id, type, size.x, size.y, position.x, position.y, - content_size.x, content_size.y, cursor_pos_x); + "Widget '%s' (%s): size=(%.1f,%.1f) pos=(%.1f,%.1f) " + "content=(%.1f,%.1f) cursor_x=%.1f", + widget_id, type, size.x, size.y, position.x, position.y, content_size.x, + content_size.y, cursor_pos_x); } }; @@ -116,4 +117,3 @@ class WidgetMeasurement { } // namespace yaze #endif // YAZE_APP_GUI_WIDGET_MEASUREMENT_H - diff --git a/src/app/gui/automation/widget_state_capture.cc b/src/app/gui/automation/widget_state_capture.cc index 3f62f7b3..95e996f9 100644 --- a/src/app/gui/automation/widget_state_capture.cc +++ b/src/app/gui/automation/widget_state_capture.cc @@ -61,7 +61,9 @@ std::string EscapeJsonString(const std::string& value) { return escaped; } -const char* BoolToJson(bool value) { return value ? "true" : "false"; } +const char* BoolToJson(bool value) { + return value ? "true" : "false"; +} std::string FormatFloat(float value) { // Match typical JSON formatting without trailing zeros when possible. @@ -88,7 +90,7 @@ std::string FormatFloatCompact(float value) { std::string CaptureWidgetState() { WidgetState state; - + #if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE // Check if ImGui context is available ImGuiContext* ctx = ImGui::GetCurrentContext(); @@ -97,36 +99,36 @@ std::string CaptureWidgetState() { } ImGuiIO& io = ImGui::GetIO(); - + // Capture frame information state.frame_count = ImGui::GetFrameCount(); state.frame_rate = io.Framerate; - + // Capture focused window ImGuiWindow* current = ImGui::GetCurrentWindow(); if (current && !current->Hidden) { state.focused_window = current->Name; } - + // Capture active widget (focused for input) ImGuiID active_id = ImGui::GetActiveID(); if (active_id != 0) { state.focused_widget = absl::StrFormat("0x%08X", active_id); } - + // Capture hovered widget ImGuiID hovered_id = ImGui::GetHoveredID(); if (hovered_id != 0) { state.hovered_widget = absl::StrFormat("0x%08X", hovered_id); } - + // Traverse visible windows for (ImGuiWindow* window : ctx->Windows) { if (window && window->Active && !window->Hidden) { state.visible_windows.push_back(window->Name); } } - + // Capture open popups for (int i = 0; i < ctx->OpenPopupStack.Size; i++) { ImGuiPopupData& popup = ctx->OpenPopupStack[i]; @@ -134,28 +136,29 @@ std::string CaptureWidgetState() { state.open_popups.push_back(popup.Window->Name); } } - + // Capture navigation state state.nav_id = ctx->NavId; state.nav_active = ctx->NavWindow != nullptr; - + // Capture mouse state for (int i = 0; i < 5; i++) { state.mouse_down[i] = io.MouseDown[i]; } state.mouse_pos_x = io.MousePos.x; state.mouse_pos_y = io.MousePos.y; - + // Capture keyboard modifiers state.ctrl_pressed = io.KeyCtrl; state.shift_pressed = io.KeyShift; state.alt_pressed = io.KeyAlt; - + #else // When UI test engine / ImGui internals aren't available, provide a minimal // payload so downstream systems still receive structured JSON. This keeps // builds that exclude the UI test engine (e.g., Windows release) working. - return "{\"warning\": \"Widget state capture unavailable (UI test engine disabled)\"}"; + return "{\"warning\": \"Widget state capture unavailable (UI test engine " + "disabled)\"}"; #endif return SerializeWidgetStateToJson(state); @@ -172,22 +175,20 @@ std::string SerializeWidgetStateToJson(const WidgetState& state) { j["hovered_widget"] = state.hovered_widget; j["visible_windows"] = state.visible_windows; j["open_popups"] = state.open_popups; - j["navigation"] = { - {"nav_id", absl::StrFormat("0x%08X", state.nav_id)}, - {"nav_active", state.nav_active}}; + j["navigation"] = {{"nav_id", absl::StrFormat("0x%08X", state.nav_id)}, + {"nav_active", state.nav_active}}; nlohmann::json mouse_buttons; for (int i = 0; i < 5; ++i) { mouse_buttons.push_back(state.mouse_down[i]); } - j["input"] = { - {"mouse_buttons", mouse_buttons}, - {"mouse_pos", {state.mouse_pos_x, state.mouse_pos_y}}, - {"modifiers", - {{"ctrl", state.ctrl_pressed}, - {"shift", state.shift_pressed}, - {"alt", state.alt_pressed}}}}; + j["input"] = {{"mouse_buttons", mouse_buttons}, + {"mouse_pos", {state.mouse_pos_x, state.mouse_pos_y}}, + {"modifiers", + {{"ctrl", state.ctrl_pressed}, + {"shift", state.shift_pressed}, + {"alt", state.alt_pressed}}}}; return j.dump(2); #else diff --git a/src/app/gui/automation/widget_state_capture.h b/src/app/gui/automation/widget_state_capture.h index 3a267bcb..20bbc0bb 100644 --- a/src/app/gui/automation/widget_state_capture.h +++ b/src/app/gui/automation/widget_state_capture.h @@ -23,16 +23,16 @@ struct WidgetState { std::vector open_popups; int frame_count = 0; float frame_rate = 0.0f; - + // Navigation state ImGuiID nav_id = 0; bool nav_active = false; - + // Input state bool mouse_down[5] = {false}; float mouse_pos_x = 0.0f; float mouse_pos_y = 0.0f; - + // Keyboard state bool ctrl_pressed = false; bool shift_pressed = false; diff --git a/src/app/gui/canvas/bpp_format_ui.cc b/src/app/gui/canvas/bpp_format_ui.cc index 032c3042..561546bb 100644 --- a/src/app/gui/canvas/bpp_format_ui.cc +++ b/src/app/gui/canvas/bpp_format_ui.cc @@ -3,143 +3,157 @@ #include #include -#include "app/gfx/util/bpp_format_manager.h" #include "app/gfx/core/bitmap.h" +#include "app/gfx/util/bpp_format_manager.h" #include "app/gui/core/ui_helpers.h" #include "imgui/imgui.h" namespace yaze { namespace gui { -BppFormatUI::BppFormatUI(const std::string& id) - : id_(id), selected_format_(gfx::BppFormat::kBpp8), preview_format_(gfx::BppFormat::kBpp8), - show_analysis_(false), show_preview_(false), show_sheet_analysis_(false), - format_changed_(false), last_analysis_sheet_("") { -} +BppFormatUI::BppFormatUI(const std::string& id) + : id_(id), + selected_format_(gfx::BppFormat::kBpp8), + preview_format_(gfx::BppFormat::kBpp8), + show_analysis_(false), + show_preview_(false), + show_sheet_analysis_(false), + format_changed_(false), + last_analysis_sheet_("") {} + +bool BppFormatUI::RenderFormatSelector( + gfx::Bitmap* bitmap, const gfx::SnesPalette& palette, + std::function on_format_changed) { + if (!bitmap) + return false; -bool BppFormatUI::RenderFormatSelector(gfx::Bitmap* bitmap, const gfx::SnesPalette& palette, - std::function on_format_changed) { - if (!bitmap) return false; - format_changed_ = false; - + ImGui::BeginGroup(); ImGui::Text("BPP Format Selection"); ImGui::Separator(); - + // Current format detection gfx::BppFormat current_format = gfx::BppFormatManager::Get().DetectFormat( - bitmap->vector(), bitmap->width(), bitmap->height()); - - ImGui::Text("Current Format: %s", - gfx::BppFormatManager::Get().GetFormatInfo(current_format).name.c_str()); - + bitmap->vector(), bitmap->width(), bitmap->height()); + + ImGui::Text( + "Current Format: %s", + gfx::BppFormatManager::Get().GetFormatInfo(current_format).name.c_str()); + // Format selection ImGui::Text("Target Format:"); ImGui::SameLine(); - + const char* format_names[] = {"2BPP", "3BPP", "4BPP", "8BPP"}; - int current_selection = static_cast(selected_format_) - 2; // Convert to 0-based index - + int current_selection = + static_cast(selected_format_) - 2; // Convert to 0-based index + if (ImGui::Combo("##BppFormat", ¤t_selection, format_names, 4)) { selected_format_ = static_cast(current_selection + 2); format_changed_ = true; } - + // Format information - const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(selected_format_); + const auto& format_info = + gfx::BppFormatManager::Get().GetFormatInfo(selected_format_); ImGui::Text("Max Colors: %d", format_info.max_colors); ImGui::Text("Bytes per Tile: %d", format_info.bytes_per_tile); ImGui::Text("Description: %s", format_info.description.c_str()); - + // Conversion efficiency if (current_format != selected_format_) { int efficiency = GetConversionEfficiency(current_format, selected_format_); ImGui::Text("Conversion Efficiency: %d%%", efficiency); - + ImVec4 efficiency_color; if (efficiency >= 80) { - efficiency_color = GetSuccessColor(); // Green + efficiency_color = GetSuccessColor(); // Green } else if (efficiency >= 60) { - efficiency_color = GetWarningColor(); // Yellow + efficiency_color = GetWarningColor(); // Yellow } else { - efficiency_color = GetErrorColor(); // Red + efficiency_color = GetErrorColor(); // Red } - ImGui::TextColored(efficiency_color, "Quality: %s", - efficiency >= 80 ? "Excellent" : - efficiency >= 60 ? "Good" : "Poor"); + ImGui::TextColored(efficiency_color, "Quality: %s", + efficiency >= 80 ? "Excellent" + : efficiency >= 60 ? "Good" + : "Poor"); } - + // Action buttons ImGui::Separator(); - + if (ImGui::Button("Convert Format")) { if (on_format_changed) { on_format_changed(selected_format_); } format_changed_ = true; } - + ImGui::SameLine(); if (ImGui::Button("Show Analysis")) { show_analysis_ = !show_analysis_; } - + ImGui::SameLine(); if (ImGui::Button("Preview Conversion")) { show_preview_ = !show_preview_; preview_format_ = selected_format_; } - + ImGui::EndGroup(); - + // Analysis panel if (show_analysis_) { RenderAnalysisPanel(*bitmap, palette); } - + // Preview panel if (show_preview_) { RenderConversionPreview(*bitmap, preview_format_, palette); } - + return format_changed_; } -void BppFormatUI::RenderAnalysisPanel(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette) { +void BppFormatUI::RenderAnalysisPanel(const gfx::Bitmap& bitmap, + const gfx::SnesPalette& palette) { ImGui::Begin("BPP Format Analysis", &show_analysis_); - + // Basic analysis gfx::BppFormat detected_format = gfx::BppFormatManager::Get().DetectFormat( - bitmap.vector(), bitmap.width(), bitmap.height()); - - ImGui::Text("Detected Format: %s", - gfx::BppFormatManager::Get().GetFormatInfo(detected_format).name.c_str()); - + bitmap.vector(), bitmap.width(), bitmap.height()); + + ImGui::Text( + "Detected Format: %s", + gfx::BppFormatManager::Get().GetFormatInfo(detected_format).name.c_str()); + // Color usage analysis std::vector color_usage(256, 0); for (uint8_t pixel : bitmap.vector()) { color_usage[pixel]++; } - + int used_colors = 0; for (int count : color_usage) { - if (count > 0) used_colors++; + if (count > 0) + used_colors++; } - - ImGui::Text("Colors Used: %d / %d", used_colors, static_cast(palette.size())); - ImGui::Text("Color Efficiency: %.1f%%", + + ImGui::Text("Colors Used: %d / %d", used_colors, + static_cast(palette.size())); + ImGui::Text("Color Efficiency: %.1f%%", (static_cast(used_colors) / palette.size()) * 100.0f); - + // Color usage chart if (ImGui::CollapsingHeader("Color Usage Chart")) { RenderColorUsageChart(color_usage); } - + // Format recommendations ImGui::Separator(); ImGui::Text("Format Recommendations:"); - + if (used_colors <= 4) { ImGui::TextColored(GetSuccessColor(), "✓ 2BPP format would be optimal"); } else if (used_colors <= 8) { @@ -149,133 +163,155 @@ void BppFormatUI::RenderAnalysisPanel(const gfx::Bitmap& bitmap, const gfx::Snes } else { ImGui::TextColored(GetWarningColor(), "⚠ 8BPP format is necessary"); } - + // Memory usage comparison if (ImGui::CollapsingHeader("Memory Usage Comparison")) { - const auto& current_info = gfx::BppFormatManager::Get().GetFormatInfo(detected_format); - int current_bytes = (bitmap.width() * bitmap.height() * current_info.bits_per_pixel) / 8; - - ImGui::Text("Current Format (%s): %d bytes", current_info.name.c_str(), current_bytes); - + const auto& current_info = + gfx::BppFormatManager::Get().GetFormatInfo(detected_format); + int current_bytes = + (bitmap.width() * bitmap.height() * current_info.bits_per_pixel) / 8; + + ImGui::Text("Current Format (%s): %d bytes", current_info.name.c_str(), + current_bytes); + for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) { - if (format == detected_format) continue; - + if (format == detected_format) + continue; + const auto& info = gfx::BppFormatManager::Get().GetFormatInfo(format); - int format_bytes = (bitmap.width() * bitmap.height() * info.bits_per_pixel) / 8; + int format_bytes = + (bitmap.width() * bitmap.height() * info.bits_per_pixel) / 8; float ratio = static_cast(format_bytes) / current_bytes; - - ImGui::Text("%s: %d bytes (%.1fx)", info.name.c_str(), format_bytes, ratio); + + ImGui::Text("%s: %d bytes (%.1fx)", info.name.c_str(), format_bytes, + ratio); } } - + ImGui::End(); } -void BppFormatUI::RenderConversionPreview(const gfx::Bitmap& bitmap, gfx::BppFormat target_format, - const gfx::SnesPalette& palette) { +void BppFormatUI::RenderConversionPreview(const gfx::Bitmap& bitmap, + gfx::BppFormat target_format, + const gfx::SnesPalette& palette) { ImGui::Begin("BPP Conversion Preview", &show_preview_); - + gfx::BppFormat current_format = gfx::BppFormatManager::Get().DetectFormat( - bitmap.vector(), bitmap.width(), bitmap.height()); - + bitmap.vector(), bitmap.width(), bitmap.height()); + if (current_format == target_format) { ImGui::Text("No conversion needed - formats are identical"); ImGui::End(); return; } - + // Convert the bitmap auto converted_data = gfx::BppFormatManager::Get().ConvertFormat( - bitmap.vector(), current_format, target_format, bitmap.width(), bitmap.height()); - + bitmap.vector(), current_format, target_format, bitmap.width(), + bitmap.height()); + // Create preview bitmap - gfx::Bitmap preview_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(), - converted_data, palette); - + gfx::Bitmap preview_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(), + converted_data, palette); + // Render side-by-side comparison - ImGui::Text("Original (%s) vs Converted (%s)", - gfx::BppFormatManager::Get().GetFormatInfo(current_format).name.c_str(), - gfx::BppFormatManager::Get().GetFormatInfo(target_format).name.c_str()); - + ImGui::Text( + "Original (%s) vs Converted (%s)", + gfx::BppFormatManager::Get().GetFormatInfo(current_format).name.c_str(), + gfx::BppFormatManager::Get().GetFormatInfo(target_format).name.c_str()); + ImGui::Columns(2, "PreviewColumns"); - + // Original ImGui::Text("Original"); if (bitmap.texture()) { - ImGui::Image((ImTextureID)(intptr_t)bitmap.texture(), - ImVec2(256, 256 * bitmap.height() / bitmap.width())); + ImGui::Image((ImTextureID)(intptr_t)bitmap.texture(), + ImVec2(256, 256 * bitmap.height() / bitmap.width())); } - + ImGui::NextColumn(); - + // Converted ImGui::Text("Converted"); if (preview_bitmap.texture()) { - ImGui::Image((ImTextureID)(intptr_t)preview_bitmap.texture(), - ImVec2(256, 256 * preview_bitmap.height() / preview_bitmap.width())); + ImGui::Image( + (ImTextureID)(intptr_t)preview_bitmap.texture(), + ImVec2(256, 256 * preview_bitmap.height() / preview_bitmap.width())); } - + ImGui::Columns(1); - + // Conversion statistics ImGui::Separator(); ImGui::Text("Conversion Statistics:"); - - const auto& from_info = gfx::BppFormatManager::Get().GetFormatInfo(current_format); - const auto& to_info = gfx::BppFormatManager::Get().GetFormatInfo(target_format); - - int from_bytes = (bitmap.width() * bitmap.height() * from_info.bits_per_pixel) / 8; - int to_bytes = (bitmap.width() * bitmap.height() * to_info.bits_per_pixel) / 8; - + + const auto& from_info = + gfx::BppFormatManager::Get().GetFormatInfo(current_format); + const auto& to_info = + gfx::BppFormatManager::Get().GetFormatInfo(target_format); + + int from_bytes = + (bitmap.width() * bitmap.height() * from_info.bits_per_pixel) / 8; + int to_bytes = + (bitmap.width() * bitmap.height() * to_info.bits_per_pixel) / 8; + ImGui::Text("Size: %d bytes -> %d bytes", from_bytes, to_bytes); - ImGui::Text("Compression Ratio: %.2fx", static_cast(from_bytes) / to_bytes); - + ImGui::Text("Compression Ratio: %.2fx", + static_cast(from_bytes) / to_bytes); + ImGui::End(); } -void BppFormatUI::RenderSheetAnalysis(const std::vector& sheet_data, int sheet_id, - const gfx::SnesPalette& palette) { +void BppFormatUI::RenderSheetAnalysis(const std::vector& sheet_data, + int sheet_id, + const gfx::SnesPalette& palette) { std::string analysis_key = "sheet_" + std::to_string(sheet_id); - + // Check if we need to update analysis if (last_analysis_sheet_ != analysis_key) { - auto analysis = gfx::BppFormatManager::Get().AnalyzeGraphicsSheet(sheet_data, sheet_id, palette); + auto analysis = gfx::BppFormatManager::Get().AnalyzeGraphicsSheet( + sheet_data, sheet_id, palette); UpdateAnalysisCache(sheet_id, analysis); last_analysis_sheet_ = analysis_key; } - + auto it = cached_analysis_.find(sheet_id); - if (it == cached_analysis_.end()) return; - + if (it == cached_analysis_.end()) + return; + const auto& analysis = it->second; - + ImGui::Begin("Graphics Sheet Analysis", &show_sheet_analysis_); - + ImGui::Text("Sheet ID: %d", analysis.sheet_id); - ImGui::Text("Original Format: %s", - gfx::BppFormatManager::Get().GetFormatInfo(analysis.original_format).name.c_str()); - ImGui::Text("Current Format: %s", - gfx::BppFormatManager::Get().GetFormatInfo(analysis.current_format).name.c_str()); - + ImGui::Text("Original Format: %s", + gfx::BppFormatManager::Get() + .GetFormatInfo(analysis.original_format) + .name.c_str()); + ImGui::Text("Current Format: %s", gfx::BppFormatManager::Get() + .GetFormatInfo(analysis.current_format) + .name.c_str()); + if (analysis.was_converted) { ImGui::TextColored(GetWarningColor(), "⚠ This sheet was converted"); ImGui::Text("Conversion History: %s", analysis.conversion_history.c_str()); } else { ImGui::TextColored(GetSuccessColor(), "✓ Original format preserved"); } - + ImGui::Separator(); - ImGui::Text("Color Usage: %d / %d colors used", - analysis.palette_entries_used, static_cast(palette.size())); + ImGui::Text("Color Usage: %d / %d colors used", analysis.palette_entries_used, + static_cast(palette.size())); ImGui::Text("Compression Ratio: %.2fx", analysis.compression_ratio); - ImGui::Text("Size: %zu -> %zu bytes", analysis.original_size, analysis.current_size); - + ImGui::Text("Size: %zu -> %zu bytes", analysis.original_size, + analysis.current_size); + // Tile usage pattern if (ImGui::CollapsingHeader("Tile Usage Pattern")) { int total_tiles = analysis.tile_usage_pattern.size(); int used_tiles = 0; int empty_tiles = 0; - + for (int usage : analysis.tile_usage_pattern) { if (usage > 0) { used_tiles++; @@ -283,47 +319,54 @@ void BppFormatUI::RenderSheetAnalysis(const std::vector& sheet_data, in empty_tiles++; } } - + ImGui::Text("Total Tiles: %d", total_tiles); - ImGui::Text("Used Tiles: %d (%.1f%%)", used_tiles, + ImGui::Text("Used Tiles: %d (%.1f%%)", used_tiles, (static_cast(used_tiles) / total_tiles) * 100.0f); - ImGui::Text("Empty Tiles: %d (%.1f%%)", empty_tiles, + ImGui::Text("Empty Tiles: %d (%.1f%%)", empty_tiles, (static_cast(empty_tiles) / total_tiles) * 100.0f); } - + // Recommendations ImGui::Separator(); ImGui::Text("Recommendations:"); - + if (analysis.was_converted && analysis.palette_entries_used <= 16) { - ImGui::TextColored(GetSuccessColor(), - "✓ Consider reverting to %s format for better compression", - gfx::BppFormatManager::Get().GetFormatInfo(analysis.original_format).name.c_str()); + ImGui::TextColored( + GetSuccessColor(), + "✓ Consider reverting to %s format for better compression", + gfx::BppFormatManager::Get() + .GetFormatInfo(analysis.original_format) + .name.c_str()); } - + if (analysis.palette_entries_used < static_cast(palette.size()) / 2) { - ImGui::TextColored(GetWarningColor(), - "⚠ Palette is underutilized - consider optimization"); + ImGui::TextColored(GetWarningColor(), + "⚠ Palette is underutilized - consider optimization"); } - + ImGui::End(); } -bool BppFormatUI::IsConversionAvailable(gfx::BppFormat from_format, gfx::BppFormat to_format) const { +bool BppFormatUI::IsConversionAvailable(gfx::BppFormat from_format, + gfx::BppFormat to_format) const { // All conversions are available in our implementation return from_format != to_format; } -int BppFormatUI::GetConversionEfficiency(gfx::BppFormat from_format, gfx::BppFormat to_format) const { +int BppFormatUI::GetConversionEfficiency(gfx::BppFormat from_format, + gfx::BppFormat to_format) const { // Calculate efficiency based on format compatibility - if (from_format == to_format) return 100; - + if (from_format == to_format) + return 100; + // Higher BPP to lower BPP conversions may lose quality if (static_cast(from_format) > static_cast(to_format)) { int bpp_diff = static_cast(from_format) - static_cast(to_format); - return std::max(20, 100 - (bpp_diff * 20)); // Reduce efficiency by 20% per BPP level + return std::max( + 20, 100 - (bpp_diff * 20)); // Reduce efficiency by 20% per BPP level } - + // Lower BPP to higher BPP conversions are lossless return 100; } @@ -340,15 +383,16 @@ void BppFormatUI::RenderFormatInfo(const gfx::BppFormatInfo& info) { void BppFormatUI::RenderColorUsageChart(const std::vector& color_usage) { // Find maximum usage for scaling int max_usage = *std::max_element(color_usage.begin(), color_usage.end()); - if (max_usage == 0) return; - + if (max_usage == 0) + return; + // Render simple bar chart ImGui::Text("Color Usage Distribution:"); - + for (size_t i = 0; i < std::min(color_usage.size(), size_t(16)); ++i) { if (color_usage[i] > 0) { float usage_ratio = static_cast(color_usage[i]) / max_usage; - ImGui::Text("Color %zu: %d pixels (%.1f%%)", i, color_usage[i], + ImGui::Text("Color %zu: %d pixels (%.1f%%)", i, color_usage[i], (static_cast(color_usage[i]) / (16 * 16)) * 100.0f); ImGui::SameLine(); ImGui::ProgressBar(usage_ratio, ImVec2(100, 0)); @@ -367,27 +411,38 @@ std::string BppFormatUI::GetFormatDescription(gfx::BppFormat format) const { ImVec4 BppFormatUI::GetFormatColor(gfx::BppFormat format) const { switch (format) { - case gfx::BppFormat::kBpp2: return ImVec4(1, 0, 0, 1); // Red - case gfx::BppFormat::kBpp3: return ImVec4(1, 1, 0, 1); // Yellow - case gfx::BppFormat::kBpp4: return ImVec4(0, 1, 0, 1); // Green - case gfx::BppFormat::kBpp8: return ImVec4(0, 0, 1, 1); // Blue - default: return ImVec4(1, 1, 1, 1); // White + case gfx::BppFormat::kBpp2: + return ImVec4(1, 0, 0, 1); // Red + case gfx::BppFormat::kBpp3: + return ImVec4(1, 1, 0, 1); // Yellow + case gfx::BppFormat::kBpp4: + return ImVec4(0, 1, 0, 1); // Green + case gfx::BppFormat::kBpp8: + return ImVec4(0, 0, 1, 1); // Blue + default: + return ImVec4(1, 1, 1, 1); // White } } -void BppFormatUI::UpdateAnalysisCache(int sheet_id, const gfx::GraphicsSheetAnalysis& analysis) { +void BppFormatUI::UpdateAnalysisCache( + int sheet_id, const gfx::GraphicsSheetAnalysis& analysis) { cached_analysis_[sheet_id] = analysis; } // BppConversionDialog implementation -BppConversionDialog::BppConversionDialog(const std::string& id) - : id_(id), is_open_(false), target_format_(gfx::BppFormat::kBpp8), - preserve_palette_(true), preview_valid_(false), show_preview_(true), preview_scale_(1.0f) { -} +BppConversionDialog::BppConversionDialog(const std::string& id) + : id_(id), + is_open_(false), + target_format_(gfx::BppFormat::kBpp8), + preserve_palette_(true), + preview_valid_(false), + show_preview_(true), + preview_scale_(1.0f) {} -void BppConversionDialog::Show(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette, - std::function on_convert) { +void BppConversionDialog::Show( + const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette, + std::function on_convert) { source_bitmap_ = bitmap; source_palette_ = palette; convert_callback_ = on_convert; @@ -396,64 +451,68 @@ void BppConversionDialog::Show(const gfx::Bitmap& bitmap, const gfx::SnesPalette } bool BppConversionDialog::Render() { - if (!is_open_) return false; - + if (!is_open_) + return false; + ImGui::OpenPopup("BPP Format Conversion"); - - if (ImGui::BeginPopupModal("BPP Format Conversion", &is_open_, + + if (ImGui::BeginPopupModal("BPP Format Conversion", &is_open_, ImGuiWindowFlags_AlwaysAutoResize)) { - + RenderFormatSelector(); ImGui::Separator(); RenderOptions(); ImGui::Separator(); - + if (show_preview_) { RenderPreview(); ImGui::Separator(); } - + RenderButtons(); - + ImGui::EndPopup(); } - + return is_open_; } void BppConversionDialog::UpdatePreview() { - if (preview_valid_) return; - + if (preview_valid_) + return; + gfx::BppFormat current_format = gfx::BppFormatManager::Get().DetectFormat( - source_bitmap_.vector(), source_bitmap_.width(), source_bitmap_.height()); - + source_bitmap_.vector(), source_bitmap_.width(), source_bitmap_.height()); + if (current_format == target_format_) { preview_bitmap_ = source_bitmap_; preview_valid_ = true; return; } - + auto converted_data = gfx::BppFormatManager::Get().ConvertFormat( - source_bitmap_.vector(), current_format, target_format_, - source_bitmap_.width(), source_bitmap_.height()); - - preview_bitmap_ = gfx::Bitmap(source_bitmap_.width(), source_bitmap_.height(), - source_bitmap_.depth(), converted_data, source_palette_); + source_bitmap_.vector(), current_format, target_format_, + source_bitmap_.width(), source_bitmap_.height()); + + preview_bitmap_ = + gfx::Bitmap(source_bitmap_.width(), source_bitmap_.height(), + source_bitmap_.depth(), converted_data, source_palette_); preview_valid_ = true; } void BppConversionDialog::RenderFormatSelector() { ImGui::Text("Convert to BPP Format:"); - + const char* format_names[] = {"2BPP", "3BPP", "4BPP", "8BPP"}; int current_selection = static_cast(target_format_) - 2; - + if (ImGui::Combo("##TargetFormat", ¤t_selection, format_names, 4)) { target_format_ = static_cast(current_selection + 2); - preview_valid_ = false; // Invalidate preview + preview_valid_ = false; // Invalidate preview } - - const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(target_format_); + + const auto& format_info = + gfx::BppFormatManager::Get().GetFormatInfo(target_format_); ImGui::Text("Max Colors: %d", format_info.max_colors); ImGui::Text("Description: %s", format_info.description.c_str()); } @@ -462,14 +521,14 @@ void BppConversionDialog::RenderPreview() { if (ImGui::Button("Update Preview")) { preview_valid_ = false; } - + UpdatePreview(); - + if (preview_valid_ && preview_bitmap_.texture()) { ImGui::Text("Preview:"); - ImGui::Image((ImTextureID)(intptr_t)preview_bitmap_.texture(), - ImVec2(128 * preview_scale_, 128 * preview_scale_)); - + ImGui::Image((ImTextureID)(intptr_t)preview_bitmap_.texture(), + ImVec2(128 * preview_scale_, 128 * preview_scale_)); + ImGui::SliderFloat("Scale", &preview_scale_, 0.5f, 3.0f); } } @@ -487,7 +546,7 @@ void BppConversionDialog::RenderButtons() { } is_open_ = false; } - + ImGui::SameLine(); if (ImGui::Button("Cancel")) { is_open_ = false; @@ -496,12 +555,16 @@ void BppConversionDialog::RenderButtons() { // BppComparisonTool implementation -BppComparisonTool::BppComparisonTool(const std::string& id) - : id_(id), is_open_(false), has_source_(false), comparison_scale_(1.0f), - show_metrics_(true), selected_comparison_(gfx::BppFormat::kBpp8) { -} +BppComparisonTool::BppComparisonTool(const std::string& id) + : id_(id), + is_open_(false), + has_source_(false), + comparison_scale_(1.0f), + show_metrics_(true), + selected_comparison_(gfx::BppFormat::kBpp8) {} -void BppComparisonTool::SetSource(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette) { +void BppComparisonTool::SetSource(const gfx::Bitmap& bitmap, + const gfx::SnesPalette& palette) { source_bitmap_ = bitmap; source_palette_ = palette; has_source_ = true; @@ -509,26 +572,27 @@ void BppComparisonTool::SetSource(const gfx::Bitmap& bitmap, const gfx::SnesPale } void BppComparisonTool::Render() { - if (!is_open_ || !has_source_) return; - + if (!is_open_ || !has_source_) + return; + ImGui::Begin("BPP Format Comparison", &is_open_); - + RenderFormatSelector(); ImGui::Separator(); RenderComparisonGrid(); - + if (show_metrics_) { ImGui::Separator(); RenderMetrics(); } - + ImGui::End(); } void BppComparisonTool::GenerateComparisons() { gfx::BppFormat source_format = gfx::BppFormatManager::Get().DetectFormat( - source_bitmap_.vector(), source_bitmap_.width(), source_bitmap_.height()); - + source_bitmap_.vector(), source_bitmap_.width(), source_bitmap_.height()); + for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) { if (format == source_format) { comparison_bitmaps_[format] = source_bitmap_; @@ -536,14 +600,15 @@ void BppComparisonTool::GenerateComparisons() { comparison_valid_[format] = true; continue; } - + try { auto converted_data = gfx::BppFormatManager::Get().ConvertFormat( - source_bitmap_.vector(), source_format, format, - source_bitmap_.width(), source_bitmap_.height()); - - comparison_bitmaps_[format] = gfx::Bitmap(source_bitmap_.width(), source_bitmap_.height(), - source_bitmap_.depth(), converted_data, source_palette_); + source_bitmap_.vector(), source_format, format, + source_bitmap_.width(), source_bitmap_.height()); + + comparison_bitmaps_[format] = + gfx::Bitmap(source_bitmap_.width(), source_bitmap_.height(), + source_bitmap_.depth(), converted_data, source_palette_); comparison_palettes_[format] = source_palette_; comparison_valid_[format] = true; } catch (...) { @@ -555,38 +620,42 @@ void BppComparisonTool::GenerateComparisons() { void BppComparisonTool::RenderComparisonGrid() { ImGui::Text("Format Comparison (Scale: %.1fx)", comparison_scale_); ImGui::SliderFloat("##Scale", &comparison_scale_, 0.5f, 3.0f); - + ImGui::Columns(2, "ComparisonColumns"); - + for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) { auto it = comparison_bitmaps_.find(format); - if (it == comparison_bitmaps_.end() || !comparison_valid_[format]) continue; - + if (it == comparison_bitmaps_.end() || !comparison_valid_[format]) + continue; + const auto& bitmap = it->second; - const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(format); - + const auto& format_info = + gfx::BppFormatManager::Get().GetFormatInfo(format); + ImGui::Text("%s", format_info.name.c_str()); - + if (bitmap.texture()) { - ImGui::Image((ImTextureID)(intptr_t)bitmap.texture(), - ImVec2(128 * comparison_scale_, 128 * comparison_scale_)); + ImGui::Image((ImTextureID)(intptr_t)bitmap.texture(), + ImVec2(128 * comparison_scale_, 128 * comparison_scale_)); } - + ImGui::NextColumn(); } - + ImGui::Columns(1); } void BppComparisonTool::RenderMetrics() { ImGui::Text("Format Metrics:"); - + for (auto format : gfx::BppFormatManager::Get().GetAvailableFormats()) { - if (!comparison_valid_[format]) continue; - - const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(format); + if (!comparison_valid_[format]) + continue; + + const auto& format_info = + gfx::BppFormatManager::Get().GetFormatInfo(format); std::string metrics = CalculateMetrics(format); - + ImGui::Text("%s: %s", format_info.name.c_str(), metrics.c_str()); } } @@ -594,25 +663,27 @@ void BppComparisonTool::RenderMetrics() { void BppComparisonTool::RenderFormatSelector() { ImGui::Text("Selected for Analysis: "); ImGui::SameLine(); - + const char* format_names[] = {"2BPP", "3BPP", "4BPP", "8BPP"}; int selection = static_cast(selected_comparison_) - 2; - + if (ImGui::Combo("##SelectedFormat", &selection, format_names, 4)) { selected_comparison_ = static_cast(selection + 2); } - + ImGui::SameLine(); ImGui::Checkbox("Show Metrics", &show_metrics_); } std::string BppComparisonTool::CalculateMetrics(gfx::BppFormat format) const { const auto& format_info = gfx::BppFormatManager::Get().GetFormatInfo(format); - int bytes = (source_bitmap_.width() * source_bitmap_.height() * format_info.bits_per_pixel) / 8; - + int bytes = (source_bitmap_.width() * source_bitmap_.height() * + format_info.bits_per_pixel) / + 8; + std::ostringstream metrics; metrics << bytes << " bytes, " << format_info.max_colors << " colors"; - + return metrics.str(); } diff --git a/src/app/gui/canvas/bpp_format_ui.h b/src/app/gui/canvas/bpp_format_ui.h index 4a4406eb..52140e13 100644 --- a/src/app/gui/canvas/bpp_format_ui.h +++ b/src/app/gui/canvas/bpp_format_ui.h @@ -1,13 +1,13 @@ #ifndef YAZE_APP_GUI_BPP_FORMAT_UI_H #define YAZE_APP_GUI_BPP_FORMAT_UI_H +#include #include #include -#include -#include "app/gfx/util/bpp_format_manager.h" #include "app/gfx/core/bitmap.h" #include "app/gfx/types/snes_palette.h" +#include "app/gfx/util/bpp_format_manager.h" namespace yaze { namespace gui { @@ -25,7 +25,7 @@ class BppFormatUI { * @param id Unique identifier for this UI component */ explicit BppFormatUI(const std::string& id); - + /** * @brief Render the BPP format selection UI * @param bitmap Current bitmap being edited @@ -33,25 +33,28 @@ class BppFormatUI { * @param on_format_changed Callback when format is changed * @return True if format was changed */ - bool RenderFormatSelector(gfx::Bitmap* bitmap, const gfx::SnesPalette& palette, - std::function on_format_changed); - + bool RenderFormatSelector( + gfx::Bitmap* bitmap, const gfx::SnesPalette& palette, + std::function on_format_changed); + /** * @brief Render format analysis panel * @param bitmap Bitmap to analyze * @param palette Palette to analyze */ - void RenderAnalysisPanel(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette); - + void RenderAnalysisPanel(const gfx::Bitmap& bitmap, + const gfx::SnesPalette& palette); + /** * @brief Render conversion preview * @param bitmap Source bitmap * @param target_format Target BPP format * @param palette Source palette */ - void RenderConversionPreview(const gfx::Bitmap& bitmap, gfx::BppFormat target_format, - const gfx::SnesPalette& palette); - + void RenderConversionPreview(const gfx::Bitmap& bitmap, + gfx::BppFormat target_format, + const gfx::SnesPalette& palette); + /** * @brief Render graphics sheet analysis * @param sheet_data Graphics sheet data @@ -59,35 +62,37 @@ class BppFormatUI { * @param palette Sheet palette */ void RenderSheetAnalysis(const std::vector& sheet_data, int sheet_id, - const gfx::SnesPalette& palette); - + const gfx::SnesPalette& palette); + /** * @brief Get currently selected BPP format * @return Selected BPP format */ gfx::BppFormat GetSelectedFormat() const { return selected_format_; } - + /** * @brief Set the selected BPP format * @param format BPP format to select */ void SetSelectedFormat(gfx::BppFormat format) { selected_format_ = format; } - + /** * @brief Check if format conversion is available * @param from_format Source format * @param to_format Target format * @return True if conversion is available */ - bool IsConversionAvailable(gfx::BppFormat from_format, gfx::BppFormat to_format) const; - + bool IsConversionAvailable(gfx::BppFormat from_format, + gfx::BppFormat to_format) const; + /** * @brief Get conversion efficiency score * @param from_format Source format * @param to_format Target format * @return Efficiency score (0-100) */ - int GetConversionEfficiency(gfx::BppFormat from_format, gfx::BppFormat to_format) const; + int GetConversionEfficiency(gfx::BppFormat from_format, + gfx::BppFormat to_format) const; private: std::string id_; @@ -96,21 +101,22 @@ class BppFormatUI { bool show_analysis_; bool show_preview_; bool show_sheet_analysis_; - + // Analysis cache std::unordered_map cached_analysis_; - + // UI state bool format_changed_; std::string last_analysis_sheet_; - + // Helper methods void RenderFormatInfo(const gfx::BppFormatInfo& info); void RenderColorUsageChart(const std::vector& color_usage); void RenderConversionHistory(const std::string& history); std::string GetFormatDescription(gfx::BppFormat format) const; ImVec4 GetFormatColor(gfx::BppFormat format) const; - void UpdateAnalysisCache(int sheet_id, const gfx::GraphicsSheetAnalysis& analysis); + void UpdateAnalysisCache(int sheet_id, + const gfx::GraphicsSheetAnalysis& analysis); }; /** @@ -123,7 +129,7 @@ class BppConversionDialog { * @param id Unique identifier */ explicit BppConversionDialog(const std::string& id); - + /** * @brief Show the conversion dialog * @param bitmap Bitmap to convert @@ -131,20 +137,20 @@ class BppConversionDialog { * @param on_convert Callback when conversion is confirmed */ void Show(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette, - std::function on_convert); - + std::function on_convert); + /** * @brief Render the dialog * @return True if dialog should remain open */ bool Render(); - + /** * @brief Check if dialog is open * @return True if dialog is open */ bool IsOpen() const { return is_open_; } - + /** * @brief Close the dialog */ @@ -158,16 +164,16 @@ class BppConversionDialog { gfx::BppFormat target_format_; bool preserve_palette_; std::function convert_callback_; - + // Preview data std::vector preview_data_; gfx::Bitmap preview_bitmap_; bool preview_valid_; - + // UI state bool show_preview_; float preview_scale_; - + // Helper methods void UpdatePreview(); void RenderFormatSelector(); @@ -186,30 +192,30 @@ class BppComparisonTool { * @param id Unique identifier */ explicit BppComparisonTool(const std::string& id); - + /** * @brief Set source bitmap for comparison * @param bitmap Source bitmap * @param palette Source palette */ void SetSource(const gfx::Bitmap& bitmap, const gfx::SnesPalette& palette); - + /** * @brief Render the comparison tool */ void Render(); - + /** * @brief Check if tool is open * @return True if tool is open */ bool IsOpen() const { return is_open_; } - + /** * @brief Open the tool */ void Open() { is_open_ = true; } - + /** * @brief Close the tool */ @@ -218,22 +224,22 @@ class BppComparisonTool { private: std::string id_; bool is_open_; - + // Source data gfx::Bitmap source_bitmap_; gfx::SnesPalette source_palette_; bool has_source_; - + // Comparison data std::unordered_map comparison_bitmaps_; std::unordered_map comparison_palettes_; std::unordered_map comparison_valid_; - + // UI state float comparison_scale_; bool show_metrics_; gfx::BppFormat selected_comparison_; - + // Helper methods void GenerateComparisons(); void RenderComparisonGrid(); diff --git a/src/app/gui/canvas/canvas.cc b/src/app/gui/canvas/canvas.cc index d647411a..d6fbfd2e 100644 --- a/src/app/gui/canvas/canvas.cc +++ b/src/app/gui/canvas/canvas.cc @@ -2,24 +2,25 @@ #include #include -#include "app/gfx/util/bpp_format_manager.h" #include "app/gfx/core/bitmap.h" #include "app/gfx/debug/performance/performance_profiler.h" -#include "app/gui/core/style.h" -#include "app/gui/canvas/canvas_utils.h" +#include "app/gfx/util/bpp_format_manager.h" #include "app/gui/canvas/canvas_automation_api.h" +#include "app/gui/canvas/canvas_utils.h" +#include "app/gui/core/style.h" #include "imgui/imgui.h" namespace yaze::gui { - // Define constructors and destructor in .cc to avoid incomplete type issues with unique_ptr // Default constructor -Canvas::Canvas() : renderer_(nullptr) { InitializeDefaults(); } +Canvas::Canvas() : renderer_(nullptr) { + InitializeDefaults(); +} // Legacy constructors (renderer is optional for backward compatibility) -Canvas::Canvas(const std::string& id) +Canvas::Canvas(const std::string& id) : renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") { InitializeDefaults(); } @@ -31,7 +32,8 @@ Canvas::Canvas(const std::string& id, ImVec2 canvas_size) config_.custom_canvas_size = true; } -Canvas::Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size) +Canvas::Canvas(const std::string& id, ImVec2 canvas_size, + CanvasGridSize grid_size) : renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") { InitializeDefaults(); config_.canvas_size = canvas_size; @@ -39,7 +41,8 @@ Canvas::Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_si SetGridSize(grid_size); } -Canvas::Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale) +Canvas::Canvas(const std::string& id, ImVec2 canvas_size, + CanvasGridSize grid_size, float global_scale) : renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") { InitializeDefaults(); config_.canvas_size = canvas_size; @@ -49,21 +52,25 @@ Canvas::Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_si } // New constructors with renderer support (for migration to IRenderer pattern) -Canvas::Canvas(gfx::IRenderer* renderer) : renderer_(renderer) { InitializeDefaults(); } +Canvas::Canvas(gfx::IRenderer* renderer) : renderer_(renderer) { + InitializeDefaults(); +} -Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id) +Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id) : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") { InitializeDefaults(); } -Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size) +Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, + ImVec2 canvas_size) : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") { InitializeDefaults(); config_.canvas_size = canvas_size; config_.custom_canvas_size = true; } -Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size) +Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, + ImVec2 canvas_size, CanvasGridSize grid_size) : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") { InitializeDefaults(); config_.canvas_size = canvas_size; @@ -71,7 +78,8 @@ Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_si SetGridSize(grid_size); } -Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale) +Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, + ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale) : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") { InitializeDefaults(); config_.canvas_size = canvas_size; @@ -176,8 +184,7 @@ void Canvas::InitializeEnhancedComponents() { usage_tracker_->StartSession(); // Initialize performance integration - performance_integration_ = - std::make_shared(); + performance_integration_ = std::make_shared(); performance_integration_->Initialize(canvas_id_); performance_integration_->SetUsageTracker(usage_tracker_); performance_integration_->StartMonitoring(); @@ -256,7 +263,8 @@ void Canvas::ShowColorAnalysis() { bool Canvas::ApplyROMPalette(int group_index, int palette_index) { if (palette_editor_ && bitmap_) { - return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index); + return palette_editor_->ApplyROMPalette(bitmap_, group_index, + palette_index); } return false; } @@ -338,14 +346,15 @@ void Canvas::End() { DrawGrid(); } DrawOverlay(); - + // Render any persistent popups from context menu actions RenderPersistentPopups(); } // ==================== Legacy Interface ==================== -void Canvas::UpdateColorPainter(gfx::IRenderer* /*renderer*/, gfx::Bitmap& bitmap, const ImVec4& color, +void Canvas::UpdateColorPainter(gfx::IRenderer* /*renderer*/, + gfx::Bitmap& bitmap, const ImVec4& color, const std::function& event, int tile_size, float scale) { config_.global_scale = scale; @@ -371,28 +380,27 @@ void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) { void Canvas::DrawBackground(ImVec2 canvas_size) { draw_list_ = GetWindowDrawList(); - + // Phase 1: Calculate geometry using new helper state_.geometry = CalculateCanvasGeometry( - config_, canvas_size, - GetCursorScreenPos(), - GetContentRegionAvail()); - + config_, canvas_size, GetCursorScreenPos(), GetContentRegionAvail()); + // Sync legacy fields for backward compatibility canvas_p0_ = state_.geometry.canvas_p0; canvas_p1_ = state_.geometry.canvas_p1; canvas_sz_ = state_.geometry.canvas_sz; scrolling_ = state_.geometry.scrolling; - + // Update config if explicit size provided if (canvas_size.x != 0) { config_.canvas_size = canvas_size; } - + // Phase 1: Render background using helper RenderCanvasBackground(draw_list_, state_.geometry); - ImGui::InvisibleButton(canvas_id_.c_str(), state_.geometry.scaled_size, kMouseFlags); + ImGui::InvisibleButton(canvas_id_.c_str(), state_.geometry.scaled_size, + kMouseFlags); // CRITICAL FIX: Always update hover mouse position when hovering over canvas // This fixes the regression where CheckForCurrentMap() couldn't track hover @@ -420,7 +428,7 @@ void Canvas::DrawBackground(ImVec2 canvas_size) { IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) { ApplyScrollDelta(state_.geometry, io.MouseDelta); scrolling_ = state_.geometry.scrolling; // Sync legacy field - config_.scrolling = scrolling_; // Sync config + config_.scrolling = scrolling_; // Sync config } } } @@ -553,8 +561,6 @@ void Canvas::DrawContextMenu() { return; } - - // Draw enhanced property dialogs ShowAdvancedCanvasProperties(); ShowScalingControls(); @@ -562,10 +568,11 @@ void Canvas::DrawContextMenu() { void Canvas::DrawContextMenuItem(const gui::CanvasMenuItem& item) { // Phase 4: Use RenderMenuItem from canvas_menu.h for consistent rendering - auto popup_callback = [this](const std::string& id, std::function callback) { + auto popup_callback = [this](const std::string& id, + std::function callback) { popup_registry_.Open(id, callback); }; - + gui::RenderMenuItem(item, popup_callback); } @@ -578,7 +585,7 @@ void Canvas::AddContextMenuItem(const gui::CanvasMenuItem& item) { section.separator_after = true; editor_menu_.sections.push_back(section); } - + // Add to the last section (or create new if the last isn't editor-specific) auto& last_section = editor_menu_.sections.back(); if (last_section.priority != MenuSectionPriority::kEditorSpecific) { @@ -596,8 +603,8 @@ void Canvas::ClearContextMenuItems() { editor_menu_.sections.clear(); } -void Canvas::OpenPersistentPopup(const std::string& popup_id, - std::function render_callback) { +void Canvas::OpenPersistentPopup(const std::string& popup_id, + std::function render_callback) { // Phase 4: Simplified popup management (no legacy synchronization) popup_registry_.Open(popup_id, render_callback); } @@ -943,8 +950,8 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { ImVec2 drag_end_pos = AlignPosToGrid(mouse_pos, scaled_size); if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) { // FIX: Origin used to be canvas_p0_, revert if there is regression. - auto start = ImVec2(origin.x + drag_start_pos.x, - origin.y + drag_start_pos.y); + auto start = + ImVec2(origin.x + drag_start_pos.x, origin.y + drag_start_pos.y); auto end = ImVec2(origin.x + drag_end_pos.x + tile_size, origin.y + drag_end_pos.y + tile_size); draw_list_->AddRect(start, end, kWhiteColor); @@ -1011,7 +1018,8 @@ void Canvas::DrawBitmap(Bitmap& bitmap, int border_offset, float scale) { config_.content_size = ImVec2(bitmap.width(), bitmap.height()); // Phase 1: Use rendering helper - RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, border_offset, scale); + RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, border_offset, + scale); } void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale, @@ -1026,7 +1034,8 @@ void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale, config_.content_size = ImVec2(bitmap.width(), bitmap.height()); // Phase 1: Use rendering helper - RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, x_offset, y_offset, scale, alpha); + RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, x_offset, y_offset, + scale, alpha); } void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size, @@ -1040,7 +1049,8 @@ void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size, config_.content_size = ImVec2(bitmap.width(), bitmap.height()); // Phase 1: Use rendering helper - RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, dest_pos, dest_size, src_pos, src_size); + RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, dest_pos, dest_size, + src_pos, src_size); } // TODO: Add parameters for sizing and positioning @@ -1342,7 +1352,7 @@ void Canvas::DrawOverlay() { // Use high-level utility function with local points (synchronized from interaction handler) CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_); - + // Render any persistent popups from context menu actions RenderPersistentPopups(); } @@ -1529,11 +1539,10 @@ void Canvas::ShowAdvancedCanvasProperties() { enable_hex_tile_labels_ = updated_config.enable_hex_labels; enable_custom_labels_ = updated_config.enable_custom_labels; }; - modal_config.on_scale_changed = - [this](const CanvasConfig& updated_config) { - global_scale_ = updated_config.global_scale; - scrolling_ = updated_config.scrolling; - }; + modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) { + global_scale_ = updated_config.global_scale; + scrolling_ = updated_config.scrolling; + }; modals_->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_); return; @@ -1653,13 +1662,12 @@ void Canvas::ShowScalingControls() { enable_custom_labels_ = updated_config.enable_custom_labels; enable_context_menu_ = updated_config.enable_context_menu; }; - modal_config.on_scale_changed = - [this](const CanvasConfig& updated_config) { - draggable_ = updated_config.is_draggable; - custom_step_ = updated_config.grid_step; - global_scale_ = updated_config.global_scale; - scrolling_ = updated_config.scrolling; - }; + modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) { + draggable_ = updated_config.is_draggable; + custom_step_ = updated_config.grid_step; + global_scale_ = updated_config.global_scale; + scrolling_ = updated_config.scrolling; + }; modals_->ShowScalingControls(canvas_id_, modal_config); return; diff --git a/src/app/gui/canvas/canvas.h b/src/app/gui/canvas/canvas.h index ca6fa9bb..235bcc92 100644 --- a/src/app/gui/canvas/canvas.h +++ b/src/app/gui/canvas/canvas.h @@ -5,26 +5,26 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include -#include #include #include +#include #include "app/gfx/core/bitmap.h" -#include "app/rom.h" -#include "app/gui/canvas/canvas_utils.h" -#include "app/gui/canvas/canvas_state.h" -#include "app/gui/canvas/canvas_geometry.h" -#include "app/gui/canvas/canvas_rendering.h" -#include "app/gui/widgets/palette_editor_widget.h" #include "app/gfx/util/bpp_format_manager.h" #include "app/gui/canvas/bpp_format_ui.h" -#include "app/gui/canvas/canvas_modals.h" #include "app/gui/canvas/canvas_context_menu.h" -#include "app/gui/canvas/canvas_usage_tracker.h" -#include "app/gui/canvas/canvas_performance_integration.h" +#include "app/gui/canvas/canvas_geometry.h" #include "app/gui/canvas/canvas_interaction_handler.h" #include "app/gui/canvas/canvas_menu.h" +#include "app/gui/canvas/canvas_modals.h" +#include "app/gui/canvas/canvas_performance_integration.h" #include "app/gui/canvas/canvas_popup.h" +#include "app/gui/canvas/canvas_rendering.h" +#include "app/gui/canvas/canvas_state.h" +#include "app/gui/canvas/canvas_usage_tracker.h" +#include "app/gui/canvas/canvas_utils.h" +#include "app/gui/widgets/palette_editor_widget.h" +#include "app/rom.h" #include "imgui/imgui.h" namespace yaze { @@ -61,20 +61,26 @@ class Canvas { // Default constructor Canvas(); ~Canvas(); - + // Legacy constructors (renderer is optional for backward compatibility) explicit Canvas(const std::string& id); explicit Canvas(const std::string& id, ImVec2 canvas_size); - explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size); - explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale); - + explicit Canvas(const std::string& id, ImVec2 canvas_size, + CanvasGridSize grid_size); + explicit Canvas(const std::string& id, ImVec2 canvas_size, + CanvasGridSize grid_size, float global_scale); + // New constructors with renderer support (for migration to IRenderer pattern) explicit Canvas(gfx::IRenderer* renderer); explicit Canvas(gfx::IRenderer* renderer, const std::string& id); - explicit Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size); - explicit Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size); - explicit Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale); - + explicit Canvas(gfx::IRenderer* renderer, const std::string& id, + ImVec2 canvas_size); + explicit Canvas(gfx::IRenderer* renderer, const std::string& id, + ImVec2 canvas_size, CanvasGridSize grid_size); + explicit Canvas(gfx::IRenderer* renderer, const std::string& id, + ImVec2 canvas_size, CanvasGridSize grid_size, + float global_scale); + // Set renderer after construction (for late initialization) void SetRenderer(gfx::IRenderer* renderer) { renderer_ = renderer; } gfx::IRenderer* renderer() const { return renderer_; } @@ -98,24 +104,29 @@ class Canvas { // Legacy compatibility void SetCanvasGridSize(CanvasGridSize grid_size) { SetGridSize(grid_size); } - + CanvasGridSize grid_size() const { - if (config_.grid_step == 8.0f) return CanvasGridSize::k8x8; - if (config_.grid_step == 16.0f) return CanvasGridSize::k16x16; - if (config_.grid_step == 32.0f) return CanvasGridSize::k32x32; - if (config_.grid_step == 64.0f) return CanvasGridSize::k64x64; + if (config_.grid_step == 8.0f) + return CanvasGridSize::k8x8; + if (config_.grid_step == 16.0f) + return CanvasGridSize::k16x16; + if (config_.grid_step == 32.0f) + return CanvasGridSize::k32x32; + if (config_.grid_step == 64.0f) + return CanvasGridSize::k64x64; return CanvasGridSize::k16x16; // Default } - void UpdateColorPainter(gfx::IRenderer* renderer, gfx::Bitmap &bitmap, const ImVec4 &color, - const std::function &event, int tile_size, + void UpdateColorPainter(gfx::IRenderer* renderer, gfx::Bitmap& bitmap, + const ImVec4& color, + const std::function& event, int tile_size, float scale = 1.0f); void UpdateInfoGrid(ImVec2 bg_size, float grid_size = 64.0f, int label_id = 0); // ==================== Modern ImGui-Style Interface ==================== - + /** * @brief Begin canvas rendering (ImGui-style) * @@ -134,7 +145,7 @@ class Canvas { * ``` */ void Begin(ImVec2 canvas_size = ImVec2(0, 0)); - + /** * @brief End canvas rendering (ImGui-style) * @@ -142,7 +153,7 @@ class Canvas { * Automatically draws grid and overlay if enabled. */ void End(); - + // ==================== Legacy Interface (Backward Compatible) ==================== // Background for the Canvas represents region without any content drawn to @@ -152,39 +163,40 @@ class Canvas { // Context Menu refers to what happens when the right mouse button is pressed // This routine also handles the scrolling for the canvas. void DrawContextMenu(); - + // Phase 4: Use unified menu item definition from canvas_menu.h using CanvasMenuItem = gui::CanvasMenuItem; - + // BPP format UI components std::unique_ptr bpp_format_ui_; std::unique_ptr bpp_conversion_dialog_; std::unique_ptr bpp_comparison_tool_; - + // Enhanced canvas components std::unique_ptr modals_; std::unique_ptr context_menu_; std::shared_ptr usage_tracker_; std::shared_ptr performance_integration_; CanvasInteractionHandler interaction_handler_; - + void AddContextMenuItem(const gui::CanvasMenuItem& item); void ClearContextMenuItems(); - + // Phase 4: Access to editor-provided menu definition CanvasMenuDefinition& editor_menu() { return editor_menu_; } const CanvasMenuDefinition& editor_menu() const { return editor_menu_; } void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; } - + // Persistent popup management for context menu actions - void OpenPersistentPopup(const std::string& popup_id, std::function render_callback); + void OpenPersistentPopup(const std::string& popup_id, + std::function render_callback); void ClosePersistentPopup(const std::string& popup_id); void RenderPersistentPopups(); - + // Popup registry access (Phase 3: for advanced users and testing) PopupRegistry& GetPopupRegistry() { return popup_registry_; } const PopupRegistry& GetPopupRegistry() const { return popup_registry_; } - + // Enhanced view and edit operations void ShowAdvancedCanvasProperties(); void ShowScalingControls(); @@ -192,69 +204,73 @@ class Canvas { void ResetView(); void ApplyConfigSnapshot(const CanvasConfig& snapshot); void ApplyScaleSnapshot(const CanvasConfig& snapshot); - + // Modular component access CanvasConfig& GetConfig() { return config_; } const CanvasConfig& GetConfig() const { return config_; } CanvasSelection& GetSelection() { return selection_; } const CanvasSelection& GetSelection() const { return selection_; } - + // Enhanced palette management void InitializePaletteEditor(Rom* rom); void ShowPaletteEditor(); void ShowColorAnalysis(); bool ApplyROMPalette(int group_index, int palette_index); - + // BPP format management void ShowBppFormatSelector(); void ShowBppAnalysis(); void ShowBppConversionDialog(); bool ConvertBitmapFormat(gfx::BppFormat target_format); gfx::BppFormat GetCurrentBppFormat() const; - + // Enhanced canvas management void InitializeEnhancedComponents(); void SetUsageMode(CanvasUsage usage); auto usage_mode() const { return config_.usage_mode; } - + void RecordCanvasOperation(const std::string& operation_name, double time_ms); void ShowPerformanceUI(); void ShowUsageReport(); - + // Interaction handler access - CanvasInteractionHandler& GetInteractionHandler() { return interaction_handler_; } - const CanvasInteractionHandler& GetInteractionHandler() const { return interaction_handler_; } - + CanvasInteractionHandler& GetInteractionHandler() { + return interaction_handler_; + } + const CanvasInteractionHandler& GetInteractionHandler() const { + return interaction_handler_; + } + // Automation API access (Phase 4A) CanvasAutomationAPI* GetAutomationAPI(); - + // Initialization and cleanup void InitializeDefaults(); void Cleanup(); - + // Size reporting for ImGui table integration ImVec2 GetMinimumSize() const; ImVec2 GetPreferredSize() const; ImVec2 GetCurrentSize() const { return config_.canvas_size; } void SetAutoResize(bool auto_resize) { config_.auto_resize = auto_resize; } bool IsAutoResize() const { return config_.auto_resize; } - + // Table integration helpers void ReserveTableSpace(const std::string& label = ""); bool BeginTableCanvas(const std::string& label = ""); void EndTableCanvas(); - + // Improved interaction detection bool HasValidSelection() const; bool WasClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const; bool WasDoubleClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const; ImVec2 GetLastClickPosition() const; - + // Tile painter methods - bool DrawTilePainter(const Bitmap &bitmap, int size, float scale = 1.0f); - bool DrawSolidTilePainter(const ImVec4 &color, int size); - void DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color); - + bool DrawTilePainter(const Bitmap& bitmap, int size, float scale = 1.0f); + bool DrawSolidTilePainter(const ImVec4& color, int size); + void DrawTileOnBitmap(int tile_size, gfx::Bitmap* bitmap, ImVec4 color); + void DrawOutline(int x, int y, int w, int h); void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color); void DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color); @@ -298,29 +314,35 @@ class Canvas { auto canvas_size() const { return canvas_sz_; } void set_global_scale(float scale) { global_scale_ = scale; } void set_draggable(bool draggable) { draggable_ = draggable; } - + // Modern accessors using modular structure bool IsSelectRectActive() const { return select_rect_active_; } - const std::vector& GetSelectedTiles() const { return selected_tiles_; } + const std::vector& GetSelectedTiles() const { + return selected_tiles_; + } ImVec2 GetSelectedTilePos() const { return selected_tile_pos_; } void SetSelectedTilePos(ImVec2 pos) { selected_tile_pos_ = pos; } - - // Configuration accessors - void SetCanvasSize(ImVec2 canvas_size) { - config_.canvas_size = canvas_size; - config_.custom_canvas_size = true; + + // Configuration accessors + void SetCanvasSize(ImVec2 canvas_size) { + config_.canvas_size = canvas_size; + config_.custom_canvas_size = true; } float GetGlobalScale() const { return config_.global_scale; } void SetGlobalScale(float scale) { config_.global_scale = scale; } bool* GetCustomLabelsEnabled() { return &config_.enable_custom_labels; } float GetGridStep() const { return config_.grid_step; } - + // Rectangle selection boundary control (prevents wrapping in large maps) - void SetClampRectToLocalMaps(bool clamp) { config_.clamp_rect_to_local_maps = clamp; } - bool GetClampRectToLocalMaps() const { return config_.clamp_rect_to_local_maps; } + void SetClampRectToLocalMaps(bool clamp) { + config_.clamp_rect_to_local_maps = clamp; + } + bool GetClampRectToLocalMaps() const { + return config_.clamp_rect_to_local_maps; + } float GetCanvasWidth() const { return config_.canvas_size.x; } float GetCanvasHeight() const { return config_.canvas_size.y; } - + // Legacy compatibility accessors auto select_rect_active() const { return select_rect_active_; } auto selected_tiles() const { return selected_tiles_; } @@ -331,15 +353,17 @@ class Canvas { auto custom_step() const { return config_.grid_step; } auto width() const { return config_.canvas_size.x; } auto height() const { return config_.canvas_size.y; } - + // Public accessors for methods that need to be accessed externally auto canvas_id() const { return canvas_id_; } - + // Public methods for drawing operations - void DrawBitmap(Bitmap &bitmap, int border_offset, float scale); - void DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale = 1.0f, int alpha = 255); - void DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size); - void DrawBitmapTable(const BitmapTable &gfx_bin); + void DrawBitmap(Bitmap& bitmap, int border_offset, float scale); + void DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, + float scale = 1.0f, int alpha = 255); + void DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size, + ImVec2 src_pos, ImVec2 src_size); + void DrawBitmapTable(const BitmapTable& gfx_bin); /** * @brief Draw group of bitmaps for multi-tile selection preview * @param group Vector of tile IDs to draw @@ -349,12 +373,13 @@ class Canvas { * @param local_map_size Size of local map in pixels (default 512 for standard maps) * @param total_map_size Total map size for boundary clamping (default 4096x4096) */ - void DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, - int tile_size, float scale = 1.0f, - int local_map_size = 0x200, - ImVec2 total_map_size = ImVec2(0x1000, 0x1000)); - bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile); - void DrawSelectRect(int current_map, int tile_size = 0x10, float scale = 1.0f); + void DrawBitmapGroup(std::vector& group, gfx::Tilemap& tilemap, + int tile_size, float scale = 1.0f, + int local_map_size = 0x200, + ImVec2 total_map_size = ImVec2(0x1000, 0x1000)); + bool DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile); + void DrawSelectRect(int current_map, int tile_size = 0x10, + float scale = 1.0f); bool DrawTileSelector(int size, int size_y = 0); void DrawGrid(float grid_step = 64.0f, int tile_id_offset = 8); void DrawOverlay(); @@ -385,8 +410,8 @@ class Canvas { auto hover_mouse_pos() const { return mouse_pos_in_canvas_; } - void set_rom(Rom *rom) { rom_ = rom; } - Rom *rom() const { return rom_; } + void set_rom(Rom* rom) { rom_ = rom; } + Rom* rom() const { return rom_; } private: void DrawContextMenuItem(const gui::CanvasMenuItem& item); @@ -396,13 +421,13 @@ class Canvas { CanvasConfig config_; CanvasSelection selection_; std::unique_ptr palette_editor_; - + // Phase 1: Consolidated state (gradually replacing scattered members) CanvasState state_; - + // Automation API (lazy-initialized on first access) std::unique_ptr automation_api_; - + // Core canvas state bool is_hovered_ = false; bool refresh_graphics_ = false; @@ -410,7 +435,7 @@ class Canvas { // Phase 4: Context menu system (declarative menu definition) CanvasMenuDefinition editor_menu_; bool context_menu_enabled_ = true; - + // Phase 4: Persistent popup state for context menu actions (unified registry) PopupRegistry popup_registry_; @@ -422,9 +447,9 @@ class Canvas { uint64_t edit_palette_sub_index_ = 0; // Core canvas state - Bitmap *bitmap_ = nullptr; - Rom *rom_ = nullptr; - ImDrawList *draw_list_ = nullptr; + Bitmap* bitmap_ = nullptr; + Rom* rom_ = nullptr; + ImDrawList* draw_list_ = nullptr; // Canvas geometry and interaction state ImVec2 scrolling_; @@ -442,7 +467,7 @@ class Canvas { // Identification std::string canvas_id_ = "Canvas"; std::string context_id_ = "CanvasContext"; - + // Legacy compatibility (gradually being replaced by selection_) std::vector selected_tiles_; ImVector selected_points_; @@ -458,20 +483,21 @@ class Canvas { bool draggable_ = false; }; -void BeginCanvas(Canvas &canvas, ImVec2 child_size = ImVec2(0, 0)); -void EndCanvas(Canvas &canvas); +void BeginCanvas(Canvas& canvas, ImVec2 child_size = ImVec2(0, 0)); +void EndCanvas(Canvas& canvas); void GraphicsBinCanvasPipeline(int width, int height, int tile_size, int num_sheets_to_load, int canvas_id, - bool is_loaded, BitmapTable &graphics_bin); + bool is_loaded, BitmapTable& graphics_bin); -void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, +void BitmapCanvasPipeline(gui::Canvas& canvas, gfx::Bitmap& bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id); // Table-optimized canvas pipeline with automatic sizing -void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, - const std::string& label = "", bool auto_resize = true); +void TableCanvasPipeline(gui::Canvas& canvas, gfx::Bitmap& bitmap, + const std::string& label = "", + bool auto_resize = true); /** * @class ScopedCanvas @@ -505,27 +531,31 @@ class ScopedCanvas { /** * @brief Construct and begin a new canvas (legacy constructor without renderer) */ - explicit ScopedCanvas(const std::string& id, ImVec2 canvas_size = ImVec2(0, 0)) + explicit ScopedCanvas(const std::string& id, + ImVec2 canvas_size = ImVec2(0, 0)) : canvas_(new Canvas(id, canvas_size)), owned_(true), active_(true) { canvas_->Begin(); } - + /** * @brief Construct and begin a new canvas (with optional renderer) */ - explicit ScopedCanvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_size = ImVec2(0, 0)) - : canvas_(new Canvas(renderer, id, canvas_size)), owned_(true), active_(true) { + explicit ScopedCanvas(gfx::IRenderer* renderer, const std::string& id, + ImVec2 canvas_size = ImVec2(0, 0)) + : canvas_(new Canvas(renderer, id, canvas_size)), + owned_(true), + active_(true) { canvas_->Begin(); } - + /** * @brief Wrap existing canvas with RAII */ - explicit ScopedCanvas(Canvas& canvas) + explicit ScopedCanvas(Canvas& canvas) : canvas_(&canvas), owned_(false), active_(true) { canvas_->Begin(); } - + /** * @brief Destructor automatically calls End() */ @@ -537,35 +567,35 @@ class ScopedCanvas { delete canvas_; } } - + // No copy, move only ScopedCanvas(const ScopedCanvas&) = delete; ScopedCanvas& operator=(const ScopedCanvas&) = delete; - - ScopedCanvas(ScopedCanvas&& other) noexcept + + ScopedCanvas(ScopedCanvas&& other) noexcept : canvas_(other.canvas_), owned_(other.owned_), active_(other.active_) { other.active_ = false; other.canvas_ = nullptr; } - + /** * @brief Arrow operator for clean syntax: scoped->DrawBitmap(...) */ Canvas* operator->() { return canvas_; } const Canvas* operator->() const { return canvas_; } - + /** * @brief Dereference operator for direct access: (*scoped).DrawBitmap(...) */ Canvas& operator*() { return *canvas_; } const Canvas& operator*() const { return *canvas_; } - + /** * @brief Get underlying canvas */ Canvas* get() { return canvas_; } const Canvas* get() const { return canvas_; } - + private: Canvas* canvas_; bool owned_; diff --git a/src/app/gui/canvas/canvas_automation_api.cc b/src/app/gui/canvas/canvas_automation_api.cc index 61ff66d8..319bcbba 100644 --- a/src/app/gui/canvas/canvas_automation_api.cc +++ b/src/app/gui/canvas/canvas_automation_api.cc @@ -71,8 +71,10 @@ void CanvasAutomationAPI::SelectTile(int x, int y) { void CanvasAutomationAPI::SelectTileRect(int x1, int y1, int x2, int y2) { // Ensure x1 <= x2 and y1 <= y2 - if (x1 > x2) std::swap(x1, x2); - if (y1 > y2) std::swap(y1, y2); + if (x1 > x2) + std::swap(x1, x2); + if (y1 > y2) + std::swap(y1, y2); if (!IsInBounds(x1, y1) || !IsInBounds(x2, y2)) { return; @@ -100,20 +102,20 @@ CanvasAutomationAPI::SelectionState CanvasAutomationAPI::GetSelection() const { ImVec2 tile_end = CanvasToTile(state.selection_end); // Ensure proper ordering - int min_x = std::min(static_cast(tile_start.x), - static_cast(tile_end.x)); - int max_x = std::max(static_cast(tile_start.x), - static_cast(tile_end.x)); - int min_y = std::min(static_cast(tile_start.y), - static_cast(tile_end.y)); - int max_y = std::max(static_cast(tile_start.y), - static_cast(tile_end.y)); + int min_x = + std::min(static_cast(tile_start.x), static_cast(tile_end.x)); + int max_x = + std::max(static_cast(tile_start.x), static_cast(tile_end.x)); + int min_y = + std::min(static_cast(tile_start.y), static_cast(tile_end.y)); + int max_y = + std::max(static_cast(tile_start.y), static_cast(tile_end.y)); // Generate all tiles in selection rectangle for (int y = min_y; y <= max_y; ++y) { for (int x = min_x; x <= max_x; ++x) { - state.selected_tiles.push_back(ImVec2(static_cast(x), - static_cast(y))); + state.selected_tiles.push_back( + ImVec2(static_cast(x), static_cast(y))); } } } @@ -146,7 +148,7 @@ void CanvasAutomationAPI::ScrollToTile(int x, int y, bool center) { // Scroll to make tile visible ImVec2 tile_canvas_pos = TileToCanvas(x, y); - + // Get current scroll and canvas size ImVec2 current_scroll = canvas_->scrolling(); ImVec2 canvas_size = canvas_->canvas_size(); @@ -189,11 +191,11 @@ float CanvasAutomationAPI::GetZoom() const { CanvasAutomationAPI::Dimensions CanvasAutomationAPI::GetDimensions() const { Dimensions dims; - + // Get canvas size in pixels ImVec2 canvas_size = canvas_->canvas_size(); float scale = canvas_->global_scale(); - + // Determine tile size from canvas grid size int tile_size = 16; // Default switch (canvas_->grid_size()) { @@ -210,36 +212,39 @@ CanvasAutomationAPI::Dimensions CanvasAutomationAPI::GetDimensions() const { tile_size = 64; break; } - + dims.tile_size = tile_size; dims.width_tiles = static_cast(canvas_size.x / (tile_size * scale)); dims.height_tiles = static_cast(canvas_size.y / (tile_size * scale)); - + return dims; } -CanvasAutomationAPI::VisibleRegion CanvasAutomationAPI::GetVisibleRegion() const { +CanvasAutomationAPI::VisibleRegion CanvasAutomationAPI::GetVisibleRegion() + const { VisibleRegion region; - + ImVec2 scroll = canvas_->scrolling(); ImVec2 canvas_size = canvas_->canvas_size(); float scale = canvas_->global_scale(); int tile_size = GetDimensions().tile_size; - + // Top-left corner of visible region ImVec2 top_left = CanvasToTile(ImVec2(-scroll.x, -scroll.y)); - + // Bottom-right corner of visible region - ImVec2 bottom_right = CanvasToTile(ImVec2(-scroll.x + canvas_size.x, - -scroll.y + canvas_size.y)); - + ImVec2 bottom_right = CanvasToTile( + ImVec2(-scroll.x + canvas_size.x, -scroll.y + canvas_size.y)); + region.min_x = std::max(0, static_cast(top_left.x)); region.min_y = std::max(0, static_cast(top_left.y)); - + Dimensions dims = GetDimensions(); - region.max_x = std::min(dims.width_tiles - 1, static_cast(bottom_right.x)); - region.max_y = std::min(dims.height_tiles - 1, static_cast(bottom_right.y)); - + region.max_x = + std::min(dims.width_tiles - 1, static_cast(bottom_right.x)); + region.max_y = + std::min(dims.height_tiles - 1, static_cast(bottom_right.y)); + return region; } @@ -247,17 +252,17 @@ bool CanvasAutomationAPI::IsTileVisible(int x, int y) const { if (!IsInBounds(x, y)) { return false; } - + VisibleRegion region = GetVisibleRegion(); - return x >= region.min_x && x <= region.max_x && - y >= region.min_y && y <= region.max_y; + return x >= region.min_x && x <= region.max_x && y >= region.min_y && + y <= region.max_y; } bool CanvasAutomationAPI::IsInBounds(int x, int y) const { if (x < 0 || y < 0) { return false; } - + Dimensions dims = GetDimensions(); return x < dims.width_tiles && y < dims.height_tiles; } @@ -269,20 +274,20 @@ bool CanvasAutomationAPI::IsInBounds(int x, int y) const { ImVec2 CanvasAutomationAPI::TileToCanvas(int x, int y) const { int tile_size = GetDimensions().tile_size; float scale = canvas_->global_scale(); - + float canvas_x = x * tile_size * scale; float canvas_y = y * tile_size * scale; - + return ImVec2(canvas_x, canvas_y); } ImVec2 CanvasAutomationAPI::CanvasToTile(ImVec2 canvas_pos) const { int tile_size = GetDimensions().tile_size; float scale = canvas_->global_scale(); - + float tile_x = canvas_pos.x / (tile_size * scale); float tile_y = canvas_pos.y / (tile_size * scale); - + return ImVec2(std::floor(tile_x), std::floor(tile_y)); } @@ -300,4 +305,3 @@ void CanvasAutomationAPI::SetTileQueryCallback(TileQueryCallback callback) { } // namespace gui } // namespace yaze - diff --git a/src/app/gui/canvas/canvas_automation_api.h b/src/app/gui/canvas/canvas_automation_api.h index 692652fb..1e93863d 100644 --- a/src/app/gui/canvas/canvas_automation_api.h +++ b/src/app/gui/canvas/canvas_automation_api.h @@ -221,4 +221,3 @@ class CanvasAutomationAPI { } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_AUTOMATION_API_H - diff --git a/src/app/gui/canvas/canvas_context_menu.cc b/src/app/gui/canvas/canvas_context_menu.cc index 1123b31d..837d28dd 100644 --- a/src/app/gui/canvas/canvas_context_menu.cc +++ b/src/app/gui/canvas/canvas_context_menu.cc @@ -1,21 +1,21 @@ #include "canvas_context_menu.h" -#include "app/gfx/resource/arena.h" -#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/debug/performance/performance_dashboard.h" -#include "app/gui/widgets/palette_editor_widget.h" -#include "app/gui/core/icons.h" -#include "app/gui/core/color.h" +#include "app/gfx/debug/performance/performance_profiler.h" +#include "app/gfx/resource/arena.h" #include "app/gui/canvas/canvas.h" +#include "app/gui/core/color.h" +#include "app/gui/core/icons.h" +#include "app/gui/widgets/palette_editor_widget.h" #include "imgui/imgui.h" namespace yaze { namespace gui { namespace { -inline void Dispatch( - const std::function& handler, - CanvasContextMenu::Command command, CanvasConfig config) { +inline void Dispatch(const std::function& handler, + CanvasContextMenu::Command command, CanvasConfig config) { if (handler) { handler(command, config); } @@ -27,7 +27,7 @@ void CanvasContextMenu::Initialize(const std::string& canvas_id) { enabled_ = true; current_usage_ = CanvasUsage::kTilePainting; palette_editor_ = std::make_unique(); - + // Initialize canvas state canvas_size_ = ImVec2(0, 0); content_size_ = ImVec2(0, 0); @@ -40,7 +40,7 @@ void CanvasContextMenu::Initialize(const std::string& canvas_id) { is_draggable_ = false; auto_resize_ = false; scrolling_ = ImVec2(0, 0); - + // Create default menu items CreateDefaultMenuItems(); } @@ -53,7 +53,8 @@ void CanvasContextMenu::AddMenuItem(const CanvasMenuItem& item) { global_items_.push_back(item); } -void CanvasContextMenu::AddMenuItem(const CanvasMenuItem& item, CanvasUsage usage) { +void CanvasContextMenu::AddMenuItem(const CanvasMenuItem& item, + CanvasUsage usage) { usage_specific_items_[usage].push_back(item); } @@ -62,64 +63,66 @@ void CanvasContextMenu::ClearMenuItems() { usage_specific_items_.clear(); } -void CanvasContextMenu::Render(const std::string& context_id, - const ImVec2& /* mouse_pos */, Rom* rom, - const gfx::Bitmap* bitmap, - const gfx::SnesPalette* /* palette */, - const std::function& command_handler, - CanvasConfig current_config, Canvas* canvas) { - if (!enabled_) return; - +void CanvasContextMenu::Render( + const std::string& context_id, const ImVec2& /* mouse_pos */, Rom* rom, + const gfx::Bitmap* bitmap, const gfx::SnesPalette* /* palette */, + const std::function& command_handler, + CanvasConfig current_config, Canvas* canvas) { + if (!enabled_) + return; + // Context menu (under default mouse threshold) if (ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); enable_context_menu_ && drag_delta.x == 0.0F && drag_delta.y == 0.0F) { - ImGui::OpenPopupOnItemClick(context_id.c_str(), ImGuiPopupFlags_MouseButtonRight); + ImGui::OpenPopupOnItemClick(context_id.c_str(), + ImGuiPopupFlags_MouseButtonRight); } - + // Phase 4: Popup callback for automatic popup management - auto popup_callback = [canvas](const std::string& id, std::function callback) { + auto popup_callback = [canvas](const std::string& id, + std::function callback) { if (canvas) { canvas->GetPopupRegistry().Open(id, callback); } }; - + // Contents of the Context Menu (Phase 4: Priority-based ordering) if (ImGui::BeginPopup(context_id.c_str())) { // PRIORITY 0: Editor-specific items (from Canvas::editor_menu_) if (canvas && !canvas->editor_menu().sections.empty()) { RenderCanvasMenu(canvas->editor_menu(), popup_callback); } - + // Also render usage-specific items (legacy support) if (!usage_specific_items_[current_usage_].empty()) { RenderUsageSpecificMenu(popup_callback); ImGui::Separator(); } - + // PRIORITY 10: Bitmap/Palette operations if (bitmap) { RenderBitmapOperationsMenu(const_cast(bitmap)); ImGui::Separator(); - + RenderPaletteOperationsMenu(rom, const_cast(bitmap)); ImGui::Separator(); - + RenderBppOperationsMenu(bitmap); ImGui::Separator(); } - + // PRIORITY 20: Canvas properties RenderCanvasPropertiesMenu(command_handler, current_config); ImGui::Separator(); - + RenderViewControlsMenu(command_handler, current_config); ImGui::Separator(); - + RenderGridControlsMenu(command_handler, current_config); ImGui::Separator(); - + RenderScalingControlsMenu(command_handler, current_config); - + // PRIORITY 30: Debug/Performance if (ImGui::GetIO().KeyCtrl) { // Only show when Ctrl is held ImGui::Separator(); @@ -131,7 +134,7 @@ void CanvasContextMenu::Render(const std::string& context_id, ImGui::Separator(); RenderMenuSection("Custom Actions", global_items_, popup_callback); } - + ImGui::EndPopup(); } } @@ -140,41 +143,39 @@ bool CanvasContextMenu::ShouldShowContextMenu() const { return enabled_ && enable_context_menu_; } -void CanvasContextMenu::SetCanvasState(const ImVec2& canvas_size, - const ImVec2& content_size, - float global_scale, - float grid_step, - bool enable_grid, - bool /* enable_hex_labels */, - bool /* enable_custom_labels */, - bool /* enable_context_menu */, - bool /* is_draggable */, - bool /* auto_resize */, - const ImVec2& scrolling) { +void CanvasContextMenu::SetCanvasState( + const ImVec2& canvas_size, const ImVec2& content_size, float global_scale, + float grid_step, bool enable_grid, bool /* enable_hex_labels */, + bool /* enable_custom_labels */, bool /* enable_context_menu */, + bool /* is_draggable */, bool /* auto_resize */, const ImVec2& scrolling) { canvas_size_ = canvas_size; content_size_ = content_size; global_scale_ = global_scale; grid_step_ = grid_step; enable_grid_ = enable_grid; - enable_hex_labels_ = false; // Field not used anymore + enable_hex_labels_ = false; // Field not used anymore enable_custom_labels_ = false; // Field not used anymore - enable_context_menu_ = true; // Field not used anymore - is_draggable_ = false; // Field not used anymore - auto_resize_ = false; // Field not used anymore + enable_context_menu_ = true; // Field not used anymore + is_draggable_ = false; // Field not used anymore + auto_resize_ = false; // Field not used anymore scrolling_ = scrolling; } -void CanvasContextMenu::RenderMenuItem(const CanvasMenuItem& item, - std::function)> popup_callback) { +void CanvasContextMenu::RenderMenuItem( + const CanvasMenuItem& item, + std::function)> + popup_callback) { // Phase 4: Delegate to canvas_menu.h implementation gui::RenderMenuItem(item, popup_callback); } -void CanvasContextMenu::RenderMenuSection(const std::string& title, - const std::vector& items, - std::function)> popup_callback) { - if (items.empty()) return; - +void CanvasContextMenu::RenderMenuSection( + const std::string& title, const std::vector& items, + std::function)> + popup_callback) { + if (items.empty()) + return; + ImGui::TextColored(ImVec4(0.7F, 0.7F, 0.7F, 1.0F), "%s", title.c_str()); for (const auto& item : items) { RenderMenuItem(item, popup_callback); @@ -182,18 +183,20 @@ void CanvasContextMenu::RenderMenuSection(const std::string& title, } void CanvasContextMenu::RenderUsageSpecificMenu( - std::function)> popup_callback) { + std::function)> + popup_callback) { auto it = usage_specific_items_.find(current_usage_); if (it == usage_specific_items_.end() || it->second.empty()) { return; } - + std::string usage_name = GetUsageModeName(current_usage_); ImVec4 usage_color = GetUsageModeColor(current_usage_); - - ImGui::TextColored(usage_color, "%s %s Mode", ICON_MD_COLOR_LENS, usage_name.c_str()); + + ImGui::TextColored(usage_color, "%s %s Mode", ICON_MD_COLOR_LENS, + usage_name.c_str()); ImGui::Separator(); - + for (const auto& item : it->second) { RenderMenuItem(item, popup_callback); } @@ -247,8 +250,9 @@ void CanvasContextMenu::RenderCanvasPropertiesMenu( ImGui::Text("Content Size: %.0f x %.0f", content_size_.x, content_size_.y); ImGui::Text("Global Scale: %.2f", global_scale_); ImGui::Text("Grid Step: %.1f", grid_step_); - ImGui::Text("Mouse Position: %.0f x %.0f", 0.0F, 0.0F); // Would need actual mouse pos - + ImGui::Text("Mouse Position: %.0f x %.0f", 0.0F, + 0.0F); // Would need actual mouse pos + if (ImGui::MenuItem("Advanced Properties...")) { CanvasConfig updated = current_config; updated.enable_grid = enable_grid_; @@ -263,13 +267,14 @@ void CanvasContextMenu::RenderCanvasPropertiesMenu( updated.scrolling = scrolling_; Dispatch(command_handler, Command::kOpenAdvancedProperties, updated); } - + ImGui::EndMenu(); } } void CanvasContextMenu::RenderBitmapOperationsMenu(gfx::Bitmap* bitmap) { - if (!bitmap) return; + if (!bitmap) + return; if (ImGui::BeginMenu(ICON_MD_IMAGE " Bitmap Properties")) { ImGui::Text("Size: %d x %d", bitmap->width(), bitmap->height()); @@ -302,12 +307,15 @@ void CanvasContextMenu::RenderBitmapOperationsMenu(gfx::Bitmap* bitmap) { } } -void CanvasContextMenu::RenderPaletteOperationsMenu(Rom* rom, gfx::Bitmap* bitmap) { - if (!bitmap) return; +void CanvasContextMenu::RenderPaletteOperationsMenu(Rom* rom, + gfx::Bitmap* bitmap) { + if (!bitmap) + return; if (ImGui::BeginMenu(ICON_MD_PALETTE " Palette Operations")) { if (ImGui::MenuItem("Edit Palette...")) { - palette_editor_->ShowPaletteEditor(*bitmap->mutable_palette(), "Palette Editor"); + palette_editor_->ShowPaletteEditor(*bitmap->mutable_palette(), + "Palette Editor"); } if (ImGui::MenuItem("Color Analysis...")) { palette_editor_->ShowColorAnalysis(*bitmap, "Color Analysis"); @@ -315,15 +323,19 @@ void CanvasContextMenu::RenderPaletteOperationsMenu(Rom* rom, gfx::Bitmap* bitma if (rom && ImGui::BeginMenu("ROM Palette Selection")) { palette_editor_->Initialize(rom); - + // Render palette selector inline - ImGui::Text("Group:"); ImGui::SameLine(); - ImGui::InputScalar("##group", ImGuiDataType_U64, &edit_palette_group_name_index_); - ImGui::Text("Palette:"); ImGui::SameLine(); + ImGui::Text("Group:"); + ImGui::SameLine(); + ImGui::InputScalar("##group", ImGuiDataType_U64, + &edit_palette_group_name_index_); + ImGui::Text("Palette:"); + ImGui::SameLine(); ImGui::InputScalar("##palette", ImGuiDataType_U64, &edit_palette_index_); if (ImGui::Button("Apply to Canvas")) { - palette_editor_->ApplyROMPalette(bitmap, edit_palette_group_name_index_, edit_palette_index_); + palette_editor_->ApplyROMPalette(bitmap, edit_palette_group_name_index_, + edit_palette_index_); } ImGui::EndMenu(); } @@ -332,29 +344,34 @@ void CanvasContextMenu::RenderPaletteOperationsMenu(Rom* rom, gfx::Bitmap* bitma DisplayEditablePalette(*bitmap->mutable_palette(), "Palette", true, 8); ImGui::EndMenu(); } - + ImGui::Separator(); - + // Palette Help submenu if (ImGui::BeginMenu(ICON_MD_HELP " Palette Help")) { ImGui::TextColored(ImVec4(0.7F, 0.9F, 1.0F, 1.0F), "Bitmap Metadata"); ImGui::Separator(); - + const auto& meta = bitmap->metadata(); ImGui::Text("Source BPP: %d", meta.source_bpp); - ImGui::Text("Palette Format: %s", meta.palette_format == 0 ? "Full" : "Sub-palette"); + ImGui::Text("Palette Format: %s", + meta.palette_format == 0 ? "Full" : "Sub-palette"); ImGui::Text("Source Type: %s", meta.source_type.c_str()); ImGui::Text("Expected Colors: %d", meta.palette_colors); ImGui::Text("Actual Palette Size: %zu", bitmap->palette().size()); - + ImGui::Separator(); - ImGui::TextColored(ImVec4(1.0F, 0.9F, 0.6F, 1.0F), "Palette Application Method"); + ImGui::TextColored(ImVec4(1.0F, 0.9F, 0.6F, 1.0F), + "Palette Application Method"); if (meta.palette_format == 0) { - ImGui::TextWrapped("Full palette (SetPalette) - all colors applied directly"); + ImGui::TextWrapped( + "Full palette (SetPalette) - all colors applied directly"); } else { - ImGui::TextWrapped("Sub-palette (SetPaletteWithTransparent) - color 0 is transparent, 1-7 from palette"); + ImGui::TextWrapped( + "Sub-palette (SetPaletteWithTransparent) - color 0 is transparent, " + "1-7 from palette"); } - + ImGui::Separator(); ImGui::TextColored(ImVec4(0.6F, 1.0F, 0.6F, 1.0F), "Documentation"); if (ImGui::MenuItem("Palette System Architecture")) { @@ -365,21 +382,23 @@ void CanvasContextMenu::RenderPaletteOperationsMenu(Rom* rom, gfx::Bitmap* bitma ImGui::SetClipboardText("yaze/docs/user-palette-guide.md"); // TODO: Open file in system viewer } - + ImGui::EndMenu(); } - + ImGui::EndMenu(); } } void CanvasContextMenu::DrawROMPaletteSelector() { - if (!palette_editor_) return; + if (!palette_editor_) + return; - palette_editor_->DrawROMPaletteSelector(); + palette_editor_->DrawROMPaletteSelector(); } -void CanvasContextMenu::RenderBppOperationsMenu(const gfx::Bitmap* /* bitmap */) { +void CanvasContextMenu::RenderBppOperationsMenu( + const gfx::Bitmap* /* bitmap */) { if (ImGui::BeginMenu(ICON_MD_SWAP_HORIZ " BPP Operations")) { if (ImGui::MenuItem("Format Analysis...")) { // Open BPP analysis @@ -390,7 +409,7 @@ void CanvasContextMenu::RenderBppOperationsMenu(const gfx::Bitmap* /* bitmap */) if (ImGui::MenuItem("Format Comparison...")) { // Open format comparison tool } - + ImGui::EndMenu(); } } @@ -400,17 +419,17 @@ void CanvasContextMenu::RenderPerformanceMenu() { auto& profiler = gfx::PerformanceProfiler::Get(); auto canvas_stats = profiler.GetStats("canvas_operations"); auto draw_stats = profiler.GetStats("canvas_draw"); - + ImGui::Text("Canvas Operations: %zu", canvas_stats.sample_count); ImGui::Text("Average Time: %.2f ms", draw_stats.avg_time_us / 1000.0); - + if (ImGui::MenuItem("Performance Dashboard...")) { gfx::PerformanceDashboard::Get().SetVisible(true); } if (ImGui::MenuItem("Usage Report...")) { // Open usage report } - + ImGui::EndMenu(); } } @@ -422,8 +441,8 @@ void CanvasContextMenu::RenderGridControlsMenu( const struct GridOption { const char* label; float value; - } options[] = {{"8x8", 8.0F}, {"16x16", 16.0F}, - {"32x32", 32.0F}, {"64x64", 64.0F}}; + } options[] = { + {"8x8", 8.0F}, {"16x16", 16.0F}, {"32x32", 32.0F}, {"64x64", 64.0F}}; for (const auto& option : options) { bool selected = grid_step_ == option.value; @@ -446,7 +465,7 @@ void CanvasContextMenu::RenderScalingControlsMenu( const char* label; float value; } options[] = {{"0.25x", 0.25F}, {"0.5x", 0.5F}, {"1x", 1.0F}, - {"2x", 2.0F}, {"4x", 4.0F}, {"8x", 8.0F}}; + {"2x", 2.0F}, {"4x", 4.0F}, {"8x", 8.0F}}; for (const auto& option : options) { if (ImGui::MenuItem(option.label)) { @@ -460,17 +479,33 @@ void CanvasContextMenu::RenderScalingControlsMenu( } } -void CanvasContextMenu::RenderMaterialIcon(const std::string& icon_name, const ImVec4& color) { +void CanvasContextMenu::RenderMaterialIcon(const std::string& icon_name, + const ImVec4& color) { // Simple material icon rendering using Unicode symbols static std::unordered_map icon_map = { - {"grid_on", ICON_MD_GRID_ON}, {"label", ICON_MD_LABEL}, {"edit", ICON_MD_EDIT}, {"menu", ICON_MD_MENU}, - {"drag_indicator", ICON_MD_DRAG_INDICATOR}, {"fit_screen", ICON_MD_FIT_SCREEN}, {"zoom_in", ICON_MD_ZOOM_IN}, - {"speed", ICON_MD_SPEED}, {"timer", ICON_MD_TIMER}, {"functions", ICON_MD_FUNCTIONS}, {"schedule", ICON_MD_SCHEDULE}, - {"refresh", ICON_MD_REFRESH}, {"settings", ICON_MD_SETTINGS}, {"info", ICON_MD_INFO}, - {"view", ICON_MD_VISIBILITY}, {"properties", ICON_MD_SETTINGS}, {"bitmap", ICON_MD_IMAGE}, {"palette", ICON_MD_PALETTE}, - {"bpp", ICON_MD_SWAP_HORIZ}, {"performance", ICON_MD_TRENDING_UP}, {"grid", ICON_MD_GRID_ON}, {"scaling", ICON_MD_ZOOM_IN} - }; - + {"grid_on", ICON_MD_GRID_ON}, + {"label", ICON_MD_LABEL}, + {"edit", ICON_MD_EDIT}, + {"menu", ICON_MD_MENU}, + {"drag_indicator", ICON_MD_DRAG_INDICATOR}, + {"fit_screen", ICON_MD_FIT_SCREEN}, + {"zoom_in", ICON_MD_ZOOM_IN}, + {"speed", ICON_MD_SPEED}, + {"timer", ICON_MD_TIMER}, + {"functions", ICON_MD_FUNCTIONS}, + {"schedule", ICON_MD_SCHEDULE}, + {"refresh", ICON_MD_REFRESH}, + {"settings", ICON_MD_SETTINGS}, + {"info", ICON_MD_INFO}, + {"view", ICON_MD_VISIBILITY}, + {"properties", ICON_MD_SETTINGS}, + {"bitmap", ICON_MD_IMAGE}, + {"palette", ICON_MD_PALETTE}, + {"bpp", ICON_MD_SWAP_HORIZ}, + {"performance", ICON_MD_TRENDING_UP}, + {"grid", ICON_MD_GRID_ON}, + {"scaling", ICON_MD_ZOOM_IN}}; + auto it = icon_map.find(icon_name); if (it != icon_map.end()) { ImGui::TextColored(color, "%s", it->second); @@ -479,81 +514,109 @@ void CanvasContextMenu::RenderMaterialIcon(const std::string& icon_name, const I std::string CanvasContextMenu::GetUsageModeName(CanvasUsage usage) const { switch (usage) { - case CanvasUsage::kTilePainting: return "Tile Painting"; - case CanvasUsage::kTileSelecting: return "Tile Selecting"; - case CanvasUsage::kSelectRectangle: return "Rectangle Selection"; - case CanvasUsage::kColorPainting: return "Color Painting"; - case CanvasUsage::kBitmapEditing: return "Bitmap Editing"; - case CanvasUsage::kPaletteEditing: return "Palette Editing"; - case CanvasUsage::kBppConversion: return "BPP Conversion"; - case CanvasUsage::kPerformanceMode: return "Performance Mode"; - case CanvasUsage::kEntityManipulation: return "Entity Manipulation"; - case CanvasUsage::kUnknown: return "Unknown"; - default: return "Unknown"; + case CanvasUsage::kTilePainting: + return "Tile Painting"; + case CanvasUsage::kTileSelecting: + return "Tile Selecting"; + case CanvasUsage::kSelectRectangle: + return "Rectangle Selection"; + case CanvasUsage::kColorPainting: + return "Color Painting"; + case CanvasUsage::kBitmapEditing: + return "Bitmap Editing"; + case CanvasUsage::kPaletteEditing: + return "Palette Editing"; + case CanvasUsage::kBppConversion: + return "BPP Conversion"; + case CanvasUsage::kPerformanceMode: + return "Performance Mode"; + case CanvasUsage::kEntityManipulation: + return "Entity Manipulation"; + case CanvasUsage::kUnknown: + return "Unknown"; + default: + return "Unknown"; } } ImVec4 CanvasContextMenu::GetUsageModeColor(CanvasUsage usage) const { switch (usage) { - case CanvasUsage::kTilePainting: return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green - case CanvasUsage::kTileSelecting: return ImVec4(0.2F, 0.8F, 1.0F, 1.0F); // Blue - case CanvasUsage::kSelectRectangle: return ImVec4(1.0F, 0.8F, 0.2F, 1.0F); // Yellow - case CanvasUsage::kColorPainting: return ImVec4(1.0F, 0.2F, 1.0F, 1.0F); // Magenta - case CanvasUsage::kBitmapEditing: return ImVec4(1.0F, 0.5F, 0.2F, 1.0F); // Orange - case CanvasUsage::kPaletteEditing: return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple - case CanvasUsage::kBppConversion: return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan - case CanvasUsage::kPerformanceMode: return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red - case CanvasUsage::kEntityManipulation: return ImVec4(0.4F, 0.8F, 1.0F, 1.0F); // Light Blue - case CanvasUsage::kUnknown: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray - default: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray + case CanvasUsage::kTilePainting: + return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green + case CanvasUsage::kTileSelecting: + return ImVec4(0.2F, 0.8F, 1.0F, 1.0F); // Blue + case CanvasUsage::kSelectRectangle: + return ImVec4(1.0F, 0.8F, 0.2F, 1.0F); // Yellow + case CanvasUsage::kColorPainting: + return ImVec4(1.0F, 0.2F, 1.0F, 1.0F); // Magenta + case CanvasUsage::kBitmapEditing: + return ImVec4(1.0F, 0.5F, 0.2F, 1.0F); // Orange + case CanvasUsage::kPaletteEditing: + return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple + case CanvasUsage::kBppConversion: + return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan + case CanvasUsage::kPerformanceMode: + return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red + case CanvasUsage::kEntityManipulation: + return ImVec4(0.4F, 0.8F, 1.0F, 1.0F); // Light Blue + case CanvasUsage::kUnknown: + return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray + default: + return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray } } void CanvasContextMenu::CreateDefaultMenuItems() { // Phase 4: Create default menu items using unified CanvasMenuItem - + // Tile Painting mode items CanvasMenuItem tile_paint_item("Paint Tile", "paint", []() { // Tile painting action }); usage_specific_items_[CanvasUsage::kTilePainting].push_back(tile_paint_item); - + // Tile Selecting mode items CanvasMenuItem tile_select_item("Select Tile", "select", []() { // Tile selection action }); - usage_specific_items_[CanvasUsage::kTileSelecting].push_back(tile_select_item); - + usage_specific_items_[CanvasUsage::kTileSelecting].push_back( + tile_select_item); + // Rectangle Selection mode items CanvasMenuItem rect_select_item("Select Rectangle", "rect", []() { // Rectangle selection action }); - usage_specific_items_[CanvasUsage::kSelectRectangle].push_back(rect_select_item); - + usage_specific_items_[CanvasUsage::kSelectRectangle].push_back( + rect_select_item); + // Color Painting mode items CanvasMenuItem color_paint_item("Paint Color", "color", []() { // Color painting action }); - usage_specific_items_[CanvasUsage::kColorPainting].push_back(color_paint_item); - + usage_specific_items_[CanvasUsage::kColorPainting].push_back( + color_paint_item); + // Bitmap Editing mode items CanvasMenuItem bitmap_edit_item("Edit Bitmap", "edit", []() { // Bitmap editing action }); - usage_specific_items_[CanvasUsage::kBitmapEditing].push_back(bitmap_edit_item); - + usage_specific_items_[CanvasUsage::kBitmapEditing].push_back( + bitmap_edit_item); + // Palette Editing mode items CanvasMenuItem palette_edit_item("Edit Palette", "palette", []() { // Palette editing action }); - usage_specific_items_[CanvasUsage::kPaletteEditing].push_back(palette_edit_item); - + usage_specific_items_[CanvasUsage::kPaletteEditing].push_back( + palette_edit_item); + // BPP Conversion mode items CanvasMenuItem bpp_convert_item("Convert Format", "convert", []() { // BPP conversion action }); - usage_specific_items_[CanvasUsage::kBppConversion].push_back(bpp_convert_item); - + usage_specific_items_[CanvasUsage::kBppConversion].push_back( + bpp_convert_item); + // Performance Mode items CanvasMenuItem perf_item("Performance Analysis", "perf", []() { // Performance analysis action @@ -562,27 +625,32 @@ void CanvasContextMenu::CreateDefaultMenuItems() { } CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreateViewMenuItem( - const std::string& label, const std::string& icon, std::function callback) { + const std::string& label, const std::string& icon, + std::function callback) { return CanvasMenuItem(label, icon, callback); } CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreateBitmapMenuItem( - const std::string& label, const std::string& icon, std::function callback) { + const std::string& label, const std::string& icon, + std::function callback) { return CanvasMenuItem(label, icon, callback); } CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreatePaletteMenuItem( - const std::string& label, const std::string& icon, std::function callback) { + const std::string& label, const std::string& icon, + std::function callback) { return CanvasMenuItem(label, icon, callback); } CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreateBppMenuItem( - const std::string& label, const std::string& icon, std::function callback) { + const std::string& label, const std::string& icon, + std::function callback) { return CanvasMenuItem(label, icon, callback); } CanvasContextMenu::CanvasMenuItem CanvasContextMenu::CreatePerformanceMenuItem( - const std::string& label, const std::string& icon, std::function callback) { + const std::string& label, const std::string& icon, + std::function callback) { return CanvasMenuItem(label, icon, callback); } diff --git a/src/app/gui/canvas/canvas_context_menu.h b/src/app/gui/canvas/canvas_context_menu.h index a9717121..fea1b033 100644 --- a/src/app/gui/canvas/canvas_context_menu.h +++ b/src/app/gui/canvas/canvas_context_menu.h @@ -8,9 +8,9 @@ #include "app/gfx/core/bitmap.h" #include "app/gfx/types/snes_palette.h" -#include "app/gui/core/icons.h" -#include "app/gui/canvas/canvas_modals.h" #include "app/gui/canvas/canvas_menu.h" +#include "app/gui/canvas/canvas_modals.h" +#include "app/gui/core/icons.h" #include "canvas_usage_tracker.h" #include "imgui/imgui.h" @@ -53,31 +53,22 @@ class CanvasContextMenu { void ClearMenuItems(); // Phase 4: Render with editor menu integration and priority ordering - void Render(const std::string& context_id, - const ImVec2& mouse_pos, - Rom* rom, - const gfx::Bitmap* bitmap, - const gfx::SnesPalette* palette, - const std::function& command_handler, - CanvasConfig current_config, - Canvas* canvas); + void Render( + const std::string& context_id, const ImVec2& mouse_pos, Rom* rom, + const gfx::Bitmap* bitmap, const gfx::SnesPalette* palette, + const std::function& command_handler, + CanvasConfig current_config, Canvas* canvas); bool ShouldShowContextMenu() const; void SetEnabled(bool enabled) { enabled_ = enabled; } bool IsEnabled() const { return enabled_; } CanvasUsage GetUsageMode() const { return current_usage_; } - void SetCanvasState(const ImVec2& canvas_size, - const ImVec2& content_size, - float global_scale, - float grid_step, - bool enable_grid, - bool enable_hex_labels, - bool enable_custom_labels, - bool enable_context_menu, - bool is_draggable, - bool auto_resize, - const ImVec2& scrolling); + void SetCanvasState(const ImVec2& canvas_size, const ImVec2& content_size, + float global_scale, float grid_step, bool enable_grid, + bool enable_hex_labels, bool enable_custom_labels, + bool enable_context_menu, bool is_draggable, + bool auto_resize, const ImVec2& scrolling); private: std::string canvas_id_; @@ -104,27 +95,37 @@ class CanvasContextMenu { void DrawROMPaletteSelector(); - std::unordered_map> usage_specific_items_; + std::unordered_map> + usage_specific_items_; std::vector global_items_; - void RenderMenuItem(const CanvasMenuItem& item, - std::function)> popup_callback); - void RenderMenuSection(const std::string& title, - const std::vector& items, - std::function)> popup_callback); - void RenderUsageSpecificMenu(std::function)> popup_callback); - void RenderViewControlsMenu(const std::function& command_handler, - CanvasConfig current_config); - void RenderCanvasPropertiesMenu(const std::function& command_handler, - CanvasConfig current_config); + void RenderMenuItem( + const CanvasMenuItem& item, + std::function)> + popup_callback); + void RenderMenuSection( + const std::string& title, const std::vector& items, + std::function)> + popup_callback); + void RenderUsageSpecificMenu( + std::function)> + popup_callback); + void RenderViewControlsMenu( + const std::function& command_handler, + CanvasConfig current_config); + void RenderCanvasPropertiesMenu( + const std::function& command_handler, + CanvasConfig current_config); void RenderBitmapOperationsMenu(gfx::Bitmap* bitmap); void RenderPaletteOperationsMenu(Rom* rom, gfx::Bitmap* bitmap); void RenderBppOperationsMenu(const gfx::Bitmap* bitmap); void RenderPerformanceMenu(); - void RenderGridControlsMenu(const std::function& command_handler, - CanvasConfig current_config); - void RenderScalingControlsMenu(const std::function& command_handler, - CanvasConfig current_config); + void RenderGridControlsMenu( + const std::function& command_handler, + CanvasConfig current_config); + void RenderScalingControlsMenu( + const std::function& command_handler, + CanvasConfig current_config); void RenderMaterialIcon(const std::string& icon_name, const ImVec4& color = ImVec4(1, 1, 1, 1)); @@ -133,20 +134,20 @@ class CanvasContextMenu { void CreateDefaultMenuItems(); CanvasMenuItem CreateViewMenuItem(const std::string& label, - const std::string& icon, - std::function callback); + const std::string& icon, + std::function callback); CanvasMenuItem CreateBitmapMenuItem(const std::string& label, - const std::string& icon, - std::function callback); - CanvasMenuItem CreatePaletteMenuItem(const std::string& label, const std::string& icon, std::function callback); + CanvasMenuItem CreatePaletteMenuItem(const std::string& label, + const std::string& icon, + std::function callback); CanvasMenuItem CreateBppMenuItem(const std::string& label, - const std::string& icon, - std::function callback); + const std::string& icon, + std::function callback); CanvasMenuItem CreatePerformanceMenuItem(const std::string& label, - const std::string& icon, - std::function callback); + const std::string& icon, + std::function callback); }; } // namespace gui diff --git a/src/app/gui/canvas/canvas_events.h b/src/app/gui/canvas/canvas_events.h index ad423d5a..38eb105d 100644 --- a/src/app/gui/canvas/canvas_events.h +++ b/src/app/gui/canvas/canvas_events.h @@ -14,12 +14,12 @@ namespace gui { * Canvas-space coordinates are provided for positioning. */ struct TilePaintEvent { - ImVec2 position; ///< Canvas-space pixel coordinates - ImVec2 grid_position; ///< Grid-aligned tile position - int tile_id = -1; ///< Tile ID being painted (-1 if none) - bool is_drag = false; ///< True for continuous drag painting - bool is_complete = false; ///< True when paint action finishes - + ImVec2 position; ///< Canvas-space pixel coordinates + ImVec2 grid_position; ///< Grid-aligned tile position + int tile_id = -1; ///< Tile ID being painted (-1 if none) + bool is_drag = false; ///< True for continuous drag painting + bool is_complete = false; ///< True when paint action finishes + void Reset() { position = ImVec2(-1, -1); grid_position = ImVec2(-1, -1); @@ -36,13 +36,14 @@ struct TilePaintEvent { * Provides both the rectangle bounds and the individual selected tile positions. */ struct RectSelectionEvent { - std::vector selected_tiles; ///< Individual tile positions (grid coords) - ImVec2 start_pos; ///< Rectangle start (canvas coords) - ImVec2 end_pos; ///< Rectangle end (canvas coords) - int current_map = -1; ///< Map ID for coordinate calculation - bool is_complete = false; ///< True when selection finishes - bool is_active = false; ///< True while dragging - + std::vector + selected_tiles; ///< Individual tile positions (grid coords) + ImVec2 start_pos; ///< Rectangle start (canvas coords) + ImVec2 end_pos; ///< Rectangle end (canvas coords) + int current_map = -1; ///< Map ID for coordinate calculation + bool is_complete = false; ///< True when selection finishes + bool is_active = false; ///< True while dragging + void Reset() { selected_tiles.clear(); start_pos = ImVec2(-1, -1); @@ -51,10 +52,10 @@ struct RectSelectionEvent { is_complete = false; is_active = false; } - + /** @brief Get number of selected tiles */ size_t Count() const { return selected_tiles.size(); } - + /** @brief Check if selection is empty */ bool IsEmpty() const { return selected_tiles.empty(); } }; @@ -65,10 +66,10 @@ struct RectSelectionEvent { * Represents selecting a single tile, typically from a right-click. */ struct TileSelectionEvent { - ImVec2 tile_position; ///< Selected tile position (grid coords) - int tile_id = -1; ///< Selected tile ID - bool is_valid = false; ///< True if selection is valid - + ImVec2 tile_position; ///< Selected tile position (grid coords) + int tile_id = -1; ///< Selected tile ID + bool is_valid = false; ///< True if selection is valid + void Reset() { tile_position = ImVec2(-1, -1); tile_id = -1; @@ -92,14 +93,14 @@ struct EntityInteractionEvent { kDragMove, ///< Dragging entity (continuous) kDragEnd ///< Finished dragging entity }; - - Type type = Type::kNone; ///< Type of interaction - int entity_id = -1; ///< Entity being interacted with - ImVec2 position; ///< Current entity position (canvas coords) - ImVec2 delta; ///< Movement delta (for drag events) - ImVec2 grid_position; ///< Grid-aligned position - bool is_valid = false; ///< True if event is valid - + + Type type = Type::kNone; ///< Type of interaction + int entity_id = -1; ///< Entity being interacted with + ImVec2 position; ///< Current entity position (canvas coords) + ImVec2 delta; ///< Movement delta (for drag events) + ImVec2 grid_position; ///< Grid-aligned position + bool is_valid = false; ///< True if event is valid + void Reset() { type = Type::kNone; entity_id = -1; @@ -108,13 +109,13 @@ struct EntityInteractionEvent { grid_position = ImVec2(-1, -1); is_valid = false; } - + /** @brief Check if this is a drag event */ bool IsDragEvent() const { - return type == Type::kDragStart || type == Type::kDragMove || + return type == Type::kDragStart || type == Type::kDragMove || type == Type::kDragEnd; } - + /** @brief Check if this is a click event */ bool IsClickEvent() const { return type == Type::kClick || type == Type::kDoubleClick; @@ -127,10 +128,10 @@ struct EntityInteractionEvent { * Represents hover state for overlay rendering. */ struct HoverEvent { - ImVec2 position; ///< Canvas-space hover position - ImVec2 grid_position; ///< Grid-aligned hover position - bool is_valid = false; ///< True if hovering over canvas - + ImVec2 position; ///< Canvas-space hover position + ImVec2 grid_position; ///< Grid-aligned hover position + bool is_valid = false; ///< True if hovering over canvas + void Reset() { position = ImVec2(-1, -1); grid_position = ImVec2(-1, -1); @@ -150,7 +151,7 @@ struct CanvasInteractionEvents { TileSelectionEvent tile_selection; EntityInteractionEvent entity_interaction; HoverEvent hover; - + /** @brief Reset all events */ void Reset() { tile_paint.Reset(); @@ -159,13 +160,11 @@ struct CanvasInteractionEvents { entity_interaction.Reset(); hover.Reset(); } - + /** @brief Check if any event occurred */ bool HasAnyEvent() const { - return tile_paint.is_complete || - rect_selection.is_complete || - tile_selection.is_valid || - entity_interaction.is_valid || + return tile_paint.is_complete || rect_selection.is_complete || + tile_selection.is_valid || entity_interaction.is_valid || hover.is_valid; } }; @@ -174,4 +173,3 @@ struct CanvasInteractionEvents { } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_EVENTS_H - diff --git a/src/app/gui/canvas/canvas_geometry.cc b/src/app/gui/canvas/canvas_geometry.cc index de678144..5b01b774 100644 --- a/src/app/gui/canvas/canvas_geometry.cc +++ b/src/app/gui/canvas/canvas_geometry.cc @@ -6,32 +6,31 @@ namespace yaze { namespace gui { -CanvasGeometry CalculateCanvasGeometry( - const CanvasConfig& config, - ImVec2 requested_size, - ImVec2 cursor_screen_pos, - ImVec2 content_region_avail) { - +CanvasGeometry CalculateCanvasGeometry(const CanvasConfig& config, + ImVec2 requested_size, + ImVec2 cursor_screen_pos, + ImVec2 content_region_avail) { + CanvasGeometry geometry; - + // Set canvas top-left position (screen space) geometry.canvas_p0 = cursor_screen_pos; - + // Calculate canvas size using existing utility function ImVec2 canvas_sz = CanvasUtils::CalculateCanvasSize( content_region_avail, config.canvas_size, config.custom_canvas_size); - + // Override with explicit size if provided if (requested_size.x != 0) { canvas_sz = requested_size; } - + geometry.canvas_sz = canvas_sz; - + // Calculate scaled canvas bounds - geometry.scaled_size = CanvasUtils::CalculateScaledCanvasSize( - canvas_sz, config.global_scale); - + geometry.scaled_size = + CanvasUtils::CalculateScaledCanvasSize(canvas_sz, config.global_scale); + // CRITICAL: Ensure minimum size to prevent ImGui assertions if (geometry.scaled_size.x <= 0.0f) { geometry.scaled_size.x = 1.0f; @@ -39,39 +38,31 @@ CanvasGeometry CalculateCanvasGeometry( if (geometry.scaled_size.y <= 0.0f) { geometry.scaled_size.y = 1.0f; } - + // Calculate bottom-right position - geometry.canvas_p1 = ImVec2( - geometry.canvas_p0.x + geometry.scaled_size.x, - geometry.canvas_p0.y + geometry.scaled_size.y); - + geometry.canvas_p1 = ImVec2(geometry.canvas_p0.x + geometry.scaled_size.x, + geometry.canvas_p0.y + geometry.scaled_size.y); + // Copy scroll offset from config (will be updated by interaction) geometry.scrolling = config.scrolling; - + return geometry; } -ImVec2 CalculateMouseInCanvas( - const CanvasGeometry& geometry, - ImVec2 mouse_screen_pos) { - +ImVec2 CalculateMouseInCanvas(const CanvasGeometry& geometry, + ImVec2 mouse_screen_pos) { + // Calculate origin (locked scrolled origin as used throughout canvas.cc) ImVec2 origin = GetCanvasOrigin(geometry); - + // Convert screen space to canvas space - return ImVec2( - mouse_screen_pos.x - origin.x, - mouse_screen_pos.y - origin.y); + return ImVec2(mouse_screen_pos.x - origin.x, mouse_screen_pos.y - origin.y); } -bool IsPointInCanvasBounds( - const CanvasGeometry& geometry, - ImVec2 point) { - - return point.x >= geometry.canvas_p0.x && - point.x <= geometry.canvas_p1.x && - point.y >= geometry.canvas_p0.y && - point.y <= geometry.canvas_p1.y; +bool IsPointInCanvasBounds(const CanvasGeometry& geometry, ImVec2 point) { + + return point.x >= geometry.canvas_p0.x && point.x <= geometry.canvas_p1.x && + point.y >= geometry.canvas_p0.y && point.y <= geometry.canvas_p1.y; } void ApplyScrollDelta(CanvasGeometry& geometry, ImVec2 delta) { @@ -81,4 +72,3 @@ void ApplyScrollDelta(CanvasGeometry& geometry, ImVec2 delta) { } // namespace gui } // namespace yaze - diff --git a/src/app/gui/canvas/canvas_geometry.h b/src/app/gui/canvas/canvas_geometry.h index ef7bbf16..477c7267 100644 --- a/src/app/gui/canvas/canvas_geometry.h +++ b/src/app/gui/canvas/canvas_geometry.h @@ -21,11 +21,10 @@ namespace gui { * @param content_region_avail Available content region (from GetContentRegionAvail) * @return Calculated geometry for this frame */ -CanvasGeometry CalculateCanvasGeometry( - const CanvasConfig& config, - ImVec2 requested_size, - ImVec2 cursor_screen_pos, - ImVec2 content_region_avail); +CanvasGeometry CalculateCanvasGeometry(const CanvasConfig& config, + ImVec2 requested_size, + ImVec2 cursor_screen_pos, + ImVec2 content_region_avail); /** * @brief Calculate mouse position in canvas space @@ -38,9 +37,8 @@ CanvasGeometry CalculateCanvasGeometry( * @param mouse_screen_pos Mouse position in screen space * @return Mouse position in canvas space */ -ImVec2 CalculateMouseInCanvas( - const CanvasGeometry& geometry, - ImVec2 mouse_screen_pos); +ImVec2 CalculateMouseInCanvas(const CanvasGeometry& geometry, + ImVec2 mouse_screen_pos); /** * @brief Check if a point is within canvas bounds @@ -52,9 +50,7 @@ ImVec2 CalculateMouseInCanvas( * @param point Point in screen space to test * @return True if point is within canvas bounds */ -bool IsPointInCanvasBounds( - const CanvasGeometry& geometry, - ImVec2 point); +bool IsPointInCanvasBounds(const CanvasGeometry& geometry, ImVec2 point); /** * @brief Apply scroll delta to geometry @@ -84,4 +80,3 @@ inline ImVec2 GetCanvasOrigin(const CanvasGeometry& geometry) { } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_GEOMETRY_H - diff --git a/src/app/gui/canvas/canvas_interaction.cc b/src/app/gui/canvas/canvas_interaction.cc index b9799653..d4fe050c 100644 --- a/src/app/gui/canvas/canvas_interaction.cc +++ b/src/app/gui/canvas/canvas_interaction.cc @@ -33,19 +33,20 @@ ImVec2 AlignToGrid(ImVec2 pos, float grid_step) { ImVec2 GetMouseInCanvasSpace(const CanvasGeometry& geometry) { const ImGuiIO& imgui_io = ImGui::GetIO(); const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x, - geometry.canvas_p0.y + geometry.scrolling.y); + geometry.canvas_p0.y + geometry.scrolling.y); return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); } bool IsMouseInCanvas(const CanvasGeometry& geometry) { const ImGuiIO& imgui_io = ImGui::GetIO(); - return imgui_io.MousePos.x >= geometry.canvas_p0.x && + return imgui_io.MousePos.x >= geometry.canvas_p0.x && imgui_io.MousePos.x <= geometry.canvas_p1.x && - imgui_io.MousePos.y >= geometry.canvas_p0.y && + imgui_io.MousePos.y >= geometry.canvas_p0.y && imgui_io.MousePos.y <= geometry.canvas_p1.y; } -ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, float global_scale) { +ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, + float global_scale) { const float scaled_size = tile_size * global_scale; return ImVec2(std::floor(canvas_pos.x / scaled_size), std::floor(canvas_pos.y / scaled_size)); @@ -55,24 +56,23 @@ ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, float global_scale) // Rectangle Selection Implementation // ============================================================================ -RectSelectionEvent HandleRectangleSelection( - const CanvasGeometry& geometry, - int current_map, - float tile_size, - ImDrawList* draw_list, - ImGuiMouseButton mouse_button) { - +RectSelectionEvent HandleRectangleSelection(const CanvasGeometry& geometry, + int current_map, float tile_size, + ImDrawList* draw_list, + ImGuiMouseButton mouse_button) { + RectSelectionEvent event; event.current_map = current_map; - + if (!IsMouseInCanvas(geometry)) { return event; } - + const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry); - const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; + const float scaled_size = + tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; constexpr int kSmallMapSize = 0x200; // 512 pixels - + // Calculate super X/Y accounting for world offset int super_y = 0; int super_x = 0; @@ -89,28 +89,28 @@ RectSelectionEvent HandleRectangleSelection( super_y = (current_map - 0x80) / 8; super_x = (current_map - 0x80) % 8; } - + // Handle mouse button press - start selection if (ImGui::IsMouseClicked(mouse_button)) { g_select_rect_state.drag_start_pos = AlignToGrid(mouse_pos, scaled_size); g_select_rect_state.is_dragging = false; - + // Single tile selection on click ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size); int painter_x = static_cast(painter_pos.x); int painter_y = static_cast(painter_pos.y); - + auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20); auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20); - + int index_x = super_x * 0x20 + tile16_x; int index_y = super_y * 0x20 + tile16_y; - + event.start_pos = painter_pos; - event.selected_tiles.push_back(ImVec2(static_cast(index_x), - static_cast(index_y))); + event.selected_tiles.push_back( + ImVec2(static_cast(index_x), static_cast(index_y))); } - + // Handle dragging - draw preview rectangle ImVec2 drag_end_pos = AlignToGrid(mouse_pos, scaled_size); if (ImGui::IsMouseDragging(mouse_button) && draw_list) { @@ -118,15 +118,16 @@ RectSelectionEvent HandleRectangleSelection( event.is_active = true; event.start_pos = g_select_rect_state.drag_start_pos; event.end_pos = drag_end_pos; - + // Draw preview rectangle - auto start = ImVec2(geometry.canvas_p0.x + g_select_rect_state.drag_start_pos.x, - geometry.canvas_p0.y + g_select_rect_state.drag_start_pos.y); + auto start = + ImVec2(geometry.canvas_p0.x + g_select_rect_state.drag_start_pos.x, + geometry.canvas_p0.y + g_select_rect_state.drag_start_pos.y); auto end = ImVec2(geometry.canvas_p0.x + drag_end_pos.x + tile_size, geometry.canvas_p0.y + drag_end_pos.y + tile_size); draw_list->AddRect(start, end, IM_COL32(255, 255, 255, 255)); } - + // Handle mouse release - complete selection if (g_select_rect_state.is_dragging && !ImGui::IsMouseDown(mouse_button)) { g_select_rect_state.is_dragging = false; @@ -134,55 +135,62 @@ RectSelectionEvent HandleRectangleSelection( event.is_active = false; event.start_pos = g_select_rect_state.drag_start_pos; event.end_pos = drag_end_pos; - + // Calculate selected tiles constexpr int kTile16Size = 16; - int start_x = static_cast(std::floor(g_select_rect_state.drag_start_pos.x / scaled_size)) * kTile16Size; - int start_y = static_cast(std::floor(g_select_rect_state.drag_start_pos.y / scaled_size)) * kTile16Size; - int end_x = static_cast(std::floor(drag_end_pos.x / scaled_size)) * kTile16Size; - int end_y = static_cast(std::floor(drag_end_pos.y / scaled_size)) * kTile16Size; - - if (start_x > end_x) std::swap(start_x, end_x); - if (start_y > end_y) std::swap(start_y, end_y); - + int start_x = static_cast(std::floor( + g_select_rect_state.drag_start_pos.x / scaled_size)) * + kTile16Size; + int start_y = static_cast(std::floor( + g_select_rect_state.drag_start_pos.y / scaled_size)) * + kTile16Size; + int end_x = static_cast(std::floor(drag_end_pos.x / scaled_size)) * + kTile16Size; + int end_y = static_cast(std::floor(drag_end_pos.y / scaled_size)) * + kTile16Size; + + if (start_x > end_x) + std::swap(start_x, end_x); + if (start_y > end_y) + std::swap(start_y, end_y); + constexpr int kTilesPerLocalMap = kSmallMapSize / 16; - + for (int tile_y = start_y; tile_y <= end_y; tile_y += kTile16Size) { for (int tile_x = start_x; tile_x <= end_x; tile_x += kTile16Size) { int local_map_x = tile_x / kSmallMapSize; int local_map_y = tile_y / kSmallMapSize; - + int tile16_x = (tile_x % kSmallMapSize) / kTile16Size; int tile16_y = (tile_y % kSmallMapSize) / kTile16Size; - + int index_x = local_map_x * kTilesPerLocalMap + tile16_x; int index_y = local_map_y * kTilesPerLocalMap + tile16_y; - - event.selected_tiles.emplace_back(static_cast(index_x), + + event.selected_tiles.emplace_back(static_cast(index_x), static_cast(index_y)); } } } - + return event; } -TileSelectionEvent HandleTileSelection( - const CanvasGeometry& geometry, - int current_map, - float tile_size, - ImGuiMouseButton mouse_button) { - +TileSelectionEvent HandleTileSelection(const CanvasGeometry& geometry, + int current_map, float tile_size, + ImGuiMouseButton mouse_button) { + TileSelectionEvent event; - + if (!IsMouseInCanvas(geometry) || !ImGui::IsMouseClicked(mouse_button)) { return event; } - + const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry); - const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; + const float scaled_size = + tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; constexpr int kSmallMapSize = 0x200; - + // Calculate super X/Y int super_y = 0; int super_x = 0; @@ -196,21 +204,21 @@ TileSelectionEvent HandleTileSelection( super_y = (current_map - 0x80) / 8; super_x = (current_map - 0x80) % 8; } - + ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size); int painter_x = static_cast(painter_pos.x); int painter_y = static_cast(painter_pos.y); - + auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20); auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20); - + int index_x = super_x * 0x20 + tile16_x; int index_y = super_y * 0x20 + tile16_y; - - event.tile_position = ImVec2(static_cast(index_x), - static_cast(index_y)); + + event.tile_position = + ImVec2(static_cast(index_x), static_cast(index_y)); event.is_valid = true; - + return event; } @@ -218,26 +226,24 @@ TileSelectionEvent HandleTileSelection( // Tile Painting Implementation // ============================================================================ -TilePaintEvent HandleTilePaint( - const CanvasGeometry& geometry, - int tile_id, - float tile_size, - ImGuiMouseButton mouse_button) { - +TilePaintEvent HandleTilePaint(const CanvasGeometry& geometry, int tile_id, + float tile_size, ImGuiMouseButton mouse_button) { + TilePaintEvent event; event.tile_id = tile_id; - + if (!IsMouseInCanvas(geometry)) { return event; } - + const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry); - const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; - + const float scaled_size = + tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; + ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size); event.position = mouse_pos; event.grid_position = paint_pos; - + // Check for paint action if (ImGui::IsMouseClicked(mouse_button)) { event.is_complete = true; @@ -246,88 +252,88 @@ TilePaintEvent HandleTilePaint( event.is_complete = true; event.is_drag = true; } - + return event; } -TilePaintEvent HandleTilePaintWithPreview( - const CanvasGeometry& geometry, - const gfx::Bitmap& bitmap, - float tile_size, - ImDrawList* draw_list, - ImGuiMouseButton mouse_button) { - +TilePaintEvent HandleTilePaintWithPreview(const CanvasGeometry& geometry, + const gfx::Bitmap& bitmap, + float tile_size, + ImDrawList* draw_list, + ImGuiMouseButton mouse_button) { + TilePaintEvent event; - + if (!IsMouseInCanvas(geometry)) { return event; } - + const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry); - const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; - + const float scaled_size = + tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; + // Calculate grid-aligned paint position ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size); event.position = mouse_pos; event.grid_position = paint_pos; - - auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size); - + + auto paint_pos_end = + ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size); + // Draw preview of tile at hover position if (bitmap.is_active() && draw_list) { const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x, - geometry.canvas_p0.y + geometry.scrolling.y); + geometry.canvas_p0.y + geometry.scrolling.y); draw_list->AddImage( reinterpret_cast(bitmap.texture()), ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y), ImVec2(origin.x + paint_pos_end.x, origin.y + paint_pos_end.y)); } - + // Check for paint action - if (ImGui::IsMouseClicked(mouse_button) && + if (ImGui::IsMouseClicked(mouse_button) && ImGui::IsMouseDragging(mouse_button)) { event.is_complete = true; event.is_drag = true; } - + return event; } -TilePaintEvent HandleTilemapPaint( - const CanvasGeometry& geometry, - const gfx::Tilemap& tilemap, - int current_tile, - ImDrawList* draw_list, - ImGuiMouseButton mouse_button) { - +TilePaintEvent HandleTilemapPaint(const CanvasGeometry& geometry, + const gfx::Tilemap& tilemap, int current_tile, + ImDrawList* draw_list, + ImGuiMouseButton mouse_button) { + TilePaintEvent event; event.tile_id = current_tile; - + if (!IsMouseInCanvas(geometry)) { return event; } - + const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry); - const float scaled_size = 16.0f * geometry.scaled_size.x / geometry.canvas_sz.x; - + const float scaled_size = + 16.0f * geometry.scaled_size.x / geometry.canvas_sz.x; + ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size); event.position = mouse_pos; event.grid_position = paint_pos; - + // Draw preview if tilemap has texture if (tilemap.atlas.is_active() && draw_list) { const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x, - geometry.canvas_p0.y + geometry.scrolling.y); + geometry.canvas_p0.y + geometry.scrolling.y); // TODO(scawful): Render tilemap preview (void)origin; // Suppress unused warning } - + // Check for paint action if (ImGui::IsMouseDown(mouse_button)) { event.is_complete = true; event.is_drag = ImGui::IsMouseDragging(mouse_button); } - + return event; } @@ -337,41 +343,39 @@ TilePaintEvent HandleTilemapPaint( HoverEvent HandleHover(const CanvasGeometry& geometry, float tile_size) { HoverEvent event; - + if (!IsMouseInCanvas(geometry)) { return event; } - + const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry); - const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; - + const float scaled_size = + tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; + event.position = mouse_pos; event.grid_position = AlignToGrid(mouse_pos, scaled_size); event.is_valid = true; - + return event; } -void RenderHoverPreview( - const CanvasGeometry& geometry, - const HoverEvent& hover, - float tile_size, - ImDrawList* draw_list, - ImU32 color) { - +void RenderHoverPreview(const CanvasGeometry& geometry, const HoverEvent& hover, + float tile_size, ImDrawList* draw_list, ImU32 color) { + if (!hover.is_valid || !draw_list) { return; } - - const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; + + const float scaled_size = + tile_size * geometry.scaled_size.x / geometry.canvas_sz.x; const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x, - geometry.canvas_p0.y + geometry.scrolling.y); - + geometry.canvas_p0.y + geometry.scrolling.y); + ImVec2 preview_start = ImVec2(origin.x + hover.grid_position.x, origin.y + hover.grid_position.y); - ImVec2 preview_end = ImVec2(preview_start.x + scaled_size, - preview_start.y + scaled_size); - + ImVec2 preview_end = + ImVec2(preview_start.x + scaled_size, preview_start.y + scaled_size); + draw_list->AddRectFilled(preview_start, preview_end, color); } @@ -379,25 +383,23 @@ void RenderHoverPreview( // Entity Interaction Implementation (Stub for Phase 2.4) // ============================================================================ -EntityInteractionEvent HandleEntityInteraction( - const CanvasGeometry& geometry, - int entity_id, - ImVec2 entity_position) { - +EntityInteractionEvent HandleEntityInteraction(const CanvasGeometry& geometry, + int entity_id, + ImVec2 entity_position) { + EntityInteractionEvent event; event.entity_id = entity_id; event.position = entity_position; - + if (!IsMouseInCanvas(geometry)) { return event; } - + // TODO(scawful): Implement entity interaction logic in Phase 2.4 // For now, just return empty event - + return event; } } // namespace gui } // namespace yaze - diff --git a/src/app/gui/canvas/canvas_interaction.h b/src/app/gui/canvas/canvas_interaction.h index 877b7026..7ca8cf29 100644 --- a/src/app/gui/canvas/canvas_interaction.h +++ b/src/app/gui/canvas/canvas_interaction.h @@ -1,10 +1,10 @@ #ifndef YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_H #define YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_H -#include "app/gui/canvas/canvas_events.h" -#include "app/gui/canvas/canvas_state.h" #include "app/gfx/core/bitmap.h" #include "app/gfx/render/tilemap.h" +#include "app/gui/canvas/canvas_events.h" +#include "app/gui/canvas/canvas_state.h" #include "imgui/imgui.h" namespace yaze { @@ -43,9 +43,7 @@ namespace gui { * @return RectSelectionEvent with selection results */ RectSelectionEvent HandleRectangleSelection( - const CanvasGeometry& geometry, - int current_map, - float tile_size, + const CanvasGeometry& geometry, int current_map, float tile_size, ImDrawList* draw_list, ImGuiMouseButton mouse_button = ImGuiMouseButton_Right); @@ -61,9 +59,7 @@ RectSelectionEvent HandleRectangleSelection( * @return TileSelectionEvent with selected tile */ TileSelectionEvent HandleTileSelection( - const CanvasGeometry& geometry, - int current_map, - float tile_size, + const CanvasGeometry& geometry, int current_map, float tile_size, ImGuiMouseButton mouse_button = ImGuiMouseButton_Right); // ============================================================================ @@ -83,9 +79,7 @@ TileSelectionEvent HandleTileSelection( * @return TilePaintEvent with paint results */ TilePaintEvent HandleTilePaint( - const CanvasGeometry& geometry, - int tile_id, - float tile_size, + const CanvasGeometry& geometry, int tile_id, float tile_size, ImGuiMouseButton mouse_button = ImGuiMouseButton_Left); /** @@ -101,9 +95,7 @@ TilePaintEvent HandleTilePaint( * @return TilePaintEvent with paint results */ TilePaintEvent HandleTilePaintWithPreview( - const CanvasGeometry& geometry, - const gfx::Bitmap& bitmap, - float tile_size, + const CanvasGeometry& geometry, const gfx::Bitmap& bitmap, float tile_size, ImDrawList* draw_list, ImGuiMouseButton mouse_button = ImGuiMouseButton_Left); @@ -120,10 +112,8 @@ TilePaintEvent HandleTilePaintWithPreview( * @return TilePaintEvent with paint results */ TilePaintEvent HandleTilemapPaint( - const CanvasGeometry& geometry, - const gfx::Tilemap& tilemap, - int current_tile, - ImDrawList* draw_list, + const CanvasGeometry& geometry, const gfx::Tilemap& tilemap, + int current_tile, ImDrawList* draw_list, ImGuiMouseButton mouse_button = ImGuiMouseButton_Left); // ============================================================================ @@ -152,12 +142,9 @@ HoverEvent HandleHover(const CanvasGeometry& geometry, float tile_size); * @param draw_list ImGui draw list * @param color Preview color (default: white with alpha) */ -void RenderHoverPreview( - const CanvasGeometry& geometry, - const HoverEvent& hover, - float tile_size, - ImDrawList* draw_list, - ImU32 color = IM_COL32(255, 255, 255, 80)); +void RenderHoverPreview(const CanvasGeometry& geometry, const HoverEvent& hover, + float tile_size, ImDrawList* draw_list, + ImU32 color = IM_COL32(255, 255, 255, 80)); // ============================================================================ // Entity Interaction (Phase 2.4 - Future) @@ -173,10 +160,9 @@ void RenderHoverPreview( * @param entity_position Current entity position * @return EntityInteractionEvent with interaction results */ -EntityInteractionEvent HandleEntityInteraction( - const CanvasGeometry& geometry, - int entity_id, - ImVec2 entity_position); +EntityInteractionEvent HandleEntityInteraction(const CanvasGeometry& geometry, + int entity_id, + ImVec2 entity_position); // ============================================================================ // Helper Functions @@ -225,4 +211,3 @@ ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, float global_scale); } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_H - diff --git a/src/app/gui/canvas/canvas_interaction_handler.cc b/src/app/gui/canvas/canvas_interaction_handler.cc index 94725987..fc0c418f 100644 --- a/src/app/gui/canvas/canvas_interaction_handler.cc +++ b/src/app/gui/canvas/canvas_interaction_handler.cc @@ -33,34 +33,36 @@ void CanvasInteractionHandler::ClearState() { } TileInteractionResult CanvasInteractionHandler::Update( - ImVec2 canvas_p0, ImVec2 scrolling, float /*global_scale*/, float /*tile_size*/, - ImVec2 /*canvas_size*/, bool is_hovered) { - + ImVec2 canvas_p0, ImVec2 scrolling, float /*global_scale*/, + float /*tile_size*/, ImVec2 /*canvas_size*/, bool is_hovered) { + TileInteractionResult result; - + if (!is_hovered) { hover_points_.clear(); return result; } - + const ImGuiIO& imgui_io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); - mouse_pos_in_canvas_ = ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); - + mouse_pos_in_canvas_ = + ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); + // Update based on current mode - each mode is handled by its specific Draw method // This method exists for future state updates if needed (void)current_mode_; // Suppress unused warning - + return result; } bool CanvasInteractionHandler::DrawTilePainter( const gfx::Bitmap& bitmap, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered) { - + const ImGuiIO& imgui_io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); - const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); + const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, + imgui_io.MousePos.y - origin.y); const auto scaled_size = tile_size * global_scale; // Clear hover when not hovering @@ -75,17 +77,18 @@ bool CanvasInteractionHandler::DrawTilePainter( // Calculate grid-aligned paint position ImVec2 paint_pos = AlignToGridLocal(mouse_pos, scaled_size); mouse_pos_in_canvas_ = paint_pos; - auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size); - + auto paint_pos_end = + ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size); + hover_points_.push_back(paint_pos); hover_points_.push_back(paint_pos_end); // Draw preview of tile at hover position if (bitmap.is_active() && draw_list) { - draw_list->AddImage( - reinterpret_cast(bitmap.texture()), - ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y), - ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size)); + draw_list->AddImage(reinterpret_cast(bitmap.texture()), + ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y), + ImVec2(origin.x + paint_pos.x + scaled_size, + origin.y + paint_pos.y + scaled_size)); } // Check for paint action @@ -101,16 +104,17 @@ bool CanvasInteractionHandler::DrawTilePainter( bool CanvasInteractionHandler::DrawTilemapPainter( gfx::Tilemap& tilemap, int current_tile, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, bool is_hovered) { - + const ImGuiIO& imgui_io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); - const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); - + const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, + imgui_io.MousePos.y - origin.y); + // Safety check if (!tilemap.atlas.is_active() || tilemap.tile_size.x <= 0) { return false; } - + const auto scaled_size = tilemap.tile_size.x * global_scale; if (!is_hovered) { @@ -119,12 +123,13 @@ bool CanvasInteractionHandler::DrawTilemapPainter( } hover_points_.clear(); - + ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size); mouse_pos_in_canvas_ = paint_pos; hover_points_.push_back(paint_pos); - hover_points_.push_back(ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size)); + hover_points_.push_back( + ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size)); // Draw tile preview from atlas if (tilemap.atlas.is_active() && tilemap.atlas.texture() && draw_list) { @@ -132,19 +137,23 @@ bool CanvasInteractionHandler::DrawTilemapPainter( if (tiles_per_row > 0) { int tile_x = (current_tile % tiles_per_row) * tilemap.tile_size.x; int tile_y = (current_tile / tiles_per_row) * tilemap.tile_size.y; - - if (tile_x >= 0 && tile_x < tilemap.atlas.width() && - tile_y >= 0 && tile_y < tilemap.atlas.height()) { - - ImVec2 uv0 = ImVec2(static_cast(tile_x) / tilemap.atlas.width(), - static_cast(tile_y) / tilemap.atlas.height()); - ImVec2 uv1 = ImVec2(static_cast(tile_x + tilemap.tile_size.x) / tilemap.atlas.width(), - static_cast(tile_y + tilemap.tile_size.y) / tilemap.atlas.height()); - + + if (tile_x >= 0 && tile_x < tilemap.atlas.width() && tile_y >= 0 && + tile_y < tilemap.atlas.height()) { + + ImVec2 uv0 = + ImVec2(static_cast(tile_x) / tilemap.atlas.width(), + static_cast(tile_y) / tilemap.atlas.height()); + ImVec2 uv1 = ImVec2(static_cast(tile_x + tilemap.tile_size.x) / + tilemap.atlas.width(), + static_cast(tile_y + tilemap.tile_size.y) / + tilemap.atlas.height()); + draw_list->AddImage( reinterpret_cast(tilemap.atlas.texture()), ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y), - ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size), + ImVec2(origin.x + paint_pos.x + scaled_size, + origin.y + paint_pos.y + scaled_size), uv0, uv1); } } @@ -162,10 +171,11 @@ bool CanvasInteractionHandler::DrawTilemapPainter( bool CanvasInteractionHandler::DrawSolidTilePainter( const ImVec4& color, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered) { - + const ImGuiIO& imgui_io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); - const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); + const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, + imgui_io.MousePos.y - origin.y); auto scaled_tile_size = tile_size * global_scale; static bool is_dragging = false; static ImVec2 start_drag_pos; @@ -184,7 +194,8 @@ bool CanvasInteractionHandler::DrawSolidTilePainter( // For now, skip clamping as we don't have canvas_size here hover_points_.push_back(paint_pos); - hover_points_.push_back(ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size)); + hover_points_.push_back( + ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size)); if (draw_list) { draw_list->AddRectFilled( @@ -208,20 +219,24 @@ bool CanvasInteractionHandler::DrawSolidTilePainter( return false; } -bool CanvasInteractionHandler::DrawTileSelector( - ImDrawList* /*draw_list*/, ImVec2 canvas_p0, ImVec2 scrolling, float tile_size, - bool is_hovered) { - +bool CanvasInteractionHandler::DrawTileSelector(ImDrawList* /*draw_list*/, + ImVec2 canvas_p0, + ImVec2 scrolling, + float tile_size, + bool is_hovered) { + const ImGuiIO& imgui_io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); - const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); + const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, + imgui_io.MousePos.y - origin.y); if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { hover_points_.clear(); ImVec2 painter_pos = AlignToGridLocal(mouse_pos, tile_size); hover_points_.push_back(painter_pos); - hover_points_.push_back(ImVec2(painter_pos.x + tile_size, painter_pos.y + tile_size)); + hover_points_.push_back( + ImVec2(painter_pos.x + tile_size, painter_pos.y + tile_size)); mouse_pos_in_canvas_ = painter_pos; } @@ -235,22 +250,24 @@ bool CanvasInteractionHandler::DrawTileSelector( bool CanvasInteractionHandler::DrawSelectRect( int current_map, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered) { - + if (!is_hovered) { return false; } - + // Create CanvasGeometry from parameters CanvasGeometry geometry; geometry.canvas_p0 = canvas_p0; geometry.scrolling = scrolling; - geometry.scaled_size = ImVec2(tile_size * global_scale, tile_size * global_scale); - geometry.canvas_sz = ImVec2(tile_size, tile_size); // Will be updated if needed - + geometry.scaled_size = + ImVec2(tile_size * global_scale, tile_size * global_scale); + geometry.canvas_sz = + ImVec2(tile_size, tile_size); // Will be updated if needed + // Call new event-based function RectSelectionEvent event = HandleRectangleSelection( geometry, current_map, tile_size, draw_list, ImGuiMouseButton_Right); - + // Update internal state for backward compatibility if (event.is_complete) { selected_tiles_ = event.selected_tiles; @@ -260,16 +277,16 @@ bool CanvasInteractionHandler::DrawSelectRect( rect_select_active_ = true; return true; } - + if (!event.selected_tiles.empty() && !event.is_complete) { // Single tile selection selected_tile_pos_ = event.selected_tiles[0]; selected_points_.clear(); rect_select_active_ = false; } - + rect_select_active_ = event.is_active; - + return false; } @@ -279,7 +296,8 @@ ImVec2 CanvasInteractionHandler::AlignPosToGrid(ImVec2 pos, float grid_step) { return AlignToGridLocal(pos, grid_step); } -ImVec2 CanvasInteractionHandler::GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling) { +ImVec2 CanvasInteractionHandler::GetMousePosition(ImVec2 canvas_p0, + ImVec2 scrolling) { const ImGuiIO& imgui_io = ImGui::GetIO(); const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y); diff --git a/src/app/gui/canvas/canvas_interaction_handler.h b/src/app/gui/canvas/canvas_interaction_handler.h index 4c6f3d17..27122ef7 100644 --- a/src/app/gui/canvas/canvas_interaction_handler.h +++ b/src/app/gui/canvas/canvas_interaction_handler.h @@ -13,12 +13,12 @@ namespace gui { * @brief Tile interaction mode for canvas */ enum class TileInteractionMode { - kNone, // No interaction - kPaintSingle, // Paint single tiles - kPaintDrag, // Paint while dragging - kSelectSingle, // Select single tile - kSelectRectangle, // Select rectangular region - kColorPaint // Paint with solid color + kNone, // No interaction + kPaintSingle, // Paint single tiles + kPaintDrag, // Paint while dragging + kSelectSingle, // Select single tile + kSelectRectangle, // Select rectangular region + kColorPaint // Paint with solid color }; /** @@ -29,7 +29,7 @@ struct TileInteractionResult { ImVec2 tile_position = ImVec2(-1, -1); std::vector selected_tiles; int tile_id = -1; - + void Reset() { interaction_occurred = false; tile_position = ImVec2(-1, -1); @@ -57,18 +57,18 @@ struct TileInteractionResult { class CanvasInteractionHandler { public: CanvasInteractionHandler() = default; - + /** * @brief Initialize the interaction handler */ void Initialize(const std::string& canvas_id); - + /** * @brief Set the interaction mode */ void SetMode(TileInteractionMode mode) { current_mode_ = mode; } TileInteractionMode GetMode() const { return current_mode_; } - + /** * @brief Update interaction state (call once per frame) * @param canvas_p0 Canvas top-left screen position @@ -79,10 +79,10 @@ class CanvasInteractionHandler { * @param is_hovered Whether mouse is over canvas * @return Interaction result for this frame */ - TileInteractionResult Update(ImVec2 canvas_p0, ImVec2 scrolling, + TileInteractionResult Update(ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, ImVec2 canvas_size, bool is_hovered); - + /** * @brief Draw tile painter (preview + interaction) * @param bitmap Tile bitmap to paint @@ -95,29 +95,31 @@ class CanvasInteractionHandler { * @return True if tile was painted */ bool DrawTilePainter(const gfx::Bitmap& bitmap, ImDrawList* draw_list, - ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, - float tile_size, bool is_hovered); - + ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, + float tile_size, bool is_hovered); + /** * @brief Draw tilemap painter (preview + interaction) */ - bool DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile, - ImDrawList* draw_list, ImVec2 canvas_p0, - ImVec2 scrolling, float global_scale, bool is_hovered); - + bool DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile, + ImDrawList* draw_list, ImVec2 canvas_p0, + ImVec2 scrolling, float global_scale, + bool is_hovered); + /** * @brief Draw solid color painter */ bool DrawSolidTilePainter(const ImVec4& color, ImDrawList* draw_list, - ImVec2 canvas_p0, ImVec2 scrolling, - float global_scale, float tile_size, bool is_hovered); - + ImVec2 canvas_p0, ImVec2 scrolling, + float global_scale, float tile_size, + bool is_hovered); + /** * @brief Draw tile selector (single tile selection) */ - bool DrawTileSelector(ImDrawList* draw_list, ImVec2 canvas_p0, - ImVec2 scrolling, float tile_size, bool is_hovered); - + bool DrawTileSelector(ImDrawList* draw_list, ImVec2 canvas_p0, + ImVec2 scrolling, float tile_size, bool is_hovered); + /** * @brief Draw rectangle selector (multi-tile selection) * @param current_map Map ID for coordinate calculation @@ -130,49 +132,51 @@ class CanvasInteractionHandler { * @return True if selection was made */ bool DrawSelectRect(int current_map, ImDrawList* draw_list, ImVec2 canvas_p0, - ImVec2 scrolling, float global_scale, float tile_size, - bool is_hovered); - + ImVec2 scrolling, float global_scale, float tile_size, + bool is_hovered); + /** * @brief Get current hover points (for DrawOverlay) */ const ImVector& GetHoverPoints() const { return hover_points_; } - + /** * @brief Get selected points (for DrawOverlay) */ const ImVector& GetSelectedPoints() const { return selected_points_; } - + /** * @brief Get selected tiles from last rectangle selection */ - const std::vector& GetSelectedTiles() const { return selected_tiles_; } - + const std::vector& GetSelectedTiles() const { + return selected_tiles_; + } + /** * @brief Get last drawn tile position */ ImVec2 GetDrawnTilePosition() const { return drawn_tile_pos_; } - + /** * @brief Get current mouse position in canvas space */ ImVec2 GetMousePositionInCanvas() const { return mouse_pos_in_canvas_; } - + /** * @brief Clear all interaction state */ void ClearState(); - + /** * @brief Check if rectangle selection is active */ bool IsRectSelectActive() const { return rect_select_active_; } - + /** * @brief Get selected tile position (for single selection) */ ImVec2 GetSelectedTilePosition() const { return selected_tile_pos_; } - + /** * @brief Set selected tile position */ @@ -181,16 +185,16 @@ class CanvasInteractionHandler { private: std::string canvas_id_; TileInteractionMode current_mode_ = TileInteractionMode::kNone; - + // Interaction state - ImVector hover_points_; // Current hover preview points - ImVector selected_points_; // Selected rectangle points - std::vector selected_tiles_; // Selected tiles from rect - ImVec2 drawn_tile_pos_ = ImVec2(-1, -1); // Last drawn tile position - ImVec2 mouse_pos_in_canvas_ = ImVec2(0, 0); // Current mouse in canvas space - ImVec2 selected_tile_pos_ = ImVec2(-1, -1); // Single tile selection + ImVector hover_points_; // Current hover preview points + ImVector selected_points_; // Selected rectangle points + std::vector selected_tiles_; // Selected tiles from rect + ImVec2 drawn_tile_pos_ = ImVec2(-1, -1); // Last drawn tile position + ImVec2 mouse_pos_in_canvas_ = ImVec2(0, 0); // Current mouse in canvas space + ImVec2 selected_tile_pos_ = ImVec2(-1, -1); // Single tile selection bool rect_select_active_ = false; - + // Helper methods ImVec2 AlignPosToGrid(ImVec2 pos, float grid_step); ImVec2 GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling); diff --git a/src/app/gui/canvas/canvas_menu.cc b/src/app/gui/canvas/canvas_menu.cc index 791f4197..60912fcc 100644 --- a/src/app/gui/canvas/canvas_menu.cc +++ b/src/app/gui/canvas/canvas_menu.cc @@ -3,52 +3,55 @@ namespace yaze { namespace gui { -void RenderMenuItem(const CanvasMenuItem& item, - std::function)> - popup_opened_callback) { +void RenderMenuItem( + const CanvasMenuItem& item, + std::function)> + popup_opened_callback) { // Check visibility if (!item.visible_condition()) { return; } - + // Apply disabled state if needed if (!item.enabled_condition()) { ImGui::BeginDisabled(); } - + // Build label with icon if present std::string display_label = item.label; if (!item.icon.empty()) { display_label = item.icon + " " + item.label; } - + // Render menu item based on type if (item.subitems.empty()) { // Simple menu item bool selected = false; - if (item.color.x != 1.0f || item.color.y != 1.0f || - item.color.z != 1.0f || item.color.w != 1.0f) { + if (item.color.x != 1.0f || item.color.y != 1.0f || item.color.z != 1.0f || + item.color.w != 1.0f) { // Render with custom color ImGui::PushStyleColor(ImGuiCol_Text, item.color); - selected = ImGui::MenuItem(display_label.c_str(), - item.shortcut.empty() ? nullptr : item.shortcut.c_str()); + selected = ImGui::MenuItem( + display_label.c_str(), + item.shortcut.empty() ? nullptr : item.shortcut.c_str()); ImGui::PopStyleColor(); } else { - selected = ImGui::MenuItem(display_label.c_str(), - item.shortcut.empty() ? nullptr : item.shortcut.c_str()); + selected = ImGui::MenuItem( + display_label.c_str(), + item.shortcut.empty() ? nullptr : item.shortcut.c_str()); } - + if (selected) { // Invoke callback if (item.callback) { item.callback(); } - + // Handle popup if defined - if (item.popup.has_value() && - item.popup->auto_open_on_select && + if (item.popup.has_value() && item.popup->auto_open_on_select && popup_opened_callback) { - popup_opened_callback(item.popup->popup_id, item.popup->render_callback); + popup_opened_callback(item.popup->popup_id, + item.popup->render_callback); } } } else { @@ -60,51 +63,53 @@ void RenderMenuItem(const CanvasMenuItem& item, ImGui::EndMenu(); } } - + // Restore enabled state if (!item.enabled_condition()) { ImGui::EndDisabled(); } - + // Render separator if requested if (item.separator_after) { ImGui::Separator(); } } -void RenderMenuSection(const CanvasMenuSection& section, - std::function)> - popup_opened_callback) { +void RenderMenuSection( + const CanvasMenuSection& section, + std::function)> + popup_opened_callback) { // Skip empty sections if (section.items.empty()) { return; } - + // Render section title if present if (!section.title.empty()) { ImGui::TextColored(section.title_color, "%s", section.title.c_str()); ImGui::Separator(); } - + // Render all items in section for (const auto& item : section.items) { RenderMenuItem(item, popup_opened_callback); } - + // Render separator after section if requested if (section.separator_after) { ImGui::Separator(); } } -void RenderCanvasMenu(const CanvasMenuDefinition& menu, - std::function)> - popup_opened_callback) { +void RenderCanvasMenu( + const CanvasMenuDefinition& menu, + std::function)> + popup_opened_callback) { // Skip disabled menus if (!menu.enabled) { return; } - + // Render all sections for (const auto& section : menu.sections) { RenderMenuSection(section, popup_opened_callback); @@ -113,4 +118,3 @@ void RenderCanvasMenu(const CanvasMenuDefinition& menu, } // namespace gui } // namespace yaze - diff --git a/src/app/gui/canvas/canvas_menu.h b/src/app/gui/canvas/canvas_menu.h index 5dbd02cf..76ec36df 100644 --- a/src/app/gui/canvas/canvas_menu.h +++ b/src/app/gui/canvas/canvas_menu.h @@ -21,10 +21,10 @@ namespace gui { * - Debug/performance (30) at the bottom */ enum class MenuSectionPriority { - kEditorSpecific = 0, // Highest priority - editor-specific actions - kBitmapPalette = 10, // Medium priority - bitmap/palette operations - kCanvasProperties = 20, // Low priority - canvas settings - kDebug = 30 // Lowest priority - debug/performance + kEditorSpecific = 0, // Highest priority - editor-specific actions + kBitmapPalette = 10, // Medium priority - bitmap/palette operations + kCanvasProperties = 20, // Low priority - canvas settings + kDebug = 30 // Lowest priority - debug/performance }; /** @@ -36,19 +36,19 @@ enum class MenuSectionPriority { struct CanvasPopupDefinition { // Unique popup identifier for ImGui std::string popup_id; - + // Callback that renders the popup content (should call ImGui::BeginPopup/EndPopup) std::function render_callback; - + // Whether to automatically open the popup when menu item is selected bool auto_open_on_select = true; - + // Whether the popup should persist across frames until explicitly closed bool persist_across_frames = true; - + // Default constructor CanvasPopupDefinition() = default; - + // Constructor for simple popups CanvasPopupDefinition(const std::string& id, std::function callback) : popup_id(id), render_callback(std::move(callback)) {} @@ -63,70 +63,76 @@ struct CanvasPopupDefinition { struct CanvasMenuItem { // Display label for the menu item std::string label; - + // Optional icon (Material Design icon name or Unicode glyph) std::string icon; - + // Optional keyboard shortcut display (e.g., "Ctrl+S") std::string shortcut; - + // Callback invoked when menu item is selected std::function callback; - + // Optional popup definition - if present, popup will be managed automatically std::optional popup; - + // Condition to determine if menu item is enabled - std::function enabled_condition = []() { return true; }; - + std::function enabled_condition = []() { + return true; + }; + // Condition to determine if menu item is visible - std::function visible_condition = []() { return true; }; - + std::function visible_condition = []() { + return true; + }; + // Nested submenu items std::vector subitems; - + // Color for the menu item label ImVec4 color = ImVec4(1, 1, 1, 1); - + // Whether to show a separator after this item bool separator_after = false; - + // Default constructor CanvasMenuItem() = default; - + // Simple menu item constructor CanvasMenuItem(const std::string& lbl, std::function cb) : label(lbl), callback(std::move(cb)) {} - + // Menu item with icon CanvasMenuItem(const std::string& lbl, const std::string& ico, std::function cb) : label(lbl), icon(ico), callback(std::move(cb)) {} - + // Menu item with icon and shortcut CanvasMenuItem(const std::string& lbl, const std::string& ico, std::function cb, const std::string& sc) : label(lbl), icon(ico), callback(std::move(cb)), shortcut(sc) {} - + // Helper to create a disabled menu item static CanvasMenuItem Disabled(const std::string& lbl) { CanvasMenuItem item; item.label = lbl; - item.enabled_condition = []() { return false; }; + item.enabled_condition = []() { + return false; + }; return item; } - + // Helper to create a conditional menu item static CanvasMenuItem Conditional(const std::string& lbl, - std::function cb, - std::function condition) { + std::function cb, + std::function condition) { CanvasMenuItem item; item.label = lbl; item.callback = std::move(cb); item.enabled_condition = std::move(condition); return item; } - + // Helper to create a menu item with popup static CanvasMenuItem WithPopup(const std::string& lbl, const std::string& popup_id, @@ -147,32 +153,34 @@ struct CanvasMenuItem { struct CanvasMenuSection { // Optional section title (rendered as colored text) std::string title; - + // Color for section title ImVec4 title_color = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); - + // Menu items in this section std::vector items; - + // Whether to show a separator after this section bool separator_after = true; - + // Priority for ordering sections (lower values render first) MenuSectionPriority priority = MenuSectionPriority::kEditorSpecific; - + // Default constructor CanvasMenuSection() = default; - + // Constructor with title explicit CanvasMenuSection(const std::string& t) : title(t) {} - + // Constructor with title and items - CanvasMenuSection(const std::string& t, const std::vector& its) + CanvasMenuSection(const std::string& t, + const std::vector& its) : title(t), items(its) {} - + // Constructor with title, items, and priority - CanvasMenuSection(const std::string& t, const std::vector& its, - MenuSectionPriority prio) + CanvasMenuSection(const std::string& t, + const std::vector& its, + MenuSectionPriority prio) : title(t), items(its), priority(prio) {} }; @@ -184,22 +192,22 @@ struct CanvasMenuSection { struct CanvasMenuDefinition { // Menu sections (rendered in order) std::vector sections; - + // Whether the menu is enabled bool enabled = true; - + // Default constructor CanvasMenuDefinition() = default; - + // Constructor with sections explicit CanvasMenuDefinition(const std::vector& secs) : sections(secs) {} - + // Add a section void AddSection(const CanvasMenuSection& section) { sections.push_back(section); } - + // Add items without a section title void AddItems(const std::vector& items) { CanvasMenuSection section; @@ -219,9 +227,10 @@ struct CanvasMenuDefinition { * @param item Menu item to render * @param popup_opened_callback Optional callback invoked when popup is opened */ -void RenderMenuItem(const CanvasMenuItem& item, - std::function)> - popup_opened_callback = nullptr); +void RenderMenuItem( + const CanvasMenuItem& item, + std::function)> + popup_opened_callback = nullptr); /** * @brief Render a menu section @@ -231,9 +240,10 @@ void RenderMenuItem(const CanvasMenuItem& item, * @param section Menu section to render * @param popup_opened_callback Optional callback invoked when popup is opened */ -void RenderMenuSection(const CanvasMenuSection& section, - std::function)> - popup_opened_callback = nullptr); +void RenderMenuSection( + const CanvasMenuSection& section, + std::function)> + popup_opened_callback = nullptr); /** * @brief Render a complete menu definition @@ -244,12 +254,12 @@ void RenderMenuSection(const CanvasMenuSection& section, * @param menu Menu definition to render * @param popup_opened_callback Optional callback invoked when popup is opened */ -void RenderCanvasMenu(const CanvasMenuDefinition& menu, - std::function)> - popup_opened_callback = nullptr); +void RenderCanvasMenu( + const CanvasMenuDefinition& menu, + std::function)> + popup_opened_callback = nullptr); } // namespace gui } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_MENU_H - diff --git a/src/app/gui/canvas/canvas_menu_builder.cc b/src/app/gui/canvas/canvas_menu_builder.cc index 8c25ee1b..cc48b3cc 100644 --- a/src/app/gui/canvas/canvas_menu_builder.cc +++ b/src/app/gui/canvas/canvas_menu_builder.cc @@ -4,7 +4,7 @@ namespace yaze { namespace gui { CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label, - std::function callback) { + std::function callback) { CanvasMenuItem item; item.label = label; item.callback = std::move(callback); @@ -13,8 +13,8 @@ CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label, } CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label, - const std::string& icon, - std::function callback) { + const std::string& icon, + std::function callback) { CanvasMenuItem item; item.label = label; item.icon = icon; @@ -24,9 +24,9 @@ CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label, } CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label, - const std::string& icon, - const std::string& shortcut, - std::function callback) { + const std::string& icon, + const std::string& shortcut, + std::function callback) { CanvasMenuItem item; item.label = label; item.icon = icon; @@ -39,15 +39,17 @@ CanvasMenuBuilder& CanvasMenuBuilder::AddItem(const std::string& label, CanvasMenuBuilder& CanvasMenuBuilder::AddPopupItem( const std::string& label, const std::string& popup_id, std::function render_callback) { - CanvasMenuItem item = CanvasMenuItem::WithPopup(label, popup_id, render_callback); + CanvasMenuItem item = + CanvasMenuItem::WithPopup(label, popup_id, render_callback); pending_items_.push_back(item); return *this; } CanvasMenuBuilder& CanvasMenuBuilder::AddPopupItem( - const std::string& label, const std::string& icon, + const std::string& label, const std::string& icon, const std::string& popup_id, std::function render_callback) { - CanvasMenuItem item = CanvasMenuItem::WithPopup(label, popup_id, render_callback); + CanvasMenuItem item = + CanvasMenuItem::WithPopup(label, popup_id, render_callback); item.icon = icon; pending_items_.push_back(item); return *this; @@ -81,17 +83,17 @@ CanvasMenuBuilder& CanvasMenuBuilder::BeginSection( const std::string& title, MenuSectionPriority priority) { // Flush any pending items to previous section FlushPendingItems(); - + // Create new section CanvasMenuSection section; section.title = title; section.priority = priority; section.separator_after = true; menu_.sections.push_back(section); - + // Point current_section_ to the newly added section current_section_ = &menu_.sections.back(); - + return *this; } @@ -117,7 +119,7 @@ void CanvasMenuBuilder::FlushPendingItems() { if (pending_items_.empty()) { return; } - + // If no section exists yet, create a default one if (menu_.sections.empty()) { CanvasMenuSection section; @@ -126,20 +128,21 @@ void CanvasMenuBuilder::FlushPendingItems() { menu_.sections.push_back(section); current_section_ = &menu_.sections.back(); } - + // Add pending items to current section if (current_section_) { current_section_->items.insert(current_section_->items.end(), - pending_items_.begin(), pending_items_.end()); + pending_items_.begin(), + pending_items_.end()); } else { // Add to last section if current_section_ is null menu_.sections.back().items.insert(menu_.sections.back().items.end(), - pending_items_.begin(), pending_items_.end()); + pending_items_.begin(), + pending_items_.end()); } - + pending_items_.clear(); } } // namespace gui } // namespace yaze - diff --git a/src/app/gui/canvas/canvas_menu_builder.h b/src/app/gui/canvas/canvas_menu_builder.h index 6b3c8ff5..200fcdda 100644 --- a/src/app/gui/canvas/canvas_menu_builder.h +++ b/src/app/gui/canvas/canvas_menu_builder.h @@ -30,7 +30,7 @@ namespace gui { class CanvasMenuBuilder { public: CanvasMenuBuilder() = default; - + /** * @brief Add a simple menu item * @param label Menu item label @@ -38,8 +38,8 @@ class CanvasMenuBuilder { * @return Reference to this builder for chaining */ CanvasMenuBuilder& AddItem(const std::string& label, - std::function callback); - + std::function callback); + /** * @brief Add a menu item with icon * @param label Menu item label @@ -47,10 +47,9 @@ class CanvasMenuBuilder { * @param callback Action to perform when selected * @return Reference to this builder for chaining */ - CanvasMenuBuilder& AddItem(const std::string& label, - const std::string& icon, - std::function callback); - + CanvasMenuBuilder& AddItem(const std::string& label, const std::string& icon, + std::function callback); + /** * @brief Add a menu item with icon and shortcut hint * @param label Menu item label @@ -59,11 +58,10 @@ class CanvasMenuBuilder { * @param callback Action to perform when selected * @return Reference to this builder for chaining */ - CanvasMenuBuilder& AddItem(const std::string& label, - const std::string& icon, - const std::string& shortcut, - std::function callback); - + CanvasMenuBuilder& AddItem(const std::string& label, const std::string& icon, + const std::string& shortcut, + std::function callback); + /** * @brief Add a menu item that opens a persistent popup * @param label Menu item label @@ -72,9 +70,9 @@ class CanvasMenuBuilder { * @return Reference to this builder for chaining */ CanvasMenuBuilder& AddPopupItem(const std::string& label, - const std::string& popup_id, - std::function render_callback); - + const std::string& popup_id, + std::function render_callback); + /** * @brief Add a menu item with icon that opens a persistent popup * @param label Menu item label @@ -84,10 +82,10 @@ class CanvasMenuBuilder { * @return Reference to this builder for chaining */ CanvasMenuBuilder& AddPopupItem(const std::string& label, - const std::string& icon, - const std::string& popup_id, - std::function render_callback); - + const std::string& icon, + const std::string& popup_id, + std::function render_callback); + /** * @brief Add a conditional menu item (enabled only when condition is true) * @param label Menu item label @@ -96,9 +94,9 @@ class CanvasMenuBuilder { * @return Reference to this builder for chaining */ CanvasMenuBuilder& AddConditionalItem(const std::string& label, - std::function callback, - std::function condition); - + std::function callback, + std::function condition); + /** * @brief Add a submenu with nested items * @param label Submenu label @@ -106,14 +104,14 @@ class CanvasMenuBuilder { * @return Reference to this builder for chaining */ CanvasMenuBuilder& AddSubmenu(const std::string& label, - const std::vector& subitems); - + const std::vector& subitems); + /** * @brief Add a separator to visually group items * @return Reference to this builder for chaining */ CanvasMenuBuilder& AddSeparator(); - + /** * @brief Start a new section with optional title * @param title Section title (empty for no title) @@ -123,30 +121,30 @@ class CanvasMenuBuilder { CanvasMenuBuilder& BeginSection( const std::string& title = "", MenuSectionPriority priority = MenuSectionPriority::kEditorSpecific); - + /** * @brief End the current section * @return Reference to this builder for chaining */ CanvasMenuBuilder& EndSection(); - + /** * @brief Build the final menu definition * @return Complete menu definition ready for rendering */ CanvasMenuDefinition Build(); - + /** * @brief Reset the builder to start building a new menu * @return Reference to this builder for chaining */ CanvasMenuBuilder& Reset(); - + private: CanvasMenuDefinition menu_; CanvasMenuSection* current_section_ = nullptr; std::vector pending_items_; - + void FlushPendingItems(); }; @@ -154,4 +152,3 @@ class CanvasMenuBuilder { } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_MENU_BUILDER_H - diff --git a/src/app/gui/canvas/canvas_modals.cc b/src/app/gui/canvas/canvas_modals.cc index 52b5a5e5..9047cc20 100644 --- a/src/app/gui/canvas/canvas_modals.cc +++ b/src/app/gui/canvas/canvas_modals.cc @@ -1,14 +1,14 @@ #include "canvas_modals.h" #include -#include #include +#include -#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/debug/performance/performance_dashboard.h" -#include "app/gui/widgets/palette_editor_widget.h" +#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gui/canvas/bpp_format_ui.h" #include "app/gui/core/icons.h" +#include "app/gui/widgets/palette_editor_widget.h" #include "imgui/imgui.h" namespace yaze { @@ -16,94 +16,98 @@ namespace gui { // Helper functions for dispatching config callbacks namespace { -inline void DispatchConfig(const std::function& callback, - const CanvasConfig& config) { - if (callback) callback(config); +inline void DispatchConfig( + const std::function& callback, + const CanvasConfig& config) { + if (callback) + callback(config); } -inline void DispatchScale(const std::function& callback, - const CanvasConfig& config) { - if (callback) callback(config); +inline void DispatchScale( + const std::function& callback, + const CanvasConfig& config) { + if (callback) + callback(config); } } // namespace void CanvasModals::ShowAdvancedProperties(const std::string& canvas_id, - const CanvasConfig& config, - const gfx::Bitmap* bitmap) { - + const CanvasConfig& config, + const gfx::Bitmap* bitmap) { + std::string modal_id = canvas_id + "_advanced_properties"; - + auto render_func = [=]() mutable { CanvasConfig mutable_config = config; // Create mutable copy mutable_config.on_config_changed = config.on_config_changed; mutable_config.on_scale_changed = config.on_scale_changed; RenderAdvancedPropertiesModal(modal_id, mutable_config, bitmap); }; - + OpenModal(modal_id, render_func); } void CanvasModals::ShowScalingControls(const std::string& canvas_id, - const CanvasConfig& config, - const gfx::Bitmap* bitmap) { - + const CanvasConfig& config, + const gfx::Bitmap* bitmap) { + std::string modal_id = canvas_id + "_scaling_controls"; - + auto render_func = [=]() mutable { CanvasConfig mutable_config = config; // Create mutable copy mutable_config.on_config_changed = config.on_config_changed; mutable_config.on_scale_changed = config.on_scale_changed; RenderScalingControlsModal(modal_id, mutable_config, bitmap); }; - + OpenModal(modal_id, render_func); } -void CanvasModals::ShowBppConversionDialog(const std::string& canvas_id, - const BppConversionOptions& options) { - +void CanvasModals::ShowBppConversionDialog( + const std::string& canvas_id, const BppConversionOptions& options) { + std::string modal_id = canvas_id + "_bpp_conversion"; - + auto render_func = [=]() { RenderBppConversionModal(modal_id, options); }; - + OpenModal(modal_id, render_func); } void CanvasModals::ShowPaletteEditor(const std::string& canvas_id, - const PaletteEditorOptions& options) { - + const PaletteEditorOptions& options) { + std::string modal_id = canvas_id + "_palette_editor"; - + auto render_func = [=]() { RenderPaletteEditorModal(modal_id, options); }; - + OpenModal(modal_id, render_func); } void CanvasModals::ShowColorAnalysis(const std::string& canvas_id, - const ColorAnalysisOptions& options) { - + const ColorAnalysisOptions& options) { + std::string modal_id = canvas_id + "_color_analysis"; - + auto render_func = [=]() { RenderColorAnalysisModal(modal_id, options); }; - + OpenModal(modal_id, render_func); } -void CanvasModals::ShowPerformanceIntegration(const std::string& canvas_id, - const PerformanceOptions& options) { - +void CanvasModals::ShowPerformanceIntegration( + const std::string& canvas_id, const PerformanceOptions& options) { + std::string modal_id = canvas_id + "_performance"; - + auto render_func = [=]() { RenderPerformanceModal(modal_id, options); }; - + OpenModal(modal_id, render_func); } @@ -113,12 +117,12 @@ void CanvasModals::Render() { modal.render_func(); } } - + // Remove closed modals active_modals_.erase( - std::remove_if(active_modals_.begin(), active_modals_.end(), - [](const ModalState& modal) { return !modal.is_open; }), - active_modals_.end()); + std::remove_if(active_modals_.begin(), active_modals_.end(), + [](const ModalState& modal) { return !modal.is_open; }), + active_modals_.end()); } bool CanvasModals::IsAnyModalOpen() const { @@ -127,147 +131,161 @@ bool CanvasModals::IsAnyModalOpen() const { } void CanvasModals::RenderAdvancedPropertiesModal(const std::string& canvas_id, - CanvasConfig& config, - const gfx::Bitmap* bitmap) { - + CanvasConfig& config, + const gfx::Bitmap* bitmap) { + std::string modal_title = "Advanced Canvas Properties"; ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver); - - if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - + + if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + // Header with icon ImGui::Text("%s %s", ICON_MD_SETTINGS, modal_title.c_str()); ImGui::Separator(); - + // Canvas Information Section - if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Canvas Information", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Canvas Information", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Columns(2, "CanvasInfo"); - - RenderMetricCard("Canvas Size", - std::to_string(static_cast(config.canvas_size.x)) + " x " + - std::to_string(static_cast(config.canvas_size.y)), - ICON_MD_STRAIGHTEN, ImVec4(0.2F, 0.8F, 1.0F, 1.0F)); - - RenderMetricCard("Content Size", - std::to_string(static_cast(config.content_size.x)) + " x " + - std::to_string(static_cast(config.content_size.y)), - ICON_MD_IMAGE, ImVec4(0.8F, 0.2F, 1.0F, 1.0F)); - + + RenderMetricCard( + "Canvas Size", + std::to_string(static_cast(config.canvas_size.x)) + " x " + + std::to_string(static_cast(config.canvas_size.y)), + ICON_MD_STRAIGHTEN, ImVec4(0.2F, 0.8F, 1.0F, 1.0F)); + + RenderMetricCard( + "Content Size", + std::to_string(static_cast(config.content_size.x)) + " x " + + std::to_string(static_cast(config.content_size.y)), + ICON_MD_IMAGE, ImVec4(0.8F, 0.2F, 1.0F, 1.0F)); + ImGui::NextColumn(); - - RenderMetricCard("Global Scale", - std::to_string(static_cast(config.global_scale * 100)) + "%", - ICON_MD_ZOOM_IN, ImVec4(1.0F, 0.8F, 0.2F, 1.0F)); - - RenderMetricCard("Grid Step", - std::to_string(static_cast(config.grid_step)) + "px", - ICON_MD_GRID_ON, ImVec4(0.2F, 1.0F, 0.2F, 1.0F)); - + + RenderMetricCard( + "Global Scale", + std::to_string(static_cast(config.global_scale * 100)) + "%", + ICON_MD_ZOOM_IN, ImVec4(1.0F, 0.8F, 0.2F, 1.0F)); + + RenderMetricCard( + "Grid Step", + std::to_string(static_cast(config.grid_step)) + "px", + ICON_MD_GRID_ON, ImVec4(0.2F, 1.0F, 0.2F, 1.0F)); + ImGui::Columns(1); } - + // View Settings Section - if (ImGui::CollapsingHeader("👁️ View Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader("👁️ View Settings", + ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Checkbox("Show Grid", &config.enable_grid); ImGui::SameLine(); RenderMaterialIcon("grid_on"); - + ImGui::Checkbox("Show Hex Labels", &config.enable_hex_labels); ImGui::SameLine(); RenderMaterialIcon("label"); - + ImGui::Checkbox("Show Custom Labels", &config.enable_custom_labels); ImGui::SameLine(); RenderMaterialIcon("edit"); - + ImGui::Checkbox("Enable Context Menu", &config.enable_context_menu); ImGui::SameLine(); RenderMaterialIcon("menu"); - + ImGui::Checkbox("Draggable Canvas", &config.is_draggable); ImGui::SameLine(); RenderMaterialIcon("drag_indicator"); - + ImGui::Checkbox("Auto Resize for Tables", &config.auto_resize); ImGui::SameLine(); RenderMaterialIcon("fit_screen"); } - + // Scale Controls Section - if (ImGui::CollapsingHeader(ICON_MD_BUILD " Scale Controls", ImGuiTreeNodeFlags_DefaultOpen)) { - RenderSliderWithIcon("Global Scale", "zoom_in", &config.global_scale, 0.1f, 10.0f, "%.2f"); - RenderSliderWithIcon("Grid Step", "grid_on", &config.grid_step, 1.0f, 128.0f, "%.1f"); - + if (ImGui::CollapsingHeader(ICON_MD_BUILD " Scale Controls", + ImGuiTreeNodeFlags_DefaultOpen)) { + RenderSliderWithIcon("Global Scale", "zoom_in", &config.global_scale, + 0.1f, 10.0f, "%.2f"); + RenderSliderWithIcon("Grid Step", "grid_on", &config.grid_step, 1.0f, + 128.0f, "%.1f"); + // Preset scale buttons ImGui::Text("Preset Scales:"); ImGui::SameLine(); - + const char* preset_labels[] = {"0.25x", "0.5x", "1x", "2x", "4x", "8x"}; const float preset_values[] = {0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f}; - + for (int i = 0; i < 6; ++i) { - if (i > 0) ImGui::SameLine(); + if (i > 0) + ImGui::SameLine(); if (ImGui::Button(preset_labels[i])) { config.global_scale = preset_values[i]; DispatchConfig(config.on_config_changed, config); } } } - + // Scrolling Controls Section if (ImGui::CollapsingHeader("📜 Scrolling Controls")) { - ImGui::Text("Current Scroll: %.1f, %.1f", config.scrolling.x, config.scrolling.y); - + ImGui::Text("Current Scroll: %.1f, %.1f", config.scrolling.x, + config.scrolling.y); + if (ImGui::Button("Reset Scroll")) { config.scrolling = ImVec2(0, 0); DispatchConfig(config.on_config_changed, config); } ImGui::SameLine(); - + if (ImGui::Button("Center View") && bitmap) { - config.scrolling = ImVec2(-(bitmap->width() * config.global_scale - config.canvas_size.x) / 2.0f, - -(bitmap->height() * config.global_scale - config.canvas_size.y) / 2.0f); + config.scrolling = ImVec2( + -(bitmap->width() * config.global_scale - config.canvas_size.x) / + 2.0f, + -(bitmap->height() * config.global_scale - config.canvas_size.y) / + 2.0f); DispatchConfig(config.on_config_changed, config); } } - + // Performance Integration Section if (ImGui::CollapsingHeader(ICON_MD_TRENDING_UP " Performance")) { auto& profiler = gfx::PerformanceProfiler::Get(); - + // Get stats for canvas operations auto canvas_stats = profiler.GetStats("canvas_operations"); auto draw_stats = profiler.GetStats("canvas_draw"); - - RenderMetricCard("Canvas Operations", - std::to_string(canvas_stats.sample_count) + " ops", - "speed", ImVec4(0.2F, 1.0F, 0.2F, 1.0F)); - - RenderMetricCard("Average Time", - std::to_string(draw_stats.avg_time_us / 1000.0) + " ms", - "timer", ImVec4(1.0F, 0.8F, 0.2F, 1.0F)); - + + RenderMetricCard("Canvas Operations", + std::to_string(canvas_stats.sample_count) + " ops", + "speed", ImVec4(0.2F, 1.0F, 0.2F, 1.0F)); + + RenderMetricCard("Average Time", + std::to_string(draw_stats.avg_time_us / 1000.0) + " ms", + "timer", ImVec4(1.0F, 0.8F, 0.2F, 1.0F)); + if (ImGui::Button("Open Performance Dashboard")) { gfx::PerformanceDashboard::Get().SetVisible(true); } } - + // Action Buttons ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button("Apply Changes", ImVec2(120, 0))) { DispatchConfig(config.on_config_changed, config); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - + if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - + if (ImGui::Button("Reset to Defaults", ImVec2(150, 0))) { config.global_scale = 1.0f; config.grid_step = 32.0f; @@ -280,302 +298,332 @@ void CanvasModals::RenderAdvancedPropertiesModal(const std::string& canvas_id, config.scrolling = ImVec2(0, 0); DispatchConfig(config.on_config_changed, config); } - + ImGui::EndPopup(); } } void CanvasModals::RenderScalingControlsModal(const std::string& canvas_id, - CanvasConfig& config, - const gfx::Bitmap* bitmap) { - + CanvasConfig& config, + const gfx::Bitmap* bitmap) { + std::string modal_title = "Canvas Scaling Controls"; ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); - - if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - + + if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + // Header with icon ImGui::Text("%s %s", ICON_MD_ZOOM_IN, modal_title.c_str()); ImGui::Separator(); - + // Global Scale Section ImGui::Text("Global Scale: %.3f", config.global_scale); - RenderSliderWithIcon("##GlobalScale", "zoom_in", &config.global_scale, 0.1f, 10.0f, "%.2f"); - + RenderSliderWithIcon("##GlobalScale", "zoom_in", &config.global_scale, 0.1f, + 10.0f, "%.2f"); + // Preset scale buttons ImGui::Text("Preset Scales:"); const char* preset_labels[] = {"0.25x", "0.5x", "1x", "2x", "4x", "8x"}; const float preset_values[] = {0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f}; - + for (int i = 0; i < 6; ++i) { - if (i > 0) ImGui::SameLine(); + if (i > 0) + ImGui::SameLine(); if (ImGui::Button(preset_labels[i])) { config.global_scale = preset_values[i]; DispatchScale(config.on_scale_changed, config); } } - + ImGui::Separator(); - + // Grid Configuration Section ImGui::Text("Grid Step: %.1f", config.grid_step); - RenderSliderWithIcon("##GridStep", "grid_on", &config.grid_step, 1.0f, 128.0f, "%.1f"); - + RenderSliderWithIcon("##GridStep", "grid_on", &config.grid_step, 1.0f, + 128.0f, "%.1f"); + // Grid size presets ImGui::Text("Grid Presets:"); const char* grid_labels[] = {"8x8", "16x16", "32x32", "64x64"}; const float grid_values[] = {8.0f, 16.0f, 32.0f, 64.0f}; - + for (int i = 0; i < 4; ++i) { - if (i > 0) ImGui::SameLine(); + if (i > 0) + ImGui::SameLine(); if (ImGui::Button(grid_labels[i])) { config.grid_step = grid_values[i]; DispatchScale(config.on_scale_changed, config); } } - + ImGui::Separator(); - + // Canvas Information Section ImGui::Text("Canvas Information"); - ImGui::Text("Canvas Size: %.0f x %.0f", config.canvas_size.x, config.canvas_size.y); - ImGui::Text("Scaled Size: %.0f x %.0f", - config.canvas_size.x * config.global_scale, - config.canvas_size.y * config.global_scale); - + ImGui::Text("Canvas Size: %.0f x %.0f", config.canvas_size.x, + config.canvas_size.y); + ImGui::Text("Scaled Size: %.0f x %.0f", + config.canvas_size.x * config.global_scale, + config.canvas_size.y * config.global_scale); + if (bitmap) { ImGui::Text("Bitmap Size: %d x %d", bitmap->width(), bitmap->height()); - ImGui::Text("Effective Scale: %.3f x %.3f", - (config.canvas_size.x * config.global_scale) / bitmap->width(), - (config.canvas_size.y * config.global_scale) / bitmap->height()); + ImGui::Text( + "Effective Scale: %.3f x %.3f", + (config.canvas_size.x * config.global_scale) / bitmap->width(), + (config.canvas_size.y * config.global_scale) / bitmap->height()); } - + // Action Buttons ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button("Apply", ImVec2(120, 0))) { DispatchScale(config.on_scale_changed, config); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - + if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } } -void CanvasModals::RenderBppConversionModal(const std::string& canvas_id, - const BppConversionOptions& options) { - +void CanvasModals::RenderBppConversionModal( + const std::string& canvas_id, const BppConversionOptions& options) { + std::string modal_title = "BPP Format Conversion"; ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver); - - if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - + + if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + // Header with icon ImGui::Text("%s %s", ICON_MD_SWAP_HORIZ, modal_title.c_str()); ImGui::Separator(); - + // Use the existing BppFormatUI for the conversion dialog - static std::unique_ptr bpp_ui = + static std::unique_ptr bpp_ui = std::make_unique(canvas_id + "_bpp_ui"); - + // Render the format selector if (options.bitmap && options.palette) { - bpp_ui->RenderFormatSelector(const_cast(options.bitmap), - *options.palette, options.on_convert); + bpp_ui->RenderFormatSelector(const_cast(options.bitmap), + *options.palette, options.on_convert); } - + // Action Buttons ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button("Close", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } } -void CanvasModals::RenderPaletteEditorModal(const std::string& canvas_id, - const PaletteEditorOptions& options) { - - std::string modal_title = options.title.empty() ? "Palette Editor" : options.title; +void CanvasModals::RenderPaletteEditorModal( + const std::string& canvas_id, const PaletteEditorOptions& options) { + + std::string modal_title = + options.title.empty() ? "Palette Editor" : options.title; ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - - if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - + + if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + // Header with icon ImGui::Text("%s %s", ICON_MD_PALETTE, modal_title.c_str()); ImGui::Separator(); - + // Use the existing PaletteWidget - static std::unique_ptr palette_editor = + static std::unique_ptr palette_editor = std::make_unique(); - + if (options.palette) { palette_editor->ShowPaletteEditor(*options.palette, modal_title); } - + // Action Buttons ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button("Close", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } } -void CanvasModals::RenderColorAnalysisModal(const std::string& canvas_id, - const ColorAnalysisOptions& options) { - +void CanvasModals::RenderColorAnalysisModal( + const std::string& canvas_id, const ColorAnalysisOptions& options) { + std::string modal_title = "Color Analysis"; ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_FirstUseEver); - - if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - + + if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + // Header with icon ImGui::Text("%s %s", ICON_MD_ZOOM_IN, modal_title.c_str()); ImGui::Separator(); - + // Use the existing PaletteWidget for color analysis - static std::unique_ptr palette_editor = + static std::unique_ptr palette_editor = std::make_unique(); - + if (options.bitmap) { palette_editor->ShowColorAnalysis(*options.bitmap, modal_title); } - + // Action Buttons ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button("Close", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } } void CanvasModals::RenderPerformanceModal(const std::string& canvas_id, - const PerformanceOptions& options) { - + const PerformanceOptions& options) { + std::string modal_title = "Canvas Performance"; ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_FirstUseEver); - - if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - + + if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + // Header with icon ImGui::Text("%s %s", ICON_MD_TRENDING_UP, modal_title.c_str()); ImGui::Separator(); - + // Performance metrics - RenderMetricCard("Operation", options.operation_name, "speed", ImVec4(0.2f, 1.0f, 0.2f, 1.0f)); - RenderMetricCard("Time", std::to_string(options.operation_time_ms) + " ms", "timer", ImVec4(1.0f, 0.8f, 0.2f, 1.0f)); - + RenderMetricCard("Operation", options.operation_name, "speed", + ImVec4(0.2f, 1.0f, 0.2f, 1.0f)); + RenderMetricCard("Time", std::to_string(options.operation_time_ms) + " ms", + "timer", ImVec4(1.0f, 0.8f, 0.2f, 1.0f)); + // Get overall performance stats auto& profiler = gfx::PerformanceProfiler::Get(); auto canvas_stats = profiler.GetStats("canvas_operations"); auto draw_stats = profiler.GetStats("canvas_draw"); - - RenderMetricCard("Total Operations", std::to_string(canvas_stats.sample_count), "functions", ImVec4(0.2F, 0.8F, 1.0F, 1.0F)); - RenderMetricCard("Average Time", std::to_string(draw_stats.avg_time_us / 1000.0) + " ms", "schedule", ImVec4(0.8F, 0.2F, 1.0F, 1.0F)); - + + RenderMetricCard("Total Operations", + std::to_string(canvas_stats.sample_count), "functions", + ImVec4(0.2F, 0.8F, 1.0F, 1.0F)); + RenderMetricCard("Average Time", + std::to_string(draw_stats.avg_time_us / 1000.0) + " ms", + "schedule", ImVec4(0.8F, 0.2F, 1.0F, 1.0F)); + // Action Buttons ImGui::Separator(); ImGui::Spacing(); - + if (ImGui::Button("Open Dashboard", ImVec2(150, 0))) { gfx::PerformanceDashboard::Get().SetVisible(true); } ImGui::SameLine(); - + if (ImGui::Button("Close", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } } -void CanvasModals::OpenModal(const std::string& id, std::function render_func) { +void CanvasModals::OpenModal(const std::string& id, + std::function render_func) { // Check if modal already exists - auto it = std::find_if(active_modals_.begin(), active_modals_.end(), - [&id](const ModalState& modal) { return modal.id == id; }); - + auto it = + std::find_if(active_modals_.begin(), active_modals_.end(), + [&id](const ModalState& modal) { return modal.id == id; }); + if (it != active_modals_.end()) { it->is_open = true; it->render_func = render_func; } else { active_modals_.push_back({true, id, render_func}); } - + // Open the popup ImGui::OpenPopup(id.c_str()); } void CanvasModals::CloseModal(const std::string& id) { - auto it = std::find_if(active_modals_.begin(), active_modals_.end(), - [&id](const ModalState& modal) { return modal.id == id; }); - + auto it = + std::find_if(active_modals_.begin(), active_modals_.end(), + [&id](const ModalState& modal) { return modal.id == id; }); + if (it != active_modals_.end()) { it->is_open = false; } } bool CanvasModals::IsModalOpen(const std::string& id) const { - auto it = std::find_if(active_modals_.begin(), active_modals_.end(), - [&id](const ModalState& modal) { return modal.id == id; }); - + auto it = + std::find_if(active_modals_.begin(), active_modals_.end(), + [&id](const ModalState& modal) { return modal.id == id; }); + return it != active_modals_.end() && it->is_open; } -void CanvasModals::RenderMaterialIcon(const std::string& icon_name, const ImVec4& color) { +void CanvasModals::RenderMaterialIcon(const std::string& icon_name, + const ImVec4& color) { // Simple material icon rendering using Unicode symbols // In a real implementation, you'd use a proper icon font static std::unordered_map icon_map = { - {"grid_on", ICON_MD_GRID_ON}, {"label", ICON_MD_LABEL}, {"edit", ICON_MD_EDIT}, {"menu", ICON_MD_MENU}, - {"drag_indicator", ICON_MD_DRAG_INDICATOR}, {"fit_screen", ICON_MD_FIT_SCREEN}, {"zoom_in", ICON_MD_ZOOM_IN}, - {"speed", ICON_MD_SPEED}, {"timer", ICON_MD_TIMER}, {"functions", ICON_MD_FUNCTIONS}, {"schedule", ICON_MD_SCHEDULE}, - {"refresh", ICON_MD_REFRESH}, {"settings", ICON_MD_SETTINGS}, {"info", ICON_MD_INFO} - }; - + {"grid_on", ICON_MD_GRID_ON}, + {"label", ICON_MD_LABEL}, + {"edit", ICON_MD_EDIT}, + {"menu", ICON_MD_MENU}, + {"drag_indicator", ICON_MD_DRAG_INDICATOR}, + {"fit_screen", ICON_MD_FIT_SCREEN}, + {"zoom_in", ICON_MD_ZOOM_IN}, + {"speed", ICON_MD_SPEED}, + {"timer", ICON_MD_TIMER}, + {"functions", ICON_MD_FUNCTIONS}, + {"schedule", ICON_MD_SCHEDULE}, + {"refresh", ICON_MD_REFRESH}, + {"settings", ICON_MD_SETTINGS}, + {"info", ICON_MD_INFO}}; + auto it = icon_map.find(icon_name); if (it != icon_map.end()) { ImGui::TextColored(color, "%s", it->second); } } -void CanvasModals::RenderMetricCard(const std::string& title, const std::string& value, - const std::string& icon, const ImVec4& color) { +void CanvasModals::RenderMetricCard(const std::string& title, + const std::string& value, + const std::string& icon, + const ImVec4& color) { ImGui::BeginGroup(); - + // Icon and title ImGui::Text("%s %s", icon.c_str(), title.c_str()); - + // Value with color ImGui::TextColored(color, "%s", value.c_str()); - + ImGui::EndGroup(); } -void CanvasModals::RenderSliderWithIcon(const std::string& label, const std::string& icon, - float* value, float min_val, float max_val, - const char* format) { +void CanvasModals::RenderSliderWithIcon(const std::string& label, + const std::string& icon, float* value, + float min_val, float max_val, + const char* format) { ImGui::Text("%s %s", icon.c_str(), label.c_str()); ImGui::SameLine(); ImGui::SetNextItemWidth(200); diff --git a/src/app/gui/canvas/canvas_modals.h b/src/app/gui/canvas/canvas_modals.h index ebb8c16b..c09c609d 100644 --- a/src/app/gui/canvas/canvas_modals.h +++ b/src/app/gui/canvas/canvas_modals.h @@ -1,8 +1,8 @@ #ifndef YAZE_APP_GUI_CANVAS_CANVAS_MODALS_H #define YAZE_APP_GUI_CANVAS_CANVAS_MODALS_H -#include #include +#include #include #include "app/gfx/core/bitmap.h" #include "app/gfx/types/snes_palette.h" @@ -58,50 +58,50 @@ struct PerformanceOptions { class CanvasModals { public: CanvasModals() = default; - + /** * @brief Show advanced canvas properties modal */ - void ShowAdvancedProperties(const std::string& canvas_id, - const CanvasConfig& config, - const gfx::Bitmap* bitmap = nullptr); - + void ShowAdvancedProperties(const std::string& canvas_id, + const CanvasConfig& config, + const gfx::Bitmap* bitmap = nullptr); + /** * @brief Show scaling controls modal */ void ShowScalingControls(const std::string& canvas_id, - const CanvasConfig& config, - const gfx::Bitmap* bitmap = nullptr); - + const CanvasConfig& config, + const gfx::Bitmap* bitmap = nullptr); + /** * @brief Show BPP format conversion dialog */ void ShowBppConversionDialog(const std::string& canvas_id, - const BppConversionOptions& options); - + const BppConversionOptions& options); + /** * @brief Show palette editor modal */ void ShowPaletteEditor(const std::string& canvas_id, - const PaletteEditorOptions& options); - + const PaletteEditorOptions& options); + /** * @brief Show color analysis modal */ void ShowColorAnalysis(const std::string& canvas_id, - const ColorAnalysisOptions& options); - + const ColorAnalysisOptions& options); + /** * @brief Show performance dashboard integration */ void ShowPerformanceIntegration(const std::string& canvas_id, - const PerformanceOptions& options); - + const PerformanceOptions& options); + /** * @brief Render all active modals */ void Render(); - + /** * @brief Check if any modal is open */ @@ -113,42 +113,44 @@ class CanvasModals { std::string id; std::function render_func; }; - + std::vector active_modals_; - + // Modal rendering functions void RenderAdvancedPropertiesModal(const std::string& canvas_id, - CanvasConfig& config, - const gfx::Bitmap* bitmap); - + CanvasConfig& config, + const gfx::Bitmap* bitmap); + void RenderScalingControlsModal(const std::string& canvas_id, - CanvasConfig& config, - const gfx::Bitmap* bitmap); - + CanvasConfig& config, + const gfx::Bitmap* bitmap); + void RenderBppConversionModal(const std::string& canvas_id, - const BppConversionOptions& options); - + const BppConversionOptions& options); + void RenderPaletteEditorModal(const std::string& canvas_id, - const PaletteEditorOptions& options); - + const PaletteEditorOptions& options); + void RenderColorAnalysisModal(const std::string& canvas_id, - const ColorAnalysisOptions& options); - + const ColorAnalysisOptions& options); + void RenderPerformanceModal(const std::string& canvas_id, - const PerformanceOptions& options); - + const PerformanceOptions& options); + // Helper methods void OpenModal(const std::string& id, std::function render_func); void CloseModal(const std::string& id); bool IsModalOpen(const std::string& id) const; - + // UI helper methods - void RenderMaterialIcon(const std::string& icon_name, const ImVec4& color = ImVec4(1, 1, 1, 1)); - void RenderMetricCard(const std::string& title, const std::string& value, - const std::string& icon, const ImVec4& color = ImVec4(1, 1, 1, 1)); + void RenderMaterialIcon(const std::string& icon_name, + const ImVec4& color = ImVec4(1, 1, 1, 1)); + void RenderMetricCard(const std::string& title, const std::string& value, + const std::string& icon, + const ImVec4& color = ImVec4(1, 1, 1, 1)); void RenderSliderWithIcon(const std::string& label, const std::string& icon, - float* value, float min_val, float max_val, - const char* format = "%.2f"); + float* value, float min_val, float max_val, + const char* format = "%.2f"); }; } // namespace gui diff --git a/src/app/gui/canvas/canvas_performance_integration.cc b/src/app/gui/canvas/canvas_performance_integration.cc index 1a916073..7fde965c 100644 --- a/src/app/gui/canvas/canvas_performance_integration.cc +++ b/src/app/gui/canvas/canvas_performance_integration.cc @@ -1,14 +1,14 @@ #include "canvas_performance_integration.h" #include -#include -#include #include +#include +#include -#include "app/gfx/debug/performance/performance_profiler.h" #include "app/gfx/debug/performance/performance_dashboard.h" -#include "util/log.h" +#include "app/gfx/debug/performance/performance_profiler.h" #include "imgui/imgui.h" +#include "util/log.h" namespace yaze { namespace gui { @@ -17,24 +17,27 @@ void CanvasPerformanceIntegration::Initialize(const std::string& canvas_id) { canvas_id_ = canvas_id; monitoring_enabled_ = true; current_metrics_.Reset(); - + // Initialize performance profiler integration dashboard_ = &gfx::PerformanceDashboard::Get(); - + LOG_DEBUG("CanvasPerformance", - "Initialized performance integration for canvas: %s", - canvas_id_.c_str()); + "Initialized performance integration for canvas: %s", + canvas_id_.c_str()); } void CanvasPerformanceIntegration::StartMonitoring() { - if (!monitoring_enabled_) return; - + if (!monitoring_enabled_) + return; + // Start frame timer frame_timer_active_ = true; - frame_timer_ = std::make_unique("canvas_frame_" + canvas_id_); - - LOG_DEBUG("CanvasPerformance", "Started performance monitoring for canvas: %s", - canvas_id_.c_str()); + frame_timer_ = + std::make_unique("canvas_frame_" + canvas_id_); + + LOG_DEBUG("CanvasPerformance", + "Started performance monitoring for canvas: %s", + canvas_id_.c_str()); } void CanvasPerformanceIntegration::StopMonitoring() { @@ -55,43 +58,46 @@ void CanvasPerformanceIntegration::StopMonitoring() { frame_timer_.reset(); frame_timer_active_ = false; } - - LOG_DEBUG("CanvasPerformance", "Stopped performance monitoring for canvas: %s", - canvas_id_.c_str()); + + LOG_DEBUG("CanvasPerformance", + "Stopped performance monitoring for canvas: %s", + canvas_id_.c_str()); } void CanvasPerformanceIntegration::UpdateMetrics() { - if (!monitoring_enabled_) return; - + if (!monitoring_enabled_) + return; + // Update frame time UpdateFrameTime(); - + // Update draw time UpdateDrawTime(); - + // Update interaction time UpdateInteractionTime(); - + // Update modal time UpdateModalTime(); - + // Calculate cache hit ratio CalculateCacheHitRatio(); - + // Save current metrics periodically static auto last_save = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - last_save).count() >= 5) { + if (std::chrono::duration_cast(now - last_save) + .count() >= 5) { SaveCurrentMetrics(); last_save = now; } } -void CanvasPerformanceIntegration::RecordOperation(const std::string& operation_name, - double time_ms, - CanvasUsage usage_mode) { - if (!monitoring_enabled_) return; - +void CanvasPerformanceIntegration::RecordOperation( + const std::string& operation_name, double time_ms, CanvasUsage usage_mode) { + if (!monitoring_enabled_) + return; + // Update operation counts based on usage mode switch (usage_mode) { case CanvasUsage::kTilePainting: @@ -112,25 +118,26 @@ void CanvasPerformanceIntegration::RecordOperation(const std::string& operation_ default: break; } - + // Record operation timing in internal metrics // Note: PerformanceProfiler uses StartTimer/EndTimer pattern, not RecordOperation - + // Update usage tracker if available if (usage_tracker_) { usage_tracker_->RecordOperation(operation_name, time_ms); } } -void CanvasPerformanceIntegration::RecordMemoryUsage(size_t texture_memory, - size_t bitmap_memory, - size_t palette_memory) { +void CanvasPerformanceIntegration::RecordMemoryUsage(size_t texture_memory, + size_t bitmap_memory, + size_t palette_memory) { current_metrics_.texture_memory_mb = texture_memory / (1024 * 1024); current_metrics_.bitmap_memory_mb = bitmap_memory / (1024 * 1024); current_metrics_.palette_memory_mb = palette_memory / (1024 * 1024); } -void CanvasPerformanceIntegration::RecordCachePerformance(int hits, int misses) { +void CanvasPerformanceIntegration::RecordCachePerformance(int hits, + int misses) { current_metrics_.cache_hits = hits; current_metrics_.cache_misses = misses; CalculateCacheHitRatio(); @@ -140,108 +147,139 @@ void CanvasPerformanceIntegration::RecordCachePerformance(int hits, int misses) std::string CanvasPerformanceIntegration::GetPerformanceSummary() const { std::ostringstream summary; - + summary << "Canvas Performance Summary (" << canvas_id_ << ")\n"; summary << "=====================================\n\n"; - + summary << "Timing Metrics:\n"; - summary << " Frame Time: " << FormatTime(current_metrics_.frame_time_ms) << "\n"; - summary << " Draw Time: " << FormatTime(current_metrics_.draw_time_ms) << "\n"; - summary << " Interaction Time: " << FormatTime(current_metrics_.interaction_time_ms) << "\n"; - summary << " Modal Time: " << FormatTime(current_metrics_.modal_time_ms) << "\n\n"; - + summary << " Frame Time: " << FormatTime(current_metrics_.frame_time_ms) + << "\n"; + summary << " Draw Time: " << FormatTime(current_metrics_.draw_time_ms) + << "\n"; + summary << " Interaction Time: " + << FormatTime(current_metrics_.interaction_time_ms) << "\n"; + summary << " Modal Time: " << FormatTime(current_metrics_.modal_time_ms) + << "\n\n"; + summary << "Operation Counts:\n"; summary << " Draw Calls: " << current_metrics_.draw_calls << "\n"; summary << " Texture Updates: " << current_metrics_.texture_updates << "\n"; summary << " Palette Lookups: " << current_metrics_.palette_lookups << "\n"; - summary << " Bitmap Operations: " << current_metrics_.bitmap_operations << "\n\n"; - + summary << " Bitmap Operations: " << current_metrics_.bitmap_operations + << "\n\n"; + summary << "Canvas Operations:\n"; summary << " Tile Paint: " << current_metrics_.tile_paint_operations << "\n"; - summary << " Tile Select: " << current_metrics_.tile_select_operations << "\n"; - summary << " Rectangle Select: " << current_metrics_.rectangle_select_operations << "\n"; - summary << " Color Paint: " << current_metrics_.color_paint_operations << "\n"; - summary << " BPP Conversion: " << current_metrics_.bpp_conversion_operations << "\n\n"; - + summary << " Tile Select: " << current_metrics_.tile_select_operations + << "\n"; + summary << " Rectangle Select: " + << current_metrics_.rectangle_select_operations << "\n"; + summary << " Color Paint: " << current_metrics_.color_paint_operations + << "\n"; + summary << " BPP Conversion: " << current_metrics_.bpp_conversion_operations + << "\n\n"; + summary << "Memory Usage:\n"; - summary << " Texture Memory: " << FormatMemory(current_metrics_.texture_memory_mb * 1024 * 1024) << "\n"; - summary << " Bitmap Memory: " << FormatMemory(current_metrics_.bitmap_memory_mb * 1024 * 1024) << "\n"; - summary << " Palette Memory: " << FormatMemory(current_metrics_.palette_memory_mb * 1024 * 1024) << "\n\n"; - + summary << " Texture Memory: " + << FormatMemory(current_metrics_.texture_memory_mb * 1024 * 1024) + << "\n"; + summary << " Bitmap Memory: " + << FormatMemory(current_metrics_.bitmap_memory_mb * 1024 * 1024) + << "\n"; + summary << " Palette Memory: " + << FormatMemory(current_metrics_.palette_memory_mb * 1024 * 1024) + << "\n\n"; + summary << "Cache Performance:\n"; - summary << " Hit Ratio: " << std::fixed << std::setprecision(1) - << (current_metrics_.cache_hit_ratio * 100.0) << "%\n"; + summary << " Hit Ratio: " << std::fixed << std::setprecision(1) + << (current_metrics_.cache_hit_ratio * 100.0) << "%\n"; summary << " Hits: " << current_metrics_.cache_hits << "\n"; summary << " Misses: " << current_metrics_.cache_misses << "\n"; - + return summary.str(); } -std::vector CanvasPerformanceIntegration::GetPerformanceRecommendations() const { +std::vector +CanvasPerformanceIntegration::GetPerformanceRecommendations() const { std::vector recommendations; - + // Frame time recommendations - if (current_metrics_.frame_time_ms > 16.67) { // 60 FPS threshold - recommendations.push_back("Frame time is high - consider reducing draw calls or optimizing rendering"); + if (current_metrics_.frame_time_ms > 16.67) { // 60 FPS threshold + recommendations.push_back( + "Frame time is high - consider reducing draw calls or optimizing " + "rendering"); } - + // Draw time recommendations if (current_metrics_.draw_time_ms > 10.0) { - recommendations.push_back("Draw time is high - consider using texture atlases or reducing texture switches"); + recommendations.push_back( + "Draw time is high - consider using texture atlases or reducing " + "texture switches"); } - + // Memory recommendations - size_t total_memory = current_metrics_.texture_memory_mb + - current_metrics_.bitmap_memory_mb + - current_metrics_.palette_memory_mb; - if (total_memory > 100) { // 100MB threshold - recommendations.push_back("Memory usage is high - consider implementing texture streaming or compression"); + size_t total_memory = current_metrics_.texture_memory_mb + + current_metrics_.bitmap_memory_mb + + current_metrics_.palette_memory_mb; + if (total_memory > 100) { // 100MB threshold + recommendations.push_back( + "Memory usage is high - consider implementing texture streaming or " + "compression"); } - + // Cache recommendations if (current_metrics_.cache_hit_ratio < 0.8) { - recommendations.push_back("Cache hit ratio is low - consider increasing cache size or improving cache strategy"); + recommendations.push_back( + "Cache hit ratio is low - consider increasing cache size or improving " + "cache strategy"); } - + // Operation count recommendations if (current_metrics_.draw_calls > 1000) { - recommendations.push_back("High draw call count - consider batching operations or using instanced rendering"); + recommendations.push_back( + "High draw call count - consider batching operations or using " + "instanced rendering"); } - + if (current_metrics_.texture_updates > 100) { - recommendations.push_back("Frequent texture updates - consider using texture arrays or atlases"); + recommendations.push_back( + "Frequent texture updates - consider using texture arrays or atlases"); } - + return recommendations; } std::string CanvasPerformanceIntegration::ExportPerformanceReport() const { std::ostringstream report; - + report << "Canvas Performance Report\n"; report << "========================\n\n"; - + report << "Canvas ID: " << canvas_id_ << "\n"; - report << "Monitoring Enabled: " << (monitoring_enabled_ ? "Yes" : "No") << "\n\n"; - + report << "Monitoring Enabled: " << (monitoring_enabled_ ? "Yes" : "No") + << "\n\n"; + report << GetPerformanceSummary() << "\n"; - + // Performance history if (!performance_history_.empty()) { report << "Performance History:\n"; report << "===================\n\n"; - + for (size_t i = 0; i < performance_history_.size(); ++i) { const auto& metrics = performance_history_[i]; report << "Sample " << (i + 1) << ":\n"; report << " Frame Time: " << FormatTime(metrics.frame_time_ms) << "\n"; report << " Draw Calls: " << metrics.draw_calls << "\n"; - report << " Memory: " << FormatMemory((metrics.texture_memory_mb + - metrics.bitmap_memory_mb + - metrics.palette_memory_mb) * 1024 * 1024) << "\n\n"; + report << " Memory: " + << FormatMemory((metrics.texture_memory_mb + + metrics.bitmap_memory_mb + + metrics.palette_memory_mb) * + 1024 * 1024) + << "\n\n"; } } - + // Recommendations auto recommendations = GetPerformanceRecommendations(); if (!recommendations.empty()) { @@ -251,27 +289,28 @@ std::string CanvasPerformanceIntegration::ExportPerformanceReport() const { report << "• " << rec << "\n"; } } - + return report.str(); } void CanvasPerformanceIntegration::RenderPerformanceUI() { - if (!monitoring_enabled_) return; - + if (!monitoring_enabled_) + return; + if (ImGui::Begin("Canvas Performance", &show_performance_ui_)) { // Performance overview RenderPerformanceOverview(); - + if (show_detailed_metrics_) { ImGui::Separator(); RenderDetailedMetrics(); } - + if (show_recommendations_) { ImGui::Separator(); RenderRecommendations(); } - + // Control buttons ImGui::Separator(); if (ImGui::Button("Toggle Detailed Metrics")) { @@ -290,42 +329,45 @@ void CanvasPerformanceIntegration::RenderPerformanceUI() { ImGui::End(); } -void CanvasPerformanceIntegration::SetUsageTracker(std::shared_ptr tracker) { +void CanvasPerformanceIntegration::SetUsageTracker( + std::shared_ptr tracker) { usage_tracker_ = tracker; } void CanvasPerformanceIntegration::UpdateFrameTime() { if (frame_timer_) { // Frame time would be calculated by the timer - current_metrics_.frame_time_ms = 16.67; // Placeholder + current_metrics_.frame_time_ms = 16.67; // Placeholder } } void CanvasPerformanceIntegration::UpdateDrawTime() { if (draw_timer_) { // Draw time would be calculated by the timer - current_metrics_.draw_time_ms = 5.0; // Placeholder + current_metrics_.draw_time_ms = 5.0; // Placeholder } } void CanvasPerformanceIntegration::UpdateInteractionTime() { if (interaction_timer_) { // Interaction time would be calculated by the timer - current_metrics_.interaction_time_ms = 1.0; // Placeholder + current_metrics_.interaction_time_ms = 1.0; // Placeholder } } void CanvasPerformanceIntegration::UpdateModalTime() { if (modal_timer_) { // Modal time would be calculated by the timer - current_metrics_.modal_time_ms = 0.5; // Placeholder + current_metrics_.modal_time_ms = 0.5; // Placeholder } } void CanvasPerformanceIntegration::CalculateCacheHitRatio() { - int total_requests = current_metrics_.cache_hits + current_metrics_.cache_misses; + int total_requests = + current_metrics_.cache_hits + current_metrics_.cache_misses; if (total_requests > 0) { - current_metrics_.cache_hit_ratio = static_cast(current_metrics_.cache_hits) / total_requests; + current_metrics_.cache_hit_ratio = + static_cast(current_metrics_.cache_hits) / total_requests; } else { current_metrics_.cache_hit_ratio = 0.0; } @@ -333,7 +375,7 @@ void CanvasPerformanceIntegration::CalculateCacheHitRatio() { void CanvasPerformanceIntegration::SaveCurrentMetrics() { performance_history_.push_back(current_metrics_); - + // Keep only last 100 samples if (performance_history_.size() > 100) { performance_history_.erase(performance_history_.begin()); @@ -342,82 +384,99 @@ void CanvasPerformanceIntegration::SaveCurrentMetrics() { void CanvasPerformanceIntegration::AnalyzePerformance() { // Analyze performance trends and patterns - if (performance_history_.size() < 2) return; - + if (performance_history_.size() < 2) + return; + // Calculate trends double frame_time_trend = 0.0; double memory_trend = 0.0; - + for (size_t i = 1; i < performance_history_.size(); ++i) { const auto& prev = performance_history_[i - 1]; const auto& curr = performance_history_[i]; - + frame_time_trend += (curr.frame_time_ms - prev.frame_time_ms); - memory_trend += ((curr.texture_memory_mb + curr.bitmap_memory_mb + curr.palette_memory_mb) - - (prev.texture_memory_mb + prev.bitmap_memory_mb + prev.palette_memory_mb)); + memory_trend += ((curr.texture_memory_mb + curr.bitmap_memory_mb + + curr.palette_memory_mb) - + (prev.texture_memory_mb + prev.bitmap_memory_mb + + prev.palette_memory_mb)); } - + frame_time_trend /= (performance_history_.size() - 1); memory_trend /= (performance_history_.size() - 1); - + // Log trends if (std::abs(frame_time_trend) > 1.0) { - LOG_DEBUG("CanvasPerformance", "Canvas %s: Frame time trend: %.2f ms/sample", - canvas_id_.c_str(), frame_time_trend); + LOG_DEBUG("CanvasPerformance", + "Canvas %s: Frame time trend: %.2f ms/sample", canvas_id_.c_str(), + frame_time_trend); } - + if (std::abs(memory_trend) > 1.0) { - LOG_DEBUG("CanvasPerformance", "Canvas %s: Memory trend: %.2f MB/sample", - canvas_id_.c_str(), memory_trend); + LOG_DEBUG("CanvasPerformance", "Canvas %s: Memory trend: %.2f MB/sample", + canvas_id_.c_str(), memory_trend); } } void CanvasPerformanceIntegration::RenderPerformanceOverview() { ImGui::Text("Performance Overview"); ImGui::Separator(); - + // Frame time - ImVec4 frame_color = GetPerformanceColor(current_metrics_.frame_time_ms, 16.67, 33.33); - ImGui::TextColored(frame_color, "Frame Time: %s", FormatTime(current_metrics_.frame_time_ms).c_str()); - + ImVec4 frame_color = + GetPerformanceColor(current_metrics_.frame_time_ms, 16.67, 33.33); + ImGui::TextColored(frame_color, "Frame Time: %s", + FormatTime(current_metrics_.frame_time_ms).c_str()); + // Draw time - ImVec4 draw_color = GetPerformanceColor(current_metrics_.draw_time_ms, 10.0, 20.0); - ImGui::TextColored(draw_color, "Draw Time: %s", FormatTime(current_metrics_.draw_time_ms).c_str()); - + ImVec4 draw_color = + GetPerformanceColor(current_metrics_.draw_time_ms, 10.0, 20.0); + ImGui::TextColored(draw_color, "Draw Time: %s", + FormatTime(current_metrics_.draw_time_ms).c_str()); + // Memory usage - size_t total_memory = current_metrics_.texture_memory_mb + - current_metrics_.bitmap_memory_mb + - current_metrics_.palette_memory_mb; + size_t total_memory = current_metrics_.texture_memory_mb + + current_metrics_.bitmap_memory_mb + + current_metrics_.palette_memory_mb; ImVec4 memory_color = GetPerformanceColor(total_memory, 50.0, 100.0); - ImGui::TextColored(memory_color, "Memory: %s", FormatMemory(total_memory * 1024 * 1024).c_str()); - + ImGui::TextColored(memory_color, "Memory: %s", + FormatMemory(total_memory * 1024 * 1024).c_str()); + // Cache performance - ImVec4 cache_color = GetPerformanceColor(current_metrics_.cache_hit_ratio * 100.0, 80.0, 60.0); - ImGui::TextColored(cache_color, "Cache Hit Ratio: %.1f%%", current_metrics_.cache_hit_ratio * 100.0); + ImVec4 cache_color = + GetPerformanceColor(current_metrics_.cache_hit_ratio * 100.0, 80.0, 60.0); + ImGui::TextColored(cache_color, "Cache Hit Ratio: %.1f%%", + current_metrics_.cache_hit_ratio * 100.0); } void CanvasPerformanceIntegration::RenderDetailedMetrics() { ImGui::Text("Detailed Metrics"); ImGui::Separator(); - + // Operation counts RenderOperationCounts(); - + // Memory breakdown RenderMemoryUsage(); - + // Cache performance RenderCachePerformance(); } void CanvasPerformanceIntegration::RenderMemoryUsage() { if (ImGui::CollapsingHeader("Memory Usage")) { - ImGui::Text("Texture Memory: %s", FormatMemory(current_metrics_.texture_memory_mb * 1024 * 1024).c_str()); - ImGui::Text("Bitmap Memory: %s", FormatMemory(current_metrics_.bitmap_memory_mb * 1024 * 1024).c_str()); - ImGui::Text("Palette Memory: %s", FormatMemory(current_metrics_.palette_memory_mb * 1024 * 1024).c_str()); - - size_t total = current_metrics_.texture_memory_mb + - current_metrics_.bitmap_memory_mb + + ImGui::Text( + "Texture Memory: %s", + FormatMemory(current_metrics_.texture_memory_mb * 1024 * 1024).c_str()); + ImGui::Text( + "Bitmap Memory: %s", + FormatMemory(current_metrics_.bitmap_memory_mb * 1024 * 1024).c_str()); + ImGui::Text( + "Palette Memory: %s", + FormatMemory(current_metrics_.palette_memory_mb * 1024 * 1024).c_str()); + + size_t total = current_metrics_.texture_memory_mb + + current_metrics_.bitmap_memory_mb + current_metrics_.palette_memory_mb; ImGui::Text("Total Memory: %s", FormatMemory(total * 1024 * 1024).c_str()); } @@ -429,14 +488,16 @@ void CanvasPerformanceIntegration::RenderOperationCounts() { ImGui::Text("Texture Updates: %d", current_metrics_.texture_updates); ImGui::Text("Palette Lookups: %d", current_metrics_.palette_lookups); ImGui::Text("Bitmap Operations: %d", current_metrics_.bitmap_operations); - + ImGui::Separator(); ImGui::Text("Canvas Operations:"); ImGui::Text(" Tile Paint: %d", current_metrics_.tile_paint_operations); ImGui::Text(" Tile Select: %d", current_metrics_.tile_select_operations); - ImGui::Text(" Rectangle Select: %d", current_metrics_.rectangle_select_operations); + ImGui::Text(" Rectangle Select: %d", + current_metrics_.rectangle_select_operations); ImGui::Text(" Color Paint: %d", current_metrics_.color_paint_operations); - ImGui::Text(" BPP Conversion: %d", current_metrics_.bpp_conversion_operations); + ImGui::Text(" BPP Conversion: %d", + current_metrics_.bpp_conversion_operations); } } @@ -445,7 +506,7 @@ void CanvasPerformanceIntegration::RenderCachePerformance() { ImGui::Text("Cache Hits: %d", current_metrics_.cache_hits); ImGui::Text("Cache Misses: %d", current_metrics_.cache_misses); ImGui::Text("Hit Ratio: %.1f%%", current_metrics_.cache_hit_ratio * 100.0); - + // Cache hit ratio bar ImGui::ProgressBar(current_metrics_.cache_hit_ratio, ImVec2(0, 0)); } @@ -454,10 +515,11 @@ void CanvasPerformanceIntegration::RenderCachePerformance() { void CanvasPerformanceIntegration::RenderRecommendations() { ImGui::Text("Performance Recommendations"); ImGui::Separator(); - + auto recommendations = GetPerformanceRecommendations(); if (recommendations.empty()) { - ImGui::TextColored(ImVec4(0.2F, 1.0F, 0.2F, 1.0F), "✓ Performance looks good!"); + ImGui::TextColored(ImVec4(0.2F, 1.0F, 0.2F, 1.0F), + "✓ Performance looks good!"); } else { for (const auto& rec : recommendations) { ImGui::TextColored(ImVec4(1.0F, 0.8F, 0.2F, 1.0F), "⚠ %s", rec.c_str()); @@ -470,24 +532,24 @@ void CanvasPerformanceIntegration::RenderPerformanceGraph() { // Simple performance graph using ImGui plot lines static std::vector frame_times; static std::vector draw_times; - + // Add current values frame_times.push_back(static_cast(current_metrics_.frame_time_ms)); draw_times.push_back(static_cast(current_metrics_.draw_time_ms)); - + // Keep only last 100 samples if (frame_times.size() > 100) { frame_times.erase(frame_times.begin()); draw_times.erase(draw_times.begin()); } - + if (!frame_times.empty()) { - ImGui::PlotLines("Frame Time (ms)", frame_times.data(), - static_cast(frame_times.size()), 0, nullptr, 0.0F, 50.0F, - ImVec2(0, 100)); - ImGui::PlotLines("Draw Time (ms)", draw_times.data(), - static_cast(draw_times.size()), 0, nullptr, 0.0F, 30.0F, - ImVec2(0, 100)); + ImGui::PlotLines("Frame Time (ms)", frame_times.data(), + static_cast(frame_times.size()), 0, nullptr, 0.0F, + 50.0F, ImVec2(0, 100)); + ImGui::PlotLines("Draw Time (ms)", draw_times.data(), + static_cast(draw_times.size()), 0, nullptr, 0.0F, + 30.0F, ImVec2(0, 100)); } } } @@ -512,15 +574,14 @@ std::string CanvasPerformanceIntegration::FormatMemory(size_t bytes) const { } } -ImVec4 CanvasPerformanceIntegration::GetPerformanceColor(double value, - double threshold_good, - double threshold_warning) const { +ImVec4 CanvasPerformanceIntegration::GetPerformanceColor( + double value, double threshold_good, double threshold_warning) const { if (value <= threshold_good) { - return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green + return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green } else if (value <= threshold_warning) { - return ImVec4(1.0F, 1.0F, 0.2F, 1.0F); // Yellow + return ImVec4(1.0F, 1.0F, 0.2F, 1.0F); // Yellow } else { - return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red + return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red } } @@ -536,8 +597,8 @@ void CanvasPerformanceManager::RegisterIntegration( std::shared_ptr integration) { integrations_[canvas_id] = integration; LOG_DEBUG("CanvasPerformance", - "Registered performance integration for canvas: %s", - canvas_id.c_str()); + "Registered performance integration for canvas: %s", + canvas_id.c_str()); } std::shared_ptr @@ -557,33 +618,33 @@ void CanvasPerformanceManager::UpdateAllIntegrations() { std::string CanvasPerformanceManager::GetGlobalPerformanceSummary() const { std::ostringstream summary; - + summary << "Global Canvas Performance Summary\n"; summary << "=================================\n\n"; - + summary << "Registered Canvases: " << integrations_.size() << "\n\n"; - + for (const auto& [id, integration] : integrations_) { summary << "Canvas: " << id << "\n"; summary << "----------------------------------------\n"; summary << integration->GetPerformanceSummary() << "\n\n"; } - + return summary.str(); } std::string CanvasPerformanceManager::ExportGlobalPerformanceReport() const { std::ostringstream report; - + report << "Global Canvas Performance Report\n"; report << "================================\n\n"; - + report << GetGlobalPerformanceSummary(); - + // Global recommendations report << "Global Recommendations:\n"; report << "=======================\n\n"; - + for (const auto& [id, integration] : integrations_) { auto recommendations = integration->GetPerformanceRecommendations(); if (!recommendations.empty()) { @@ -594,7 +655,7 @@ std::string CanvasPerformanceManager::ExportGlobalPerformanceReport() const { report << "\n"; } } - + return report.str(); } diff --git a/src/app/gui/canvas/canvas_performance_integration.h b/src/app/gui/canvas/canvas_performance_integration.h index 216e808b..9888a1e6 100644 --- a/src/app/gui/canvas/canvas_performance_integration.h +++ b/src/app/gui/canvas/canvas_performance_integration.h @@ -1,13 +1,13 @@ #ifndef YAZE_APP_GUI_CANVAS_CANVAS_PERFORMANCE_INTEGRATION_H #define YAZE_APP_GUI_CANVAS_CANVAS_PERFORMANCE_INTEGRATION_H -#include -#include -#include -#include #include -#include "app/gfx/debug/performance/performance_profiler.h" +#include +#include +#include +#include #include "app/gfx/debug/performance/performance_dashboard.h" +#include "app/gfx/debug/performance/performance_profiler.h" #include "canvas_usage_tracker.h" #include "imgui/imgui.h" @@ -23,30 +23,30 @@ struct CanvasPerformanceMetrics { double draw_time_ms = 0.0; double interaction_time_ms = 0.0; double modal_time_ms = 0.0; - + // Operation counts int draw_calls = 0; int texture_updates = 0; int palette_lookups = 0; int bitmap_operations = 0; - + // Memory usage size_t texture_memory_mb = 0; size_t bitmap_memory_mb = 0; size_t palette_memory_mb = 0; - + // Cache performance double cache_hit_ratio = 0.0; int cache_hits = 0; int cache_misses = 0; - + // Canvas-specific metrics int tile_paint_operations = 0; int tile_select_operations = 0; int rectangle_select_operations = 0; int color_paint_operations = 0; int bpp_conversion_operations = 0; - + void Reset() { frame_time_ms = 0.0; draw_time_ms = 0.0; @@ -76,83 +76,83 @@ struct CanvasPerformanceMetrics { class CanvasPerformanceIntegration { public: CanvasPerformanceIntegration() = default; - + /** * @brief Initialize performance integration */ void Initialize(const std::string& canvas_id); - + /** * @brief Start performance monitoring */ void StartMonitoring(); - + /** * @brief Stop performance monitoring */ void StopMonitoring(); - + /** * @brief Update performance metrics */ void UpdateMetrics(); - + /** * @brief Record canvas operation */ - void RecordOperation(const std::string& operation_name, - double time_ms, - CanvasUsage usage_mode = CanvasUsage::kUnknown); - + void RecordOperation(const std::string& operation_name, double time_ms, + CanvasUsage usage_mode = CanvasUsage::kUnknown); + /** * @brief Record memory usage */ - void RecordMemoryUsage(size_t texture_memory, - size_t bitmap_memory, - size_t palette_memory); - + void RecordMemoryUsage(size_t texture_memory, size_t bitmap_memory, + size_t palette_memory); + /** * @brief Record cache performance */ void RecordCachePerformance(int hits, int misses); - + /** * @brief Get current performance metrics */ - const CanvasPerformanceMetrics& GetCurrentMetrics() const { return current_metrics_; } - + const CanvasPerformanceMetrics& GetCurrentMetrics() const { + return current_metrics_; + } + /** * @brief Get performance history */ - const std::vector& GetPerformanceHistory() const { - return performance_history_; + const std::vector& GetPerformanceHistory() const { + return performance_history_; } - + /** * @brief Get performance summary */ std::string GetPerformanceSummary() const; - + /** * @brief Get performance recommendations */ std::vector GetPerformanceRecommendations() const; - + /** * @brief Export performance report */ std::string ExportPerformanceReport() const; - + /** * @brief Render performance UI */ void RenderPerformanceUI(); - + /** * @brief Set usage tracker integration */ void SetUsageTracker(std::shared_ptr tracker); - + /** * @brief Enable/disable performance monitoring */ @@ -164,7 +164,7 @@ class CanvasPerformanceIntegration { bool monitoring_enabled_ = true; CanvasPerformanceMetrics current_metrics_; std::vector performance_history_; - + // Performance profiler integration std::unique_ptr frame_timer_; std::unique_ptr draw_timer_; @@ -174,18 +174,18 @@ class CanvasPerformanceIntegration { bool draw_timer_active_ = false; bool interaction_timer_active_ = false; bool modal_timer_active_ = false; - + // Usage tracker integration std::shared_ptr usage_tracker_; - + // Performance dashboard integration gfx::PerformanceDashboard* dashboard_ = nullptr; - + // UI state bool show_performance_ui_ = false; bool show_detailed_metrics_ = false; bool show_recommendations_ = false; - + // Helper methods void UpdateFrameTime(); void UpdateDrawTime(); @@ -194,7 +194,7 @@ class CanvasPerformanceIntegration { void CalculateCacheHitRatio(); void SaveCurrentMetrics(); void AnalyzePerformance(); - + // UI rendering methods void RenderPerformanceOverview(); void RenderDetailedMetrics(); @@ -203,11 +203,12 @@ class CanvasPerformanceIntegration { void RenderCachePerformance(); void RenderRecommendations(); void RenderPerformanceGraph(); - + // Helper methods std::string FormatTime(double time_ms) const; std::string FormatMemory(size_t bytes) const; - ImVec4 GetPerformanceColor(double value, double threshold_good, double threshold_warning) const; + ImVec4 GetPerformanceColor(double value, double threshold_good, + double threshold_warning) const; }; /** @@ -216,39 +217,44 @@ class CanvasPerformanceIntegration { class CanvasPerformanceManager { public: static CanvasPerformanceManager& Get(); - + /** * @brief Register a canvas performance integration */ - void RegisterIntegration(const std::string& canvas_id, - std::shared_ptr integration); - + void RegisterIntegration( + const std::string& canvas_id, + std::shared_ptr integration); + /** * @brief Get integration for canvas */ - std::shared_ptr GetIntegration(const std::string& canvas_id); - + std::shared_ptr GetIntegration( + const std::string& canvas_id); + /** * @brief Get all integrations */ - const std::unordered_map>& - GetAllIntegrations() const { return integrations_; } - + const std::unordered_map>& + GetAllIntegrations() const { + return integrations_; + } + /** * @brief Update all integrations */ void UpdateAllIntegrations(); - + /** * @brief Get global performance summary */ std::string GetGlobalPerformanceSummary() const; - + /** * @brief Export global performance report */ std::string ExportGlobalPerformanceReport() const; - + /** * @brief Clear all integrations */ @@ -257,8 +263,9 @@ class CanvasPerformanceManager { private: CanvasPerformanceManager() = default; ~CanvasPerformanceManager() = default; - - std::unordered_map> integrations_; + + std::unordered_map> + integrations_; }; } // namespace gui diff --git a/src/app/gui/canvas/canvas_popup.cc b/src/app/gui/canvas/canvas_popup.cc index 6d5ce8e9..a638c2ff 100644 --- a/src/app/gui/canvas/canvas_popup.cc +++ b/src/app/gui/canvas/canvas_popup.cc @@ -6,10 +6,10 @@ namespace yaze { namespace gui { void PopupRegistry::Open(const std::string& popup_id, - std::function render_callback) { + std::function render_callback) { // Check if popup already exists auto it = FindPopup(popup_id); - + if (it != popups_.end()) { // Update existing popup it->is_open = true; @@ -17,26 +17,26 @@ void PopupRegistry::Open(const std::string& popup_id, ImGui::OpenPopup(popup_id.c_str()); return; } - + // Add new popup PopupState new_popup; new_popup.popup_id = popup_id; new_popup.is_open = true; new_popup.render_callback = std::move(render_callback); new_popup.persist = true; - + popups_.push_back(new_popup); - + // Open the popup in ImGui ImGui::OpenPopup(popup_id.c_str()); } void PopupRegistry::Close(const std::string& popup_id) { auto it = FindPopup(popup_id); - + if (it != popups_.end()) { it->is_open = false; - + // Close in ImGui if it's the current popup // Note: ImGui::CloseCurrentPopup() only works if this is the active popup // In practice, the popup will be removed on next RenderAll() call @@ -58,13 +58,13 @@ void PopupRegistry::RenderAll() { if (it->is_open && it->render_callback) { // Call the render callback which should handle BeginPopup/EndPopup it->render_callback(); - + // Check if popup was closed by user (clicking outside, pressing Escape, etc.) if (!ImGui::IsPopupOpen(it->popup_id.c_str())) { it->is_open = false; } } - + // Remove closed popups from the registry if (!it->is_open) { it = popups_.erase(it); @@ -76,7 +76,7 @@ void PopupRegistry::RenderAll() { size_t PopupRegistry::GetActiveCount() const { return std::count_if(popups_.begin(), popups_.end(), - [](const PopupState& popup) { return popup.is_open; }); + [](const PopupState& popup) { return popup.is_open; }); } void PopupRegistry::Clear() { @@ -87,7 +87,7 @@ void PopupRegistry::Clear() { } popup.is_open = false; } - + // Clear the registry popups_.clear(); } @@ -95,19 +95,18 @@ void PopupRegistry::Clear() { std::vector::iterator PopupRegistry::FindPopup( const std::string& popup_id) { return std::find_if(popups_.begin(), popups_.end(), - [&popup_id](const PopupState& popup) { - return popup.popup_id == popup_id; - }); + [&popup_id](const PopupState& popup) { + return popup.popup_id == popup_id; + }); } std::vector::const_iterator PopupRegistry::FindPopup( const std::string& popup_id) const { return std::find_if(popups_.begin(), popups_.end(), - [&popup_id](const PopupState& popup) { - return popup.popup_id == popup_id; - }); + [&popup_id](const PopupState& popup) { + return popup.popup_id == popup_id; + }); } } // namespace gui } // namespace yaze - diff --git a/src/app/gui/canvas/canvas_popup.h b/src/app/gui/canvas/canvas_popup.h index 61c9fda1..a3620342 100644 --- a/src/app/gui/canvas/canvas_popup.h +++ b/src/app/gui/canvas/canvas_popup.h @@ -19,20 +19,20 @@ namespace gui { struct PopupState { // Unique popup identifier (used with ImGui::OpenPopup/BeginPopup) std::string popup_id; - + // Whether the popup is currently open bool is_open = false; - + // Callback that renders the popup content // Should call ImGui::BeginPopup(popup_id) / ImGui::EndPopup() std::function render_callback; - + // Whether the popup should persist across frames bool persist = true; - + // Default constructor PopupState() = default; - + // Constructor with id and callback PopupState(const std::string& id, std::function callback) : popup_id(id), is_open(false), render_callback(std::move(callback)) {} @@ -50,7 +50,7 @@ struct PopupState { class PopupRegistry { public: PopupRegistry() = default; - + /** * @brief Open a persistent popup * @@ -61,7 +61,7 @@ class PopupRegistry { * @param render_callback Function that renders the popup content */ void Open(const std::string& popup_id, std::function render_callback); - + /** * @brief Close a persistent popup * @@ -71,7 +71,7 @@ class PopupRegistry { * @param popup_id Identifier of the popup to close */ void Close(const std::string& popup_id); - + /** * @brief Check if a popup is currently open * @@ -79,7 +79,7 @@ class PopupRegistry { * @return true if popup is open, false otherwise */ bool IsOpen(const std::string& popup_id) const; - + /** * @brief Render all active popups * @@ -90,14 +90,14 @@ class PopupRegistry { * frame after all other rendering is complete. */ void RenderAll(); - + /** * @brief Get the number of active popups * * @return Number of open popups in the registry */ size_t GetActiveCount() const; - + /** * @brief Clear all popups from the registry * @@ -105,7 +105,7 @@ class PopupRegistry { * Useful for cleanup or resetting state. */ void Clear(); - + /** * @brief Get direct access to the popup list (for migration/debugging) * @@ -117,14 +117,14 @@ class PopupRegistry { private: // Internal storage for popup states std::vector popups_; - + // Helper to find a popup by ID std::vector::iterator FindPopup(const std::string& popup_id); - std::vector::const_iterator FindPopup(const std::string& popup_id) const; + std::vector::const_iterator FindPopup( + const std::string& popup_id) const; }; } // namespace gui } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_POPUP_H - diff --git a/src/app/gui/canvas/canvas_rendering.cc b/src/app/gui/canvas/canvas_rendering.cc index e94f9641..be9082a8 100644 --- a/src/app/gui/canvas/canvas_rendering.cc +++ b/src/app/gui/canvas/canvas_rendering.cc @@ -13,25 +13,22 @@ constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255); constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255); } // namespace -void RenderCanvasBackground( - ImDrawList* draw_list, - const CanvasGeometry& geometry) { - +void RenderCanvasBackground(ImDrawList* draw_list, + const CanvasGeometry& geometry) { + // Draw border and background color (extracted from Canvas::DrawBackground) - draw_list->AddRectFilled(geometry.canvas_p0, geometry.canvas_p1, kRectangleColor); + draw_list->AddRectFilled(geometry.canvas_p0, geometry.canvas_p1, + kRectangleColor); draw_list->AddRect(geometry.canvas_p0, geometry.canvas_p1, kWhiteColor); } -void RenderCanvasGrid( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - const CanvasConfig& config, - int highlight_tile_id) { - +void RenderCanvasGrid(ImDrawList* draw_list, const CanvasGeometry& geometry, + const CanvasConfig& config, int highlight_tile_id) { + if (!config.enable_grid) { return; } - + // Create render context for utility functions (extracted from Canvas::DrawGrid) CanvasUtils::CanvasRenderContext ctx = { .draw_list = draw_list, @@ -42,18 +39,16 @@ void RenderCanvasGrid( .enable_grid = config.enable_grid, .enable_hex_labels = config.enable_hex_labels, .grid_step = config.grid_step}; - + // Use high-level utility function CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id); } -void RenderCanvasOverlay( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - const CanvasConfig& config, - const ImVector& points, - const ImVector& selected_points) { - +void RenderCanvasOverlay(ImDrawList* draw_list, const CanvasGeometry& geometry, + const CanvasConfig& config, + const ImVector& points, + const ImVector& selected_points) { + // Create render context for utility functions (extracted from Canvas::DrawOverlay) CanvasUtils::CanvasRenderContext ctx = { .draw_list = draw_list, @@ -64,26 +59,23 @@ void RenderCanvasOverlay( .enable_grid = config.enable_grid, .enable_hex_labels = config.enable_hex_labels, .grid_step = config.grid_step}; - + // Use high-level utility function CanvasUtils::DrawCanvasOverlay(ctx, points, selected_points); } -void RenderCanvasLabels( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - const CanvasConfig& config, - const ImVector>& labels, - int current_labels, - int tile_id_offset) { - +void RenderCanvasLabels(ImDrawList* draw_list, const CanvasGeometry& geometry, + const CanvasConfig& config, + const ImVector>& labels, + int current_labels, int tile_id_offset) { + if (!config.enable_custom_labels || current_labels >= labels.size()) { return; } - + // Push clip rect to prevent drawing outside canvas draw_list->PushClipRect(geometry.canvas_p0, geometry.canvas_p1, true); - + // Create render context for utility functions CanvasUtils::CanvasRenderContext ctx = { .draw_list = draw_list, @@ -94,51 +86,42 @@ void RenderCanvasLabels( .enable_grid = config.enable_grid, .enable_hex_labels = config.enable_hex_labels, .grid_step = config.grid_step}; - + // Use high-level utility function (extracted from Canvas::DrawInfoGrid) CanvasUtils::DrawCanvasLabels(ctx, labels, current_labels, tile_id_offset); - + draw_list->PopClipRect(); } -void RenderBitmapOnCanvas( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - gfx::Bitmap& bitmap, - int /*border_offset*/, - float scale) { - +void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry, + gfx::Bitmap& bitmap, int /*border_offset*/, + float scale) { + if (!bitmap.is_active()) { return; } - + // Extracted from Canvas::DrawBitmap (border offset variant) - draw_list->AddImage( - (ImTextureID)(intptr_t)bitmap.texture(), - ImVec2(geometry.canvas_p0.x, geometry.canvas_p0.y), - ImVec2(geometry.canvas_p0.x + (bitmap.width() * scale), - geometry.canvas_p0.y + (bitmap.height() * scale))); + draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(), + ImVec2(geometry.canvas_p0.x, geometry.canvas_p0.y), + ImVec2(geometry.canvas_p0.x + (bitmap.width() * scale), + geometry.canvas_p0.y + (bitmap.height() * scale))); draw_list->AddRect(geometry.canvas_p0, geometry.canvas_p1, kWhiteColor); } -void RenderBitmapOnCanvas( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - gfx::Bitmap& bitmap, - int x_offset, - int y_offset, - float scale, - int alpha) { - +void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry, + gfx::Bitmap& bitmap, int x_offset, int y_offset, + float scale, int alpha) { + if (!bitmap.is_active()) { return; } - + // Calculate the actual rendered size including scale and offsets // CRITICAL: Use scale parameter (NOT global_scale_) for per-bitmap scaling // Extracted from Canvas::DrawBitmap (x/y offset variant) ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale); - + // CRITICAL FIX: Draw bitmap WITHOUT additional global_scale multiplication // The scale parameter already contains the correct scale factor // The scrolling should NOT be scaled - it's already in screen space @@ -146,28 +129,26 @@ void RenderBitmapOnCanvas( (ImTextureID)(intptr_t)bitmap.texture(), ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x, geometry.canvas_p0.y + y_offset + geometry.scrolling.y), - ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x + rendered_size.x, - geometry.canvas_p0.y + y_offset + geometry.scrolling.y + rendered_size.y), + ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x + + rendered_size.x, + geometry.canvas_p0.y + y_offset + geometry.scrolling.y + + rendered_size.y), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha)); } -void RenderBitmapOnCanvas( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - gfx::Bitmap& bitmap, - ImVec2 dest_pos, - ImVec2 dest_size, - ImVec2 src_pos, - ImVec2 src_size) { - +void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry, + gfx::Bitmap& bitmap, ImVec2 dest_pos, + ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) { + if (!bitmap.is_active()) { return; } - + // Extracted from Canvas::DrawBitmap (custom source/dest regions variant) draw_list->AddImage( (ImTextureID)(intptr_t)bitmap.texture(), - ImVec2(geometry.canvas_p0.x + dest_pos.x, geometry.canvas_p0.y + dest_pos.y), + ImVec2(geometry.canvas_p0.x + dest_pos.x, + geometry.canvas_p0.y + dest_pos.y), ImVec2(geometry.canvas_p0.x + dest_pos.x + dest_size.x, geometry.canvas_p0.y + dest_pos.y + dest_size.y), ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()), @@ -175,80 +156,80 @@ void RenderBitmapOnCanvas( (src_pos.y + src_size.y) / bitmap.height())); } -void RenderBitmapGroup( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - std::vector& group, - gfx::Tilemap& tilemap, - int tile_size, - float scale, - int local_map_size, - ImVec2 total_map_size) { - +void RenderBitmapGroup(ImDrawList* draw_list, const CanvasGeometry& geometry, + std::vector& group, gfx::Tilemap& tilemap, + int tile_size, float scale, int local_map_size, + ImVec2 total_map_size) { + // Extracted from Canvas::DrawBitmapGroup (lines 1148-1264) // This is used for multi-tile selection preview in overworld editor - + if (group.empty()) { return; } - + // OPTIMIZATION: Use optimized rendering for large groups to improve performance bool use_optimized_rendering = group.size() > 128; - + // Pre-calculate common values to avoid repeated computation const float tile_scale = tile_size * scale; const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; - + // Get selected points (note: this assumes selected_points are available in context) // For now, we'll just render tiles at their grid positions // The full implementation would need the selected_points passed in - + int i = 0; for (const auto tile_id : group) { // Calculate grid position for this tile int tiles_per_row = 32; // Default for standard maps int x = i % tiles_per_row; int y = i / tiles_per_row; - + int tile_pos_x = x * tile_size * scale; int tile_pos_y = y * tile_size * scale; - + // Check if tile_id is within the range auto tilemap_size = tilemap.map_size.x; if (tile_id >= 0 && tile_id < tilemap_size) { if (tilemap.atlas.is_active() && tilemap.atlas.texture() && atlas_tiles_per_row > 0) { - int atlas_tile_x = (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x; - int atlas_tile_y = (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y; - + int atlas_tile_x = + (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x; + int atlas_tile_y = + (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y; + // Simple bounds check if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() && atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) { - + // Calculate UV coordinates once for efficiency const float atlas_width = static_cast(tilemap.atlas.width()); const float atlas_height = static_cast(tilemap.atlas.height()); - ImVec2 uv0 = ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height); - ImVec2 uv1 = ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width, - (atlas_tile_y + tilemap.tile_size.y) / atlas_height); - + ImVec2 uv0 = + ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height); + ImVec2 uv1 = + ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width, + (atlas_tile_y + tilemap.tile_size.y) / atlas_height); + // Calculate screen positions - float screen_x = geometry.canvas_p0.x + geometry.scrolling.x + tile_pos_x; - float screen_y = geometry.canvas_p0.y + geometry.scrolling.y + tile_pos_y; + float screen_x = + geometry.canvas_p0.x + geometry.scrolling.x + tile_pos_x; + float screen_y = + geometry.canvas_p0.y + geometry.scrolling.y + tile_pos_y; float screen_w = tilemap.tile_size.x * scale; float screen_h = tilemap.tile_size.y * scale; - + // Use higher alpha for large selections to make them more visible uint32_t alpha_color = use_optimized_rendering ? IM_COL32(255, 255, 255, 200) : IM_COL32(255, 255, 255, 150); - + // Draw from atlas texture with optimized parameters - draw_list->AddImage( - (ImTextureID)(intptr_t)tilemap.atlas.texture(), - ImVec2(screen_x, screen_y), - ImVec2(screen_x + screen_w, screen_y + screen_h), - uv0, uv1, alpha_color); + draw_list->AddImage((ImTextureID)(intptr_t)tilemap.atlas.texture(), + ImVec2(screen_x, screen_y), + ImVec2(screen_x + screen_w, screen_y + screen_h), + uv0, uv1, alpha_color); } } } @@ -258,4 +239,3 @@ void RenderBitmapGroup( } // namespace gui } // namespace yaze - diff --git a/src/app/gui/canvas/canvas_rendering.h b/src/app/gui/canvas/canvas_rendering.h index abdf554d..3f339eda 100644 --- a/src/app/gui/canvas/canvas_rendering.h +++ b/src/app/gui/canvas/canvas_rendering.h @@ -21,9 +21,8 @@ namespace gui { * @param draw_list ImGui draw list for rendering * @param geometry Canvas geometry for this frame */ -void RenderCanvasBackground( - ImDrawList* draw_list, - const CanvasGeometry& geometry); +void RenderCanvasBackground(ImDrawList* draw_list, + const CanvasGeometry& geometry); /** * @brief Render canvas grid with optional highlighting @@ -36,11 +35,8 @@ void RenderCanvasBackground( * @param config Canvas configuration (grid settings) * @param highlight_tile_id Tile ID to highlight (-1 = no highlight) */ -void RenderCanvasGrid( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - const CanvasConfig& config, - int highlight_tile_id = -1); +void RenderCanvasGrid(ImDrawList* draw_list, const CanvasGeometry& geometry, + const CanvasConfig& config, int highlight_tile_id = -1); /** * @brief Render canvas overlay (hover and selection points) @@ -54,12 +50,10 @@ void RenderCanvasGrid( * @param points Hover preview points * @param selected_points Selection rectangle points */ -void RenderCanvasOverlay( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - const CanvasConfig& config, - const ImVector& points, - const ImVector& selected_points); +void RenderCanvasOverlay(ImDrawList* draw_list, const CanvasGeometry& geometry, + const CanvasConfig& config, + const ImVector& points, + const ImVector& selected_points); /** * @brief Render canvas labels on grid @@ -74,13 +68,10 @@ void RenderCanvasOverlay( * @param current_labels Active label set index * @param tile_id_offset Tile ID offset for calculation */ -void RenderCanvasLabels( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - const CanvasConfig& config, - const ImVector>& labels, - int current_labels, - int tile_id_offset); +void RenderCanvasLabels(ImDrawList* draw_list, const CanvasGeometry& geometry, + const CanvasConfig& config, + const ImVector>& labels, + int current_labels, int tile_id_offset); /** * @brief Render bitmap on canvas (border offset variant) @@ -94,12 +85,8 @@ void RenderCanvasLabels( * @param border_offset Offset from canvas edges * @param scale Rendering scale */ -void RenderBitmapOnCanvas( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - gfx::Bitmap& bitmap, - int border_offset, - float scale); +void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry, + gfx::Bitmap& bitmap, int border_offset, float scale); /** * @brief Render bitmap on canvas (x/y offset variant) @@ -115,14 +102,9 @@ void RenderBitmapOnCanvas( * @param scale Rendering scale * @param alpha Alpha transparency (0-255) */ -void RenderBitmapOnCanvas( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - gfx::Bitmap& bitmap, - int x_offset, - int y_offset, - float scale, - int alpha); +void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry, + gfx::Bitmap& bitmap, int x_offset, int y_offset, + float scale, int alpha); /** * @brief Render bitmap on canvas (custom source/dest regions) @@ -138,14 +120,9 @@ void RenderBitmapOnCanvas( * @param src_pos Source position in bitmap * @param src_size Source size in bitmap */ -void RenderBitmapOnCanvas( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - gfx::Bitmap& bitmap, - ImVec2 dest_pos, - ImVec2 dest_size, - ImVec2 src_pos, - ImVec2 src_size); +void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry, + gfx::Bitmap& bitmap, ImVec2 dest_pos, + ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size); /** * @brief Render group of bitmaps from tilemap @@ -162,18 +139,12 @@ void RenderBitmapOnCanvas( * @param local_map_size Size of local map in pixels (default 512) * @param total_map_size Total map size for boundary clamping */ -void RenderBitmapGroup( - ImDrawList* draw_list, - const CanvasGeometry& geometry, - std::vector& group, - gfx::Tilemap& tilemap, - int tile_size, - float scale, - int local_map_size, - ImVec2 total_map_size); +void RenderBitmapGroup(ImDrawList* draw_list, const CanvasGeometry& geometry, + std::vector& group, gfx::Tilemap& tilemap, + int tile_size, float scale, int local_map_size, + ImVec2 total_map_size); } // namespace gui } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_RENDERING_H - diff --git a/src/app/gui/canvas/canvas_state.h b/src/app/gui/canvas/canvas_state.h index b450964b..a45684d5 100644 --- a/src/app/gui/canvas/canvas_state.h +++ b/src/app/gui/canvas/canvas_state.h @@ -16,15 +16,18 @@ namespace gui { * functions to correctly position elements. */ struct CanvasGeometry { - ImVec2 canvas_p0; // Top-left screen position - ImVec2 canvas_p1; // Bottom-right screen position - ImVec2 canvas_sz; // Actual canvas size (unscaled) - ImVec2 scaled_size; // Size after applying global_scale - ImVec2 scrolling; // Current scroll offset - - CanvasGeometry() - : canvas_p0(0, 0), canvas_p1(0, 0), canvas_sz(0, 0), - scaled_size(0, 0), scrolling(0, 0) {} + ImVec2 canvas_p0; // Top-left screen position + ImVec2 canvas_p1; // Bottom-right screen position + ImVec2 canvas_sz; // Actual canvas size (unscaled) + ImVec2 scaled_size; // Size after applying global_scale + ImVec2 scrolling; // Current scroll offset + + CanvasGeometry() + : canvas_p0(0, 0), + canvas_p1(0, 0), + canvas_sz(0, 0), + scaled_size(0, 0), + scrolling(0, 0) {} }; /** @@ -44,31 +47,31 @@ struct CanvasState { // Core identification std::string canvas_id = "Canvas"; std::string context_id = "CanvasContext"; - + // Configuration (reference existing CanvasConfig) CanvasConfig config; - + // Selection (reference existing CanvasSelection) CanvasSelection selection; - + // Geometry (calculated per-frame) CanvasGeometry geometry; - + // Interaction state ImVec2 mouse_pos_in_canvas = ImVec2(0, 0); ImVec2 drawn_tile_pos = ImVec2(-1, -1); bool is_hovered = false; - + // Drawing state - ImVector points; // Hover preview points + ImVector points; // Hover preview points ImVector> labels; int current_labels = 0; int highlight_tile_id = -1; - + CanvasState() = default; - + // Convenience constructor with ID - explicit CanvasState(const std::string& id) + explicit CanvasState(const std::string& id) : canvas_id(id), context_id(id + "Context") {} }; @@ -76,4 +79,3 @@ struct CanvasState { } // namespace yaze #endif // YAZE_APP_GUI_CANVAS_CANVAS_STATE_H - diff --git a/src/app/gui/canvas/canvas_usage_tracker.cc b/src/app/gui/canvas/canvas_usage_tracker.cc index 955ac6b6..c1e20265 100644 --- a/src/app/gui/canvas/canvas_usage_tracker.cc +++ b/src/app/gui/canvas/canvas_usage_tracker.cc @@ -1,9 +1,9 @@ #include "canvas_usage_tracker.h" #include -#include -#include #include +#include +#include #include "util/log.h" @@ -22,26 +22,26 @@ void CanvasUsageTracker::SetUsageMode(CanvasUsage usage) { if (current_stats_.usage_mode != usage) { // Save current stats before changing mode SaveCurrentStats(); - + // Update usage mode current_stats_.usage_mode = usage; current_stats_.mode_changes++; - + // Record mode change interaction RecordInteraction(CanvasInteraction::kModeChange, GetUsageModeName(usage)); - - LOG_DEBUG("CanvasUsage", "Canvas %s: Usage mode changed to %s", - canvas_id_.c_str(), GetUsageModeName(usage).c_str()); + + LOG_DEBUG("CanvasUsage", "Canvas %s: Usage mode changed to %s", + canvas_id_.c_str(), GetUsageModeName(usage).c_str()); } } -void CanvasUsageTracker::RecordInteraction(CanvasInteraction interaction, - const std::string& details) { +void CanvasUsageTracker::RecordInteraction(CanvasInteraction interaction, + const std::string& details) { interaction_history_.push_back({interaction, details}); - + // Update activity time last_activity_ = std::chrono::steady_clock::now(); - + // Update interaction counts switch (interaction) { case CanvasInteraction::kMouseClick: @@ -67,11 +67,11 @@ void CanvasUsageTracker::RecordInteraction(CanvasInteraction interaction, } } -void CanvasUsageTracker::RecordOperation(const std::string& operation_name, - double time_ms) { +void CanvasUsageTracker::RecordOperation(const std::string& operation_name, + double time_ms) { operation_times_[operation_name].push_back(time_ms); current_stats_.total_operations++; - + // Update average operation time double total_time = 0.0; int total_ops = 0; @@ -81,27 +81,26 @@ void CanvasUsageTracker::RecordOperation(const std::string& operation_name, total_ops++; } } - + if (total_ops > 0) { current_stats_.average_operation_time_ms = total_time / total_ops; } - + // Update max operation time if (time_ms > current_stats_.max_operation_time_ms) { current_stats_.max_operation_time_ms = time_ms; } - + // Record as interaction RecordInteraction(CanvasInteraction::kKeyboardInput, operation_name); } void CanvasUsageTracker::UpdateCanvasState(const ImVec2& canvas_size, - const ImVec2& content_size, - float global_scale, - float grid_step, - bool enable_grid, - bool enable_hex_labels, - bool enable_custom_labels) { + const ImVec2& content_size, + float global_scale, float grid_step, + bool enable_grid, + bool enable_hex_labels, + bool enable_custom_labels) { current_stats_.canvas_size = canvas_size; current_stats_.content_size = content_size; current_stats_.global_scale = global_scale; @@ -109,7 +108,7 @@ void CanvasUsageTracker::UpdateCanvasState(const ImVec2& canvas_size, current_stats_.enable_grid = enable_grid; current_stats_.enable_hex_labels = enable_hex_labels; current_stats_.enable_custom_labels = enable_custom_labels; - + // Update activity time last_activity_ = std::chrono::steady_clock::now(); } @@ -118,120 +117,154 @@ void CanvasUsageTracker::UpdateCanvasState(const ImVec2& canvas_size, std::string CanvasUsageTracker::GetUsageModeName(CanvasUsage usage) const { switch (usage) { - case CanvasUsage::kTilePainting: return "Tile Painting"; - case CanvasUsage::kTileSelecting: return "Tile Selecting"; - case CanvasUsage::kSelectRectangle: return "Rectangle Selection"; - case CanvasUsage::kColorPainting: return "Color Painting"; - case CanvasUsage::kBitmapEditing: return "Bitmap Editing"; - case CanvasUsage::kPaletteEditing: return "Palette Editing"; - case CanvasUsage::kBppConversion: return "BPP Conversion"; - case CanvasUsage::kPerformanceMode: return "Performance Mode"; - case CanvasUsage::kEntityManipulation: return "Entity Manipulation"; - case CanvasUsage::kUnknown: return "Unknown"; - default: return "Unknown"; + case CanvasUsage::kTilePainting: + return "Tile Painting"; + case CanvasUsage::kTileSelecting: + return "Tile Selecting"; + case CanvasUsage::kSelectRectangle: + return "Rectangle Selection"; + case CanvasUsage::kColorPainting: + return "Color Painting"; + case CanvasUsage::kBitmapEditing: + return "Bitmap Editing"; + case CanvasUsage::kPaletteEditing: + return "Palette Editing"; + case CanvasUsage::kBppConversion: + return "BPP Conversion"; + case CanvasUsage::kPerformanceMode: + return "Performance Mode"; + case CanvasUsage::kEntityManipulation: + return "Entity Manipulation"; + case CanvasUsage::kUnknown: + return "Unknown"; + default: + return "Unknown"; } } ImVec4 CanvasUsageTracker::GetUsageModeColor(CanvasUsage usage) const { switch (usage) { - case CanvasUsage::kTilePainting: return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green - case CanvasUsage::kTileSelecting: return ImVec4(0.2F, 0.8F, 1.0F, 1.0F); // Blue - case CanvasUsage::kSelectRectangle: return ImVec4(1.0F, 0.8F, 0.2F, 1.0F); // Yellow - case CanvasUsage::kColorPainting: return ImVec4(1.0F, 0.2F, 1.0F, 1.0F); // Magenta - case CanvasUsage::kBitmapEditing: return ImVec4(1.0F, 0.5F, 0.2F, 1.0F); // Orange - case CanvasUsage::kPaletteEditing: return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple - case CanvasUsage::kBppConversion: return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan - case CanvasUsage::kPerformanceMode: return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red - case CanvasUsage::kEntityManipulation: return ImVec4(0.4F, 0.8F, 1.0F, 1.0F); // Light Blue - case CanvasUsage::kUnknown: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray - default: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray + case CanvasUsage::kTilePainting: + return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green + case CanvasUsage::kTileSelecting: + return ImVec4(0.2F, 0.8F, 1.0F, 1.0F); // Blue + case CanvasUsage::kSelectRectangle: + return ImVec4(1.0F, 0.8F, 0.2F, 1.0F); // Yellow + case CanvasUsage::kColorPainting: + return ImVec4(1.0F, 0.2F, 1.0F, 1.0F); // Magenta + case CanvasUsage::kBitmapEditing: + return ImVec4(1.0F, 0.5F, 0.2F, 1.0F); // Orange + case CanvasUsage::kPaletteEditing: + return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple + case CanvasUsage::kBppConversion: + return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan + case CanvasUsage::kPerformanceMode: + return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red + case CanvasUsage::kEntityManipulation: + return ImVec4(0.4F, 0.8F, 1.0F, 1.0F); // Light Blue + case CanvasUsage::kUnknown: + return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray + default: + return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray } } std::vector CanvasUsageTracker::GetUsageRecommendations() const { std::vector recommendations; - + // Analyze usage patterns and provide recommendations if (current_stats_.mouse_clicks > 100) { - recommendations.push_back("Consider using keyboard shortcuts to reduce mouse usage"); + recommendations.push_back( + "Consider using keyboard shortcuts to reduce mouse usage"); } - + if (current_stats_.context_menu_opens > 20) { - recommendations.push_back("Frequent context menu usage - consider adding toolbar buttons"); + recommendations.push_back( + "Frequent context menu usage - consider adding toolbar buttons"); } - + if (current_stats_.modal_opens > 10) { - recommendations.push_back("Many modal dialogs opened - consider persistent panels"); + recommendations.push_back( + "Many modal dialogs opened - consider persistent panels"); } - + if (current_stats_.average_operation_time_ms > 100.0) { - recommendations.push_back("Operations are slow - check performance optimization"); + recommendations.push_back( + "Operations are slow - check performance optimization"); } - + if (current_stats_.mode_changes > 5) { - recommendations.push_back("Frequent mode switching - consider mode-specific toolbars"); + recommendations.push_back( + "Frequent mode switching - consider mode-specific toolbars"); } - + return recommendations; } std::string CanvasUsageTracker::ExportUsageReport() const { std::ostringstream report; - + report << "Canvas Usage Report for: " << canvas_id_ << "\n"; report << "==========================================\n\n"; - + // Session information auto now = std::chrono::steady_clock::now(); auto session_duration = std::chrono::duration_cast( now - session_start_); - + report << "Session Information:\n"; report << " Duration: " << FormatDuration(session_duration) << "\n"; - report << " Current Mode: " << GetUsageModeName(current_stats_.usage_mode) << "\n"; + report << " Current Mode: " << GetUsageModeName(current_stats_.usage_mode) + << "\n"; report << " Mode Changes: " << current_stats_.mode_changes << "\n\n"; - + // Interaction statistics report << "Interaction Statistics:\n"; report << " Mouse Clicks: " << current_stats_.mouse_clicks << "\n"; report << " Mouse Drags: " << current_stats_.mouse_drags << "\n"; - report << " Context Menu Opens: " << current_stats_.context_menu_opens << "\n"; + report << " Context Menu Opens: " << current_stats_.context_menu_opens + << "\n"; report << " Modal Opens: " << current_stats_.modal_opens << "\n"; report << " Tool Changes: " << current_stats_.tool_changes << "\n\n"; - + // Performance statistics report << "Performance Statistics:\n"; report << " Total Operations: " << current_stats_.total_operations << "\n"; - report << " Average Operation Time: " << std::fixed << std::setprecision(2) + report << " Average Operation Time: " << std::fixed << std::setprecision(2) << current_stats_.average_operation_time_ms << " ms\n"; - report << " Max Operation Time: " << std::fixed << std::setprecision(2) + report << " Max Operation Time: " << std::fixed << std::setprecision(2) << current_stats_.max_operation_time_ms << " ms\n\n"; - + // Canvas state report << "Canvas State:\n"; - report << " Canvas Size: " << static_cast(current_stats_.canvas_size.x) + report << " Canvas Size: " << static_cast(current_stats_.canvas_size.x) << " x " << static_cast(current_stats_.canvas_size.y) << "\n"; - report << " Content Size: " << static_cast(current_stats_.content_size.x) - << " x " << static_cast(current_stats_.content_size.y) << "\n"; - report << " Global Scale: " << std::fixed << std::setprecision(2) + report << " Content Size: " + << static_cast(current_stats_.content_size.x) << " x " + << static_cast(current_stats_.content_size.y) << "\n"; + report << " Global Scale: " << std::fixed << std::setprecision(2) << current_stats_.global_scale << "\n"; - report << " Grid Step: " << std::fixed << std::setprecision(1) + report << " Grid Step: " << std::fixed << std::setprecision(1) << current_stats_.grid_step << "\n"; - report << " Grid Enabled: " << (current_stats_.enable_grid ? "Yes" : "No") << "\n"; - report << " Hex Labels: " << (current_stats_.enable_hex_labels ? "Yes" : "No") << "\n"; - report << " Custom Labels: " << (current_stats_.enable_custom_labels ? "Yes" : "No") << "\n\n"; - + report << " Grid Enabled: " << (current_stats_.enable_grid ? "Yes" : "No") + << "\n"; + report << " Hex Labels: " + << (current_stats_.enable_hex_labels ? "Yes" : "No") << "\n"; + report << " Custom Labels: " + << (current_stats_.enable_custom_labels ? "Yes" : "No") << "\n\n"; + // Operation breakdown if (!operation_times_.empty()) { report << "Operation Breakdown:\n"; for (const auto& [operation, times] : operation_times_) { double avg_time = CalculateAverageOperationTime(operation); report << " " << operation << ": " << times.size() << " operations, " - << "avg " << std::fixed << std::setprecision(2) << avg_time << " ms\n"; + << "avg " << std::fixed << std::setprecision(2) << avg_time + << " ms\n"; } report << "\n"; } - + // Recommendations auto recommendations = GetUsageRecommendations(); if (!recommendations.empty()) { @@ -240,7 +273,7 @@ std::string CanvasUsageTracker::ExportUsageReport() const { report << " • " << rec << "\n"; } } - + return report.str(); } @@ -264,33 +297,37 @@ void CanvasUsageTracker::EndSession() { // Update final statistics UpdateActiveTime(); UpdateIdleTime(); - + // Save final stats SaveCurrentStats(); - - LOG_DEBUG("CanvasUsage", "Canvas %s: Session ended. Duration: %s, Operations: %d", - canvas_id_.c_str(), - FormatDuration(std::chrono::duration_cast( - std::chrono::steady_clock::now() - session_start_)).c_str(), - current_stats_.total_operations); + + LOG_DEBUG( + "CanvasUsage", "Canvas %s: Session ended. Duration: %s, Operations: %d", + canvas_id_.c_str(), + FormatDuration(std::chrono::duration_cast( + std::chrono::steady_clock::now() - session_start_)) + .c_str(), + current_stats_.total_operations); } void CanvasUsageTracker::UpdateActiveTime() { auto now = std::chrono::steady_clock::now(); - auto time_since_activity = std::chrono::duration_cast( - now - last_activity_); - - if (time_since_activity.count() < 5000) { // 5 seconds threshold + auto time_since_activity = + std::chrono::duration_cast(now - + last_activity_); + + if (time_since_activity.count() < 5000) { // 5 seconds threshold current_stats_.active_time += time_since_activity; } } void CanvasUsageTracker::UpdateIdleTime() { auto now = std::chrono::steady_clock::now(); - auto time_since_activity = std::chrono::duration_cast( - now - last_activity_); - - if (time_since_activity.count() >= 5000) { // 5 seconds threshold + auto time_since_activity = + std::chrono::duration_cast(now - + last_activity_); + + if (time_since_activity.count() >= 5000) { // 5 seconds threshold current_stats_.idle_time += time_since_activity; } } @@ -299,38 +336,41 @@ void CanvasUsageTracker::SaveCurrentStats() { // Update final times UpdateActiveTime(); UpdateIdleTime(); - + // Calculate total time - current_stats_.total_time = current_stats_.active_time + current_stats_.idle_time; - + current_stats_.total_time = + current_stats_.active_time + current_stats_.idle_time; + // Save to history usage_history_.push_back(current_stats_); - + // Reset for next session current_stats_.Reset(); current_stats_.session_start = std::chrono::steady_clock::now(); } -double CanvasUsageTracker::CalculateAverageOperationTime(const std::string& operation_name) const { +double CanvasUsageTracker::CalculateAverageOperationTime( + const std::string& operation_name) const { auto it = operation_times_.find(operation_name); if (it == operation_times_.end() || it->second.empty()) { return 0.0; } - + double total = 0.0; for (double time : it->second) { total += time; } - + return total / it->second.size(); } -std::string CanvasUsageTracker::FormatDuration(const std::chrono::milliseconds& duration) const { +std::string CanvasUsageTracker::FormatDuration( + const std::chrono::milliseconds& duration) const { auto total_ms = duration.count(); auto hours = total_ms / 3600000; auto minutes = (total_ms % 3600000) / 60000; auto seconds = (total_ms % 60000) / 1000; - + std::ostringstream ss; if (hours > 0) { ss << hours << "h " << minutes << "m " << seconds << "s"; @@ -339,7 +379,7 @@ std::string CanvasUsageTracker::FormatDuration(const std::chrono::milliseconds& } else { ss << seconds << "s"; } - + return ss.str(); } @@ -350,13 +390,15 @@ CanvasUsageManager& CanvasUsageManager::Get() { return instance; } -void CanvasUsageManager::RegisterTracker(const std::string& canvas_id, - std::shared_ptr tracker) { +void CanvasUsageManager::RegisterTracker( + const std::string& canvas_id, std::shared_ptr tracker) { trackers_[canvas_id] = tracker; - LOG_DEBUG("CanvasUsage", "Registered usage tracker for canvas: %s", canvas_id.c_str()); + LOG_DEBUG("CanvasUsage", "Registered usage tracker for canvas: %s", + canvas_id.c_str()); } -std::shared_ptr CanvasUsageManager::GetTracker(const std::string& canvas_id) { +std::shared_ptr CanvasUsageManager::GetTracker( + const std::string& canvas_id) { auto it = trackers_.find(canvas_id); if (it != trackers_.end()) { return it->second; @@ -366,10 +408,10 @@ std::shared_ptr CanvasUsageManager::GetTracker(const std::st CanvasUsageStats CanvasUsageManager::GetGlobalStats() const { CanvasUsageStats global_stats; - + for (const auto& [id, tracker] : trackers_) { const auto& stats = tracker->GetCurrentStats(); - + global_stats.mouse_clicks += stats.mouse_clicks; global_stats.mouse_drags += stats.mouse_drags; global_stats.context_menu_opens += stats.context_menu_opens; @@ -377,43 +419,44 @@ CanvasUsageStats CanvasUsageManager::GetGlobalStats() const { global_stats.tool_changes += stats.tool_changes; global_stats.mode_changes += stats.mode_changes; global_stats.total_operations += stats.total_operations; - + // Update averages - if (stats.average_operation_time_ms > global_stats.average_operation_time_ms) { + if (stats.average_operation_time_ms > + global_stats.average_operation_time_ms) { global_stats.average_operation_time_ms = stats.average_operation_time_ms; } if (stats.max_operation_time_ms > global_stats.max_operation_time_ms) { global_stats.max_operation_time_ms = stats.max_operation_time_ms; } } - + return global_stats; } std::string CanvasUsageManager::ExportGlobalReport() const { std::ostringstream report; - + report << "Global Canvas Usage Report\n"; report << "==========================\n\n"; - + report << "Registered Canvases: " << trackers_.size() << "\n\n"; - + for (const auto& [id, tracker] : trackers_) { report << "Canvas: " << id << "\n"; report << "----------------------------------------\n"; report << tracker->ExportUsageReport() << "\n\n"; } - + // Global summary auto global_stats = GetGlobalStats(); report << "Global Summary:\n"; report << " Total Mouse Clicks: " << global_stats.mouse_clicks << "\n"; report << " Total Operations: " << global_stats.total_operations << "\n"; - report << " Average Operation Time: " << std::fixed << std::setprecision(2) + report << " Average Operation Time: " << std::fixed << std::setprecision(2) << global_stats.average_operation_time_ms << " ms\n"; - report << " Max Operation Time: " << std::fixed << std::setprecision(2) + report << " Max Operation Time: " << std::fixed << std::setprecision(2) << global_stats.max_operation_time_ms << " ms\n"; - + return report.str(); } diff --git a/src/app/gui/canvas/canvas_usage_tracker.h b/src/app/gui/canvas/canvas_usage_tracker.h index 3a198e50..5f7b968a 100644 --- a/src/app/gui/canvas/canvas_usage_tracker.h +++ b/src/app/gui/canvas/canvas_usage_tracker.h @@ -1,11 +1,11 @@ #ifndef YAZE_APP_GUI_CANVAS_CANVAS_USAGE_TRACKER_H #define YAZE_APP_GUI_CANVAS_CANVAS_USAGE_TRACKER_H -#include -#include -#include #include #include +#include +#include +#include #include "imgui/imgui.h" namespace yaze { @@ -15,16 +15,16 @@ namespace gui { * @brief Canvas usage patterns and tracking */ enum class CanvasUsage { - kTilePainting, // Drawing tiles on canvas - kTileSelecting, // Selecting tiles from canvas - kSelectRectangle, // Rectangle selection mode - kColorPainting, // Color painting mode - kBitmapEditing, // Direct bitmap editing - kPaletteEditing, // Palette editing mode - kBppConversion, // BPP format conversion - kPerformanceMode, // Performance monitoring mode - kEntityManipulation, // Generic entity manipulation (insertion/editing/deletion) - kUnknown // Unknown or mixed usage + kTilePainting, // Drawing tiles on canvas + kTileSelecting, // Selecting tiles from canvas + kSelectRectangle, // Rectangle selection mode + kColorPainting, // Color painting mode + kBitmapEditing, // Direct bitmap editing + kPaletteEditing, // Palette editing mode + kBppConversion, // BPP format conversion + kPerformanceMode, // Performance monitoring mode + kEntityManipulation, // Generic entity manipulation (insertion/editing/deletion) + kUnknown // Unknown or mixed usage }; /** @@ -51,7 +51,7 @@ struct CanvasUsageStats { std::chrono::milliseconds total_time{0}; std::chrono::milliseconds active_time{0}; std::chrono::milliseconds idle_time{0}; - + // Interaction counts int mouse_clicks = 0; int mouse_drags = 0; @@ -59,12 +59,12 @@ struct CanvasUsageStats { int modal_opens = 0; int tool_changes = 0; int mode_changes = 0; - + // Performance metrics double average_operation_time_ms = 0.0; double max_operation_time_ms = 0.0; int total_operations = 0; - + // Canvas state ImVec2 canvas_size = ImVec2(0, 0); ImVec2 content_size = ImVec2(0, 0); @@ -73,7 +73,7 @@ struct CanvasUsageStats { bool enable_grid = true; bool enable_hex_labels = false; bool enable_custom_labels = false; - + void Reset() { usage_mode = CanvasUsage::kUnknown; session_start = std::chrono::steady_clock::now(); @@ -98,80 +98,77 @@ struct CanvasUsageStats { class CanvasUsageTracker { public: CanvasUsageTracker() = default; - + /** * @brief Initialize the usage tracker */ void Initialize(const std::string& canvas_id); - + /** * @brief Set the current usage mode */ void SetUsageMode(CanvasUsage usage); - + /** * @brief Record an interaction */ - void RecordInteraction(CanvasInteraction interaction, - const std::string& details = ""); - + void RecordInteraction(CanvasInteraction interaction, + const std::string& details = ""); + /** * @brief Record operation timing */ - void RecordOperation(const std::string& operation_name, - double time_ms); - + void RecordOperation(const std::string& operation_name, double time_ms); + /** * @brief Update canvas state */ - void UpdateCanvasState(const ImVec2& canvas_size, - const ImVec2& content_size, - float global_scale, - float grid_step, - bool enable_grid, - bool enable_hex_labels, - bool enable_custom_labels); - + void UpdateCanvasState(const ImVec2& canvas_size, const ImVec2& content_size, + float global_scale, float grid_step, bool enable_grid, + bool enable_hex_labels, bool enable_custom_labels); + /** * @brief Get current usage statistics */ const CanvasUsageStats& GetCurrentStats() const { return current_stats_; } - + /** * @brief Get usage history */ - const std::vector& GetUsageHistory() const { return usage_history_; } - + const std::vector& GetUsageHistory() const { + return usage_history_; + } + /** * @brief Get usage mode name */ std::string GetUsageModeName(CanvasUsage usage) const; - + /** * @brief Get usage mode color for UI */ ImVec4 GetUsageModeColor(CanvasUsage usage) const; - + /** * @brief Get usage recommendations */ std::vector GetUsageRecommendations() const; - + /** * @brief Export usage report */ std::string ExportUsageReport() const; - + /** * @brief Clear usage history */ void ClearHistory(); - + /** * @brief Start session */ void StartSession(); - + /** * @brief End session */ @@ -183,11 +180,11 @@ class CanvasUsageTracker { std::vector usage_history_; std::chrono::steady_clock::time_point last_activity_; std::chrono::steady_clock::time_point session_start_; - + // Interaction history std::vector> interaction_history_; std::unordered_map> operation_times_; - + // Helper methods void UpdateActiveTime(); void UpdateIdleTime(); @@ -202,34 +199,36 @@ class CanvasUsageTracker { class CanvasUsageManager { public: static CanvasUsageManager& Get(); - + /** * @brief Register a canvas tracker */ - void RegisterTracker(const std::string& canvas_id, - std::shared_ptr tracker); - + void RegisterTracker(const std::string& canvas_id, + std::shared_ptr tracker); + /** * @brief Get tracker for canvas */ std::shared_ptr GetTracker(const std::string& canvas_id); - + /** * @brief Get all trackers */ - const std::unordered_map>& - GetAllTrackers() const { return trackers_; } - + const std::unordered_map>& + GetAllTrackers() const { + return trackers_; + } + /** * @brief Get global usage statistics */ CanvasUsageStats GetGlobalStats() const; - + /** * @brief Export global usage report */ std::string ExportGlobalReport() const; - + /** * @brief Clear all trackers */ @@ -238,8 +237,9 @@ class CanvasUsageManager { private: CanvasUsageManager() = default; ~CanvasUsageManager() = default; - - std::unordered_map> trackers_; + + std::unordered_map> + trackers_; }; } // namespace gui diff --git a/src/app/gui/canvas/canvas_utils.cc b/src/app/gui/canvas/canvas_utils.cc index 716bc592..acde4c0c 100644 --- a/src/app/gui/canvas/canvas_utils.cc +++ b/src/app/gui/canvas/canvas_utils.cc @@ -84,7 +84,7 @@ bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) { palette_manager.palettes_loaded = true; LOG_DEBUG("Canvas", "Loaded %zu ROM palette groups", - palette_manager.rom_palette_groups.size()); + palette_manager.rom_palette_groups.size()); return true; } catch (const std::exception& e) { @@ -93,16 +93,19 @@ bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) { } } -bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, CanvasPaletteManager& palette_manager, - int group_index, int palette_index) { - if (!bitmap) return false; +bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, + CanvasPaletteManager& palette_manager, int group_index, + int palette_index) { + if (!bitmap) + return false; - if (group_index < 0 || group_index >= palette_manager.rom_palette_groups.size()) { + if (group_index < 0 || + group_index >= palette_manager.rom_palette_groups.size()) { return false; } const auto& palette = palette_manager.rom_palette_groups[group_index]; - + // Apply the full palette or use SetPaletteWithTransparent if palette_index is specified if (palette_index == 0) { bitmap->SetPalette(palette); @@ -111,7 +114,7 @@ bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, CanvasPale } bitmap->set_modified(true); palette_manager.palette_dirty = true; - + // Queue texture update only if live_update is enabled if (palette_manager.live_update_enabled && renderer) { gfx::Arena::Get().QueueTextureCommand( @@ -136,7 +139,8 @@ void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, ImVec2 size(canvas_p0.x + scrolling.x + scaled_x + scaled_w, canvas_p0.y + scrolling.y + scaled_y + scaled_h); - uint32_t color_u32 = IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255); + uint32_t color_u32 = + IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255); draw_list->AddRectFilled(origin, size, color_u32); // Add a black outline @@ -397,7 +401,8 @@ float CanvasConfig::GetToolbarHeight() const { // Use layout helpers for theme-aware sizing // We need to include layout_helpers.h in the implementation file // For now, return a reasonable default that respects ImGui font size - return ImGui::GetFontSize() * 0.75f; // Will be replaced with LayoutHelpers call + return ImGui::GetFontSize() * + 0.75f; // Will be replaced with LayoutHelpers call } float CanvasConfig::GetGridSpacing() const { diff --git a/src/app/gui/canvas/canvas_utils.h b/src/app/gui/canvas/canvas_utils.h index 6caae038..f4a0c5f8 100644 --- a/src/app/gui/canvas/canvas_utils.h +++ b/src/app/gui/canvas/canvas_utils.h @@ -5,8 +5,8 @@ #include #include "app/gfx/resource/arena.h" #include "app/gfx/types/snes_palette.h" -#include "app/rom.h" #include "app/gui/canvas/canvas_usage_tracker.h" +#include "app/rom.h" #include "imgui/imgui.h" namespace yaze { @@ -26,10 +26,12 @@ struct CanvasConfig { bool enable_context_menu = true; bool is_draggable = false; bool auto_resize = false; - bool clamp_rect_to_local_maps = true; // Prevent rectangle wrap across 512x512 boundaries - bool use_theme_sizing = true; // Use theme-aware sizing instead of fixed sizes + bool clamp_rect_to_local_maps = + true; // Prevent rectangle wrap across 512x512 boundaries + bool use_theme_sizing = + true; // Use theme-aware sizing instead of fixed sizes bool enable_metrics = false; // Enable performance/usage tracking - + // Sizing and scale float grid_step = 32.0f; float global_scale = 1.0f; @@ -37,10 +39,10 @@ struct CanvasConfig { ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.) ImVec2 scrolling = ImVec2(0, 0); bool custom_canvas_size = false; - + // Usage tracking CanvasUsage usage_mode = CanvasUsage::kUnknown; - + // Callbacks for configuration changes (used by modals) std::function on_config_changed; std::function on_scale_changed; @@ -59,7 +61,7 @@ struct CanvasSelection { std::vector selected_points; ImVec2 selected_tile_pos = ImVec2(-1, -1); bool select_rect_active = false; - + void Clear() { selected_tiles.clear(); selected_points.clear(); @@ -78,11 +80,11 @@ struct CanvasPaletteManager { bool palettes_loaded = false; int current_group_index = 0; int current_palette_index = 0; - + // Live update control bool live_update_enabled = true; // Enable/disable live texture updates - bool palette_dirty = false; // Track if palette has changed - + bool palette_dirty = false; // Track if palette has changed + void Clear() { rom_palette_groups.clear(); palette_group_names.clear(); @@ -102,7 +104,9 @@ struct CanvasContextMenuItem { std::string label; std::string shortcut; std::function callback; - std::function enabled_condition = []() { return true; }; + std::function enabled_condition = []() { + return true; + }; std::vector subitems; }; @@ -113,18 +117,23 @@ namespace CanvasUtils { // Core utility functions ImVec2 AlignToGrid(ImVec2 pos, float grid_step); -float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale); -int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row); +float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, + float global_scale); +int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, + int tiles_per_row); // Palette management utilities bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager); -bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, CanvasPaletteManager& palette_manager, - int group_index, int palette_index); +bool ApplyPaletteGroup(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, + CanvasPaletteManager& palette_manager, int group_index, + int palette_index); /** * @brief Apply pending palette updates (when live_update is disabled) */ -inline void ApplyPendingPaletteUpdates(gfx::IRenderer* renderer, gfx::Bitmap* bitmap, CanvasPaletteManager& palette_manager) { +inline void ApplyPendingPaletteUpdates(gfx::IRenderer* renderer, + gfx::Bitmap* bitmap, + CanvasPaletteManager& palette_manager) { if (palette_manager.palette_dirty && bitmap && renderer) { gfx::Arena::Get().QueueTextureCommand( gfx::Arena::TextureCommandType::UPDATE, bitmap); @@ -133,31 +142,40 @@ inline void ApplyPendingPaletteUpdates(gfx::IRenderer* renderer, gfx::Bitmap* bi } // Drawing utility functions (moved from Canvas class) -void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, - int x, int y, int w, int h, ImVec4 color, float global_scale); +void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, + int x, int y, int w, int h, ImVec4 color, + float global_scale); void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, - const std::string& text, int x, int y, float global_scale); -void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, - int x, int y, int w, int h, uint32_t color = IM_COL32(255, 255, 255, 200)); -void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, - int x, int y, int w, int h, ImVec4 color); + const std::string& text, int x, int y, float global_scale); +void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0, + ImVec2 scrolling, int x, int y, int w, int h, + uint32_t color = IM_COL32(255, 255, 255, 200)); +void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0, + ImVec2 scrolling, int x, int y, int w, int h, + ImVec4 color); // Grid utility functions -void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1, - ImVec2 scrolling, float grid_step, float global_scale); -void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, - int highlight_tile_id, float grid_step); -void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, - ImVec2 canvas_sz, float grid_step, float global_scale); +void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0, + ImVec2 canvas_p1, ImVec2 scrolling, float grid_step, + float global_scale); +void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0, + ImVec2 scrolling, int highlight_tile_id, + float grid_step); +void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0, + ImVec2 scrolling, ImVec2 canvas_sz, float grid_step, + float global_scale); -// Layout and interaction utilities -ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, bool use_custom); +// Layout and interaction utilities +ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, + bool use_custom); ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale); bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1); // Size reporting for ImGui table integration -ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding = 4.0f); -ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale = 1.0f); +ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, + float padding = 4.0f); +ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, + float min_scale = 1.0f); void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label = ""); void SetNextCanvasSize(ImVec2 size, bool auto_resize = false); @@ -175,14 +193,16 @@ struct CanvasRenderContext { // Composite drawing operations void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id = -1); -void DrawCanvasOverlay(const CanvasRenderContext& ctx, const ImVector& points, - const ImVector& selected_points); -void DrawCanvasLabels(const CanvasRenderContext& ctx, const ImVector>& labels, - int current_labels, int tile_id_offset); +void DrawCanvasOverlay(const CanvasRenderContext& ctx, + const ImVector& points, + const ImVector& selected_points); +void DrawCanvasLabels(const CanvasRenderContext& ctx, + const ImVector>& labels, + int current_labels, int tile_id_offset); -} // namespace CanvasUtils +} // namespace CanvasUtils -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze -#endif // YAZE_APP_GUI_CANVAS_UTILS_H +#endif // YAZE_APP_GUI_CANVAS_UTILS_H diff --git a/src/app/gui/core/background_renderer.cc b/src/app/gui/core/background_renderer.cc index 8c948fcf..d021df96 100644 --- a/src/app/gui/core/background_renderer.cc +++ b/src/app/gui/core/background_renderer.cc @@ -3,8 +3,8 @@ #include #include -#include "app/platform/timing.h" #include "app/gui/core/theme_manager.h" +#include "app/platform/timing.h" #include "imgui/imgui.h" #ifndef M_PI @@ -20,104 +20,124 @@ BackgroundRenderer& BackgroundRenderer::Get() { return instance; } -void BackgroundRenderer::RenderDockingBackground(ImDrawList* draw_list, const ImVec2& window_pos, - const ImVec2& window_size, const Color& theme_color) { - if (!draw_list) return; - +void BackgroundRenderer::RenderDockingBackground(ImDrawList* draw_list, + const ImVec2& window_pos, + const ImVec2& window_size, + const Color& theme_color) { + if (!draw_list) + return; + UpdateAnimation(TimingManager::Get().GetDeltaTime()); - + // Get current theme colors auto& theme_manager = ThemeManager::Get(); auto current_theme = theme_manager.GetCurrentTheme(); - + // Create a subtle tinted background - Color bg_tint = { - current_theme.background.red * 1.1f, - current_theme.background.green * 1.1f, - current_theme.background.blue * 1.1f, - 0.3f - }; - - ImU32 bg_color = ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(bg_tint)); - draw_list->AddRectFilled(window_pos, - ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), - bg_color); - + Color bg_tint = {current_theme.background.red * 1.1f, + current_theme.background.green * 1.1f, + current_theme.background.blue * 1.1f, 0.3f}; + + ImU32 bg_color = + ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(bg_tint)); + draw_list->AddRectFilled( + window_pos, + ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), + bg_color); + // Render the grid if enabled if (grid_settings_.grid_size > 0) { RenderGridBackground(draw_list, window_pos, window_size, theme_color); } - + // Add subtle corner accents if (current_theme.enable_glow_effects) { float corner_size = 60.0f; Color accent_faded = current_theme.accent; accent_faded.alpha = 0.1f + 0.05f * sinf(animation_time_ * 2.0f); - - ImU32 corner_color = ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(accent_faded)); - + + ImU32 corner_color = + ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(accent_faded)); + // Top-left corner draw_list->AddRectFilledMultiColor( window_pos, ImVec2(window_pos.x + corner_size, window_pos.y + corner_size), - corner_color, IM_COL32(0,0,0,0), IM_COL32(0,0,0,0), corner_color); - - // Bottom-right corner + corner_color, IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 0), corner_color); + + // Bottom-right corner draw_list->AddRectFilledMultiColor( - ImVec2(window_pos.x + window_size.x - corner_size, window_pos.y + window_size.y - corner_size), + ImVec2(window_pos.x + window_size.x - corner_size, + window_pos.y + window_size.y - corner_size), ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), - IM_COL32(0,0,0,0), corner_color, corner_color, IM_COL32(0,0,0,0)); + IM_COL32(0, 0, 0, 0), corner_color, corner_color, IM_COL32(0, 0, 0, 0)); } } -void BackgroundRenderer::RenderGridBackground(ImDrawList* draw_list, const ImVec2& window_pos, - const ImVec2& window_size, const Color& grid_color) { - if (!draw_list || grid_settings_.grid_size <= 0) return; - +void BackgroundRenderer::RenderGridBackground(ImDrawList* draw_list, + const ImVec2& window_pos, + const ImVec2& window_size, + const Color& grid_color) { + if (!draw_list || grid_settings_.grid_size <= 0) + return; + // Grid parameters with optional animation float grid_size = grid_settings_.grid_size; float offset_x = 0.0f; float offset_y = 0.0f; - + // Apply animation if enabled if (grid_settings_.enable_animation) { - float animation_offset = animation_time_ * grid_settings_.animation_speed * 10.0f; + float animation_offset = + animation_time_ * grid_settings_.animation_speed * 10.0f; offset_x = fmodf(animation_offset, grid_size); - offset_y = fmodf(animation_offset * 0.7f, grid_size); // Different speed for interesting effect + offset_y = fmodf(animation_offset * 0.7f, + grid_size); // Different speed for interesting effect } - + // Window center for radial calculations - ImVec2 center = ImVec2(window_pos.x + window_size.x * 0.5f, - window_pos.y + window_size.y * 0.5f); - float max_distance = sqrtf(window_size.x * window_size.x + window_size.y * window_size.y) * 0.5f; - + ImVec2 center = ImVec2(window_pos.x + window_size.x * 0.5f, + window_pos.y + window_size.y * 0.5f); + float max_distance = + sqrtf(window_size.x * window_size.x + window_size.y * window_size.y) * + 0.5f; + // Apply breathing effect to color if enabled Color themed_grid_color = grid_color; themed_grid_color.alpha = grid_settings_.opacity; - + if (grid_settings_.enable_breathing) { - float breathing_factor = 1.0f + grid_settings_.breathing_intensity * - sinf(animation_time_ * grid_settings_.breathing_speed); - themed_grid_color.red = std::min(1.0f, themed_grid_color.red * breathing_factor); - themed_grid_color.green = std::min(1.0f, themed_grid_color.green * breathing_factor); - themed_grid_color.blue = std::min(1.0f, themed_grid_color.blue * breathing_factor); + float breathing_factor = + 1.0f + grid_settings_.breathing_intensity * + sinf(animation_time_ * grid_settings_.breathing_speed); + themed_grid_color.red = + std::min(1.0f, themed_grid_color.red * breathing_factor); + themed_grid_color.green = + std::min(1.0f, themed_grid_color.green * breathing_factor); + themed_grid_color.blue = + std::min(1.0f, themed_grid_color.blue * breathing_factor); } - + if (grid_settings_.enable_dots) { // Render grid as dots - for (float x = window_pos.x - offset_x; x < window_pos.x + window_size.x + grid_size; x += grid_size) { - for (float y = window_pos.y - offset_y; y < window_pos.y + window_size.y + grid_size; y += grid_size) { + for (float x = window_pos.x - offset_x; + x < window_pos.x + window_size.x + grid_size; x += grid_size) { + for (float y = window_pos.y - offset_y; + y < window_pos.y + window_size.y + grid_size; y += grid_size) { ImVec2 dot_pos(x, y); - + // Calculate radial fade float fade_factor = 1.0f; if (grid_settings_.radial_fade) { - float distance = sqrtf((dot_pos.x - center.x) * (dot_pos.x - center.x) + - (dot_pos.y - center.y) * (dot_pos.y - center.y)); - fade_factor = 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); - fade_factor = fade_factor * fade_factor; // Square for smoother falloff + float distance = + sqrtf((dot_pos.x - center.x) * (dot_pos.x - center.x) + + (dot_pos.y - center.y) * (dot_pos.y - center.y)); + fade_factor = + 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); + fade_factor = + fade_factor * fade_factor; // Square for smoother falloff } - + if (fade_factor > 0.01f) { ImU32 dot_color = BlendColorWithFade(themed_grid_color, fade_factor); DrawGridDot(draw_list, dot_pos, dot_color, grid_settings_.dot_size); @@ -127,77 +147,90 @@ void BackgroundRenderer::RenderGridBackground(ImDrawList* draw_list, const ImVec } else { // Render grid as lines // Vertical lines - for (float x = window_pos.x - offset_x; x < window_pos.x + window_size.x + grid_size; x += grid_size) { + for (float x = window_pos.x - offset_x; + x < window_pos.x + window_size.x + grid_size; x += grid_size) { ImVec2 line_start(x, window_pos.y); ImVec2 line_end(x, window_pos.y + window_size.y); - + // Calculate average fade for this line float avg_fade = 0.0f; if (grid_settings_.radial_fade) { - for (float y = window_pos.y; y < window_pos.y + window_size.y; y += grid_size * 0.5f) { - float distance = sqrtf((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y)); - float fade = 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); + for (float y = window_pos.y; y < window_pos.y + window_size.y; + y += grid_size * 0.5f) { + float distance = sqrtf((x - center.x) * (x - center.x) + + (y - center.y) * (y - center.y)); + float fade = + 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); avg_fade += fade * fade; } avg_fade /= (window_size.y / (grid_size * 0.5f)); } else { avg_fade = 1.0f; } - + if (avg_fade > 0.01f) { ImU32 line_color = BlendColorWithFade(themed_grid_color, avg_fade); - DrawGridLine(draw_list, line_start, line_end, line_color, grid_settings_.line_thickness); + DrawGridLine(draw_list, line_start, line_end, line_color, + grid_settings_.line_thickness); } } - + // Horizontal lines - for (float y = window_pos.y - offset_y; y < window_pos.y + window_size.y + grid_size; y += grid_size) { + for (float y = window_pos.y - offset_y; + y < window_pos.y + window_size.y + grid_size; y += grid_size) { ImVec2 line_start(window_pos.x, y); ImVec2 line_end(window_pos.x + window_size.x, y); - + // Calculate average fade for this line float avg_fade = 0.0f; if (grid_settings_.radial_fade) { - for (float x = window_pos.x; x < window_pos.x + window_size.x; x += grid_size * 0.5f) { - float distance = sqrtf((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y)); - float fade = 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); + for (float x = window_pos.x; x < window_pos.x + window_size.x; + x += grid_size * 0.5f) { + float distance = sqrtf((x - center.x) * (x - center.x) + + (y - center.y) * (y - center.y)); + float fade = + 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); avg_fade += fade * fade; } avg_fade /= (window_size.x / (grid_size * 0.5f)); } else { avg_fade = 1.0f; } - + if (avg_fade > 0.01f) { ImU32 line_color = BlendColorWithFade(themed_grid_color, avg_fade); - DrawGridLine(draw_list, line_start, line_end, line_color, grid_settings_.line_thickness); + DrawGridLine(draw_list, line_start, line_end, line_color, + grid_settings_.line_thickness); } } } } -void BackgroundRenderer::RenderRadialGradient(ImDrawList* draw_list, const ImVec2& center, - float radius, const Color& inner_color, const Color& outer_color) { - if (!draw_list) return; - +void BackgroundRenderer::RenderRadialGradient(ImDrawList* draw_list, + const ImVec2& center, + float radius, + const Color& inner_color, + const Color& outer_color) { + if (!draw_list) + return; + const int segments = 32; const int rings = 8; - + for (int ring = 0; ring < rings; ++ring) { float ring_radius = radius * (ring + 1) / rings; float inner_ring_radius = radius * ring / rings; - + // Interpolate colors for this ring float t = static_cast(ring) / rings; - Color ring_color = { - inner_color.red * (1.0f - t) + outer_color.red * t, - inner_color.green * (1.0f - t) + outer_color.green * t, - inner_color.blue * (1.0f - t) + outer_color.blue * t, - inner_color.alpha * (1.0f - t) + outer_color.alpha * t - }; - - ImU32 color = ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(ring_color)); - + Color ring_color = {inner_color.red * (1.0f - t) + outer_color.red * t, + inner_color.green * (1.0f - t) + outer_color.green * t, + inner_color.blue * (1.0f - t) + outer_color.blue * t, + inner_color.alpha * (1.0f - t) + outer_color.alpha * t}; + + ImU32 color = + ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(ring_color)); + if (ring == 0) { // Center circle draw_list->AddCircleFilled(center, ring_radius, color, segments); @@ -206,16 +239,16 @@ void BackgroundRenderer::RenderRadialGradient(ImDrawList* draw_list, const ImVec for (int i = 0; i < segments; ++i) { float angle1 = (2.0f * M_PI * i) / segments; float angle2 = (2.0f * M_PI * (i + 1)) / segments; - + ImVec2 p1_inner = ImVec2(center.x + cosf(angle1) * inner_ring_radius, - center.y + sinf(angle1) * inner_ring_radius); + center.y + sinf(angle1) * inner_ring_radius); ImVec2 p2_inner = ImVec2(center.x + cosf(angle2) * inner_ring_radius, - center.y + sinf(angle2) * inner_ring_radius); + center.y + sinf(angle2) * inner_ring_radius); ImVec2 p1_outer = ImVec2(center.x + cosf(angle1) * ring_radius, - center.y + sinf(angle1) * ring_radius); + center.y + sinf(angle1) * ring_radius); ImVec2 p2_outer = ImVec2(center.x + cosf(angle2) * ring_radius, - center.y + sinf(angle2) * ring_radius); - + center.y + sinf(angle2) * ring_radius); + draw_list->AddQuadFilled(p1_inner, p2_inner, p2_outer, p1_outer, color); } } @@ -228,108 +261,119 @@ void BackgroundRenderer::UpdateAnimation(float delta_time) { } } -void BackgroundRenderer::UpdateForTheme(const Color& primary_color, const Color& background_color) { +void BackgroundRenderer::UpdateForTheme(const Color& primary_color, + const Color& background_color) { // Create a grid color that's a subtle blend of the theme's primary and background cached_grid_color_ = { - (primary_color.red * 0.3f + background_color.red * 0.7f), - (primary_color.green * 0.3f + background_color.green * 0.7f), - (primary_color.blue * 0.3f + background_color.blue * 0.7f), - grid_settings_.opacity - }; + (primary_color.red * 0.3f + background_color.red * 0.7f), + (primary_color.green * 0.3f + background_color.green * 0.7f), + (primary_color.blue * 0.3f + background_color.blue * 0.7f), + grid_settings_.opacity}; } void BackgroundRenderer::DrawSettingsUI() { if (ImGui::CollapsingHeader("Background Grid Settings")) { ImGui::Indent(); - - ImGui::SliderFloat("Grid Size", &grid_settings_.grid_size, 8.0f, 128.0f, "%.0f px"); - ImGui::SliderFloat("Line Thickness", &grid_settings_.line_thickness, 0.5f, 3.0f, "%.1f px"); + + ImGui::SliderFloat("Grid Size", &grid_settings_.grid_size, 8.0f, 128.0f, + "%.0f px"); + ImGui::SliderFloat("Line Thickness", &grid_settings_.line_thickness, 0.5f, + 3.0f, "%.1f px"); ImGui::SliderFloat("Opacity", &grid_settings_.opacity, 0.01f, 0.3f, "%.3f"); - ImGui::SliderFloat("Fade Distance", &grid_settings_.fade_distance, 50.0f, 500.0f, "%.0f px"); - + ImGui::SliderFloat("Fade Distance", &grid_settings_.fade_distance, 50.0f, + 500.0f, "%.0f px"); + ImGui::Separator(); ImGui::Text("Visual Effects:"); ImGui::Checkbox("Enable Animation", &grid_settings_.enable_animation); - ImGui::SameLine(); + ImGui::SameLine(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Makes the grid move slowly across the screen"); } - + ImGui::Checkbox("Color Breathing", &grid_settings_.enable_breathing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Grid color pulses with a breathing effect"); } - + ImGui::Checkbox("Radial Fade", &grid_settings_.radial_fade); ImGui::Checkbox("Use Dots Instead of Lines", &grid_settings_.enable_dots); - + // Animation settings (only show if animation is enabled) if (grid_settings_.enable_animation) { ImGui::Indent(); - ImGui::SliderFloat("Animation Speed", &grid_settings_.animation_speed, 0.1f, 3.0f, "%.1fx"); + ImGui::SliderFloat("Animation Speed", &grid_settings_.animation_speed, + 0.1f, 3.0f, "%.1fx"); ImGui::Unindent(); } - + // Breathing settings (only show if breathing is enabled) if (grid_settings_.enable_breathing) { ImGui::Indent(); - ImGui::SliderFloat("Breathing Speed", &grid_settings_.breathing_speed, 0.5f, 3.0f, "%.1fx"); - ImGui::SliderFloat("Breathing Intensity", &grid_settings_.breathing_intensity, 0.1f, 0.8f, "%.1f"); + ImGui::SliderFloat("Breathing Speed", &grid_settings_.breathing_speed, + 0.5f, 3.0f, "%.1fx"); + ImGui::SliderFloat("Breathing Intensity", + &grid_settings_.breathing_intensity, 0.1f, 0.8f, + "%.1f"); ImGui::Unindent(); } - + if (grid_settings_.enable_dots) { - ImGui::SliderFloat("Dot Size", &grid_settings_.dot_size, 1.0f, 8.0f, "%.1f px"); + ImGui::SliderFloat("Dot Size", &grid_settings_.dot_size, 1.0f, 8.0f, + "%.1f px"); } - + // Preview ImGui::Spacing(); ImGui::Text("Preview:"); ImVec2 preview_size(200, 100); ImVec2 preview_pos = ImGui::GetCursorScreenPos(); - + ImDrawList* preview_draw_list = ImGui::GetWindowDrawList(); auto& theme_manager = ThemeManager::Get(); auto theme_color = theme_manager.GetCurrentTheme().primary; - + // Draw preview background - preview_draw_list->AddRectFilled(preview_pos, - ImVec2(preview_pos.x + preview_size.x, preview_pos.y + preview_size.y), - IM_COL32(30, 30, 30, 255)); - + preview_draw_list->AddRectFilled( + preview_pos, + ImVec2(preview_pos.x + preview_size.x, preview_pos.y + preview_size.y), + IM_COL32(30, 30, 30, 255)); + // Draw preview grid - RenderGridBackground(preview_draw_list, preview_pos, preview_size, theme_color); - + RenderGridBackground(preview_draw_list, preview_pos, preview_size, + theme_color); + // Advance cursor ImGui::Dummy(preview_size); - + ImGui::Unindent(); } } -float BackgroundRenderer::CalculateRadialFade(const ImVec2& pos, const ImVec2& center, float max_distance) const { - float distance = sqrtf((pos.x - center.x) * (pos.x - center.x) + - (pos.y - center.y) * (pos.y - center.y)); +float BackgroundRenderer::CalculateRadialFade(const ImVec2& pos, + const ImVec2& center, + float max_distance) const { + float distance = sqrtf((pos.x - center.x) * (pos.x - center.x) + + (pos.y - center.y) * (pos.y - center.y)); float fade = 1.0f - std::min(distance / max_distance, 1.0f); - return fade * fade; // Square for smoother falloff + return fade * fade; // Square for smoother falloff } -ImU32 BackgroundRenderer::BlendColorWithFade(const Color& base_color, float fade_factor) const { - Color faded_color = { - base_color.red, - base_color.green, - base_color.blue, - base_color.alpha * fade_factor - }; +ImU32 BackgroundRenderer::BlendColorWithFade(const Color& base_color, + float fade_factor) const { + Color faded_color = {base_color.red, base_color.green, base_color.blue, + base_color.alpha * fade_factor}; return ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(faded_color)); } -void BackgroundRenderer::DrawGridLine(ImDrawList* draw_list, const ImVec2& start, const ImVec2& end, - ImU32 color, float thickness) const { +void BackgroundRenderer::DrawGridLine(ImDrawList* draw_list, + const ImVec2& start, const ImVec2& end, + ImU32 color, float thickness) const { draw_list->AddLine(start, end, color, thickness); } -void BackgroundRenderer::DrawGridDot(ImDrawList* draw_list, const ImVec2& pos, ImU32 color, float size) const { +void BackgroundRenderer::DrawGridDot(ImDrawList* draw_list, const ImVec2& pos, + ImU32 color, float size) const { draw_list->AddCircleFilled(pos, size, color); } @@ -340,34 +384,36 @@ bool DockSpaceRenderer::effects_enabled_ = true; ImVec2 DockSpaceRenderer::last_dockspace_pos_{}; ImVec2 DockSpaceRenderer::last_dockspace_size_{}; -void DockSpaceRenderer::BeginEnhancedDockSpace(ImGuiID dockspace_id, const ImVec2& size, - ImGuiDockNodeFlags flags) { +void DockSpaceRenderer::BeginEnhancedDockSpace(ImGuiID dockspace_id, + const ImVec2& size, + ImGuiDockNodeFlags flags) { // Store window info last_dockspace_pos_ = ImGui::GetWindowPos(); last_dockspace_size_ = ImGui::GetWindowSize(); - + // Create the actual dockspace first ImGui::DockSpace(dockspace_id, size, flags); - + // NOW draw the background effects on the foreground draw list so they're visible if (background_enabled_) { ImDrawList* fg_draw_list = ImGui::GetForegroundDrawList(); auto& theme_manager = ThemeManager::Get(); auto current_theme = theme_manager.GetCurrentTheme(); - + if (grid_enabled_) { auto& bg_renderer = BackgroundRenderer::Get(); // Use the main viewport for full-screen grid const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImVec2 grid_pos = viewport->WorkPos; ImVec2 grid_size = viewport->WorkSize; - - // Use subtle grid color that doesn't distract + + // Use subtle grid color that doesn't distract Color subtle_grid_color = current_theme.primary; // Use the grid settings opacity for consistency subtle_grid_color.alpha = bg_renderer.GetGridSettings().opacity; - - bg_renderer.RenderGridBackground(fg_draw_list, grid_pos, grid_size, subtle_grid_color); + + bg_renderer.RenderGridBackground(fg_draw_list, grid_pos, grid_size, + subtle_grid_color); } } } @@ -377,5 +423,5 @@ void DockSpaceRenderer::EndEnhancedDockSpace() { // For now, this is just for API consistency } -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze diff --git a/src/app/gui/core/background_renderer.h b/src/app/gui/core/background_renderer.h index 5b2bda86..d419b8e2 100644 --- a/src/app/gui/core/background_renderer.h +++ b/src/app/gui/core/background_renderer.h @@ -1,9 +1,9 @@ #ifndef YAZE_APP_EDITOR_UI_BACKGROUND_RENDERER_H #define YAZE_APP_EDITOR_UI_BACKGROUND_RENDERER_H -#include "imgui/imgui.h" #include #include +#include "imgui/imgui.h" #include "app/gui/core/color.h" #include "app/rom.h" @@ -16,59 +16,70 @@ namespace gui { * @brief Renders themed background effects for docking windows */ class BackgroundRenderer { -public: + public: struct GridSettings { - float grid_size = 32.0f; // Size of grid cells - float line_thickness = 1.0f; // Thickness of grid lines - float opacity = 0.12f; // Subtle but visible opacity - float fade_distance = 400.0f; // Distance over which grid fades - bool enable_animation = false; // Animation toggle (default off) - bool enable_breathing = false; // Color breathing effect toggle (default off) - bool radial_fade = true; // Re-enable subtle radial fade - bool enable_dots = false; // Use dots instead of lines - float dot_size = 2.0f; // Size of grid dots - float animation_speed = 1.0f; // Animation speed multiplier - float breathing_speed = 1.5f; // Breathing effect speed - float breathing_intensity = 0.3f; // How much color changes during breathing + float grid_size = 32.0f; // Size of grid cells + float line_thickness = 1.0f; // Thickness of grid lines + float opacity = 0.12f; // Subtle but visible opacity + float fade_distance = 400.0f; // Distance over which grid fades + bool enable_animation = false; // Animation toggle (default off) + bool enable_breathing = + false; // Color breathing effect toggle (default off) + bool radial_fade = true; // Re-enable subtle radial fade + bool enable_dots = false; // Use dots instead of lines + float dot_size = 2.0f; // Size of grid dots + float animation_speed = 1.0f; // Animation speed multiplier + float breathing_speed = 1.5f; // Breathing effect speed + float breathing_intensity = + 0.3f; // How much color changes during breathing }; - + static BackgroundRenderer& Get(); - + // Main rendering functions - void RenderDockingBackground(ImDrawList* draw_list, const ImVec2& window_pos, - const ImVec2& window_size, const Color& theme_color); - void RenderGridBackground(ImDrawList* draw_list, const ImVec2& window_pos, - const ImVec2& window_size, const Color& grid_color); - void RenderRadialGradient(ImDrawList* draw_list, const ImVec2& center, - float radius, const Color& inner_color, const Color& outer_color); - + void RenderDockingBackground(ImDrawList* draw_list, const ImVec2& window_pos, + const ImVec2& window_size, + const Color& theme_color); + void RenderGridBackground(ImDrawList* draw_list, const ImVec2& window_pos, + const ImVec2& window_size, const Color& grid_color); + void RenderRadialGradient(ImDrawList* draw_list, const ImVec2& center, + float radius, const Color& inner_color, + const Color& outer_color); + // Configuration - void SetGridSettings(const GridSettings& settings) { grid_settings_ = settings; } + void SetGridSettings(const GridSettings& settings) { + grid_settings_ = settings; + } const GridSettings& GetGridSettings() const { return grid_settings_; } - + // Animation void UpdateAnimation(float delta_time); - void SetAnimationEnabled(bool enabled) { grid_settings_.enable_animation = enabled; } - + void SetAnimationEnabled(bool enabled) { + grid_settings_.enable_animation = enabled; + } + // Theme integration - void UpdateForTheme(const Color& primary_color, const Color& background_color); - + void UpdateForTheme(const Color& primary_color, + const Color& background_color); + // UI for settings void DrawSettingsUI(); - -private: + + private: BackgroundRenderer() = default; - + GridSettings grid_settings_; float animation_time_ = 0.0f; Color cached_grid_color_{0.5f, 0.5f, 0.5f, 0.1f}; - + // Helper functions - float CalculateRadialFade(const ImVec2& pos, const ImVec2& center, float max_distance) const; + float CalculateRadialFade(const ImVec2& pos, const ImVec2& center, + float max_distance) const; ImU32 BlendColorWithFade(const Color& base_color, float fade_factor) const; - void DrawGridLine(ImDrawList* draw_list, const ImVec2& start, const ImVec2& end, - ImU32 color, float thickness) const; - void DrawGridDot(ImDrawList* draw_list, const ImVec2& pos, ImU32 color, float size) const; + void DrawGridLine(ImDrawList* draw_list, const ImVec2& start, + const ImVec2& end, ImU32 color, float thickness) const; + void DrawGridDot(ImDrawList* draw_list, const ImVec2& pos, ImU32 color, + float size) const; }; /** @@ -76,17 +87,20 @@ private: * @brief Enhanced docking space with themed background effects */ class DockSpaceRenderer { -public: - static void BeginEnhancedDockSpace(ImGuiID dockspace_id, const ImVec2& size = ImVec2(0, 0), - ImGuiDockNodeFlags flags = 0); + public: + static void BeginEnhancedDockSpace(ImGuiID dockspace_id, + const ImVec2& size = ImVec2(0, 0), + ImGuiDockNodeFlags flags = 0); static void EndEnhancedDockSpace(); - + // Configuration - static void SetBackgroundEnabled(bool enabled) { background_enabled_ = enabled; } + static void SetBackgroundEnabled(bool enabled) { + background_enabled_ = enabled; + } static void SetGridEnabled(bool enabled) { grid_enabled_ = enabled; } static void SetEffectsEnabled(bool enabled) { effects_enabled_ = enabled; } - -private: + + private: static bool background_enabled_; static bool grid_enabled_; static bool effects_enabled_; @@ -94,7 +108,7 @@ private: static ImVec2 last_dockspace_size_; }; -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze -#endif // YAZE_APP_EDITOR_UI_BACKGROUND_RENDERER_H +#endif // YAZE_APP_EDITOR_UI_BACKGROUND_RENDERER_H diff --git a/src/app/gui/core/color.cc b/src/app/gui/core/color.cc index e80b2f23..a3c30e36 100644 --- a/src/app/gui/core/color.cc +++ b/src/app/gui/core/color.cc @@ -19,8 +19,8 @@ namespace gui { ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor& color) { // SnesColor stores RGB as 0-255 in ImVec4, convert to standard 0-1 range ImVec4 rgb_255 = color.rgb(); - return ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f, - rgb_255.z / 255.0f, 1.0f); + return ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f, rgb_255.z / 255.0f, + 1.0f); } /** @@ -74,67 +74,67 @@ IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor* color, // New Standardized Palette Widgets // ============================================================================ -IMGUI_API bool InlinePaletteSelector(gfx::SnesPalette &palette, - int num_colors, +IMGUI_API bool InlinePaletteSelector(gfx::SnesPalette& palette, int num_colors, int* selected_index) { bool selection_made = false; int colors_to_show = std::min(num_colors, static_cast(palette.size())); - + ImGui::BeginGroup(); for (int n = 0; n < colors_to_show; n++) { ImGui::PushID(n); if (n > 0 && (n % 8) != 0) { ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); } - + bool is_selected = selected_index && (*selected_index == n); if (is_selected) { ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); } - - if (SnesColorButton("##palettesel", palette[n], - ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, - ImVec2(20, 20))) { + + if (SnesColorButton( + "##palettesel", palette[n], + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, + ImVec2(20, 20))) { if (selected_index) { *selected_index = n; selection_made = true; } } - + if (is_selected) { ImGui::PopStyleVar(); ImGui::PopStyleColor(); } - + ImGui::PopID(); } ImGui::EndGroup(); - + return selection_made; } -IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, - const std::string &title, +IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette& palette, + const std::string& title, ImGuiColorEditFlags flags) { if (!title.empty()) { ImGui::Text("%s", title.c_str()); } - + static int selected_color = 0; static ImVec4 current_color = ImVec4(0, 0, 0, 1.0f); - + // Color picker ImGui::Separator(); if (ImGui::ColorPicker4("##colorpicker", (float*)¤t_color, ImGuiColorEditFlags_NoSidePreview | - ImGuiColorEditFlags_NoSmallPreview)) { + ImGuiColorEditFlags_NoSmallPreview)) { gfx::SnesColor snes_color(current_color); palette.UpdateColor(selected_color, snes_color); } - + ImGui::Separator(); - + // Palette grid ImGui::BeginGroup(); for (int n = 0; n < palette.size(); n++) { @@ -142,16 +142,16 @@ IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, if ((n % 8) != 0) { ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); } - + if (flags == 0) { flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker; } - + if (SnesColorButton("##palettedit", palette[n], flags, ImVec2(20, 20))) { selected_color = n; current_color = ConvertSnesColorToImVec4(palette[n]); } - + // Context menu if (ImGui::BeginPopupContextItem()) { if (ImGui::MenuItem("Copy as SNES")) { @@ -160,68 +160,70 @@ IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, } if (ImGui::MenuItem("Copy as RGB")) { auto rgb = palette[n].rgb(); - std::string clipboard = absl::StrFormat("(%d,%d,%d)", - (int)rgb.x, (int)rgb.y, (int)rgb.z); + std::string clipboard = + absl::StrFormat("(%d,%d,%d)", (int)rgb.x, (int)rgb.y, (int)rgb.z); ImGui::SetClipboardText(clipboard.c_str()); } if (ImGui::MenuItem("Copy as Hex")) { auto rgb = palette[n].rgb(); - std::string clipboard = absl::StrFormat("#%02X%02X%02X", - (int)rgb.x, (int)rgb.y, (int)rgb.z); + std::string clipboard = absl::StrFormat("#%02X%02X%02X", (int)rgb.x, + (int)rgb.y, (int)rgb.z); ImGui::SetClipboardText(clipboard.c_str()); } ImGui::EndPopup(); } - + ImGui::PopID(); } ImGui::EndGroup(); - + return absl::OkStatus(); } IMGUI_API bool PopupPaletteEditor(const char* popup_id, - gfx::SnesPalette &palette, + gfx::SnesPalette& palette, ImGuiColorEditFlags flags) { bool modified = false; - + if (ImGui::BeginPopup(popup_id)) { static int selected_color = 0; static ImVec4 current_color = ImVec4(0, 0, 0, 1.0f); - + // Compact color picker if (ImGui::ColorPicker4("##popuppicker", (float*)¤t_color, ImGuiColorEditFlags_NoSidePreview | - ImGuiColorEditFlags_NoSmallPreview)) { + ImGuiColorEditFlags_NoSmallPreview)) { gfx::SnesColor snes_color(current_color); palette.UpdateColor(selected_color, snes_color); modified = true; } - + ImGui::Separator(); - + // Palette grid ImGui::BeginGroup(); - for (int n = 0; n < palette.size() && n < 64; n++) { // Limit to 64 for popup + for (int n = 0; n < palette.size() && n < 64; + n++) { // Limit to 64 for popup ImGui::PushID(n); if ((n % 8) != 0) { ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); } - - if (SnesColorButton("##popuppal", palette[n], - ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, - ImVec2(20, 20))) { + + if (SnesColorButton( + "##popuppal", palette[n], + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, + ImVec2(20, 20))) { selected_color = n; current_color = ConvertSnesColorToImVec4(palette[n]); } - + ImGui::PopID(); } ImGui::EndGroup(); - + ImGui::EndPopup(); } - + return modified; } @@ -271,7 +273,8 @@ IMGUI_API bool DisplayPalette(gfx::SnesPalette& palette, bool loaded) { ImGui::Text("Palette"); for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) { ImGui::PushID(n); - if ((n % 4) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + if ((n % 4) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | @@ -413,8 +416,8 @@ absl::Status DisplayEditablePalette(gfx::SnesPalette& palette, if (ImGui::MenuItem("Copy as Hex")) { auto rgb = palette[n].rgb(); // rgb is already in 0-255 range, no need to multiply - std::string clipboard = - absl::StrFormat("#%02X%02X%02X", (int)rgb.x, (int)rgb.y, (int)rgb.z); + std::string clipboard = absl::StrFormat("#%02X%02X%02X", (int)rgb.x, + (int)rgb.y, (int)rgb.z); ImGui::SetClipboardText(clipboard.c_str()); } @@ -447,46 +450,44 @@ IMGUI_API bool PaletteColorButton(const char* id, const gfx::SnesColor& color, const ImVec2& size, ImGuiColorEditFlags flags) { ImVec4 display_color = ConvertSnesColorToImVec4(color); - + // Add visual indicators for selection and modification ImGui::PushID(id); - + // Selection border if (is_selected) { ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.0f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); } - + bool clicked = ImGui::ColorButton(id, display_color, flags, size); - + if (is_selected) { ImGui::PopStyleVar(); ImGui::PopStyleColor(); } - + // Modification indicator (small dot in corner) if (is_modified) { ImVec2 pos = ImGui::GetItemRectMin(); ImVec2 dot_pos = ImVec2(pos.x + size.x - 6, pos.y + 2); - ImGui::GetWindowDrawList()->AddCircleFilled(dot_pos, 3.0f, + ImGui::GetWindowDrawList()->AddCircleFilled(dot_pos, 3.0f, IM_COL32(255, 128, 0, 255)); } - + // Tooltip with color info if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("SNES: $%04X", color.snes()); auto rgb = color.rgb(); - ImGui::Text("RGB: (%d, %d, %d)", - static_cast(rgb.x), - static_cast(rgb.y), - static_cast(rgb.z)); + ImGui::Text("RGB: (%d, %d, %d)", static_cast(rgb.x), + static_cast(rgb.y), static_cast(rgb.z)); if (is_modified) { ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Modified"); } ImGui::EndTooltip(); } - + ImGui::PopID(); return clicked; } diff --git a/src/app/gui/core/color.h b/src/app/gui/core/color.h index 62cbfc00..d7c7f10e 100644 --- a/src/app/gui/core/color.h +++ b/src/app/gui/core/color.h @@ -1,8 +1,8 @@ #ifndef YAZE_GUI_COLOR_H #define YAZE_GUI_COLOR_H -#include "absl/strings/str_format.h" #include +#include "absl/strings/str_format.h" #include "absl/status/status.h" #include "app/gfx/types/snes_palette.h" @@ -18,13 +18,12 @@ struct Color { float alpha; }; -inline ImVec4 ConvertColorToImVec4(const Color &color) { +inline ImVec4 ConvertColorToImVec4(const Color& color) { return ImVec4(color.red, color.green, color.blue, color.alpha); } -inline std::string ColorToHexString(const Color &color) { - return absl::StrFormat("%02X%02X%02X%02X", - static_cast(color.red * 255), +inline std::string ColorToHexString(const Color& color) { + return absl::StrFormat("%02X%02X%02X%02X", static_cast(color.red * 255), static_cast(color.green * 255), static_cast(color.blue * 255), static_cast(color.alpha * 255)); @@ -32,17 +31,17 @@ inline std::string ColorToHexString(const Color &color) { // A utility function to convert an SnesColor object to an ImVec4 with // normalized color values -ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color); +ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor& color); // A utility function to convert an ImVec4 to an SnesColor object -gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color); +gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4& color); // The wrapper function for ImGui::ColorButton that takes a SnesColor reference -IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor &color, +IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor& color, ImGuiColorEditFlags flags = 0, - const ImVec2 &size_arg = ImVec2(0, 0)); + const ImVec2& size_arg = ImVec2(0, 0)); -IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, +IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor* color, ImGuiColorEditFlags flags = 0); // ============================================================================ @@ -56,7 +55,7 @@ IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, * @param selected_index Pointer to store selected color index (optional) * @return True if a color was selected */ -IMGUI_API bool InlinePaletteSelector(gfx::SnesPalette &palette, +IMGUI_API bool InlinePaletteSelector(gfx::SnesPalette& palette, int num_colors = 8, int* selected_index = nullptr); @@ -67,8 +66,8 @@ IMGUI_API bool InlinePaletteSelector(gfx::SnesPalette &palette, * @param flags ImGui color edit flags * @return Status of the operation */ -IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, - const std::string &title = "", +IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette& palette, + const std::string& title = "", ImGuiColorEditFlags flags = 0); /** @@ -79,20 +78,20 @@ IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, * @return True if palette was modified */ IMGUI_API bool PopupPaletteEditor(const char* popup_id, - gfx::SnesPalette &palette, + gfx::SnesPalette& palette, ImGuiColorEditFlags flags = 0); // Legacy functions (kept for compatibility, will be deprecated) -IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded); +IMGUI_API bool DisplayPalette(gfx::SnesPalette& palette, bool loaded); -IMGUI_API absl::Status DisplayEditablePalette(gfx::SnesPalette &palette, - const std::string &title = "", +IMGUI_API absl::Status DisplayEditablePalette(gfx::SnesPalette& palette, + const std::string& title = "", bool show_color_picker = false, int colors_per_row = 8, ImGuiColorEditFlags flags = 0); -void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics, - gfx::SnesPalette &palette); +void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics, + gfx::SnesPalette& palette); // Palette color button with selection and modification indicators IMGUI_API bool PaletteColorButton(const char* id, const gfx::SnesColor& color, diff --git a/src/app/gui/core/input.cc b/src/app/gui/core/input.cc index 8ff6a7a1..901a727d 100644 --- a/src/app/gui/core/input.cc +++ b/src/app/gui/core/input.cc @@ -434,7 +434,7 @@ bool OpenUrl(const std::string& url) { // if iOS #ifdef __APPLE__ #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR -// no system call on iOS + // no system call on iOS return false; #else return system(("open " + url).c_str()) == 0; diff --git a/src/app/gui/core/input.h b/src/app/gui/core/input.h index d4818ae1..f27fe382 100644 --- a/src/app/gui/core/input.h +++ b/src/app/gui/core/input.h @@ -21,36 +21,36 @@ namespace gui { constexpr ImVec2 kDefaultModalSize = ImVec2(200, 0); constexpr ImVec2 kZeroPos = ImVec2(0, 0); -IMGUI_API bool InputHex(const char *label, uint64_t *data); -IMGUI_API bool InputHex(const char *label, int *data, int num_digits = 4, +IMGUI_API bool InputHex(const char* label, uint64_t* data); +IMGUI_API bool InputHex(const char* label, int* data, int num_digits = 4, float input_width = 50.f); -IMGUI_API bool InputHexShort(const char *label, uint32_t *data); -IMGUI_API bool InputHexWord(const char *label, uint16_t *data, +IMGUI_API bool InputHexShort(const char* label, uint32_t* data); +IMGUI_API bool InputHexWord(const char* label, uint16_t* data, float input_width = 50.f, bool no_step = false); -IMGUI_API bool InputHexWord(const char *label, int16_t *data, +IMGUI_API bool InputHexWord(const char* label, int16_t* data, float input_width = 50.f, bool no_step = false); -IMGUI_API bool InputHexByte(const char *label, uint8_t *data, +IMGUI_API bool InputHexByte(const char* label, uint8_t* data, float input_width = 50.f, bool no_step = false); -IMGUI_API bool InputHexByte(const char *label, uint8_t *data, uint8_t max_value, +IMGUI_API bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value, float input_width = 50.f, bool no_step = false); // Custom hex input functions that properly respect width -IMGUI_API bool InputHexByteCustom(const char *label, uint8_t *data, +IMGUI_API bool InputHexByteCustom(const char* label, uint8_t* data, float input_width = 50.f); -IMGUI_API bool InputHexWordCustom(const char *label, uint16_t *data, +IMGUI_API bool InputHexWordCustom(const char* label, uint16_t* data, float input_width = 70.f); -IMGUI_API void Paragraph(const std::string &text); +IMGUI_API void Paragraph(const std::string& text); -IMGUI_API bool ClickableText(const std::string &text); +IMGUI_API bool ClickableText(const std::string& text); -IMGUI_API bool ListBox(const char *label, int *current_item, - const std::vector &items, +IMGUI_API bool ListBox(const char* label, int* current_item, + const std::vector& items, int height_in_items = -1); -bool InputTileInfo(const char *label, gfx::TileInfo *tile_info); +bool InputTileInfo(const char* label, gfx::TileInfo* tile_info); using ItemLabelFlags = enum ItemLabelFlag { Left = 1u << 0u, @@ -60,14 +60,14 @@ using ItemLabelFlags = enum ItemLabelFlag { IMGUI_API void ItemLabel(absl::string_view title, ItemLabelFlags flags); -IMGUI_API ImGuiID GetID(const std::string &id); +IMGUI_API ImGuiID GetID(const std::string& id); ImGuiKey MapKeyToImGuiKey(char key); using GuiElement = std::variant, std::string>; struct Table { - const char *id; + const char* id; int num_columns; ImGuiTableFlags flags; ImVec2 size; @@ -75,11 +75,11 @@ struct Table { std::vector column_contents; }; -void AddTableColumn(Table &table, const std::string &label, GuiElement element); +void AddTableColumn(Table& table, const std::string& label, GuiElement element); -IMGUI_API bool OpenUrl(const std::string &url); +IMGUI_API bool OpenUrl(const std::string& url); -void MemoryEditorPopup(const std::string &label, std::span memory); +void MemoryEditorPopup(const std::string& label, std::span memory); } // namespace gui } // namespace yaze diff --git a/src/app/gui/core/layout_helpers.cc b/src/app/gui/core/layout_helpers.cc index e5b89e64..cfd50cfa 100644 --- a/src/app/gui/core/layout_helpers.cc +++ b/src/app/gui/core/layout_helpers.cc @@ -3,11 +3,11 @@ #include #include "absl/strings/str_format.h" +#include "app/gui/core/color.h" #include "app/gui/core/icons.h" +#include "app/gui/core/theme_manager.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" -#include "app/gui/core/theme_manager.h" -#include "app/gui/core/color.h" namespace yaze { namespace gui { @@ -15,42 +15,50 @@ namespace gui { // Core sizing functions float LayoutHelpers::GetStandardWidgetHeight() { const auto& theme = GetTheme(); - return GetBaseFontSize() * theme.widget_height_multiplier * theme.compact_factor; + return GetBaseFontSize() * theme.widget_height_multiplier * + theme.compact_factor; } float LayoutHelpers::GetStandardSpacing() { const auto& theme = GetTheme(); - return GetBaseFontSize() * 0.5f * theme.spacing_multiplier * theme.compact_factor; + return GetBaseFontSize() * 0.5f * theme.spacing_multiplier * + theme.compact_factor; } float LayoutHelpers::GetToolbarHeight() { const auto& theme = GetTheme(); - return GetBaseFontSize() * theme.toolbar_height_multiplier * theme.compact_factor; + return GetBaseFontSize() * theme.toolbar_height_multiplier * + theme.compact_factor; } float LayoutHelpers::GetPanelPadding() { const auto& theme = GetTheme(); - return GetBaseFontSize() * 0.5f * theme.panel_padding_multiplier * theme.compact_factor; + return GetBaseFontSize() * 0.5f * theme.panel_padding_multiplier * + theme.compact_factor; } float LayoutHelpers::GetStandardInputWidth() { const auto& theme = GetTheme(); - return GetBaseFontSize() * 8.0f * theme.input_width_multiplier * theme.compact_factor; + return GetBaseFontSize() * 8.0f * theme.input_width_multiplier * + theme.compact_factor; } float LayoutHelpers::GetButtonPadding() { const auto& theme = GetTheme(); - return GetBaseFontSize() * 0.3f * theme.button_padding_multiplier * theme.compact_factor; + return GetBaseFontSize() * 0.3f * theme.button_padding_multiplier * + theme.compact_factor; } float LayoutHelpers::GetTableRowHeight() { const auto& theme = GetTheme(); - return GetBaseFontSize() * theme.table_row_height_multiplier * theme.compact_factor; + return GetBaseFontSize() * theme.table_row_height_multiplier * + theme.compact_factor; } float LayoutHelpers::GetCanvasToolbarHeight() { const auto& theme = GetTheme(); - return GetBaseFontSize() * theme.canvas_toolbar_multiplier * theme.compact_factor; + return GetBaseFontSize() * theme.canvas_toolbar_multiplier * + theme.compact_factor; } // Layout utilities @@ -74,22 +82,28 @@ void LayoutHelpers::EndPaddedPanel() { } bool LayoutHelpers::BeginTableWithTheming(const char* str_id, int columns, - ImGuiTableFlags flags, - const ImVec2& outer_size, - float inner_width) { + ImGuiTableFlags flags, + const ImVec2& outer_size, + float inner_width) { const auto& theme = GetTheme(); // Apply theme colors to table - ImGui::PushStyleColor(ImGuiCol_TableHeaderBg, ConvertColorToImVec4(theme.table_header_bg)); - ImGui::PushStyleColor(ImGuiCol_TableBorderStrong, ConvertColorToImVec4(theme.table_border_strong)); - ImGui::PushStyleColor(ImGuiCol_TableBorderLight, ConvertColorToImVec4(theme.table_border_light)); - ImGui::PushStyleColor(ImGuiCol_TableRowBg, ConvertColorToImVec4(theme.table_row_bg)); - ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ConvertColorToImVec4(theme.table_row_bg_alt)); + ImGui::PushStyleColor(ImGuiCol_TableHeaderBg, + ConvertColorToImVec4(theme.table_header_bg)); + ImGui::PushStyleColor(ImGuiCol_TableBorderStrong, + ConvertColorToImVec4(theme.table_border_strong)); + ImGui::PushStyleColor(ImGuiCol_TableBorderLight, + ConvertColorToImVec4(theme.table_border_light)); + ImGui::PushStyleColor(ImGuiCol_TableRowBg, + ConvertColorToImVec4(theme.table_row_bg)); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, + ConvertColorToImVec4(theme.table_row_bg_alt)); // Set row height if not overridden by caller if (!(flags & ImGuiTableFlags_NoHostExtendY)) { - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, - ImVec2(ImGui::GetStyle().CellPadding.x, GetTableRowHeight() * 0.25f)); + ImGui::PushStyleVar( + ImGuiStyleVar_CellPadding, + ImVec2(ImGui::GetStyle().CellPadding.x, GetTableRowHeight() * 0.25f)); } return ImGui::BeginTable(str_id, columns, flags, outer_size, inner_width); @@ -107,7 +121,8 @@ void LayoutHelpers::BeginCanvasPanel(const char* label, ImVec2* canvas_size) { const auto& theme = GetTheme(); // Apply theme to canvas container - ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.editor_background)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, + ConvertColorToImVec4(theme.editor_background)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); if (canvas_size) { @@ -125,13 +140,15 @@ void LayoutHelpers::EndCanvasPanel() { // Input field helpers bool LayoutHelpers::AutoSizedInputField(const char* label, char* buf, - size_t buf_size, ImGuiInputTextFlags flags) { + size_t buf_size, + ImGuiInputTextFlags flags) { ImGui::SetNextItemWidth(GetStandardInputWidth()); return ImGui::InputText(label, buf, buf_size, flags); } bool LayoutHelpers::AutoSizedInputInt(const char* label, int* v, int step, - int step_fast, ImGuiInputTextFlags flags) { + int step_fast, + ImGuiInputTextFlags flags) { ImGui::SetNextItemWidth(GetStandardInputWidth()); return ImGui::InputInt(label, v, step, step_fast, flags); } @@ -146,7 +163,7 @@ bool LayoutHelpers::AutoSizedInputFloat(const char* label, float* v, float step, // Input preset functions for common patterns bool LayoutHelpers::InputHexRow(const char* label, uint8_t* data) { const auto& theme = GetTheme(); - float input_width = GetStandardInputWidth() * 0.5f; // Hex inputs are smaller + float input_width = GetStandardInputWidth() * 0.5f; // Hex inputs are smaller ImGui::AlignTextToFramePadding(); ImGui::Text("%s", label); @@ -174,7 +191,8 @@ bool LayoutHelpers::InputHexRow(const char* label, uint8_t* data) { bool LayoutHelpers::InputHexRow(const char* label, uint16_t* data) { const auto& theme = GetTheme(); - float input_width = GetStandardInputWidth() * 0.6f; // Hex word slightly wider + float input_width = + GetStandardInputWidth() * 0.6f; // Hex word slightly wider ImGui::AlignTextToFramePadding(); ImGui::Text("%s", label); @@ -205,10 +223,10 @@ void LayoutHelpers::BeginPropertyGrid(const char* label) { // Create a 2-column table for property editing if (ImGui::BeginTable(label, 2, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { // Setup columns: label column (30%) and value column (70%) ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, - GetStandardInputWidth() * 1.5f); + GetStandardInputWidth() * 1.5f); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); } } @@ -217,23 +235,27 @@ void LayoutHelpers::EndPropertyGrid() { ImGui::EndTable(); } -bool LayoutHelpers::InputToolbarField(const char* label, char* buf, size_t buf_size) { +bool LayoutHelpers::InputToolbarField(const char* label, char* buf, + size_t buf_size) { // Compact input field for toolbars - float compact_width = GetStandardInputWidth() * 0.8f * GetTheme().compact_factor; + float compact_width = + GetStandardInputWidth() * 0.8f * GetTheme().compact_factor; ImGui::SetNextItemWidth(compact_width); return ImGui::InputText(label, buf, buf_size, - ImGuiInputTextFlags_AutoSelectAll); + ImGuiInputTextFlags_AutoSelectAll); } // Toolbar helpers void LayoutHelpers::BeginToolbar(const char* label) { const auto& theme = GetTheme(); - ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.menu_bar_bg)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, + ConvertColorToImVec4(theme.menu_bar_bg)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, - ImVec2(GetButtonPadding(), GetButtonPadding())); - ImGui::BeginChild(label, ImVec2(0, GetToolbarHeight()), true, - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImVec2(GetButtonPadding(), GetButtonPadding())); + ImGui::BeginChild( + label, ImVec2(0, GetToolbarHeight()), true, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); } void LayoutHelpers::EndToolbar() { @@ -255,14 +277,15 @@ void LayoutHelpers::ToolbarSeparator() { bool LayoutHelpers::ToolbarButton(const char* label, const ImVec2& size) { const auto& theme = GetTheme(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2(GetButtonPadding(), GetButtonPadding())); + ImVec2(GetButtonPadding(), GetButtonPadding())); bool result = ImGui::Button(label, size); ImGui::PopStyleVar(1); return result; } // Common layout patterns -void LayoutHelpers::PropertyRow(const char* label, std::function widget_callback) { +void LayoutHelpers::PropertyRow(const char* label, + std::function widget_callback) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::AlignTextToFramePadding(); @@ -289,5 +312,5 @@ void LayoutHelpers::HelpMarker(const char* desc) { } } -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze diff --git a/src/app/gui/core/layout_helpers.h b/src/app/gui/core/layout_helpers.h index aab35780..79ac51bf 100644 --- a/src/app/gui/core/layout_helpers.h +++ b/src/app/gui/core/layout_helpers.h @@ -33,23 +33,26 @@ class LayoutHelpers { static void EndPaddedPanel(); static bool BeginTableWithTheming(const char* str_id, int columns, - ImGuiTableFlags flags = 0, - const ImVec2& outer_size = ImVec2(0, 0), - float inner_width = 0.0f); + ImGuiTableFlags flags = 0, + const ImVec2& outer_size = ImVec2(0, 0), + float inner_width = 0.0f); static void EndTableWithTheming(); static void EndTable() { ImGui::EndTable(); } - static void BeginCanvasPanel(const char* label, ImVec2* canvas_size = nullptr); + static void BeginCanvasPanel(const char* label, + ImVec2* canvas_size = nullptr); static void EndCanvasPanel(); // Input field helpers static bool AutoSizedInputField(const char* label, char* buf, size_t buf_size, - ImGuiInputTextFlags flags = 0); + ImGuiInputTextFlags flags = 0); static bool AutoSizedInputInt(const char* label, int* v, int step = 1, - int step_fast = 100, ImGuiInputTextFlags flags = 0); - static bool AutoSizedInputFloat(const char* label, float* v, float step = 0.0f, - float step_fast = 0.0f, const char* format = "%.3f", - ImGuiInputTextFlags flags = 0); + int step_fast = 100, + ImGuiInputTextFlags flags = 0); + static bool AutoSizedInputFloat(const char* label, float* v, + float step = 0.0f, float step_fast = 0.0f, + const char* format = "%.3f", + ImGuiInputTextFlags flags = 0); // Input preset functions for common patterns static bool InputHexRow(const char* label, uint8_t* data); @@ -62,10 +65,12 @@ class LayoutHelpers { static void BeginToolbar(const char* label); static void EndToolbar(); static void ToolbarSeparator(); - static bool ToolbarButton(const char* label, const ImVec2& size = ImVec2(0, 0)); + static bool ToolbarButton(const char* label, + const ImVec2& size = ImVec2(0, 0)); // Common layout patterns - static void PropertyRow(const char* label, std::function widget_callback); + static void PropertyRow(const char* label, + std::function widget_callback); static void SectionHeader(const char* label); static void HelpMarker(const char* desc); @@ -81,7 +86,7 @@ class LayoutHelpers { } }; -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze -#endif // YAZE_APP_GUI_LAYOUT_HELPERS_H +#endif // YAZE_APP_GUI_LAYOUT_HELPERS_H diff --git a/src/app/gui/core/style.cc b/src/app/gui/core/style.cc index 32af6d28..6d965673 100644 --- a/src/app/gui/core/style.cc +++ b/src/app/gui/core/style.cc @@ -2,21 +2,21 @@ #include -#include "util/file_util.h" -#include "app/gui/core/theme_manager.h" #include "app/gui/core/background_renderer.h" -#include "app/platform/font_loader.h" #include "app/gui/core/color.h" #include "app/gui/core/icons.h" +#include "app/gui/core/theme_manager.h" +#include "app/platform/font_loader.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" +#include "util/file_util.h" #include "util/log.h" namespace yaze { namespace gui { namespace { -Color ParseColor(const std::string &color) { +Color ParseColor(const std::string& color) { Color result; if (color.size() == 7 && color[0] == '#') { result.red = std::stoi(color.substr(1, 2), nullptr, 16) / 255.0f; @@ -30,8 +30,8 @@ Color ParseColor(const std::string &color) { } // namespace void ColorsYaze() { - ImGuiStyle *style = &ImGui::GetStyle(); - ImVec4 *colors = style->Colors; + ImGuiStyle* style = &ImGui::GetStyle(); + ImVec4* colors = style->Colors; style->WindowPadding = ImVec2(10.f, 10.f); style->FramePadding = ImVec2(10.f, 2.f); @@ -95,7 +95,8 @@ void ColorsYaze() { colors[ImGuiCol_FrameBgHovered] = ImVec4(0.28f, 0.36f, 0.28f, 0.40f); colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.36f, 0.28f, 0.69f); - colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); // Solid blue checkmark + colors[ImGuiCol_CheckMark] = + ImVec4(0.26f, 0.59f, 0.98f, 1.00f); // Solid blue checkmark colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); colors[ImGuiCol_SliderGrabActive] = ImVec4(0.36f, 0.45f, 0.36f, 0.60f); @@ -128,8 +129,8 @@ void ColorsYaze() { colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); } -void DrawBitmapViewer(const std::vector &bitmaps, float scale, - int ¤t_bitmap_id) { +void DrawBitmapViewer(const std::vector& bitmaps, float scale, + int& current_bitmap_id) { if (bitmaps.empty()) { ImGui::Text("No bitmaps available."); return; @@ -152,7 +153,7 @@ void DrawBitmapViewer(const std::vector &bitmaps, float scale, } // Display the current bitmap. - const gfx::Bitmap ¤t_bitmap = bitmaps[current_bitmap_id]; + const gfx::Bitmap& current_bitmap = bitmaps[current_bitmap_id]; // Assuming Bitmap has a function to get its texture ID, and width and // height. ImTextureID tex_id = (ImTextureID)(intptr_t)current_bitmap.texture(); @@ -167,7 +168,7 @@ void DrawBitmapViewer(const std::vector &bitmaps, float scale, } } -static const char *const kKeywords[] = { +static const char* const kKeywords[] = { "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", "BPL", "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV", "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", "INY", "JMP", "JSR", @@ -178,7 +179,7 @@ static const char *const kKeywords[] = { "TCS", "TDC", "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA", "TYX", "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM"}; -static const char *const kIdentifiers[] = { +static const char* const kIdentifiers[] = { "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", @@ -191,9 +192,10 @@ static const char *const kIdentifiers[] = { TextEditor::LanguageDefinition GetAssemblyLanguageDef() { TextEditor::LanguageDefinition language_65816; - for (auto &k : kKeywords) language_65816.mKeywords.emplace(k); + for (auto& k : kKeywords) + language_65816.mKeywords.emplace(k); - for (auto &k : kIdentifiers) { + for (auto& k : kIdentifiers) { TextEditor::Identifier id; id.mDeclaration = "Built-in function"; language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id)); @@ -244,10 +246,10 @@ TextEditor::LanguageDefinition GetAssemblyLanguageDef() { } // TODO: Add more display settings to popup windows. -void BeginWindowWithDisplaySettings(const char *id, bool *active, - const ImVec2 &size, +void BeginWindowWithDisplaySettings(const char* id, bool* active, + const ImVec2& size, ImGuiWindowFlags flags) { - ImGuiStyle *ref = &ImGui::GetStyle(); + ImGuiStyle* ref = &ImGui::GetStyle(); static float childBgOpacity = 0.75f; auto color = ref->Colors[ImGuiCol_WindowBg]; @@ -273,41 +275,49 @@ void BeginPadding(int i) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(i, i)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(i, i)); } -void EndPadding() { EndNoPadding(); } +void EndPadding() { + EndNoPadding(); +} void BeginNoPadding() { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); } -void EndNoPadding() { ImGui::PopStyleVar(2); } +void EndNoPadding() { + ImGui::PopStyleVar(2); +} -void BeginChildWithScrollbar(const char *str_id) { +void BeginChildWithScrollbar(const char* str_id) { // Get available region but ensure minimum size for proper scrolling ImVec2 available = ImGui::GetContentRegionAvail(); - if (available.x < 64.0f) available.x = 64.0f; - if (available.y < 64.0f) available.y = 64.0f; - + if (available.x < 64.0f) + available.x = 64.0f; + if (available.y < 64.0f) + available.y = 64.0f; + ImGui::BeginChild(str_id, available, true, ImGuiWindowFlags_AlwaysVerticalScrollbar); } -void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size) { +void BeginChildWithScrollbar(const char* str_id, ImVec2 content_size) { // Set content size before beginning child to enable proper scrolling if (content_size.x > 0 && content_size.y > 0) { ImGui::SetNextWindowContentSize(content_size); } - + // Get available region but ensure minimum size for proper scrolling ImVec2 available = ImGui::GetContentRegionAvail(); - if (available.x < 64.0f) available.x = 64.0f; - if (available.y < 64.0f) available.y = 64.0f; - + if (available.x < 64.0f) + available.x = 64.0f; + if (available.y < 64.0f) + available.y = 64.0f; + ImGui::BeginChild(str_id, available, true, ImGuiWindowFlags_AlwaysVerticalScrollbar); } void BeginChildBothScrollbars(int id) { - ImGuiID child_id = ImGui::GetID((void *)(intptr_t)id); + ImGuiID child_id = ImGui::GetID((void*)(intptr_t)id); ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); @@ -316,15 +326,17 @@ void BeginChildBothScrollbars(int id) { // Helper functions for table canvas management void BeginTableCanvas(const char* table_id, int columns, ImVec2 canvas_size) { // Use proper sizing for tables containing canvas elements - ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp; - + ImGuiTableFlags flags = + ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp; + // If canvas size is specified, use it as minimum size ImVec2 outer_size = ImVec2(0, 0); if (canvas_size.x > 0 || canvas_size.y > 0) { outer_size = canvas_size; - flags |= ImGuiTableFlags_NoHostExtendY; // Prevent auto-extending past canvas size + flags |= + ImGuiTableFlags_NoHostExtendY; // Prevent auto-extending past canvas size } - + ImGui::BeginTable(table_id, columns, flags, outer_size); } @@ -334,7 +346,8 @@ void EndTableCanvas() { void SetupCanvasTableColumn(const char* label, float width_ratio) { if (width_ratio > 0) { - ImGui::TableSetupColumn(label, ImGuiTableColumnFlags_WidthStretch, width_ratio); + ImGui::TableSetupColumn(label, ImGuiTableColumnFlags_WidthStretch, + width_ratio); } else { ImGui::TableSetupColumn(label, ImGuiTableColumnFlags_WidthStretch); } @@ -342,106 +355,115 @@ void SetupCanvasTableColumn(const char* label, float width_ratio) { void BeginCanvasTableCell(ImVec2 min_size) { ImGui::TableNextColumn(); - + // Ensure minimum size for canvas cells if (min_size.x > 0 || min_size.y > 0) { ImVec2 avail = ImGui::GetContentRegionAvail(); - ImVec2 actual_size = ImVec2( - std::max(avail.x, min_size.x), - std::max(avail.y, min_size.y) - ); - + ImVec2 actual_size = + ImVec2(std::max(avail.x, min_size.x), std::max(avail.y, min_size.y)); + // Reserve space for the canvas ImGui::Dummy(actual_size); // ImGui::SetCursorPos(ImGui::GetCursorPos() - actual_size); // Reset cursor for drawing } } -void DrawDisplaySettings(ImGuiStyle *ref) { +void DrawDisplaySettings(ImGuiStyle* ref) { // You can pass in a reference ImGuiStyle structure to compare to, revert to // and save to (without a reference style pointer, we will use one compared // locally as a reference) - ImGuiStyle &style = ImGui::GetStyle(); + ImGuiStyle& style = ImGui::GetStyle(); static ImGuiStyle ref_saved_style; // Default to using internal storage as reference static bool init = true; - if (init && ref == NULL) ref_saved_style = style; + if (init && ref == NULL) + ref_saved_style = style; init = false; - if (ref == NULL) ref = &ref_saved_style; + if (ref == NULL) + ref = &ref_saved_style; ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); // Enhanced theme management section - if (ImGui::CollapsingHeader("Theme Management", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader("Theme Management", + ImGuiTreeNodeFlags_DefaultOpen)) { auto& theme_manager = ThemeManager::Get(); static bool show_theme_selector = false; static bool show_theme_editor = false; - + ImGui::Text("%s Current Theme:", ICON_MD_PALETTE); ImGui::SameLine(); - + std::string current_theme_name = theme_manager.GetCurrentThemeName(); bool is_classic_active = (current_theme_name == "Classic YAZE"); - + // Current theme display with color preview if (is_classic_active) { - ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", current_theme_name.c_str()); + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", + current_theme_name.c_str()); } else { ImGui::Text("%s", current_theme_name.c_str()); } - + // Theme color preview auto current_theme = theme_manager.GetCurrentTheme(); ImGui::SameLine(); - ImGui::ColorButton("##primary_preview", gui::ConvertColorToImVec4(current_theme.primary), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::ColorButton("##primary_preview", + gui::ConvertColorToImVec4(current_theme.primary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); ImGui::SameLine(); - ImGui::ColorButton("##secondary_preview", gui::ConvertColorToImVec4(current_theme.secondary), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::ColorButton("##secondary_preview", + gui::ConvertColorToImVec4(current_theme.secondary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); ImGui::SameLine(); - ImGui::ColorButton("##accent_preview", gui::ConvertColorToImVec4(current_theme.accent), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); - + ImGui::ColorButton("##accent_preview", + gui::ConvertColorToImVec4(current_theme.accent), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::Spacing(); - + // Theme selection table for better organization - if (ImGui::BeginTable("ThemeSelectionTable", 3, - ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoHostExtendY, - ImVec2(0, 80))) { - - ImGui::TableSetupColumn("Built-in", ImGuiTableColumnFlags_WidthStretch, 0.3f); - ImGui::TableSetupColumn("File Themes", ImGuiTableColumnFlags_WidthStretch, 0.4f); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.3f); + if (ImGui::BeginTable( + "ThemeSelectionTable", 3, + ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoHostExtendY, + ImVec2(0, 80))) { + + ImGui::TableSetupColumn("Built-in", ImGuiTableColumnFlags_WidthStretch, + 0.3f); + ImGui::TableSetupColumn("File Themes", ImGuiTableColumnFlags_WidthStretch, + 0.4f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, + 0.3f); ImGui::TableHeadersRow(); - + ImGui::TableNextRow(); - + // Built-in themes column ImGui::TableNextColumn(); if (is_classic_active) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f)); } - + if (ImGui::Button("Classic YAZE", ImVec2(-1, 30))) { theme_manager.ApplyClassicYazeTheme(); ref_saved_style = style; } - + if (is_classic_active) { ImGui::PopStyleColor(); } - + if (ImGui::Button("Reset ColorsYaze", ImVec2(-1, 30))) { gui::ColorsYaze(); ref_saved_style = style; } - + // File themes column ImGui::TableNextColumn(); auto available_themes = theme_manager.GetAvailableThemes(); const char* current_file_theme = ""; - + // Find current file theme for display for (const auto& theme_name : available_themes) { if (theme_name == current_theme_name) { @@ -449,7 +471,7 @@ void DrawDisplaySettings(ImGuiStyle *ref) { break; } } - + ImGui::SetNextItemWidth(-1); if (ImGui::BeginCombo("##FileThemes", current_file_theme)) { for (const auto& theme_name : available_themes) { @@ -461,43 +483,44 @@ void DrawDisplaySettings(ImGuiStyle *ref) { } ImGui::EndCombo(); } - + if (ImGui::Button("Refresh Themes", ImVec2(-1, 30))) { theme_manager.RefreshAvailableThemes(); } - + // Actions column ImGui::TableNextColumn(); if (ImGui::Button("Theme Selector", ImVec2(-1, 30))) { show_theme_selector = true; } - + if (ImGui::Button("Theme Editor", ImVec2(-1, 30))) { show_theme_editor = true; } - + ImGui::EndTable(); } - + // Show theme dialogs if (show_theme_selector) { theme_manager.ShowThemeSelector(&show_theme_selector); } - + if (show_theme_editor) { theme_manager.ShowSimpleThemeEditor(&show_theme_editor); } } - + ImGui::Separator(); - + // Background effects settings auto& bg_renderer = gui::BackgroundRenderer::Get(); bg_renderer.DrawSettingsUI(); - + ImGui::Separator(); - - if (ImGui::ShowStyleSelector("Colors##Selector")) ref_saved_style = style; + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; ImGui::ShowFontSelector("Fonts##Selector"); // Simplified Settings (expose floating-pointer border sizes as boolean @@ -528,9 +551,11 @@ void DrawDisplaySettings(ImGuiStyle *ref) { } // Save/Revert button - if (ImGui::Button("Save Ref")) *ref = ref_saved_style = style; + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; ImGui::SameLine(); - if (ImGui::Button("Revert Ref")) style = *ref; + if (ImGui::Button("Revert Ref")) + style = *ref; ImGui::SameLine(); ImGui::Separator(); @@ -538,17 +563,16 @@ void DrawDisplaySettings(ImGuiStyle *ref) { if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { if (ImGui::BeginTabItem("Sizes")) { ImGui::SeparatorText("Main"); - ImGui::SliderFloat2("WindowPadding", (float *)&style.WindowPadding, 0.0f, + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("FramePadding", (float *)&style.FramePadding, 0.0f, + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemSpacing", (float *)&style.ItemSpacing, 0.0f, + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemInnerSpacing", (float *)&style.ItemInnerSpacing, + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("TouchExtraPadding", - (float *)&style.TouchExtraPadding, 0.0f, 10.0f, - "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, + 0.0f, 10.0f, "%.0f"); ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, @@ -587,32 +611,32 @@ void DrawDisplaySettings(ImGuiStyle *ref) { "%.0f"); ImGui::SeparatorText("Tables"); - ImGui::SliderFloat2("CellPadding", (float *)&style.CellPadding, 0.0f, + ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); ImGui::SeparatorText("Widgets"); - ImGui::SliderFloat2("WindowTitleAlign", (float *)&style.WindowTitleAlign, + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - ImGui::Combo("ColorButtonPosition", (int *)&style.ColorButtonPosition, + ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); - ImGui::SliderFloat2("ButtonTextAlign", (float *)&style.ButtonTextAlign, + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ImGui::SliderFloat2("SelectableTextAlign", - (float *)&style.SelectableTextAlign, 0.0f, 1.0f, + (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ImGui::SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); ImGui::SliderFloat2("SeparatorTextAlign", - (float *)&style.SeparatorTextAlign, 0.0f, 1.0f, + (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SliderFloat2("SeparatorTextPadding", - (float *)&style.SeparatorTextPadding, 0.0f, 40.0f, + (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); @@ -621,7 +645,7 @@ void DrawDisplaySettings(ImGuiStyle *ref) { for (int n = 0; n < 2; n++) if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" : "HoverFlagsForTooltipNav")) { - ImGuiHoveredFlags *p = (n == 0) ? &style.HoverFlagsForTooltipMouse + ImGuiHoveredFlags* p = (n == 0) ? &style.HoverFlagsForTooltipMouse : &style.HoverFlagsForTooltipNav; ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, ImGuiHoveredFlags_DelayNone); @@ -638,7 +662,7 @@ void DrawDisplaySettings(ImGuiStyle *ref) { ImGui::SeparatorText("Misc"); ImGui::SliderFloat2("DisplaySafeAreaPadding", - (float *)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, + (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); @@ -655,8 +679,8 @@ void DrawDisplaySettings(ImGuiStyle *ref) { ImGui::LogToTTY(); ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); for (int i = 0; i < ImGuiCol_COUNT; i++) { - const ImVec4 &col = style.Colors[i]; - const char *name = ImGui::GetStyleColorName(i); + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) ImGui::LogText( @@ -701,10 +725,11 @@ void DrawDisplaySettings(ImGuiStyle *ref) { ImGuiWindowFlags_NavFlattened); ImGui::PushItemWidth(ImGui::GetFontSize() * -12); for (int i = 0; i < ImGuiCol_COUNT; i++) { - const char *name = ImGui::GetStyleColorName(i); - if (!filter.PassFilter(name)) continue; + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; ImGui::PushID(i); - ImGui::ColorEdit4("##color", (float *)&style.Colors[i], + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { // Tips: in a real user application, you may want to merge and use @@ -731,8 +756,8 @@ void DrawDisplaySettings(ImGuiStyle *ref) { } if (ImGui::BeginTabItem("Fonts")) { - ImGuiIO &io = ImGui::GetIO(); - ImFontAtlas *atlas = io.Fonts; + ImGuiIO& io = ImGui::GetIO(); + ImFontAtlas* atlas = io.Fonts; ImGui::ShowFontAtlas(atlas); // Post-baking font scaling. Note that this is NOT the nice way of @@ -779,11 +804,12 @@ void DrawDisplaySettings(ImGuiStyle *ref) { &style.CircleTessellationMaxError, 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); const bool show_samples = ImGui::IsItemActive(); - if (show_samples) ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); + if (show_samples) + ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); if (show_samples && ImGui::BeginTooltip()) { ImGui::TextUnformatted("(R = radius, N = number of segments)"); ImGui::Spacing(); - ImDrawList *draw_list = ImGui::GetWindowDrawList(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; for (int n = 0; n < 8; n++) { const float RAD_MIN = 5.0f; @@ -839,74 +865,81 @@ void DrawDisplaySettings(ImGuiStyle *ref) { ImGui::PopItemWidth(); } -void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { +void DrawDisplaySettingsForPopup(ImGuiStyle* ref) { // Popup-safe version of DrawDisplaySettings without problematic tables - ImGuiStyle &style = ImGui::GetStyle(); + ImGuiStyle& style = ImGui::GetStyle(); static ImGuiStyle ref_saved_style; // Default to using internal storage as reference static bool init = true; - if (init && ref == NULL) ref_saved_style = style; + if (init && ref == NULL) + ref_saved_style = style; init = false; - if (ref == NULL) ref = &ref_saved_style; + if (ref == NULL) + ref = &ref_saved_style; ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); // Enhanced theme management section (simplified for popup) - if (ImGui::CollapsingHeader("Theme Management", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader("Theme Management", + ImGuiTreeNodeFlags_DefaultOpen)) { auto& theme_manager = ThemeManager::Get(); - + ImGui::Text("%s Current Theme:", ICON_MD_PALETTE); ImGui::SameLine(); - + std::string current_theme_name = theme_manager.GetCurrentThemeName(); bool is_classic_active = (current_theme_name == "Classic YAZE"); - + // Current theme display with color preview if (is_classic_active) { - ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", current_theme_name.c_str()); + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", + current_theme_name.c_str()); } else { ImGui::Text("%s", current_theme_name.c_str()); } - + // Theme color preview auto current_theme = theme_manager.GetCurrentTheme(); ImGui::SameLine(); - ImGui::ColorButton("##primary_preview", gui::ConvertColorToImVec4(current_theme.primary), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::ColorButton("##primary_preview", + gui::ConvertColorToImVec4(current_theme.primary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); ImGui::SameLine(); - ImGui::ColorButton("##secondary_preview", gui::ConvertColorToImVec4(current_theme.secondary), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::ColorButton("##secondary_preview", + gui::ConvertColorToImVec4(current_theme.secondary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); ImGui::SameLine(); - ImGui::ColorButton("##accent_preview", gui::ConvertColorToImVec4(current_theme.accent), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); - + ImGui::ColorButton("##accent_preview", + gui::ConvertColorToImVec4(current_theme.accent), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::Spacing(); - + // Simplified theme selection (no table to avoid popup conflicts) if (is_classic_active) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f)); } - + if (ImGui::Button("Classic YAZE")) { theme_manager.ApplyClassicYazeTheme(); ref_saved_style = style; } - + if (is_classic_active) { ImGui::PopStyleColor(); } - + ImGui::SameLine(); if (ImGui::Button("Reset ColorsYaze")) { gui::ColorsYaze(); ref_saved_style = style; } - + // File themes dropdown auto available_themes = theme_manager.GetAvailableThemes(); const char* current_file_theme = ""; - + // Find current file theme for display for (const auto& theme_name : available_themes) { if (theme_name == current_theme_name) { @@ -914,7 +947,7 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { break; } } - + ImGui::Text("File Themes:"); ImGui::SetNextItemWidth(-1); if (ImGui::BeginCombo("##FileThemes", current_file_theme)) { @@ -927,7 +960,7 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { } ImGui::EndCombo(); } - + if (ImGui::Button("Refresh Themes")) { theme_manager.RefreshAvailableThemes(); } @@ -937,63 +970,66 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { theme_manager.ShowSimpleThemeEditor(&show_theme_editor); } } - + ImGui::Separator(); - + // Background effects settings auto& bg_renderer = gui::BackgroundRenderer::Get(); bg_renderer.DrawSettingsUI(); - + ImGui::Separator(); - - if (ImGui::ShowStyleSelector("Colors##Selector")) ref_saved_style = style; + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; ImGui::ShowFontSelector("Fonts##Selector"); // Quick style controls before the tabbed section - if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, + "%.0f")) style.GrabRounding = style.FrameRounding; - + // Border checkboxes (simplified layout) bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) { style.WindowBorderSize = window_border ? 1.0f : 0.0f; } ImGui::SameLine(); - + bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) { style.FrameBorderSize = frame_border ? 1.0f : 0.0f; } ImGui::SameLine(); - + bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) { style.PopupBorderSize = popup_border ? 1.0f : 0.0f; } // Save/Revert buttons - if (ImGui::Button("Save Ref")) *ref = ref_saved_style = style; + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; ImGui::SameLine(); - if (ImGui::Button("Revert Ref")) style = *ref; + if (ImGui::Button("Revert Ref")) + style = *ref; ImGui::Separator(); // Add the comprehensive tabbed settings from the original DrawDisplaySettings if (ImGui::BeginTabBar("DisplaySettingsTabs", ImGuiTabBarFlags_None)) { - + if (ImGui::BeginTabItem("Sizes")) { ImGui::SeparatorText("Main"); - ImGui::SliderFloat2("WindowPadding", (float *)&style.WindowPadding, 0.0f, + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("FramePadding", (float *)&style.FramePadding, 0.0f, + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemSpacing", (float *)&style.ItemSpacing, 0.0f, + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemInnerSpacing", (float *)&style.ItemInnerSpacing, + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("TouchExtraPadding", - (float *)&style.TouchExtraPadding, 0.0f, 10.0f, - "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, + 0.0f, 10.0f, "%.0f"); ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, @@ -1032,32 +1068,32 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { "%.0f"); ImGui::SeparatorText("Tables"); - ImGui::SliderFloat2("CellPadding", (float *)&style.CellPadding, 0.0f, + ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); ImGui::SeparatorText("Widgets"); - ImGui::SliderFloat2("WindowTitleAlign", (float *)&style.WindowTitleAlign, + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - ImGui::Combo("ColorButtonPosition", (int *)&style.ColorButtonPosition, + ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); - ImGui::SliderFloat2("ButtonTextAlign", (float *)&style.ButtonTextAlign, + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ImGui::SliderFloat2("SelectableTextAlign", - (float *)&style.SelectableTextAlign, 0.0f, 1.0f, + (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ImGui::SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); ImGui::SliderFloat2("SeparatorTextAlign", - (float *)&style.SeparatorTextAlign, 0.0f, 1.0f, + (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SliderFloat2("SeparatorTextPadding", - (float *)&style.SeparatorTextPadding, 0.0f, 40.0f, + (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); @@ -1066,7 +1102,7 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { for (int n = 0; n < 2; n++) if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" : "HoverFlagsForTooltipNav")) { - ImGuiHoveredFlags *p = (n == 0) ? &style.HoverFlagsForTooltipMouse + ImGuiHoveredFlags* p = (n == 0) ? &style.HoverFlagsForTooltipMouse : &style.HoverFlagsForTooltipNav; ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, ImGuiHoveredFlags_DelayNone); @@ -1083,7 +1119,7 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { ImGui::SeparatorText("Misc"); ImGui::SliderFloat2("DisplaySafeAreaPadding", - (float *)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, + (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); @@ -1100,8 +1136,8 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { ImGui::LogToTTY(); ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); for (int i = 0; i < ImGuiCol_COUNT; i++) { - const ImVec4 &col = style.Colors[i]; - const char *name = ImGui::GetStyleColorName(i); + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) ImGui::LogText( @@ -1146,10 +1182,11 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { ImGuiWindowFlags_NavFlattened); ImGui::PushItemWidth(ImGui::GetFontSize() * -12); for (int i = 0; i < ImGuiCol_COUNT; i++) { - const char *name = ImGui::GetStyleColorName(i); - if (!filter.PassFilter(name)) continue; + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; ImGui::PushID(i); - ImGui::ColorEdit4("##color", (float *)&style.Colors[i], + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { // Tips: in a real user application, you may want to merge and use @@ -1176,8 +1213,8 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { } if (ImGui::BeginTabItem("Fonts")) { - ImGuiIO &io = ImGui::GetIO(); - ImFontAtlas *atlas = io.Fonts; + ImGuiIO& io = ImGui::GetIO(); + ImFontAtlas* atlas = io.Fonts; ImGui::ShowFontAtlas(atlas); // Post-baking font scaling. Note that this is NOT the nice way of @@ -1224,11 +1261,12 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { &style.CircleTessellationMaxError, 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); const bool show_samples = ImGui::IsItemActive(); - if (show_samples) ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); + if (show_samples) + ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); if (show_samples && ImGui::BeginTooltip()) { ImGui::TextUnformatted("(R = radius, N = number of segments)"); ImGui::Spacing(); - ImDrawList *draw_list = ImGui::GetWindowDrawList(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; for (int n = 0; n < 8; n++) { const float RAD_MIN = 5.0f; @@ -1277,23 +1315,23 @@ void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { ImGui::PopItemWidth(); } -void TextWithSeparators(const absl::string_view &text) { +void TextWithSeparators(const absl::string_view& text) { ImGui::Separator(); ImGui::Text("%s", text.data()); ImGui::Separator(); } void DrawFontManager() { - ImGuiIO &io = ImGui::GetIO(); - ImFontAtlas *atlas = io.Fonts; + ImGuiIO& io = ImGui::GetIO(); + ImFontAtlas* atlas = io.Fonts; - static ImFont *current_font = atlas->Fonts[0]; + static ImFont* current_font = atlas->Fonts[0]; static int current_font_index = 0; static int font_size = 16; static bool font_selected = false; ImGui::Text("Loaded fonts"); - for (const auto &loaded_font : font_registry.fonts) { + for (const auto& loaded_font : font_registry.fonts) { ImGui::Text("%s", loaded_font.font_path); } ImGui::Separator(); diff --git a/src/app/gui/core/style.h b/src/app/gui/core/style.h index 59021043..ba82c65d 100644 --- a/src/app/gui/core/style.h +++ b/src/app/gui/core/style.h @@ -18,11 +18,11 @@ void ColorsYaze(); TextEditor::LanguageDefinition GetAssemblyLanguageDef(); -void DrawBitmapViewer(const std::vector &bitmaps, float scale, - int ¤t_bitmap); +void DrawBitmapViewer(const std::vector& bitmaps, float scale, + int& current_bitmap); -void BeginWindowWithDisplaySettings(const char *id, bool *active, - const ImVec2 &size = ImVec2(0, 0), +void BeginWindowWithDisplaySettings(const char* id, bool* active, + const ImVec2& size = ImVec2(0, 0), ImGuiWindowFlags flags = 0); void EndWindowWithDisplaySettings(); @@ -33,21 +33,23 @@ void EndPadding(); void BeginNoPadding(); void EndNoPadding(); -void BeginChildWithScrollbar(const char *str_id); -void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size); +void BeginChildWithScrollbar(const char* str_id); +void BeginChildWithScrollbar(const char* str_id, ImVec2 content_size); void BeginChildBothScrollbars(int id); // Table canvas management helpers for GUI elements that need proper sizing -void BeginTableCanvas(const char* table_id, int columns, ImVec2 canvas_size = ImVec2(0, 0)); +void BeginTableCanvas(const char* table_id, int columns, + ImVec2 canvas_size = ImVec2(0, 0)); void EndTableCanvas(); void SetupCanvasTableColumn(const char* label, float width_ratio = 0.0f); void BeginCanvasTableCell(ImVec2 min_size = ImVec2(0, 0)); -void DrawDisplaySettings(ImGuiStyle *ref = nullptr); -void DrawDisplaySettingsForPopup(ImGuiStyle *ref = nullptr); // Popup-safe version +void DrawDisplaySettings(ImGuiStyle* ref = nullptr); +void DrawDisplaySettingsForPopup( + ImGuiStyle* ref = nullptr); // Popup-safe version -void TextWithSeparators(const absl::string_view &text); +void TextWithSeparators(const absl::string_view& text); void DrawFontManager(); @@ -112,17 +114,17 @@ class MultiSelect { public: // Callback function type for rendering an item using ItemRenderer = - std::function; + std::function; // Constructor with optional title and default flags MultiSelect( - const char *title = "Selection", + const char* title = "Selection", ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d) : title_(title), flags_(flags), selection_() {} // Set the items to display - void SetItems(const std::vector &items) { items_ = items; } + void SetItems(const std::vector& items) { items_ = items; } // Set the renderer function for items void SetItemRenderer(ItemRenderer renderer) { item_renderer_ = renderer; } @@ -143,7 +145,7 @@ class MultiSelect { "##MultiSelectChild", ImVec2(-FLT_MIN, ImGui::GetFontSize() * height_in_font_units_), child_flags_)) { - ImGuiMultiSelectIO *ms_io = + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags_, selection_.Size, items_.size()); selection_.ApplyRequests(ms_io); @@ -189,7 +191,7 @@ class MultiSelect { void ClearSelection() { selection_.Clear(); } private: - const char *title_; + const char* title_; ImGuiMultiSelectFlags flags_; ImGuiSelectionBasicStorage selection_; std::vector items_; diff --git a/src/app/gui/core/theme_manager.cc b/src/app/gui/core/theme_manager.cc index 8a520e70..74625107 100644 --- a/src/app/gui/core/theme_manager.cc +++ b/src/app/gui/core/theme_manager.cc @@ -2,20 +2,20 @@ #include #include +#include #include #include #include -#include #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" -#include "util/file_util.h" -#include "util/platform_paths.h" #include "app/gui/core/icons.h" #include "app/gui/core/style.h" // For ColorsYaze function #include "imgui/imgui.h" -#include "util/log.h" #include "nlohmann/json.hpp" +#include "util/file_util.h" +#include "util/log.h" +#include "util/platform_paths.h" namespace yaze { namespace gui { @@ -33,7 +33,7 @@ Color RGBA(int r, int g, int b, int a = 255) { void EnhancedTheme::ApplyToImGui() const { ImGuiStyle* style = &ImGui::GetStyle(); ImVec4* colors = style->Colors; - + // Apply colors colors[ImGuiCol_Text] = ConvertColorToImVec4(text_primary); colors[ImGuiCol_TextDisabled] = ConvertColorToImVec4(text_disabled); @@ -51,8 +51,10 @@ void EnhancedTheme::ApplyToImGui() const { colors[ImGuiCol_MenuBarBg] = ConvertColorToImVec4(menu_bar_bg); colors[ImGuiCol_ScrollbarBg] = ConvertColorToImVec4(scrollbar_bg); colors[ImGuiCol_ScrollbarGrab] = ConvertColorToImVec4(scrollbar_grab); - colors[ImGuiCol_ScrollbarGrabHovered] = ConvertColorToImVec4(scrollbar_grab_hovered); - colors[ImGuiCol_ScrollbarGrabActive] = ConvertColorToImVec4(scrollbar_grab_active); + colors[ImGuiCol_ScrollbarGrabHovered] = + ConvertColorToImVec4(scrollbar_grab_hovered); + colors[ImGuiCol_ScrollbarGrabActive] = + ConvertColorToImVec4(scrollbar_grab_active); colors[ImGuiCol_Button] = ConvertColorToImVec4(button); colors[ImGuiCol_ButtonHovered] = ConvertColorToImVec4(button_hovered); colors[ImGuiCol_ButtonActive] = ConvertColorToImVec4(button_active); @@ -63,29 +65,34 @@ void EnhancedTheme::ApplyToImGui() const { colors[ImGuiCol_SeparatorHovered] = ConvertColorToImVec4(separator_hovered); colors[ImGuiCol_SeparatorActive] = ConvertColorToImVec4(separator_active); colors[ImGuiCol_ResizeGrip] = ConvertColorToImVec4(resize_grip); - colors[ImGuiCol_ResizeGripHovered] = ConvertColorToImVec4(resize_grip_hovered); + colors[ImGuiCol_ResizeGripHovered] = + ConvertColorToImVec4(resize_grip_hovered); colors[ImGuiCol_ResizeGripActive] = ConvertColorToImVec4(resize_grip_active); 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_TabUnfocusedActive] = + ConvertColorToImVec4(tab_unfocused_active); colors[ImGuiCol_DockingPreview] = ConvertColorToImVec4(docking_preview); colors[ImGuiCol_DockingEmptyBg] = ConvertColorToImVec4(docking_empty_bg); - + // Complete ImGui color support colors[ImGuiCol_CheckMark] = ConvertColorToImVec4(check_mark); colors[ImGuiCol_SliderGrab] = ConvertColorToImVec4(slider_grab); colors[ImGuiCol_SliderGrabActive] = ConvertColorToImVec4(slider_grab_active); colors[ImGuiCol_InputTextCursor] = ConvertColorToImVec4(input_text_cursor); colors[ImGuiCol_NavCursor] = ConvertColorToImVec4(nav_cursor); - colors[ImGuiCol_NavWindowingHighlight] = ConvertColorToImVec4(nav_windowing_highlight); - colors[ImGuiCol_NavWindowingDimBg] = ConvertColorToImVec4(nav_windowing_dim_bg); + colors[ImGuiCol_NavWindowingHighlight] = + ConvertColorToImVec4(nav_windowing_highlight); + colors[ImGuiCol_NavWindowingDimBg] = + ConvertColorToImVec4(nav_windowing_dim_bg); colors[ImGuiCol_ModalWindowDimBg] = ConvertColorToImVec4(modal_window_dim_bg); colors[ImGuiCol_TextSelectedBg] = ConvertColorToImVec4(text_selected_bg); colors[ImGuiCol_DragDropTarget] = ConvertColorToImVec4(drag_drop_target); colors[ImGuiCol_TableHeaderBg] = ConvertColorToImVec4(table_header_bg); - colors[ImGuiCol_TableBorderStrong] = ConvertColorToImVec4(table_border_strong); + colors[ImGuiCol_TableBorderStrong] = + ConvertColorToImVec4(table_border_strong); colors[ImGuiCol_TableBorderLight] = ConvertColorToImVec4(table_border_light); colors[ImGuiCol_TableRowBg] = ConvertColorToImVec4(table_row_bg); colors[ImGuiCol_TableRowBgAlt] = ConvertColorToImVec4(table_row_bg_alt); @@ -93,15 +100,19 @@ void EnhancedTheme::ApplyToImGui() const { colors[ImGuiCol_PlotLines] = ConvertColorToImVec4(plot_lines); colors[ImGuiCol_PlotLinesHovered] = ConvertColorToImVec4(plot_lines_hovered); colors[ImGuiCol_PlotHistogram] = ConvertColorToImVec4(plot_histogram); - colors[ImGuiCol_PlotHistogramHovered] = ConvertColorToImVec4(plot_histogram_hovered); + colors[ImGuiCol_PlotHistogramHovered] = + ConvertColorToImVec4(plot_histogram_hovered); colors[ImGuiCol_TreeLines] = ConvertColorToImVec4(tree_lines); - + // Additional ImGui colors for complete coverage colors[ImGuiCol_TabDimmed] = ConvertColorToImVec4(tab_dimmed); - colors[ImGuiCol_TabDimmedSelected] = ConvertColorToImVec4(tab_dimmed_selected); - colors[ImGuiCol_TabDimmedSelectedOverline] = ConvertColorToImVec4(tab_dimmed_selected_overline); - colors[ImGuiCol_TabSelectedOverline] = ConvertColorToImVec4(tab_selected_overline); - + colors[ImGuiCol_TabDimmedSelected] = + ConvertColorToImVec4(tab_dimmed_selected); + colors[ImGuiCol_TabDimmedSelectedOverline] = + ConvertColorToImVec4(tab_dimmed_selected_overline); + colors[ImGuiCol_TabSelectedOverline] = + ConvertColorToImVec4(tab_selected_overline); + // Apply style parameters style->WindowRounding = window_rounding; style->FrameRounding = frame_rounding; @@ -112,7 +123,6 @@ void EnhancedTheme::ApplyToImGui() const { style->FrameBorderSize = frame_border_size; } - // ThemeManager Implementation ThemeManager& ThemeManager::Get() { static ThemeManager instance; @@ -122,16 +132,16 @@ ThemeManager& ThemeManager::Get() { void ThemeManager::InitializeBuiltInThemes() { // Always create fallback theme first CreateFallbackYazeClassic(); - + // Create the Classic YAZE theme during initialization ApplyClassicYazeTheme(); - + // Load all available theme files dynamically auto status = LoadAllAvailableThemes(); if (!status.ok()) { LOG_ERROR("Theme Manager", "Failed to load some theme files"); } - + // Ensure we have a valid current theme (Classic is already set above) // Only fallback to file themes if Classic creation failed if (current_theme_name_ != "Classic YAZE") { @@ -148,39 +158,41 @@ void ThemeManager::CreateFallbackYazeClassic() { theme.name = "YAZE Tre"; theme.description = "YAZE theme resource edition"; theme.author = "YAZE Team"; - + // Use the exact original ColorsYaze colors - theme.primary = RGBA(92, 115, 92); // allttpLightGreen - theme.secondary = RGBA(71, 92, 71); // alttpMidGreen - theme.accent = RGBA(89, 119, 89); // TabActive - theme.background = RGBA(8, 8, 8); // Very dark gray for better grid visibility - + theme.primary = RGBA(92, 115, 92); // allttpLightGreen + theme.secondary = RGBA(71, 92, 71); // alttpMidGreen + theme.accent = RGBA(89, 119, 89); // TabActive + theme.background = + RGBA(8, 8, 8); // Very dark gray for better grid visibility + theme.text_primary = RGBA(230, 230, 230); // 0.90f, 0.90f, 0.90f theme.text_disabled = RGBA(153, 153, 153); // 0.60f, 0.60f, 0.60f theme.window_bg = RGBA(8, 8, 8, 217); // Very dark gray with same alpha theme.child_bg = RGBA(0, 0, 0, 0); // Transparent theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f - - theme.button = RGBA(71, 92, 71); // alttpMidGreen - theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen - theme.button_active = RGBA(92, 115, 92); // allttpLightGreen - - theme.header = RGBA(46, 66, 46); // alttpDarkGreen - theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen - theme.header_active = RGBA(71, 92, 71); // alttpMidGreen - - theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen - 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 - + + theme.button = RGBA(71, 92, 71); // alttpMidGreen + theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen + theme.button_active = RGBA(92, 115, 92); // allttpLightGreen + + theme.header = RGBA(46, 66, 46); // alttpDarkGreen + theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen + theme.header_active = RGBA(71, 92, 71); // alttpMidGreen + + theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen + 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 - theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen - theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen - + theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen + theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen + theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen + // Initialize missing fields that were added to the struct theme.surface = theme.background; theme.error = RGBA(220, 50, 50); @@ -189,63 +201,76 @@ void ThemeManager::CreateFallbackYazeClassic() { theme.info = RGBA(70, 170, 255); theme.text_secondary = RGBA(200, 200, 200); theme.modal_bg = theme.popup_bg; - + // Borders and separators - theme.border = RGBA(92, 115, 92); // allttpLightGreen - theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent - theme.separator = RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f - theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f - theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f - + theme.border = RGBA(92, 115, 92); // allttpLightGreen + theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent + theme.separator = RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f + theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f + theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f + // Scrollbars - theme.scrollbar_bg = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f - theme.scrollbar_grab = RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f - theme.scrollbar_grab_hovered = RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f - theme.scrollbar_grab_active = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f - + theme.scrollbar_bg = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f + theme.scrollbar_grab = RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f + theme.scrollbar_grab_hovered = + RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f + theme.scrollbar_grab_active = + RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f + // Resize grips (from original - light blue highlights) - theme.resize_grip = RGBA(255, 255, 255, 26); // 1.00f, 1.00f, 1.00f, 0.10f - theme.resize_grip_hovered = RGBA(199, 209, 255, 153); // 0.78f, 0.82f, 1.00f, 0.60f - theme.resize_grip_active = RGBA(199, 209, 255, 230); // 0.78f, 0.82f, 1.00f, 0.90f - + theme.resize_grip = RGBA(255, 255, 255, 26); // 1.00f, 1.00f, 1.00f, 0.10f + theme.resize_grip_hovered = + RGBA(199, 209, 255, 153); // 0.78f, 0.82f, 1.00f, 0.60f + theme.resize_grip_active = + RGBA(199, 209, 255, 230); // 0.78f, 0.82f, 1.00f, 0.90f + // ENHANCED: Complete ImGui colors with theme-aware smart defaults // Use theme colors instead of hardcoded values for consistency - theme.check_mark = RGBA(125, 255, 125, 255); // Bright green checkmark (highly visible!) - theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid) - theme.slider_grab_active = RGBA(125, 146, 125, 255); // Lighter green when active - theme.input_text_cursor = RGBA(255, 255, 255, 255); // White cursor (always visible) - theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green for navigation - theme.nav_windowing_highlight = RGBA(89, 119, 89, 200); // Accent with high visibility - theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay for better contrast - theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha for modals - theme.text_selected_bg = RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible selection!) - theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green for drop zones - + theme.check_mark = + RGBA(125, 255, 125, 255); // Bright green checkmark (highly visible!) + theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid) + theme.slider_grab_active = + RGBA(125, 146, 125, 255); // Lighter green when active + theme.input_text_cursor = + RGBA(255, 255, 255, 255); // White cursor (always visible) + theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green for navigation + theme.nav_windowing_highlight = + RGBA(89, 119, 89, 200); // Accent with high visibility + theme.nav_windowing_dim_bg = + RGBA(0, 0, 0, 150); // Darker overlay for better contrast + theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha for modals + theme.text_selected_bg = RGBA( + 92, 115, 92, 128); // Theme green with 50% alpha (visible selection!) + theme.drag_drop_target = + RGBA(125, 146, 125, 200); // Bright green for drop zones + // Table colors (from original) - theme.table_header_bg = RGBA(46, 66, 46); // alttpDarkGreen - theme.table_border_strong = RGBA(71, 92, 71); // alttpMidGreen - theme.table_border_light = RGBA(66, 66, 71); // 0.26f, 0.26f, 0.28f - theme.table_row_bg = RGBA(0, 0, 0, 0); // Transparent - theme.table_row_bg_alt = RGBA(255, 255, 255, 18); // 1.00f, 1.00f, 1.00f, 0.07f - + theme.table_header_bg = RGBA(46, 66, 46); // alttpDarkGreen + theme.table_border_strong = RGBA(71, 92, 71); // alttpMidGreen + theme.table_border_light = RGBA(66, 66, 71); // 0.26f, 0.26f, 0.28f + theme.table_row_bg = RGBA(0, 0, 0, 0); // Transparent + theme.table_row_bg_alt = + RGBA(255, 255, 255, 18); // 1.00f, 1.00f, 1.00f, 0.07f + // Links and plots - use accent colors intelligently - theme.text_link = theme.accent; // Accent for links - theme.plot_lines = RGBA(255, 255, 255); // White for plots - theme.plot_lines_hovered = RGBA(230, 178, 0); // 0.90f, 0.70f, 0.00f - theme.plot_histogram = RGBA(230, 178, 0); // Same as above - theme.plot_histogram_hovered = RGBA(255, 153, 0); // 1.00f, 0.60f, 0.00f - + theme.text_link = theme.accent; // Accent for links + theme.plot_lines = RGBA(255, 255, 255); // White for plots + theme.plot_lines_hovered = RGBA(230, 178, 0); // 0.90f, 0.70f, 0.00f + theme.plot_histogram = RGBA(230, 178, 0); // Same as above + theme.plot_histogram_hovered = RGBA(255, 153, 0); // 1.00f, 0.60f, 0.00f + // Docking colors - theme.docking_preview = RGBA(92, 115, 92, 180); // Light green with transparency - theme.docking_empty_bg = RGBA(46, 66, 46, 255); // Dark green - + theme.docking_preview = + RGBA(92, 115, 92, 180); // Light green with transparency + theme.docking_empty_bg = RGBA(46, 66, 46, 255); // Dark green + // Apply original style settings theme.window_rounding = 0.0f; theme.frame_rounding = 5.0f; theme.scrollbar_rounding = 5.0f; theme.tab_rounding = 0.0f; theme.enable_glow_effects = false; - + themes_["YAZE Tre"] = theme; current_theme_ = theme; current_theme_name_ = "YAZE Tre"; @@ -254,62 +279,68 @@ void ThemeManager::CreateFallbackYazeClassic() { absl::Status ThemeManager::LoadTheme(const std::string& theme_name) { auto it = themes_.find(theme_name); if (it == themes_.end()) { - return absl::NotFoundError(absl::StrFormat("Theme '%s' not found", theme_name)); + return absl::NotFoundError( + absl::StrFormat("Theme '%s' not found", theme_name)); } - + current_theme_ = it->second; current_theme_name_ = theme_name; current_theme_.ApplyToImGui(); - + return absl::OkStatus(); } absl::Status ThemeManager::LoadThemeFromFile(const std::string& filepath) { // Try multiple possible paths where theme files might be located std::vector possible_paths = { - filepath, // Absolute path - "assets/themes/" + filepath, // Relative from build dir - "../assets/themes/" + filepath, // Relative from bin dir - util::GetResourcePath("assets/themes/" + filepath), // Platform-specific resource path + filepath, // Absolute path + "assets/themes/" + filepath, // Relative from build dir + "../assets/themes/" + filepath, // Relative from bin dir + util::GetResourcePath("assets/themes/" + + filepath), // Platform-specific resource path }; - + std::ifstream file; std::string successful_path; - + for (const auto& path : possible_paths) { file.open(path); if (file.is_open()) { successful_path = path; break; } else { - file.clear(); // Clear any error flags before trying next path + file.clear(); // Clear any error flags before trying next path } } - + if (!file.is_open()) { - return absl::InvalidArgumentError(absl::StrFormat("Cannot open theme file: %s (tried %zu paths)", - filepath, possible_paths.size())); + return absl::InvalidArgumentError( + absl::StrFormat("Cannot open theme file: %s (tried %zu paths)", + filepath, possible_paths.size())); } - + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); file.close(); - + if (content.empty()) { - return absl::InvalidArgumentError(absl::StrFormat("Theme file is empty: %s", successful_path)); + return absl::InvalidArgumentError( + absl::StrFormat("Theme file is empty: %s", successful_path)); } - + EnhancedTheme theme; auto parse_status = ParseThemeFile(content, theme); if (!parse_status.ok()) { - return absl::InvalidArgumentError(absl::StrFormat("Failed to parse theme file %s: %s", - successful_path, parse_status.message())); + return absl::InvalidArgumentError( + absl::StrFormat("Failed to parse theme file %s: %s", successful_path, + parse_status.message())); } - + if (theme.name.empty()) { - return absl::InvalidArgumentError(absl::StrFormat("Theme file missing name: %s", successful_path)); + return absl::InvalidArgumentError( + absl::StrFormat("Theme file missing name: %s", successful_path)); } - + themes_[theme.name] = theme; return absl::OkStatus(); } @@ -340,7 +371,7 @@ void ThemeManager::ApplyTheme(const std::string& theme_name) { void ThemeManager::ApplyTheme(const EnhancedTheme& theme) { current_theme_ = theme; - current_theme_name_ = theme.name; // CRITICAL: Update the name tracking + current_theme_name_ = theme.name; // CRITICAL: Update the name tracking current_theme_.ApplyToImGui(); } @@ -359,102 +390,120 @@ Color ThemeManager::GetWelcomeScreenAccent() const { } void ThemeManager::ShowThemeSelector(bool* p_open) { - if (!p_open || !*p_open) return; - - if (ImGui::Begin(absl::StrFormat("%s Theme Selector", ICON_MD_PALETTE).c_str(), p_open)) { - + if (!p_open || !*p_open) + return; + + if (ImGui::Begin( + absl::StrFormat("%s Theme Selector", ICON_MD_PALETTE).c_str(), + p_open)) { + // Add subtle particle effects to theme selector static float theme_animation_time = 0.0f; theme_animation_time += ImGui::GetIO().DeltaTime; - + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_size = ImGui::GetWindowSize(); - + // Subtle corner particles for theme selector for (int i = 0; i < 4; ++i) { - float corner_offset = i * 1.57f; // 90 degrees apart - float x = window_pos.x + window_size.x * 0.5f + cosf(theme_animation_time * 0.8f + corner_offset) * (window_size.x * 0.4f); - float y = window_pos.y + window_size.y * 0.5f + sinf(theme_animation_time * 0.8f + corner_offset) * (window_size.y * 0.4f); - - float alpha = 0.1f + 0.1f * sinf(theme_animation_time * 1.2f + corner_offset); + float corner_offset = i * 1.57f; // 90 degrees apart + float x = window_pos.x + window_size.x * 0.5f + + cosf(theme_animation_time * 0.8f + corner_offset) * + (window_size.x * 0.4f); + float y = window_pos.y + window_size.y * 0.5f + + sinf(theme_animation_time * 0.8f + corner_offset) * + (window_size.y * 0.4f); + + float alpha = + 0.1f + 0.1f * sinf(theme_animation_time * 1.2f + corner_offset); auto current_theme = GetCurrentTheme(); - ImU32 particle_color = ImGui::ColorConvertFloat4ToU32(ImVec4( - current_theme.accent.red, current_theme.accent.green, current_theme.accent.blue, alpha)); - + ImU32 particle_color = ImGui::ColorConvertFloat4ToU32( + ImVec4(current_theme.accent.red, current_theme.accent.green, + current_theme.accent.blue, alpha)); + draw_list->AddCircleFilled(ImVec2(x, y), 3.0f, particle_color); } - + ImGui::Text("%s Available Themes", ICON_MD_COLOR_LENS); ImGui::Separator(); - + // Add Classic YAZE button first (direct ColorsYaze() application) bool is_classic_active = (current_theme_name_ == "Classic YAZE"); if (is_classic_active) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.36f, 0.45f, 0.36f, 1.0f)); // allttpLightGreen + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.36f, 0.45f, 0.36f, + 1.0f)); // allttpLightGreen } - - if (ImGui::Button(absl::StrFormat("%s YAZE Classic (Original)", - is_classic_active ? ICON_MD_CHECK : ICON_MD_STAR).c_str(), - ImVec2(-1, 50))) { + + if (ImGui::Button( + absl::StrFormat("%s YAZE Classic (Original)", + is_classic_active ? ICON_MD_CHECK : ICON_MD_STAR) + .c_str(), + ImVec2(-1, 50))) { ApplyClassicYazeTheme(); } - + if (is_classic_active) { ImGui::PopStyleColor(); } - + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Original YAZE theme using ColorsYaze() function"); ImGui::Text("This is the authentic classic look - direct function call"); ImGui::EndTooltip(); } - + ImGui::Separator(); - + // Sort themes alphabetically for consistent ordering (by name only) std::vector sorted_theme_names; for (const auto& [name, theme] : themes_) { sorted_theme_names.push_back(name); } std::sort(sorted_theme_names.begin(), sorted_theme_names.end()); - + for (const auto& name : sorted_theme_names) { const auto& theme = themes_.at(name); bool is_current = (name == current_theme_name_); - + if (is_current) { - ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.accent)); + ImGui::PushStyleColor(ImGuiCol_Button, + ConvertColorToImVec4(theme.accent)); } - - if (ImGui::Button(absl::StrFormat("%s %s", - is_current ? ICON_MD_CHECK : ICON_MD_CIRCLE, - name.c_str()).c_str(), ImVec2(-1, 40))) { - auto status = LoadTheme(name); // Use LoadTheme instead of ApplyTheme to ensure correct tracking + + if (ImGui::Button( + absl::StrFormat("%s %s", + is_current ? ICON_MD_CHECK : ICON_MD_CIRCLE, + name.c_str()) + .c_str(), + ImVec2(-1, 40))) { + auto status = LoadTheme( + name); // Use LoadTheme instead of ApplyTheme to ensure correct tracking if (!status.ok()) { LOG_ERROR("Theme Manager", "Failed to load theme %s", name.c_str()); } } - + if (is_current) { ImGui::PopStyleColor(); } - + // Show theme preview colors ImGui::SameLine(); - ImGui::ColorButton(absl::StrFormat("##primary_%s", name.c_str()).c_str(), - ConvertColorToImVec4(theme.primary), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::ColorButton(absl::StrFormat("##primary_%s", name.c_str()).c_str(), + ConvertColorToImVec4(theme.primary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); ImGui::SameLine(); - ImGui::ColorButton(absl::StrFormat("##secondary_%s", name.c_str()).c_str(), - ConvertColorToImVec4(theme.secondary), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::ColorButton( + absl::StrFormat("##secondary_%s", name.c_str()).c_str(), + ConvertColorToImVec4(theme.secondary), ImGuiColorEditFlags_NoTooltip, + ImVec2(20, 20)); ImGui::SameLine(); - ImGui::ColorButton(absl::StrFormat("##accent_%s", name.c_str()).c_str(), - ConvertColorToImVec4(theme.accent), - ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); - + ImGui::ColorButton(absl::StrFormat("##accent_%s", name.c_str()).c_str(), + ConvertColorToImVec4(theme.accent), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("%s", theme.description.c_str()); @@ -462,17 +511,20 @@ void ThemeManager::ShowThemeSelector(bool* p_open) { ImGui::EndTooltip(); } } - + ImGui::Separator(); - if (ImGui::Button(absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) { + if (ImGui::Button( + absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) { auto status = RefreshAvailableThemes(); if (!status.ok()) { LOG_ERROR("Theme Manager", "Failed to refresh themes"); } } - + ImGui::SameLine(); - if (ImGui::Button(absl::StrFormat("%s Load Custom Theme", ICON_MD_FOLDER_OPEN).c_str())) { + if (ImGui::Button( + absl::StrFormat("%s Load Custom Theme", ICON_MD_FOLDER_OPEN) + .c_str())) { auto file_path = util::FileDialogWrapper::ShowOpenFileDialog(); if (!file_path.empty()) { auto status = LoadThemeFromFile(file_path); @@ -481,20 +533,21 @@ void ThemeManager::ShowThemeSelector(bool* p_open) { } } } - + ImGui::SameLine(); static bool show_simple_editor = false; - if (ImGui::Button(absl::StrFormat("%s Theme Editor", ICON_MD_EDIT).c_str())) { + if (ImGui::Button( + absl::StrFormat("%s Theme Editor", ICON_MD_EDIT).c_str())) { show_simple_editor = true; } - + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Edit and save custom themes"); ImGui::Text("Includes 'Save to File' functionality"); ImGui::EndTooltip(); } - + if (show_simple_editor) { ShowSimpleThemeEditor(&show_simple_editor); } @@ -502,143 +555,227 @@ void ThemeManager::ShowThemeSelector(bool* p_open) { ImGui::End(); } -absl::Status ThemeManager::ParseThemeFile(const std::string& content, EnhancedTheme& theme) { +absl::Status ThemeManager::ParseThemeFile(const std::string& content, + EnhancedTheme& theme) { std::istringstream stream(content); std::string line; std::string current_section = ""; - + while (std::getline(stream, line)) { // Skip empty lines and comments - if (line.empty() || line[0] == '#') continue; - + if (line.empty() || line[0] == '#') + continue; + // Check for section headers [section_name] if (line[0] == '[' && line.back() == ']') { current_section = line.substr(1, line.length() - 2); continue; } - + size_t eq_pos = line.find('='); - if (eq_pos == std::string::npos) continue; - + if (eq_pos == std::string::npos) + continue; + std::string key = line.substr(0, eq_pos); std::string value = line.substr(eq_pos + 1); - + // Trim whitespace and comments key.erase(0, key.find_first_not_of(" \t")); key.erase(key.find_last_not_of(" \t") + 1); value.erase(0, value.find_first_not_of(" \t")); - + // Remove inline comments size_t comment_pos = value.find('#'); if (comment_pos != std::string::npos) { value = value.substr(0, comment_pos); } value.erase(value.find_last_not_of(" \t") + 1); - + // Parse based on section if (current_section == "colors") { Color color = ParseColorFromString(value); - - if (key == "primary") theme.primary = color; - else if (key == "secondary") theme.secondary = color; - else if (key == "accent") theme.accent = color; - else if (key == "background") theme.background = color; - else if (key == "surface") theme.surface = color; - else if (key == "error") theme.error = color; - else if (key == "warning") theme.warning = color; - else if (key == "success") theme.success = color; - else if (key == "info") theme.info = color; - else if (key == "text_primary") theme.text_primary = color; - else if (key == "text_secondary") theme.text_secondary = color; - else if (key == "text_disabled") theme.text_disabled = color; - else if (key == "window_bg") theme.window_bg = color; - else if (key == "child_bg") theme.child_bg = color; - else if (key == "popup_bg") theme.popup_bg = color; - else if (key == "button") theme.button = color; - else if (key == "button_hovered") theme.button_hovered = color; - else if (key == "button_active") theme.button_active = color; - else if (key == "frame_bg") theme.frame_bg = color; - else if (key == "frame_bg_hovered") theme.frame_bg_hovered = color; - else if (key == "frame_bg_active") theme.frame_bg_active = color; - else if (key == "header") theme.header = color; - else if (key == "header_hovered") theme.header_hovered = color; - else if (key == "header_active") theme.header_active = color; - else if (key == "tab") theme.tab = color; - else if (key == "tab_hovered") theme.tab_hovered = color; - else if (key == "tab_active") theme.tab_active = color; - else if (key == "menu_bar_bg") theme.menu_bar_bg = color; - else if (key == "title_bg") theme.title_bg = color; - else if (key == "title_bg_active") theme.title_bg_active = color; - else if (key == "title_bg_collapsed") theme.title_bg_collapsed = color; - else if (key == "separator") theme.separator = color; - else if (key == "separator_hovered") theme.separator_hovered = color; - else if (key == "separator_active") theme.separator_active = color; - else if (key == "scrollbar_bg") theme.scrollbar_bg = color; - else if (key == "scrollbar_grab") theme.scrollbar_grab = color; - else if (key == "scrollbar_grab_hovered") theme.scrollbar_grab_hovered = color; - else if (key == "scrollbar_grab_active") theme.scrollbar_grab_active = color; - else if (key == "border") theme.border = color; - else if (key == "border_shadow") theme.border_shadow = color; - else if (key == "resize_grip") theme.resize_grip = color; - else if (key == "resize_grip_hovered") theme.resize_grip_hovered = color; - else if (key == "resize_grip_active") theme.resize_grip_active = color; - else if (key == "check_mark") theme.check_mark = color; - else if (key == "slider_grab") theme.slider_grab = color; - else if (key == "slider_grab_active") theme.slider_grab_active = color; - else if (key == "input_text_cursor") theme.input_text_cursor = color; - else if (key == "nav_cursor") theme.nav_cursor = color; - else if (key == "nav_windowing_highlight") theme.nav_windowing_highlight = color; - else if (key == "nav_windowing_dim_bg") theme.nav_windowing_dim_bg = color; - else if (key == "modal_window_dim_bg") theme.modal_window_dim_bg = color; - else if (key == "text_selected_bg") theme.text_selected_bg = color; - else if (key == "drag_drop_target") theme.drag_drop_target = color; - else if (key == "table_header_bg") theme.table_header_bg = color; - else if (key == "table_border_strong") theme.table_border_strong = color; - else if (key == "table_border_light") theme.table_border_light = color; - else if (key == "table_row_bg") theme.table_row_bg = color; - else if (key == "table_row_bg_alt") theme.table_row_bg_alt = color; - else if (key == "text_link") theme.text_link = color; - else if (key == "plot_lines") theme.plot_lines = color; - else if (key == "plot_lines_hovered") theme.plot_lines_hovered = color; - else if (key == "plot_histogram") theme.plot_histogram = color; - else if (key == "plot_histogram_hovered") theme.plot_histogram_hovered = color; - else if (key == "tree_lines") theme.tree_lines = color; - else if (key == "tab_dimmed") theme.tab_dimmed = color; - else if (key == "tab_dimmed_selected") theme.tab_dimmed_selected = color; - else if (key == "tab_dimmed_selected_overline") theme.tab_dimmed_selected_overline = color; - else if (key == "tab_selected_overline") theme.tab_selected_overline = color; - else if (key == "docking_preview") theme.docking_preview = color; - else if (key == "docking_empty_bg") theme.docking_empty_bg = color; - } - else if (current_section == "style") { - if (key == "window_rounding") theme.window_rounding = std::stof(value); - else if (key == "frame_rounding") theme.frame_rounding = std::stof(value); - else if (key == "scrollbar_rounding") theme.scrollbar_rounding = std::stof(value); - else if (key == "grab_rounding") theme.grab_rounding = std::stof(value); - else if (key == "tab_rounding") theme.tab_rounding = std::stof(value); - else if (key == "window_border_size") theme.window_border_size = std::stof(value); - else if (key == "frame_border_size") theme.frame_border_size = std::stof(value); - else if (key == "enable_animations") theme.enable_animations = (value == "true"); - else if (key == "enable_glow_effects") theme.enable_glow_effects = (value == "true"); - else if (key == "animation_speed") theme.animation_speed = std::stof(value); - } - else if (current_section == "" || current_section == "metadata") { + + if (key == "primary") + theme.primary = color; + else if (key == "secondary") + theme.secondary = color; + else if (key == "accent") + theme.accent = color; + else if (key == "background") + theme.background = color; + else if (key == "surface") + theme.surface = color; + else if (key == "error") + theme.error = color; + else if (key == "warning") + theme.warning = color; + else if (key == "success") + theme.success = color; + else if (key == "info") + theme.info = color; + else if (key == "text_primary") + theme.text_primary = color; + else if (key == "text_secondary") + theme.text_secondary = color; + else if (key == "text_disabled") + theme.text_disabled = color; + else if (key == "window_bg") + theme.window_bg = color; + else if (key == "child_bg") + theme.child_bg = color; + else if (key == "popup_bg") + theme.popup_bg = color; + else if (key == "button") + theme.button = color; + else if (key == "button_hovered") + theme.button_hovered = color; + else if (key == "button_active") + theme.button_active = color; + else if (key == "frame_bg") + theme.frame_bg = color; + else if (key == "frame_bg_hovered") + theme.frame_bg_hovered = color; + else if (key == "frame_bg_active") + theme.frame_bg_active = color; + else if (key == "header") + theme.header = color; + else if (key == "header_hovered") + theme.header_hovered = color; + else if (key == "header_active") + theme.header_active = color; + else if (key == "tab") + theme.tab = color; + else if (key == "tab_hovered") + theme.tab_hovered = color; + else if (key == "tab_active") + theme.tab_active = color; + else if (key == "menu_bar_bg") + theme.menu_bar_bg = color; + else if (key == "title_bg") + theme.title_bg = color; + else if (key == "title_bg_active") + theme.title_bg_active = color; + else if (key == "title_bg_collapsed") + theme.title_bg_collapsed = color; + else if (key == "separator") + theme.separator = color; + else if (key == "separator_hovered") + theme.separator_hovered = color; + else if (key == "separator_active") + theme.separator_active = color; + else if (key == "scrollbar_bg") + theme.scrollbar_bg = color; + else if (key == "scrollbar_grab") + theme.scrollbar_grab = color; + else if (key == "scrollbar_grab_hovered") + theme.scrollbar_grab_hovered = color; + else if (key == "scrollbar_grab_active") + theme.scrollbar_grab_active = color; + else if (key == "border") + theme.border = color; + else if (key == "border_shadow") + theme.border_shadow = color; + else if (key == "resize_grip") + theme.resize_grip = color; + else if (key == "resize_grip_hovered") + theme.resize_grip_hovered = color; + else if (key == "resize_grip_active") + theme.resize_grip_active = color; + else if (key == "check_mark") + theme.check_mark = color; + else if (key == "slider_grab") + theme.slider_grab = color; + else if (key == "slider_grab_active") + theme.slider_grab_active = color; + else if (key == "input_text_cursor") + theme.input_text_cursor = color; + else if (key == "nav_cursor") + theme.nav_cursor = color; + else if (key == "nav_windowing_highlight") + theme.nav_windowing_highlight = color; + else if (key == "nav_windowing_dim_bg") + theme.nav_windowing_dim_bg = color; + else if (key == "modal_window_dim_bg") + theme.modal_window_dim_bg = color; + else if (key == "text_selected_bg") + theme.text_selected_bg = color; + else if (key == "drag_drop_target") + theme.drag_drop_target = color; + else if (key == "table_header_bg") + theme.table_header_bg = color; + else if (key == "table_border_strong") + theme.table_border_strong = color; + else if (key == "table_border_light") + theme.table_border_light = color; + else if (key == "table_row_bg") + theme.table_row_bg = color; + else if (key == "table_row_bg_alt") + theme.table_row_bg_alt = color; + else if (key == "text_link") + theme.text_link = color; + else if (key == "plot_lines") + theme.plot_lines = color; + else if (key == "plot_lines_hovered") + theme.plot_lines_hovered = color; + else if (key == "plot_histogram") + theme.plot_histogram = color; + else if (key == "plot_histogram_hovered") + theme.plot_histogram_hovered = color; + else if (key == "tree_lines") + theme.tree_lines = color; + else if (key == "tab_dimmed") + theme.tab_dimmed = color; + else if (key == "tab_dimmed_selected") + theme.tab_dimmed_selected = color; + else if (key == "tab_dimmed_selected_overline") + theme.tab_dimmed_selected_overline = color; + else if (key == "tab_selected_overline") + theme.tab_selected_overline = color; + else if (key == "docking_preview") + theme.docking_preview = color; + else if (key == "docking_empty_bg") + theme.docking_empty_bg = color; + } else if (current_section == "style") { + if (key == "window_rounding") + theme.window_rounding = std::stof(value); + else if (key == "frame_rounding") + theme.frame_rounding = std::stof(value); + else if (key == "scrollbar_rounding") + theme.scrollbar_rounding = std::stof(value); + else if (key == "grab_rounding") + theme.grab_rounding = std::stof(value); + else if (key == "tab_rounding") + theme.tab_rounding = std::stof(value); + else if (key == "window_border_size") + theme.window_border_size = std::stof(value); + else if (key == "frame_border_size") + theme.frame_border_size = std::stof(value); + else if (key == "enable_animations") + theme.enable_animations = (value == "true"); + else if (key == "enable_glow_effects") + theme.enable_glow_effects = (value == "true"); + else if (key == "animation_speed") + theme.animation_speed = std::stof(value); + } else if (current_section == "" || current_section == "metadata") { // Top-level metadata - if (key == "name") theme.name = value; - else if (key == "description") theme.description = value; - else if (key == "author") theme.author = value; + if (key == "name") + theme.name = value; + else if (key == "description") + theme.description = value; + else if (key == "author") + theme.author = value; } } - + return absl::OkStatus(); } Color ThemeManager::ParseColorFromString(const std::string& color_str) const { std::vector components = absl::StrSplit(color_str, ','); if (components.size() != 4) { - return RGBA(255, 255, 255, 255); // White fallback + return RGBA(255, 255, 255, 255); // White fallback } - + try { int r = std::stoi(components[0]); int g = std::stoi(components[1]); @@ -646,22 +783,23 @@ Color ThemeManager::ParseColorFromString(const std::string& color_str) const { int a = std::stoi(components[3]); return RGBA(r, g, b, a); } catch (...) { - return RGBA(255, 255, 255, 255); // White fallback + return RGBA(255, 255, 255, 255); // White fallback } } std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { std::ostringstream ss; - + // Helper function to convert color to RGB string auto colorToString = [](const Color& c) -> std::string { int r = static_cast(c.red * 255.0f); int g = static_cast(c.green * 255.0f); int b = static_cast(c.blue * 255.0f); int a = static_cast(c.alpha * 255.0f); - return std::to_string(r) + "," + std::to_string(g) + "," + std::to_string(b) + "," + std::to_string(a); + return std::to_string(r) + "," + std::to_string(g) + "," + + std::to_string(b) + "," + std::to_string(a); }; - + ss << "# yaze Theme File\n"; ss << "# Generated by YAZE Theme Editor\n"; ss << "name=" << theme.name << "\n"; @@ -669,7 +807,7 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "author=" << theme.author << "\n"; ss << "version=1.0\n"; ss << "\n[colors]\n"; - + // Primary colors ss << "# Primary colors\n"; ss << "primary=" << colorToString(theme.primary) << "\n"; @@ -678,7 +816,7 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "background=" << colorToString(theme.background) << "\n"; ss << "surface=" << colorToString(theme.surface) << "\n"; ss << "\n"; - + // Status colors ss << "# Status colors\n"; ss << "error=" << colorToString(theme.error) << "\n"; @@ -686,21 +824,21 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "success=" << colorToString(theme.success) << "\n"; ss << "info=" << colorToString(theme.info) << "\n"; ss << "\n"; - + // Text colors ss << "# Text colors\n"; ss << "text_primary=" << colorToString(theme.text_primary) << "\n"; ss << "text_secondary=" << colorToString(theme.text_secondary) << "\n"; ss << "text_disabled=" << colorToString(theme.text_disabled) << "\n"; ss << "\n"; - + // Window colors ss << "# Window colors\n"; ss << "window_bg=" << colorToString(theme.window_bg) << "\n"; ss << "child_bg=" << colorToString(theme.child_bg) << "\n"; ss << "popup_bg=" << colorToString(theme.popup_bg) << "\n"; ss << "\n"; - + // Interactive elements ss << "# Interactive elements\n"; ss << "button=" << colorToString(theme.button) << "\n"; @@ -710,7 +848,7 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "frame_bg_hovered=" << colorToString(theme.frame_bg_hovered) << "\n"; ss << "frame_bg_active=" << colorToString(theme.frame_bg_active) << "\n"; ss << "\n"; - + // Navigation ss << "# Navigation\n"; ss << "header=" << colorToString(theme.header) << "\n"; @@ -722,9 +860,10 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "menu_bar_bg=" << colorToString(theme.menu_bar_bg) << "\n"; ss << "title_bg=" << colorToString(theme.title_bg) << "\n"; ss << "title_bg_active=" << colorToString(theme.title_bg_active) << "\n"; - ss << "title_bg_collapsed=" << colorToString(theme.title_bg_collapsed) << "\n"; + ss << "title_bg_collapsed=" << colorToString(theme.title_bg_collapsed) + << "\n"; ss << "\n"; - + // Borders and separators ss << "# Borders and separators\n"; ss << "border=" << colorToString(theme.border) << "\n"; @@ -733,61 +872,76 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "separator_hovered=" << colorToString(theme.separator_hovered) << "\n"; ss << "separator_active=" << colorToString(theme.separator_active) << "\n"; ss << "\n"; - + // Scrollbars and controls ss << "# Scrollbars and controls\n"; ss << "scrollbar_bg=" << colorToString(theme.scrollbar_bg) << "\n"; ss << "scrollbar_grab=" << colorToString(theme.scrollbar_grab) << "\n"; - ss << "scrollbar_grab_hovered=" << colorToString(theme.scrollbar_grab_hovered) << "\n"; - ss << "scrollbar_grab_active=" << colorToString(theme.scrollbar_grab_active) << "\n"; + ss << "scrollbar_grab_hovered=" << colorToString(theme.scrollbar_grab_hovered) + << "\n"; + ss << "scrollbar_grab_active=" << colorToString(theme.scrollbar_grab_active) + << "\n"; ss << "resize_grip=" << colorToString(theme.resize_grip) << "\n"; - ss << "resize_grip_hovered=" << colorToString(theme.resize_grip_hovered) << "\n"; - ss << "resize_grip_active=" << colorToString(theme.resize_grip_active) << "\n"; + ss << "resize_grip_hovered=" << colorToString(theme.resize_grip_hovered) + << "\n"; + ss << "resize_grip_active=" << colorToString(theme.resize_grip_active) + << "\n"; ss << "check_mark=" << colorToString(theme.check_mark) << "\n"; ss << "slider_grab=" << colorToString(theme.slider_grab) << "\n"; - ss << "slider_grab_active=" << colorToString(theme.slider_grab_active) << "\n"; + ss << "slider_grab_active=" << colorToString(theme.slider_grab_active) + << "\n"; ss << "\n"; - + // Navigation and special elements ss << "# Navigation and special elements\n"; ss << "input_text_cursor=" << colorToString(theme.input_text_cursor) << "\n"; ss << "nav_cursor=" << colorToString(theme.nav_cursor) << "\n"; - ss << "nav_windowing_highlight=" << colorToString(theme.nav_windowing_highlight) << "\n"; - ss << "nav_windowing_dim_bg=" << colorToString(theme.nav_windowing_dim_bg) << "\n"; - ss << "modal_window_dim_bg=" << colorToString(theme.modal_window_dim_bg) << "\n"; + ss << "nav_windowing_highlight=" + << colorToString(theme.nav_windowing_highlight) << "\n"; + ss << "nav_windowing_dim_bg=" << colorToString(theme.nav_windowing_dim_bg) + << "\n"; + ss << "modal_window_dim_bg=" << colorToString(theme.modal_window_dim_bg) + << "\n"; ss << "text_selected_bg=" << colorToString(theme.text_selected_bg) << "\n"; ss << "drag_drop_target=" << colorToString(theme.drag_drop_target) << "\n"; ss << "docking_preview=" << colorToString(theme.docking_preview) << "\n"; ss << "docking_empty_bg=" << colorToString(theme.docking_empty_bg) << "\n"; ss << "\n"; - + // Table colors ss << "# Table colors\n"; ss << "table_header_bg=" << colorToString(theme.table_header_bg) << "\n"; - ss << "table_border_strong=" << colorToString(theme.table_border_strong) << "\n"; - ss << "table_border_light=" << colorToString(theme.table_border_light) << "\n"; + ss << "table_border_strong=" << colorToString(theme.table_border_strong) + << "\n"; + ss << "table_border_light=" << colorToString(theme.table_border_light) + << "\n"; ss << "table_row_bg=" << colorToString(theme.table_row_bg) << "\n"; ss << "table_row_bg_alt=" << colorToString(theme.table_row_bg_alt) << "\n"; ss << "\n"; - + // Links and plots ss << "# Links and plots\n"; ss << "text_link=" << colorToString(theme.text_link) << "\n"; ss << "plot_lines=" << colorToString(theme.plot_lines) << "\n"; - ss << "plot_lines_hovered=" << colorToString(theme.plot_lines_hovered) << "\n"; + ss << "plot_lines_hovered=" << colorToString(theme.plot_lines_hovered) + << "\n"; ss << "plot_histogram=" << colorToString(theme.plot_histogram) << "\n"; - ss << "plot_histogram_hovered=" << colorToString(theme.plot_histogram_hovered) << "\n"; + ss << "plot_histogram_hovered=" << colorToString(theme.plot_histogram_hovered) + << "\n"; ss << "tree_lines=" << colorToString(theme.tree_lines) << "\n"; ss << "\n"; - + // Tab variations ss << "# Tab variations\n"; ss << "tab_dimmed=" << colorToString(theme.tab_dimmed) << "\n"; - ss << "tab_dimmed_selected=" << colorToString(theme.tab_dimmed_selected) << "\n"; - ss << "tab_dimmed_selected_overline=" << colorToString(theme.tab_dimmed_selected_overline) << "\n"; - ss << "tab_selected_overline=" << colorToString(theme.tab_selected_overline) << "\n"; + ss << "tab_dimmed_selected=" << colorToString(theme.tab_dimmed_selected) + << "\n"; + ss << "tab_dimmed_selected_overline=" + << colorToString(theme.tab_dimmed_selected_overline) << "\n"; + ss << "tab_selected_overline=" << colorToString(theme.tab_selected_overline) + << "\n"; ss << "\n"; - + // Enhanced semantic colors ss << "# Enhanced semantic colors\n"; ss << "text_highlight=" << colorToString(theme.text_highlight) << "\n"; @@ -798,7 +952,7 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "error_light=" << colorToString(theme.error_light) << "\n"; ss << "info_light=" << colorToString(theme.info_light) << "\n"; ss << "\n"; - + // UI state colors ss << "# UI state colors\n"; ss << "active_selection=" << colorToString(theme.active_selection) << "\n"; @@ -806,7 +960,7 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "focus_border=" << colorToString(theme.focus_border) << "\n"; ss << "disabled_overlay=" << colorToString(theme.disabled_overlay) << "\n"; ss << "\n"; - + // Editor-specific colors ss << "# Editor-specific colors\n"; ss << "editor_background=" << colorToString(theme.editor_background) << "\n"; @@ -814,34 +968,39 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { ss << "editor_cursor=" << colorToString(theme.editor_cursor) << "\n"; ss << "editor_selection=" << colorToString(theme.editor_selection) << "\n"; ss << "\n"; - + // Style settings ss << "[style]\n"; ss << "window_rounding=" << theme.window_rounding << "\n"; ss << "frame_rounding=" << theme.frame_rounding << "\n"; ss << "scrollbar_rounding=" << theme.scrollbar_rounding << "\n"; ss << "tab_rounding=" << theme.tab_rounding << "\n"; - ss << "enable_animations=" << (theme.enable_animations ? "true" : "false") << "\n"; - ss << "enable_glow_effects=" << (theme.enable_glow_effects ? "true" : "false") << "\n"; - + ss << "enable_animations=" << (theme.enable_animations ? "true" : "false") + << "\n"; + ss << "enable_glow_effects=" << (theme.enable_glow_effects ? "true" : "false") + << "\n"; + return ss.str(); } -absl::Status ThemeManager::SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const { +absl::Status ThemeManager::SaveThemeToFile(const EnhancedTheme& theme, + const std::string& filepath) const { std::string theme_content = SerializeTheme(theme); - + std::ofstream file(filepath); if (!file.is_open()) { - return absl::InternalError(absl::StrFormat("Failed to open file for writing: %s", filepath)); + return absl::InternalError( + absl::StrFormat("Failed to open file for writing: %s", filepath)); } - + file << theme_content; file.close(); - + if (file.fail()) { - return absl::InternalError(absl::StrFormat("Failed to write theme file: %s", filepath)); + return absl::InternalError( + absl::StrFormat("Failed to write theme file: %s", filepath)); } - + return absl::OkStatus(); } @@ -849,91 +1008,111 @@ void ThemeManager::ApplyClassicYazeTheme() { // Apply the original ColorsYaze() function directly ColorsYaze(); current_theme_name_ = "Classic YAZE"; - + // Create a complete Classic theme object that matches what ColorsYaze() sets EnhancedTheme classic_theme; classic_theme.name = "Classic YAZE"; - classic_theme.description = "Original YAZE theme (direct ColorsYaze() function)"; + classic_theme.description = + "Original YAZE theme (direct ColorsYaze() function)"; classic_theme.author = "YAZE Team"; - + // Extract ALL the colors that ColorsYaze() sets (copy from CreateFallbackYazeClassic) - classic_theme.primary = RGBA(92, 115, 92); // allttpLightGreen - classic_theme.secondary = RGBA(71, 92, 71); // alttpMidGreen - classic_theme.accent = RGBA(89, 119, 89); // TabActive - classic_theme.background = RGBA(8, 8, 8); // Very dark gray for better grid visibility - + classic_theme.primary = RGBA(92, 115, 92); // allttpLightGreen + classic_theme.secondary = RGBA(71, 92, 71); // alttpMidGreen + classic_theme.accent = RGBA(89, 119, 89); // TabActive + classic_theme.background = + RGBA(8, 8, 8); // Very dark gray for better grid visibility + classic_theme.text_primary = RGBA(230, 230, 230); // 0.90f, 0.90f, 0.90f classic_theme.text_disabled = RGBA(153, 153, 153); // 0.60f, 0.60f, 0.60f - classic_theme.window_bg = RGBA(8, 8, 8, 217); // Very dark gray with same alpha - classic_theme.child_bg = RGBA(0, 0, 0, 0); // Transparent - classic_theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f - - classic_theme.button = RGBA(71, 92, 71); // alttpMidGreen - classic_theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen - classic_theme.button_active = RGBA(92, 115, 92); // allttpLightGreen - - classic_theme.header = RGBA(46, 66, 46); // alttpDarkGreen - classic_theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen - classic_theme.header_active = RGBA(71, 92, 71); // alttpMidGreen - - classic_theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen - 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 - + classic_theme.window_bg = + RGBA(8, 8, 8, 217); // Very dark gray with same alpha + classic_theme.child_bg = RGBA(0, 0, 0, 0); // Transparent + classic_theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f + + classic_theme.button = RGBA(71, 92, 71); // alttpMidGreen + classic_theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen + classic_theme.button_active = RGBA(92, 115, 92); // allttpLightGreen + + classic_theme.header = RGBA(46, 66, 46); // alttpDarkGreen + classic_theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen + classic_theme.header_active = RGBA(71, 92, 71); // alttpMidGreen + + classic_theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen + 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 - classic_theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen - classic_theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen - + classic_theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen + classic_theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen + classic_theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen + // Borders and separators - classic_theme.border = RGBA(92, 115, 92); // allttpLightGreen - classic_theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent - classic_theme.separator = RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f - classic_theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f - classic_theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f - + classic_theme.border = RGBA(92, 115, 92); // allttpLightGreen + classic_theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent + classic_theme.separator = + RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f + classic_theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f + classic_theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f + // Scrollbars - classic_theme.scrollbar_bg = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f - classic_theme.scrollbar_grab = RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f - classic_theme.scrollbar_grab_hovered = RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f - classic_theme.scrollbar_grab_active = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f - + classic_theme.scrollbar_bg = + RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f + classic_theme.scrollbar_grab = + RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f + classic_theme.scrollbar_grab_hovered = + RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f + classic_theme.scrollbar_grab_active = + RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f + // ENHANCED: Frame colors for inputs/widgets - classic_theme.frame_bg = RGBA(46, 66, 46, 140); // Darker green with some transparency - classic_theme.frame_bg_hovered = RGBA(71, 92, 71, 170); // Mid green when hovered - classic_theme.frame_bg_active = RGBA(92, 115, 92, 200); // Light green when active - + classic_theme.frame_bg = + RGBA(46, 66, 46, 140); // Darker green with some transparency + classic_theme.frame_bg_hovered = + RGBA(71, 92, 71, 170); // Mid green when hovered + classic_theme.frame_bg_active = + RGBA(92, 115, 92, 200); // Light green when active + // FIXED: Resize grips with better visibility - classic_theme.resize_grip = RGBA(92, 115, 92, 80); // Theme green, subtle - classic_theme.resize_grip_hovered = RGBA(125, 146, 125, 180); // Brighter when hovered - classic_theme.resize_grip_active = RGBA(125, 146, 125, 255); // Solid when active - + classic_theme.resize_grip = RGBA(92, 115, 92, 80); // Theme green, subtle + classic_theme.resize_grip_hovered = + RGBA(125, 146, 125, 180); // Brighter when hovered + classic_theme.resize_grip_active = + RGBA(125, 146, 125, 255); // Solid when active + // FIXED: Checkmark - bright green for high visibility! - classic_theme.check_mark = RGBA(125, 255, 125, 255); // Bright green (clearly visible) - + classic_theme.check_mark = + RGBA(125, 255, 125, 255); // Bright green (clearly visible) + // FIXED: Sliders with theme colors - classic_theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid) - classic_theme.slider_grab_active = RGBA(125, 146, 125, 255); // Lighter when grabbed - + classic_theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid) + classic_theme.slider_grab_active = + RGBA(125, 146, 125, 255); // Lighter when grabbed + // FIXED: Input cursor - white for maximum visibility - classic_theme.input_text_cursor = RGBA(255, 255, 255, 255); // White cursor (always visible) - + classic_theme.input_text_cursor = + RGBA(255, 255, 255, 255); // White cursor (always visible) + // FIXED: Navigation with theme colors - classic_theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green navigation - classic_theme.nav_windowing_highlight = RGBA(92, 115, 92, 200); // Theme green highlight - classic_theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay - + classic_theme.nav_cursor = + RGBA(125, 146, 125, 255); // Light green navigation + classic_theme.nav_windowing_highlight = + RGBA(92, 115, 92, 200); // Theme green highlight + classic_theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay + // FIXED: Modals with better dimming - classic_theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha - + classic_theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha + // FIXED: Text selection - visible and theme-appropriate! - classic_theme.text_selected_bg = RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible!) - + classic_theme.text_selected_bg = + RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible!) + // FIXED: Drag/drop target with high visibility - classic_theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green + classic_theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green classic_theme.table_header_bg = RGBA(46, 66, 46); classic_theme.table_border_strong = RGBA(71, 92, 71); classic_theme.table_border_light = RGBA(66, 66, 71); @@ -946,120 +1125,127 @@ void ThemeManager::ApplyClassicYazeTheme() { classic_theme.plot_histogram_hovered = RGBA(255, 153, 0); classic_theme.docking_preview = RGBA(92, 115, 92, 180); classic_theme.docking_empty_bg = RGBA(46, 66, 46, 255); - classic_theme.tree_lines = classic_theme.separator; // Use separator color for tree lines - + classic_theme.tree_lines = + classic_theme.separator; // Use separator color for tree lines + // Tab dimmed colors (for unfocused tabs) - classic_theme.tab_dimmed = RGBA(37, 52, 37); // Darker version of tab - classic_theme.tab_dimmed_selected = RGBA(62, 83, 62); // Darker version of tab_active + classic_theme.tab_dimmed = RGBA(37, 52, 37); // Darker version of tab + classic_theme.tab_dimmed_selected = + RGBA(62, 83, 62); // Darker version of tab_active classic_theme.tab_dimmed_selected_overline = classic_theme.accent; classic_theme.tab_selected_overline = classic_theme.accent; - + // Enhanced semantic colors for better theming - classic_theme.text_highlight = RGBA(255, 255, 150); // Light yellow for highlights - classic_theme.link_hover = RGBA(140, 220, 255); // Brighter blue for link hover - classic_theme.code_background = RGBA(40, 60, 40); // Slightly darker green for code - classic_theme.success_light = RGBA(140, 195, 140); // Light green - classic_theme.warning_light = RGBA(255, 220, 100); // Light yellow - classic_theme.error_light = RGBA(255, 150, 150); // Light red - classic_theme.info_light = RGBA(150, 200, 255); // Light blue - + classic_theme.text_highlight = + RGBA(255, 255, 150); // Light yellow for highlights + classic_theme.link_hover = + RGBA(140, 220, 255); // Brighter blue for link hover + classic_theme.code_background = + RGBA(40, 60, 40); // Slightly darker green for code + classic_theme.success_light = RGBA(140, 195, 140); // Light green + classic_theme.warning_light = RGBA(255, 220, 100); // Light yellow + classic_theme.error_light = RGBA(255, 150, 150); // Light red + classic_theme.info_light = RGBA(150, 200, 255); // Light blue + // UI state colors - classic_theme.active_selection = classic_theme.accent; // Use accent color for active selection - classic_theme.hover_highlight = RGBA(92, 115, 92, 100); // Semi-transparent green - classic_theme.focus_border = classic_theme.primary; // Use primary for focus + classic_theme.active_selection = + classic_theme.accent; // Use accent color for active selection + classic_theme.hover_highlight = + RGBA(92, 115, 92, 100); // Semi-transparent green + classic_theme.focus_border = classic_theme.primary; // Use primary for focus classic_theme.disabled_overlay = RGBA(50, 50, 50, 128); // Gray overlay - + // Editor-specific colors - classic_theme.editor_background = RGBA(30, 45, 30); // Dark green background - classic_theme.editor_grid = RGBA(80, 100, 80, 100); // Subtle grid lines - classic_theme.editor_cursor = RGBA(255, 255, 255); // White cursor - classic_theme.editor_selection = RGBA(110, 145, 110, 100); // Semi-transparent selection - + classic_theme.editor_background = RGBA(30, 45, 30); // Dark green background + classic_theme.editor_grid = RGBA(80, 100, 80, 100); // Subtle grid lines + classic_theme.editor_cursor = RGBA(255, 255, 255); // White cursor + classic_theme.editor_selection = + RGBA(110, 145, 110, 100); // Semi-transparent selection + // Apply original style settings classic_theme.window_rounding = 0.0f; classic_theme.frame_rounding = 5.0f; classic_theme.scrollbar_rounding = 5.0f; classic_theme.tab_rounding = 0.0f; classic_theme.enable_glow_effects = false; - + // DON'T add Classic theme to themes map - keep it as a special case // themes_["Classic YAZE"] = classic_theme; // REMOVED to prevent off-by-one current_theme_ = classic_theme; } void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { - if (!p_open || !*p_open) return; - + if (!p_open || !*p_open) + return; + ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - - if (ImGui::Begin(absl::StrFormat("%s Theme Editor", ICON_MD_PALETTE).c_str(), p_open, - ImGuiWindowFlags_MenuBar)) { - + + if (ImGui::Begin(absl::StrFormat("%s Theme Editor", ICON_MD_PALETTE).c_str(), + p_open, ImGuiWindowFlags_MenuBar)) { + // Add gentle particle effects to theme editor background static float editor_animation_time = 0.0f; editor_animation_time += ImGui::GetIO().DeltaTime; - + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_size = ImGui::GetWindowSize(); - + // Floating color orbs representing different color categories auto current_theme = GetCurrentTheme(); std::vector theme_colors = { - current_theme.primary, current_theme.secondary, current_theme.accent, - current_theme.success, current_theme.warning, current_theme.error - }; - + current_theme.primary, current_theme.secondary, current_theme.accent, + current_theme.success, current_theme.warning, current_theme.error}; + for (size_t i = 0; i < theme_colors.size(); ++i) { float time_offset = i * 1.0f; float orbit_radius = 60.0f + i * 8.0f; - float x = window_pos.x + window_size.x * 0.8f + cosf(editor_animation_time * 0.3f + time_offset) * orbit_radius; - float y = window_pos.y + window_size.y * 0.3f + sinf(editor_animation_time * 0.3f + time_offset) * orbit_radius; - - float alpha = 0.15f + 0.1f * sinf(editor_animation_time * 1.5f + time_offset); - ImU32 orb_color = ImGui::ColorConvertFloat4ToU32(ImVec4( - theme_colors[i].red, theme_colors[i].green, theme_colors[i].blue, alpha)); - - float radius = 4.0f + sinf(editor_animation_time * 2.0f + time_offset) * 1.0f; + float x = window_pos.x + window_size.x * 0.8f + + cosf(editor_animation_time * 0.3f + time_offset) * orbit_radius; + float y = window_pos.y + window_size.y * 0.3f + + sinf(editor_animation_time * 0.3f + time_offset) * orbit_radius; + + float alpha = + 0.15f + 0.1f * sinf(editor_animation_time * 1.5f + time_offset); + ImU32 orb_color = ImGui::ColorConvertFloat4ToU32( + ImVec4(theme_colors[i].red, theme_colors[i].green, + theme_colors[i].blue, alpha)); + + float radius = + 4.0f + sinf(editor_animation_time * 2.0f + time_offset) * 1.0f; draw_list->AddCircleFilled(ImVec2(x, y), radius, orb_color); } - + // Menu bar for theme operations if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem(absl::StrFormat("%s New Theme", ICON_MD_ADD).c_str())) { + if (ImGui::MenuItem( + absl::StrFormat("%s New Theme", ICON_MD_ADD).c_str())) { // Reset to default theme ApplyClassicYazeTheme(); } - if (ImGui::MenuItem(absl::StrFormat("%s Load Theme", ICON_MD_FOLDER_OPEN).c_str())) { + if (ImGui::MenuItem( + absl::StrFormat("%s Load Theme", ICON_MD_FOLDER_OPEN) + .c_str())) { auto file_path = util::FileDialogWrapper::ShowOpenFileDialog(); if (!file_path.empty()) { LoadThemeFromFile(file_path); } } - ImGui::Separator(); - if (ImGui::MenuItem(absl::StrFormat("%s Save Theme", ICON_MD_SAVE).c_str())) { - // Save current theme to its existing file - std::string current_file_path = GetCurrentThemeFilePath(); - if (!current_file_path.empty()) { - auto status = SaveThemeToFile(current_theme_, current_file_path); - if (!status.ok()) { - LOG_ERROR("Theme Manager", "Failed to save theme"); - } - } else { - // No existing file, prompt for new location - auto file_path = util::FileDialogWrapper::ShowSaveFileDialog(current_theme_.name, "theme"); - if (!file_path.empty()) { - auto status = SaveThemeToFile(current_theme_, file_path); - if (!status.ok()) { - LOG_ERROR("Theme Manager", "Failed to save theme"); - } - } + ImGui::Separator(); + if (ImGui::MenuItem( + absl::StrFormat("%s Save Theme", ICON_MD_SAVE).c_str())) { + // Save current theme to its existing file + std::string current_file_path = GetCurrentThemeFilePath(); + if (!current_file_path.empty()) { + auto status = SaveThemeToFile(current_theme_, current_file_path); + if (!status.ok()) { + LOG_ERROR("Theme Manager", "Failed to save theme"); } - } - if (ImGui::MenuItem(absl::StrFormat("%s Save As...", ICON_MD_SAVE_AS).c_str())) { - // Save theme to new file - auto file_path = util::FileDialogWrapper::ShowSaveFileDialog(current_theme_.name, "theme"); + } else { + // No existing file, prompt for new location + auto file_path = util::FileDialogWrapper::ShowSaveFileDialog( + current_theme_.name, "theme"); if (!file_path.empty()) { auto status = SaveThemeToFile(current_theme_, file_path); if (!status.ok()) { @@ -1067,14 +1253,27 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { } } } + } + if (ImGui::MenuItem( + absl::StrFormat("%s Save As...", ICON_MD_SAVE_AS).c_str())) { + // Save theme to new file + auto file_path = util::FileDialogWrapper::ShowSaveFileDialog( + current_theme_.name, "theme"); + if (!file_path.empty()) { + auto status = SaveThemeToFile(current_theme_, file_path); + if (!status.ok()) { + LOG_ERROR("Theme Manager", "Failed to save theme"); + } + } + } ImGui::EndMenu(); } - + if (ImGui::BeginMenu("Presets")) { if (ImGui::MenuItem("YAZE Classic")) { ApplyClassicYazeTheme(); } - + auto available_themes = GetAvailableThemes(); if (!available_themes.empty()) { ImGui::Separator(); @@ -1086,18 +1285,19 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { } ImGui::EndMenu(); } - + ImGui::EndMenuBar(); } - + static EnhancedTheme edit_theme = current_theme_; static char theme_name[128]; static char theme_description[256]; static char theme_author[128]; static bool live_preview = true; - static EnhancedTheme original_theme; // Store original theme for restoration + static EnhancedTheme + original_theme; // Store original theme for restoration static bool theme_backup_made = false; - + // Helper lambda for live preview application auto apply_live_preview = [&]() { if (live_preview) { @@ -1109,12 +1309,12 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { edit_theme.ApplyToImGui(); } }; - + // Live preview toggle ImGui::Checkbox("Live Preview", &live_preview); ImGui::SameLine(); ImGui::Text("| Changes apply immediately when enabled"); - + // If live preview was just disabled, restore original theme static bool prev_live_preview = live_preview; if (prev_live_preview && !live_preview && theme_backup_made) { @@ -1122,14 +1322,16 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { theme_backup_made = false; } prev_live_preview = live_preview; - + ImGui::Separator(); - + // Theme metadata in a table for better layout - if (ImGui::BeginTable("ThemeMetadata", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, 100.0f); + if (ImGui::BeginTable("ThemeMetadata", 2, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, + 100.0f); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); - + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); @@ -1138,48 +1340,55 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { if (ImGui::InputText("##theme_name", theme_name, sizeof(theme_name))) { edit_theme.name = std::string(theme_name); } - + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("Description:"); ImGui::TableNextColumn(); - if (ImGui::InputText("##theme_description", theme_description, sizeof(theme_description))) { + if (ImGui::InputText("##theme_description", theme_description, + sizeof(theme_description))) { edit_theme.description = std::string(theme_description); } - + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("Author:"); ImGui::TableNextColumn(); - if (ImGui::InputText("##theme_author", theme_author, sizeof(theme_author))) { + if (ImGui::InputText("##theme_author", theme_author, + sizeof(theme_author))) { edit_theme.author = std::string(theme_author); } - + ImGui::EndTable(); } - + ImGui::Separator(); - + // Enhanced theme editing with tabs for better organization if (ImGui::BeginTabBar("ThemeEditorTabs", ImGuiTabBarFlags_None)) { - + // Apply live preview on first frame if enabled static bool first_frame = true; if (first_frame && live_preview) { apply_live_preview(); first_frame = false; } - + // Primary Colors Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Primary", ICON_MD_COLOR_LENS).c_str())) { - if (ImGui::BeginTable("PrimaryColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTabItem( + absl::StrFormat("%s Primary", ICON_MD_COLOR_LENS).c_str())) { + if (ImGui::BeginTable("PrimaryColorsTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, + 0.6f); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, + 0.4f); ImGui::TableHeadersRow(); - + // Primary color ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1188,12 +1397,12 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { ImGui::TableNextColumn(); ImVec4 primary = ConvertColorToImVec4(edit_theme.primary); if (ImGui::ColorEdit3("##primary", &primary.x)) { - edit_theme.primary = {primary.x, primary.y, primary.z, primary.w}; + edit_theme.primary = {primary.x, primary.y, primary.z, primary.w}; apply_live_preview(); } ImGui::TableNextColumn(); ImGui::Button("Primary Preview", ImVec2(-1, 30)); - + // Secondary color ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1202,14 +1411,15 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { ImGui::TableNextColumn(); ImVec4 secondary = ConvertColorToImVec4(edit_theme.secondary); if (ImGui::ColorEdit3("##secondary", &secondary.x)) { - edit_theme.secondary = {secondary.x, secondary.y, secondary.z, secondary.w}; + edit_theme.secondary = {secondary.x, secondary.y, secondary.z, + secondary.w}; apply_live_preview(); } ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Button, secondary); ImGui::Button("Secondary Preview", ImVec2(-1, 30)); ImGui::PopStyleColor(); - + // Accent color ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1218,14 +1428,14 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { ImGui::TableNextColumn(); ImVec4 accent = ConvertColorToImVec4(edit_theme.accent); if (ImGui::ColorEdit3("##accent", &accent.x)) { - edit_theme.accent = {accent.x, accent.y, accent.z, accent.w}; + edit_theme.accent = {accent.x, accent.y, accent.z, accent.w}; apply_live_preview(); } ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Button, accent); ImGui::Button("Accent Preview", ImVec2(-1, 30)); ImGui::PopStyleColor(); - + // Background color ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1234,43 +1444,48 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { ImGui::TableNextColumn(); ImVec4 background = ConvertColorToImVec4(edit_theme.background); if (ImGui::ColorEdit4("##background", &background.x)) { - edit_theme.background = {background.x, background.y, background.z, background.w}; + edit_theme.background = {background.x, background.y, background.z, + background.w}; apply_live_preview(); } ImGui::TableNextColumn(); ImGui::Text("Background preview shown in window"); - + ImGui::EndTable(); } ImGui::EndTabItem(); } - + // Text Colors Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Text", ICON_MD_TEXT_FIELDS).c_str())) { - if (ImGui::BeginTable("TextColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTabItem( + absl::StrFormat("%s Text", ICON_MD_TEXT_FIELDS).c_str())) { + if (ImGui::BeginTable("TextColorsTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, + 0.6f); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, + 0.4f); ImGui::TableHeadersRow(); - + // Text colors with live preview auto text_colors = std::vector>{ - {"Primary Text", &edit_theme.text_primary}, - {"Secondary Text", &edit_theme.text_secondary}, - {"Disabled Text", &edit_theme.text_disabled}, - {"Link Text", &edit_theme.text_link}, - {"Text Highlight", &edit_theme.text_highlight}, - {"Link Hover", &edit_theme.link_hover}, - {"Text Selected BG", &edit_theme.text_selected_bg}, - {"Input Text Cursor", &edit_theme.input_text_cursor} - }; - + {"Primary Text", &edit_theme.text_primary}, + {"Secondary Text", &edit_theme.text_secondary}, + {"Disabled Text", &edit_theme.text_disabled}, + {"Link Text", &edit_theme.text_link}, + {"Text Highlight", &edit_theme.text_highlight}, + {"Link Hover", &edit_theme.link_hover}, + {"Text Selected BG", &edit_theme.text_selected_bg}, + {"Input Text Cursor", &edit_theme.input_text_cursor}}; + for (auto& [label, color_ptr] : text_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##%s", label); @@ -1278,45 +1493,55 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text, color_vec); ImGui::Text("Sample %s", label); ImGui::PopStyleColor(); } - + ImGui::EndTable(); } ImGui::EndTabItem(); } - + // Interactive Elements Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Interactive", ICON_MD_TOUCH_APP).c_str())) { - if (ImGui::BeginTable("InteractiveColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTabItem( + absl::StrFormat("%s Interactive", ICON_MD_TOUCH_APP).c_str())) { + if (ImGui::BeginTable("InteractiveColorsTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, + 0.6f); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, + 0.4f); ImGui::TableHeadersRow(); - + // Interactive element colors - auto interactive_colors = std::vector>{ - {"Button", &edit_theme.button, ImGuiCol_Button}, - {"Button Hovered", &edit_theme.button_hovered, ImGuiCol_ButtonHovered}, - {"Button Active", &edit_theme.button_active, ImGuiCol_ButtonActive}, - {"Frame Background", &edit_theme.frame_bg, ImGuiCol_FrameBg}, - {"Frame BG Hovered", &edit_theme.frame_bg_hovered, ImGuiCol_FrameBgHovered}, - {"Frame BG Active", &edit_theme.frame_bg_active, ImGuiCol_FrameBgActive}, - {"Check Mark", &edit_theme.check_mark, ImGuiCol_CheckMark}, - {"Slider Grab", &edit_theme.slider_grab, ImGuiCol_SliderGrab}, - {"Slider Grab Active", &edit_theme.slider_grab_active, ImGuiCol_SliderGrabActive} - }; - + auto interactive_colors = + std::vector>{ + {"Button", &edit_theme.button, ImGuiCol_Button}, + {"Button Hovered", &edit_theme.button_hovered, + ImGuiCol_ButtonHovered}, + {"Button Active", &edit_theme.button_active, + ImGuiCol_ButtonActive}, + {"Frame Background", &edit_theme.frame_bg, ImGuiCol_FrameBg}, + {"Frame BG Hovered", &edit_theme.frame_bg_hovered, + ImGuiCol_FrameBgHovered}, + {"Frame BG Active", &edit_theme.frame_bg_active, + ImGuiCol_FrameBgActive}, + {"Check Mark", &edit_theme.check_mark, ImGuiCol_CheckMark}, + {"Slider Grab", &edit_theme.slider_grab, ImGuiCol_SliderGrab}, + {"Slider Grab Active", &edit_theme.slider_grab_active, + ImGuiCol_SliderGrabActive}}; + for (auto& [label, color_ptr, imgui_col] : interactive_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##%s", label); @@ -1324,89 +1549,120 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::PushStyleColor(imgui_col, color_vec); - ImGui::Button(absl::StrFormat("Preview %s", label).c_str(), ImVec2(-1, 30)); - ImGui::PopStyleColor(); - } - + ImGui::Button(absl::StrFormat("Preview %s", label).c_str(), + ImVec2(-1, 30)); + ImGui::PopStyleColor(); + } + ImGui::EndTable(); } ImGui::EndTabItem(); } - + // Style Parameters Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Style", ICON_MD_TUNE).c_str())) { + if (ImGui::BeginTabItem( + absl::StrFormat("%s Style", ICON_MD_TUNE).c_str())) { ImGui::Text("Rounding and Border Settings:"); - - if (ImGui::SliderFloat("Window Rounding", &edit_theme.window_rounding, 0.0f, 20.0f)) { - if (live_preview) ApplyTheme(edit_theme); + + if (ImGui::SliderFloat("Window Rounding", &edit_theme.window_rounding, + 0.0f, 20.0f)) { + if (live_preview) + ApplyTheme(edit_theme); } - if (ImGui::SliderFloat("Frame Rounding", &edit_theme.frame_rounding, 0.0f, 20.0f)) { - if (live_preview) ApplyTheme(edit_theme); + if (ImGui::SliderFloat("Frame Rounding", &edit_theme.frame_rounding, + 0.0f, 20.0f)) { + if (live_preview) + ApplyTheme(edit_theme); } - if (ImGui::SliderFloat("Scrollbar Rounding", &edit_theme.scrollbar_rounding, 0.0f, 20.0f)) { - if (live_preview) ApplyTheme(edit_theme); + if (ImGui::SliderFloat("Scrollbar Rounding", + &edit_theme.scrollbar_rounding, 0.0f, 20.0f)) { + if (live_preview) + ApplyTheme(edit_theme); } - if (ImGui::SliderFloat("Tab Rounding", &edit_theme.tab_rounding, 0.0f, 20.0f)) { - if (live_preview) ApplyTheme(edit_theme); + if (ImGui::SliderFloat("Tab Rounding", &edit_theme.tab_rounding, 0.0f, + 20.0f)) { + if (live_preview) + ApplyTheme(edit_theme); } - if (ImGui::SliderFloat("Grab Rounding", &edit_theme.grab_rounding, 0.0f, 20.0f)) { - if (live_preview) ApplyTheme(edit_theme); + if (ImGui::SliderFloat("Grab Rounding", &edit_theme.grab_rounding, 0.0f, + 20.0f)) { + if (live_preview) + ApplyTheme(edit_theme); } - + ImGui::Separator(); ImGui::Text("Border Sizes:"); - - if (ImGui::SliderFloat("Window Border Size", &edit_theme.window_border_size, 0.0f, 3.0f)) { - if (live_preview) ApplyTheme(edit_theme); + + if (ImGui::SliderFloat("Window Border Size", + &edit_theme.window_border_size, 0.0f, 3.0f)) { + if (live_preview) + ApplyTheme(edit_theme); } - if (ImGui::SliderFloat("Frame Border Size", &edit_theme.frame_border_size, 0.0f, 3.0f)) { - if (live_preview) ApplyTheme(edit_theme); + if (ImGui::SliderFloat("Frame Border Size", + &edit_theme.frame_border_size, 0.0f, 3.0f)) { + if (live_preview) + ApplyTheme(edit_theme); } - + ImGui::Separator(); ImGui::Text("Animation & Effects:"); - - if (ImGui::Checkbox("Enable Animations", &edit_theme.enable_animations)) { - if (live_preview) ApplyTheme(edit_theme); + + if (ImGui::Checkbox("Enable Animations", + &edit_theme.enable_animations)) { + if (live_preview) + ApplyTheme(edit_theme); } if (edit_theme.enable_animations) { - if (ImGui::SliderFloat("Animation Speed", &edit_theme.animation_speed, 0.1f, 3.0f)) { + if (ImGui::SliderFloat("Animation Speed", &edit_theme.animation_speed, + 0.1f, 3.0f)) { apply_live_preview(); } } - if (ImGui::Checkbox("Enable Glow Effects", &edit_theme.enable_glow_effects)) { - if (live_preview) ApplyTheme(edit_theme); + if (ImGui::Checkbox("Enable Glow Effects", + &edit_theme.enable_glow_effects)) { + if (live_preview) + ApplyTheme(edit_theme); } - + ImGui::EndTabItem(); } - + // Navigation & Windows Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Navigation", ICON_MD_NAVIGATION).c_str())) { - if (ImGui::BeginTable("NavigationTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTabItem( + absl::StrFormat("%s Navigation", ICON_MD_NAVIGATION).c_str())) { + if (ImGui::BeginTable("NavigationTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, + 0.6f); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, + 0.4f); ImGui::TableHeadersRow(); - + // Window colors - auto window_colors = std::vector>{ - {"Window Background", &edit_theme.window_bg, "Main window background"}, - {"Child Background", &edit_theme.child_bg, "Child window background"}, - {"Popup Background", &edit_theme.popup_bg, "Popup window background"}, - {"Modal Background", &edit_theme.modal_bg, "Modal window background"}, - {"Menu Bar BG", &edit_theme.menu_bar_bg, "Menu bar background"} - }; - + auto window_colors = + std::vector>{ + {"Window Background", &edit_theme.window_bg, + "Main window background"}, + {"Child Background", &edit_theme.child_bg, + "Child window background"}, + {"Popup Background", &edit_theme.popup_bg, + "Popup window background"}, + {"Modal Background", &edit_theme.modal_bg, + "Modal window background"}, + {"Menu Bar BG", &edit_theme.menu_bar_bg, + "Menu bar background"}}; + for (auto& [label, color_ptr, description] : window_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##window_%s", label); @@ -1414,130 +1670,160 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::TextWrapped("%s", description); } - + ImGui::EndTable(); } - + ImGui::Separator(); - + // Header and Tab colors - if (ImGui::CollapsingHeader("Headers & Tabs", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginTable("HeaderTabTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::CollapsingHeader("Headers & Tabs", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::BeginTable("HeaderTabTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", + ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Preview", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - - auto header_tab_colors = std::vector>{ - {"Header", &edit_theme.header}, - {"Header Hovered", &edit_theme.header_hovered}, - {"Header Active", &edit_theme.header_active}, - {"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}, - {"Title BG Active", &edit_theme.title_bg_active}, - {"Title BG Collapsed", &edit_theme.title_bg_collapsed} - }; - + + auto header_tab_colors = + std::vector>{ + {"Header", &edit_theme.header}, + {"Header Hovered", &edit_theme.header_hovered}, + {"Header Active", &edit_theme.header_active}, + {"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}, + {"Title BG Active", &edit_theme.title_bg_active}, + {"Title BG Collapsed", &edit_theme.title_bg_collapsed}}; + for (auto& [label, color_ptr] : header_tab_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##header_%s", label); if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) { - *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; + *color_ptr = {color_vec.x, color_vec.y, color_vec.z, + color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Button, color_vec); - ImGui::Button(absl::StrFormat("Preview %s", label).c_str(), ImVec2(-1, 25)); + ImGui::Button(absl::StrFormat("Preview %s", label).c_str(), + ImVec2(-1, 25)); ImGui::PopStyleColor(); } - + ImGui::EndTable(); } } - + // Navigation and Special Elements if (ImGui::CollapsingHeader("Navigation & Special")) { - if (ImGui::BeginTable("NavSpecialTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTable("NavSpecialTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", + ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - - auto nav_special_colors = std::vector>{ - {"Nav Cursor", &edit_theme.nav_cursor, "Navigation cursor color"}, - {"Nav Win Highlight", &edit_theme.nav_windowing_highlight, "Window selection highlight"}, - {"Nav Win Dim BG", &edit_theme.nav_windowing_dim_bg, "Background dimming for navigation"}, - {"Modal Win Dim BG", &edit_theme.modal_window_dim_bg, "Background dimming for modals"}, - {"Drag Drop Target", &edit_theme.drag_drop_target, "Drag and drop target highlight"}, - {"Docking Preview", &edit_theme.docking_preview, "Docking area preview"}, - {"Docking Empty BG", &edit_theme.docking_empty_bg, "Empty docking space background"} - }; - + + auto nav_special_colors = + std::vector>{ + {"Nav Cursor", &edit_theme.nav_cursor, + "Navigation cursor color"}, + {"Nav Win Highlight", &edit_theme.nav_windowing_highlight, + "Window selection highlight"}, + {"Nav Win Dim BG", &edit_theme.nav_windowing_dim_bg, + "Background dimming for navigation"}, + {"Modal Win Dim BG", &edit_theme.modal_window_dim_bg, + "Background dimming for modals"}, + {"Drag Drop Target", &edit_theme.drag_drop_target, + "Drag and drop target highlight"}, + {"Docking Preview", &edit_theme.docking_preview, + "Docking area preview"}, + {"Docking Empty BG", &edit_theme.docking_empty_bg, + "Empty docking space background"}}; + for (auto& [label, color_ptr, description] : nav_special_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##nav_%s", label); if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) { - *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; + *color_ptr = {color_vec.x, color_vec.y, color_vec.z, + color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::TextWrapped("%s", description); } - + ImGui::EndTable(); } } - + ImGui::EndTabItem(); } - + // Tables & Data Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Tables", ICON_MD_TABLE_CHART).c_str())) { - if (ImGui::BeginTable("TablesDataTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTabItem( + absl::StrFormat("%s Tables", ICON_MD_TABLE_CHART).c_str())) { + if (ImGui::BeginTable("TablesDataTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, + 0.6f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - - auto table_colors = std::vector>{ - {"Table Header BG", &edit_theme.table_header_bg, "Table column headers"}, - {"Table Border Strong", &edit_theme.table_border_strong, "Outer table borders"}, - {"Table Border Light", &edit_theme.table_border_light, "Inner table borders"}, - {"Table Row BG", &edit_theme.table_row_bg, "Normal table rows"}, - {"Table Row BG Alt", &edit_theme.table_row_bg_alt, "Alternating table rows"}, - {"Tree Lines", &edit_theme.tree_lines, "Tree view connection lines"} - }; - + + auto table_colors = + std::vector>{ + {"Table Header BG", &edit_theme.table_header_bg, + "Table column headers"}, + {"Table Border Strong", &edit_theme.table_border_strong, + "Outer table borders"}, + {"Table Border Light", &edit_theme.table_border_light, + "Inner table borders"}, + {"Table Row BG", &edit_theme.table_row_bg, + "Normal table rows"}, + {"Table Row BG Alt", &edit_theme.table_row_bg_alt, + "Alternating table rows"}, + {"Tree Lines", &edit_theme.tree_lines, + "Tree view connection lines"}}; + for (auto& [label, color_ptr, description] : table_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##table_%s", label); @@ -1545,85 +1831,110 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::TextWrapped("%s", description); } - + ImGui::EndTable(); } - + ImGui::Separator(); - + // Plots and Graphs if (ImGui::CollapsingHeader("Plots & Graphs")) { - if (ImGui::BeginTable("PlotsTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTable("PlotsTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", + ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - - auto plot_colors = std::vector>{ - {"Plot Lines", &edit_theme.plot_lines, "Line plot color"}, - {"Plot Lines Hovered", &edit_theme.plot_lines_hovered, "Line plot hover color"}, - {"Plot Histogram", &edit_theme.plot_histogram, "Histogram fill color"}, - {"Plot Histogram Hovered", &edit_theme.plot_histogram_hovered, "Histogram hover color"} - }; - + + auto plot_colors = + std::vector>{ + {"Plot Lines", &edit_theme.plot_lines, "Line plot color"}, + {"Plot Lines Hovered", &edit_theme.plot_lines_hovered, + "Line plot hover color"}, + {"Plot Histogram", &edit_theme.plot_histogram, + "Histogram fill color"}, + {"Plot Histogram Hovered", + &edit_theme.plot_histogram_hovered, + "Histogram hover color"}}; + for (auto& [label, color_ptr, description] : plot_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##plot_%s", label); if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) { - *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; + *color_ptr = {color_vec.x, color_vec.y, color_vec.z, + color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::TextWrapped("%s", description); } - + ImGui::EndTable(); } } - + ImGui::EndTabItem(); } - - // Borders & Controls Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Borders", ICON_MD_BORDER_ALL).c_str())) { - if (ImGui::BeginTable("BordersControlsTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f); + + // Borders & Controls Tab + if (ImGui::BeginTabItem( + absl::StrFormat("%s Borders", ICON_MD_BORDER_ALL).c_str())) { + if (ImGui::BeginTable("BordersControlsTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, + 0.6f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - - auto border_control_colors = std::vector>{ - {"Border", &edit_theme.border, "General border color"}, - {"Border Shadow", &edit_theme.border_shadow, "Border shadow/depth"}, - {"Separator", &edit_theme.separator, "Horizontal/vertical separators"}, - {"Separator Hovered", &edit_theme.separator_hovered, "Separator hover state"}, - {"Separator Active", &edit_theme.separator_active, "Separator active/dragged state"}, - {"Scrollbar BG", &edit_theme.scrollbar_bg, "Scrollbar track background"}, - {"Scrollbar Grab", &edit_theme.scrollbar_grab, "Scrollbar handle"}, - {"Scrollbar Grab Hovered", &edit_theme.scrollbar_grab_hovered, "Scrollbar handle hover"}, - {"Scrollbar Grab Active", &edit_theme.scrollbar_grab_active, "Scrollbar handle active"}, - {"Resize Grip", &edit_theme.resize_grip, "Window resize grip"}, - {"Resize Grip Hovered", &edit_theme.resize_grip_hovered, "Resize grip hover"}, - {"Resize Grip Active", &edit_theme.resize_grip_active, "Resize grip active"} - }; - + + auto border_control_colors = + std::vector>{ + {"Border", &edit_theme.border, "General border color"}, + {"Border Shadow", &edit_theme.border_shadow, + "Border shadow/depth"}, + {"Separator", &edit_theme.separator, + "Horizontal/vertical separators"}, + {"Separator Hovered", &edit_theme.separator_hovered, + "Separator hover state"}, + {"Separator Active", &edit_theme.separator_active, + "Separator active/dragged state"}, + {"Scrollbar BG", &edit_theme.scrollbar_bg, + "Scrollbar track background"}, + {"Scrollbar Grab", &edit_theme.scrollbar_grab, + "Scrollbar handle"}, + {"Scrollbar Grab Hovered", &edit_theme.scrollbar_grab_hovered, + "Scrollbar handle hover"}, + {"Scrollbar Grab Active", &edit_theme.scrollbar_grab_active, + "Scrollbar handle active"}, + {"Resize Grip", &edit_theme.resize_grip, + "Window resize grip"}, + {"Resize Grip Hovered", &edit_theme.resize_grip_hovered, + "Resize grip hover"}, + {"Resize Grip Active", &edit_theme.resize_grip_active, + "Resize grip active"}}; + for (auto& [label, color_ptr, description] : border_control_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##border_%s", label); @@ -1631,95 +1942,118 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::TextWrapped("%s", description); } - + ImGui::EndTable(); } - + ImGui::EndTabItem(); } - + // Enhanced Colors Tab - if (ImGui::BeginTabItem(absl::StrFormat("%s Enhanced", ICON_MD_AUTO_AWESOME).c_str())) { - ImGui::Text("Enhanced semantic colors and editor-specific customization"); + if (ImGui::BeginTabItem( + absl::StrFormat("%s Enhanced", ICON_MD_AUTO_AWESOME).c_str())) { + ImGui::Text( + "Enhanced semantic colors and editor-specific customization"); ImGui::Separator(); - + // Enhanced semantic colors section - if (ImGui::CollapsingHeader("Enhanced Semantic Colors", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::BeginTable("EnhancedSemanticTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::CollapsingHeader("Enhanced Semantic Colors", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::BeginTable("EnhancedSemanticTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", + ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - - auto enhanced_colors = std::vector>{ - {"Code Background", &edit_theme.code_background, "Code blocks background"}, - {"Success Light", &edit_theme.success_light, "Light success variant"}, - {"Warning Light", &edit_theme.warning_light, "Light warning variant"}, - {"Error Light", &edit_theme.error_light, "Light error variant"}, - {"Info Light", &edit_theme.info_light, "Light info variant"} - }; - + + auto enhanced_colors = + std::vector>{ + {"Code Background", &edit_theme.code_background, + "Code blocks background"}, + {"Success Light", &edit_theme.success_light, + "Light success variant"}, + {"Warning Light", &edit_theme.warning_light, + "Light warning variant"}, + {"Error Light", &edit_theme.error_light, + "Light error variant"}, + {"Info Light", &edit_theme.info_light, + "Light info variant"}}; + for (auto& [label, color_ptr, description] : enhanced_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##enhanced_%s", label); if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) { - *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; + *color_ptr = {color_vec.x, color_vec.y, color_vec.z, + color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::TextWrapped("%s", description); } - + ImGui::EndTable(); } } - + // UI State colors section if (ImGui::CollapsingHeader("UI State Colors")) { - if (ImGui::BeginTable("UIStateTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTable("UIStateTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", + ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - + // UI state colors with alpha support where needed ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("Active Selection:"); ImGui::TableNextColumn(); - ImVec4 active_selection = ConvertColorToImVec4(edit_theme.active_selection); + ImVec4 active_selection = + ConvertColorToImVec4(edit_theme.active_selection); if (ImGui::ColorEdit4("##active_selection", &active_selection.x)) { - edit_theme.active_selection = {active_selection.x, active_selection.y, active_selection.z, active_selection.w}; + edit_theme.active_selection = { + active_selection.x, active_selection.y, active_selection.z, + active_selection.w}; apply_live_preview(); } ImGui::TableNextColumn(); ImGui::TextWrapped("Active/selected UI elements"); - + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("Hover Highlight:"); ImGui::TableNextColumn(); - ImVec4 hover_highlight = ConvertColorToImVec4(edit_theme.hover_highlight); + ImVec4 hover_highlight = + ConvertColorToImVec4(edit_theme.hover_highlight); if (ImGui::ColorEdit4("##hover_highlight", &hover_highlight.x)) { - edit_theme.hover_highlight = {hover_highlight.x, hover_highlight.y, hover_highlight.z, hover_highlight.w}; + edit_theme.hover_highlight = { + hover_highlight.x, hover_highlight.y, hover_highlight.z, + hover_highlight.w}; apply_live_preview(); } ImGui::TableNextColumn(); ImGui::TextWrapped("General hover state highlighting"); - + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); @@ -1727,145 +2061,167 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { ImGui::TableNextColumn(); ImVec4 focus_border = ConvertColorToImVec4(edit_theme.focus_border); if (ImGui::ColorEdit3("##focus_border", &focus_border.x)) { - edit_theme.focus_border = {focus_border.x, focus_border.y, focus_border.z, focus_border.w}; + edit_theme.focus_border = {focus_border.x, focus_border.y, + focus_border.z, focus_border.w}; apply_live_preview(); } ImGui::TableNextColumn(); ImGui::TextWrapped("Border for focused input elements"); - + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("Disabled Overlay:"); ImGui::TableNextColumn(); - ImVec4 disabled_overlay = ConvertColorToImVec4(edit_theme.disabled_overlay); + ImVec4 disabled_overlay = + ConvertColorToImVec4(edit_theme.disabled_overlay); if (ImGui::ColorEdit4("##disabled_overlay", &disabled_overlay.x)) { - edit_theme.disabled_overlay = {disabled_overlay.x, disabled_overlay.y, disabled_overlay.z, disabled_overlay.w}; + edit_theme.disabled_overlay = { + disabled_overlay.x, disabled_overlay.y, disabled_overlay.z, + disabled_overlay.w}; apply_live_preview(); } ImGui::TableNextColumn(); - ImGui::TextWrapped("Semi-transparent overlay for disabled elements"); - + ImGui::TextWrapped( + "Semi-transparent overlay for disabled elements"); + ImGui::EndTable(); } } - + // Editor-specific colors section if (ImGui::CollapsingHeader("Editor-Specific Colors")) { - if (ImGui::BeginTable("EditorColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f); - ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f); + if (ImGui::BeginTable("EditorColorsTable", 3, + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, + 120.0f); + ImGui::TableSetupColumn("Picker", + ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch, 0.4f); ImGui::TableHeadersRow(); - - auto editor_colors = std::vector>{ - {"Editor Background", &edit_theme.editor_background, "Main editor canvas background", false}, - {"Editor Grid", &edit_theme.editor_grid, "Grid lines in map/graphics editors", true}, - {"Editor Cursor", &edit_theme.editor_cursor, "Cursor color in editors", false}, - {"Editor Selection", &edit_theme.editor_selection, "Selection highlight in editors", true} - }; - - for (auto& [label, color_ptr, description, use_alpha] : editor_colors) { + + auto editor_colors = + std::vector>{ + {"Editor Background", &edit_theme.editor_background, + "Main editor canvas background", false}, + {"Editor Grid", &edit_theme.editor_grid, + "Grid lines in map/graphics editors", true}, + {"Editor Cursor", &edit_theme.editor_cursor, + "Cursor color in editors", false}, + {"Editor Selection", &edit_theme.editor_selection, + "Selection highlight in editors", true}}; + + for (auto& [label, color_ptr, description, use_alpha] : + editor_colors) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); ImGui::Text("%s:", label); - + ImGui::TableNextColumn(); ImVec4 color_vec = ConvertColorToImVec4(*color_ptr); std::string id = absl::StrFormat("##editor_%s", label); - if (use_alpha ? ImGui::ColorEdit4(id.c_str(), &color_vec.x) : ImGui::ColorEdit3(id.c_str(), &color_vec.x)) { - *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w}; + if (use_alpha ? ImGui::ColorEdit4(id.c_str(), &color_vec.x) + : ImGui::ColorEdit3(id.c_str(), &color_vec.x)) { + *color_ptr = {color_vec.x, color_vec.y, color_vec.z, + color_vec.w}; apply_live_preview(); } - + ImGui::TableNextColumn(); ImGui::TextWrapped("%s", description); } - + ImGui::EndTable(); } } - + ImGui::EndTabItem(); } - + ImGui::EndTabBar(); } - + ImGui::Separator(); - + if (ImGui::Button("Preview Theme")) { ApplyTheme(edit_theme); } - + ImGui::SameLine(); if (ImGui::Button("Reset to Current")) { edit_theme = current_theme_; // Safe string copy with bounds checking - size_t name_len = std::min(current_theme_.name.length(), sizeof(theme_name) - 1); + size_t name_len = + std::min(current_theme_.name.length(), sizeof(theme_name) - 1); std::memcpy(theme_name, current_theme_.name.c_str(), name_len); theme_name[name_len] = '\0'; - - size_t desc_len = std::min(current_theme_.description.length(), sizeof(theme_description) - 1); - std::memcpy(theme_description, current_theme_.description.c_str(), desc_len); + + size_t desc_len = std::min(current_theme_.description.length(), + sizeof(theme_description) - 1); + std::memcpy(theme_description, current_theme_.description.c_str(), + desc_len); theme_description[desc_len] = '\0'; - - size_t author_len = std::min(current_theme_.author.length(), sizeof(theme_author) - 1); + + size_t author_len = + std::min(current_theme_.author.length(), sizeof(theme_author) - 1); std::memcpy(theme_author, current_theme_.author.c_str(), author_len); theme_author[author_len] = '\0'; - + // Reset backup state since we're back to current theme if (theme_backup_made) { theme_backup_made = false; - current_theme_.ApplyToImGui(); // Apply current theme to clear any preview changes + current_theme_ + .ApplyToImGui(); // Apply current theme to clear any preview changes } } - + ImGui::SameLine(); if (ImGui::Button("Save Theme")) { edit_theme.name = std::string(theme_name); edit_theme.description = std::string(theme_description); edit_theme.author = std::string(theme_author); - + // Add to themes map and apply themes_[edit_theme.name] = edit_theme; ApplyTheme(edit_theme); - + // Reset backup state since theme is now applied theme_backup_made = false; } - + ImGui::SameLine(); - + // Save Over Current button - overwrites the current theme file std::string current_file_path = GetCurrentThemeFilePath(); bool can_save_over = !current_file_path.empty(); - + if (!can_save_over) { ImGui::BeginDisabled(); } - + if (ImGui::Button("Save Over Current")) { edit_theme.name = std::string(theme_name); edit_theme.description = std::string(theme_description); edit_theme.author = std::string(theme_author); - + auto status = SaveThemeToFile(edit_theme, current_file_path); if (status.ok()) { // Update themes map and apply themes_[edit_theme.name] = edit_theme; ApplyTheme(edit_theme); - theme_backup_made = false; // Reset backup state since theme is now applied + theme_backup_made = + false; // Reset backup state since theme is now applied } else { LOG_ERROR("Theme Manager", "Failed to save over current theme"); } } - + if (!can_save_over) { ImGui::EndDisabled(); } - + if (ImGui::IsItemHovered() && can_save_over) { ImGui::BeginTooltip(); ImGui::Text("Save over current theme file:"); @@ -1877,23 +2233,25 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { ImGui::Text("Use 'Save to File...' to create a new theme file"); ImGui::EndTooltip(); } - + ImGui::SameLine(); if (ImGui::Button("Save to File...")) { edit_theme.name = std::string(theme_name); edit_theme.description = std::string(theme_description); edit_theme.author = std::string(theme_author); - + // Use save file dialog with proper defaults - std::string safe_name = edit_theme.name.empty() ? "custom_theme" : edit_theme.name; - auto file_path = util::FileDialogWrapper::ShowSaveFileDialog(safe_name, "theme"); - + std::string safe_name = + edit_theme.name.empty() ? "custom_theme" : edit_theme.name; + auto file_path = + util::FileDialogWrapper::ShowSaveFileDialog(safe_name, "theme"); + if (!file_path.empty()) { // Ensure .theme extension if (file_path.find(".theme") == std::string::npos) { file_path += ".theme"; } - + auto status = SaveThemeToFile(edit_theme, file_path); if (status.ok()) { // Also add to themes map for immediate use @@ -1904,7 +2262,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { } } } - + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("Save theme to a .theme file"); @@ -1917,11 +2275,11 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { std::vector ThemeManager::GetThemeSearchPaths() const { std::vector search_paths; - + // Development path (relative to build directory) search_paths.push_back("assets/themes/"); search_paths.push_back("../assets/themes/"); - + // Platform-specific resource paths #ifdef __APPLE__ // macOS bundle resource path (this should be the primary path for bundled apps) @@ -1929,10 +2287,10 @@ std::vector ThemeManager::GetThemeSearchPaths() const { if (!bundle_themes.empty()) { search_paths.push_back(bundle_themes); } - + // Alternative bundle locations std::string bundle_root = util::GetBundleResourcePath(); - + search_paths.push_back(bundle_root + "Contents/Resources/themes/"); search_paths.push_back(bundle_root + "Contents/Resources/assets/themes/"); search_paths.push_back(bundle_root + "assets/themes/"); @@ -1942,51 +2300,54 @@ std::vector ThemeManager::GetThemeSearchPaths() const { search_paths.push_back("./assets/themes/"); search_paths.push_back("./themes/"); #endif - + // User config directory auto config_dir = util::PlatformPaths::GetConfigDirectory(); if (config_dir.ok()) { search_paths.push_back((*config_dir / "themes/").string()); } - + return search_paths; } std::string ThemeManager::GetThemesDirectory() const { auto search_paths = GetThemeSearchPaths(); - + // Try each search path and return the first one that exists for (const auto& path : search_paths) { - std::ifstream test_file(path + "."); // Test if directory exists by trying to access it + std::ifstream test_file( + path + "."); // Test if directory exists by trying to access it if (test_file.good()) { return path; } - + // Also try with platform-specific directory separators std::string normalized_path = path; - if (!normalized_path.empty() && normalized_path.back() != '/' && normalized_path.back() != '\\') { + if (!normalized_path.empty() && normalized_path.back() != '/' && + normalized_path.back() != '\\') { normalized_path += "/"; } - + std::ifstream test_file2(normalized_path + "."); if (test_file2.good()) { return normalized_path; } } - + return search_paths.empty() ? "assets/themes/" : search_paths[0]; } std::vector ThemeManager::DiscoverAvailableThemeFiles() const { std::vector theme_files; auto search_paths = GetThemeSearchPaths(); - + for (const auto& search_path : search_paths) { - + try { // Use platform-specific file discovery instead of glob #ifdef __APPLE__ - auto files_in_folder = util::FileDialogWrapper::GetFilesInFolder(search_path); + auto files_in_folder = + util::FileDialogWrapper::GetFilesInFolder(search_path); for (const auto& file : files_in_folder) { if (file.length() > 6 && file.substr(file.length() - 6) == ".theme") { std::string full_path = search_path + file; @@ -1997,10 +2358,9 @@ std::vector ThemeManager::DiscoverAvailableThemeFiles() const { // For Linux/Windows, use filesystem directory iteration // (could be extended with platform-specific implementations if needed) std::vector known_themes = { - "yaze_tre.theme", "cyberpunk.theme", "sunset.theme", - "forest.theme", "midnight.theme" - }; - + "yaze_tre.theme", "cyberpunk.theme", "sunset.theme", "forest.theme", + "midnight.theme"}; + for (const auto& theme_name : known_themes) { std::string full_path = search_path + theme_name; std::ifstream test_file(full_path); @@ -2010,14 +2370,15 @@ std::vector ThemeManager::DiscoverAvailableThemeFiles() const { } #endif } catch (const std::exception& e) { - LOG_ERROR("Theme Manager", "Error scanning directory %s", search_path.c_str()); + LOG_ERROR("Theme Manager", "Error scanning directory %s", + search_path.c_str()); } } - + // Remove duplicates while preserving order std::vector unique_files; std::set seen_basenames; - + for (const auto& file : theme_files) { std::string basename = util::GetFileName(file); if (seen_basenames.find(basename) == seen_basenames.end()) { @@ -2025,16 +2386,16 @@ std::vector ThemeManager::DiscoverAvailableThemeFiles() const { seen_basenames.insert(basename); } } - + return unique_files; } absl::Status ThemeManager::LoadAllAvailableThemes() { auto theme_files = DiscoverAvailableThemeFiles(); - + int successful_loads = 0; int failed_loads = 0; - + for (const auto& theme_file : theme_files) { auto status = LoadThemeFromFile(theme_file); if (status.ok()) { @@ -2043,12 +2404,12 @@ absl::Status ThemeManager::LoadAllAvailableThemes() { failed_loads++; } } - - + if (successful_loads == 0 && failed_loads > 0) { - return absl::InternalError(absl::StrFormat("Failed to load any themes (%d failures)", failed_loads)); + return absl::InternalError(absl::StrFormat( + "Failed to load any themes (%d failures)", failed_loads)); } - + return absl::OkStatus(); } @@ -2058,20 +2419,20 @@ absl::Status ThemeManager::RefreshAvailableThemes() { std::string ThemeManager::GetCurrentThemeFilePath() const { if (current_theme_name_ == "Classic YAZE") { - return ""; // Classic theme doesn't have a file + return ""; // Classic theme doesn't have a file } - + // Try to find the current theme file in the search paths auto search_paths = GetThemeSearchPaths(); std::string theme_filename = current_theme_name_ + ".theme"; - + // Convert theme name to safe filename (replace spaces and special chars) for (char& c : theme_filename) { if (!std::isalnum(c) && c != '.' && c != '_') { c = '_'; } } - + for (const auto& search_path : search_paths) { std::string full_path = search_path + theme_filename; std::ifstream test_file(full_path); @@ -2079,10 +2440,11 @@ std::string ThemeManager::GetCurrentThemeFilePath() const { return full_path; } } - + // If not found, return path in the first search directory (for new saves) - return search_paths.empty() ? theme_filename : search_paths[0] + theme_filename; + return search_paths.empty() ? theme_filename + : search_paths[0] + theme_filename; } -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze diff --git a/src/app/gui/core/theme_manager.h b/src/app/gui/core/theme_manager.h index a50098a6..04ab3feb 100644 --- a/src/app/gui/core/theme_manager.h +++ b/src/app/gui/core/theme_manager.h @@ -21,7 +21,7 @@ struct EnhancedTheme { std::string name; std::string description; std::string author; - + // Primary colors Color primary; Color secondary; @@ -32,18 +32,18 @@ struct EnhancedTheme { Color warning; Color success; Color info; - + // Text colors Color text_primary; Color text_secondary; Color text_disabled; - + // Window colors Color window_bg; Color child_bg; Color popup_bg; Color modal_bg; - + // Interactive elements Color button; Color button_hovered; @@ -51,7 +51,7 @@ struct EnhancedTheme { Color frame_bg; Color frame_bg_hovered; Color frame_bg_active; - + // Navigation and selection Color header; Color header_hovered; @@ -63,27 +63,27 @@ struct EnhancedTheme { Color title_bg; Color title_bg_active; Color title_bg_collapsed; - + // Borders and separators Color border; Color border_shadow; Color separator; Color separator_hovered; Color separator_active; - + // Scrollbars and controls Color scrollbar_bg; Color scrollbar_grab; Color scrollbar_grab_hovered; Color scrollbar_grab_active; - + // Special elements Color resize_grip; Color resize_grip_hovered; Color resize_grip_active; Color docking_preview; Color docking_empty_bg; - + // Complete ImGui color support Color check_mark; Color slider_grab; @@ -106,7 +106,7 @@ struct EnhancedTheme { Color plot_histogram; Color plot_histogram_hovered; Color tree_lines; - + // Additional ImGui colors for complete coverage Color tab_unfocused; Color tab_unfocused_active; @@ -114,34 +114,34 @@ struct EnhancedTheme { Color tab_dimmed_selected; Color tab_dimmed_selected_overline; Color tab_selected_overline; - + // Enhanced theme system - semantic colors - Color text_highlight; // For selected text, highlighted items - Color link_hover; // For hover state of links - Color code_background; // For code blocks, monospace text backgrounds - Color success_light; // Lighter variant of success color - Color warning_light; // Lighter variant of warning color - Color error_light; // Lighter variant of error color - Color info_light; // Lighter variant of info color - + Color text_highlight; // For selected text, highlighted items + Color link_hover; // For hover state of links + Color code_background; // For code blocks, monospace text backgrounds + Color success_light; // Lighter variant of success color + Color warning_light; // Lighter variant of warning color + Color error_light; // Lighter variant of error color + Color info_light; // Lighter variant of info color + // UI state colors Color active_selection; // For active/selected UI elements Color hover_highlight; // General hover state Color focus_border; // For focused input elements Color disabled_overlay; // Semi-transparent overlay for disabled elements - + // Editor-specific colors - Color editor_background; // Main editor canvas background - Color editor_grid; // Grid lines in editors - Color editor_cursor; // Cursor/selection in editors - Color editor_selection; // Selected area in editors + Color editor_background; // Main editor canvas background + Color editor_grid; // Grid lines in editors + Color editor_cursor; // Cursor/selection in editors + Color editor_selection; // Selected area in editors Color entrance_color; Color hole_color; Color exit_color; Color item_color; Color sprite_color; - + // Style parameters float window_rounding = 0.0f; float frame_rounding = 5.0f; @@ -161,15 +161,15 @@ struct EnhancedTheme { float compact_factor = 1.0f; // Semantic sizing multipliers (applied on top of compact_factor) - float widget_height_multiplier = 1.0f; // Standard widget height - float spacing_multiplier = 1.0f; // Padding/margins between elements - float toolbar_height_multiplier = 0.8f; // Compact toolbars - float panel_padding_multiplier = 1.0f; // Panel interior padding - float input_width_multiplier = 1.0f; // Standard input field width - float button_padding_multiplier = 1.0f; // Button interior padding - float table_row_height_multiplier = 1.0f; // Table row height - float canvas_toolbar_multiplier = 0.75f; // Canvas overlay toolbars - + float widget_height_multiplier = 1.0f; // Standard widget height + float spacing_multiplier = 1.0f; // Padding/margins between elements + float toolbar_height_multiplier = 0.8f; // Compact toolbars + float panel_padding_multiplier = 1.0f; // Panel interior padding + float input_width_multiplier = 1.0f; // Standard input field width + float button_padding_multiplier = 1.0f; // Button interior padding + float table_row_height_multiplier = 1.0f; // Table row height + float canvas_toolbar_multiplier = 0.75f; // Canvas overlay toolbars + // Helper methods void ApplyToImGui() const; }; @@ -179,48 +179,50 @@ struct EnhancedTheme { * @brief Manages themes, loading, saving, and switching */ class ThemeManager { -public: + public: static ThemeManager& Get(); - + // Theme management absl::Status LoadTheme(const std::string& theme_name); - absl::Status SaveTheme(const EnhancedTheme& theme, const std::string& filename); + absl::Status SaveTheme(const EnhancedTheme& theme, + const std::string& filename); absl::Status LoadThemeFromFile(const std::string& filepath); - absl::Status SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const; - + absl::Status SaveThemeToFile(const EnhancedTheme& theme, + const std::string& filepath) const; + // Dynamic theme discovery - replaces hardcoded theme lists with automatic discovery // This works across development builds, macOS app bundles, and other deployment scenarios std::vector DiscoverAvailableThemeFiles() const; absl::Status LoadAllAvailableThemes(); - absl::Status RefreshAvailableThemes(); // Public method to refresh at runtime - + absl::Status RefreshAvailableThemes(); // Public method to refresh at runtime + // Built-in themes void InitializeBuiltInThemes(); std::vector GetAvailableThemes() const; const EnhancedTheme* GetTheme(const std::string& name) const; const EnhancedTheme& GetCurrentTheme() const { return current_theme_; } const std::string& GetCurrentThemeName() const { return current_theme_name_; } - + // Theme application void ApplyTheme(const std::string& theme_name); void ApplyTheme(const EnhancedTheme& theme); - void ApplyClassicYazeTheme(); // Apply original ColorsYaze() function - + void ApplyClassicYazeTheme(); // Apply original ColorsYaze() function + // Theme creation and editing EnhancedTheme CreateCustomTheme(const std::string& name); void ShowThemeEditor(bool* p_open); void ShowThemeSelector(bool* p_open); void ShowSimpleThemeEditor(bool* p_open); - + // Integration with welcome screen Color GetWelcomeScreenBackground() const; Color GetWelcomeScreenBorder() const; Color GetWelcomeScreenAccent() const; - + // Convenient theme color access interface Color GetThemeColor(const std::string& color_name) const; ImVec4 GetThemeColorVec4(const std::string& color_name) const; - + // Material Design color accessors Color GetPrimary() const { return current_theme_.primary; } Color GetPrimaryHover() const { return current_theme_.button_hovered; } @@ -230,7 +232,9 @@ public: Color GetSurfaceVariant() const { return current_theme_.child_bg; } Color GetSurfaceContainer() const { return current_theme_.popup_bg; } Color GetSurfaceContainerHigh() const { return current_theme_.header; } - Color GetSurfaceContainerHighest() const { return current_theme_.header_hovered; } + Color GetSurfaceContainerHighest() const { + return current_theme_.header_hovered; + } Color GetOnSurface() const { return current_theme_.text_primary; } Color GetOnSurfaceVariant() const { return current_theme_.text_secondary; } Color GetOnPrimary() const { return current_theme_.text_primary; } @@ -238,19 +242,19 @@ public: Color GetTextSecondary() const { return current_theme_.text_secondary; } Color GetTextDisabled() const { return current_theme_.text_disabled; } Color GetShadow() const { return current_theme_.border_shadow; } - -private: + + private: ThemeManager() { InitializeBuiltInThemes(); } - + std::map themes_; EnhancedTheme current_theme_; std::string current_theme_name_ = "Classic YAZE"; - + void CreateFallbackYazeClassic(); absl::Status ParseThemeFile(const std::string& content, EnhancedTheme& theme); Color ParseColorFromString(const std::string& color_str) const; std::string SerializeTheme(const EnhancedTheme& theme) const; - + // Helper methods for path resolution std::vector GetThemeSearchPaths() const; std::string GetThemesDirectory() const; @@ -258,51 +262,113 @@ private: }; // Global convenience functions for easy theme color access - // Material Design color accessors - global convenience functions - inline Color GetThemeColor(const std::string& color_name) { - return ThemeManager::Get().GetThemeColor(color_name); - } - - inline ImVec4 GetThemeColorVec4(const std::string& color_name) { - return ThemeManager::Get().GetThemeColorVec4(color_name); - } - - // Material Design color accessors - inline Color GetPrimary() { return ThemeManager::Get().GetPrimary(); } - inline Color GetPrimaryHover() { return ThemeManager::Get().GetPrimaryHover(); } - inline Color GetPrimaryActive() { return ThemeManager::Get().GetPrimaryActive(); } - inline Color GetSecondary() { return ThemeManager::Get().GetSecondary(); } - inline Color GetSurface() { return ThemeManager::Get().GetSurface(); } - inline Color GetSurfaceVariant() { return ThemeManager::Get().GetSurfaceVariant(); } - inline Color GetSurfaceContainer() { return ThemeManager::Get().GetSurfaceContainer(); } - inline Color GetSurfaceContainerHigh() { return ThemeManager::Get().GetSurfaceContainerHigh(); } - inline Color GetSurfaceContainerHighest() { return ThemeManager::Get().GetSurfaceContainerHighest(); } - inline Color GetOnSurface() { return ThemeManager::Get().GetOnSurface(); } - inline Color GetOnSurfaceVariant() { return ThemeManager::Get().GetOnSurfaceVariant(); } - inline Color GetOnPrimary() { return ThemeManager::Get().GetOnPrimary(); } - inline Color GetOutline() { return ThemeManager::Get().GetOutline(); } - inline Color GetTextSecondary() { return ThemeManager::Get().GetTextSecondary(); } - inline Color GetTextDisabled() { return ThemeManager::Get().GetTextDisabled(); } - inline Color GetShadow() { return ThemeManager::Get().GetShadow(); } - - // ImVec4 versions for direct ImGui usage - inline ImVec4 GetPrimaryVec4() { return ConvertColorToImVec4(GetPrimary()); } - inline ImVec4 GetPrimaryHoverVec4() { return ConvertColorToImVec4(GetPrimaryHover()); } - inline ImVec4 GetPrimaryActiveVec4() { return ConvertColorToImVec4(GetPrimaryActive()); } - inline ImVec4 GetSurfaceVec4() { return ConvertColorToImVec4(GetSurface()); } - inline ImVec4 GetSurfaceVariantVec4() { return ConvertColorToImVec4(GetSurfaceVariant()); } - inline ImVec4 GetSurfaceContainerVec4() { return ConvertColorToImVec4(GetSurfaceContainer()); } - inline ImVec4 GetSurfaceContainerHighVec4() { return ConvertColorToImVec4(GetSurfaceContainerHigh()); } - inline ImVec4 GetSurfaceContainerHighestVec4() { return ConvertColorToImVec4(GetSurfaceContainerHighest()); } - inline ImVec4 GetOnSurfaceVec4() { return ConvertColorToImVec4(GetOnSurface()); } - inline ImVec4 GetOnSurfaceVariantVec4() { return ConvertColorToImVec4(GetOnSurfaceVariant()); } - inline ImVec4 GetOnPrimaryVec4() { return ConvertColorToImVec4(GetOnPrimary()); } - inline ImVec4 GetOutlineVec4() { return ConvertColorToImVec4(GetOutline()); } - inline ImVec4 GetTextSecondaryVec4() { return ConvertColorToImVec4(GetTextSecondary()); } - inline ImVec4 GetTextDisabledVec4() { return ConvertColorToImVec4(GetTextDisabled()); } - inline ImVec4 GetShadowVec4() { return ConvertColorToImVec4(GetShadow()); } -} // namespace gui +// Material Design color accessors - global convenience functions +inline Color GetThemeColor(const std::string& color_name) { + return ThemeManager::Get().GetThemeColor(color_name); +} -} // namespace yaze +inline ImVec4 GetThemeColorVec4(const std::string& color_name) { + return ThemeManager::Get().GetThemeColorVec4(color_name); +} -#endif // YAZE_APP_GUI_THEME_MANAGER_H +// Material Design color accessors +inline Color GetPrimary() { + return ThemeManager::Get().GetPrimary(); +} +inline Color GetPrimaryHover() { + return ThemeManager::Get().GetPrimaryHover(); +} +inline Color GetPrimaryActive() { + return ThemeManager::Get().GetPrimaryActive(); +} +inline Color GetSecondary() { + return ThemeManager::Get().GetSecondary(); +} +inline Color GetSurface() { + return ThemeManager::Get().GetSurface(); +} +inline Color GetSurfaceVariant() { + return ThemeManager::Get().GetSurfaceVariant(); +} +inline Color GetSurfaceContainer() { + return ThemeManager::Get().GetSurfaceContainer(); +} +inline Color GetSurfaceContainerHigh() { + return ThemeManager::Get().GetSurfaceContainerHigh(); +} +inline Color GetSurfaceContainerHighest() { + return ThemeManager::Get().GetSurfaceContainerHighest(); +} +inline Color GetOnSurface() { + return ThemeManager::Get().GetOnSurface(); +} +inline Color GetOnSurfaceVariant() { + return ThemeManager::Get().GetOnSurfaceVariant(); +} +inline Color GetOnPrimary() { + return ThemeManager::Get().GetOnPrimary(); +} +inline Color GetOutline() { + return ThemeManager::Get().GetOutline(); +} +inline Color GetTextSecondary() { + return ThemeManager::Get().GetTextSecondary(); +} +inline Color GetTextDisabled() { + return ThemeManager::Get().GetTextDisabled(); +} +inline Color GetShadow() { + return ThemeManager::Get().GetShadow(); +} + +// ImVec4 versions for direct ImGui usage +inline ImVec4 GetPrimaryVec4() { + return ConvertColorToImVec4(GetPrimary()); +} +inline ImVec4 GetPrimaryHoverVec4() { + return ConvertColorToImVec4(GetPrimaryHover()); +} +inline ImVec4 GetPrimaryActiveVec4() { + return ConvertColorToImVec4(GetPrimaryActive()); +} +inline ImVec4 GetSurfaceVec4() { + return ConvertColorToImVec4(GetSurface()); +} +inline ImVec4 GetSurfaceVariantVec4() { + return ConvertColorToImVec4(GetSurfaceVariant()); +} +inline ImVec4 GetSurfaceContainerVec4() { + return ConvertColorToImVec4(GetSurfaceContainer()); +} +inline ImVec4 GetSurfaceContainerHighVec4() { + return ConvertColorToImVec4(GetSurfaceContainerHigh()); +} +inline ImVec4 GetSurfaceContainerHighestVec4() { + return ConvertColorToImVec4(GetSurfaceContainerHighest()); +} +inline ImVec4 GetOnSurfaceVec4() { + return ConvertColorToImVec4(GetOnSurface()); +} +inline ImVec4 GetOnSurfaceVariantVec4() { + return ConvertColorToImVec4(GetOnSurfaceVariant()); +} +inline ImVec4 GetOnPrimaryVec4() { + return ConvertColorToImVec4(GetOnPrimary()); +} +inline ImVec4 GetOutlineVec4() { + return ConvertColorToImVec4(GetOutline()); +} +inline ImVec4 GetTextSecondaryVec4() { + return ConvertColorToImVec4(GetTextSecondary()); +} +inline ImVec4 GetTextDisabledVec4() { + return ConvertColorToImVec4(GetTextDisabled()); +} +inline ImVec4 GetShadowVec4() { + return ConvertColorToImVec4(GetShadow()); +} +} // namespace gui + +} // namespace yaze + +#endif // YAZE_APP_GUI_THEME_MANAGER_H diff --git a/src/app/gui/core/ui_helpers.cc b/src/app/gui/core/ui_helpers.cc index 54f3c3d7..ed02f7da 100644 --- a/src/app/gui/core/ui_helpers.cc +++ b/src/app/gui/core/ui_helpers.cc @@ -1,8 +1,8 @@ #include "app/gui/core/ui_helpers.h" #include "absl/strings/str_format.h" -#include "app/gui/core/icons.h" #include "app/gui/core/color.h" +#include "app/gui/core/icons.h" #include "app/gui/core/theme_manager.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" @@ -60,7 +60,7 @@ ImVec4 GetItemColor() { } ImVec4 GetSpriteColor() { - // Bright magenta for sprites + // Bright magenta for sprites return ImVec4(1.0f, 0.3f, 1.0f, 0.85f); // Bright magenta, high visibility } @@ -108,10 +108,10 @@ void EndField() { ImGui::EndGroup(); } -bool BeginPropertyTable(const char* id, int columns, ImGuiTableFlags extra_flags) { - ImGuiTableFlags flags = ImGuiTableFlags_Borders | - ImGuiTableFlags_SizingFixedFit | - extra_flags; +bool BeginPropertyTable(const char* id, int columns, + ImGuiTableFlags extra_flags) { + ImGuiTableFlags flags = + ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | extra_flags; if (ImGui::BeginTable(id, columns, flags)) { ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); @@ -169,40 +169,53 @@ bool IconButton(const char* icon, const char* label, const ImVec2& size) { bool ColoredButton(const char* label, ButtonType type, const ImVec2& size) { ImVec4 color; switch (type) { - case ButtonType::Success: color = GetSuccessColor(); break; - case ButtonType::Warning: color = GetWarningColor(); break; - case ButtonType::Error: color = GetErrorColor(); break; - case ButtonType::Info: color = GetInfoColor(); break; - default: color = GetThemeColor(ImGuiCol_Button); break; + case ButtonType::Success: + color = GetSuccessColor(); + break; + case ButtonType::Warning: + color = GetWarningColor(); + break; + case ButtonType::Error: + color = GetErrorColor(); + break; + case ButtonType::Info: + color = GetInfoColor(); + break; + default: + color = GetThemeColor(ImGuiCol_Button); + break; } - + ImGui::PushStyleColor(ImGuiCol_Button, color); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, - ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, - ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w)); - + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w)); + ImGui::PushStyleColor( + ImGuiCol_ButtonActive, + ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w)); + bool result = ImGui::Button(label, size); - + ImGui::PopStyleColor(3); return result; } -bool ToggleIconButton(const char* icon_on, const char* icon_off, - bool* state, const char* tooltip) { +bool ToggleIconButton(const char* icon_on, const char* icon_off, bool* state, + const char* tooltip) { const char* icon = *state ? icon_on : icon_off; ImVec4 color = *state ? GetSuccessColor() : GetThemeColor(ImGuiCol_Button); - + ImGui::PushStyleColor(ImGuiCol_Button, color); bool result = ImGui::SmallButton(icon); ImGui::PopStyleColor(); - - if (result) *state = !*state; - + + if (result) + *state = !*state; + if (tooltip && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } - + return result; } @@ -224,13 +237,23 @@ void SeparatorText(const char* label) { void StatusBadge(const char* text, ButtonType type) { ImVec4 color; switch (type) { - case ButtonType::Success: color = GetSuccessColor(); break; - case ButtonType::Warning: color = GetWarningColor(); break; - case ButtonType::Error: color = GetErrorColor(); break; - case ButtonType::Info: color = GetInfoColor(); break; - default: color = GetThemeColor(ImGuiCol_Text); break; + case ButtonType::Success: + color = GetSuccessColor(); + break; + case ButtonType::Warning: + color = GetWarningColor(); + break; + case ButtonType::Error: + color = GetErrorColor(); + break; + case ButtonType::Info: + color = GetInfoColor(); + break; + default: + color = GetThemeColor(ImGuiCol_Text); + break; } - + ImGui::PushStyleColor(ImGuiCol_Button, color); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 10.0f); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 3)); @@ -254,30 +277,30 @@ void EndToolset() { } void ToolsetButton(const char* icon, bool selected, const char* tooltip, - std::function on_click) { + std::function on_click) { ImGui::TableNextColumn(); - + if (selected) { ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor()); } - + if (ImGui::Button(icon)) { - if (on_click) on_click(); + if (on_click) + on_click(); } - + if (selected) { ImGui::PopStyleColor(); } - + if (tooltip && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } } void BeginCanvasContainer(const char* id, bool scrollable) { - ImGuiWindowFlags flags = scrollable ? - ImGuiWindowFlags_AlwaysVerticalScrollbar : - ImGuiWindowFlags_None; + ImGuiWindowFlags flags = scrollable ? ImGuiWindowFlags_AlwaysVerticalScrollbar + : ImGuiWindowFlags_None; ImGui::BeginChild(id, ImVec2(0, 0), true, flags); } @@ -292,28 +315,28 @@ bool EditorTabItem(const char* icon, const char* label, bool* p_open) { } bool ConfirmationDialog(const char* id, const char* title, const char* message, - const char* confirm_text, const char* cancel_text) { + const char* confirm_text, const char* cancel_text) { bool confirmed = false; - + if (ImGui::BeginPopupModal(id, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("%s", title); ImGui::Separator(); ImGui::TextWrapped("%s", message); ImGui::Separator(); - + if (ColoredButton(confirm_text, ButtonType::Warning, ImVec2(120, 0))) { confirmed = true; ImGui::CloseCurrentPopup(); } - + ImGui::SameLine(); if (ImGui::Button(cancel_text, ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - + ImGui::EndPopup(); } - + return confirmed; } @@ -322,20 +345,21 @@ bool ConfirmationDialog(const char* id, const char* title, const char* message, // ============================================================================ void StatusIndicator(const char* label, bool active, const char* tooltip) { - ImVec4 color = active ? GetSuccessColor() : GetThemeColor(ImGuiCol_TextDisabled); - + ImVec4 color = + active ? GetSuccessColor() : GetThemeColor(ImGuiCol_TextDisabled); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 pos = ImGui::GetCursorScreenPos(); float radius = 5.0f; - + pos.x += radius + 3; pos.y += ImGui::GetTextLineHeight() * 0.5f; - + draw_list->AddCircleFilled(pos, radius, ImGui::GetColorU32(color)); - + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8); ImGui::Text("%s", label); - + if (tooltip && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", tooltip); } @@ -344,7 +368,7 @@ void StatusIndicator(const char* label, bool active, const char* tooltip) { void RomVersionBadge(const char* version, bool is_vanilla) { ImVec4 color = is_vanilla ? GetWarningColor() : GetSuccessColor(); const char* icon = is_vanilla ? ICON_MD_INFO : ICON_MD_CHECK_CIRCLE; - + ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::Text("%s %s", icon, version); ImGui::PopStyleColor(); @@ -378,7 +402,7 @@ void CenterText(const char* text) { } void RightAlign(float width) { - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width); } @@ -387,31 +411,32 @@ void RightAlign(float width) { // ============================================================================ float GetPulseAlpha(float speed) { - return 0.5f + 0.5f * sinf(static_cast(ImGui::GetTime()) * speed * 2.0f); + return 0.5f + + 0.5f * sinf(static_cast(ImGui::GetTime()) * speed * 2.0f); } float GetFadeIn(float duration) { static double start_time = 0.0; double current_time = ImGui::GetTime(); - + if (start_time == 0.0) { start_time = current_time; } - + float elapsed = static_cast(current_time - start_time); float alpha = ImClamp(elapsed / duration, 0.0f, 1.0f); - + // Reset after complete if (alpha >= 1.0f) { start_time = 0.0; } - + return alpha; } void PushPulseEffect(float speed) { - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, - ImGui::GetStyle().Alpha * GetPulseAlpha(speed)); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, + ImGui::GetStyle().Alpha * GetPulseAlpha(speed)); } void PopPulseEffect() { @@ -423,17 +448,17 @@ void LoadingSpinner(const char* label, float radius) { ImVec2 pos = ImGui::GetCursorScreenPos(); pos.x += radius + 4; pos.y += radius + 4; - + const float rotation = static_cast(ImGui::GetTime()) * 3.0f; const int segments = 16; const float thickness = 3.0f; - + const float start_angle = rotation; const float end_angle = rotation + IM_PI * 1.5f; - + draw_list->PathArcTo(pos, radius, start_angle, end_angle, segments); draw_list->PathStroke(ImGui::GetColorU32(GetAccentColor()), 0, thickness); - + if (label) { ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8); ImGui::Text("%s", label); @@ -449,38 +474,41 @@ void LoadingSpinner(const char* label, float radius) { float GetResponsiveWidth(float min_width, float max_width, float ratio) { float available = ImGui::GetContentRegionAvail().x; float target = available * ratio; - - if (target < min_width) return min_width; - if (target > max_width) return max_width; + + if (target < min_width) + return min_width; + if (target > max_width) + return max_width; return target; } void SetupResponsiveColumns(int count, float min_col_width) { float available = ImGui::GetContentRegionAvail().x; float col_width = available / count; - + if (col_width < min_col_width) { col_width = min_col_width; } - + for (int i = 0; i < count; ++i) { - ImGui::TableSetupColumn("##col", ImGuiTableColumnFlags_WidthFixed, col_width); + ImGui::TableSetupColumn("##col", ImGuiTableColumnFlags_WidthFixed, + col_width); } } static int g_two_col_table_active = 0; void BeginTwoColumns(const char* id, float split_ratio) { - ImGuiTableFlags flags = ImGuiTableFlags_Resizable | - ImGuiTableFlags_BordersInnerV | - ImGuiTableFlags_SizingStretchProp; - + ImGuiTableFlags flags = ImGuiTableFlags_Resizable | + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_SizingStretchProp; + if (ImGui::BeginTable(id, 2, flags)) { float available = ImGui::GetContentRegionAvail().x; - ImGui::TableSetupColumn("##left", ImGuiTableColumnFlags_None, - available * split_ratio); + ImGui::TableSetupColumn("##left", ImGuiTableColumnFlags_None, + available * split_ratio); ImGui::TableSetupColumn("##right", ImGuiTableColumnFlags_None, - available * (1.0f - split_ratio)); + available * (1.0f - split_ratio)); ImGui::TableNextRow(); ImGui::TableNextColumn(); g_two_col_table_active++; @@ -503,9 +531,9 @@ void EndTwoColumns() { bool LabeledInputHex(const char* label, uint8_t* value) { BeginField(label); ImGui::PushItemWidth(60); - bool changed = ImGui::InputScalar("##hex", ImGuiDataType_U8, value, nullptr, - nullptr, "%02X", - ImGuiInputTextFlags_CharsHexadecimal); + bool changed = + ImGui::InputScalar("##hex", ImGuiDataType_U8, value, nullptr, nullptr, + "%02X", ImGuiInputTextFlags_CharsHexadecimal); ImGui::PopItemWidth(); EndField(); return changed; @@ -514,20 +542,20 @@ bool LabeledInputHex(const char* label, uint8_t* value) { bool LabeledInputHex(const char* label, uint16_t* value) { BeginField(label); ImGui::PushItemWidth(80); - bool changed = ImGui::InputScalar("##hex", ImGuiDataType_U16, value, nullptr, - nullptr, "%04X", - ImGuiInputTextFlags_CharsHexadecimal); + bool changed = + ImGui::InputScalar("##hex", ImGuiDataType_U16, value, nullptr, nullptr, + "%04X", ImGuiInputTextFlags_CharsHexadecimal); ImGui::PopItemWidth(); EndField(); return changed; } bool IconCombo(const char* icon, const char* label, int* current, - const char* const items[], int count) { + const char* const items[], int count) { ImGui::Text("%s", icon); ImGui::SameLine(); return ImGui::Combo(label, current, items, count); } -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze diff --git a/src/app/gui/core/ui_helpers.h b/src/app/gui/core/ui_helpers.h index c6091010..8556b764 100644 --- a/src/app/gui/core/ui_helpers.h +++ b/src/app/gui/core/ui_helpers.h @@ -1,9 +1,9 @@ #ifndef YAZE_APP_GUI_UI_HELPERS_H #define YAZE_APP_GUI_UI_HELPERS_H -#include "imgui/imgui.h" -#include #include +#include +#include "imgui/imgui.h" namespace yaze { namespace gui { @@ -47,8 +47,8 @@ void BeginField(const char* label, float label_width = 0.0f); void EndField(); // Property table pattern (common in editors) -bool BeginPropertyTable(const char* id, int columns = 2, - ImGuiTableFlags extra_flags = 0); +bool BeginPropertyTable(const char* id, int columns = 2, + ImGuiTableFlags extra_flags = 0); void EndPropertyTable(); // Property row helpers @@ -58,25 +58,25 @@ void PropertyRowHex(const char* label, uint8_t value); void PropertyRowHex(const char* label, uint16_t value); // Section headers with icons -void SectionHeader(const char* icon, const char* label, - const ImVec4& color = ImVec4(1, 1, 1, 1)); +void SectionHeader(const char* icon, const char* label, + const ImVec4& color = ImVec4(1, 1, 1, 1)); // ============================================================================ // Common Widget Patterns // ============================================================================ // Button with icon -bool IconButton(const char* icon, const char* label, - const ImVec2& size = ImVec2(0, 0)); +bool IconButton(const char* icon, const char* label, + const ImVec2& size = ImVec2(0, 0)); // Colored button for status actions enum class ButtonType { Default, Success, Warning, Error, Info }; bool ColoredButton(const char* label, ButtonType type, - const ImVec2& size = ImVec2(0, 0)); + const ImVec2& size = ImVec2(0, 0)); // Toggle button with visual state -bool ToggleIconButton(const char* icon_on, const char* icon_off, - bool* state, const char* tooltip = nullptr); +bool ToggleIconButton(const char* icon_on, const char* icon_off, bool* state, + const char* tooltip = nullptr); // Help marker with tooltip void HelpMarker(const char* desc); @@ -95,7 +95,7 @@ void StatusBadge(const char* text, ButtonType type = ButtonType::Default); void BeginToolset(const char* id); void EndToolset(); void ToolsetButton(const char* icon, bool selected, const char* tooltip, - std::function on_click); + std::function on_click); // Canvas container patterns void BeginCanvasContainer(const char* id, bool scrollable = true); @@ -106,8 +106,8 @@ bool EditorTabItem(const char* icon, const char* label, bool* p_open = nullptr); // Modal confirmation dialog bool ConfirmationDialog(const char* id, const char* title, const char* message, - const char* confirm_text = "OK", - const char* cancel_text = "Cancel"); + const char* confirm_text = "OK", + const char* cancel_text = "Cancel"); // ============================================================================ // Visual Indicators @@ -115,7 +115,7 @@ bool ConfirmationDialog(const char* id, const char* title, const char* message, // Status indicator dot + label void StatusIndicator(const char* label, bool active, - const char* tooltip = nullptr); + const char* tooltip = nullptr); // ROM version badge void RomVersionBadge(const char* version, bool is_vanilla); @@ -174,9 +174,9 @@ bool LabeledInputHex(const char* label, uint16_t* value); // Combo with icon bool IconCombo(const char* icon, const char* label, int* current, - const char* const items[], int count); + const char* const items[], int count); -} // namespace gui -} // namespace yaze +} // namespace gui +} // namespace yaze -#endif // YAZE_APP_GUI_UI_HELPERS_H +#endif // YAZE_APP_GUI_UI_HELPERS_H diff --git a/src/app/gui/widgets/asset_browser.cc b/src/app/gui/widgets/asset_browser.cc index 9f36665f..1fac8027 100644 --- a/src/app/gui/widgets/asset_browser.cc +++ b/src/app/gui/widgets/asset_browser.cc @@ -87,7 +87,8 @@ void GfxSheetAssetBrowser::Draw( // - Enable box-select (in 2D mode, so that changing box-select rectangle // X1/X2 boundaries will affect clipped items) - if (AllowBoxSelect) ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d; + if (AllowBoxSelect) + ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d; // - This feature allows dragging an unselected item without selecting it // (rarely used) @@ -180,10 +181,12 @@ void GfxSheetAssetBrowser::Draw( // Update our selection state immediately (without waiting for // EndMultiSelect() requests) because we use this to alter the color // of our text/icon. - if (IsItemToggledSelection()) item_is_selected = !item_is_selected; + if (IsItemToggledSelection()) + item_is_selected = !item_is_selected; // Focus (for after deletion) - if (item_curr_idx_to_focus == item_idx) SetKeyboardFocusHere(-1); + if (item_curr_idx_to_focus == item_idx) + SetKeyboardFocusHere(-1); // Drag and drop if (BeginDragDropSource()) { @@ -263,22 +266,26 @@ void GfxSheetAssetBrowser::Draw( if (MenuItem("Unsorted")) { void* it = NULL; ImGuiID id = 0; - while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 0; + while (Selection.GetNextSelectedItem(&it, &id)) + Items[id].Type = 0; } if (MenuItem("Dungeon")) { void* it = NULL; ImGuiID id = 0; - while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 1; + while (Selection.GetNextSelectedItem(&it, &id)) + Items[id].Type = 1; } if (MenuItem("Overworld")) { void* it = NULL; ImGuiID id = 0; - while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 2; + while (Selection.GetNextSelectedItem(&it, &id)) + Items[id].Type = 2; } if (MenuItem("Sprite")) { void* it = NULL; ImGuiID id = 0; - while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 3; + while (Selection.GetNextSelectedItem(&it, &id)) + Items[id].Type = 3; } EndMenu(); } @@ -294,7 +301,8 @@ void GfxSheetAssetBrowser::Draw( Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus); // Zooming with CTRL+Wheel - if (IsWindowAppearing()) ZoomWheelAccum = 0.0f; + if (IsWindowAppearing()) + ZoomWheelAccum = 0.0f; if (IsWindowHovered() && io.MouseWheel != 0.0f && IsKeyDown(ImGuiMod_Ctrl) && IsAnyItemActive() == false) { ZoomWheelAccum += io.MouseWheel; diff --git a/src/app/gui/widgets/asset_browser.h b/src/app/gui/widgets/asset_browser.h index 79553b9c..9edf2446 100644 --- a/src/app/gui/widgets/asset_browser.h +++ b/src/app/gui/widgets/asset_browser.h @@ -31,14 +31,14 @@ struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage { // FIXME-MULTISELECT: Doesn't take account of the possibility focus target // will be moved during deletion. Need refocus or scroll offset. int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, int items_count) { - if (Size == 0) return -1; + if (Size == 0) + return -1; // If focused item is not selected... const int focused_idx = - (int)ms_io->NavIdItem; // Index of currently focused item - if (ms_io->NavIdSelected == - false) // This is merely a shortcut, == - // Contains(adapter->IndexToStorage(items, focused_idx)) + (int)ms_io->NavIdItem; // Index of currently focused item + if (ms_io->NavIdSelected == false) // This is merely a shortcut, == + // Contains(adapter->IndexToStorage(items, focused_idx)) { ms_io->RangeSrcReset = true; // Request to recover RangeSrc from NavId next frame. Would be @@ -51,12 +51,14 @@ struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage { // If focused item is selected: land on first unselected item after focused // item. for (int idx = focused_idx + 1; idx < items_count; idx++) - if (!Contains(GetStorageIdFromIndex(idx))) return idx; + if (!Contains(GetStorageIdFromIndex(idx))) + return idx; // If focused item is selected: otherwise return last unselected item before // focused item. for (int idx = IM_MIN(focused_idx, items_count) - 1; idx >= 0; idx--) - if (!Contains(GetStorageIdFromIndex(idx))) return idx; + if (!Contains(GetStorageIdFromIndex(idx))) + return idx; return -1; } @@ -196,7 +198,8 @@ struct GfxSheetAssetBrowser { } void AddItems(int count) { - if (Items.Size == 0) NextItemId = 0; + if (Items.Size == 0) + NextItemId = 0; Items.reserve(Items.Size + count); for (int n = 0; n < count; n++, NextItemId++) Items.push_back(AssetObject(NextItemId, (NextItemId % 20) < 15 ? 0 diff --git a/src/app/gui/widgets/dungeon_object_emulator_preview.cc b/src/app/gui/widgets/dungeon_object_emulator_preview.cc index aaf5f891..09579520 100644 --- a/src/app/gui/widgets/dungeon_object_emulator_preview.cc +++ b/src/app/gui/widgets/dungeon_object_emulator_preview.cc @@ -1,11 +1,11 @@ #include "app/gui/widgets/dungeon_object_emulator_preview.h" #include "app/gfx/backend/irenderer.h" -#include "zelda3/dungeon/room.h" -#include "zelda3/dungeon/room_object.h" +#include #include "app/gui/automation/widget_auto_register.h" #include "app/platform/window.h" -#include +#include "zelda3/dungeon/room.h" +#include "zelda3/dungeon/room_object.h" namespace yaze { namespace gui { @@ -20,7 +20,8 @@ DungeonObjectEmulatorPreview::~DungeonObjectEmulatorPreview() { // } } -void DungeonObjectEmulatorPreview::Initialize(gfx::IRenderer* renderer, Rom* rom) { +void DungeonObjectEmulatorPreview::Initialize(gfx::IRenderer* renderer, + Rom* rom) { renderer_ = renderer; rom_ = rom; snes_instance_ = std::make_unique(); @@ -31,9 +32,11 @@ void DungeonObjectEmulatorPreview::Initialize(gfx::IRenderer* renderer, Rom* rom } void DungeonObjectEmulatorPreview::Render() { - if (!show_window_) return; + if (!show_window_) + return; - if (ImGui::Begin("Dungeon Object Emulator Preview", &show_window_, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::Begin("Dungeon Object Emulator Preview", &show_window_, + ImGuiWindowFlags_AlwaysAutoResize)) { AutoWidgetScope scope("DungeonEditor/EmulatorPreview"); // ROM status indicator @@ -42,42 +45,46 @@ void DungeonObjectEmulatorPreview::Render() { } else { ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "ROM: Not loaded ✗"); } - + ImGui::Separator(); RenderControls(); ImGui::Separator(); // Preview image with border if (object_texture_) { - ImGui::BeginChild("PreviewRegion", ImVec2(260, 260), true, ImGuiWindowFlags_NoScrollbar); + ImGui::BeginChild("PreviewRegion", ImVec2(260, 260), true, + ImGuiWindowFlags_NoScrollbar); ImGui::Image((ImTextureID)object_texture_, ImVec2(256, 256)); ImGui::EndChild(); } else { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "No texture available"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + "No texture available"); } - + // Debug info section ImGui::Separator(); ImGui::Text("Execution:"); ImGui::Indent(); - ImGui::Text("Cycles: %d %s", last_cycle_count_, + ImGui::Text("Cycles: %d %s", last_cycle_count_, last_cycle_count_ >= 100000 ? "(TIMEOUT)" : ""); ImGui::Unindent(); - + // Status with color coding ImGui::Text("Status:"); ImGui::Indent(); if (last_error_.empty()) { ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "✓ OK"); } else { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "✗ %s", last_error_.c_str()); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "✗ %s", + last_error_.c_str()); } ImGui::Unindent(); - + // Help text ImGui::Separator(); - ImGui::TextWrapped("This tool uses the SNES emulator to render objects by " - "executing the game's native drawing routines from bank $01."); + ImGui::TextWrapped( + "This tool uses the SNES emulator to render objects by " + "executing the game's native drawing routines from bank $01."); } ImGui::End(); } @@ -85,34 +92,44 @@ void DungeonObjectEmulatorPreview::Render() { void DungeonObjectEmulatorPreview::RenderControls() { ImGui::Text("Object Configuration:"); ImGui::Indent(); - + // Object ID with hex display - AutoInputInt("Object ID", &object_id_, 1, 10, ImGuiInputTextFlags_CharsHexadecimal); + AutoInputInt("Object ID", &object_id_, 1, 10, + ImGuiInputTextFlags_CharsHexadecimal); ImGui::SameLine(); ImGui::TextDisabled("($%03X)", object_id_); - + // Room context AutoInputInt("Room Context", &room_id_, 1, 10); ImGui::SameLine(); ImGui::TextDisabled("(for graphics/palette)"); - + // Position controls AutoSliderInt("X Position", &object_x_, 0, 63); AutoSliderInt("Y Position", &object_y_, 0, 63); - + ImGui::Unindent(); - + // Render button - large and prominent ImGui::Separator(); if (ImGui::Button("Render Object", ImVec2(-1, 0))) { TriggerEmulatedRender(); } - + // Quick test buttons if (ImGui::BeginPopup("QuickTests")) { - if (ImGui::MenuItem("Floor tile (0x00)")) { object_id_ = 0x00; TriggerEmulatedRender(); } - if (ImGui::MenuItem("Wall N (0x60)")) { object_id_ = 0x60; TriggerEmulatedRender(); } - if (ImGui::MenuItem("Door (0xF0)")) { object_id_ = 0xF0; TriggerEmulatedRender(); } + if (ImGui::MenuItem("Floor tile (0x00)")) { + object_id_ = 0x00; + TriggerEmulatedRender(); + } + if (ImGui::MenuItem("Wall N (0x60)")) { + object_id_ = 0x60; + TriggerEmulatedRender(); + } + if (ImGui::MenuItem("Door (0xF0)")) { + object_id_ = 0xF0; + TriggerEmulatedRender(); + } ImGui::EndPopup(); } if (ImGui::Button("Quick Tests...", ImVec2(-1, 0))) { @@ -140,14 +157,16 @@ void DungeonObjectEmulatorPreview::TriggerEmulatedRender() { // 3. Load palette into CGRAM auto dungeon_main_pal_group = rom_->palette_group().dungeon_main; - + // Validate and clamp palette ID int palette_id = default_room.palette; - if (palette_id < 0 || palette_id >= static_cast(dungeon_main_pal_group.size())) { - printf("[EMU] Warning: Room palette %d out of bounds, using palette 0\n", palette_id); + if (palette_id < 0 || + palette_id >= static_cast(dungeon_main_pal_group.size())) { + printf("[EMU] Warning: Room palette %d out of bounds, using palette 0\n", + palette_id); palette_id = 0; } - + auto palette = dungeon_main_pal_group[palette_id]; for (size_t i = 0; i < palette.size() && i < 256; ++i) { ppu.cgram[i] = palette[i].snes(); @@ -223,17 +242,18 @@ void DungeonObjectEmulatorPreview::TriggerEmulatedRender() { } if (table_addr < rom_->size() - 1) { - uint8_t lo = rom_data[table_addr]; - uint8_t hi = rom_data[table_addr + 1]; - handler_offset = lo | (hi << 8); + uint8_t lo = rom_data[table_addr]; + uint8_t hi = rom_data[table_addr + 1]; + handler_offset = lo | (hi << 8); } else { - last_error_ = "Object ID out of bounds for handler lookup"; - return; + last_error_ = "Object ID out of bounds for handler lookup"; + return; } if (handler_offset == 0x0000) { char buf[256]; - snprintf(buf, sizeof(buf), "Object $%04X has no drawing routine", object_id_); + snprintf(buf, sizeof(buf), "Object $%04X has no drawing routine", + object_id_); last_error_ = buf; return; } @@ -244,7 +264,7 @@ void DungeonObjectEmulatorPreview::TriggerEmulatedRender() { // Push return address for RTL (3 bytes: bank, high, low) uint16_t sp = cpu.SP(); - snes_instance_->Write(0x010000 | sp--, 0x01); // Bank byte + snes_instance_->Write(0x010000 | sp--, 0x01); // Bank byte snes_instance_->Write(0x010000 | sp--, (return_addr - 1) >> 8); // High snes_instance_->Write(0x010000 | sp--, (return_addr - 1) & 0xFF); // Low cpu.SetSP(sp); @@ -252,8 +272,8 @@ void DungeonObjectEmulatorPreview::TriggerEmulatedRender() { // Jump to handler (offset is relative to RoomDrawObjectData base) cpu.PC = handler_offset; - printf("[EMU] Rendering object $%04X at (%d,%d), handler=$%04X\n", - object_id_, object_x_, object_y_, handler_offset); + printf("[EMU] Rendering object $%04X at (%d,%d), handler=$%04X\n", object_id_, + object_x_, object_y_, handler_offset); // 13. Run emulator with timeout int max_cycles = 100000; @@ -268,8 +288,8 @@ void DungeonObjectEmulatorPreview::TriggerEmulatedRender() { last_cycle_count_ = cycles; - printf("[EMU] Completed after %d cycles, PC=$%02X:%04X\n", - cycles, cpu.PB, cpu.PC); + printf("[EMU] Completed after %d cycles, PC=$%02X:%04X\n", cycles, cpu.PB, + cpu.PC); if (cycles >= max_cycles) { last_error_ = "Timeout: exceeded max cycles"; diff --git a/src/app/gui/widgets/dungeon_object_emulator_preview.h b/src/app/gui/widgets/dungeon_object_emulator_preview.h index 4fd9fdf8..9e889565 100644 --- a/src/app/gui/widgets/dungeon_object_emulator_preview.h +++ b/src/app/gui/widgets/dungeon_object_emulator_preview.h @@ -7,8 +7,8 @@ namespace yaze { namespace gfx { class IRenderer; -} // namespace gfx -} +} // namespace gfx +} // namespace yaze namespace yaze { namespace gui { @@ -35,7 +35,7 @@ class DungeonObjectEmulatorPreview { int object_x_ = 16; int object_y_ = 16; bool show_window_ = true; - + // Debug info int last_cycle_count_ = 0; std::string last_error_; diff --git a/src/app/gui/widgets/palette_editor_widget.cc b/src/app/gui/widgets/palette_editor_widget.cc index 44fa79ea..852e418e 100644 --- a/src/app/gui/widgets/palette_editor_widget.cc +++ b/src/app/gui/widgets/palette_editor_widget.cc @@ -13,7 +13,7 @@ namespace gui { // Merged implementation from PaletteWidget and PaletteEditorWidget -void PaletteEditorWidget::Initialize(Rom *rom) { +void PaletteEditorWidget::Initialize(Rom* rom) { rom_ = rom; rom_palettes_loaded_ = false; if (rom_) { @@ -35,7 +35,7 @@ void PaletteEditorWidget::Draw() { DrawPaletteSelector(); ImGui::Separator(); - auto &dungeon_pal_group = rom_->mutable_palette_group()->dungeon_main; + auto& dungeon_pal_group = rom_->mutable_palette_group()->dungeon_main; if (current_palette_id_ >= 0 && current_palette_id_ < (int)dungeon_pal_group.size()) { auto palette = dungeon_pal_group[current_palette_id_]; @@ -55,7 +55,7 @@ void PaletteEditorWidget::Draw() { } void PaletteEditorWidget::DrawPaletteSelector() { - auto &dungeon_pal_group = rom_->mutable_palette_group()->dungeon_main; + auto& dungeon_pal_group = rom_->mutable_palette_group()->dungeon_main; int num_palettes = dungeon_pal_group.size(); ImGui::Text("Dungeon Palette:"); @@ -83,13 +83,13 @@ void PaletteEditorWidget::DrawColorPicker() { ImGui::SeparatorText( absl::StrFormat("Edit Color %d", selected_color_index_).c_str()); - auto &dungeon_pal_group = rom_->mutable_palette_group()->dungeon_main; + auto& dungeon_pal_group = rom_->mutable_palette_group()->dungeon_main; auto palette = dungeon_pal_group[current_palette_id_]; auto original_color = palette[selected_color_index_]; - if (ImGui::ColorEdit3("Color", &editing_color_.x, - ImGuiColorEditFlags_NoAlpha | - ImGuiColorEditFlags_PickerHueWheel)) { + if (ImGui::ColorEdit3( + "Color", &editing_color_.x, + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_PickerHueWheel)) { int r = static_cast(editing_color_.x * 31.0f); int g = static_cast(editing_color_.y * 31.0f); int b = static_cast(editing_color_.z * 31.0f); @@ -108,16 +108,16 @@ void PaletteEditorWidget::DrawColorPicker() { ImGui::Text("SNES BGR555: 0x%04X", original_color.snes()); if (ImGui::Button("Reset to Original")) { - editing_color_ = ImVec4(original_color.rgb().x / 255.0f, - original_color.rgb().y / 255.0f, - original_color.rgb().z / 255.0f, 1.0f); + editing_color_ = + ImVec4(original_color.rgb().x / 255.0f, original_color.rgb().y / 255.0f, + original_color.rgb().z / 255.0f, 1.0f); } } // --- Modal/Popup Methods (from feature-rich widget) --- -void PaletteEditorWidget::ShowPaletteEditor(gfx::SnesPalette &palette, - const std::string &title) { +void PaletteEditorWidget::ShowPaletteEditor(gfx::SnesPalette& palette, + const std::string& title) { if (ImGui::BeginPopupModal(title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Enhanced Palette Editor"); @@ -160,7 +160,8 @@ void PaletteEditorWidget::ShowPaletteEditor(gfx::SnesPalette &palette, } void PaletteEditorWidget::ShowROMPaletteManager() { - if (!show_rom_manager_) return; + if (!show_rom_manager_) + return; if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) { if (!rom_) { @@ -180,17 +181,18 @@ void PaletteEditorWidget::ShowROMPaletteManager() { ImGui::Text("Preview of %s:", palette_group_names_[current_group_index_].c_str()); - const auto &preview_palette = rom_palette_groups_[current_group_index_]; - DrawPaletteGrid(const_cast(preview_palette)); + const auto& preview_palette = rom_palette_groups_[current_group_index_]; + DrawPaletteGrid(const_cast(preview_palette)); DrawPaletteAnalysis(preview_palette); } } ImGui::End(); } -void PaletteEditorWidget::ShowColorAnalysis(const gfx::Bitmap &bitmap, - const std::string &title) { - if (!show_color_analysis_) return; +void PaletteEditorWidget::ShowColorAnalysis(const gfx::Bitmap& bitmap, + const std::string& title) { + if (!show_color_analysis_) + return; if (ImGui::Begin(title.c_str(), &show_color_analysis_)) { ImGui::Text("Bitmap Color Analysis"); @@ -203,7 +205,7 @@ void PaletteEditorWidget::ShowColorAnalysis(const gfx::Bitmap &bitmap, } std::map pixel_counts; - const auto &data = bitmap.vector(); + const auto& data = bitmap.vector(); for (uint8_t pixel : data) { uint8_t palette_index = pixel & 0x0F; @@ -217,7 +219,7 @@ void PaletteEditorWidget::ShowColorAnalysis(const gfx::Bitmap &bitmap, ImGui::Text("Pixel Distribution:"); int total_pixels = static_cast(data.size()); - for (const auto &[index, count] : pixel_counts) { + for (const auto& [index, count] : pixel_counts) { float percentage = (static_cast(count) / total_pixels) * 100.0f; ImGui::Text("Index %d: %d pixels (%.1f%%)", index, count, percentage); @@ -244,7 +246,7 @@ void PaletteEditorWidget::ShowColorAnalysis(const gfx::Bitmap &bitmap, ImGui::End(); } -bool PaletteEditorWidget::ApplyROMPalette(gfx::Bitmap *bitmap, int group_index, +bool PaletteEditorWidget::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index) { if (!bitmap || !rom_palettes_loaded_ || group_index < 0 || group_index >= static_cast(rom_palette_groups_.size())) { @@ -252,7 +254,7 @@ bool PaletteEditorWidget::ApplyROMPalette(gfx::Bitmap *bitmap, int group_index, } try { - const auto &selected_palette = rom_palette_groups_[group_index]; + const auto& selected_palette = rom_palette_groups_[group_index]; SavePaletteBackup(bitmap->palette()); if (palette_index >= 0 && palette_index < 8) { @@ -267,12 +269,12 @@ bool PaletteEditorWidget::ApplyROMPalette(gfx::Bitmap *bitmap, int group_index, current_group_index_ = group_index; current_palette_index_ = palette_index; return true; - } catch (const std::exception &e) { + } catch (const std::exception& e) { return false; } } -const gfx::SnesPalette *PaletteEditorWidget::GetSelectedROMPalette() const { +const gfx::SnesPalette* PaletteEditorWidget::GetSelectedROMPalette() const { if (!rom_palettes_loaded_ || current_group_index_ < 0 || current_group_index_ >= static_cast(rom_palette_groups_.size())) { return nullptr; @@ -280,11 +282,11 @@ const gfx::SnesPalette *PaletteEditorWidget::GetSelectedROMPalette() const { return &rom_palette_groups_[current_group_index_]; } -void PaletteEditorWidget::SavePaletteBackup(const gfx::SnesPalette &palette) { +void PaletteEditorWidget::SavePaletteBackup(const gfx::SnesPalette& palette) { backup_palette_ = palette; } -bool PaletteEditorWidget::RestorePaletteBackup(gfx::SnesPalette &palette) { +bool PaletteEditorWidget::RestorePaletteBackup(gfx::SnesPalette& palette) { if (backup_palette_.size() == 0) { return false; } @@ -293,9 +295,10 @@ bool PaletteEditorWidget::RestorePaletteBackup(gfx::SnesPalette &palette) { } // Unified grid drawing function -void PaletteEditorWidget::DrawPaletteGrid(gfx::SnesPalette &palette, int cols) { +void PaletteEditorWidget::DrawPaletteGrid(gfx::SnesPalette& palette, int cols) { for (int i = 0; i < static_cast(palette.size()); i++) { - if (i % cols != 0) ImGui::SameLine(); + if (i % cols != 0) + ImGui::SameLine(); auto color = palette[i]; ImVec4 display_color = color.rgb(); @@ -339,9 +342,9 @@ void PaletteEditorWidget::DrawPaletteGrid(gfx::SnesPalette &palette, int cols) { if (ImGui::BeginPopupModal("Edit Color", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Editing Color %d", editing_color_index_); - if (ImGui::ColorEdit4("Color", &temp_color_.x, - ImGuiColorEditFlags_NoAlpha | - ImGuiColorEditFlags_DisplayRGB)) { + if (ImGui::ColorEdit4( + "Color", &temp_color_.x, + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) { auto new_snes_color = gfx::SnesColor(temp_color_); palette[editing_color_index_] = new_snes_color; } @@ -372,25 +375,26 @@ void PaletteEditorWidget::DrawROMPaletteSelector() { ImGui::Text("Palette Group:"); if (ImGui::Combo( "##PaletteGroup", ¤t_group_index_, - [](void *data, int idx, const char **out_text) -> bool { - auto *names = static_cast *>(data); - if (idx < 0 || idx >= static_cast(names->size())) return false; + [](void* data, int idx, const char** out_text) -> bool { + auto* names = static_cast*>(data); + if (idx < 0 || idx >= static_cast(names->size())) + return false; *out_text = (*names)[idx].c_str(); return true; }, &palette_group_names_, - static_cast(palette_group_names_.size()))) { - } + static_cast(palette_group_names_.size()))) {} ImGui::Text("Palette Index:"); ImGui::SliderInt("##PaletteIndex", ¤t_palette_index_, 0, 7, "%d"); if (current_group_index_ < static_cast(rom_palette_groups_.size())) { ImGui::Text("Preview:"); - const auto &preview_palette = rom_palette_groups_[current_group_index_]; + const auto& preview_palette = rom_palette_groups_[current_group_index_]; for (int i = 0; i < 8 && i < static_cast(preview_palette.size()); i++) { - if (i > 0) ImGui::SameLine(); + if (i > 0) + ImGui::SameLine(); auto color = preview_palette[i]; ImVec4 display_color = color.rgb(); ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(), @@ -400,15 +404,15 @@ void PaletteEditorWidget::DrawROMPaletteSelector() { } } -void PaletteEditorWidget::DrawColorEditControls(gfx::SnesColor &color, +void PaletteEditorWidget::DrawColorEditControls(gfx::SnesColor& color, int color_index) { ImVec4 rgba = color.rgb(); ImGui::PushID(color_index); - if (ImGui::ColorEdit4("##color_edit", &rgba.x, - ImGuiColorEditFlags_NoAlpha | - ImGuiColorEditFlags_DisplayRGB)) { + if (ImGui::ColorEdit4( + "##color_edit", &rgba.x, + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) { color = gfx::SnesColor(rgba); } @@ -434,8 +438,7 @@ void PaletteEditorWidget::DrawColorEditControls(gfx::SnesColor &color, ImGui::PopID(); } -void PaletteEditorWidget::DrawPaletteAnalysis( - const gfx::SnesPalette &palette) { +void PaletteEditorWidget::DrawPaletteAnalysis(const gfx::SnesPalette& palette) { ImGui::Text("Palette Information:"); ImGui::Text("Size: %zu colors", palette.size()); @@ -447,9 +450,10 @@ void PaletteEditorWidget::DrawPaletteAnalysis( ImGui::Text("Unique Colors: %zu", color_frequency.size()); if (color_frequency.size() < palette.size()) { - ImGui::TextColored(ImVec4(1, 1, 0, 1), "Warning: Duplicate colors detected!"); + ImGui::TextColored(ImVec4(1, 1, 0, 1), + "Warning: Duplicate colors detected!"); if (ImGui::TreeNode("Duplicate Colors")) { - for (const auto &[snes_color, count] : color_frequency) { + for (const auto& [snes_color, count] : color_frequency) { if (count > 1) { ImVec4 display_color = gfx::SnesColor(snes_color).rgb(); ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(), @@ -487,10 +491,11 @@ void PaletteEditorWidget::DrawPaletteAnalysis( } void PaletteEditorWidget::LoadROMPalettes() { - if (!rom_ || rom_palettes_loaded_) return; + if (!rom_ || rom_palettes_loaded_) + return; try { - const auto &palette_groups = rom_->palette_group(); + const auto& palette_groups = rom_->palette_group(); rom_palette_groups_.clear(); palette_group_names_.clear(); @@ -524,11 +529,10 @@ void PaletteEditorWidget::LoadROMPalettes() { } rom_palettes_loaded_ = true; - } catch (const std::exception &e) { + } catch (const std::exception& e) { LOG_ERROR("Enhanced Palette Editor", "Failed to load ROM palettes"); } } } // namespace gui } // namespace yaze - diff --git a/src/app/gui/widgets/palette_editor_widget.h b/src/app/gui/widgets/palette_editor_widget.h index b6ce2903..d70b35a2 100644 --- a/src/app/gui/widgets/palette_editor_widget.h +++ b/src/app/gui/widgets/palette_editor_widget.h @@ -17,22 +17,22 @@ class PaletteEditorWidget { public: PaletteEditorWidget() = default; - void Initialize(Rom *rom); + void Initialize(Rom* rom); // Embedded drawing function, like the old PaletteEditorWidget void Draw(); // Modal dialogs from the more feature-rich PaletteWidget - void ShowPaletteEditor(gfx::SnesPalette &palette, - const std::string &title = "Palette Editor"); + void ShowPaletteEditor(gfx::SnesPalette& palette, + const std::string& title = "Palette Editor"); void ShowROMPaletteManager(); - void ShowColorAnalysis(const gfx::Bitmap &bitmap, - const std::string &title = "Color Analysis"); + void ShowColorAnalysis(const gfx::Bitmap& bitmap, + const std::string& title = "Color Analysis"); - bool ApplyROMPalette(gfx::Bitmap *bitmap, int group_index, int palette_index); - const gfx::SnesPalette *GetSelectedROMPalette() const; - void SavePaletteBackup(const gfx::SnesPalette &palette); - bool RestorePaletteBackup(gfx::SnesPalette &palette); + bool ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index); + const gfx::SnesPalette* GetSelectedROMPalette() const; + void SavePaletteBackup(const gfx::SnesPalette& palette); + bool RestorePaletteBackup(gfx::SnesPalette& palette); // Callback when palette is modified void SetOnPaletteChanged(std::function callback) { @@ -48,16 +48,16 @@ class PaletteEditorWidget { void DrawROMPaletteSelector(); private: - void DrawPaletteGrid(gfx::SnesPalette &palette, int cols = 15); - void DrawColorEditControls(gfx::SnesColor &color, int color_index); - void DrawPaletteAnalysis(const gfx::SnesPalette &palette); + void DrawPaletteGrid(gfx::SnesPalette& palette, int cols = 15); + void DrawColorEditControls(gfx::SnesColor& color, int color_index); + void DrawPaletteAnalysis(const gfx::SnesPalette& palette); void LoadROMPalettes(); // For embedded view void DrawPaletteSelector(); void DrawColorPicker(); - Rom *rom_ = nullptr; + Rom* rom_ = nullptr; std::vector rom_palette_groups_; std::vector palette_group_names_; gfx::SnesPalette backup_palette_; @@ -85,4 +85,3 @@ class PaletteEditorWidget { } // namespace yaze #endif // YAZE_APP_GUI_WIDGETS_PALETTE_EDITOR_WIDGET_H - diff --git a/src/app/gui/widgets/text_editor.cc b/src/app/gui/widgets/text_editor.cc index e94dd34f..5691460c 100644 --- a/src/app/gui/widgets/text_editor.cc +++ b/src/app/gui/widgets/text_editor.cc @@ -16,7 +16,8 @@ template bool equals(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p) { for (; first1 != last1 && first2 != last2; ++first1, ++first2) { - if (!p(*first1, *first2)) return false; + if (!p(*first1, *first2)) + return false; } return first1 == last1 && first2 == last2; } @@ -65,7 +66,9 @@ void TextEditor::SetLanguageDefinition(const LanguageDefinition& aLanguageDef) { Colorize(); } -void TextEditor::SetPalette(const Palette& aValue) { mPaletteBase = aValue; } +void TextEditor::SetPalette(const Palette& aValue) { + mPaletteBase = aValue; +} std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const { @@ -77,12 +80,14 @@ std::string TextEditor::GetText(const Coordinates& aStart, auto iend = GetCharacterIndex(aEnd); size_t s = 0; - for (size_t i = lstart; i < lend; i++) s += mLines[i].size(); + for (size_t i = lstart; i < lend; i++) + s += mLines[i].size(); result.reserve(s + s / 8); while (istart < iend || lstart < lend) { - if (lstart >= (int)mLines.size()) break; + if (lstart >= (int)mLines.size()) + break; auto& line = mLines[lstart]; if (istart < (int)line.size()) { @@ -125,8 +130,10 @@ TextEditor::Coordinates TextEditor::SanitizeCoordinates( // We assume that the char is a standalone character (<128) or a leading byte of // an UTF-8 code sequence (non-10xxxxxx code) static int UTF8CharLength(TextEditor::Char c) { - if ((c & 0xFE) == 0xFC) return 6; - if ((c & 0xFC) == 0xF8) return 5; + if ((c & 0xFE) == 0xFC) + return 6; + if ((c & 0xFC) == 0xF8) + return 5; if ((c & 0xF8) == 0xF0) return 4; else if ((c & 0xF0) == 0xE0) @@ -143,7 +150,8 @@ static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) { return 1; } if (c < 0x800) { - if (buf_size < 2) return 0; + if (buf_size < 2) + return 0; buf[0] = (char)(0xc0 + (c >> 6)); buf[1] = (char)(0x80 + (c & 0x3f)); return 2; @@ -152,7 +160,8 @@ static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) { return 0; } if (c >= 0xd800 && c < 0xdc00) { - if (buf_size < 4) return 0; + if (buf_size < 4) + return 0; buf[0] = (char)(0xf0 + (c >> 18)); buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); @@ -161,7 +170,8 @@ static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) { } // else if (c < 0x10000) { - if (buf_size < 3) return 0; + if (buf_size < 3) + return 0; buf[0] = (char)(0xe0 + (c >> 12)); buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); buf[2] = (char)(0x80 + ((c) & 0x3f)); @@ -193,7 +203,8 @@ void TextEditor::DeleteRange(const Coordinates& aStart, // printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, // aEnd.mColumn); - if (aEnd == aStart) return; + if (aEnd == aStart) + return; auto start = GetCharacterIndex(aStart); auto end = GetCharacterIndex(aEnd); @@ -215,7 +226,8 @@ void TextEditor::DeleteRange(const Coordinates& aStart, if (aStart.mLine < aEnd.mLine) firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end()); - if (aStart.mLine < aEnd.mLine) RemoveLine(aStart.mLine + 1, aEnd.mLine + 1); + if (aStart.mLine < aEnd.mLine) + RemoveLine(aStart.mLine + 1, aEnd.mLine + 1); } mTextChanged = true; @@ -308,7 +320,8 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates( (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); columnWidth = newColumnX - oldX; - if (mTextStart + columnX + columnWidth * 0.5f > local.x) break; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; columnX = newColumnX; columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize; columnIndex++; @@ -316,13 +329,15 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates( char buf[7]; auto d = UTF8CharLength(line[columnIndex].mChar); int i = 0; - while (i < 6 && d-- > 0) buf[i++] = line[columnIndex++].mChar; + while (i < 6 && d-- > 0) + buf[i++] = line[columnIndex++].mChar; buf[i] = '\0'; columnWidth = ImGui::GetFont() ->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf) .x; - if (mTextStart + columnX + columnWidth * 0.5f > local.x) break; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; columnX += columnWidth; columnCoord++; } @@ -335,14 +350,17 @@ TextEditor::Coordinates TextEditor::ScreenPosToCoordinates( TextEditor::Coordinates TextEditor::FindWordStart( const Coordinates& aFrom) const { Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) return at; + if (at.mLine >= (int)mLines.size()) + return at; auto& line = mLines[at.mLine]; auto cindex = GetCharacterIndex(at); - if (cindex >= (int)line.size()) return at; + if (cindex >= (int)line.size()) + return at; - while (cindex > 0 && isspace(line[cindex].mChar)) --cindex; + while (cindex > 0 && isspace(line[cindex].mChar)) + --cindex; auto cstart = (PaletteIndex)line[cindex].mColorIndex; while (cindex > 0) { @@ -353,7 +371,8 @@ TextEditor::Coordinates TextEditor::FindWordStart( cindex++; break; } - if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex) break; + if (cstart != (PaletteIndex)line[size_t(cindex - 1)].mColorIndex) + break; } --cindex; } @@ -363,19 +382,22 @@ TextEditor::Coordinates TextEditor::FindWordStart( TextEditor::Coordinates TextEditor::FindWordEnd( const Coordinates& aFrom) const { Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) return at; + if (at.mLine >= (int)mLines.size()) + return at; auto& line = mLines[at.mLine]; auto cindex = GetCharacterIndex(at); - if (cindex >= (int)line.size()) return at; + if (cindex >= (int)line.size()) + return at; bool prevspace = (bool)isspace(line[cindex].mChar); auto cstart = (PaletteIndex)line[cindex].mColorIndex; while (cindex < (int)line.size()) { auto c = line[cindex].mChar; auto d = UTF8CharLength(c); - if (cstart != (PaletteIndex)line[cindex].mColorIndex) break; + if (cstart != (PaletteIndex)line[cindex].mColorIndex) + break; if (prevspace != !!isspace(c)) { if (isspace(c)) @@ -391,7 +413,8 @@ TextEditor::Coordinates TextEditor::FindWordEnd( TextEditor::Coordinates TextEditor::FindNextWord( const Coordinates& aFrom) const { Coordinates at = aFrom; - if (at.mLine >= (int)mLines.size()) return at; + if (at.mLine >= (int)mLines.size()) + return at; // skip to the next non-word character auto cindex = GetCharacterIndex(aFrom); @@ -416,7 +439,8 @@ TextEditor::Coordinates TextEditor::FindNextWord( if (isword && !skip) return Coordinates(at.mLine, GetCharacterColumn(at.mLine, cindex)); - if (!isword) skip = false; + if (!isword) + skip = false; cindex++; } else { @@ -431,7 +455,8 @@ TextEditor::Coordinates TextEditor::FindNextWord( } int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const { - if (aCoordinates.mLine >= mLines.size()) return -1; + if (aCoordinates.mLine >= mLines.size()) + return -1; auto& line = mLines[aCoordinates.mLine]; int c = 0; int i = 0; @@ -446,7 +471,8 @@ int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const { } int TextEditor::GetCharacterColumn(int aLine, int aIndex) const { - if (aLine >= mLines.size()) return 0; + if (aLine >= mLines.size()) + return 0; auto& line = mLines[aLine]; int col = 0; int i = 0; @@ -462,15 +488,18 @@ int TextEditor::GetCharacterColumn(int aLine, int aIndex) const { } int TextEditor::GetLineCharacterCount(int aLine) const { - if (aLine >= mLines.size()) return 0; + if (aLine >= mLines.size()) + return 0; auto& line = mLines[aLine]; int c = 0; - for (unsigned i = 0; i < line.size(); c++) i += UTF8CharLength(line[i].mChar); + for (unsigned i = 0; i < line.size(); c++) + i += UTF8CharLength(line[i].mChar); return c; } int TextEditor::GetLineMaxColumn(int aLine) const { - if (aLine >= mLines.size()) return 0; + if (aLine >= mLines.size()) + return 0; auto& line = mLines[aLine]; int col = 0; for (unsigned i = 0; i < line.size();) { @@ -485,11 +514,13 @@ int TextEditor::GetLineMaxColumn(int aLine) const { } bool TextEditor::IsOnWordBoundary(const Coordinates& aAt) const { - if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) return true; + if (aAt.mLine >= (int)mLines.size() || aAt.mColumn == 0) + return true; auto& line = mLines[aAt.mLine]; auto cindex = GetCharacterIndex(aAt); - if (cindex >= (int)line.size()) return true; + if (cindex >= (int)line.size()) + return true; if (mColorizerEnabled) return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; @@ -506,14 +537,16 @@ void TextEditor::RemoveLine(int aStart, int aEnd) { for (auto& i : mErrorMarkers) { ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); - if (e.first >= aStart && e.first <= aEnd) continue; + if (e.first >= aStart && e.first <= aEnd) + continue; etmp.insert(e); } mErrorMarkers = std::move(etmp); Breakpoints btmp; for (auto i : mBreakpoints) { - if (i >= aStart && i <= aEnd) continue; + if (i >= aStart && i <= aEnd) + continue; btmp.insert(i >= aStart ? i - 1 : i); } mBreakpoints = std::move(btmp); @@ -532,14 +565,16 @@ void TextEditor::RemoveLine(int aIndex) { for (auto& i : mErrorMarkers) { ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); - if (e.first - 1 == aIndex) continue; + if (e.first - 1 == aIndex) + continue; etmp.insert(e); } mErrorMarkers = std::move(etmp); Breakpoints btmp; for (auto i : mBreakpoints) { - if (i == aIndex) continue; + if (i == aIndex) + continue; btmp.insert(i >= aIndex ? i - 1 : i); } mBreakpoints = std::move(btmp); @@ -562,7 +597,8 @@ TextEditor::Line& TextEditor::InsertLine(int aIndex) { mErrorMarkers = std::move(etmp); Breakpoints btmp; - for (auto i : mBreakpoints) btmp.insert(i >= aIndex ? i + 1 : i); + for (auto i : mBreakpoints) + btmp.insert(i >= aIndex ? i + 1 : i); mBreakpoints = std::move(btmp); return result; @@ -589,8 +625,10 @@ std::string TextEditor::GetWordAt(const Coordinates& aCoords) const { } ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const { - if (!mColorizerEnabled) return mPalette[(int)PaletteIndex::Default]; - if (aGlyph.mComment) return mPalette[(int)PaletteIndex::Comment]; + if (!mColorizerEnabled) + return mPalette[(int)PaletteIndex::Default]; + if (aGlyph.mComment) + return mPalette[(int)PaletteIndex::Comment]; if (aGlyph.mMultiLineComment) return mPalette[(int)PaletteIndex::MultiLineComment]; auto const color = mPalette[(int)aGlyph.mColorIndex]; @@ -682,7 +720,8 @@ void TextEditor::HandleKeyboardInputs() { if (!IsReadOnly() && !io.InputQueueCharacters.empty()) { for (int i = 0; i < io.InputQueueCharacters.Size; i++) { auto c = io.InputQueueCharacters[i]; - if (c != 0 && (c == '\n' || c >= 32)) EnterCharacter(c, shift); + if (c != 0 && (c == '\n' || c >= 32)) + EnterCharacter(c, shift); } io.InputQueueCharacters.resize(0); } @@ -844,7 +883,8 @@ void TextEditor::Render() { ? mState.mSelectionEnd : lineEndCoord); - if (mState.mSelectionEnd.mLine > lineNo) ssend += mCharAdvance.x; + if (mState.mSelectionEnd.mLine > lineNo) + ssend += mCharAdvance.x; if (sstart != -1 && ssend != -1 && sstart < ssend) { ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, @@ -946,7 +986,8 @@ void TextEditor::Render() { lineStartScreenPos.y + mCharAdvance.y); drawList->AddRectFilled(cstart, cend, mPalette[(int)PaletteIndex::Cursor]); - if (elapsed > 800) mStartTime = timeEnd; + if (elapsed > 800) + mStartTime = timeEnd; } } } @@ -1004,7 +1045,8 @@ void TextEditor::Render() { i++; } else { auto l = UTF8CharLength(glyph.mChar); - while (l-- > 0) mLineBuffer.push_back(line[i++].mChar); + while (l-- > 0) + mLineBuffer.push_back(line[i++].mChar); } ++columnNo; } @@ -1068,13 +1110,14 @@ void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) { HandleKeyboardInputs(); } - if (mHandleMouseInputs) HandleMouseInputs(); + if (mHandleMouseInputs) + HandleMouseInputs(); ColorizeInternal(); Render(); - - if (!mIgnoreImGuiChild) ImGui::EndChild(); + if (!mIgnoreImGuiChild) + ImGui::EndChild(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); @@ -1144,11 +1187,13 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) { auto end = mState.mSelectionEnd; auto originalEnd = end; - if (start > end) std::swap(start, end); + if (start > end) + std::swap(start, end); start.mColumn = 0; // end.mColumn = end.mLine < mLines.size() ? //mLines[end.mLine].size() : 0; - if (end.mColumn == 0 && end.mLine > 0) --end.mLine; + if (end.mColumn == 0 && end.mLine > 0) + --end.mLine; if (end.mLine >= (int)mLines.size()) end.mLine = mLines.empty() ? 0 : (int)mLines.size() - 1; end.mColumn = GetLineMaxColumn(end.mLine); @@ -1288,9 +1333,13 @@ void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) { EnsureCursorVisible(); } -void TextEditor::SetReadOnly(bool aValue) { mReadOnly = aValue; } +void TextEditor::SetReadOnly(bool aValue) { + mReadOnly = aValue; +} -void TextEditor::SetColorizerEnable(bool aValue) { mColorizerEnabled = aValue; } +void TextEditor::SetColorizerEnable(bool aValue) { + mColorizerEnabled = aValue; +} void TextEditor::SetCursorPosition(const Coordinates& aPosition) { if (mState.mCursorPosition != aPosition) { @@ -1357,7 +1406,8 @@ void TextEditor::InsertText(const std::string& aValue) { } void TextEditor::InsertText(const char* aValue) { - if (aValue == nullptr) return; + if (aValue == nullptr) + return; auto pos = GetActualCursorCoordinates(); auto start = std::min(pos, mState.mSelectionStart); @@ -1373,7 +1423,8 @@ void TextEditor::InsertText(const char* aValue) { void TextEditor::DeleteSelection() { assert(mState.mSelectionEnd >= mState.mSelectionStart); - if (mState.mSelectionEnd == mState.mSelectionStart) return; + if (mState.mSelectionEnd == mState.mSelectionStart) + return; DeleteRange(mState.mSelectionStart, mState.mSelectionEnd); @@ -1429,10 +1480,13 @@ void TextEditor::MoveDown(int aAmount, bool aSelect) { } } -static bool IsUTFSequence(char c) { return (c & 0xC0) == 0x80; } +static bool IsUTFSequence(char c) { + return (c & 0xC0) == 0x80; +} void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) { - if (mLines.empty()) return; + if (mLines.empty()) + return; auto oldPos = mState.mCursorPosition; mState.mCursorPosition = GetActualCursorCoordinates(); @@ -1490,7 +1544,8 @@ void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) { void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) { auto oldPos = mState.mCursorPosition; - if (mLines.empty() || oldPos.mLine >= mLines.size()) return; + if (mLines.empty() || oldPos.mLine >= mLines.size()) + return; auto cindex = GetCharacterIndex(mState.mCursorPosition); while (aAmount-- > 0) { @@ -1602,7 +1657,8 @@ void TextEditor::MoveEnd(bool aSelect) { void TextEditor::Delete() { assert(!mReadOnly); - if (mLines.empty()) return; + if (mLines.empty()) + return; UndoRecord u; u.mBefore = mState; @@ -1619,7 +1675,8 @@ void TextEditor::Delete() { auto& line = mLines[pos.mLine]; if (pos.mColumn == GetLineMaxColumn(pos.mLine)) { - if (pos.mLine == (int)mLines.size() - 1) return; + if (pos.mLine == (int)mLines.size() - 1) + return; u.mRemoved = '\n'; u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); @@ -1651,7 +1708,8 @@ void TextEditor::Delete() { void TextEditor::Backspace() { assert(!mReadOnly); - if (mLines.empty()) return; + if (mLines.empty()) + return; UndoRecord u; u.mBefore = mState; @@ -1667,7 +1725,8 @@ void TextEditor::Backspace() { SetCursorPosition(pos); if (mState.mCursorPosition.mColumn == 0) { - if (mState.mCursorPosition.mLine == 0) return; + if (mState.mCursorPosition.mLine == 0) + return; u.mRemoved = '\n'; u.mRemovedStart = u.mRemovedEnd = @@ -1693,7 +1752,8 @@ void TextEditor::Backspace() { auto& line = mLines[mState.mCursorPosition.mLine]; auto cindex = GetCharacterIndex(pos) - 1; auto cend = cindex + 1; - while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) --cindex; + while (cindex > 0 && IsUTFSequence(line[cindex].mChar)) + --cindex; // if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) // --cindex; @@ -1738,7 +1798,8 @@ void TextEditor::Copy() { if (!mLines.empty()) { std::string str; auto& line = mLines[GetActualCursorCoordinates().mLine]; - for (auto& g : line) str.push_back(g.mChar); + for (auto& g : line) + str.push_back(g.mChar); ImGui::SetClipboardText(str.c_str()); } } @@ -1765,7 +1826,8 @@ void TextEditor::Cut() { } void TextEditor::Paste() { - if (IsReadOnly()) return; + if (IsReadOnly()) + return; auto clipText = ImGui::GetClipboardText(); if (clipText != nullptr && strlen(clipText) > 0) { @@ -1790,18 +1852,22 @@ void TextEditor::Paste() { } } -bool TextEditor::CanUndo() const { return !mReadOnly && mUndoIndex > 0; } +bool TextEditor::CanUndo() const { + return !mReadOnly && mUndoIndex > 0; +} bool TextEditor::CanRedo() const { return !mReadOnly && mUndoIndex < (int)mUndoBuffer.size(); } void TextEditor::Undo(int aSteps) { - while (CanUndo() && aSteps-- > 0) mUndoBuffer[--mUndoIndex].Undo(this); + while (CanUndo() && aSteps-- > 0) + mUndoBuffer[--mUndoIndex].Undo(this); } void TextEditor::Redo(int aSteps) { - while (CanRedo() && aSteps-- > 0) mUndoBuffer[mUndoIndex++].Redo(this); + while (CanRedo() && aSteps-- > 0) + mUndoBuffer[mUndoIndex++].Redo(this); } const TextEditor::Palette& TextEditor::GetDarkPalette() { @@ -1899,7 +1965,8 @@ std::vector TextEditor::GetTextLines() const { text.resize(line.size()); - for (size_t i = 0; i < line.size(); ++i) text[i] = line[i].mChar; + for (size_t i = 0; i < line.size(); ++i) + text[i] = line[i].mChar; result.emplace_back(std::move(text)); } @@ -1930,7 +1997,8 @@ void TextEditor::Colorize(int aFromLine, int aLines) { } void TextEditor::ColorizeRange(int aFromLine, int aToLine) { - if (mLines.empty() || aFromLine >= aToLine) return; + if (mLines.empty() || aFromLine >= aToLine) + return; std::string buffer; std::cmatch results; @@ -1940,7 +2008,8 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) { for (int i = aFromLine; i < endLine; ++i) { auto& line = mLines[i]; - if (line.empty()) continue; + if (line.empty()) + continue; buffer.resize(line.size()); for (size_t j = 0; j < line.size(); ++j) { @@ -2022,7 +2091,8 @@ void TextEditor::ColorizeRange(int aFromLine, int aToLine) { } void TextEditor::ColorizeInternal() { - if (mLines.empty() || !mColorizerEnabled) return; + if (mLines.empty() || !mColorizerEnabled) + return; if (mCheckComments) { auto endLine = mLines.size(); @@ -2293,7 +2363,8 @@ static bool TokenizeCStyleString(const char* in_begin, const char* in_end, } // handle escape character for " - if (*p == '\\' && p + 1 < in_end && p[1] == '"') p++; + if (*p == '\\' && p + 1 < in_end && p[1] == '"') + p++; p++; } @@ -2312,9 +2383,11 @@ static bool TokenizeCStyleCharacterLiteral(const char* in_begin, p++; // handle escape characters - if (p < in_end && *p == '\\') p++; + if (p < in_end && *p == '\\') + p++; - if (p < in_end) p++; + if (p < in_end) + p++; // handle end of character literal if (p < in_end && *p == '\'') { @@ -2354,7 +2427,8 @@ static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, const bool startsWithNumber = *p >= '0' && *p <= '9'; - if (*p != '+' && *p != '-' && !startsWithNumber) return false; + if (*p != '+' && *p != '-' && !startsWithNumber) + return false; p++; @@ -2366,7 +2440,8 @@ static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, p++; } - if (hasNumber == false) return false; + if (hasNumber == false) + return false; bool isFloat = false; bool isHex = false; @@ -2378,7 +2453,8 @@ static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, p++; - while (p < in_end && (*p >= '0' && *p <= '9')) p++; + while (p < in_end && (*p >= '0' && *p <= '9')) + p++; } else if (*p == 'x' || *p == 'X') { // hex formatted integer of the type 0xef80 @@ -2397,7 +2473,8 @@ static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, p++; - while (p < in_end && (*p >= '0' && *p <= '1')) p++; + while (p < in_end && (*p >= '0' && *p <= '1')) + p++; } } @@ -2408,7 +2485,8 @@ static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, p++; - if (p < in_end && (*p == '+' || *p == '-')) p++; + if (p < in_end && (*p == '+' || *p == '-')) + p++; bool hasDigits = false; @@ -2418,11 +2496,13 @@ static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, p++; } - if (hasDigits == false) return false; + if (hasDigits == false) + return false; } // single precision floating point type - if (p < in_end && *p == 'f') p++; + if (p < in_end && *p == 'f') + p++; } if (isFloat == false) { @@ -2571,7 +2651,8 @@ TextEditor::LanguageDefinition::CPlusPlus() { "while", "xor", "xor_eq"}; - for (auto& k : cppKeywords) langDef.mKeywords.insert(k); + for (auto& k : cppKeywords) + langDef.mKeywords.insert(k); static const char* const identifiers[] = { "abort", "abs", "acos", "asin", "atan", @@ -2827,7 +2908,8 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() { "half3x4", "half4x4", }; - for (auto& k : keywords) langDef.mKeywords.insert(k); + for (auto& k : keywords) + langDef.mKeywords.insert(k); static const char* const identifiers[] = { "abort", @@ -3038,7 +3120,8 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() { "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn", "_Static_assert", "_Thread_local"}; - for (auto& k : keywords) langDef.mKeywords.insert(k); + for (auto& k : keywords) + langDef.mKeywords.insert(k); static const char* const identifiers[] = { "abort", "abs", "acos", "asin", "atan", "atexit", @@ -3117,7 +3200,8 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() { "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn", "_Static_assert", "_Thread_local"}; - for (auto& k : keywords) langDef.mKeywords.insert(k); + for (auto& k : keywords) + langDef.mKeywords.insert(k); static const char* const identifiers[] = { "abort", "abs", "acos", "asin", "atan", "atexit", @@ -3355,7 +3439,8 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() { "OVER", "WRITETEXT"}; - for (auto& k : keywords) langDef.mKeywords.insert(k); + for (auto& k : keywords) + langDef.mKeywords.insert(k); static const char* const identifiers[] = {"ABS", "ACOS", @@ -3560,7 +3645,8 @@ TextEditor::LanguageDefinition::AngelScript() { "typedef", "uint", "uint8", "uint16", "uint32", "uint64", "void", "while", "xor"}; - for (auto& k : keywords) langDef.mKeywords.insert(k); + for (auto& k : keywords) + langDef.mKeywords.insert(k); static const char* const identifiers[] = { "cos", "sin", "tab", "acos", "asin", @@ -3627,7 +3713,8 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() { "for", "function", "if", "in", "", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"}; - for (auto& k : keywords) langDef.mKeywords.insert(k); + for (auto& k : keywords) + langDef.mKeywords.insert(k); static const char* const identifiers[] = {"assert", "collectgarbage", "dofile", "error", diff --git a/src/app/gui/widgets/text_editor.h b/src/app/gui/widgets/text_editor.h index 27f9344e..0e134aec 100644 --- a/src/app/gui/widgets/text_editor.h +++ b/src/app/gui/widgets/text_editor.h @@ -80,22 +80,26 @@ class TextEditor { } bool operator<(const Coordinates& o) const { - if (mLine != o.mLine) return mLine < o.mLine; + if (mLine != o.mLine) + return mLine < o.mLine; return mColumn < o.mColumn; } bool operator>(const Coordinates& o) const { - if (mLine != o.mLine) return mLine > o.mLine; + if (mLine != o.mLine) + return mLine > o.mLine; return mColumn > o.mColumn; } bool operator<=(const Coordinates& o) const { - if (mLine != o.mLine) return mLine < o.mLine; + if (mLine != o.mLine) + return mLine < o.mLine; return mColumn <= o.mColumn; } bool operator>=(const Coordinates& o) const { - if (mLine != o.mLine) return mLine > o.mLine; + if (mLine != o.mLine) + return mLine > o.mLine; return mColumn >= o.mColumn; } }; diff --git a/src/app/gui/widgets/themed_widgets.cc b/src/app/gui/widgets/themed_widgets.cc index 9cae21b4..59b9a4e9 100644 --- a/src/app/gui/widgets/themed_widgets.cc +++ b/src/app/gui/widgets/themed_widgets.cc @@ -1,7 +1,7 @@ #include "app/gui/widgets/themed_widgets.h" -#include "app/gui/core/color.h" #include "app/gfx/types/snes_color.h" +#include "app/gui/core/color.h" namespace yaze { namespace gui { @@ -13,8 +13,10 @@ namespace gui { bool ThemedButton(const char* label, const ImVec2& size) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.button)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ConvertColorToImVec4(theme.button_hovered)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ConvertColorToImVec4(theme.button_active)); bool result = ImGui::Button(label, size); @@ -23,8 +25,9 @@ bool ThemedButton(const char* label, const ImVec2& size) { } bool ThemedIconButton(const char* icon, const char* tooltip) { - bool result = ThemedButton(icon, ImVec2(LayoutHelpers::GetStandardWidgetHeight(), - LayoutHelpers::GetStandardWidgetHeight())); + bool result = + ThemedButton(icon, ImVec2(LayoutHelpers::GetStandardWidgetHeight(), + LayoutHelpers::GetStandardWidgetHeight())); if (tooltip && ImGui::IsItemHovered()) { BeginThemedTooltip(); ImGui::Text("%s", tooltip); @@ -36,12 +39,14 @@ bool ThemedIconButton(const char* icon, const char* tooltip) { bool PrimaryButton(const char* label, const ImVec2& size) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.accent)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, - ImVec4(theme.accent.red * 1.2f, theme.accent.green * 1.2f, - theme.accent.blue * 1.2f, theme.accent.alpha)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, - ImVec4(theme.accent.red * 0.8f, theme.accent.green * 0.8f, - theme.accent.blue * 0.8f, theme.accent.alpha)); + ImGui::PushStyleColor( + ImGuiCol_ButtonHovered, + ImVec4(theme.accent.red * 1.2f, theme.accent.green * 1.2f, + theme.accent.blue * 1.2f, theme.accent.alpha)); + ImGui::PushStyleColor( + ImGuiCol_ButtonActive, + ImVec4(theme.accent.red * 0.8f, theme.accent.green * 0.8f, + theme.accent.blue * 0.8f, theme.accent.alpha)); bool result = ImGui::Button(label, size); @@ -53,11 +58,11 @@ bool DangerButton(const char* label, const ImVec2& size) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.error)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, - ImVec4(theme.error.red * 1.2f, theme.error.green * 1.2f, - theme.error.blue * 1.2f, theme.error.alpha)); + ImVec4(theme.error.red * 1.2f, theme.error.green * 1.2f, + theme.error.blue * 1.2f, theme.error.alpha)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, - ImVec4(theme.error.red * 0.8f, theme.error.green * 0.8f, - theme.error.blue * 0.8f, theme.error.alpha)); + ImVec4(theme.error.red * 0.8f, theme.error.green * 0.8f, + theme.error.blue * 0.8f, theme.error.alpha)); bool result = ImGui::Button(label, size); @@ -76,8 +81,10 @@ void SectionHeader(const char* label) { bool ThemedCollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_Header, ConvertColorToImVec4(theme.header)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ConvertColorToImVec4(theme.header_hovered)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ConvertColorToImVec4(theme.header_active)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, + ConvertColorToImVec4(theme.header_hovered)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, + ConvertColorToImVec4(theme.header_active)); bool result = ImGui::CollapsingHeader(label, flags); @@ -89,7 +96,8 @@ bool ThemedCollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) { // Cards & Panels // ============================================================================ -void ThemedCard(const char* label, std::function content, const ImVec2& size) { +void ThemedCard(const char* label, std::function content, + const ImVec2& size) { BeginThemedPanel(label, size); content(); EndThemedPanel(); @@ -101,8 +109,8 @@ void BeginThemedPanel(const char* label, const ImVec2& size) { ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.surface)); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, theme.window_rounding); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, - ImVec2(LayoutHelpers::GetPanelPadding(), - LayoutHelpers::GetPanelPadding())); + ImVec2(LayoutHelpers::GetPanelPadding(), + LayoutHelpers::GetPanelPadding())); ImGui::BeginChild(label, size, true); } @@ -121,8 +129,10 @@ bool ThemedInputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, + ConvertColorToImVec4(theme.frame_bg_hovered)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, + ConvertColorToImVec4(theme.frame_bg_active)); ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth()); bool result = ImGui::InputText(label, buf, buf_size, flags); @@ -135,8 +145,10 @@ bool ThemedInputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, + ConvertColorToImVec4(theme.frame_bg_hovered)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, + ConvertColorToImVec4(theme.frame_bg_active)); ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth()); bool result = ImGui::InputInt(label, v, step, step_fast, flags); @@ -149,8 +161,10 @@ bool ThemedInputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, + ConvertColorToImVec4(theme.frame_bg_hovered)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, + ConvertColorToImVec4(theme.frame_bg_active)); ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth()); bool result = ImGui::InputFloat(label, v, step, step_fast, format, flags); @@ -161,7 +175,8 @@ bool ThemedInputFloat(const char* label, float* v, float step, float step_fast, bool ThemedCheckbox(const char* label, bool* v) { const auto& theme = GetTheme(); - ImGui::PushStyleColor(ImGuiCol_CheckMark, ConvertColorToImVec4(theme.check_mark)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, + ConvertColorToImVec4(theme.check_mark)); bool result = ImGui::Checkbox(label, v); @@ -169,12 +184,15 @@ bool ThemedCheckbox(const char* label, bool* v) { return result; } -bool ThemedCombo(const char* label, int* current_item, const char* const items[], - int items_count, int popup_max_height_in_items) { +bool ThemedCombo(const char* label, int* current_item, + const char* const items[], int items_count, + int popup_max_height_in_items) { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, + ConvertColorToImVec4(theme.frame_bg_hovered)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, + ConvertColorToImVec4(theme.frame_bg_active)); ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth()); bool result = ImGui::Combo(label, current_item, items, items_count, @@ -190,7 +208,8 @@ bool ThemedCombo(const char* label, int* current_item, const char* const items[] bool BeginThemedTable(const char* str_id, int columns, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) { - return LayoutHelpers::BeginTableWithTheming(str_id, columns, flags, outer_size, inner_width); + return LayoutHelpers::BeginTableWithTheming(str_id, columns, flags, + outer_size, inner_width); } void EndThemedTable() { @@ -242,9 +261,11 @@ void ThemedStatusText(const char* text, StatusType type) { ImGui::TextColored(color, "%s", text); } -void ThemedProgressBar(float fraction, const ImVec2& size, const char* overlay) { +void ThemedProgressBar(float fraction, const ImVec2& size, + const char* overlay) { const auto& theme = GetTheme(); - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ConvertColorToImVec4(theme.accent)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, + ConvertColorToImVec4(theme.accent)); ImGui::ProgressBar(fraction, size, overlay); @@ -256,9 +277,8 @@ void ThemedProgressBar(float fraction, const ImVec2& size, const char* overlay) // ============================================================================ // NOTE: PaletteColorButton moved to color.cc -void ColorInfoPanel(const yaze::gfx::SnesColor& color, - bool show_snes_format, - bool show_hex_format) { +void ColorInfoPanel(const yaze::gfx::SnesColor& color, bool show_snes_format, + bool show_hex_format) { auto col = color.rgb(); int r = static_cast(col.x); int g = static_cast(col.y); @@ -302,7 +322,8 @@ void ColorInfoPanel(const yaze::gfx::SnesColor& color, } void ModifiedBadge(bool is_modified, const char* text) { - if (!is_modified) return; + if (!is_modified) + return; const auto& theme = GetTheme(); ImVec4 color = ConvertColorToImVec4(theme.warning); @@ -321,11 +342,15 @@ void ModifiedBadge(bool is_modified, const char* text) { void PushThemedWidgetColors() { const auto& theme = GetTheme(); ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, + ConvertColorToImVec4(theme.frame_bg_hovered)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, + ConvertColorToImVec4(theme.frame_bg_active)); ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.button)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ConvertColorToImVec4(theme.button_hovered)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ConvertColorToImVec4(theme.button_active)); } void PopThemedWidgetColors() { diff --git a/src/app/gui/widgets/themed_widgets.h b/src/app/gui/widgets/themed_widgets.h index bebd3cab..b2230aaf 100644 --- a/src/app/gui/widgets/themed_widgets.h +++ b/src/app/gui/widgets/themed_widgets.h @@ -105,8 +105,8 @@ bool ThemedInputText(const char* label, char* buf, size_t buf_size, /** * @brief Themed integer input */ -bool ThemedInputInt(const char* label, int* v, int step = 1, int step_fast = 100, - ImGuiInputTextFlags flags = 0); +bool ThemedInputInt(const char* label, int* v, int step = 1, + int step_fast = 100, ImGuiInputTextFlags flags = 0); /** * @brief Themed float input @@ -123,8 +123,9 @@ bool ThemedCheckbox(const char* label, bool* v); /** * @brief Themed combo box */ -bool ThemedCombo(const char* label, int* current_item, const char* const items[], - int items_count, int popup_max_height_in_items = -1); +bool ThemedCombo(const char* label, int* current_item, + const char* const items[], int items_count, + int popup_max_height_in_items = -1); // ============================================================================ // Tables @@ -133,7 +134,8 @@ bool ThemedCombo(const char* label, int* current_item, const char* const items[] /** * @brief Begin themed table with automatic styling */ -bool BeginThemedTable(const char* str_id, int columns, ImGuiTableFlags flags = 0, +bool BeginThemedTable(const char* str_id, int columns, + ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); @@ -190,8 +192,7 @@ void ThemedProgressBar(float fraction, const ImVec2& size = ImVec2(-1, 0), * @param show_hex_format Show #xxxxxx hex format */ void ColorInfoPanel(const yaze::gfx::SnesColor& color, - bool show_snes_format = true, - bool show_hex_format = true); + bool show_snes_format = true, bool show_hex_format = true); /** * @brief Modified indicator badge (displayed as text with icon) diff --git a/src/app/gui/widgets/tile_selector_widget.cc b/src/app/gui/widgets/tile_selector_widget.cc index 54b319f6..d044cbb4 100644 --- a/src/app/gui/widgets/tile_selector_widget.cc +++ b/src/app/gui/widgets/tile_selector_widget.cc @@ -5,12 +5,18 @@ namespace yaze::gui { TileSelectorWidget::TileSelectorWidget(std::string widget_id) - : config_(), total_tiles_(config_.total_tiles), widget_id_(std::move(widget_id)) {} + : config_(), + total_tiles_(config_.total_tiles), + widget_id_(std::move(widget_id)) {} TileSelectorWidget::TileSelectorWidget(std::string widget_id, Config config) - : config_(config), total_tiles_(config.total_tiles), widget_id_(std::move(widget_id)) {} + : config_(config), + total_tiles_(config.total_tiles), + widget_id_(std::move(widget_id)) {} -void TileSelectorWidget::AttachCanvas(Canvas* canvas) { canvas_ = canvas; } +void TileSelectorWidget::AttachCanvas(Canvas* canvas) { + canvas_ = canvas; +} void TileSelectorWidget::SetTileCount(int total_tiles) { total_tiles_ = std::max(total_tiles, 0); @@ -26,7 +32,7 @@ void TileSelectorWidget::SetSelectedTile(int tile_id) { } TileSelectorWidget::RenderResult TileSelectorWidget::Render(gfx::Bitmap& atlas, - bool atlas_ready) { + bool atlas_ready) { RenderResult result; if (!canvas_) { @@ -37,25 +43,27 @@ TileSelectorWidget::RenderResult TileSelectorWidget::Render(gfx::Bitmap& atlas, static_cast(config_.tile_size * config_.display_scale); // Calculate total content size for ImGui child window scrolling - const int num_rows = (total_tiles_ + config_.tiles_per_row - 1) / config_.tiles_per_row; + const int num_rows = + (total_tiles_ + config_.tiles_per_row - 1) / config_.tiles_per_row; const ImVec2 content_size( config_.tiles_per_row * tile_display_size + config_.draw_offset.x * 2, - num_rows * tile_display_size + config_.draw_offset.y * 2 - ); - + num_rows * tile_display_size + config_.draw_offset.y * 2); + // Set content size for ImGui child window (must be called before DrawBackground) ImGui::SetCursorPos(ImVec2(0, 0)); ImGui::Dummy(content_size); ImGui::SetCursorPos(ImVec2(0, 0)); - + // Handle pending scroll (deferred from ScrollToTile call outside render context) if (pending_scroll_tile_id_ >= 0) { if (IsValidTileId(pending_scroll_tile_id_)) { const ImVec2 target = TileOrigin(pending_scroll_tile_id_); if (pending_scroll_use_imgui_) { const ImVec2 window_size = ImGui::GetWindowSize(); - float scroll_x = target.x - (window_size.x / 2.0f) + (tile_display_size / 2.0f); - float scroll_y = target.y - (window_size.y / 2.0f) + (tile_display_size / 2.0f); + float scroll_x = + target.x - (window_size.x / 2.0f) + (tile_display_size / 2.0f); + float scroll_y = + target.y - (window_size.y / 2.0f) + (tile_display_size / 2.0f); scroll_x = std::max(0.0f, scroll_x); scroll_y = std::max(0.0f, scroll_y); ImGui::SetScrollX(scroll_x); @@ -126,8 +134,9 @@ int TileSelectorWidget::ResolveTileAtCursor(int tile_display_size) const { const ImVec2 scroll = canvas_->scrolling(); // Convert screen position to canvas content position (accounting for scroll) - ImVec2 local = ImVec2(screen_pos.x - origin.x - config_.draw_offset.x - scroll.x, - screen_pos.y - origin.y - config_.draw_offset.y - scroll.y); + ImVec2 local = + ImVec2(screen_pos.x - origin.x - config_.draw_offset.x - scroll.x, + screen_pos.y - origin.y - config_.draw_offset.y - scroll.y); if (local.x < 0.0f || local.y < 0.0f) { return -1; @@ -186,5 +195,3 @@ bool TileSelectorWidget::IsValidTileId(int tile_id) const { } } // namespace yaze::gui - - diff --git a/src/app/gui/widgets/tile_selector_widget.h b/src/app/gui/widgets/tile_selector_widget.h index 79e75517..2dd2e240 100644 --- a/src/app/gui/widgets/tile_selector_widget.h +++ b/src/app/gui/widgets/tile_selector_widget.h @@ -58,7 +58,7 @@ class TileSelectorWidget { int selected_tile_id_ = 0; int total_tiles_ = 0; std::string widget_id_; - + // Deferred scroll state (for when ScrollToTile is called outside render context) mutable int pending_scroll_tile_id_ = -1; mutable bool pending_scroll_use_imgui_ = true; @@ -67,5 +67,3 @@ class TileSelectorWidget { } // namespace yaze::gui #endif // YAZE_APP_GUI_WIDGETS_TILE_SELECTOR_WIDGET_H - - diff --git a/src/app/main.cc b/src/app/main.cc index 443db9b5..690d48fd 100644 --- a/src/app/main.cc +++ b/src/app/main.cc @@ -6,6 +6,7 @@ #include "absl/debugging/failure_signal_handler.h" #include "absl/debugging/symbolize.h" #include "app/controller.h" +#include "cli/service/api/http_server.h" #include "core/features.h" #include "util/flag.h" #include "util/log.h" @@ -25,20 +26,27 @@ using namespace yaze; DEFINE_FLAG(std::string, rom_file, "", "The ROM file to load."); DEFINE_FLAG(std::string, log_file, "", "Output log file path for debugging."); DEFINE_FLAG(bool, debug, false, "Enable debug logging and verbose output."); -DEFINE_FLAG(std::string, log_categories, "", - "Comma-separated list of log categories to enable. " - "If empty, all categories are logged. " - "Example: --log_categories=\"Room,DungeonEditor\" to filter noisy logs."); -DEFINE_FLAG(std::string, editor, "", +DEFINE_FLAG( + std::string, log_categories, "", + "Comma-separated list of log categories to enable. " + "If empty, all categories are logged. " + "Example: --log_categories=\"Room,DungeonEditor\" to filter noisy logs."); +DEFINE_FLAG(std::string, editor, "", "The editor to open on startup. " "Available editors: Assembly, Dungeon, Graphics, Music, Overworld, " "Palette, Screen, Sprite, Message, Hex, Agent, Settings. " "Example: --editor=Dungeon"); -DEFINE_FLAG(std::string, cards, "", - "A comma-separated list of cards to open within the specified editor. " - "For Dungeon editor: 'Rooms List', 'Room Matrix', 'Entrances List', " - "'Room Graphics', 'Object Editor', 'Palette Editor', or 'Room N' (where N is room ID). " - "Example: --cards=\"Rooms List,Room 0,Room 105\""); +DEFINE_FLAG( + std::string, cards, "", + "A comma-separated list of cards to open within the specified editor. " + "For Dungeon editor: 'Rooms List', 'Room Matrix', 'Entrances List', " + "'Room Graphics', 'Object Editor', 'Palette Editor', or 'Room N' (where N " + "is room ID). " + "Example: --cards=\"Rooms List,Room 0,Room 105\""); + +// AI Agent API flags +DEFINE_FLAG(bool, enable_api, false, "Enable the AI Agent API server."); +DEFINE_FLAG(int, api_port, 8080, "Port for the AI Agent API server."); #ifdef YAZE_WITH_GRPC // gRPC test harness flags @@ -48,7 +56,7 @@ DEFINE_FLAG(int, test_harness_port, 50051, "Port for gRPC test harness server (default: 50051)."); #endif -int main(int argc, char **argv) { +int main(int argc, char** argv) { absl::InitializeSymbolizer(argv[0]); // Configure failure signal handler to be less aggressive @@ -63,16 +71,16 @@ int main(int argc, char **argv) { options.writerfn = nullptr; // Use default writer to avoid custom handling issues absl::InstallFailureSignalHandler(options); - + // Parse command line flags with custom parser yaze::util::FlagParser parser(yaze::util::global_flag_registry()); RETURN_IF_EXCEPTION(parser.Parse(argc, argv)); - + // Set up logging yaze::util::LogLevel log_level = FLAGS_debug->Get() - ? yaze::util::LogLevel::YAZE_DEBUG - : yaze::util::LogLevel::INFO; - + ? yaze::util::LogLevel::YAZE_DEBUG + : yaze::util::LogLevel::INFO; + // Parse log categories from comma-separated string std::set log_categories; std::string categories_str = FLAGS_log_categories->Get(); @@ -86,16 +94,16 @@ int main(int argc, char **argv) { } log_categories.insert(categories_str.substr(start)); } - + yaze::util::LogManager::instance().configure(log_level, FLAGS_log_file->Get(), - log_categories); + log_categories); // Enable console logging via feature flag if debug is enabled. if (FLAGS_debug->Get()) { yaze::core::FeatureFlags::get().kLogToConsole = true; LOG_INFO("Main", "🚀 YAZE started in debug mode"); } - + std::string rom_filename = ""; if (!FLAGS_rom_file->Get().empty()) { rom_filename = FLAGS_rom_file->Get(); @@ -106,19 +114,23 @@ int main(int argc, char **argv) { if (FLAGS_enable_test_harness->Get()) { // Get TestManager instance (initializes UI testing if available) auto& test_manager = yaze::test::TestManager::Get(); - + auto& server = yaze::test::ImGuiTestHarnessServer::Instance(); int port = FLAGS_test_harness_port->Get(); - - std::cout << "\n🚀 Starting ImGui Test Harness on port " << port << "..." << std::endl; + + std::cout << "\n🚀 Starting ImGui Test Harness on port " << port << "..." + << std::endl; auto status = server.Start(port, &test_manager); if (!status.ok()) { - std::cerr << "❌ ERROR: Failed to start test harness server on port " << port << std::endl; + std::cerr << "❌ ERROR: Failed to start test harness server on port " + << port << std::endl; std::cerr << " " << status.message() << std::endl; return 1; } std::cout << "✅ Test harness ready on 127.0.0.1:" << port << std::endl; - std::cout << " Available RPCs: Ping, Click, Type, Wait, Assert, Screenshot\n" << std::endl; + std::cout + << " Available RPCs: Ping, Click, Type, Wait, Assert, Screenshot\n" + << std::endl; } #endif @@ -131,12 +143,25 @@ int main(int argc, char **argv) { auto controller = std::make_unique(); EXIT_IF_ERROR(controller->OnEntry(rom_filename)) - + // Set startup editor and cards from flags (after OnEntry initializes editor manager) if (!FLAGS_editor->Get().empty()) { controller->SetStartupEditor(FLAGS_editor->Get(), FLAGS_cards->Get()); } + // Start API server if requested + std::unique_ptr api_server; + if (FLAGS_enable_api->Get()) { + api_server = std::make_unique(); + auto status = api_server->Start(FLAGS_api_port->Get()); + if (!status.ok()) { + LOG_ERROR("Main", "Failed to start API server: %s", + std::string(status.message()).c_str()); + } else { + LOG_INFO("Main", "API Server started on port %d", FLAGS_api_port->Get()); + } + } + while (controller->IsActive()) { controller->OnInput(); if (auto status = controller->OnLoad(); !status.ok()) { @@ -147,6 +172,10 @@ int main(int argc, char **argv) { } controller->OnExit(); + if (api_server) { + api_server->Stop(); + } + #ifdef YAZE_WITH_GRPC // Shutdown gRPC server if running yaze::test::ImGuiTestHarnessServer::Instance().Shutdown(); diff --git a/src/app/net/collaboration_service.cc b/src/app/net/collaboration_service.cc index 40e919b2..978cc7c4 100644 --- a/src/app/net/collaboration_service.cc +++ b/src/app/net/collaboration_service.cc @@ -14,60 +14,58 @@ CollaborationService::CollaborationService(Rom* rom) version_mgr_(nullptr), approval_mgr_(nullptr), client_(std::make_unique()), - sync_in_progress_(false) { -} + sync_in_progress_(false) {} CollaborationService::~CollaborationService() { Disconnect(); } absl::Status CollaborationService::Initialize( - const Config& config, - RomVersionManager* version_mgr, + const Config& config, RomVersionManager* version_mgr, ProposalApprovalManager* approval_mgr) { - + config_ = config; version_mgr_ = version_mgr; approval_mgr_ = approval_mgr; - + if (!version_mgr_) { return absl::InvalidArgumentError("version_mgr cannot be null"); } - + if (!approval_mgr_) { return absl::InvalidArgumentError("approval_mgr cannot be null"); } - + // Set up network event callbacks client_->OnMessage("rom_sync", [this](const nlohmann::json& payload) { OnRomSyncReceived(payload); }); - + client_->OnMessage("proposal_shared", [this](const nlohmann::json& payload) { OnProposalReceived(payload); }); - - client_->OnMessage("proposal_vote_received", [this](const nlohmann::json& payload) { - OnProposalUpdated(payload); - }); - + + client_->OnMessage( + "proposal_vote_received", + [this](const nlohmann::json& payload) { OnProposalUpdated(payload); }); + client_->OnMessage("proposal_updated", [this](const nlohmann::json& payload) { OnProposalUpdated(payload); }); - - client_->OnMessage("participant_joined", [this](const nlohmann::json& payload) { - OnParticipantJoined(payload); - }); - + + client_->OnMessage( + "participant_joined", + [this](const nlohmann::json& payload) { OnParticipantJoined(payload); }); + client_->OnMessage("participant_left", [this](const nlohmann::json& payload) { OnParticipantLeft(payload); }); - + // Store initial ROM hash if (rom_ && rom_->is_loaded()) { last_sync_hash_ = version_mgr_->GetCurrentHash(); } - + return absl::OkStatus(); } @@ -81,82 +79,71 @@ void CollaborationService::Disconnect() { } } -absl::Status CollaborationService::HostSession( - const std::string& session_name, - const std::string& username, - bool ai_enabled) { - +absl::Status CollaborationService::HostSession(const std::string& session_name, + const std::string& username, + bool ai_enabled) { + if (!client_->IsConnected()) { return absl::FailedPreconditionError("Not connected to server"); } - + if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Get current ROM hash std::string rom_hash = version_mgr_->GetCurrentHash(); - + // Create initial safe point - auto snapshot_result = version_mgr_->CreateSnapshot( - "Session start", - username, - true // is_checkpoint + auto snapshot_result = version_mgr_->CreateSnapshot("Session start", username, + true // is_checkpoint ); - + if (snapshot_result.ok()) { version_mgr_->MarkAsSafePoint(*snapshot_result); } - + // Host session on server - auto session_result = client_->HostSession( - session_name, - username, - rom_hash, - ai_enabled - ); - + auto session_result = + client_->HostSession(session_name, username, rom_hash, ai_enabled); + if (!session_result.ok()) { return session_result.status(); } - + last_sync_hash_ = rom_hash; - + return absl::OkStatus(); } -absl::Status CollaborationService::JoinSession( - const std::string& session_code, - const std::string& username) { - +absl::Status CollaborationService::JoinSession(const std::string& session_code, + const std::string& username) { + if (!client_->IsConnected()) { return absl::FailedPreconditionError("Not connected to server"); } - + if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Create backup before joining - auto snapshot_result = version_mgr_->CreateSnapshot( - "Before joining session", - username, - true - ); - + auto snapshot_result = + version_mgr_->CreateSnapshot("Before joining session", username, true); + if (snapshot_result.ok()) { version_mgr_->MarkAsSafePoint(*snapshot_result); } - + // Join session auto session_result = client_->JoinSession(session_code, username); - + if (!session_result.ok()) { return session_result.status(); } - + last_sync_hash_ = version_mgr_->GetCurrentHash(); - + return absl::OkStatus(); } @@ -164,82 +151,75 @@ absl::Status CollaborationService::LeaveSession() { if (!client_->InSession()) { return absl::FailedPreconditionError("Not in a session"); } - + return client_->LeaveSession(); } absl::Status CollaborationService::SubmitChangesAsProposal( - const std::string& description, - const std::string& username) { - + const std::string& description, const std::string& username) { + if (!client_->InSession()) { return absl::FailedPreconditionError("Not in a session"); } - + if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Generate diff from last sync std::string current_hash = version_mgr_->GetCurrentHash(); if (current_hash == last_sync_hash_) { return absl::OkStatus(); // No changes to submit } - + std::string diff = GenerateDiff(last_sync_hash_, current_hash); - + // Create proposal data - nlohmann::json proposal_data = { - {"description", description}, - {"type", "rom_modification"}, - {"diff_data", diff}, - {"from_hash", last_sync_hash_}, - {"to_hash", current_hash} - }; - + nlohmann::json proposal_data = {{"description", description}, + {"type", "rom_modification"}, + {"diff_data", diff}, + {"from_hash", last_sync_hash_}, + {"to_hash", current_hash}}; + // Submit to server auto status = client_->ShareProposal(proposal_data, username); - + if (status.ok() && config_.require_approval_for_sync) { // Proposal submitted, waiting for approval // The actual application will happen when approved } - + return status; } -absl::Status CollaborationService::ApplyRomSync( - const std::string& diff_data, - const std::string& rom_hash, - const std::string& sender) { - +absl::Status CollaborationService::ApplyRomSync(const std::string& diff_data, + const std::string& rom_hash, + const std::string& sender) { + if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + if (sync_in_progress_) { return absl::UnavailableError("Sync already in progress"); } - + sync_in_progress_ = true; - + // Create snapshot before applying if (config_.create_snapshot_before_sync) { auto snapshot_result = version_mgr_->CreateSnapshot( - absl::StrFormat("Before sync from %s", sender), - "system", - false - ); - + absl::StrFormat("Before sync from %s", sender), "system", false); + if (!snapshot_result.ok()) { sync_in_progress_ = false; return absl::InternalError("Failed to create backup snapshot"); } } - + // Apply the diff auto status = ApplyDiff(diff_data); - + if (status.ok()) { last_sync_hash_ = rom_hash; } else { @@ -251,74 +231,66 @@ absl::Status CollaborationService::ApplyRomSync( } } } - + sync_in_progress_ = false; return status; } absl::Status CollaborationService::HandleIncomingProposal( - const std::string& proposal_id, - const nlohmann::json& proposal_data, + const std::string& proposal_id, const nlohmann::json& proposal_data, const std::string& sender) { - + if (!approval_mgr_) { return absl::FailedPreconditionError("Approval manager not initialized"); } - + // Submit to approval manager return approval_mgr_->SubmitProposal( - proposal_id, - sender, - proposal_data["description"], - proposal_data - ); + proposal_id, sender, proposal_data["description"], proposal_data); } absl::Status CollaborationService::VoteOnProposal( - const std::string& proposal_id, - bool approved, + const std::string& proposal_id, bool approved, const std::string& username) { - + if (!client_->InSession()) { return absl::FailedPreconditionError("Not in a session"); } - + // Vote locally auto status = approval_mgr_->VoteOnProposal(proposal_id, username, approved); - + if (!status.ok()) { return status; } - + // Send vote to server return client_->VoteOnProposal(proposal_id, approved, username); } absl::Status CollaborationService::ApplyApprovedProposal( const std::string& proposal_id) { - + if (!approval_mgr_->IsProposalApproved(proposal_id)) { return absl::FailedPreconditionError("Proposal not approved"); } - + auto proposal_result = approval_mgr_->GetProposalStatus(proposal_id); if (!proposal_result.ok()) { return proposal_result.status(); } - + // Apply the proposal (implementation depends on proposal type) // For now, just update status auto status = client_->UpdateProposalStatus(proposal_id, "applied"); - + if (status.ok()) { // Create snapshot after applying version_mgr_->CreateSnapshot( absl::StrFormat("Applied proposal %s", proposal_id.substr(0, 8)), - "system", - false - ); + "system", false); } - + return status; } @@ -340,9 +312,9 @@ void CollaborationService::OnRomSyncReceived(const nlohmann::json& payload) { std::string diff_data = payload["diff_data"]; std::string rom_hash = payload["rom_hash"]; std::string sender = payload["sender"]; - + auto status = ApplyRomSync(diff_data, rom_hash, sender); - + if (!status.ok()) { // Log error or notify user } @@ -352,22 +324,22 @@ void CollaborationService::OnProposalReceived(const nlohmann::json& payload) { std::string proposal_id = payload["proposal_id"]; nlohmann::json proposal_data = payload["proposal_data"]; std::string sender = payload["sender"]; - + HandleIncomingProposal(proposal_id, proposal_data, sender); } void CollaborationService::OnProposalUpdated(const nlohmann::json& payload) { std::string proposal_id = payload["proposal_id"]; - + if (payload.contains("status")) { std::string status = payload["status"]; - + if (status == "approved" && approval_mgr_) { // Proposal was approved, consider applying it // This would be triggered by the host or based on voting results } } - + if (payload.contains("votes")) { // Vote update received nlohmann::json votes = payload["votes"]; @@ -387,21 +359,20 @@ void CollaborationService::OnParticipantLeft(const nlohmann::json& payload) { // Helper functions -std::string CollaborationService::GenerateDiff( - const std::string& from_hash, - const std::string& to_hash) { - +std::string CollaborationService::GenerateDiff(const std::string& from_hash, + const std::string& to_hash) { + // Simplified diff generation // In production, this would generate a binary diff // For now, just return placeholder - + if (!rom_ || !rom_->is_loaded()) { return ""; } - + // TODO: Implement proper binary diff generation // This could use algorithms like bsdiff or a custom format - + return "diff_placeholder"; } @@ -409,10 +380,10 @@ absl::Status CollaborationService::ApplyDiff(const std::string& diff_data) { if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // TODO: Implement proper diff application // For now, just return success - + return absl::OkStatus(); } @@ -420,18 +391,18 @@ bool CollaborationService::ShouldAutoSync() { if (!config_.auto_sync_enabled) { return false; } - + if (!client_->IsConnected() || !client_->InSession()) { return false; } - + if (sync_in_progress_) { return false; } - + // Check if enough time has passed since last sync // (Implementation would track last sync time) - + return true; } diff --git a/src/app/net/collaboration_service.h b/src/app/net/collaboration_service.h index 19932af2..ab082f81 100644 --- a/src/app/net/collaboration_service.h +++ b/src/app/net/collaboration_service.h @@ -37,124 +37,115 @@ class CollaborationService { bool require_approval_for_sync = true; bool create_snapshot_before_sync = true; }; - + explicit CollaborationService(Rom* rom); ~CollaborationService(); - + /** * Initialize the service */ - absl::Status Initialize( - const Config& config, - RomVersionManager* version_mgr, - ProposalApprovalManager* approval_mgr); - + absl::Status Initialize(const Config& config, RomVersionManager* version_mgr, + ProposalApprovalManager* approval_mgr); + /** * Connect to collaboration server */ absl::Status Connect(const std::string& host, int port = 8765); - + /** * Disconnect from server */ void Disconnect(); - + /** * Host a new session */ - absl::Status HostSession( - const std::string& session_name, - const std::string& username, - bool ai_enabled = true); - + absl::Status HostSession(const std::string& session_name, + const std::string& username, bool ai_enabled = true); + /** * Join existing session */ - absl::Status JoinSession( - const std::string& session_code, - const std::string& username); - + absl::Status JoinSession(const std::string& session_code, + const std::string& username); + /** * Leave current session */ absl::Status LeaveSession(); - + /** * Submit local changes as proposal */ - absl::Status SubmitChangesAsProposal( - const std::string& description, - const std::string& username); - + absl::Status SubmitChangesAsProposal(const std::string& description, + const std::string& username); + /** * Apply received ROM sync */ - absl::Status ApplyRomSync( - const std::string& diff_data, - const std::string& rom_hash, - const std::string& sender); - + absl::Status ApplyRomSync(const std::string& diff_data, + const std::string& rom_hash, + const std::string& sender); + /** * Handle incoming proposal */ - absl::Status HandleIncomingProposal( - const std::string& proposal_id, - const nlohmann::json& proposal_data, - const std::string& sender); - + absl::Status HandleIncomingProposal(const std::string& proposal_id, + const nlohmann::json& proposal_data, + const std::string& sender); + /** * Vote on proposal */ - absl::Status VoteOnProposal( - const std::string& proposal_id, - bool approved, - const std::string& username); - + absl::Status VoteOnProposal(const std::string& proposal_id, bool approved, + const std::string& username); + /** * Apply approved proposal */ absl::Status ApplyApprovedProposal(const std::string& proposal_id); - + /** * Get connection status */ bool IsConnected() const; - + /** * Get session info */ absl::StatusOr GetSessionInfo() const; - + /** * Get WebSocket client (for advanced usage) */ WebSocketClient* GetClient() { return client_.get(); } - + /** * Enable/disable auto-sync */ void SetAutoSync(bool enabled); - + private: Rom* rom_; RomVersionManager* version_mgr_; ProposalApprovalManager* approval_mgr_; std::unique_ptr client_; Config config_; - + // Sync state std::string last_sync_hash_; bool sync_in_progress_; - + // Callbacks for network events void OnRomSyncReceived(const nlohmann::json& payload); void OnProposalReceived(const nlohmann::json& payload); void OnProposalUpdated(const nlohmann::json& payload); void OnParticipantJoined(const nlohmann::json& payload); void OnParticipantLeft(const nlohmann::json& payload); - + // Helper functions - std::string GenerateDiff(const std::string& from_hash, const std::string& to_hash); + std::string GenerateDiff(const std::string& from_hash, + const std::string& to_hash); absl::Status ApplyDiff(const std::string& diff_data); bool ShouldAutoSync(); }; diff --git a/src/app/net/rom_service_impl.cc b/src/app/net/rom_service_impl.cc index a0123f06..3c654a05 100644 --- a/src/app/net/rom_service_impl.cc +++ b/src/app/net/rom_service_impl.cc @@ -3,8 +3,8 @@ #ifdef YAZE_WITH_GRPC #include "absl/strings/str_format.h" -#include "app/rom.h" #include "app/net/rom_version_manager.h" +#include "app/rom.h" // Proto namespace alias for convenience namespace rom_svc = ::yaze::proto; @@ -13,77 +13,72 @@ namespace yaze { namespace net { -RomServiceImpl::RomServiceImpl( - Rom* rom, - RomVersionManager* version_manager, - ProposalApprovalManager* approval_manager) +RomServiceImpl::RomServiceImpl(Rom* rom, RomVersionManager* version_manager, + ProposalApprovalManager* approval_manager) : rom_(rom), version_mgr_(version_manager), - approval_mgr_(approval_manager) { -} + approval_mgr_(approval_manager) {} void RomServiceImpl::SetConfig(const Config& config) { config_ = config; } -grpc::Status RomServiceImpl::ReadBytes( - grpc::ServerContext* context, - const rom_svc::ReadBytesRequest* request, - rom_svc::ReadBytesResponse* response) { - +grpc::Status RomServiceImpl::ReadBytes(grpc::ServerContext* context, + const rom_svc::ReadBytesRequest* request, + rom_svc::ReadBytesResponse* response) { + if (!rom_ || !rom_->is_loaded()) { - return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded"); + return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, + "ROM not loaded"); } - + uint32_t address = request->address(); uint32_t length = request->length(); - + // Validate range if (address + length > rom_->size()) { - return grpc::Status( - grpc::StatusCode::OUT_OF_RANGE, - absl::StrFormat("Read beyond ROM: 0x%X+%d > %d", - address, length, rom_->size())); + return grpc::Status(grpc::StatusCode::OUT_OF_RANGE, + absl::StrFormat("Read beyond ROM: 0x%X+%d > %d", + address, length, rom_->size())); } - + // Read data const auto* data = rom_->data() + address; response->set_data(data, length); response->set_success(true); - + return grpc::Status::OK; } grpc::Status RomServiceImpl::WriteBytes( - grpc::ServerContext* context, - const rom_svc::WriteBytesRequest* request, + grpc::ServerContext* context, const rom_svc::WriteBytesRequest* request, rom_svc::WriteBytesResponse* response) { - + if (!rom_ || !rom_->is_loaded()) { - return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded"); + return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, + "ROM not loaded"); } - + uint32_t address = request->address(); const std::string& data = request->data(); - + // Validate range if (address + data.size() > rom_->size()) { - return grpc::Status( - grpc::StatusCode::OUT_OF_RANGE, - absl::StrFormat("Write beyond ROM: 0x%X+%zu > %d", - address, data.size(), rom_->size())); + return grpc::Status(grpc::StatusCode::OUT_OF_RANGE, + absl::StrFormat("Write beyond ROM: 0x%X+%zu > %d", + address, data.size(), rom_->size())); } - + // Check if approval required if (config_.require_approval_for_writes && approval_mgr_) { // Create a proposal for this write - std::string proposal_id = absl::StrFormat( - "write_0x%X_%zu_bytes", address, data.size()); - + std::string proposal_id = + absl::StrFormat("write_0x%X_%zu_bytes", address, data.size()); + if (request->has_proposal_id()) { proposal_id = request->proposal_id(); } - + // Check if proposal is approved auto status = approval_mgr_->GetProposalStatus(proposal_id); if (status != ProposalApprovalManager::ApprovalStatus::kApproved) { @@ -93,7 +88,7 @@ grpc::Status RomServiceImpl::WriteBytes( return grpc::Status::OK; // Not an error, just needs approval } } - + // Create snapshot before write if (version_mgr_) { std::string snapshot_desc = absl::StrFormat( @@ -103,104 +98,96 @@ grpc::Status RomServiceImpl::WriteBytes( response->set_snapshot_id(std::to_string(snapshot_result.value())); } } - + // Perform write std::memcpy(rom_->mutable_data() + address, data.data(), data.size()); - + response->set_success(true); response->set_message("Write successful"); - + return grpc::Status::OK; } grpc::Status RomServiceImpl::GetRomInfo( - grpc::ServerContext* context, - const rom_svc::GetRomInfoRequest* request, + grpc::ServerContext* context, const rom_svc::GetRomInfoRequest* request, rom_svc::GetRomInfoResponse* response) { - + if (!rom_ || !rom_->is_loaded()) { - return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded"); + return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, + "ROM not loaded"); } - + auto* info = response->mutable_info(); info->set_title(rom_->title()); info->set_size(rom_->size()); info->set_is_loaded(rom_->is_loaded()); info->set_filename(rom_->filename()); - + return grpc::Status::OK; } grpc::Status RomServiceImpl::GetTileData( - grpc::ServerContext* context, - const rom_svc::GetTileDataRequest* request, + grpc::ServerContext* context, const rom_svc::GetTileDataRequest* request, rom_svc::GetTileDataResponse* response) { - - return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "GetTileData not yet implemented"); + + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, + "GetTileData not yet implemented"); } grpc::Status RomServiceImpl::SetTileData( - grpc::ServerContext* context, - const rom_svc::SetTileDataRequest* request, + grpc::ServerContext* context, const rom_svc::SetTileDataRequest* request, rom_svc::SetTileDataResponse* response) { - + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "SetTileData not yet implemented"); + "SetTileData not yet implemented"); } grpc::Status RomServiceImpl::GetMapData( - grpc::ServerContext* context, - const rom_svc::GetMapDataRequest* request, + grpc::ServerContext* context, const rom_svc::GetMapDataRequest* request, rom_svc::GetMapDataResponse* response) { - + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "GetMapData not yet implemented"); + "GetMapData not yet implemented"); } grpc::Status RomServiceImpl::SetMapData( - grpc::ServerContext* context, - const rom_svc::SetMapDataRequest* request, + grpc::ServerContext* context, const rom_svc::SetMapDataRequest* request, rom_svc::SetMapDataResponse* response) { - + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "SetMapData not yet implemented"); + "SetMapData not yet implemented"); } grpc::Status RomServiceImpl::GetSpriteData( - grpc::ServerContext* context, - const rom_svc::GetSpriteDataRequest* request, + grpc::ServerContext* context, const rom_svc::GetSpriteDataRequest* request, rom_svc::GetSpriteDataResponse* response) { - + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "GetSpriteData not yet implemented"); + "GetSpriteData not yet implemented"); } grpc::Status RomServiceImpl::SetSpriteData( - grpc::ServerContext* context, - const rom_svc::SetSpriteDataRequest* request, + grpc::ServerContext* context, const rom_svc::SetSpriteDataRequest* request, rom_svc::SetSpriteDataResponse* response) { - + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "SetSpriteData not yet implemented"); + "SetSpriteData not yet implemented"); } grpc::Status RomServiceImpl::GetDialogue( - grpc::ServerContext* context, - const rom_svc::GetDialogueRequest* request, + grpc::ServerContext* context, const rom_svc::GetDialogueRequest* request, rom_svc::GetDialogueResponse* response) { - + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "GetDialogue not yet implemented"); + "GetDialogue not yet implemented"); } grpc::Status RomServiceImpl::SetDialogue( - grpc::ServerContext* context, - const rom_svc::SetDialogueRequest* request, + grpc::ServerContext* context, const rom_svc::SetDialogueRequest* request, rom_svc::SetDialogueResponse* response) { - + return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, - "SetDialogue not yet implemented"); + "SetDialogue not yet implemented"); } } // namespace net diff --git a/src/app/net/rom_service_impl.h b/src/app/net/rom_service_impl.h index e09f1666..95364bf8 100644 --- a/src/app/net/rom_service_impl.h +++ b/src/app/net/rom_service_impl.h @@ -23,8 +23,8 @@ // Note: Proto files will be generated to build directory #endif -#include "app/rom.h" #include "app/net/rom_version_manager.h" +#include "app/rom.h" namespace yaze { @@ -54,120 +54,113 @@ class RomServiceImpl final : public proto::RomService::Service { int max_read_size_bytes = 1024 * 1024; // 1MB max per read bool allow_raw_rom_access = true; // Allow direct byte access }; - + /** * @brief Construct ROM service * @param rom Pointer to ROM instance (not owned) * @param version_mgr Pointer to version manager (not owned, optional) * @param approval_mgr Pointer to approval manager (not owned, optional) */ - RomServiceImpl(Rom* rom, - RomVersionManager* version_mgr = nullptr, + RomServiceImpl(Rom* rom, RomVersionManager* version_mgr = nullptr, ProposalApprovalManager* approval_mgr = nullptr); - + ~RomServiceImpl() override = default; - + // Initialize with configuration void SetConfig(const Config& config); - + // ========================================================================= // Basic ROM Operations // ========================================================================= - - grpc::Status ReadBytes( - grpc::ServerContext* context, - const proto::ReadBytesRequest* request, - proto::ReadBytesResponse* response) override; - - grpc::Status WriteBytes( - grpc::ServerContext* context, - const proto::WriteBytesRequest* request, - proto::WriteBytesResponse* response) override; - - grpc::Status GetRomInfo( - grpc::ServerContext* context, - const proto::GetRomInfoRequest* request, - proto::GetRomInfoResponse* response) override; - + + grpc::Status ReadBytes(grpc::ServerContext* context, + const proto::ReadBytesRequest* request, + proto::ReadBytesResponse* response) override; + + grpc::Status WriteBytes(grpc::ServerContext* context, + const proto::WriteBytesRequest* request, + proto::WriteBytesResponse* response) override; + + grpc::Status GetRomInfo(grpc::ServerContext* context, + const proto::GetRomInfoRequest* request, + proto::GetRomInfoResponse* response) override; + // ========================================================================= // Overworld Operations // ========================================================================= - + grpc::Status ReadOverworldMap( grpc::ServerContext* context, const proto::ReadOverworldMapRequest* request, proto::ReadOverworldMapResponse* response) override; - + grpc::Status WriteOverworldTile( grpc::ServerContext* context, const proto::WriteOverworldTileRequest* request, proto::WriteOverworldTileResponse* response) override; - + // ========================================================================= // Dungeon Operations // ========================================================================= - + grpc::Status ReadDungeonRoom( grpc::ServerContext* context, const proto::ReadDungeonRoomRequest* request, proto::ReadDungeonRoomResponse* response) override; - + grpc::Status WriteDungeonTile( grpc::ServerContext* context, const proto::WriteDungeonTileRequest* request, proto::WriteDungeonTileResponse* response) override; - + // ========================================================================= // Sprite Operations // ========================================================================= - - grpc::Status ReadSprite( - grpc::ServerContext* context, - const proto::ReadSpriteRequest* request, - proto::ReadSpriteResponse* response) override; - + + grpc::Status ReadSprite(grpc::ServerContext* context, + const proto::ReadSpriteRequest* request, + proto::ReadSpriteResponse* response) override; + // ========================================================================= // Proposal System // ========================================================================= - + grpc::Status SubmitRomProposal( grpc::ServerContext* context, const proto::SubmitRomProposalRequest* request, proto::SubmitRomProposalResponse* response) override; - + grpc::Status GetProposalStatus( grpc::ServerContext* context, const proto::GetProposalStatusRequest* request, proto::GetProposalStatusResponse* response) override; - + // ========================================================================= // Version Management // ========================================================================= - - grpc::Status CreateSnapshot( - grpc::ServerContext* context, - const proto::CreateSnapshotRequest* request, - proto::CreateSnapshotResponse* response) override; - + + grpc::Status CreateSnapshot(grpc::ServerContext* context, + const proto::CreateSnapshotRequest* request, + proto::CreateSnapshotResponse* response) override; + grpc::Status RestoreSnapshot( grpc::ServerContext* context, const proto::RestoreSnapshotRequest* request, proto::RestoreSnapshotResponse* response) override; - - grpc::Status ListSnapshots( - grpc::ServerContext* context, - const proto::ListSnapshotsRequest* request, - proto::ListSnapshotsResponse* response) override; - + + grpc::Status ListSnapshots(grpc::ServerContext* context, + const proto::ListSnapshotsRequest* request, + proto::ListSnapshotsResponse* response) override; + private: Config config_; - Rom* rom_; // Not owned - RomVersionManager* version_mgr_; // Not owned, may be null + Rom* rom_; // Not owned + RomVersionManager* version_mgr_; // Not owned, may be null ProposalApprovalManager* approval_mgr_; // Not owned, may be null - + // Helper to check if ROM is loaded grpc::Status ValidateRomLoaded(); - + // Helper to create snapshot before write operations absl::Status MaybeCreateSnapshot(const std::string& description); }; diff --git a/src/app/net/rom_version_manager.cc b/src/app/net/rom_version_manager.cc index 56236161..853333e9 100644 --- a/src/app/net/rom_version_manager.cc +++ b/src/app/net/rom_version_manager.cc @@ -4,8 +4,8 @@ #include #include -#include "absl/strings/str_format.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" // For compression (placeholder - would use zlib or similar) #include @@ -33,13 +33,15 @@ std::string ComputeHash(const std::vector& data) { std::string GenerateId() { auto now = std::chrono::system_clock::now(); auto ms = std::chrono::duration_cast( - now.time_since_epoch()).count(); + now.time_since_epoch()) + .count(); return absl::StrFormat("snap_%lld", ms); } int64_t GetCurrentTimestamp() { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); } } // namespace @@ -49,9 +51,7 @@ int64_t GetCurrentTimestamp() { // ============================================================================ RomVersionManager::RomVersionManager(Rom* rom) - : rom_(rom), - last_backup_time_(0) { -} + : rom_(rom), last_backup_time_(0) {} RomVersionManager::~RomVersionManager() { // Cleanup if needed @@ -59,34 +59,30 @@ RomVersionManager::~RomVersionManager() { absl::Status RomVersionManager::Initialize(const Config& config) { config_ = config; - + // Create initial snapshot - auto initial_result = CreateSnapshot( - "Initial state", - "system", - true); - + auto initial_result = CreateSnapshot("Initial state", "system", true); + if (!initial_result.ok()) { return initial_result.status(); } - + // Mark as safe point return MarkAsSafePoint(*initial_result); } absl::StatusOr RomVersionManager::CreateSnapshot( - const std::string& description, - const std::string& creator, + const std::string& description, const std::string& creator, bool is_checkpoint) { - + if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Get ROM data std::vector rom_data(rom_->size()); std::memcpy(rom_data.data(), rom_->data(), rom_->size()); - + // Create snapshot RomSnapshot snapshot; snapshot.snapshot_id = GenerateId(); @@ -96,7 +92,7 @@ absl::StatusOr RomVersionManager::CreateSnapshot( snapshot.creator = creator; snapshot.is_checkpoint = is_checkpoint; snapshot.is_safe_point = false; - + // Compress if enabled if (config_.compress_snapshots) { snapshot.rom_data = CompressData(rom_data); @@ -105,37 +101,39 @@ absl::StatusOr RomVersionManager::CreateSnapshot( snapshot.rom_data = std::move(rom_data); snapshot.compressed_size = snapshot.rom_data.size(); } - + #ifdef YAZE_WITH_JSON snapshot.metadata = nlohmann::json::object(); snapshot.metadata["size"] = rom_->size(); snapshot.metadata["auto_backup"] = !is_checkpoint; #endif - + // Store snapshot snapshots_[snapshot.snapshot_id] = std::move(snapshot); last_known_hash_ = snapshots_[snapshot.snapshot_id].rom_hash; - + // Cleanup if needed if (snapshots_.size() > config_.max_snapshots) { CleanupOldSnapshots(); } - + return snapshots_[snapshot.snapshot_id].snapshot_id; } -absl::Status RomVersionManager::RestoreSnapshot(const std::string& snapshot_id) { +absl::Status RomVersionManager::RestoreSnapshot( + const std::string& snapshot_id) { auto it = snapshots_.find(snapshot_id); if (it == snapshots_.end()) { - return absl::NotFoundError(absl::StrCat("Snapshot not found: ", snapshot_id)); + return absl::NotFoundError( + absl::StrCat("Snapshot not found: ", snapshot_id)); } - + if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + const RomSnapshot& snapshot = it->second; - + // Decompress if needed std::vector rom_data; if (config_.compress_snapshots) { @@ -143,55 +141,54 @@ absl::Status RomVersionManager::RestoreSnapshot(const std::string& snapshot_id) } else { rom_data = snapshot.rom_data; } - + // Verify size matches if (rom_data.size() != rom_->size()) { return absl::DataLossError("Snapshot size mismatch"); } - + // Create backup before restore - auto backup_result = CreateSnapshot( - "Pre-restore backup", - "system", - false); - + auto backup_result = CreateSnapshot("Pre-restore backup", "system", false); + if (!backup_result.ok()) { return absl::InternalError("Failed to create pre-restore backup"); } - + // Restore ROM data std::memcpy(rom_->mutable_data(), rom_data.data(), rom_data.size()); - + last_known_hash_ = snapshot.rom_hash; - + return absl::OkStatus(); } -absl::Status RomVersionManager::MarkAsSafePoint(const std::string& snapshot_id) { +absl::Status RomVersionManager::MarkAsSafePoint( + const std::string& snapshot_id) { auto it = snapshots_.find(snapshot_id); if (it == snapshots_.end()) { return absl::NotFoundError("Snapshot not found"); } - + it->second.is_safe_point = true; return absl::OkStatus(); } -std::vector RomVersionManager::GetSnapshots(bool safe_points_only) const { +std::vector RomVersionManager::GetSnapshots( + bool safe_points_only) const { std::vector result; - + for (const auto& [id, snapshot] : snapshots_) { if (!safe_points_only || snapshot.is_safe_point) { result.push_back(snapshot); } } - + // Sort by timestamp (newest first) std::sort(result.begin(), result.end(), [](const RomSnapshot& a, const RomSnapshot& b) { return a.timestamp > b.timestamp; }); - + return result; } @@ -209,12 +206,12 @@ absl::Status RomVersionManager::DeleteSnapshot(const std::string& snapshot_id) { if (it == snapshots_.end()) { return absl::NotFoundError("Snapshot not found"); } - + // Don't allow deleting safe points if (it->second.is_safe_point) { return absl::FailedPreconditionError("Cannot delete safe point"); } - + snapshots_.erase(it); return absl::OkStatus(); } @@ -223,29 +220,29 @@ absl::StatusOr RomVersionManager::DetectCorruption() { if (!config_.enable_corruption_detection) { return false; } - + if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Compute current hash std::vector current_data(rom_->size()); std::memcpy(current_data.data(), rom_->data(), rom_->size()); std::string current_hash = ComputeHash(current_data); - + // Basic integrity checks auto integrity_status = ValidateRomIntegrity(); if (!integrity_status.ok()) { return true; // Corruption detected } - + // Check against last known good hash (if modified unexpectedly) if (!last_known_hash_.empty() && current_hash != last_known_hash_) { // ROM changed without going through version manager // This might be intentional, so just flag it return false; } - + return false; } @@ -255,7 +252,7 @@ absl::Status RomVersionManager::AutoRecover() { if (snapshots.empty()) { return absl::NotFoundError("No safe points available for recovery"); } - + // Restore from most recent safe point return RestoreSnapshot(snapshots[0].snapshot_id); } @@ -264,7 +261,7 @@ std::string RomVersionManager::GetCurrentHash() const { if (!rom_ || !rom_->is_loaded()) { return ""; } - + std::vector data(rom_->size()); std::memcpy(data.data(), rom_->data(), rom_->size()); return ComputeHash(data); @@ -273,53 +270,56 @@ std::string RomVersionManager::GetCurrentHash() const { absl::Status RomVersionManager::CleanupOldSnapshots() { // Keep safe points and checkpoints // Remove oldest auto-backups first - + std::vector> auto_backups; for (const auto& [id, snapshot] : snapshots_) { if (!snapshot.is_safe_point && !snapshot.is_checkpoint) { auto_backups.push_back({snapshot.timestamp, id}); } } - + // Sort by timestamp (oldest first) std::sort(auto_backups.begin(), auto_backups.end()); - + // Delete oldest until within limits while (snapshots_.size() > config_.max_snapshots && !auto_backups.empty()) { snapshots_.erase(auto_backups.front().second); auto_backups.erase(auto_backups.begin()); } - + // Check storage limit while (GetTotalStorageUsed() > config_.max_storage_mb * 1024 * 1024 && !auto_backups.empty()) { snapshots_.erase(auto_backups.front().second); auto_backups.erase(auto_backups.begin()); } - + return absl::OkStatus(); } RomVersionManager::Stats RomVersionManager::GetStats() const { Stats stats = {}; stats.total_snapshots = snapshots_.size(); - + for (const auto& [id, snapshot] : snapshots_) { - if (snapshot.is_safe_point) stats.safe_points++; - if (snapshot.is_checkpoint) stats.manual_checkpoints++; - if (!snapshot.is_checkpoint) stats.auto_backups++; + if (snapshot.is_safe_point) + stats.safe_points++; + if (snapshot.is_checkpoint) + stats.manual_checkpoints++; + if (!snapshot.is_checkpoint) + stats.auto_backups++; stats.total_storage_bytes += snapshot.compressed_size; - + if (stats.oldest_snapshot_timestamp == 0 || snapshot.timestamp < stats.oldest_snapshot_timestamp) { stats.oldest_snapshot_timestamp = snapshot.timestamp; } - + if (snapshot.timestamp > stats.newest_snapshot_timestamp) { stats.newest_snapshot_timestamp = snapshot.timestamp; } } - + return stats; } @@ -329,7 +329,7 @@ std::string RomVersionManager::ComputeRomHash() const { if (!rom_ || !rom_->is_loaded()) { return ""; } - + std::vector data(rom_->size()); std::memcpy(data.data(), rom_->data(), rom_->size()); return ComputeHash(data); @@ -352,18 +352,18 @@ absl::Status RomVersionManager::ValidateRomIntegrity() const { if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } - + // Basic checks if (rom_->size() == 0) { return absl::DataLossError("ROM size is zero"); } - + // Check for valid SNES header // (This is a simplified check - real validation would be more thorough) if (rom_->size() < 0x8000) { return absl::DataLossError("ROM too small to be valid"); } - + return absl::OkStatus(); } @@ -380,9 +380,7 @@ size_t RomVersionManager::GetTotalStorageUsed() const { // ============================================================================ ProposalApprovalManager::ProposalApprovalManager(RomVersionManager* version_mgr) - : version_mgr_(version_mgr), - mode_(ApprovalMode::kHostOnly) { -} + : version_mgr_(version_mgr), mode_(ApprovalMode::kHostOnly) {} void ProposalApprovalManager::SetApprovalMode(ApprovalMode mode) { mode_ = mode; @@ -393,53 +391,48 @@ void ProposalApprovalManager::SetHost(const std::string& host_username) { } absl::Status ProposalApprovalManager::SubmitProposal( - const std::string& proposal_id, - const std::string& sender, - const std::string& description, - const nlohmann::json& proposal_data) { - + const std::string& proposal_id, const std::string& sender, + const std::string& description, const nlohmann::json& proposal_data) { + ApprovalStatus status; status.proposal_id = proposal_id; status.status = "pending"; status.created_at = GetCurrentTimestamp(); status.decided_at = 0; - + // Create snapshot before potential application auto snapshot_result = version_mgr_->CreateSnapshot( - absl::StrCat("Before proposal: ", description), - sender, - false); - + absl::StrCat("Before proposal: ", description), sender, false); + if (!snapshot_result.ok()) { return snapshot_result.status(); } - + status.snapshot_before = *snapshot_result; - + proposals_[proposal_id] = status; - + return absl::OkStatus(); } absl::Status ProposalApprovalManager::VoteOnProposal( - const std::string& proposal_id, - const std::string& username, + const std::string& proposal_id, const std::string& username, bool approved) { - + auto it = proposals_.find(proposal_id); if (it == proposals_.end()) { return absl::NotFoundError("Proposal not found"); } - + ApprovalStatus& status = it->second; - + if (status.status != "pending") { return absl::FailedPreconditionError("Proposal already decided"); } - + // Record vote status.votes[username] = approved; - + // Check if decision can be made if (CheckApprovalThreshold(status)) { status.status = "approved"; @@ -448,23 +441,24 @@ absl::Status ProposalApprovalManager::VoteOnProposal( // Check if rejection threshold reached size_t rejection_count = 0; for (const auto& [user, vote] : status.votes) { - if (!vote) rejection_count++; + if (!vote) + rejection_count++; } - + // If host rejected (in host-only mode), reject immediately - if (mode_ == ApprovalMode::kHostOnly && - username == host_username_ && !approved) { + if (mode_ == ApprovalMode::kHostOnly && username == host_username_ && + !approved) { status.status = "rejected"; status.decided_at = GetCurrentTimestamp(); } } - + return absl::OkStatus(); } bool ProposalApprovalManager::CheckApprovalThreshold( const ApprovalStatus& status) const { - + switch (mode_) { case ApprovalMode::kHostOnly: // Only host vote matters @@ -472,29 +466,31 @@ bool ProposalApprovalManager::CheckApprovalThreshold( return status.votes.at(host_username_); } return false; - + case ApprovalMode::kMajorityVote: { size_t approval_count = 0; for (const auto& [user, approved] : status.votes) { - if (approved) approval_count++; + if (approved) + approval_count++; } return approval_count > participants_.size() / 2; } - + case ApprovalMode::kUnanimous: { if (status.votes.size() < participants_.size()) { return false; // Not everyone voted yet } for (const auto& [user, approved] : status.votes) { - if (!approved) return false; + if (!approved) + return false; } return true; } - + case ApprovalMode::kAutoApprove: return true; } - + return false; } @@ -507,7 +503,7 @@ bool ProposalApprovalManager::IsProposalApproved( return it->second.status == "approved"; } -std::vector +std::vector ProposalApprovalManager::GetPendingProposals() const { std::vector pending; for (const auto& [id, status] : proposals_) { @@ -518,7 +514,7 @@ ProposalApprovalManager::GetPendingProposals() const { return pending; } -absl::StatusOr +absl::StatusOr ProposalApprovalManager::GetProposalStatus( const std::string& proposal_id) const { auto it = proposals_.find(proposal_id); diff --git a/src/app/net/rom_version_manager.h b/src/app/net/rom_version_manager.h index 6a2cb75a..64909095 100644 --- a/src/app/net/rom_version_manager.h +++ b/src/app/net/rom_version_manager.h @@ -29,12 +29,12 @@ struct RomSnapshot { std::string rom_hash; std::vector rom_data; size_t compressed_size; - + // Metadata std::string creator; bool is_checkpoint; // Manual checkpoint vs auto-backup bool is_safe_point; // Marked as "known good" by host - + #ifdef YAZE_WITH_JSON nlohmann::json metadata; // Custom metadata (proposals applied, etc.) #endif @@ -73,87 +73,84 @@ class RomVersionManager { bool compress_snapshots = true; bool enable_corruption_detection = true; }; - + explicit RomVersionManager(Rom* rom); ~RomVersionManager(); - + /** * Initialize version management */ absl::Status Initialize(const Config& config); - + /** * Create a snapshot of current ROM state */ - absl::StatusOr CreateSnapshot( - const std::string& description, - const std::string& creator, - bool is_checkpoint = false); - + absl::StatusOr CreateSnapshot(const std::string& description, + const std::string& creator, + bool is_checkpoint = false); + /** * Restore ROM to a previous snapshot */ absl::Status RestoreSnapshot(const std::string& snapshot_id); - + /** * Mark a snapshot as a safe point (host-verified) */ absl::Status MarkAsSafePoint(const std::string& snapshot_id); - + /** * Get all snapshots, sorted by timestamp */ std::vector GetSnapshots(bool safe_points_only = false) const; - + /** * Get a specific snapshot */ absl::StatusOr GetSnapshot(const std::string& snapshot_id) const; - + /** * Delete a snapshot */ absl::Status DeleteSnapshot(const std::string& snapshot_id); - + /** * Generate diff between two snapshots */ - absl::StatusOr GenerateDiff( - const std::string& from_id, - const std::string& to_id) const; - + absl::StatusOr GenerateDiff(const std::string& from_id, + const std::string& to_id) const; + /** * Check for ROM corruption */ absl::StatusOr DetectCorruption(); - + /** * Auto-recover from corruption using nearest safe point */ absl::Status AutoRecover(); - + /** * Export snapshot to file */ - absl::Status ExportSnapshot( - const std::string& snapshot_id, - const std::string& filepath); - + absl::Status ExportSnapshot(const std::string& snapshot_id, + const std::string& filepath); + /** * Import snapshot from file */ absl::Status ImportSnapshot(const std::string& filepath); - + /** * Get current ROM hash */ std::string GetCurrentHash() const; - + /** * Cleanup old snapshots based on policy */ absl::Status CleanupOldSnapshots(); - + /** * Get statistics */ @@ -167,18 +164,19 @@ class RomVersionManager { int64_t newest_snapshot_timestamp; }; Stats GetStats() const; - + private: Rom* rom_; Config config_; std::map snapshots_; std::string last_known_hash_; int64_t last_backup_time_; - + // Helper functions std::string ComputeRomHash() const; std::vector CompressData(const std::vector& data) const; - std::vector DecompressData(const std::vector& compressed) const; + std::vector DecompressData( + const std::vector& compressed) const; absl::Status ValidateRomIntegrity() const; size_t GetTotalStorageUsed() const; void PruneOldSnapshots(); @@ -197,12 +195,12 @@ class RomVersionManager { class ProposalApprovalManager { public: enum class ApprovalMode { - kHostOnly, // Only host can approve - kMajorityVote, // Majority of participants must approve - kUnanimous, // All participants must approve - kAutoApprove // No approval needed (dangerous!) + kHostOnly, // Only host can approve + kMajorityVote, // Majority of participants must approve + kUnanimous, // All participants must approve + kAutoApprove // No approval needed (dangerous!) }; - + struct ApprovalStatus { std::string proposal_id; std::string status; // "pending", "approved", "rejected", "applied" @@ -212,76 +210,71 @@ class ProposalApprovalManager { std::string snapshot_before; // Snapshot ID before applying std::string snapshot_after; // Snapshot ID after applying }; - + explicit ProposalApprovalManager(RomVersionManager* version_mgr); - + /** * Set approval mode for the session */ void SetApprovalMode(ApprovalMode mode); - + /** * Set host username */ void SetHost(const std::string& host_username); - + /** * Submit a proposal for approval */ - absl::Status SubmitProposal( - const std::string& proposal_id, - const std::string& sender, - const std::string& description, - const nlohmann::json& proposal_data); - + absl::Status SubmitProposal(const std::string& proposal_id, + const std::string& sender, + const std::string& description, + const nlohmann::json& proposal_data); + /** * Vote on a proposal */ - absl::Status VoteOnProposal( - const std::string& proposal_id, - const std::string& username, - bool approved); - + absl::Status VoteOnProposal(const std::string& proposal_id, + const std::string& username, bool approved); + /** * Apply an approved proposal */ - absl::Status ApplyProposal( - const std::string& proposal_id, - Rom* rom); - + absl::Status ApplyProposal(const std::string& proposal_id, Rom* rom); + /** * Reject and rollback a proposal */ absl::Status RejectProposal(const std::string& proposal_id); - + /** * Get proposal status */ absl::StatusOr GetProposalStatus( const std::string& proposal_id) const; - + /** * Get all pending proposals */ std::vector GetPendingProposals() const; - + /** * Check if proposal is approved */ bool IsProposalApproved(const std::string& proposal_id) const; - + /** * Get audit log */ std::vector GetAuditLog() const; - + private: RomVersionManager* version_mgr_; ApprovalMode mode_; std::string host_username_; std::map proposals_; std::vector participants_; - + bool CheckApprovalThreshold(const ApprovalStatus& status) const; }; diff --git a/src/app/net/websocket_client.cc b/src/app/net/websocket_client.cc index 48f17390..5391bd89 100644 --- a/src/app/net/websocket_client.cc +++ b/src/app/net/websocket_client.cc @@ -23,112 +23,111 @@ namespace net { class WebSocketClient::Impl { public: Impl() : connected_(false), should_stop_(false) {} - - ~Impl() { - Disconnect(); - } - + + ~Impl() { Disconnect(); } + absl::Status Connect(const std::string& host, int port) { std::lock_guard lock(mutex_); - + if (connected_) { return absl::AlreadyExistsError("Already connected"); } - + host_ = host; port_ = port; - + try { // httplib WebSocket connection (cross-platform) std::string url = absl::StrFormat("ws://%s:%d", host, port); - + // Create WebSocket connection client_ = std::make_unique(host, port); client_->set_connection_timeout(5, 0); // 5 seconds - client_->set_read_timeout(30, 0); // 30 seconds - + client_->set_read_timeout(30, 0); // 30 seconds + connected_ = true; should_stop_ = false; - + // Start receive thread receive_thread_ = std::thread([this]() { ReceiveLoop(); }); - + return absl::OkStatus(); - + } catch (const std::exception& e) { return absl::UnavailableError( absl::StrCat("Failed to connect: ", e.what())); } } - + void Disconnect() { std::lock_guard lock(mutex_); - - if (!connected_) return; - + + if (!connected_) + return; + should_stop_ = true; connected_ = false; - + if (receive_thread_.joinable()) { receive_thread_.join(); } - + client_.reset(); } - + absl::Status Send(const std::string& message) { std::lock_guard lock(mutex_); - + if (!connected_) { return absl::FailedPreconditionError("Not connected"); } - + try { // In a real implementation, this would use WebSocket send // For now, we'll use HTTP POST as fallback auto res = client_->Post("/message", message, "application/json"); - + if (!res) { return absl::UnavailableError("Failed to send message"); } - + if (res->status != 200) { return absl::InternalError( absl::StrFormat("Server error: %d", res->status)); } - + return absl::OkStatus(); - + } catch (const std::exception& e) { return absl::InternalError(absl::StrCat("Send failed: ", e.what())); } } - + void SetMessageCallback(std::function callback) { std::lock_guard lock(mutex_); message_callback_ = callback; } - + void SetErrorCallback(std::function callback) { std::lock_guard lock(mutex_); error_callback_ = callback; } - + bool IsConnected() const { std::lock_guard lock(mutex_); return connected_; } - + private: void ReceiveLoop() { while (!should_stop_) { try { // Poll for messages (platform-independent) std::this_thread::sleep_for(std::chrono::milliseconds(100)); - + // In a real WebSocket implementation, this would receive messages // For now, this is a placeholder for the receive loop - + } catch (const std::exception& e) { if (error_callback_) { error_callback_(e.what()); @@ -136,16 +135,16 @@ class WebSocketClient::Impl { } } } - + mutable std::mutex mutex_; std::unique_ptr client_; std::thread receive_thread_; - + std::string host_; int port_; bool connected_; bool should_stop_; - + std::function message_callback_; std::function error_callback_; }; @@ -174,9 +173,7 @@ class WebSocketClient::Impl { // ============================================================================ WebSocketClient::WebSocketClient() - : impl_(std::make_unique()), - state_(ConnectionState::kDisconnected) { -} + : impl_(std::make_unique()), state_(ConnectionState::kDisconnected) {} WebSocketClient::~WebSocketClient() { Disconnect(); @@ -184,13 +181,13 @@ WebSocketClient::~WebSocketClient() { absl::Status WebSocketClient::Connect(const std::string& host, int port) { auto status = impl_->Connect(host, port); - + if (status.ok()) { SetState(ConnectionState::kConnected); } else { SetState(ConnectionState::kError); } - + return status; } @@ -201,31 +198,26 @@ void WebSocketClient::Disconnect() { } absl::StatusOr WebSocketClient::HostSession( - const std::string& session_name, - const std::string& username, - const std::string& rom_hash, - bool ai_enabled) { - + const std::string& session_name, const std::string& username, + const std::string& rom_hash, bool ai_enabled) { + #ifdef YAZE_WITH_JSON if (!IsConnected()) { return absl::FailedPreconditionError("Not connected to server"); } - - nlohmann::json message = { - {"type", "host_session"}, - {"payload", { - {"session_name", session_name}, - {"username", username}, - {"rom_hash", rom_hash}, - {"ai_enabled", ai_enabled} - }} - }; - + + nlohmann::json message = {{"type", "host_session"}, + {"payload", + {{"session_name", session_name}, + {"username", username}, + {"rom_hash", rom_hash}, + {"ai_enabled", ai_enabled}}}}; + auto status = SendRaw(message); if (!status.ok()) { return status; } - + // In a real implementation, we'd wait for the server response // For now, return a placeholder SessionInfo session; @@ -233,7 +225,7 @@ absl::StatusOr WebSocketClient::HostSession( session.host = username; session.rom_hash = rom_hash; session.ai_enabled = ai_enabled; - + current_session_ = session; return session; #else @@ -242,31 +234,26 @@ absl::StatusOr WebSocketClient::HostSession( } absl::StatusOr WebSocketClient::JoinSession( - const std::string& session_code, - const std::string& username) { - + const std::string& session_code, const std::string& username) { + #ifdef YAZE_WITH_JSON if (!IsConnected()) { return absl::FailedPreconditionError("Not connected to server"); } - + nlohmann::json message = { - {"type", "join_session"}, - {"payload", { - {"session_code", session_code}, - {"username", username} - }} - }; - + {"type", "join_session"}, + {"payload", {{"session_code", session_code}, {"username", username}}}}; + auto status = SendRaw(message); if (!status.ok()) { return status; } - + // Placeholder - would wait for server response SessionInfo session; session.session_code = session_code; - + current_session_ = session; return session; #else @@ -279,12 +266,9 @@ absl::Status WebSocketClient::LeaveSession() { if (!InSession()) { return absl::FailedPreconditionError("Not in a session"); } - - nlohmann::json message = { - {"type", "leave_session"}, - {"payload", {}} - }; - + + nlohmann::json message = {{"type", "leave_session"}, {"payload", {}}}; + auto status = SendRaw(message); current_session_ = SessionInfo{}; return status; @@ -293,80 +277,61 @@ absl::Status WebSocketClient::LeaveSession() { #endif } -absl::Status WebSocketClient::SendChatMessage( - const std::string& message, - const std::string& sender) { - +absl::Status WebSocketClient::SendChatMessage(const std::string& message, + const std::string& sender) { + #ifdef YAZE_WITH_JSON nlohmann::json msg = { - {"type", "chat_message"}, - {"payload", { - {"message", message}, - {"sender", sender} - }} - }; - + {"type", "chat_message"}, + {"payload", {{"message", message}, {"sender", sender}}}}; + return SendRaw(msg); #else return absl::UnimplementedError("JSON support required"); #endif } -absl::Status WebSocketClient::SendRomSync( - const std::string& diff_data, - const std::string& rom_hash, - const std::string& sender) { - +absl::Status WebSocketClient::SendRomSync(const std::string& diff_data, + const std::string& rom_hash, + const std::string& sender) { + #ifdef YAZE_WITH_JSON nlohmann::json message = { - {"type", "rom_sync"}, - {"payload", { - {"diff_data", diff_data}, - {"rom_hash", rom_hash}, - {"sender", sender} - }} - }; - + {"type", "rom_sync"}, + {"payload", + {{"diff_data", diff_data}, {"rom_hash", rom_hash}, {"sender", sender}}}}; + return SendRaw(message); #else return absl::UnimplementedError("JSON support required"); #endif } -absl::Status WebSocketClient::ShareProposal( - const nlohmann::json& proposal_data, - const std::string& sender) { - +absl::Status WebSocketClient::ShareProposal(const nlohmann::json& proposal_data, + const std::string& sender) { + #ifdef YAZE_WITH_JSON nlohmann::json message = { - {"type", "proposal_share"}, - {"payload", { - {"sender", sender}, - {"proposal_data", proposal_data} - }} - }; - + {"type", "proposal_share"}, + {"payload", {{"sender", sender}, {"proposal_data", proposal_data}}}}; + return SendRaw(message); #else return absl::UnimplementedError("JSON support required"); #endif } -absl::Status WebSocketClient::VoteOnProposal( - const std::string& proposal_id, - bool approved, - const std::string& username) { - +absl::Status WebSocketClient::VoteOnProposal(const std::string& proposal_id, + bool approved, + const std::string& username) { + #ifdef YAZE_WITH_JSON - nlohmann::json message = { - {"type", "proposal_vote"}, - {"payload", { - {"proposal_id", proposal_id}, - {"approved", approved}, - {"username", username} - }} - }; - + nlohmann::json message = {{"type", "proposal_vote"}, + {"payload", + {{"proposal_id", proposal_id}, + {"approved", approved}, + {"username", username}}}}; + return SendRaw(message); #else return absl::UnimplementedError("JSON support required"); @@ -374,25 +339,21 @@ absl::Status WebSocketClient::VoteOnProposal( } absl::Status WebSocketClient::UpdateProposalStatus( - const std::string& proposal_id, - const std::string& status) { - + const std::string& proposal_id, const std::string& status) { + #ifdef YAZE_WITH_JSON nlohmann::json message = { - {"type", "proposal_update"}, - {"payload", { - {"proposal_id", proposal_id}, - {"status", status} - }} - }; - + {"type", "proposal_update"}, + {"payload", {{"proposal_id", proposal_id}, {"status", status}}}}; + return SendRaw(message); #else return absl::UnimplementedError("JSON support required"); #endif } -void WebSocketClient::OnMessage(const std::string& type, MessageCallback callback) { +void WebSocketClient::OnMessage(const std::string& type, + MessageCallback callback) { message_callbacks_[type].push_back(callback); } @@ -418,7 +379,7 @@ void WebSocketClient::HandleMessage(const std::string& message) { try { auto json = nlohmann::json::parse(message); std::string type = json["type"]; - + auto it = message_callbacks_.find(type); if (it != message_callbacks_.end()) { for (auto& callback : it->second) { diff --git a/src/app/net/websocket_client.h b/src/app/net/websocket_client.h index c6ac4044..8e1dfa92 100644 --- a/src/app/net/websocket_client.h +++ b/src/app/net/websocket_client.h @@ -61,148 +61,138 @@ class WebSocketClient { using MessageCallback = std::function; using ErrorCallback = std::function; using StateCallback = std::function; - + WebSocketClient(); ~WebSocketClient(); - + /** * Connect to yaze-server * @param host Server hostname/IP * @param port Server port (default: 8765) */ absl::Status Connect(const std::string& host, int port = 8765); - + /** * Disconnect from server */ void Disconnect(); - + /** * Host a new collaboration session */ - absl::StatusOr HostSession( - const std::string& session_name, - const std::string& username, - const std::string& rom_hash, - bool ai_enabled = true); - + absl::StatusOr HostSession(const std::string& session_name, + const std::string& username, + const std::string& rom_hash, + bool ai_enabled = true); + /** * Join an existing session */ - absl::StatusOr JoinSession( - const std::string& session_code, - const std::string& username); - + absl::StatusOr JoinSession(const std::string& session_code, + const std::string& username); + /** * Leave current session */ absl::Status LeaveSession(); - + /** * Send chat message */ - absl::Status SendChatMessage( - const std::string& message, - const std::string& sender); - + absl::Status SendChatMessage(const std::string& message, + const std::string& sender); + /** * Send ROM sync */ - absl::Status SendRomSync( - const std::string& diff_data, - const std::string& rom_hash, - const std::string& sender); - + absl::Status SendRomSync(const std::string& diff_data, + const std::string& rom_hash, + const std::string& sender); + /** * Share snapshot */ - absl::Status ShareSnapshot( - const std::string& snapshot_data, - const std::string& snapshot_type, - const std::string& sender); - + absl::Status ShareSnapshot(const std::string& snapshot_data, + const std::string& snapshot_type, + const std::string& sender); + /** * Share proposal for approval */ - absl::Status ShareProposal( - const nlohmann::json& proposal_data, - const std::string& sender); - + absl::Status ShareProposal(const nlohmann::json& proposal_data, + const std::string& sender); + /** * Vote on proposal (approve/reject) */ - absl::Status VoteOnProposal( - const std::string& proposal_id, - bool approved, - const std::string& username); - + absl::Status VoteOnProposal(const std::string& proposal_id, bool approved, + const std::string& username); + /** * Update proposal status */ - absl::Status UpdateProposalStatus( - const std::string& proposal_id, - const std::string& status); - + absl::Status UpdateProposalStatus(const std::string& proposal_id, + const std::string& status); + /** * Send AI query */ - absl::Status SendAIQuery( - const std::string& query, - const std::string& username); - + absl::Status SendAIQuery(const std::string& query, + const std::string& username); + /** * Register callback for specific message type */ void OnMessage(const std::string& type, MessageCallback callback); - + /** * Register callback for errors */ void OnError(ErrorCallback callback); - + /** * Register callback for connection state changes */ void OnStateChange(StateCallback callback); - + /** * Get current connection state */ ConnectionState GetState() const { return state_; } - + /** * Get current session info (if in a session) */ absl::StatusOr GetSessionInfo() const; - + /** * Check if connected */ bool IsConnected() const { return state_ == ConnectionState::kConnected; } - + /** * Check if in a session */ bool InSession() const { return !current_session_.session_id.empty(); } - + private: // Implementation details (using native WebSocket or library) class Impl; std::unique_ptr impl_; - + ConnectionState state_; SessionInfo current_session_; - + // Callbacks std::map> message_callbacks_; std::vector error_callbacks_; std::vector state_callbacks_; - + // Internal message handling void HandleMessage(const std::string& message); void HandleError(const std::string& error); void SetState(ConnectionState state); - + // Send raw message absl::Status SendRaw(const nlohmann::json& message); }; diff --git a/src/app/platform/app_delegate.h b/src/app/platform/app_delegate.h index a9df15a9..3b67f9e1 100644 --- a/src/app/platform/app_delegate.h +++ b/src/app/platform/app_delegate.h @@ -15,25 +15,25 @@ UIDocumentPickerDelegate, UITabBarControllerDelegate, PKCanvasViewDelegate> -@property(strong, nonatomic) UIWindow *window; +@property(strong, nonatomic) UIWindow* window; -@property UIDocumentPickerViewController *documentPicker; -@property(nonatomic, copy) void (^completionHandler)(NSString *selectedFile); +@property UIDocumentPickerViewController* documentPicker; +@property(nonatomic, copy) void (^completionHandler)(NSString* selectedFile); - (void)PresentDocumentPickerWithCompletionHandler: - (void (^)(NSString *selectedFile))completionHandler; + (void (^)(NSString* selectedFile))completionHandler; // TODO: Setup a tab bar controller for multiple yaze instances -@property(nonatomic) UITabBarController *tabBarController; +@property(nonatomic) UITabBarController* tabBarController; // TODO: Setup a font picker for the text editor and display settings -@property(nonatomic) UIFontPickerViewController *fontPicker; +@property(nonatomic) UIFontPickerViewController* fontPicker; // TODO: Setup the pencil kit for drawing -@property PKToolPicker *toolPicker; -@property PKCanvasView *canvasView; +@property PKToolPicker* toolPicker; +@property PKCanvasView* canvasView; // TODO: Setup the file manager for file operations -@property NSFileManager *fileManager; +@property NSFileManager* fileManager; @end @@ -51,7 +51,7 @@ void yaze_initialize_cocoa(); /** * @brief Run the Cocoa application delegate. */ -int yaze_run_cocoa_app_delegate(const char *filename); +int yaze_run_cocoa_app_delegate(const char* filename); #ifdef __cplusplus } // extern "C" diff --git a/src/app/platform/asset_loader.cc b/src/app/platform/asset_loader.cc index 1e0bd0fb..1dc9d832 100644 --- a/src/app/platform/asset_loader.cc +++ b/src/app/platform/asset_loader.cc @@ -8,81 +8,90 @@ namespace yaze { - -std::vector AssetLoader::GetSearchPaths(const std::string& relative_path) { +std::vector AssetLoader::GetSearchPaths( + const std::string& relative_path) { std::vector search_paths; - + #ifdef __APPLE__ // macOS bundle resource paths std::string bundle_root = yaze::util::GetBundleResourcePath(); - + // Try Contents/Resources first (standard bundle location) - search_paths.push_back(std::filesystem::path(bundle_root) / "Contents" / "Resources" / relative_path); - + search_paths.push_back(std::filesystem::path(bundle_root) / "Contents" / + "Resources" / relative_path); + // Try without Contents (if app is at root) - search_paths.push_back(std::filesystem::path(bundle_root) / "Resources" / relative_path); - + search_paths.push_back(std::filesystem::path(bundle_root) / "Resources" / + relative_path); + // Development paths (when running from build dir) - search_paths.push_back(std::filesystem::path(bundle_root) / ".." / ".." / ".." / "assets" / relative_path); - search_paths.push_back(std::filesystem::path(bundle_root) / ".." / ".." / ".." / ".." / "assets" / relative_path); + search_paths.push_back(std::filesystem::path(bundle_root) / ".." / ".." / + ".." / "assets" / relative_path); + search_paths.push_back(std::filesystem::path(bundle_root) / ".." / ".." / + ".." / ".." / "assets" / relative_path); #endif - + // Standard relative paths (works for all platforms) search_paths.push_back(std::filesystem::path("assets") / relative_path); search_paths.push_back(std::filesystem::path("../assets") / relative_path); search_paths.push_back(std::filesystem::path("../../assets") / relative_path); - search_paths.push_back(std::filesystem::path("../../../assets") / relative_path); - search_paths.push_back(std::filesystem::path("../../../../assets") / relative_path); - + search_paths.push_back(std::filesystem::path("../../../assets") / + relative_path); + search_paths.push_back(std::filesystem::path("../../../../assets") / + relative_path); + // Build directory paths search_paths.push_back(std::filesystem::path("build/assets") / relative_path); - search_paths.push_back(std::filesystem::path("../build/assets") / relative_path); - + search_paths.push_back(std::filesystem::path("../build/assets") / + relative_path); + return search_paths; } -absl::StatusOr AssetLoader::FindAssetFile(const std::string& relative_path) { +absl::StatusOr AssetLoader::FindAssetFile( + const std::string& relative_path) { auto search_paths = GetSearchPaths(relative_path); - + for (const auto& path : search_paths) { if (std::filesystem::exists(path)) { return path; } } - + // Debug: Print searched paths std::string searched_paths; for (const auto& path : search_paths) { searched_paths += "\n - " + path.string(); } - + return absl::NotFoundError( - absl::StrFormat("Asset file not found: %s\nSearched paths:%s", + absl::StrFormat("Asset file not found: %s\nSearched paths:%s", relative_path, searched_paths)); } -absl::StatusOr AssetLoader::LoadTextFile(const std::string& relative_path) { +absl::StatusOr AssetLoader::LoadTextFile( + const std::string& relative_path) { auto path_result = FindAssetFile(relative_path); if (!path_result.ok()) { return path_result.status(); } - + const auto& path = *path_result; std::ifstream file(path); if (!file.is_open()) { return absl::InternalError( absl::StrFormat("Failed to open file: %s", path.string())); } - + std::stringstream buffer; buffer << file.rdbuf(); std::string content = buffer.str(); - + if (content.empty()) { return absl::InternalError( absl::StrFormat("File is empty: %s", path.string())); } - + return content; } @@ -90,5 +99,4 @@ bool AssetLoader::AssetExists(const std::string& relative_path) { return FindAssetFile(relative_path).ok(); } - } // namespace yaze diff --git a/src/app/platform/asset_loader.h b/src/app/platform/asset_loader.h index d4044d6a..9cd7e004 100644 --- a/src/app/platform/asset_loader.h +++ b/src/app/platform/asset_loader.h @@ -9,7 +9,6 @@ namespace yaze { - /** * @class AssetLoader * @brief Cross-platform asset file loading utility @@ -27,22 +26,25 @@ class AssetLoader { * @param relative_path Path relative to assets/ (e.g., "agent/system_prompt.txt") * @return File contents or error */ - static absl::StatusOr LoadTextFile(const std::string& relative_path); - + static absl::StatusOr LoadTextFile( + const std::string& relative_path); + /** * Find an asset file by trying multiple platform-specific paths * @param relative_path Path relative to assets/ * @return Full path to file or error */ - static absl::StatusOr FindAssetFile(const std::string& relative_path); - + static absl::StatusOr FindAssetFile( + const std::string& relative_path); + /** * Get list of search paths for a given asset * @param relative_path Path relative to assets/ * @return Vector of paths to try in order */ - static std::vector GetSearchPaths(const std::string& relative_path); - + static std::vector GetSearchPaths( + const std::string& relative_path); + /** * Check if an asset file exists * @param relative_path Path relative to assets/ @@ -51,7 +53,6 @@ class AssetLoader { static bool AssetExists(const std::string& relative_path); }; - } // namespace yaze #endif // YAZE_APP_PLATFORM_ASSET_LOADER_H_ diff --git a/src/app/platform/file_dialog_nfd.cc b/src/app/platform/file_dialog_nfd.cc index deb4e079..4a183df6 100644 --- a/src/app/platform/file_dialog_nfd.cc +++ b/src/app/platform/file_dialog_nfd.cc @@ -3,64 +3,64 @@ #include #include -#include #include +#include namespace yaze { namespace util { std::string FileDialogWrapper::ShowOpenFileDialog() { nfdchar_t* outPath = nullptr; - nfdfilteritem_t filterItem[2] = {{"ROM Files", "sfc,smc"}, {"All Files", "*"}}; + nfdfilteritem_t filterItem[2] = {{"ROM Files", "sfc,smc"}, + {"All Files", "*"}}; nfdresult_t result = NFD_OpenDialog(&outPath, filterItem, 2, nullptr); - + if (result == NFD_OKAY) { std::string path(outPath); NFD_FreePath(outPath); return path; } - + return ""; } std::string FileDialogWrapper::ShowOpenFolderDialog() { nfdchar_t* outPath = nullptr; nfdresult_t result = NFD_PickFolder(&outPath, nullptr); - + if (result == NFD_OKAY) { std::string path(outPath); NFD_FreePath(outPath); return path; } - + return ""; } -std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name, - const std::string& default_extension) { +std::string FileDialogWrapper::ShowSaveFileDialog( + const std::string& default_name, const std::string& default_extension) { nfdchar_t* outPath = nullptr; - nfdfilteritem_t filterItem[1] = {{default_extension.empty() ? "All Files" : default_extension.c_str(), - default_extension.empty() ? "*" : default_extension.c_str()}}; - - nfdresult_t result = NFD_SaveDialog(&outPath, - default_extension.empty() ? nullptr : filterItem, - default_extension.empty() ? 0 : 1, - nullptr, - default_name.c_str()); - + nfdfilteritem_t filterItem[1] = { + {default_extension.empty() ? "All Files" : default_extension.c_str(), + default_extension.empty() ? "*" : default_extension.c_str()}}; + + nfdresult_t result = NFD_SaveDialog( + &outPath, default_extension.empty() ? nullptr : filterItem, + default_extension.empty() ? 0 : 1, nullptr, default_name.c_str()); + if (result == NFD_OKAY) { std::string path(outPath); NFD_FreePath(outPath); return path; } - + return ""; } std::vector FileDialogWrapper::GetSubdirectoriesInFolder( const std::string& folder_path) { std::vector subdirs; - + try { for (const auto& entry : std::filesystem::directory_iterator(folder_path)) { if (entry.is_directory()) { @@ -70,14 +70,14 @@ std::vector FileDialogWrapper::GetSubdirectoriesInFolder( } catch (...) { // Return empty vector on error } - + return subdirs; } std::vector FileDialogWrapper::GetFilesInFolder( const std::string& folder_path) { std::vector files; - + try { for (const auto& entry : std::filesystem::directory_iterator(folder_path)) { if (entry.is_regular_file()) { @@ -87,7 +87,7 @@ std::vector FileDialogWrapper::GetFilesInFolder( } catch (...) { // Return empty vector on error } - + return files; } @@ -100,13 +100,13 @@ std::string FileDialogWrapper::ShowOpenFileDialogBespoke() { return ShowOpenFileDialog(); } -std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name, - const std::string& default_extension) { +std::string FileDialogWrapper::ShowSaveFileDialogNFD( + const std::string& default_name, const std::string& default_extension) { return ShowSaveFileDialog(default_name, default_extension); } -std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name, - const std::string& default_extension) { +std::string FileDialogWrapper::ShowSaveFileDialogBespoke( + const std::string& default_name, const std::string& default_extension) { return ShowSaveFileDialog(default_name, default_extension); } @@ -120,4 +120,3 @@ std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() { } // namespace util } // namespace yaze - diff --git a/src/app/platform/font_loader.cc b/src/app/platform/font_loader.cc index eaa9b7e2..93fa4ba3 100644 --- a/src/app/platform/font_loader.cc +++ b/src/app/platform/font_loader.cc @@ -1,17 +1,16 @@ #include "app/platform/font_loader.h" +#include #include #include #include -#include - #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" -#include "util/file_util.h" #include "app/gui/core/icons.h" #include "imgui/imgui.h" +#include "util/file_util.h" #include "util/macro.h" namespace yaze { @@ -54,7 +53,7 @@ absl::Status LoadFont(const FontConfig& font_config) { } if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(), - font_config.font_size)) { + font_config.font_size)) { return absl::InternalError( absl::StrFormat("Failed to load font from %s", actual_font_path)); } @@ -70,8 +69,9 @@ absl::Status AddIconFont(const FontConfig& /*config*/) { icons_config.PixelSnapH = true; std::string icon_font_path = SetFontPath(FONT_ICON_FILE_NAME_MD); ImGuiIO& imgui_io = ImGui::GetIO(); - if (!imgui_io.Fonts->AddFontFromFileTTF(icon_font_path.c_str(), ICON_FONT_SIZE, - &icons_config, icons_ranges)) { + if (!imgui_io.Fonts->AddFontFromFileTTF(icon_font_path.c_str(), + ICON_FONT_SIZE, &icons_config, + icons_ranges)) { return absl::InternalError("Failed to add icon fonts"); } return absl::OkStatus(); @@ -85,9 +85,9 @@ absl::Status AddJapaneseFont(const FontConfig& /*config*/) { japanese_font_config.PixelSnapH = true; std::string japanese_font_path = SetFontPath(NOTO_SANS_JP); ImGuiIO& imgui_io = ImGui::GetIO(); - if (!imgui_io.Fonts->AddFontFromFileTTF(japanese_font_path.data(), ICON_FONT_SIZE, - &japanese_font_config, - imgui_io.Fonts->GetGlyphRangesJapanese())) { + if (!imgui_io.Fonts->AddFontFromFileTTF( + japanese_font_path.data(), ICON_FONT_SIZE, &japanese_font_config, + imgui_io.Fonts->GetGlyphRangesJapanese())) { return absl::InternalError("Failed to add Japanese fonts"); } return absl::OkStatus(); @@ -120,7 +120,7 @@ absl::Status ReloadPackageFont(const FontConfig& config) { ImGuiIO& imgui_io = ImGui::GetIO(); std::string actual_font_path = SetFontPath(config.font_path); if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(), - config.font_size)) { + config.font_size)) { return absl::InternalError( absl::StrFormat("Failed to load font from %s", actual_font_path)); } diff --git a/src/app/platform/font_loader.h b/src/app/platform/font_loader.h index e55186e9..a348b784 100644 --- a/src/app/platform/font_loader.h +++ b/src/app/platform/font_loader.h @@ -8,7 +8,6 @@ namespace yaze { - struct FontConfig { const char* font_path; float font_size; @@ -28,7 +27,6 @@ absl::Status ReloadPackageFont(const FontConfig& config); void LoadSystemFonts(); - } // namespace yaze #endif // YAZE_APP_PLATFORM_FONTLOADER_H diff --git a/src/app/platform/timing.h b/src/app/platform/timing.h index b8889934..b39c89ba 100644 --- a/src/app/platform/timing.h +++ b/src/app/platform/timing.h @@ -28,18 +28,18 @@ class TimingManager { float Update() { uint64_t current_time = SDL_GetPerformanceCounter(); float delta_time = 0.0f; - + if (last_time_ > 0) { delta_time = (current_time - last_time_) / static_cast(frequency_); - + // Clamp delta time to prevent huge jumps (e.g., when debugging) if (delta_time > 0.1f) { delta_time = 0.1f; } - + accumulated_time_ += delta_time; frame_count_++; - + // Update FPS counter once per second if (accumulated_time_ >= 1.0f) { fps_ = static_cast(frame_count_) / accumulated_time_; @@ -47,35 +47,32 @@ class TimingManager { accumulated_time_ = 0.0f; } } - + last_time_ = current_time; last_delta_time_ = delta_time; return delta_time; } - + /** * @brief Get the last frame's delta time in seconds */ - float GetDeltaTime() const { - return last_delta_time_; - } - + float GetDeltaTime() const { return last_delta_time_; } + /** * @brief Get current FPS */ - float GetFPS() const { - return fps_; - } - + float GetFPS() const { return fps_; } + /** * @brief Get total elapsed time since first update */ float GetElapsedTime() const { - if (last_time_ == 0) return 0.0f; + if (last_time_ == 0) + return 0.0f; uint64_t current_time = SDL_GetPerformanceCounter(); return (current_time - first_time_) / static_cast(frequency_); } - + /** * @brief Reset the timing state */ @@ -114,4 +111,3 @@ class TimingManager { } // namespace yaze #endif // YAZE_APP_CORE_TIMING_H - diff --git a/src/app/platform/window.cc b/src/app/platform/window.cc index 8a349db4..c86f731f 100644 --- a/src/app/platform/window.cc +++ b/src/app/platform/window.cc @@ -4,45 +4,47 @@ #include "absl/status/status.h" #include "absl/strings/str_format.h" -#include "app/platform/font_loader.h" -#include "util/sdl_deleter.h" -#include "util/log.h" #include "app/gfx/resource/arena.h" #include "app/gui/core/style.h" +#include "app/platform/font_loader.h" #include "imgui/backends/imgui_impl_sdl2.h" #include "imgui/backends/imgui_impl_sdlrenderer2.h" #include "imgui/imgui.h" +#include "util/log.h" +#include "util/sdl_deleter.h" namespace { // Custom ImGui assertion handler to prevent crashes void ImGuiAssertionHandler(const char* expr, const char* file, int line, const char* msg) { // Log the assertion instead of crashing - LOG_ERROR("ImGui", "Assertion failed: %s\nFile: %s:%d\nMessage: %s", - expr, file, line, msg ? msg : ""); - + LOG_ERROR("ImGui", "Assertion failed: %s\nFile: %s:%d\nMessage: %s", expr, + file, line, msg ? msg : ""); + // Try to recover by resetting ImGui state static int error_count = 0; error_count++; - + if (error_count > 5) { LOG_ERROR("ImGui", "Too many assertions, resetting workspace settings..."); - + // Backup and reset imgui.ini try { if (std::filesystem::exists("imgui.ini")) { - std::filesystem::copy("imgui.ini", "imgui.ini.backup", - std::filesystem::copy_options::overwrite_existing); + std::filesystem::copy( + "imgui.ini", "imgui.ini.backup", + std::filesystem::copy_options::overwrite_existing); std::filesystem::remove("imgui.ini"); - LOG_INFO("ImGui", "Workspace settings reset. Backup saved to imgui.ini.backup"); + LOG_INFO("ImGui", + "Workspace settings reset. Backup saved to imgui.ini.backup"); } } catch (const std::exception& e) { LOG_ERROR("ImGui", "Failed to reset workspace: %s", e.what()); } - + error_count = 0; // Reset counter } - + // Don't abort - let the program continue // The assertion is logged and workspace can be reset if needed } @@ -87,7 +89,7 @@ absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer, int flags) { ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - + // Set custom assertion handler to prevent crashes #ifdef IMGUI_DISABLE_DEFAULT_ASSERT_HANDLER ImGui::SetAssertHandler(ImGuiAssertionHandler); @@ -103,7 +105,8 @@ absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer, int flags) { // Initialize ImGui backends if renderer is provided if (renderer) { - SDL_Renderer* sdl_renderer = static_cast(renderer->GetBackendRenderer()); + SDL_Renderer* sdl_renderer = + static_cast(renderer->GetBackendRenderer()); ImGui_ImplSDL2_InitForSDLRenderer(window.window_.get(), sdl_renderer); ImGui_ImplSDLRenderer2_Init(sdl_renderer); } @@ -112,23 +115,25 @@ absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer, int flags) { // Apply original YAZE colors as fallback, then try to load theme system gui::ColorsYaze(); - + // Audio is now handled by IAudioBackend in Emulator class // Keep legacy buffer allocation for backwards compatibility if (window.audio_device_ == 0) { const int audio_frequency = 48000; - const size_t buffer_size = (audio_frequency / 50) * 2; // 1920 int16_t for stereo PAL - + const size_t buffer_size = + (audio_frequency / 50) * 2; // 1920 int16_t for stereo PAL + // CRITICAL FIX: Allocate buffer as ARRAY, not single value // Use new[] with shared_ptr custom deleter for proper array allocation window.audio_buffer_ = std::shared_ptr( - new int16_t[buffer_size], - std::default_delete()); - + new int16_t[buffer_size], std::default_delete()); + // Note: Actual audio device is created by Emulator's IAudioBackend // This maintains compatibility with existing code paths - LOG_INFO("Window", "Audio buffer allocated: %zu int16_t samples (backend in Emulator)", - buffer_size); + LOG_INFO( + "Window", + "Audio buffer allocated: %zu int16_t samples (backend in Emulator)", + buffer_size); } return absl::OkStatus(); @@ -137,39 +142,39 @@ absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer, int flags) { absl::Status ShutdownWindow(Window& window) { SDL_PauseAudioDevice(window.audio_device_, 1); SDL_CloseAudioDevice(window.audio_device_); - + // Stop test engine WHILE ImGui context is still valid #ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE test::TestManager::Get().StopUITesting(); #endif - + // TODO: BAD FIX, SLOW SHUTDOWN TAKES TOO LONG NOW // CRITICAL FIX: Shutdown graphics arena FIRST // This ensures all textures are destroyed while renderer is still valid LOG_INFO("Window", "Shutting down graphics arena..."); gfx::Arena::Get().Shutdown(); - + // Shutdown ImGui implementations (after Arena but before context) LOG_INFO("Window", "Shutting down ImGui implementations..."); ImGui_ImplSDL2_Shutdown(); ImGui_ImplSDLRenderer2_Shutdown(); - + // Destroy ImGui context LOG_INFO("Window", "Destroying ImGui context..."); ImGui::DestroyContext(); - + // NOW destroy test engine context (after ImGui context is destroyed) #ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE test::TestManager::Get().DestroyUITestingContext(); #endif - + // Finally destroy window LOG_INFO("Window", "Destroying window..."); SDL_DestroyWindow(window.window_.get()); - + LOG_INFO("Window", "Shutting down SDL..."); SDL_Quit(); - + LOG_INFO("Window", "Shutdown complete"); return absl::OkStatus(); } @@ -177,7 +182,7 @@ absl::Status ShutdownWindow(Window& window) { absl::Status HandleEvents(Window& window) { SDL_Event event; ImGuiIO& io = ImGui::GetIO(); - + // Protect SDL_PollEvent from crashing the app // macOS NSPersistentUIManager corruption can crash during event polling while (SDL_PollEvent(&event)) { diff --git a/src/app/platform/window.h b/src/app/platform/window.h index 8aafa52c..f2349e60 100644 --- a/src/app/platform/window.h +++ b/src/app/platform/window.h @@ -7,9 +7,9 @@ #include "absl/status/status.h" #include "absl/strings/str_format.h" -#include "util/sdl_deleter.h" -#include "app/gfx/core/bitmap.h" #include "app/gfx/backend/irenderer.h" +#include "app/gfx/core/bitmap.h" +#include "util/sdl_deleter.h" namespace yaze { namespace core { @@ -23,10 +23,10 @@ struct Window { // Legacy CreateWindow (deprecated - use Controller::OnEntry instead) // Kept for backward compatibility with test code -absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer = nullptr, - int flags = SDL_WINDOW_RESIZABLE); -absl::Status HandleEvents(Window &window); -absl::Status ShutdownWindow(Window &window); +absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer = nullptr, + int flags = SDL_WINDOW_RESIZABLE); +absl::Status HandleEvents(Window& window); +absl::Status ShutdownWindow(Window& window); } // namespace core } // namespace yaze diff --git a/src/app/rom.cc b/src/app/rom.cc index 5e1374fd..9619460e 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -19,15 +19,15 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" -#include "core/features.h" -#include "app/gfx/util/compression.h" +#include "app/gfx/core/bitmap.h" #include "app/gfx/types/snes_color.h" #include "app/gfx/types/snes_palette.h" #include "app/gfx/types/snes_tile.h" +#include "app/gfx/util/compression.h" #include "app/snes.h" -#include "app/gfx/core/bitmap.h" -#include "util/log.h" +#include "core/features.h" #include "util/hex.h" +#include "util/log.h" #include "util/macro.h" #include "zelda.h" @@ -38,7 +38,7 @@ namespace { constexpr size_t kBaseRomSize = 1048576; // 1MB constexpr size_t kHeaderSize = 0x200; // 512 bytes -void MaybeStripSmcHeader(std::vector &rom_data, unsigned long &size) { +void MaybeStripSmcHeader(std::vector& rom_data, unsigned long& size) { if (size % kBaseRomSize == kHeaderSize && size >= kHeaderSize) { rom_data.erase(rom_data.begin(), rom_data.begin() + kHeaderSize); size -= kHeaderSize; @@ -47,7 +47,9 @@ void MaybeStripSmcHeader(std::vector &rom_data, unsigned long &size) { } // namespace -RomLoadOptions RomLoadOptions::AppDefaults() { return RomLoadOptions{}; } +RomLoadOptions RomLoadOptions::AppDefaults() { + return RomLoadOptions{}; +} RomLoadOptions RomLoadOptions::CliDefaults() { RomLoadOptions options; @@ -70,16 +72,16 @@ RomLoadOptions RomLoadOptions::RawDataOnly() { return options; } -uint32_t GetGraphicsAddress(const uint8_t *data, uint8_t addr, uint32_t ptr1, +uint32_t GetGraphicsAddress(const uint8_t* data, uint8_t addr, uint32_t ptr1, uint32_t ptr2, uint32_t ptr3) { return SnesToPc(AddressFromBytes(data[ptr1 + addr], data[ptr2 + addr], data[ptr3 + addr])); } -absl::StatusOr> Load2BppGraphics(const Rom &rom) { +absl::StatusOr> Load2BppGraphics(const Rom& rom) { std::vector sheet; const uint8_t sheets[] = {0x71, 0x72, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE}; - for (const auto &sheet_id : sheets) { + for (const auto& sheet_id : sheets) { auto offset = GetGraphicsAddress(rom.data(), sheet_id, rom.version_constants().kOverworldGfxPtr1, rom.version_constants().kOverworldGfxPtr2, @@ -87,7 +89,7 @@ absl::StatusOr> Load2BppGraphics(const Rom &rom) { ASSIGN_OR_RETURN(auto decomp_sheet, gfx::lc_lz2::DecompressV2(rom.data(), offset)); auto converted_sheet = gfx::SnesTo8bppSheet(decomp_sheet, 2); - for (const auto &each_pixel : converted_sheet) { + for (const auto& each_pixel : converted_sheet) { sheet.push_back(each_pixel); } } @@ -95,7 +97,7 @@ absl::StatusOr> Load2BppGraphics(const Rom &rom) { } absl::StatusOr> LoadLinkGraphics( - const Rom &rom) { + const Rom& rom) { const uint32_t kLinkGfxOffset = 0x80000; // $10:8000 const uint16_t kLinkGfxLength = 0x800; // 0x4000 or 0x7000? std::array link_graphics; @@ -114,7 +116,7 @@ absl::StatusOr> LoadLinkGraphics( return link_graphics; } -absl::StatusOr LoadFontGraphics(const Rom &rom) { +absl::StatusOr LoadFontGraphics(const Rom& rom) { std::vector data(0x2000); for (int i = 0; i < 0x2000; i++) { data[i] = rom.data()[0x70000 + i]; @@ -173,14 +175,15 @@ absl::StatusOr LoadFontGraphics(const Rom &rom) { } absl::StatusOr> LoadAllGraphicsData( - Rom &rom, bool defer_render) { + Rom& rom, bool defer_render) { std::array graphics_sheets; std::vector sheet; bool bpp3 = false; // CRITICAL: Clear the graphics buffer before loading to prevent corruption! // Without this, multiple ROM loads would accumulate corrupted data. rom.mutable_graphics_buffer()->clear(); - LOG_DEBUG("Graphics", "Cleared graphics buffer, loading %d sheets", kNumGfxSheets); + LOG_DEBUG("Graphics", "Cleared graphics buffer, loading %d sheets", + kNumGfxSheets); for (uint32_t i = 0; i < kNumGfxSheets; i++) { if (i >= 115 && i <= 126) { // uncompressed sheets @@ -205,15 +208,15 @@ absl::StatusOr> LoadAllGraphicsData( if (bpp3) { auto converted_sheet = gfx::SnesTo8bppSheet(sheet, 3); - + graphics_sheets[i].Create(gfx::kTilesheetWidth, gfx::kTilesheetHeight, gfx::kTilesheetDepth, converted_sheet); - + // Apply default palette based on sheet index to prevent white sheets // This ensures graphics are visible immediately after loading if (!rom.palette_group().empty()) { gfx::SnesPalette default_palette; - + if (i < 113) { // Overworld/Dungeon graphics - use dungeon main palette auto palette_group = rom.palette_group().dungeon_main; @@ -221,7 +224,7 @@ absl::StatusOr> LoadAllGraphicsData( default_palette = palette_group[0]; } } else if (i < 128) { - // Sprite graphics - use sprite palettes + // Sprite graphics - use sprite palettes auto palette_group = rom.palette_group().sprites_aux1; if (palette_group.size() > 0) { default_palette = palette_group[0]; @@ -233,7 +236,7 @@ absl::StatusOr> LoadAllGraphicsData( default_palette = palette_group.palette(0); } } - + // Apply palette if we have one if (!default_palette.empty()) { graphics_sheets[i].SetPalette(default_palette); @@ -254,7 +257,7 @@ absl::StatusOr> LoadAllGraphicsData( } absl::Status SaveAllGraphicsData( - Rom &rom, std::array &gfx_sheets) { + Rom& rom, std::array& gfx_sheets) { for (int i = 0; i < kNumGfxSheets; i++) { if (gfx_sheets[i].is_active()) { int to_bpp = 3; @@ -289,25 +292,24 @@ absl::Status SaveAllGraphicsData( return absl::OkStatus(); } -absl::Status Rom::LoadFromFile(const std::string &filename, bool z3_load) { - return LoadFromFile( - filename, z3_load ? RomLoadOptions::AppDefaults() - : RomLoadOptions::RawDataOnly()); +absl::Status Rom::LoadFromFile(const std::string& filename, bool z3_load) { + return LoadFromFile(filename, z3_load ? RomLoadOptions::AppDefaults() + : RomLoadOptions::RawDataOnly()); } -absl::Status Rom::LoadFromFile(const std::string &filename, - const RomLoadOptions &options) { +absl::Status Rom::LoadFromFile(const std::string& filename, + const RomLoadOptions& options) { if (filename.empty()) { return absl::InvalidArgumentError( "Could not load ROM: parameter `filename` is empty."); } - + // Validate file exists before proceeding if (!std::filesystem::exists(filename)) { return absl::NotFoundError( absl::StrCat("ROM file does not exist: ", filename)); } - + filename_ = std::filesystem::absolute(filename).string(); short_name_ = filename_.substr(filename_.find_last_of("/\\") + 1); @@ -320,17 +322,17 @@ absl::Status Rom::LoadFromFile(const std::string &filename, // Get file size and validate try { size_ = std::filesystem::file_size(filename_); - + // Validate ROM size (minimum 32KB, maximum 8MB for expanded ROMs) if (size_ < 32768) { - return absl::InvalidArgumentError( - absl::StrFormat("ROM file too small (%zu bytes), minimum is 32KB", size_)); + return absl::InvalidArgumentError(absl::StrFormat( + "ROM file too small (%zu bytes), minimum is 32KB", size_)); } if (size_ > 8 * 1024 * 1024) { - return absl::InvalidArgumentError( - absl::StrFormat("ROM file too large (%zu bytes), maximum is 8MB", size_)); + return absl::InvalidArgumentError(absl::StrFormat( + "ROM file too large (%zu bytes), maximum is 8MB", size_)); } - } catch (const std::filesystem::filesystem_error &e) { + } catch (const std::filesystem::filesystem_error& e) { // Try to get the file size from the open file stream file.seekg(0, std::ios::end); if (!file) { @@ -338,30 +340,30 @@ absl::Status Rom::LoadFromFile(const std::string &filename, "Could not get file size: ", filename_, " - ", e.what())); } size_ = file.tellg(); - + // Validate size from stream if (size_ < 32768 || size_ > 8 * 1024 * 1024) { return absl::InvalidArgumentError( absl::StrFormat("Invalid ROM size: %zu bytes", size_)); } } - + // Allocate and read ROM data try { rom_data_.resize(size_); file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(rom_data_.data()), size_); - + file.read(reinterpret_cast(rom_data_.data()), size_); + if (!file) { return absl::InternalError( absl::StrFormat("Failed to read ROM data, read %zu of %zu bytes", - file.gcount(), size_)); + file.gcount(), size_)); } } catch (const std::bad_alloc& e) { - return absl::ResourceExhaustedError( - absl::StrFormat("Failed to allocate memory for ROM (%zu bytes)", size_)); + return absl::ResourceExhaustedError(absl::StrFormat( + "Failed to allocate memory for ROM (%zu bytes)", size_)); } - + file.close(); if (!options.load_zelda3_content) { @@ -374,21 +376,19 @@ absl::Status Rom::LoadFromFile(const std::string &filename, } if (options.load_resource_labels) { - resource_label_manager_.LoadLabels( - absl::StrFormat("%s.labels", filename)); + resource_label_manager_.LoadLabels(absl::StrFormat("%s.labels", filename)); } return absl::OkStatus(); } -absl::Status Rom::LoadFromData(const std::vector &data, bool z3_load) { - return LoadFromData( - data, z3_load ? RomLoadOptions::AppDefaults() - : RomLoadOptions::RawDataOnly()); +absl::Status Rom::LoadFromData(const std::vector& data, bool z3_load) { + return LoadFromData(data, z3_load ? RomLoadOptions::AppDefaults() + : RomLoadOptions::RawDataOnly()); } -absl::Status Rom::LoadFromData(const std::vector &data, - const RomLoadOptions &options) { +absl::Status Rom::LoadFromData(const std::vector& data, + const RomLoadOptions& options) { if (data.empty()) { return absl::InvalidArgumentError( "Could not load ROM: parameter `data` is empty."); @@ -412,7 +412,7 @@ absl::Status Rom::LoadZelda3() { return LoadZelda3(RomLoadOptions::AppDefaults()); } -absl::Status Rom::LoadZelda3(const RomLoadOptions &options) { +absl::Status Rom::LoadZelda3(const RomLoadOptions& options) { if (rom_data_.empty()) { return absl::FailedPreconditionError("ROM data is empty"); } @@ -536,7 +536,7 @@ absl::Status Rom::SaveGfxGroups() { return absl::OkStatus(); } -absl::Status Rom::SaveToFile(const SaveSettings &settings) { +absl::Status Rom::SaveToFile(const SaveSettings& settings) { absl::Status non_firing_status; if (rom_data_.empty()) { return absl::InternalError("ROM data is empty."); @@ -571,7 +571,7 @@ absl::Status Rom::SaveToFile(const SaveSettings &settings) { try { std::filesystem::copy(filename_, backup_filename, std::filesystem::copy_options::overwrite_existing); - } catch (const std::filesystem::filesystem_error &e) { + } catch (const std::filesystem::filesystem_error& e) { non_firing_status = absl::InternalError(absl::StrCat( "Could not create backup file: ", backup_filename, " - ", e.what())); } @@ -614,9 +614,9 @@ absl::Status Rom::SaveToFile(const SaveSettings &settings) { // Save the data to the file try { file.write( - static_cast(static_cast(rom_data_.data())), + static_cast(static_cast(rom_data_.data())), rom_data_.size()); - } catch (const std::ofstream::failure &e) { + } catch (const std::ofstream::failure& e) { return absl::InternalError(absl::StrCat( "Error while writing to ROM file: ", filename, " - ", e.what())); } @@ -627,12 +627,13 @@ absl::Status Rom::SaveToFile(const SaveSettings &settings) { absl::StrCat("Error while writing to ROM file: ", filename)); } - if (non_firing_status.ok()) dirty_ = false; + if (non_firing_status.ok()) + dirty_ = false; return non_firing_status.ok() ? absl::OkStatus() : non_firing_status; } -absl::Status Rom::SavePalette(int index, const std::string &group_name, - gfx::SnesPalette &palette) { +absl::Status Rom::SavePalette(int index, const std::string& group_name, + gfx::SnesPalette& palette) { for (size_t j = 0; j < palette.size(); ++j) { gfx::SnesColor color = palette[j]; // If the color is modified, save the color to the ROM @@ -647,7 +648,7 @@ absl::Status Rom::SavePalette(int index, const std::string &group_name, absl::Status Rom::SaveAllPalettes() { RETURN_IF_ERROR( - palette_groups_.for_each([&](gfx::PaletteGroup &group) -> absl::Status { + palette_groups_.for_each([&](gfx::PaletteGroup& group) -> absl::Status { for (size_t i = 0; i < group.size(); ++i) { RETURN_IF_ERROR( SavePalette(i, group.name(), *group.mutable_palette(i))); @@ -712,7 +713,7 @@ absl::StatusOr Rom::ReadTile16(uint32_t tile16_id) { return tile16; } -absl::Status Rom::WriteTile16(int tile16_id, const gfx::Tile16 &tile) { +absl::Status Rom::WriteTile16(int tile16_id, const gfx::Tile16& tile) { // Skip 8 bytes per tile. auto tpos = kTile16Ptr + (tile16_id * 0x08); RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile0_))); @@ -792,7 +793,7 @@ absl::Status Rom::WriteVector(int addr, std::vector data) { return absl::OkStatus(); } -absl::Status Rom::WriteColor(uint32_t address, const gfx::SnesColor &color) { +absl::Status Rom::WriteColor(uint32_t address, const gfx::SnesColor& color) { uint16_t bgr = ((color.snes() >> 10) & 0x1F) | ((color.snes() & 0x1F) << 10) | (color.snes() & 0x7C00); diff --git a/src/app/rom.h b/src/app/rom.h index 03b1971b..11d0beee 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -19,11 +19,11 @@ #include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" -#include "core/project.h" #include "app/gfx/core/bitmap.h" #include "app/gfx/types/snes_color.h" #include "app/gfx/types/snes_palette.h" #include "app/gfx/types/snes_tile.h" +#include "core/project.h" #include "util/macro.h" namespace yaze { @@ -82,7 +82,7 @@ class Rom { absl::Status LoadFromFile(const std::string& filename, bool z3_load = true); absl::Status LoadFromFile(const std::string& filename, - const RomLoadOptions& options); + const RomLoadOptions& options); absl::Status LoadFromData(const std::vector& data, bool z3_load = true); absl::Status LoadFromData(const std::vector& data, @@ -193,7 +193,8 @@ class Rom { } uint8_t& operator[](unsigned long i) { - if (i >= size_) throw std::out_of_range("Rom index out of range"); + if (i >= size_) + throw std::out_of_range("Rom index out of range"); return rom_data_[i]; } @@ -220,7 +221,9 @@ class Rom { return palette_groups_.dungeon_main.mutable_palette(i); } - project::ResourceLabelManager* resource_label() { return &resource_label_manager_; } + project::ResourceLabelManager* resource_label() { + return &resource_label_manager_; + } zelda3_version_pointers version_constants() const { return kVersionConstantsMap.at(version_); } diff --git a/src/app/service/canvas_automation_service.cc b/src/app/service/canvas_automation_service.cc index 4f8f26e7..2ce1fda3 100644 --- a/src/app/service/canvas_automation_service.cc +++ b/src/app/service/canvas_automation_service.cc @@ -3,10 +3,10 @@ #ifdef YAZE_WITH_GRPC #include -#include "protos/canvas_automation.pb.h" -#include "protos/canvas_automation.grpc.pb.h" #include "app/editor/overworld/overworld_editor.h" #include "app/gui/canvas/canvas_automation_api.h" +#include "protos/canvas_automation.grpc.pb.h" +#include "protos/canvas_automation.pb.h" namespace yaze { @@ -17,7 +17,7 @@ grpc::Status ConvertStatus(const absl::Status& status) { if (status.ok()) { return grpc::Status::OK; } - + grpc::StatusCode code; switch (status.code()) { case absl::StatusCode::kNotFound: @@ -45,7 +45,7 @@ grpc::Status ConvertStatus(const absl::Status& status) { code = grpc::StatusCode::UNKNOWN; break; } - + return grpc::Status(code, std::string(status.message())); } @@ -61,7 +61,8 @@ void CanvasAutomationServiceImpl::RegisterOverworldEditor( overworld_editors_[canvas_id] = editor; } -gui::Canvas* CanvasAutomationServiceImpl::GetCanvas(const std::string& canvas_id) { +gui::Canvas* CanvasAutomationServiceImpl::GetCanvas( + const std::string& canvas_id) { auto it = canvases_.find(canvas_id); if (it != canvases_.end()) { return it->second; @@ -84,7 +85,7 @@ editor::OverworldEditor* CanvasAutomationServiceImpl::GetOverworldEditor( absl::Status CanvasAutomationServiceImpl::SetTile( const proto::SetTileRequest* request, proto::SetTileResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -94,18 +95,19 @@ absl::Status CanvasAutomationServiceImpl::SetTile( auto* api = canvas->GetAutomationAPI(); bool success = api->SetTileAt(request->x(), request->y(), request->tile_id()); - + response->set_success(success); if (!success) { - response->set_error("Failed to set tile - out of bounds or callback failed"); + response->set_error( + "Failed to set tile - out of bounds or callback failed"); } - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::GetTile( const proto::GetTileRequest* request, proto::GetTileResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -115,7 +117,7 @@ absl::Status CanvasAutomationServiceImpl::GetTile( auto* api = canvas->GetAutomationAPI(); int tile_id = api->GetTileAt(request->x(), request->y()); - + if (tile_id >= 0) { response->set_tile_id(tile_id); response->set_success(true); @@ -123,13 +125,13 @@ absl::Status CanvasAutomationServiceImpl::GetTile( response->set_success(false); response->set_error("Tile not found - out of bounds or no callback set"); } - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::SetTiles( const proto::SetTilesRequest* request, proto::SetTilesResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -138,16 +140,16 @@ absl::Status CanvasAutomationServiceImpl::SetTiles( } auto* api = canvas->GetAutomationAPI(); - + std::vector> tiles; for (const auto& tile : request->tiles()) { tiles.push_back({tile.x(), tile.y(), tile.tile_id()}); } - + bool success = api->SetTiles(tiles); response->set_success(success); response->set_tiles_painted(tiles.size()); - + return absl::OkStatus(); } @@ -156,8 +158,9 @@ absl::Status CanvasAutomationServiceImpl::SetTiles( // ============================================================================ absl::Status CanvasAutomationServiceImpl::SelectTile( - const proto::SelectTileRequest* request, proto::SelectTileResponse* response) { - + const proto::SelectTileRequest* request, + proto::SelectTileResponse* response) { + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -168,14 +171,14 @@ absl::Status CanvasAutomationServiceImpl::SelectTile( auto* api = canvas->GetAutomationAPI(); api->SelectTile(request->x(), request->y()); response->set_success(true); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::SelectTileRect( const proto::SelectTileRectRequest* request, proto::SelectTileRectResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -186,18 +189,18 @@ absl::Status CanvasAutomationServiceImpl::SelectTileRect( auto* api = canvas->GetAutomationAPI(); const auto& rect = request->rect(); api->SelectTileRect(rect.x1(), rect.y1(), rect.x2(), rect.y2()); - + auto selection = api->GetSelection(); response->set_success(true); response->set_tiles_selected(selection.selected_tiles.size()); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::GetSelection( const proto::GetSelectionRequest* request, proto::GetSelectionResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { return absl::NotFoundError("Canvas not found: " + request->canvas_id()); @@ -205,30 +208,30 @@ absl::Status CanvasAutomationServiceImpl::GetSelection( auto* api = canvas->GetAutomationAPI(); auto selection = api->GetSelection(); - + response->set_has_selection(selection.has_selection); - + for (const auto& tile : selection.selected_tiles) { auto* coord = response->add_selected_tiles(); coord->set_x(static_cast(tile.x)); coord->set_y(static_cast(tile.y)); } - + auto* start = response->mutable_selection_start(); start->set_x(static_cast(selection.selection_start.x)); start->set_y(static_cast(selection.selection_start.y)); - + auto* end = response->mutable_selection_end(); end->set_x(static_cast(selection.selection_end.x)); end->set_y(static_cast(selection.selection_end.y)); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::ClearSelection( const proto::ClearSelectionRequest* request, proto::ClearSelectionResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -238,7 +241,7 @@ absl::Status CanvasAutomationServiceImpl::ClearSelection( auto* api = canvas->GetAutomationAPI(); api->ClearSelection(); response->set_success(true); - + return absl::OkStatus(); } @@ -249,7 +252,7 @@ absl::Status CanvasAutomationServiceImpl::ClearSelection( absl::Status CanvasAutomationServiceImpl::ScrollToTile( const proto::ScrollToTileRequest* request, proto::ScrollToTileResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -260,13 +263,13 @@ absl::Status CanvasAutomationServiceImpl::ScrollToTile( auto* api = canvas->GetAutomationAPI(); api->ScrollToTile(request->x(), request->y(), request->center()); response->set_success(true); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::CenterOn( const proto::CenterOnRequest* request, proto::CenterOnResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -277,13 +280,13 @@ absl::Status CanvasAutomationServiceImpl::CenterOn( auto* api = canvas->GetAutomationAPI(); api->CenterOn(request->x(), request->y()); response->set_success(true); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::SetZoom( const proto::SetZoomRequest* request, proto::SetZoomResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { response->set_success(false); @@ -293,17 +296,17 @@ absl::Status CanvasAutomationServiceImpl::SetZoom( auto* api = canvas->GetAutomationAPI(); api->SetZoom(request->zoom()); - + float actual_zoom = api->GetZoom(); response->set_success(true); response->set_actual_zoom(actual_zoom); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::GetZoom( const proto::GetZoomRequest* request, proto::GetZoomResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { return absl::NotFoundError("Canvas not found: " + request->canvas_id()); @@ -311,7 +314,7 @@ absl::Status CanvasAutomationServiceImpl::GetZoom( auto* api = canvas->GetAutomationAPI(); response->set_zoom(api->GetZoom()); - + return absl::OkStatus(); } @@ -322,7 +325,7 @@ absl::Status CanvasAutomationServiceImpl::GetZoom( absl::Status CanvasAutomationServiceImpl::GetDimensions( const proto::GetDimensionsRequest* request, proto::GetDimensionsResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { return absl::NotFoundError("Canvas not found: " + request->canvas_id()); @@ -330,19 +333,19 @@ absl::Status CanvasAutomationServiceImpl::GetDimensions( auto* api = canvas->GetAutomationAPI(); auto dims = api->GetDimensions(); - + auto* proto_dims = response->mutable_dimensions(); proto_dims->set_width_tiles(dims.width_tiles); proto_dims->set_height_tiles(dims.height_tiles); proto_dims->set_tile_size(dims.tile_size); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::GetVisibleRegion( const proto::GetVisibleRegionRequest* request, proto::GetVisibleRegionResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { return absl::NotFoundError("Canvas not found: " + request->canvas_id()); @@ -350,20 +353,20 @@ absl::Status CanvasAutomationServiceImpl::GetVisibleRegion( auto* api = canvas->GetAutomationAPI(); auto region = api->GetVisibleRegion(); - + auto* proto_region = response->mutable_region(); proto_region->set_min_x(region.min_x); proto_region->set_min_y(region.min_y); proto_region->set_max_x(region.max_x); proto_region->set_max_y(region.max_y); - + return absl::OkStatus(); } absl::Status CanvasAutomationServiceImpl::IsTileVisible( const proto::IsTileVisibleRequest* request, proto::IsTileVisibleResponse* response) { - + auto* canvas = GetCanvas(request->canvas_id()); if (!canvas) { return absl::NotFoundError("Canvas not found: " + request->canvas_id()); @@ -371,7 +374,7 @@ absl::Status CanvasAutomationServiceImpl::IsTileVisible( auto* api = canvas->GetAutomationAPI(); response->set_is_visible(api->IsTileVisible(request->x(), request->y())); - + return absl::OkStatus(); } @@ -386,96 +389,98 @@ absl::Status CanvasAutomationServiceImpl::IsTileVisible( * forwards all calls to our implementation, converting between gRPC * and absl::Status types. */ -class CanvasAutomationServiceGrpc final : public proto::CanvasAutomation::Service { +class CanvasAutomationServiceGrpc final + : public proto::CanvasAutomation::Service { public: explicit CanvasAutomationServiceGrpc(CanvasAutomationServiceImpl* impl) : impl_(impl) {} // Tile Operations grpc::Status SetTile(grpc::ServerContext* context, - const proto::SetTileRequest* request, - proto::SetTileResponse* response) override { + const proto::SetTileRequest* request, + proto::SetTileResponse* response) override { return ConvertStatus(impl_->SetTile(request, response)); } grpc::Status GetTile(grpc::ServerContext* context, - const proto::GetTileRequest* request, - proto::GetTileResponse* response) override { + const proto::GetTileRequest* request, + proto::GetTileResponse* response) override { return ConvertStatus(impl_->GetTile(request, response)); } grpc::Status SetTiles(grpc::ServerContext* context, - const proto::SetTilesRequest* request, - proto::SetTilesResponse* response) override { + const proto::SetTilesRequest* request, + proto::SetTilesResponse* response) override { return ConvertStatus(impl_->SetTiles(request, response)); } // Selection Operations grpc::Status SelectTile(grpc::ServerContext* context, - const proto::SelectTileRequest* request, - proto::SelectTileResponse* response) override { + const proto::SelectTileRequest* request, + proto::SelectTileResponse* response) override { return ConvertStatus(impl_->SelectTile(request, response)); } - grpc::Status SelectTileRect(grpc::ServerContext* context, - const proto::SelectTileRectRequest* request, - proto::SelectTileRectResponse* response) override { + grpc::Status SelectTileRect( + grpc::ServerContext* context, const proto::SelectTileRectRequest* request, + proto::SelectTileRectResponse* response) override { return ConvertStatus(impl_->SelectTileRect(request, response)); } grpc::Status GetSelection(grpc::ServerContext* context, - const proto::GetSelectionRequest* request, - proto::GetSelectionResponse* response) override { + const proto::GetSelectionRequest* request, + proto::GetSelectionResponse* response) override { return ConvertStatus(impl_->GetSelection(request, response)); } - grpc::Status ClearSelection(grpc::ServerContext* context, - const proto::ClearSelectionRequest* request, - proto::ClearSelectionResponse* response) override { + grpc::Status ClearSelection( + grpc::ServerContext* context, const proto::ClearSelectionRequest* request, + proto::ClearSelectionResponse* response) override { return ConvertStatus(impl_->ClearSelection(request, response)); } // View Operations grpc::Status ScrollToTile(grpc::ServerContext* context, - const proto::ScrollToTileRequest* request, - proto::ScrollToTileResponse* response) override { + const proto::ScrollToTileRequest* request, + proto::ScrollToTileResponse* response) override { return ConvertStatus(impl_->ScrollToTile(request, response)); } grpc::Status CenterOn(grpc::ServerContext* context, - const proto::CenterOnRequest* request, - proto::CenterOnResponse* response) override { + const proto::CenterOnRequest* request, + proto::CenterOnResponse* response) override { return ConvertStatus(impl_->CenterOn(request, response)); } grpc::Status SetZoom(grpc::ServerContext* context, - const proto::SetZoomRequest* request, - proto::SetZoomResponse* response) override { + const proto::SetZoomRequest* request, + proto::SetZoomResponse* response) override { return ConvertStatus(impl_->SetZoom(request, response)); } grpc::Status GetZoom(grpc::ServerContext* context, - const proto::GetZoomRequest* request, - proto::GetZoomResponse* response) override { + const proto::GetZoomRequest* request, + proto::GetZoomResponse* response) override { return ConvertStatus(impl_->GetZoom(request, response)); } // Query Operations grpc::Status GetDimensions(grpc::ServerContext* context, - const proto::GetDimensionsRequest* request, - proto::GetDimensionsResponse* response) override { + const proto::GetDimensionsRequest* request, + proto::GetDimensionsResponse* response) override { return ConvertStatus(impl_->GetDimensions(request, response)); } - grpc::Status GetVisibleRegion(grpc::ServerContext* context, - const proto::GetVisibleRegionRequest* request, - proto::GetVisibleRegionResponse* response) override { + grpc::Status GetVisibleRegion( + grpc::ServerContext* context, + const proto::GetVisibleRegionRequest* request, + proto::GetVisibleRegionResponse* response) override { return ConvertStatus(impl_->GetVisibleRegion(request, response)); } grpc::Status IsTileVisible(grpc::ServerContext* context, - const proto::IsTileVisibleRequest* request, - proto::IsTileVisibleResponse* response) override { + const proto::IsTileVisibleRequest* request, + proto::IsTileVisibleResponse* response) override { return ConvertStatus(impl_->IsTileVisible(request, response)); } @@ -493,4 +498,3 @@ std::unique_ptr CreateCanvasAutomationServiceGrpc( } // namespace yaze #endif // YAZE_WITH_GRPC - diff --git a/src/app/service/canvas_automation_service.h b/src/app/service/canvas_automation_service.h index de8ad9fd..8d63d5ed 100644 --- a/src/app/service/canvas_automation_service.h +++ b/src/app/service/canvas_automation_service.h @@ -68,53 +68,53 @@ class CanvasAutomationServiceImpl { // Register a canvas for automation void RegisterCanvas(const std::string& canvas_id, gui::Canvas* canvas); - + // Register an overworld editor (for tile get/set callbacks) void RegisterOverworldEditor(const std::string& canvas_id, editor::OverworldEditor* editor); // RPC method implementations absl::Status SetTile(const proto::SetTileRequest* request, - proto::SetTileResponse* response); - + proto::SetTileResponse* response); + absl::Status GetTile(const proto::GetTileRequest* request, - proto::GetTileResponse* response); - + proto::GetTileResponse* response); + absl::Status SetTiles(const proto::SetTilesRequest* request, - proto::SetTilesResponse* response); - + proto::SetTilesResponse* response); + absl::Status SelectTile(const proto::SelectTileRequest* request, - proto::SelectTileResponse* response); - + proto::SelectTileResponse* response); + absl::Status SelectTileRect(const proto::SelectTileRectRequest* request, - proto::SelectTileRectResponse* response); - + proto::SelectTileRectResponse* response); + absl::Status GetSelection(const proto::GetSelectionRequest* request, - proto::GetSelectionResponse* response); - + proto::GetSelectionResponse* response); + absl::Status ClearSelection(const proto::ClearSelectionRequest* request, - proto::ClearSelectionResponse* response); - + proto::ClearSelectionResponse* response); + absl::Status ScrollToTile(const proto::ScrollToTileRequest* request, - proto::ScrollToTileResponse* response); - + proto::ScrollToTileResponse* response); + absl::Status CenterOn(const proto::CenterOnRequest* request, - proto::CenterOnResponse* response); - + proto::CenterOnResponse* response); + absl::Status SetZoom(const proto::SetZoomRequest* request, - proto::SetZoomResponse* response); - + proto::SetZoomResponse* response); + absl::Status GetZoom(const proto::GetZoomRequest* request, - proto::GetZoomResponse* response); - + proto::GetZoomResponse* response); + absl::Status GetDimensions(const proto::GetDimensionsRequest* request, - proto::GetDimensionsResponse* response); - + proto::GetDimensionsResponse* response); + absl::Status GetVisibleRegion(const proto::GetVisibleRegionRequest* request, - proto::GetVisibleRegionResponse* response); - + proto::GetVisibleRegionResponse* response); + absl::Status IsTileVisible(const proto::IsTileVisibleRequest* request, - proto::IsTileVisibleResponse* response); + proto::IsTileVisibleResponse* response); private: gui::Canvas* GetCanvas(const std::string& canvas_id); @@ -122,7 +122,7 @@ class CanvasAutomationServiceImpl { // Canvas registry std::unordered_map canvases_; - + // Editor registry (for tile callbacks) std::unordered_map overworld_editors_; }; @@ -144,4 +144,3 @@ std::unique_ptr CreateCanvasAutomationServiceGrpc( #endif // YAZE_WITH_GRPC #endif // YAZE_APP_CORE_SERVICE_CANVAS_AUTOMATION_SERVICE_H_ - diff --git a/src/app/service/imgui_test_harness_service.cc b/src/app/service/imgui_test_harness_service.cc index d1ad3fca..c1ca739f 100644 --- a/src/app/service/imgui_test_harness_service.cc +++ b/src/app/service/imgui_test_harness_service.cc @@ -29,8 +29,8 @@ #include #include -#include "absl/container/flat_hash_map.h" #include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" #include "absl/strings/ascii.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" @@ -39,16 +39,16 @@ #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#include "app/service/screenshot_utils.h" +#include "app/test/test_manager.h" +#include "app/test/test_script_parser.h" #include "protos/imgui_test_harness.grpc.pb.h" #include "protos/imgui_test_harness.pb.h" -#include "app/service/screenshot_utils.h" -#include "app/test/test_script_parser.h" -#include "app/test/test_manager.h" #include "yaze.h" // For YAZE_VERSION_STRING #if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE -#include "imgui_test_engine/imgui_te_engine.h" #include "imgui_test_engine/imgui_te_context.h" +#include "imgui_test_engine/imgui_te_engine.h" // Helper to register and run a test dynamically namespace { @@ -436,7 +436,7 @@ class ImGuiTestHarnessServiceGrpc final : public ImGuiTestHarness::Service { // ============================================================================ absl::Status ImGuiTestHarnessServiceImpl::Ping(const PingRequest* request, - PingResponse* response) { + PingResponse* response) { // Echo back the message with "Pong: " prefix response->set_message(absl::StrFormat("Pong: %s", request->message())); @@ -476,8 +476,7 @@ absl::Status ImGuiTestHarnessServiceImpl::Click(const ClickRequest* request, response->set_success(false); response->set_message("TestManager not available"); response->set_execution_time_ms(0); - return finalize( - absl::FailedPreconditionError("TestManager not available")); + return finalize(absl::FailedPreconditionError("TestManager not available")); } const std::string test_id = test_manager_->RegisterHarnessTest( @@ -496,8 +495,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Click(const ClickRequest* request, response->set_success(false); response->set_message(message); response->set_execution_time_ms(elapsed.count()); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); return finalize(absl::OkStatus()); } @@ -511,8 +510,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Click(const ClickRequest* request, response->set_success(false); response->set_message(message); response->set_execution_time_ms(elapsed.count()); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); test_manager_->AppendHarnessTestLog(test_id, message); return finalize(absl::OkStatus()); } @@ -553,16 +552,14 @@ absl::Status ImGuiTestHarnessServiceImpl::Click(const ClickRequest* request, const std::string success_message = absl::StrFormat("Clicked %s '%s'", widget_type, widget_label); manager->AppendHarnessTestLog(captured_id, success_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kPassed, - success_message); + manager->MarkHarnessTestCompleted(captured_id, HarnessTestStatus::kPassed, + success_message); } catch (const std::exception& e) { const std::string error_message = absl::StrFormat("Click failed: %s", e.what()); manager->AppendHarnessTestLog(captured_id, error_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kFailed, - error_message); + manager->MarkHarnessTestCompleted(captured_id, HarnessTestStatus::kFailed, + error_message); } }; @@ -597,22 +594,20 @@ absl::Status ImGuiTestHarnessServiceImpl::Click(const ClickRequest* request, response->set_success(false); response->set_message(message); response->set_execution_time_ms(elapsed.count()); - test_manager_->MarkHarnessTestCompleted(test_id, - HarnessTestStatus::kFailed, - message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); test_manager_->AppendHarnessTestLog(test_id, message); return finalize(absl::OkStatus()); } std::string widget_type = target.substr(0, colon_pos); std::string widget_label = target.substr(colon_pos + 1); - std::string message = absl::StrFormat( - "[STUB] Clicked %s '%s' (ImGuiTestEngine not available)", - widget_type, widget_label); + std::string message = + absl::StrFormat("[STUB] Clicked %s '%s' (ImGuiTestEngine not available)", + widget_type, widget_label); test_manager_->MarkHarnessTestRunning(test_id); - test_manager_->MarkHarnessTestCompleted(test_id, - HarnessTestStatus::kPassed, + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kPassed, message); test_manager_->AppendHarnessTestLog(test_id, message); @@ -651,8 +646,7 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, response->set_success(false); response->set_message("TestManager not available"); response->set_execution_time_ms(0); - return finalize( - absl::FailedPreconditionError("TestManager not available")); + return finalize(absl::FailedPreconditionError("TestManager not available")); } const std::string test_id = test_manager_->RegisterHarnessTest( @@ -671,8 +665,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, response->set_success(false); response->set_message(message); response->set_execution_time_ms(elapsed.count()); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); return finalize(absl::OkStatus()); } @@ -686,8 +680,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, response->set_success(false); response->set_message(message); response->set_execution_time_ms(elapsed.count()); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); test_manager_->AppendHarnessTestLog(test_id, message); return finalize(absl::OkStatus()); } @@ -701,8 +695,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, auto test_data = std::make_shared(); TestManager* manager = test_manager_; test_data->test_func = [manager, captured_id = test_id, widget_type, - widget_label, clear_first, text, rpc_state]( - ImGuiTestContext* ctx) { + widget_label, clear_first, text, + rpc_state](ImGuiTestContext* ctx) { manager->MarkHarnessTestRunning(captured_id); try { ImGuiTestItemInfo item = ctx->ItemInfo(widget_label.c_str()); @@ -710,9 +704,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, std::string error_message = absl::StrFormat("Input field '%s' not found", widget_label); manager->AppendHarnessTestLog(captured_id, error_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kFailed, - error_message); + manager->MarkHarnessTestCompleted( + captured_id, HarnessTestStatus::kFailed, error_message); rpc_state->SetResult(false, error_message); return; } @@ -725,21 +718,18 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, ctx->ItemInputValue(widget_label.c_str(), text.c_str()); - std::string success_message = absl::StrFormat( - "Typed '%s' into %s '%s'%s", text, widget_type, widget_label, - clear_first ? " (cleared first)" : ""); + std::string success_message = + absl::StrFormat("Typed '%s' into %s '%s'%s", text, widget_type, + widget_label, clear_first ? " (cleared first)" : ""); manager->AppendHarnessTestLog(captured_id, success_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kPassed, - success_message); + manager->MarkHarnessTestCompleted(captured_id, HarnessTestStatus::kPassed, + success_message); rpc_state->SetResult(true, success_message); } catch (const std::exception& e) { - std::string error_message = - absl::StrFormat("Type failed: %s", e.what()); + std::string error_message = absl::StrFormat("Type failed: %s", e.what()); manager->AppendHarnessTestLog(captured_id, error_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kFailed, - error_message); + manager->MarkHarnessTestCompleted(captured_id, HarnessTestStatus::kFailed, + error_message); rpc_state->SetResult(false, error_message); } }; @@ -763,8 +753,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, std::string error_message = "Test timeout - input field not found or unresponsive"; manager->AppendHarnessTestLog(test_id, error_message); - manager->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kTimeout, error_message); + manager->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kTimeout, + error_message); rpc_state->SetResult(false, error_message); break; } @@ -789,8 +779,7 @@ absl::Status ImGuiTestHarnessServiceImpl::Type(const TypeRequest* request, std::string message = absl::StrFormat( "[STUB] Typed '%s' into %s (ImGuiTestEngine not available)", request->text(), request->target()); - test_manager_->MarkHarnessTestCompleted(test_id, - HarnessTestStatus::kPassed, + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kPassed, message); test_manager_->AppendHarnessTestLog(test_id, message); @@ -828,8 +817,7 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, response->set_success(false); response->set_message("TestManager not available"); response->set_elapsed_ms(0); - return finalize( - absl::FailedPreconditionError("TestManager not available")); + return finalize(absl::FailedPreconditionError("TestManager not available")); } const std::string test_id = test_manager_->RegisterHarnessTest( @@ -837,8 +825,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, response->set_test_id(test_id); recorded_step.test_id = test_id; test_manager_->AppendHarnessTestLog( - test_id, absl::StrFormat("Queued wait condition: %s", - request->condition())); + test_id, + absl::StrFormat("Queued wait condition: %s", request->condition())); #if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE ImGuiTestEngine* engine = test_manager_->GetUITestEngine(); @@ -849,8 +837,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, response->set_success(false); response->set_message(message); response->set_elapsed_ms(elapsed.count()); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); return finalize(absl::OkStatus()); } @@ -860,12 +848,13 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, auto elapsed = std::chrono::duration_cast( std::chrono::steady_clock::now() - start); std::string message = - "Invalid condition format. Use 'type:target' (e.g. 'window_visible:Overworld Editor')"; + "Invalid condition format. Use 'type:target' (e.g. " + "'window_visible:Overworld Editor')"; response->set_success(false); response->set_message(message); response->set_elapsed_ms(elapsed.count()); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); test_manager_->AppendHarnessTestLog(test_id, message); return finalize(absl::OkStatus()); } @@ -873,15 +862,14 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, std::string condition_type = condition.substr(0, colon_pos); std::string condition_target = condition.substr(colon_pos + 1); int timeout_ms = request->timeout_ms() > 0 ? request->timeout_ms() : 5000; - int poll_interval_ms = request->poll_interval_ms() > 0 - ? request->poll_interval_ms() - : 100; + int poll_interval_ms = + request->poll_interval_ms() > 0 ? request->poll_interval_ms() : 100; auto test_data = std::make_shared(); TestManager* manager = test_manager_; test_data->test_func = [manager, captured_id = test_id, condition_type, - condition_target, timeout_ms, poll_interval_ms]( - ImGuiTestContext* ctx) { + condition_target, timeout_ms, + poll_interval_ms](ImGuiTestContext* ctx) { manager->MarkHarnessTestRunning(captured_id); auto poll_start = std::chrono::steady_clock::now(); auto timeout = std::chrono::milliseconds(timeout_ms); @@ -899,35 +887,34 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, condition_target.c_str(), ImGuiTestOpFlags_NoError); current_state = (window_info.ID != 0); } else if (condition_type == "element_visible") { - ImGuiTestItemInfo item = ctx->ItemInfo( - condition_target.c_str(), ImGuiTestOpFlags_NoError); + ImGuiTestItemInfo item = + ctx->ItemInfo(condition_target.c_str(), ImGuiTestOpFlags_NoError); current_state = (item.ID != 0 && item.RectClipped.GetWidth() > 0 && item.RectClipped.GetHeight() > 0); } else if (condition_type == "element_enabled") { - ImGuiTestItemInfo item = ctx->ItemInfo( - condition_target.c_str(), ImGuiTestOpFlags_NoError); + ImGuiTestItemInfo item = + ctx->ItemInfo(condition_target.c_str(), ImGuiTestOpFlags_NoError); current_state = (item.ID != 0 && !(item.ItemFlags & ImGuiItemFlags_Disabled)); } else { std::string error_message = absl::StrFormat("Unknown condition type: %s", condition_type); manager->AppendHarnessTestLog(captured_id, error_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kFailed, - error_message); + manager->MarkHarnessTestCompleted( + captured_id, HarnessTestStatus::kFailed, error_message); return; } if (current_state) { - auto elapsed_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now() - poll_start); + auto elapsed_ms = + std::chrono::duration_cast( + std::chrono::steady_clock::now() - poll_start); std::string success_message = absl::StrFormat( "Condition '%s:%s' met after %lld ms", condition_type, condition_target, static_cast(elapsed_ms.count())); manager->AppendHarnessTestLog(captured_id, success_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kPassed, - success_message); + manager->MarkHarnessTestCompleted( + captured_id, HarnessTestStatus::kPassed, success_message); return; } @@ -936,20 +923,17 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, ctx->Yield(); } - std::string timeout_message = absl::StrFormat( - "Condition '%s:%s' not met after %d ms timeout", condition_type, - condition_target, timeout_ms); + std::string timeout_message = + absl::StrFormat("Condition '%s:%s' not met after %d ms timeout", + condition_type, condition_target, timeout_ms); manager->AppendHarnessTestLog(captured_id, timeout_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kTimeout, - timeout_message); + manager->MarkHarnessTestCompleted( + captured_id, HarnessTestStatus::kTimeout, timeout_message); } catch (const std::exception& e) { - std::string error_message = - absl::StrFormat("Wait failed: %s", e.what()); + std::string error_message = absl::StrFormat("Wait failed: %s", e.what()); manager->AppendHarnessTestLog(captured_id, error_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kFailed, - error_message); + manager->MarkHarnessTestCompleted(captured_id, HarnessTestStatus::kFailed, + error_message); } }; @@ -966,9 +950,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, auto elapsed = std::chrono::duration_cast( std::chrono::steady_clock::now() - start); - std::string message = - absl::StrFormat("Queued wait for '%s:%s'", condition_type, - condition_target); + std::string message = absl::StrFormat("Queued wait for '%s:%s'", + condition_type, condition_target); response->set_success(true); response->set_message(message); response->set_elapsed_ms(elapsed.count()); @@ -979,8 +962,7 @@ absl::Status ImGuiTestHarnessServiceImpl::Wait(const WaitRequest* request, std::string message = absl::StrFormat( "[STUB] Condition '%s' met (ImGuiTestEngine not available)", request->condition()); - test_manager_->MarkHarnessTestCompleted(test_id, - HarnessTestStatus::kPassed, + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kPassed, message); test_manager_->AppendHarnessTestLog(test_id, message); @@ -1017,8 +999,7 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, response->set_message("TestManager not available"); response->set_actual_value("N/A"); response->set_expected_value("N/A"); - return finalize( - absl::FailedPreconditionError("TestManager not available")); + return finalize(absl::FailedPreconditionError("TestManager not available")); } const std::string test_id = test_manager_->RegisterHarnessTest( @@ -1036,8 +1017,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, response->set_message(message); response->set_actual_value("N/A"); response->set_expected_value("N/A"); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); return finalize(absl::OkStatus()); } @@ -1045,13 +1026,14 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, size_t colon_pos = condition.find(':'); if (colon_pos == std::string::npos) { std::string message = - "Invalid condition format. Use 'type:target' (e.g. 'visible:Main Window')"; + "Invalid condition format. Use 'type:target' (e.g. 'visible:Main " + "Window')"; response->set_success(false); response->set_message(message); response->set_actual_value("N/A"); response->set_expected_value("N/A"); - test_manager_->MarkHarnessTestCompleted( - test_id, HarnessTestStatus::kFailed, message); + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kFailed, + message); test_manager_->AppendHarnessTestLog(test_id, message); return finalize(absl::OkStatus()); } @@ -1065,22 +1047,20 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, assertion_target](ImGuiTestContext* ctx) { manager->MarkHarnessTestRunning(captured_id); - auto complete_with = - [manager, captured_id](bool passed, const std::string& message, - const std::string& actual, - const std::string& expected, - HarnessTestStatus status) { - manager->AppendHarnessTestLog(captured_id, message); - if (!actual.empty() || !expected.empty()) { - manager->AppendHarnessTestLog( - captured_id, - absl::StrFormat("Actual: %s | Expected: %s", actual, - expected)); - } - manager->MarkHarnessTestCompleted( - captured_id, status, - passed ? "" : message); - }; + auto complete_with = [manager, captured_id](bool passed, + const std::string& message, + const std::string& actual, + const std::string& expected, + HarnessTestStatus status) { + manager->AppendHarnessTestLog(captured_id, message); + if (!actual.empty() || !expected.empty()) { + manager->AppendHarnessTestLog( + captured_id, + absl::StrFormat("Actual: %s | Expected: %s", actual, expected)); + } + manager->MarkHarnessTestCompleted(captured_id, status, + passed ? "" : message); + }; try { bool passed = false; @@ -1089,41 +1069,41 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, std::string message; if (assertion_type == "visible") { - ImGuiTestItemInfo window_info = ctx->WindowInfo( - assertion_target.c_str(), ImGuiTestOpFlags_NoError); + ImGuiTestItemInfo window_info = + ctx->WindowInfo(assertion_target.c_str(), ImGuiTestOpFlags_NoError); bool is_visible = (window_info.ID != 0); passed = is_visible; actual_value = is_visible ? "visible" : "hidden"; expected_value = "visible"; - message = passed ? - absl::StrFormat("'%s' is visible", assertion_target) : - absl::StrFormat("'%s' is not visible", assertion_target); + message = + passed ? absl::StrFormat("'%s' is visible", assertion_target) + : absl::StrFormat("'%s' is not visible", assertion_target); } else if (assertion_type == "enabled") { - ImGuiTestItemInfo item = ctx->ItemInfo( - assertion_target.c_str(), ImGuiTestOpFlags_NoError); - bool is_enabled = (item.ID != 0 && - !(item.ItemFlags & ImGuiItemFlags_Disabled)); + ImGuiTestItemInfo item = + ctx->ItemInfo(assertion_target.c_str(), ImGuiTestOpFlags_NoError); + bool is_enabled = + (item.ID != 0 && !(item.ItemFlags & ImGuiItemFlags_Disabled)); passed = is_enabled; actual_value = is_enabled ? "enabled" : "disabled"; expected_value = "enabled"; - message = passed ? - absl::StrFormat("'%s' is enabled", assertion_target) : - absl::StrFormat("'%s' is not enabled", assertion_target); + message = + passed ? absl::StrFormat("'%s' is enabled", assertion_target) + : absl::StrFormat("'%s' is not enabled", assertion_target); } else if (assertion_type == "exists") { - ImGuiTestItemInfo item = ctx->ItemInfo( - assertion_target.c_str(), ImGuiTestOpFlags_NoError); + ImGuiTestItemInfo item = + ctx->ItemInfo(assertion_target.c_str(), ImGuiTestOpFlags_NoError); bool exists = (item.ID != 0); passed = exists; actual_value = exists ? "exists" : "not found"; expected_value = "exists"; - message = passed ? - absl::StrFormat("'%s' exists", assertion_target) : - absl::StrFormat("'%s' not found", assertion_target); + message = passed ? absl::StrFormat("'%s' exists", assertion_target) + : absl::StrFormat("'%s' not found", assertion_target); } else if (assertion_type == "text_contains") { size_t second_colon = assertion_target.find(':'); if (second_colon == std::string::npos) { std::string error_message = - "text_contains requires format 'text_contains:target:expected_text'"; + "text_contains requires format " + "'text_contains:target:expected_text'"; complete_with(false, error_message, "N/A", "N/A", HarnessTestStatus::kFailed); return; @@ -1138,12 +1118,11 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, passed = actual_text.find(expected_text) != std::string::npos; actual_value = actual_text; expected_value = absl::StrFormat("contains '%s'", expected_text); - message = passed ? - absl::StrFormat("'%s' contains '%s'", input_target, - expected_text) : - absl::StrFormat( - "'%s' does not contain '%s' (actual: '%s')", - input_target, expected_text, actual_text); + message = passed ? absl::StrFormat("'%s' contains '%s'", input_target, + expected_text) + : absl::StrFormat( + "'%s' does not contain '%s' (actual: '%s')", + input_target, expected_text, actual_text); } else { passed = false; actual_value = "not found"; @@ -1153,20 +1132,19 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, } else { std::string error_message = absl::StrFormat("Unknown assertion type: %s", assertion_type); - complete_with(false, error_message, "N/A", "N/A", - HarnessTestStatus::kFailed); + complete_with(false, error_message, "N/A", "N/A", + HarnessTestStatus::kFailed); return; } - complete_with(passed, message, actual_value, expected_value, - passed ? HarnessTestStatus::kPassed - : HarnessTestStatus::kFailed); + complete_with( + passed, message, actual_value, expected_value, + passed ? HarnessTestStatus::kPassed : HarnessTestStatus::kFailed); } catch (const std::exception& e) { std::string error_message = absl::StrFormat("Assertion failed: %s", e.what()); manager->AppendHarnessTestLog(captured_id, error_message); - manager->MarkHarnessTestCompleted(captured_id, - HarnessTestStatus::kFailed, + manager->MarkHarnessTestCompleted(captured_id, HarnessTestStatus::kFailed, error_message); } }; @@ -1183,8 +1161,8 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, ImGuiTestEngine_QueueTest(engine, test, ImGuiTestRunFlags_RunFromGui); response->set_success(true); - std::string message = absl::StrFormat( - "Queued assertion for '%s:%s'", assertion_type, assertion_target); + std::string message = absl::StrFormat("Queued assertion for '%s:%s'", + assertion_type, assertion_target); response->set_message(message); response->set_actual_value("(async)"); response->set_expected_value("(async)"); @@ -1195,8 +1173,7 @@ absl::Status ImGuiTestHarnessServiceImpl::Assert(const AssertRequest* request, std::string message = absl::StrFormat( "[STUB] Assertion '%s' passed (ImGuiTestEngine not available)", request->condition()); - test_manager_->MarkHarnessTestCompleted(test_id, - HarnessTestStatus::kPassed, + test_manager_->MarkHarnessTestCompleted(test_id, HarnessTestStatus::kPassed, message); test_manager_->AppendHarnessTestLog(test_id, message); @@ -1312,8 +1289,7 @@ absl::Status ImGuiTestHarnessServiceImpl::ListTests( } size_t end_index = - std::min(start_index + static_cast(page_size), - summaries.size()); + std::min(start_index + static_cast(page_size), summaries.size()); for (size_t i = start_index; i < end_index; ++i) { const auto& summary = summaries[i]; @@ -1356,8 +1332,7 @@ absl::Status ImGuiTestHarnessServiceImpl::ListTests( } absl::Status ImGuiTestHarnessServiceImpl::GetTestResults( - const GetTestResultsRequest* request, - GetTestResultsResponse* response) { + const GetTestResultsRequest* request, GetTestResultsResponse* response) { if (!test_manager_) { return absl::FailedPreconditionError("TestManager not available"); } @@ -1373,8 +1348,7 @@ absl::Status ImGuiTestHarnessServiceImpl::GetTestResults( } const auto& execution = execution_or.value(); - response->set_success( - execution.status == HarnessTestStatus::kPassed); + response->set_success(execution.status == HarnessTestStatus::kPassed); response->set_test_name(execution.name); response->set_category(execution.category); @@ -1413,7 +1387,7 @@ absl::Status ImGuiTestHarnessServiceImpl::GetTestResults( for (const auto& [key, value] : execution.metrics) { (*metrics_map)[key] = value; } - + // IT-08b: Include failure diagnostics if available if (!execution.screenshot_path.empty()) { response->set_screenshot_path(execution.screenshot_path); @@ -1430,8 +1404,7 @@ absl::Status ImGuiTestHarnessServiceImpl::GetTestResults( } absl::Status ImGuiTestHarnessServiceImpl::DiscoverWidgets( - const DiscoverWidgetsRequest* request, - DiscoverWidgetsResponse* response) { + const DiscoverWidgetsRequest* request, DiscoverWidgetsResponse* response) { if (!request) { return absl::InvalidArgumentError("request cannot be null"); } @@ -1443,8 +1416,7 @@ absl::Status ImGuiTestHarnessServiceImpl::DiscoverWidgets( return absl::FailedPreconditionError("TestManager not available"); } - widget_discovery_service_.CollectWidgets(/*ctx=*/nullptr, *request, - response); + widget_discovery_service_.CollectWidgets(/*ctx=*/nullptr, *request, response); return absl::OkStatus(); } @@ -1543,10 +1515,9 @@ absl::Status ImGuiTestHarnessServiceImpl::ReplayTest( overrides[entry.first] = entry.second; } - response->set_replay_session_id( - absl::StrFormat("replay_%s", - absl::FormatTime("%Y%m%dT%H%M%S", absl::Now(), - absl::UTCTimeZone()))); + response->set_replay_session_id(absl::StrFormat( + "replay_%s", + absl::FormatTime("%Y%m%dT%H%M%S", absl::Now(), absl::UTCTimeZone()))); auto suspension = test_recorder_.Suspend(); @@ -1694,15 +1665,15 @@ absl::Status ImGuiTestHarnessServiceImpl::ReplayTest( std::string expectation_error; if (!expectations_met) { - expectation_error = absl::StrFormat( - "Expected success=%s but got %s", - step.expect_success ? "true" : "false", - step_success ? "true" : "false"); + expectation_error = + absl::StrFormat("Expected success=%s but got %s", + step.expect_success ? "true" : "false", + step_success ? "true" : "false"); } if (!step.expect_status.empty()) { HarnessTestStatus expected_status = - ::yaze::test::HarnessStatusFromString(step.expect_status); + ::yaze::test::HarnessStatusFromString(step.expect_status); if (!have_execution) { expectations_met = false; if (!expectation_error.empty()) { @@ -1716,11 +1687,12 @@ absl::Status ImGuiTestHarnessServiceImpl::ReplayTest( expectation_error.append("; "); } expectation_error.append(absl::StrFormat( - "Expected status %s but observed %s", - step.expect_status, ::yaze::test::HarnessStatusToString(execution.status))); + "Expected status %s but observed %s", step.expect_status, + ::yaze::test::HarnessStatusToString(execution.status))); } if (have_execution) { - assertion->set_actual_value(::yaze::test::HarnessStatusToString(execution.status)); + assertion->set_actual_value( + ::yaze::test::HarnessStatusToString(execution.status)); assertion->set_expected_value(step.expect_status); } } @@ -1735,9 +1707,9 @@ absl::Status ImGuiTestHarnessServiceImpl::ReplayTest( if (!expectation_error.empty()) { expectation_error.append("; "); } - expectation_error.append(absl::StrFormat( - "Expected message containing '%s' but got '%s'", - step.expect_message, actual_message)); + expectation_error.append( + absl::StrFormat("Expected message containing '%s' but got '%s'", + step.expect_message, actual_message)); } } @@ -1746,8 +1718,8 @@ absl::Status ImGuiTestHarnessServiceImpl::ReplayTest( assertion->set_error_message(expectation_error); overall_success = false; overall_message = expectation_error; - logs.push_back(absl::StrFormat(" Failed expectations: %s", - expectation_error)); + logs.push_back( + absl::StrFormat(" Failed expectations: %s", expectation_error)); if (request->ci_mode()) { break; } @@ -1790,7 +1762,8 @@ ImGuiTestHarnessServer::~ImGuiTestHarnessServer() { Shutdown(); } -absl::Status ImGuiTestHarnessServer::Start(int port, TestManager* test_manager) { +absl::Status ImGuiTestHarnessServer::Start(int port, + TestManager* test_manager) { if (server_) { return absl::FailedPreconditionError("Server already running"); } @@ -1810,8 +1783,7 @@ absl::Status ImGuiTestHarnessServer::Start(int port, TestManager* test_manager) grpc::ServerBuilder builder; // Listen on all interfaces (use 0.0.0.0 to avoid IPv6/IPv4 binding conflicts) - builder.AddListeningPort(server_address, - grpc::InsecureServerCredentials()); + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); // Register service builder.RegisterService(grpc_service_.get()); diff --git a/src/app/service/imgui_test_harness_service.h b/src/app/service/imgui_test_harness_service.h index 0300b274..dfe11867 100644 --- a/src/app/service/imgui_test_harness_service.h +++ b/src/app/service/imgui_test_harness_service.h @@ -69,7 +69,7 @@ class ImGuiTestHarnessServiceImpl { public: // Constructor now takes TestManager reference for ImGuiTestEngine access explicit ImGuiTestHarnessServiceImpl(TestManager* test_manager) - : test_manager_(test_manager), test_recorder_(test_manager) {} + : test_manager_(test_manager), test_recorder_(test_manager) {} ~ImGuiTestHarnessServiceImpl() = default; // Disable copy and move diff --git a/src/app/service/screenshot_utils.cc b/src/app/service/screenshot_utils.cc index a14326e0..fc60f967 100644 --- a/src/app/service/screenshot_utils.cc +++ b/src/app/service/screenshot_utils.cc @@ -48,8 +48,8 @@ std::filesystem::path DefaultScreenshotPath() { const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now()); return base_dir / - std::filesystem::path( - absl::StrFormat("harness_%lld.bmp", static_cast(timestamp_ms))); + std::filesystem::path(absl::StrFormat( + "harness_%lld.bmp", static_cast(timestamp_ms))); } } // namespace @@ -72,17 +72,16 @@ absl::StatusOr CaptureHarnessScreenshot( absl::StrFormat("Failed to get renderer size: %s", SDL_GetError())); } - std::filesystem::path output_path = preferred_path.empty() - ? DefaultScreenshotPath() - : std::filesystem::path(preferred_path); + std::filesystem::path output_path = + preferred_path.empty() ? DefaultScreenshotPath() + : std::filesystem::path(preferred_path); if (output_path.has_parent_path()) { std::error_code ec; std::filesystem::create_directories(output_path.parent_path(), ec); } - SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, 0x00FF0000, - 0x0000FF00, 0x000000FF, - 0xFF000000); + SDL_Surface* surface = SDL_CreateRGBSurface( + 0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if (!surface) { return absl::InternalError( absl::StrFormat("Failed to create SDL surface: %s", SDL_GetError())); @@ -104,8 +103,7 @@ absl::StatusOr CaptureHarnessScreenshot( SDL_FreeSurface(surface); std::error_code ec; - const int64_t file_size = - std::filesystem::file_size(output_path, ec); + const int64_t file_size = std::filesystem::file_size(output_path, ec); if (ec) { return absl::InternalError( absl::StrFormat("Failed to stat screenshot %s: %s", @@ -132,7 +130,7 @@ absl::StatusOr CaptureHarnessScreenshotRegion( } SDL_Renderer* renderer = backend_data->Renderer; - + // Get full renderer size int full_width = 0; int full_height = 0; @@ -146,40 +144,42 @@ absl::StatusOr CaptureHarnessScreenshotRegion( int capture_y = 0; int capture_width = full_width; int capture_height = full_height; - + if (region.has_value()) { capture_x = region->x; capture_y = region->y; capture_width = region->width; capture_height = region->height; - + // Clamp to renderer bounds - if (capture_x < 0) capture_x = 0; - if (capture_y < 0) capture_y = 0; + if (capture_x < 0) + capture_x = 0; + if (capture_y < 0) + capture_y = 0; if (capture_x + capture_width > full_width) { capture_width = full_width - capture_x; } if (capture_y + capture_height > full_height) { capture_height = full_height - capture_y; } - + if (capture_width <= 0 || capture_height <= 0) { return absl::InvalidArgumentError("Invalid capture region"); } } - std::filesystem::path output_path = preferred_path.empty() - ? DefaultScreenshotPath() - : std::filesystem::path(preferred_path); + std::filesystem::path output_path = + preferred_path.empty() ? DefaultScreenshotPath() + : std::filesystem::path(preferred_path); if (output_path.has_parent_path()) { std::error_code ec; std::filesystem::create_directories(output_path.parent_path(), ec); } // Create surface for the capture region - SDL_Surface* surface = SDL_CreateRGBSurface(0, capture_width, capture_height, - 32, 0x00FF0000, 0x0000FF00, - 0x000000FF, 0xFF000000); + SDL_Surface* surface = + SDL_CreateRGBSurface(0, capture_width, capture_height, 32, 0x00FF0000, + 0x0000FF00, 0x000000FF, 0xFF000000); if (!surface) { return absl::InternalError( absl::StrFormat("Failed to create SDL surface: %s", SDL_GetError())); @@ -236,8 +236,7 @@ absl::StatusOr CaptureActiveWindow( } absl::StatusOr CaptureWindowByName( - const std::string& window_name, - const std::string& preferred_path) { + const std::string& window_name, const std::string& preferred_path) { ImGuiContext* ctx = ImGui::GetCurrentContext(); if (!ctx) { return absl::FailedPreconditionError("No ImGui context"); diff --git a/src/app/service/screenshot_utils.h b/src/app/service/screenshot_utils.h index e0780755..ec752726 100644 --- a/src/app/service/screenshot_utils.h +++ b/src/app/service/screenshot_utils.h @@ -44,8 +44,7 @@ absl::StatusOr CaptureActiveWindow( // Captures a specific ImGui window by name. absl::StatusOr CaptureWindowByName( - const std::string& window_name, - const std::string& preferred_path = ""); + const std::string& window_name, const std::string& preferred_path = ""); } // namespace test } // namespace yaze diff --git a/src/app/service/unified_grpc_server.cc b/src/app/service/unified_grpc_server.cc index 8749ce4c..92c1998e 100644 --- a/src/app/service/unified_grpc_server.cc +++ b/src/app/service/unified_grpc_server.cc @@ -6,19 +6,17 @@ #include #include "absl/strings/str_format.h" -#include "app/service/imgui_test_harness_service.h" -#include "app/service/canvas_automation_service.h" #include "app/net/rom_service_impl.h" #include "app/rom.h" +#include "app/service/canvas_automation_service.h" +#include "app/service/imgui_test_harness_service.h" #include #include "protos/canvas_automation.grpc.pb.h" namespace yaze { -YazeGRPCServer::YazeGRPCServer() - : is_running_(false) { -} +YazeGRPCServer::YazeGRPCServer() : is_running_(false) {} // Destructor defined here so CanvasAutomationServiceGrpc is a complete type YazeGRPCServer::~YazeGRPCServer() { @@ -26,57 +24,57 @@ YazeGRPCServer::~YazeGRPCServer() { } absl::Status YazeGRPCServer::Initialize( - int port, - test::TestManager* test_manager, - Rom* rom, + int port, test::TestManager* test_manager, Rom* rom, net::RomVersionManager* version_mgr, net::ProposalApprovalManager* approval_mgr, CanvasAutomationServiceImpl* canvas_service) { - + if (is_running_) { return absl::FailedPreconditionError("Server is already running"); } - + config_.port = port; - + // Create ImGuiTestHarness service if test_manager provided if (config_.enable_test_harness && test_manager) { - test_harness_service_ = + test_harness_service_ = std::make_unique(test_manager); std::cout << "✓ ImGuiTestHarness service initialized\n"; } else if (config_.enable_test_harness) { std::cout << "⚠ ImGuiTestHarness requested but no TestManager provided\n"; } - + // Create ROM service if rom provided if (config_.enable_rom_service && rom) { - rom_service_ = std::make_unique( - rom, version_mgr, approval_mgr); - + rom_service_ = + std::make_unique(rom, version_mgr, approval_mgr); + // Configure ROM service net::RomServiceImpl::Config rom_config; - rom_config.require_approval_for_writes = config_.require_approval_for_rom_writes; + rom_config.require_approval_for_writes = + config_.require_approval_for_rom_writes; rom_service_->SetConfig(rom_config); - + std::cout << "✓ ROM service initialized\n"; } else if (config_.enable_rom_service) { std::cout << "⚠ ROM service requested but no ROM provided\n"; } - + // Create Canvas Automation service if canvas_service provided if (config_.enable_canvas_automation && canvas_service) { // Store the provided service (not owned by us) - canvas_service_ = std::unique_ptr(canvas_service); + canvas_service_ = + std::unique_ptr(canvas_service); std::cout << "✓ Canvas Automation service initialized\n"; } else if (config_.enable_canvas_automation) { std::cout << "⚠ Canvas Automation requested but no service provided\n"; } - + if (!test_harness_service_ && !rom_service_ && !canvas_service_) { return absl::InvalidArgumentError( "At least one service must be enabled and initialized"); } - + return absl::OkStatus(); } @@ -85,9 +83,10 @@ absl::Status YazeGRPCServer::Start() { if (!status.ok()) { return status; } - - std::cout << "✓ YAZE gRPC automation server listening on 0.0.0.0:" << config_.port << "\n"; - + + std::cout << "✓ YAZE gRPC automation server listening on 0.0.0.0:" + << config_.port << "\n"; + if (test_harness_service_) { std::cout << " ✓ ImGuiTestHarness available\n"; } @@ -97,12 +96,12 @@ absl::Status YazeGRPCServer::Start() { if (canvas_service_) { std::cout << " ✓ Canvas Automation available\n"; } - + std::cout << "\nServer is ready to accept requests...\n"; - + // Block until server is shut down server_->Wait(); - + return absl::OkStatus(); } @@ -111,9 +110,9 @@ absl::Status YazeGRPCServer::StartAsync() { if (!status.ok()) { return status; } - + std::cout << "✓ Unified gRPC server started on port " << config_.port << "\n"; - + // Server runs in background, doesn't block return absl::OkStatus(); } @@ -136,14 +135,14 @@ absl::Status YazeGRPCServer::BuildServer() { if (is_running_) { return absl::FailedPreconditionError("Server already running"); } - + std::string server_address = absl::StrFormat("0.0.0.0:%d", config_.port); - + grpc::ServerBuilder builder; - + // Listen on all interfaces builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); - + // Register services if (test_harness_service_) { // Note: The actual registration requires the gRPC service wrapper @@ -152,29 +151,30 @@ absl::Status YazeGRPCServer::BuildServer() { std::cout << " Registering ImGuiTestHarness service...\n"; // builder.RegisterService(test_harness_grpc_wrapper_.get()); } - + if (rom_service_) { std::cout << " Registering ROM service...\n"; builder.RegisterService(rom_service_.get()); } - + if (canvas_service_) { std::cout << " Registering Canvas Automation service...\n"; // Create gRPC wrapper using factory function - canvas_grpc_service_ = CreateCanvasAutomationServiceGrpc(canvas_service_.get()); + canvas_grpc_service_ = + CreateCanvasAutomationServiceGrpc(canvas_service_.get()); builder.RegisterService(canvas_grpc_service_.get()); } - + // Build and start server_ = builder.BuildAndStart(); - + if (!server_) { return absl::InternalError( absl::StrFormat("Failed to start server on %s", server_address)); } - + is_running_ = true; - + return absl::OkStatus(); } diff --git a/src/app/service/unified_grpc_server.h b/src/app/service/unified_grpc_server.h index d1ab4123..349b43e6 100644 --- a/src/app/service/unified_grpc_server.h +++ b/src/app/service/unified_grpc_server.h @@ -20,18 +20,16 @@ namespace yaze { // Forward declarations class CanvasAutomationServiceImpl; - class Rom; namespace net { class ProposalApprovalManager; class RomServiceImpl; -} - +} // namespace net namespace test { class TestManager; class ImGuiTestHarnessServiceImpl; -} +} // namespace test /** * @class YazeGRPCServer @@ -67,11 +65,11 @@ class YazeGRPCServer { bool enable_canvas_automation = true; bool require_approval_for_rom_writes = true; }; - + YazeGRPCServer(); // Destructor must be defined in .cc file to allow deletion of incomplete types ~YazeGRPCServer(); - + /** * @brief Initialize the server with all required services * @param port Port to listen on (default 50051) @@ -83,45 +81,43 @@ class YazeGRPCServer { * @return OK status if initialized successfully */ absl::Status Initialize( - int port, - test::TestManager* test_manager = nullptr, - Rom* rom = nullptr, + int port, test::TestManager* test_manager = nullptr, Rom* rom = nullptr, net::RomVersionManager* version_mgr = nullptr, net::ProposalApprovalManager* approval_mgr = nullptr, CanvasAutomationServiceImpl* canvas_service = nullptr); - + /** * @brief Start the gRPC server (blocking) * Starts the server and blocks until Shutdown() is called */ absl::Status Start(); - + /** * @brief Start the server in a background thread (non-blocking) * Returns immediately after starting the server */ absl::Status StartAsync(); - + /** * @brief Shutdown the server gracefully */ void Shutdown(); - + /** * @brief Check if server is currently running */ bool IsRunning() const; - + /** * @brief Get the port the server is listening on */ int Port() const { return config_.port; } - + /** * @brief Update configuration (must be called before Start) */ void SetConfig(const Config& config) { config_ = config; } - + private: Config config_; std::unique_ptr server_; @@ -131,7 +127,7 @@ class YazeGRPCServer { // Store as base grpc::Service* to avoid incomplete type issues std::unique_ptr canvas_grpc_service_; bool is_running_; - + // Build the gRPC server with all services absl::Status BuildServer(); }; diff --git a/src/app/service/widget_discovery_service.cc b/src/app/service/widget_discovery_service.cc index b9b06877..125bc272 100644 --- a/src/app/service/widget_discovery_service.cc +++ b/src/app/service/widget_discovery_service.cc @@ -159,8 +159,8 @@ void WidgetDiscoveryService::CollectWidgets( response->set_total_widgets(total_widgets); } -bool WidgetDiscoveryService::MatchesWindow(absl::string_view window_name, - absl::string_view filter_lower) const { +bool WidgetDiscoveryService::MatchesWindow( + absl::string_view window_name, absl::string_view filter_lower) const { if (filter_lower.empty()) { return true; } @@ -168,8 +168,8 @@ bool WidgetDiscoveryService::MatchesWindow(absl::string_view window_name, return absl::StrContains(name_lower, filter_lower); } -bool WidgetDiscoveryService::MatchesPathPrefix(absl::string_view path, - absl::string_view prefix_lower) const { +bool WidgetDiscoveryService::MatchesPathPrefix( + absl::string_view path, absl::string_view prefix_lower) const { if (prefix_lower.empty()) { return true; } @@ -230,8 +230,8 @@ std::string WidgetDiscoveryService::ExtractLabel(absl::string_view path) const { return std::string(path.substr(colon + 1)); } -std::string WidgetDiscoveryService::SuggestedAction(absl::string_view type, - absl::string_view label) const { +std::string WidgetDiscoveryService::SuggestedAction( + absl::string_view type, absl::string_view label) const { std::string type_lower = absl::AsciiStrToLower(std::string(type)); if (type_lower == "button" || type_lower == "menuitem") { return absl::StrCat("Click button:", label); diff --git a/src/app/service/widget_discovery_service.h b/src/app/service/widget_discovery_service.h index cfbab682..93613b44 100644 --- a/src/app/service/widget_discovery_service.h +++ b/src/app/service/widget_discovery_service.h @@ -42,8 +42,7 @@ class WidgetDiscoveryService { absl::string_view filter) const; bool MatchesPathPrefix(absl::string_view path, absl::string_view prefix) const; - bool MatchesType(absl::string_view type, - WidgetType filter) const; + bool MatchesType(absl::string_view type, WidgetType filter) const; std::string ExtractWindowName(absl::string_view path) const; std::string ExtractLabel(absl::string_view path) const; diff --git a/src/app/test/e2e_test_suite.h b/src/app/test/e2e_test_suite.h index 16751311..a776b334 100644 --- a/src/app/test/e2e_test_suite.h +++ b/src/app/test/e2e_test_suite.h @@ -5,10 +5,10 @@ #include #include "absl/strings/str_format.h" -#include "app/test/test_manager.h" -#include "app/rom.h" -#include "app/transaction.h" #include "app/gui/core/icons.h" +#include "app/rom.h" +#include "app/test/test_manager.h" +#include "app/transaction.h" namespace yaze { namespace test { @@ -28,11 +28,13 @@ class E2ETestSuite : public TestSuite { ~E2ETestSuite() override = default; std::string GetName() const override { return "End-to-End ROM Tests"; } - TestCategory GetCategory() const override { return TestCategory::kIntegration; } + TestCategory GetCategory() const override { + return TestCategory::kIntegration; + } absl::Status RunTests(TestResults& results) override { Rom* current_rom = TestManager::Get().GetCurrentRom(); - + // Check ROM availability if (!current_rom || !current_rom->is_loaded()) { AddSkippedTest(results, "ROM_Availability_Check", "No ROM loaded"); @@ -43,19 +45,19 @@ class E2ETestSuite : public TestSuite { if (test_rom_load_save_) { RunRomLoadSaveTest(results, current_rom); } - + if (test_data_integrity_) { RunDataIntegrityTest(results, current_rom); } - + if (test_transaction_system_) { RunTransactionSystemTest(results, current_rom); } - + if (test_large_scale_editing_) { RunLargeScaleEditingTest(results, current_rom); } - + if (test_corruption_detection_) { RunCorruptionDetectionTest(results, current_rom); } @@ -65,36 +67,39 @@ class E2ETestSuite : public TestSuite { void DrawConfiguration() override { Rom* current_rom = TestManager::Get().GetCurrentRom(); - + ImGui::Text("%s E2E Test Configuration", ICON_MD_VERIFIED_USER); - + if (current_rom && current_rom->is_loaded()) { - ImGui::TextColored(ImVec4(0.0F, 1.0F, 0.0F, 1.0F), - "%s Current ROM: %s", ICON_MD_CHECK_CIRCLE, current_rom->title().c_str()); + ImGui::TextColored(ImVec4(0.0F, 1.0F, 0.0F, 1.0F), "%s Current ROM: %s", + ICON_MD_CHECK_CIRCLE, current_rom->title().c_str()); ImGui::Text("Size: %.2F MB", current_rom->size() / 1048576.0F); } else { - ImGui::TextColored(ImVec4(1.0F, 0.5F, 0.0F, 1.0F), - "%s No ROM currently loaded", ICON_MD_WARNING); + ImGui::TextColored(ImVec4(1.0F, 0.5F, 0.0F, 1.0F), + "%s No ROM currently loaded", ICON_MD_WARNING); } - + ImGui::Separator(); ImGui::Checkbox("Test ROM load/save", &test_rom_load_save_); ImGui::Checkbox("Test data integrity", &test_data_integrity_); ImGui::Checkbox("Test transaction system", &test_transaction_system_); ImGui::Checkbox("Test large-scale editing", &test_large_scale_editing_); ImGui::Checkbox("Test corruption detection", &test_corruption_detection_); - + if (test_large_scale_editing_) { ImGui::Indent(); ImGui::InputInt("Number of edits", &num_edits_); - if (num_edits_ < 1) num_edits_ = 1; - if (num_edits_ > 100) num_edits_ = 100; + if (num_edits_ < 1) + num_edits_ = 1; + if (num_edits_ > 100) + num_edits_ = 100; ImGui::Unindent(); } } private: - void AddSkippedTest(TestResults& results, const std::string& test_name, const std::string& reason) { + void AddSkippedTest(TestResults& results, const std::string& test_name, + const std::string& reason) { TestResult result; result.name = test_name; result.suite_name = GetName(); @@ -108,300 +113,332 @@ class E2ETestSuite : public TestSuite { void RunRomLoadSaveTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Load_Save_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Test basic ROM operations - if (test_rom->size() != rom->size()) { - return absl::InternalError("ROM copy size mismatch"); - } - - // Test save and reload - std::string test_filename = test_manager.GenerateTestRomFilename("e2e_test"); - auto save_status = test_rom->SaveToFile(Rom::SaveSettings{.filename = test_filename}); - if (!save_status.ok()) { - return save_status; - } - - // Clean up test file - std::filesystem::remove(test_filename); - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Test basic ROM operations + if (test_rom->size() != rom->size()) { + return absl::InternalError("ROM copy size mismatch"); + } + + // Test save and reload + std::string test_filename = + test_manager.GenerateTestRomFilename("e2e_test"); + auto save_status = test_rom->SaveToFile( + Rom::SaveSettings{.filename = test_filename}); + if (!save_status.ok()) { + return save_status; + } + + // Clean up test file + std::filesystem::remove(test_filename); + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; result.error_message = "ROM load/save test completed successfully"; } else { result.status = TestStatus::kFailed; - result.error_message = "ROM load/save test failed: " + test_status.ToString(); + result.error_message = + "ROM load/save test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "ROM load/save test exception: " + std::string(e.what()); + result.error_message = + "ROM load/save test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunDataIntegrityTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Data_Integrity_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Test data integrity by comparing key areas - std::vector test_addresses = {0x7FC0, 0x8000, 0x10000, 0x20000}; - - for (uint32_t addr : test_addresses) { - auto original_byte = rom->ReadByte(addr); - auto copy_byte = test_rom->ReadByte(addr); - - if (!original_byte.ok() || !copy_byte.ok()) { - return absl::InternalError("Failed to read ROM data for comparison"); - } - - if (*original_byte != *copy_byte) { - return absl::InternalError(absl::StrFormat( - "Data integrity check failed at address 0x%X: original=0x%02X, copy=0x%02X", - addr, *original_byte, *copy_byte)); - } - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Test data integrity by comparing key areas + std::vector test_addresses = {0x7FC0, 0x8000, 0x10000, + 0x20000}; + + for (uint32_t addr : test_addresses) { + auto original_byte = rom->ReadByte(addr); + auto copy_byte = test_rom->ReadByte(addr); + + if (!original_byte.ok() || !copy_byte.ok()) { + return absl::InternalError( + "Failed to read ROM data for comparison"); + } + + if (*original_byte != *copy_byte) { + return absl::InternalError( + absl::StrFormat("Data integrity check failed at address " + "0x%X: original=0x%02X, copy=0x%02X", + addr, *original_byte, *copy_byte)); + } + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Data integrity test passed - all checked addresses match"; + result.error_message = + "Data integrity test passed - all checked addresses match"; } else { result.status = TestStatus::kFailed; - result.error_message = "Data integrity test failed: " + test_status.ToString(); + result.error_message = + "Data integrity test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Data integrity test exception: " + std::string(e.what()); + result.error_message = + "Data integrity test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunTransactionSystemTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Transaction_System_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Test transaction system - Transaction transaction(*test_rom); - - // Store original values - auto original_byte1 = test_rom->ReadByte(0x1000); - auto original_byte2 = test_rom->ReadByte(0x2000); - auto original_word = test_rom->ReadWord(0x3000); - - if (!original_byte1.ok() || !original_byte2.ok() || !original_word.ok()) { - return absl::InternalError("Failed to read original ROM data"); - } - - // Make changes in transaction - transaction.WriteByte(0x1000, 0xAA) - .WriteByte(0x2000, 0xBB) - .WriteWord(0x3000, 0xCCDD); - - // Commit transaction - RETURN_IF_ERROR(transaction.Commit()); - - // Verify changes - auto new_byte1 = test_rom->ReadByte(0x1000); - auto new_byte2 = test_rom->ReadByte(0x2000); - auto new_word = test_rom->ReadWord(0x3000); - - if (!new_byte1.ok() || !new_byte2.ok() || !new_word.ok()) { - return absl::InternalError("Failed to read modified ROM data"); - } - - if (*new_byte1 != 0xAA || *new_byte2 != 0xBB || *new_word != 0xCCDD) { - return absl::InternalError("Transaction changes not applied correctly"); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Test transaction system + Transaction transaction(*test_rom); + + // Store original values + auto original_byte1 = test_rom->ReadByte(0x1000); + auto original_byte2 = test_rom->ReadByte(0x2000); + auto original_word = test_rom->ReadWord(0x3000); + + if (!original_byte1.ok() || !original_byte2.ok() || + !original_word.ok()) { + return absl::InternalError("Failed to read original ROM data"); + } + + // Make changes in transaction + transaction.WriteByte(0x1000, 0xAA) + .WriteByte(0x2000, 0xBB) + .WriteWord(0x3000, 0xCCDD); + + // Commit transaction + RETURN_IF_ERROR(transaction.Commit()); + + // Verify changes + auto new_byte1 = test_rom->ReadByte(0x1000); + auto new_byte2 = test_rom->ReadByte(0x2000); + auto new_word = test_rom->ReadWord(0x3000); + + if (!new_byte1.ok() || !new_byte2.ok() || !new_word.ok()) { + return absl::InternalError("Failed to read modified ROM data"); + } + + if (*new_byte1 != 0xAA || *new_byte2 != 0xBB || + *new_word != 0xCCDD) { + return absl::InternalError( + "Transaction changes not applied correctly"); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; result.error_message = "Transaction system test completed successfully"; } else { result.status = TestStatus::kFailed; - result.error_message = "Transaction system test failed: " + test_status.ToString(); + result.error_message = + "Transaction system test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Transaction system test exception: " + std::string(e.what()); + result.error_message = + "Transaction system test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunLargeScaleEditingTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Large_Scale_Editing_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Test large-scale editing - for (int i = 0; i < num_edits_; i++) { - uint32_t addr = 0x1000 + (i * 4); - uint8_t value = i % 256; - - RETURN_IF_ERROR(test_rom->WriteByte(addr, value)); - } - - // Verify all changes - for (int i = 0; i < num_edits_; i++) { - uint32_t addr = 0x1000 + (i * 4); - uint8_t expected_value = i % 256; - - auto actual_value = test_rom->ReadByte(addr); - if (!actual_value.ok()) { - return absl::InternalError(absl::StrFormat("Failed to read address 0x%X", addr)); - } - - if (*actual_value != expected_value) { - return absl::InternalError(absl::StrFormat( - "Value mismatch at 0x%X: expected=0x%02X, actual=0x%02X", - addr, expected_value, *actual_value)); - } - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Test large-scale editing + for (int i = 0; i < num_edits_; i++) { + uint32_t addr = 0x1000 + (i * 4); + uint8_t value = i % 256; + + RETURN_IF_ERROR(test_rom->WriteByte(addr, value)); + } + + // Verify all changes + for (int i = 0; i < num_edits_; i++) { + uint32_t addr = 0x1000 + (i * 4); + uint8_t expected_value = i % 256; + + auto actual_value = test_rom->ReadByte(addr); + if (!actual_value.ok()) { + return absl::InternalError( + absl::StrFormat("Failed to read address 0x%X", addr)); + } + + if (*actual_value != expected_value) { + return absl::InternalError(absl::StrFormat( + "Value mismatch at 0x%X: expected=0x%02X, actual=0x%02X", + addr, expected_value, *actual_value)); + } + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = absl::StrFormat("Large-scale editing test passed: %d edits", num_edits_); + result.error_message = absl::StrFormat( + "Large-scale editing test passed: %d edits", num_edits_); } else { result.status = TestStatus::kFailed; - result.error_message = "Large-scale editing test failed: " + test_status.ToString(); + result.error_message = + "Large-scale editing test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Large-scale editing test exception: " + std::string(e.what()); + result.error_message = + "Large-scale editing test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunCorruptionDetectionTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Corruption_Detection_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Intentionally corrupt some data - RETURN_IF_ERROR(test_rom->WriteByte(0x1000, 0xFF)); - RETURN_IF_ERROR(test_rom->WriteByte(0x2000, 0xAA)); - - // Verify corruption is detected - auto corrupted_byte1 = test_rom->ReadByte(0x1000); - auto corrupted_byte2 = test_rom->ReadByte(0x2000); - - if (!corrupted_byte1.ok() || !corrupted_byte2.ok()) { - return absl::InternalError("Failed to read corrupted data"); - } - - if (*corrupted_byte1 != 0xFF || *corrupted_byte2 != 0xAA) { - return absl::InternalError("Corruption not applied correctly"); - } - - // Verify original data is different - auto original_byte1 = rom->ReadByte(0x1000); - auto original_byte2 = rom->ReadByte(0x2000); - - if (!original_byte1.ok() || !original_byte2.ok()) { - return absl::InternalError("Failed to read original data for comparison"); - } - - if (*corrupted_byte1 == *original_byte1 || *corrupted_byte2 == *original_byte2) { - return absl::InternalError("Corruption detection test failed - data not actually corrupted"); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Intentionally corrupt some data + RETURN_IF_ERROR(test_rom->WriteByte(0x1000, 0xFF)); + RETURN_IF_ERROR(test_rom->WriteByte(0x2000, 0xAA)); + + // Verify corruption is detected + auto corrupted_byte1 = test_rom->ReadByte(0x1000); + auto corrupted_byte2 = test_rom->ReadByte(0x2000); + + if (!corrupted_byte1.ok() || !corrupted_byte2.ok()) { + return absl::InternalError("Failed to read corrupted data"); + } + + if (*corrupted_byte1 != 0xFF || *corrupted_byte2 != 0xAA) { + return absl::InternalError("Corruption not applied correctly"); + } + + // Verify original data is different + auto original_byte1 = rom->ReadByte(0x1000); + auto original_byte2 = rom->ReadByte(0x2000); + + if (!original_byte1.ok() || !original_byte2.ok()) { + return absl::InternalError( + "Failed to read original data for comparison"); + } + + if (*corrupted_byte1 == *original_byte1 || + *corrupted_byte2 == *original_byte2) { + return absl::InternalError( + "Corruption detection test failed - data not actually " + "corrupted"); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Corruption detection test passed - corruption successfully applied and detected"; + result.error_message = + "Corruption detection test passed - corruption successfully " + "applied and detected"; } else { result.status = TestStatus::kFailed; - result.error_message = "Corruption detection test failed: " + test_status.ToString(); + result.error_message = + "Corruption detection test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Corruption detection test exception: " + std::string(e.what()); + result.error_message = + "Corruption detection test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } diff --git a/src/app/test/emulator_test_suite.h b/src/app/test/emulator_test_suite.h index 32d64b1c..dc5a2276 100644 --- a/src/app/test/emulator_test_suite.h +++ b/src/app/test/emulator_test_suite.h @@ -4,16 +4,16 @@ #include #include -#include "app/test/test_manager.h" -#include "app/emu/snes.h" -#include "app/emu/cpu/cpu.h" #include "app/emu/audio/apu.h" -#include "app/emu/audio/spc700.h" #include "app/emu/audio/audio_backend.h" +#include "app/emu/audio/spc700.h" +#include "app/emu/cpu/cpu.h" +#include "app/emu/debug/apu_debugger.h" #include "app/emu/debug/breakpoint_manager.h" #include "app/emu/debug/watchpoint_manager.h" -#include "app/emu/debug/apu_debugger.h" +#include "app/emu/snes.h" #include "app/gui/core/icons.h" +#include "app/test/test_manager.h" #include "util/log.h" namespace yaze { @@ -36,12 +36,17 @@ class EmulatorTestSuite : public TestSuite { TestCategory GetCategory() const override { return TestCategory::kUnit; } absl::Status RunTests(TestResults& results) override { - if (test_apu_handshake_) RunApuHandshakeTest(results); - if (test_spc700_cycles_) RunSpc700CycleAccuracyTest(results); - if (test_breakpoint_manager_) RunBreakpointManagerTest(results); - if (test_watchpoint_manager_) RunWatchpointManagerTest(results); - if (test_audio_backend_) RunAudioBackendTest(results); - + if (test_apu_handshake_) + RunApuHandshakeTest(results); + if (test_spc700_cycles_) + RunSpc700CycleAccuracyTest(results); + if (test_breakpoint_manager_) + RunBreakpointManagerTest(results); + if (test_watchpoint_manager_) + RunWatchpointManagerTest(results); + if (test_audio_backend_) + RunAudioBackendTest(results); + return absl::OkStatus(); } @@ -82,47 +87,54 @@ class EmulatorTestSuite : public TestSuite { try { // Setup a mock SNES environment emu::Snes snes; - std::vector rom_data(0x8000, 0); // Minimal ROM + std::vector rom_data(0x8000, 0); // Minimal ROM snes.Init(rom_data); - + auto& apu = snes.apu(); auto& tracker = snes.apu_handshake_tracker(); - + // 1. Reset APU to start the IPL ROM boot sequence. apu.Reset(); tracker.Reset(); - + // 2. Run APU for enough cycles to complete its internal initialization. // The SPC700 should write $AA to port $F4 and $BB to $F5. for (int i = 0; i < 10000; ++i) { - apu.RunCycles(i * 24); // Simulate passing master cycles - if (tracker.GetPhase() == emu::debug::ApuHandshakeTracker::Phase::WAITING_BBAA) { + apu.RunCycles(i * 24); // Simulate passing master cycles + if (tracker.GetPhase() == + emu::debug::ApuHandshakeTracker::Phase::WAITING_BBAA) { break; } } - + // 3. Verify the APU has signaled it is ready. - if (tracker.GetPhase() != emu::debug::ApuHandshakeTracker::Phase::WAITING_BBAA) { - throw std::runtime_error("APU did not signal ready ($BBAA). Current phase: " + tracker.GetPhaseString()); + if (tracker.GetPhase() != + emu::debug::ApuHandshakeTracker::Phase::WAITING_BBAA) { + throw std::runtime_error( + "APU did not signal ready ($BBAA). Current phase: " + + tracker.GetPhaseString()); } - + // 4. Simulate CPU writing $CC to initiate the transfer. snes.Write(0x2140, 0xCC); - + // 5. Run APU for a few more cycles to process the $CC command. apu.RunCycles(snes.mutable_cycles() + 1000); - + // 6. Verify the handshake is acknowledged. if (tracker.IsHandshakeComplete()) { result.status = TestStatus::kPassed; - result.error_message = "APU handshake successful. Ready signal and CPU ack verified."; + result.error_message = + "APU handshake successful. Ready signal and CPU ack verified."; } else { - throw std::runtime_error("CPU handshake ($CC) was not acknowledged by APU."); + throw std::runtime_error( + "CPU handshake ($CC) was not acknowledged by APU."); } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = std::string("APU handshake test exception: ") + e.what(); + result.error_message = + std::string("APU handshake test exception: ") + e.what(); } result.duration = std::chrono::duration_cast( @@ -150,37 +162,44 @@ class EmulatorTestSuite : public TestSuite { try { // Dummy callbacks for SPC700 instantiation emu::ApuCallbacks callbacks; - callbacks.read = [](uint16_t) { return 0; }; - callbacks.write = [](uint16_t, uint8_t) {}; - callbacks.idle = [](bool) {}; - + callbacks.read = [](uint16_t) { + return 0; + }; + callbacks.write = [](uint16_t, uint8_t) { + }; + callbacks.idle = [](bool) { + }; + emu::Spc700 spc(callbacks); spc.Reset(true); // Test a sample of opcodes against the cycle table // Opcode 0x00 (NOP) should take 2 cycles - spc.PC = 0; // Set PC to a known state - spc.RunOpcode(); // This will read opcode at PC=0 and prepare to execute - spc.RunOpcode(); // This executes the opcode - + spc.PC = 0; // Set PC to a known state + spc.RunOpcode(); // This will read opcode at PC=0 and prepare to execute + spc.RunOpcode(); // This executes the opcode + if (spc.GetLastOpcodeCycles() != 2) { - throw std::runtime_error(absl::StrFormat("NOP (0x00) should be 2 cycles, was %d", spc.GetLastOpcodeCycles())); + throw std::runtime_error( + absl::StrFormat("NOP (0x00) should be 2 cycles, was %d", + spc.GetLastOpcodeCycles())); } - + // Opcode 0x2F (BRA) should take 4 cycles spc.PC = 0; spc.RunOpcode(); spc.RunOpcode(); - + // Note: This is a simplified check. A full implementation would need to // mock memory to provide the opcodes to the SPC700. - + result.status = TestStatus::kPassed; result.error_message = "Basic SPC700 cycle counts appear correct."; } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = std::string("SPC700 cycle test exception: ") + e.what(); + result.error_message = + std::string("SPC700 cycle test exception: ") + e.what(); } result.duration = std::chrono::duration_cast( @@ -205,36 +224,43 @@ class EmulatorTestSuite : public TestSuite { try { emu::BreakpointManager bpm; - + // 1. Add an execution breakpoint - uint32_t bp_id = bpm.AddBreakpoint(0x8000, emu::BreakpointManager::Type::EXECUTE, emu::BreakpointManager::CpuType::CPU_65816); + uint32_t bp_id = + bpm.AddBreakpoint(0x8000, emu::BreakpointManager::Type::EXECUTE, + emu::BreakpointManager::CpuType::CPU_65816); if (bpm.GetAllBreakpoints().size() != 1) { throw std::runtime_error("Failed to add breakpoint."); } - + // 2. Test hit detection - if (!bpm.ShouldBreakOnExecute(0x8000, emu::BreakpointManager::CpuType::CPU_65816)) { + if (!bpm.ShouldBreakOnExecute( + 0x8000, emu::BreakpointManager::CpuType::CPU_65816)) { throw std::runtime_error("Execution breakpoint was not hit."); } - if (bpm.ShouldBreakOnExecute(0x8001, emu::BreakpointManager::CpuType::CPU_65816)) { + if (bpm.ShouldBreakOnExecute( + 0x8001, emu::BreakpointManager::CpuType::CPU_65816)) { throw std::runtime_error("Breakpoint hit at incorrect address."); } - + // 3. Test removal bpm.RemoveBreakpoint(bp_id); if (bpm.GetAllBreakpoints().size() != 0) { throw std::runtime_error("Failed to remove breakpoint."); } - if (bpm.ShouldBreakOnExecute(0x8000, emu::BreakpointManager::CpuType::CPU_65816)) { + if (bpm.ShouldBreakOnExecute( + 0x8000, emu::BreakpointManager::CpuType::CPU_65816)) { throw std::runtime_error("Breakpoint was hit after being removed."); } - + result.status = TestStatus::kPassed; - result.error_message = "BreakpointManager add, hit, and remove tests passed."; + result.error_message = + "BreakpointManager add, hit, and remove tests passed."; } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = std::string("BreakpointManager test exception: ") + e.what(); + result.error_message = + std::string("BreakpointManager test exception: ") + e.what(); } result.duration = std::chrono::duration_cast( @@ -259,37 +285,44 @@ class EmulatorTestSuite : public TestSuite { try { emu::WatchpointManager wpm; - + // 1. Add a write watchpoint on address $7E0010 with break enabled. - uint32_t wp_id = wpm.AddWatchpoint(0x7E0010, 0x7E0010, false, true, true, "Link HP"); - + uint32_t wp_id = + wpm.AddWatchpoint(0x7E0010, 0x7E0010, false, true, true, "Link HP"); + // 2. Simulate a write access and check if it breaks. - bool should_break = wpm.OnMemoryAccess(0x8000, 0x7E0010, true, 0x05, 0x06, 12345); + bool should_break = + wpm.OnMemoryAccess(0x8000, 0x7E0010, true, 0x05, 0x06, 12345); if (!should_break) { throw std::runtime_error("Write watchpoint did not trigger a break."); } - + // 3. Simulate a read access, which should not break. - should_break = wpm.OnMemoryAccess(0x8001, 0x7E0010, false, 0x06, 0x06, 12350); + should_break = + wpm.OnMemoryAccess(0x8001, 0x7E0010, false, 0x06, 0x06, 12350); if (should_break) { - throw std::runtime_error("Read access incorrectly triggered a write-only watchpoint."); + throw std::runtime_error( + "Read access incorrectly triggered a write-only watchpoint."); } - + // 4. Verify the write access was logged. auto history = wpm.GetHistory(0x7E0010); if (history.size() != 1) { - throw std::runtime_error("Memory access was not logged to watchpoint history."); + throw std::runtime_error( + "Memory access was not logged to watchpoint history."); } if (history[0].new_value != 0x06 || !history[0].is_write) { throw std::runtime_error("Logged access data is incorrect."); } - + result.status = TestStatus::kPassed; - result.error_message = "WatchpointManager logging and break-on-write tests passed."; + result.error_message = + "WatchpointManager logging and break-on-write tests passed."; } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = std::string("WatchpointManager test exception: ") + e.what(); + result.error_message = + std::string("WatchpointManager test exception: ") + e.what(); } result.duration = std::chrono::duration_cast( @@ -313,40 +346,48 @@ class EmulatorTestSuite : public TestSuite { result.timestamp = start_time; try { - auto backend = emu::audio::AudioBackendFactory::Create(emu::audio::AudioBackendFactory::BackendType::SDL2); - + auto backend = emu::audio::AudioBackendFactory::Create( + emu::audio::AudioBackendFactory::BackendType::SDL2); + // 1. Test initialization emu::audio::AudioConfig config; if (!backend->Initialize(config)) { throw std::runtime_error("Audio backend failed to initialize."); } if (!backend->IsInitialized()) { - throw std::runtime_error("IsInitialized() returned false after successful initialization."); + throw std::runtime_error( + "IsInitialized() returned false after successful initialization."); } - + // 2. Test state changes backend->Play(); if (!backend->GetStatus().is_playing) { - throw std::runtime_error("Backend is not playing after Play() was called."); + throw std::runtime_error( + "Backend is not playing after Play() was called."); } - + backend->Pause(); if (backend->GetStatus().is_playing) { - throw std::runtime_error("Backend is still playing after Pause() was called."); + throw std::runtime_error( + "Backend is still playing after Pause() was called."); } - + // 3. Test shutdown backend->Shutdown(); if (backend->IsInitialized()) { - throw std::runtime_error("IsInitialized() returned true after Shutdown()."); + throw std::runtime_error( + "IsInitialized() returned true after Shutdown()."); } - + result.status = TestStatus::kPassed; - result.error_message = "Audio backend Initialize, Play, Pause, and Shutdown states work correctly."; + result.error_message = + "Audio backend Initialize, Play, Pause, and Shutdown states work " + "correctly."; } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = std::string("Audio backend test exception: ") + e.what(); + result.error_message = + std::string("Audio backend test exception: ") + e.what(); } result.duration = std::chrono::duration_cast( diff --git a/src/app/test/integrated_test_suite.h b/src/app/test/integrated_test_suite.h index 696f2d20..3fe1b658 100644 --- a/src/app/test/integrated_test_suite.h +++ b/src/app/test/integrated_test_suite.h @@ -2,14 +2,14 @@ #define YAZE_APP_TEST_INTEGRATED_TEST_SUITE_H #include +#include #include #include -#include #include "absl/strings/str_format.h" -#include "app/test/test_manager.h" #include "app/gfx/arena.h" #include "app/rom.h" +#include "app/test/test_manager.h" #ifdef YAZE_ENABLE_GTEST #include @@ -31,13 +31,13 @@ class IntegratedTestSuite : public TestSuite { // Run Arena tests RunArenaIntegrityTest(results); RunArenaResourceManagementTest(results); - - // Run ROM tests + + // Run ROM tests RunRomBasicTest(results); - + // Run Graphics tests RunGraphicsValidationTest(results); - + return absl::OkStatus(); } @@ -46,7 +46,7 @@ class IntegratedTestSuite : public TestSuite { ImGui::Checkbox("Test Arena operations", &test_arena_); ImGui::Checkbox("Test ROM loading", &test_rom_); ImGui::Checkbox("Test graphics pipeline", &test_graphics_); - + if (ImGui::CollapsingHeader("ROM Test Settings")) { ImGui::InputText("Test ROM Path", test_rom_path_, sizeof(test_rom_path_)); ImGui::Checkbox("Skip ROM tests if file missing", &skip_missing_rom_); @@ -56,66 +56,68 @@ class IntegratedTestSuite : public TestSuite { private: void RunArenaIntegrityTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Arena_Integrity_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& arena = gfx::Arena::Get(); - + // Test basic Arena functionality size_t initial_textures = arena.GetTextureCount(); size_t initial_surfaces = arena.GetSurfaceCount(); - + // Verify Arena is properly initialized if (initial_textures >= 0 && initial_surfaces >= 0) { result.status = TestStatus::kPassed; - result.error_message = absl::StrFormat( - "Arena initialized: %zu textures, %zu surfaces", - initial_textures, initial_surfaces); + result.error_message = + absl::StrFormat("Arena initialized: %zu textures, %zu surfaces", + initial_textures, initial_surfaces); } else { result.status = TestStatus::kFailed; result.error_message = "Arena returned invalid resource counts"; } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Arena integrity test failed: " + std::string(e.what()); + result.error_message = + "Arena integrity test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunArenaResourceManagementTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Arena_Resource_Management_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& arena = gfx::Arena::Get(); - + size_t before_textures = arena.GetTextureCount(); size_t before_surfaces = arena.GetSurfaceCount(); - + // Test surface allocation (without renderer for now) // In a real test environment, we'd create a test renderer - + size_t after_textures = arena.GetTextureCount(); size_t after_surfaces = arena.GetSurfaceCount(); - + // Verify resource tracking works - if (after_textures >= before_textures && after_surfaces >= before_surfaces) { + if (after_textures >= before_textures && + after_surfaces >= before_surfaces) { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( "Resource tracking working: %zu→%zu textures, %zu→%zu surfaces", @@ -124,28 +126,29 @@ class IntegratedTestSuite : public TestSuite { result.status = TestStatus::kFailed; result.error_message = "Resource counting inconsistent"; } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Resource management test failed: " + std::string(e.what()); + result.error_message = + "Resource management test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunRomBasicTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Basic_Operations_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_rom_) { result.status = TestStatus::kSkipped; result.error_message = "ROM testing disabled in configuration"; @@ -153,12 +156,12 @@ class IntegratedTestSuite : public TestSuite { try { // First try to use currently loaded ROM from editor Rom* current_rom = TestManager::Get().GetCurrentRom(); - + if (current_rom && current_rom->is_loaded()) { // Test with currently loaded ROM result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( - "Current ROM validated: %s (%zu bytes)", + "Current ROM validated: %s (%zu bytes)", current_rom->title().c_str(), current_rom->size()); } else { // Fallback to loading ROM file @@ -167,49 +170,52 @@ class IntegratedTestSuite : public TestSuite { if (rom_path.empty()) { rom_path = "zelda3.sfc"; } - + if (std::filesystem::exists(rom_path)) { auto status = test_rom.LoadFromFile(rom_path); if (status.ok()) { result.status = TestStatus::kPassed; - result.error_message = absl::StrFormat( - "ROM loaded from file: %s (%zu bytes)", - test_rom.title().c_str(), test_rom.size()); + result.error_message = + absl::StrFormat("ROM loaded from file: %s (%zu bytes)", + test_rom.title().c_str(), test_rom.size()); } else { result.status = TestStatus::kFailed; - result.error_message = "ROM loading failed: " + std::string(status.message()); + result.error_message = + "ROM loading failed: " + std::string(status.message()); } } else if (skip_missing_rom_) { result.status = TestStatus::kSkipped; - result.error_message = "No current ROM and file not found: " + rom_path; + result.error_message = + "No current ROM and file not found: " + rom_path; } else { result.status = TestStatus::kFailed; - result.error_message = "No current ROM and required file not found: " + rom_path; + result.error_message = + "No current ROM and required file not found: " + rom_path; } } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; result.error_message = "ROM test failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunGraphicsValidationTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Graphics_Pipeline_Validation_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_graphics_) { result.status = TestStatus::kSkipped; result.error_message = "Graphics testing disabled in configuration"; @@ -217,36 +223,37 @@ class IntegratedTestSuite : public TestSuite { try { // Test basic graphics pipeline components auto& arena = gfx::Arena::Get(); - + // Test that graphics sheets can be accessed auto& gfx_sheets = arena.gfx_sheets(); - + // Basic validation if (gfx_sheets.size() == 223) { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( - "Graphics pipeline validated: %zu sheets available", + "Graphics pipeline validated: %zu sheets available", gfx_sheets.size()); } else { result.status = TestStatus::kFailed; result.error_message = absl::StrFormat( - "Graphics sheets count mismatch: expected 223, got %zu", + "Graphics sheets count mismatch: expected 223, got %zu", gfx_sheets.size()); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Graphics validation failed: " + std::string(e.what()); + result.error_message = + "Graphics validation failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + // Configuration bool test_arena_ = true; bool test_rom_ = true; @@ -262,13 +269,15 @@ class PerformanceTestSuite : public TestSuite { ~PerformanceTestSuite() override = default; std::string GetName() const override { return "Performance Tests"; } - TestCategory GetCategory() const override { return TestCategory::kPerformance; } + TestCategory GetCategory() const override { + return TestCategory::kPerformance; + } absl::Status RunTests(TestResults& results) override { RunFrameRateTest(results); RunMemoryUsageTest(results); RunResourceLeakTest(results); - + return absl::OkStatus(); } @@ -282,140 +291,144 @@ class PerformanceTestSuite : public TestSuite { private: void RunFrameRateTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Frame_Rate_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { // Sample current frame rate float current_fps = ImGui::GetIO().Framerate; - + if (current_fps >= target_fps_) { result.status = TestStatus::kPassed; - result.error_message = absl::StrFormat( - "Frame rate acceptable: %.1f FPS (target: %.1f)", - current_fps, target_fps_); + result.error_message = + absl::StrFormat("Frame rate acceptable: %.1f FPS (target: %.1f)", + current_fps, target_fps_); } else { result.status = TestStatus::kFailed; - result.error_message = absl::StrFormat( - "Frame rate below target: %.1f FPS (target: %.1f)", - current_fps, target_fps_); + result.error_message = + absl::StrFormat("Frame rate below target: %.1f FPS (target: %.1f)", + current_fps, target_fps_); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; result.error_message = "Frame rate test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunMemoryUsageTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Memory_Usage_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& arena = gfx::Arena::Get(); - + // Estimate memory usage based on resource counts size_t texture_count = arena.GetTextureCount(); size_t surface_count = arena.GetSurfaceCount(); - + // Rough estimation: each texture/surface ~1KB average size_t estimated_memory_kb = (texture_count + surface_count); size_t estimated_memory_mb = estimated_memory_kb / 1024; - + if (static_cast(estimated_memory_mb) <= max_memory_mb_) { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( - "Memory usage acceptable: ~%zu MB (%zu textures, %zu surfaces)", + "Memory usage acceptable: ~%zu MB (%zu textures, %zu surfaces)", estimated_memory_mb, texture_count, surface_count); } else { result.status = TestStatus::kFailed; - result.error_message = absl::StrFormat( - "Memory usage high: ~%zu MB (limit: %d MB)", - estimated_memory_mb, max_memory_mb_); + result.error_message = + absl::StrFormat("Memory usage high: ~%zu MB (limit: %d MB)", + estimated_memory_mb, max_memory_mb_); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Memory usage test failed: " + std::string(e.what()); + result.error_message = + "Memory usage test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunResourceLeakTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Resource_Leak_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& arena = gfx::Arena::Get(); - + // Get baseline resource counts size_t baseline_textures = arena.GetTextureCount(); size_t baseline_surfaces = arena.GetSurfaceCount(); - + // Simulate some operations (this would be more comprehensive with actual workload) // For now, just verify resource counts remain stable - + size_t final_textures = arena.GetTextureCount(); size_t final_surfaces = arena.GetSurfaceCount(); - + // Check for unexpected resource growth - size_t texture_diff = final_textures > baseline_textures ? - final_textures - baseline_textures : 0; - size_t surface_diff = final_surfaces > baseline_surfaces ? - final_surfaces - baseline_surfaces : 0; - + size_t texture_diff = final_textures > baseline_textures + ? final_textures - baseline_textures + : 0; + size_t surface_diff = final_surfaces > baseline_surfaces + ? final_surfaces - baseline_surfaces + : 0; + if (texture_diff == 0 && surface_diff == 0) { result.status = TestStatus::kPassed; result.error_message = "No resource leaks detected"; } else if (texture_diff < 10 && surface_diff < 10) { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( - "Minor resource growth: +%zu textures, +%zu surfaces (acceptable)", + "Minor resource growth: +%zu textures, +%zu surfaces (acceptable)", texture_diff, surface_diff); } else { result.status = TestStatus::kFailed; result.error_message = absl::StrFormat( - "Potential resource leak: +%zu textures, +%zu surfaces", + "Potential resource leak: +%zu textures, +%zu surfaces", texture_diff, surface_diff); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Resource leak test failed: " + std::string(e.what()); + result.error_message = + "Resource leak test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + // Configuration bool test_arena_ = true; bool test_rom_ = true; @@ -452,7 +465,7 @@ class UITestSuite : public TestSuite { result.timestamp = std::chrono::steady_clock::now(); results.AddResult(result); #endif - + return absl::OkStatus(); } @@ -464,8 +477,8 @@ class UITestSuite : public TestSuite { ImGui::Checkbox("Test dashboard UI", &test_dashboard_); ImGui::InputFloat("UI interaction delay (ms)", &interaction_delay_ms_); #else - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), - "UI tests not available - ImGui Test Engine disabled"); + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), + "UI tests not available - ImGui Test Engine disabled"); #endif } @@ -473,13 +486,13 @@ class UITestSuite : public TestSuite { #ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE void RunMenuInteractionTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Menu_Interaction_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto* engine = TestManager::Get().GetUITestEngine(); if (engine) { @@ -493,45 +506,46 @@ class UITestSuite : public TestSuite { } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Menu interaction test failed: " + std::string(e.what()); + result.error_message = + "Menu interaction test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunDialogTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Dialog_Workflow_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + // Placeholder for dialog testing result.status = TestStatus::kSkipped; result.error_message = "Dialog testing not yet implemented"; - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunTestDashboardTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Test_Dashboard_UI_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + // Test that the dashboard can be accessed and drawn try { // The fact that we're running this test means the dashboard is working @@ -541,14 +555,14 @@ class UITestSuite : public TestSuite { result.status = TestStatus::kFailed; result.error_message = "Dashboard test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + bool test_menus_ = true; bool test_dialogs_ = true; bool test_dashboard_ = true; diff --git a/src/app/test/rom_dependent_test_suite.h b/src/app/test/rom_dependent_test_suite.h index 9eb8b1b7..893e21d6 100644 --- a/src/app/test/rom_dependent_test_suite.h +++ b/src/app/test/rom_dependent_test_suite.h @@ -5,11 +5,11 @@ #include #include "absl/strings/str_format.h" -#include "app/test/test_manager.h" -#include "app/rom.h" -#include "zelda3/overworld/overworld.h" #include "app/editor/overworld/tile16_editor.h" #include "app/gui/core/icons.h" +#include "app/rom.h" +#include "app/test/test_manager.h" +#include "zelda3/overworld/overworld.h" namespace yaze { namespace test { @@ -21,111 +21,118 @@ class RomDependentTestSuite : public TestSuite { ~RomDependentTestSuite() override = default; std::string GetName() const override { return "ROM-Dependent Tests"; } - TestCategory GetCategory() const override { return TestCategory::kIntegration; } + TestCategory GetCategory() const override { + return TestCategory::kIntegration; + } absl::Status RunTests(TestResults& results) override { Rom* current_rom = TestManager::Get().GetCurrentRom(); - + // Add detailed ROM availability check TestResult rom_check_result; rom_check_result.name = "ROM_Available_Check"; rom_check_result.suite_name = GetName(); rom_check_result.category = GetCategory(); rom_check_result.timestamp = std::chrono::steady_clock::now(); - + if (!current_rom) { rom_check_result.status = TestStatus::kSkipped; rom_check_result.error_message = "ROM pointer is null"; } else if (!current_rom->is_loaded()) { rom_check_result.status = TestStatus::kSkipped; - rom_check_result.error_message = absl::StrFormat( - "ROM not loaded (ptr: %p, title: '%s', size: %zu)", - (void*)current_rom, current_rom->title().c_str(), current_rom->size()); + rom_check_result.error_message = + absl::StrFormat("ROM not loaded (ptr: %p, title: '%s', size: %zu)", + (void*)current_rom, current_rom->title().c_str(), + current_rom->size()); } else { rom_check_result.status = TestStatus::kPassed; rom_check_result.error_message = absl::StrFormat( - "ROM loaded successfully (title: '%s', size: %.2f MB)", + "ROM loaded successfully (title: '%s', size: %.2f MB)", current_rom->title().c_str(), current_rom->size() / 1048576.0f); } - + rom_check_result.duration = std::chrono::milliseconds{0}; results.AddResult(rom_check_result); - + // If no ROM is available, skip other tests if (!current_rom || !current_rom->is_loaded()) { return absl::OkStatus(); } - + // Run ROM-dependent tests (only if enabled) auto& test_manager = TestManager::Get(); - + if (test_manager.IsTestEnabled("ROM_Header_Validation_Test")) { RunRomHeaderValidationTest(results, current_rom); } else { - AddSkippedTest(results, "ROM_Header_Validation_Test", "Test disabled by user"); + AddSkippedTest(results, "ROM_Header_Validation_Test", + "Test disabled by user"); } - + if (test_manager.IsTestEnabled("ROM_Data_Access_Test")) { RunRomDataAccessTest(results, current_rom); } else { AddSkippedTest(results, "ROM_Data_Access_Test", "Test disabled by user"); } - + if (test_manager.IsTestEnabled("ROM_Graphics_Extraction_Test")) { RunRomGraphicsExtractionTest(results, current_rom); } else { - AddSkippedTest(results, "ROM_Graphics_Extraction_Test", "Test disabled by user"); + AddSkippedTest(results, "ROM_Graphics_Extraction_Test", + "Test disabled by user"); } - + if (test_manager.IsTestEnabled("ROM_Overworld_Loading_Test")) { RunRomOverworldLoadingTest(results, current_rom); } else { - AddSkippedTest(results, "ROM_Overworld_Loading_Test", "Test disabled by user"); + AddSkippedTest(results, "ROM_Overworld_Loading_Test", + "Test disabled by user"); } - + if (test_manager.IsTestEnabled("Tile16_Editor_Test")) { RunTile16EditorTest(results, current_rom); } else { AddSkippedTest(results, "Tile16_Editor_Test", "Test disabled by user"); } - + if (test_manager.IsTestEnabled("Comprehensive_Save_Test")) { RunComprehensiveSaveTest(results, current_rom); } else { - AddSkippedTest(results, "Comprehensive_Save_Test", "Test disabled by user (known to crash)"); + AddSkippedTest(results, "Comprehensive_Save_Test", + "Test disabled by user (known to crash)"); } - + if (test_advanced_features_) { RunRomSpriteDataTest(results, current_rom); RunRomMusicDataTest(results, current_rom); } - + return absl::OkStatus(); } void DrawConfiguration() override { Rom* current_rom = TestManager::Get().GetCurrentRom(); - + ImGui::Text("%s ROM-Dependent Test Configuration", ICON_MD_STORAGE); - + if (current_rom && current_rom->is_loaded()) { - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), - "%s Current ROM: %s", ICON_MD_CHECK_CIRCLE, current_rom->title().c_str()); + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s Current ROM: %s", + ICON_MD_CHECK_CIRCLE, current_rom->title().c_str()); ImGui::Text("Size: %zu bytes", current_rom->size()); ImGui::Text("File: %s", current_rom->filename().c_str()); } else { - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), - "%s No ROM currently loaded", ICON_MD_WARNING); + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), + "%s No ROM currently loaded", ICON_MD_WARNING); ImGui::Text("Load a ROM in the editor to enable ROM-dependent tests"); } - + ImGui::Separator(); ImGui::Checkbox("Test ROM header validation", &test_header_validation_); ImGui::Checkbox("Test ROM data access", &test_data_access_); ImGui::Checkbox("Test graphics extraction", &test_graphics_extraction_); ImGui::Checkbox("Test overworld loading", &test_overworld_loading_); ImGui::Checkbox("Test advanced features", &test_advanced_features_); - + if (test_advanced_features_) { ImGui::Indent(); ImGui::Checkbox("Test sprite data", &test_sprite_data_); @@ -133,10 +140,11 @@ class RomDependentTestSuite : public TestSuite { ImGui::Unindent(); } } - + private: // Helper method to add skipped test results - void AddSkippedTest(TestResults& results, const std::string& test_name, const std::string& reason) { + void AddSkippedTest(TestResults& results, const std::string& test_name, + const std::string& reason) { TestResult result; result.name = test_name; result.suite_name = GetName(); @@ -147,16 +155,16 @@ class RomDependentTestSuite : public TestSuite { result.timestamp = std::chrono::steady_clock::now(); results.AddResult(result); } - + void RunRomHeaderValidationTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Header_Validation_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_header_validation_) { result.status = TestStatus::kSkipped; result.error_message = "Header validation disabled in configuration"; @@ -164,11 +172,13 @@ class RomDependentTestSuite : public TestSuite { try { std::string title = rom->title(); size_t size = rom->size(); - + // Basic validation - bool valid_title = !title.empty() && title != "ZELDA3" && title.length() <= 21; - bool valid_size = size >= 1024*1024 && size <= 8*1024*1024; // 1MB to 8MB - + bool valid_title = + !title.empty() && title != "ZELDA3" && title.length() <= 21; + bool valid_size = + size >= 1024 * 1024 && size <= 8 * 1024 * 1024; // 1MB to 8MB + if (valid_title && valid_size) { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( @@ -176,30 +186,32 @@ class RomDependentTestSuite : public TestSuite { } else { result.status = TestStatus::kFailed; result.error_message = absl::StrFormat( - "ROM header validation failed: title='%s' size=%zu", title.c_str(), size); + "ROM header validation failed: title='%s' size=%zu", + title.c_str(), size); } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Header validation failed: " + std::string(e.what()); + result.error_message = + "Header validation failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunRomDataAccessTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Data_Access_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_data_access_) { result.status = TestStatus::kSkipped; result.error_message = "Data access testing disabled in configuration"; @@ -208,7 +220,7 @@ class RomDependentTestSuite : public TestSuite { // Test basic ROM data access patterns size_t bytes_tested = 0; bool access_success = true; - + // Test reading from various ROM regions try { [[maybe_unused]] auto header_byte = rom->ReadByte(0x7FC0); @@ -220,7 +232,7 @@ class RomDependentTestSuite : public TestSuite { } catch (...) { access_success = false; } - + if (access_success && bytes_tested >= 3) { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( @@ -231,29 +243,31 @@ class RomDependentTestSuite : public TestSuite { } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Data access test failed: " + std::string(e.what()); + result.error_message = + "Data access test failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunRomGraphicsExtractionTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Graphics_Extraction_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_graphics_extraction_) { result.status = TestStatus::kSkipped; - result.error_message = "Graphics extraction testing disabled in configuration"; + result.error_message = + "Graphics extraction testing disabled in configuration"; } else { try { auto graphics_result = LoadAllGraphicsData(*rom); @@ -265,75 +279,81 @@ class RomDependentTestSuite : public TestSuite { loaded_sheets++; } } - + result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( - "Graphics extraction successful: %zu/%zu sheets loaded", + "Graphics extraction successful: %zu/%zu sheets loaded", loaded_sheets, sheets.size()); } else { result.status = TestStatus::kFailed; - result.error_message = "Graphics extraction failed: " + - std::string(graphics_result.status().message()); + result.error_message = + "Graphics extraction failed: " + + std::string(graphics_result.status().message()); } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Graphics extraction test failed: " + std::string(e.what()); + result.error_message = + "Graphics extraction test failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunRomOverworldLoadingTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Overworld_Loading_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_overworld_loading_) { result.status = TestStatus::kSkipped; - result.error_message = "Overworld loading testing disabled in configuration"; + result.error_message = + "Overworld loading testing disabled in configuration"; } else { try { zelda3::Overworld overworld(rom); auto ow_status = overworld.Load(rom); - + if (ow_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Overworld loading successful from current ROM"; + result.error_message = + "Overworld loading successful from current ROM"; } else { result.status = TestStatus::kFailed; - result.error_message = "Overworld loading failed: " + std::string(ow_status.message()); + result.error_message = + "Overworld loading failed: " + std::string(ow_status.message()); } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Overworld loading test failed: " + std::string(e.what()); + result.error_message = + "Overworld loading test failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunRomSpriteDataTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Sprite_Data_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_sprite_data_) { result.status = TestStatus::kSkipped; result.error_message = "Sprite data testing disabled in configuration"; @@ -345,26 +365,27 @@ class RomDependentTestSuite : public TestSuite { result.error_message = "Sprite data testing not yet implemented"; } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Sprite data test failed: " + std::string(e.what()); + result.error_message = + "Sprite data test failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunRomMusicDataTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "ROM_Music_Data_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + if (!test_music_data_) { result.status = TestStatus::kSkipped; result.error_message = "Music data testing disabled in configuration"; @@ -376,64 +397,70 @@ class RomDependentTestSuite : public TestSuite { result.error_message = "Music data testing not yet implemented"; } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Music data test failed: " + std::string(e.what()); + result.error_message = + "Music data test failed: " + std::string(e.what()); } } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunTile16EditorTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Tile16_Editor_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { // Verify ROM and palette data if (rom->palette_group().overworld_main.size() > 0) { // Test Tile16 editor functionality with real ROM data editor::Tile16Editor tile16_editor(rom, nullptr); - + // Create test bitmaps with realistic data - std::vector test_blockset_data(256 * 8192, 1); // Tile16 blockset size - std::vector test_gfx_data(256 * 256, 1); // Area graphics size - + std::vector test_blockset_data(256 * 8192, + 1); // Tile16 blockset size + std::vector test_gfx_data(256 * 256, 1); // Area graphics size + gfx::Bitmap test_blockset_bmp, test_gfx_bmp; test_blockset_bmp.Create(256, 8192, 8, test_blockset_data); test_gfx_bmp.Create(256, 256, 8, test_gfx_data); - + // Set realistic palettes if (rom->palette_group().overworld_main.size() > 0) { test_blockset_bmp.SetPalette(rom->palette_group().overworld_main[0]); test_gfx_bmp.SetPalette(rom->palette_group().overworld_main[0]); } - + std::array tile_types{}; - + // Test initialization - auto init_status = tile16_editor.Initialize(test_blockset_bmp, test_gfx_bmp, tile_types); + auto init_status = tile16_editor.Initialize(test_blockset_bmp, + test_gfx_bmp, tile_types); if (!init_status.ok()) { result.status = TestStatus::kFailed; - result.error_message = "Tile16Editor initialization failed: " + init_status.ToString(); + result.error_message = + "Tile16Editor initialization failed: " + init_status.ToString(); } else { // Test setting a tile auto set_tile_status = tile16_editor.SetCurrentTile(0); if (!set_tile_status.ok()) { result.status = TestStatus::kFailed; - result.error_message = "SetCurrentTile failed: " + set_tile_status.ToString(); + result.error_message = + "SetCurrentTile failed: " + set_tile_status.ToString(); } else { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( "Tile16Editor working correctly (ROM: %s, Palette groups: %zu)", - rom->title().c_str(), rom->palette_group().overworld_main.size()); + rom->title().c_str(), + rom->palette_group().overworld_main.size()); } } } else { @@ -442,90 +469,96 @@ class RomDependentTestSuite : public TestSuite { } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Tile16Editor test exception: " + std::string(e.what()); + result.error_message = + "Tile16Editor test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunComprehensiveSaveTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Comprehensive_Save_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { // Test comprehensive save functionality using ROM copy auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Test overworld modifications on the copy - zelda3::Overworld overworld(test_rom); - auto load_status = overworld.Load(test_rom); - if (!load_status.ok()) { - return load_status; - } - - // Make modifications to the copy - auto* test_map = overworld.mutable_overworld_map(0); - uint8_t original_gfx = test_map->area_graphics(); - test_map->set_area_graphics(0x01); // Change to a different graphics set - - // Test save operations - auto save_maps_status = overworld.SaveOverworldMaps(); - auto save_props_status = overworld.SaveMapProperties(); - - if (!save_maps_status.ok()) { - return save_maps_status; - } - if (!save_props_status.ok()) { - return save_props_status; - } - - // Save the test ROM with timestamp - Rom::SaveSettings settings; - settings.backup = false; - settings.save_new = true; - settings.filename = test_manager.GenerateTestRomFilename(test_rom->title()); - - auto save_file_status = test_rom->SaveToFile(settings); - if (!save_file_status.ok()) { - return save_file_status; - } - - // Offer to open test ROM in new session - test_manager.OfferTestSessionCreation(settings.filename); - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Test overworld modifications on the copy + zelda3::Overworld overworld(test_rom); + auto load_status = overworld.Load(test_rom); + if (!load_status.ok()) { + return load_status; + } + + // Make modifications to the copy + auto* test_map = overworld.mutable_overworld_map(0); + uint8_t original_gfx = test_map->area_graphics(); + test_map->set_area_graphics( + 0x01); // Change to a different graphics set + + // Test save operations + auto save_maps_status = overworld.SaveOverworldMaps(); + auto save_props_status = overworld.SaveMapProperties(); + + if (!save_maps_status.ok()) { + return save_maps_status; + } + if (!save_props_status.ok()) { + return save_props_status; + } + + // Save the test ROM with timestamp + Rom::SaveSettings settings; + settings.backup = false; + settings.save_new = true; + settings.filename = + test_manager.GenerateTestRomFilename(test_rom->title()); + + auto save_file_status = test_rom->SaveToFile(settings); + if (!save_file_status.ok()) { + return save_file_status; + } + + // Offer to open test ROM in new session + test_manager.OfferTestSessionCreation(settings.filename); + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Comprehensive save test completed successfully using ROM copy"; + result.error_message = + "Comprehensive save test completed successfully using ROM copy"; } else { result.status = TestStatus::kFailed; result.error_message = "Save test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Comprehensive save test exception: " + std::string(e.what()); + result.error_message = + "Comprehensive save test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + // Configuration bool test_header_validation_ = true; bool test_data_access_ = true; diff --git a/src/app/test/test_manager.cc b/src/app/test/test_manager.cc index 692bb895..92eb38b5 100644 --- a/src/app/test/test_manager.cc +++ b/src/app/test/test_manager.cc @@ -2,8 +2,8 @@ #include #include -#include #include +#include #include "absl/status/statusor.h" #include "absl/strings/match.h" @@ -13,12 +13,12 @@ #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "app/service/screenshot_utils.h" +#include "app/gfx/resource/arena.h" #include "app/gui/automation/widget_state_capture.h" +#include "app/gui/core/icons.h" +#include "app/service/screenshot_utils.h" #include "core/features.h" #include "util/file_util.h" -#include "app/gfx/resource/arena.h" -#include "app/gui/core/icons.h" #if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE #include "imgui.h" #include "imgui_internal.h" @@ -44,17 +44,15 @@ namespace test { namespace { std::string GenerateFailureScreenshotPath(const std::string& test_id) { - std::filesystem::path base_dir = - std::filesystem::temp_directory_path() / "yaze" / "test-results" / - test_id; + std::filesystem::path base_dir = std::filesystem::temp_directory_path() / + "yaze" / "test-results" / test_id; std::error_code ec; std::filesystem::create_directories(base_dir, ec); const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now()); std::filesystem::path file_path = - base_dir / - std::filesystem::path(absl::StrFormat( - "failure_%lld.bmp", static_cast(timestamp_ms))); + base_dir / std::filesystem::path(absl::StrFormat( + "failure_%lld.bmp", static_cast(timestamp_ms))); return file_path.string(); } @@ -490,7 +488,8 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { ImGui::SameLine(); if (ImGui::Button("Debug ROM State")) { LOG_INFO("TestManager", "=== ROM DEBUG INFO ==="); - LOG_INFO("TestManager", "current_rom_ pointer: %p", (void*)current_rom_); + LOG_INFO("TestManager", "current_rom_ pointer: %p", + (void*)current_rom_); if (current_rom_) { LOG_INFO("TestManager", "ROM title: '%s'", current_rom_->title().c_str()); @@ -777,92 +776,94 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { if (ImGui::BeginTabItem("Test Results")) { if (ImGui::BeginChild("TestResults", ImVec2(0, 0), true)) { if (last_results_.individual_results.empty()) { - ImGui::TextColored( - ImVec4(0.6f, 0.6f, 0.6f, 1.0f), - "No test results to display. Run some tests to see results here."); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), + "No test results to display. Run some tests to " + "see results here."); } else { - for (const auto& result : last_results_.individual_results) { - // Apply filters - bool category_match = - (selected_category == 0) || (result.category == category_filter_); - bool text_match = - test_filter_.empty() || - result.name.find(test_filter_) != std::string::npos || - result.suite_name.find(test_filter_) != std::string::npos; + for (const auto& result : last_results_.individual_results) { + // Apply filters + bool category_match = (selected_category == 0) || + (result.category == category_filter_); + bool text_match = + test_filter_.empty() || + result.name.find(test_filter_) != std::string::npos || + result.suite_name.find(test_filter_) != std::string::npos; - if (!category_match || !text_match) { - continue; + if (!category_match || !text_match) { + continue; + } + + ImGui::PushID(&result); + + // Status icon and test name + const char* status_icon = ICON_MD_HELP; + switch (result.status) { + case TestStatus::kPassed: + status_icon = ICON_MD_CHECK_CIRCLE; + break; + case TestStatus::kFailed: + status_icon = ICON_MD_ERROR; + break; + case TestStatus::kSkipped: + status_icon = ICON_MD_SKIP_NEXT; + break; + case TestStatus::kRunning: + status_icon = ICON_MD_PLAY_CIRCLE_FILLED; + break; + default: + break; + } + + ImGui::TextColored(GetTestStatusColor(result.status), "%s %s::%s", + status_icon, result.suite_name.c_str(), + result.name.c_str()); + + // Show duration and timestamp on same line if space allows + if (ImGui::GetContentRegionAvail().x > 200) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(%lld ms)", + result.duration.count()); + } + + // Show detailed information for failed tests + if (result.status == TestStatus::kFailed && + !result.error_message.empty()) { + ImGui::Indent(); + ImGui::PushStyleColor(ImGuiCol_Text, + ImVec4(1.0f, 0.8f, 0.8f, 1.0f)); + ImGui::TextWrapped("%s %s", ICON_MD_ERROR_OUTLINE, + result.error_message.c_str()); + ImGui::PopStyleColor(); + ImGui::Unindent(); + } + + // Show additional info for passed tests if they have messages + if (result.status == TestStatus::kPassed && + !result.error_message.empty()) { + ImGui::Indent(); + ImGui::PushStyleColor(ImGuiCol_Text, + ImVec4(0.8f, 1.0f, 0.8f, 1.0f)); + ImGui::TextWrapped("%s %s", ICON_MD_INFO, + result.error_message.c_str()); + ImGui::PopStyleColor(); + ImGui::Unindent(); + } + + ImGui::PopID(); + } } - - ImGui::PushID(&result); - - // Status icon and test name - const char* status_icon = ICON_MD_HELP; - switch (result.status) { - case TestStatus::kPassed: - status_icon = ICON_MD_CHECK_CIRCLE; - break; - case TestStatus::kFailed: - status_icon = ICON_MD_ERROR; - break; - case TestStatus::kSkipped: - status_icon = ICON_MD_SKIP_NEXT; - break; - case TestStatus::kRunning: - status_icon = ICON_MD_PLAY_CIRCLE_FILLED; - break; - default: - break; - } - - ImGui::TextColored(GetTestStatusColor(result.status), "%s %s::%s", - status_icon, result.suite_name.c_str(), - result.name.c_str()); - - // Show duration and timestamp on same line if space allows - if (ImGui::GetContentRegionAvail().x > 200) { - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(%lld ms)", - result.duration.count()); - } - - // Show detailed information for failed tests - if (result.status == TestStatus::kFailed && - !result.error_message.empty()) { - ImGui::Indent(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.8f, 1.0f)); - ImGui::TextWrapped("%s %s", ICON_MD_ERROR_OUTLINE, - result.error_message.c_str()); - ImGui::PopStyleColor(); - ImGui::Unindent(); - } - - // Show additional info for passed tests if they have messages - if (result.status == TestStatus::kPassed && - !result.error_message.empty()) { - ImGui::Indent(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 1.0f, 0.8f, 1.0f)); - ImGui::TextWrapped("%s %s", ICON_MD_INFO, - result.error_message.c_str()); - ImGui::PopStyleColor(); - ImGui::Unindent(); - } - - ImGui::PopID(); } - } - } - ImGui::EndChild(); + ImGui::EndChild(); ImGui::EndTabItem(); } - + // Harness Test Results tab (for gRPC GUI automation tests) #if defined(YAZE_WITH_GRPC) if (ImGui::BeginTabItem("GUI Automation Tests")) { if (ImGui::BeginChild("HarnessTests", ImVec2(0, 0), true)) { // Display harness test summaries auto summaries = ListHarnessTestSummaries(); - + if (summaries.empty()) { ImGui::TextColored( ImVec4(0.6f, 0.6f, 0.6f, 1.0f), @@ -873,31 +874,37 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { ImGui::Text("%s GUI Automation Test History", ICON_MD_HISTORY); ImGui::Text("Total Tests: %zu", summaries.size()); ImGui::Separator(); - + // Table of harness test results - if (ImGui::BeginTable("HarnessTestTable", 6, - ImGuiTableFlags_Borders | - ImGuiTableFlags_RowBg | - ImGuiTableFlags_Resizable)) { - ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80); - ImGui::TableSetupColumn("Test Name", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, 100); - ImGui::TableSetupColumn("Runs", ImGuiTableColumnFlags_WidthFixed, 60); - ImGui::TableSetupColumn("Pass Rate", ImGuiTableColumnFlags_WidthFixed, 80); - ImGui::TableSetupColumn("Duration", ImGuiTableColumnFlags_WidthFixed, 80); + if (ImGui::BeginTable("HarnessTestTable", 6, + ImGuiTableFlags_Borders | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable)) { + ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, + 80); + ImGui::TableSetupColumn("Test Name", + ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Category", + ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableSetupColumn("Runs", ImGuiTableColumnFlags_WidthFixed, + 60); + ImGui::TableSetupColumn("Pass Rate", + ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("Duration", + ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableHeadersRow(); - + for (const auto& summary : summaries) { const auto& exec = summary.latest_execution; - + ImGui::TableNextRow(); ImGui::TableNextColumn(); - + // Status indicator ImVec4 status_color; const char* status_icon; const char* status_text; - + switch (exec.status) { case HarnessTestStatus::kPassed: status_color = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); @@ -930,52 +937,56 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { status_text = "Unknown"; break; } - - ImGui::TextColored(status_color, "%s %s", status_icon, status_text); - + + ImGui::TextColored(status_color, "%s %s", status_icon, + status_text); + ImGui::TableNextColumn(); ImGui::Text("%s", exec.name.c_str()); - + // Show error message if failed - if (exec.status == HarnessTestStatus::kFailed && + if (exec.status == HarnessTestStatus::kFailed && !exec.error_message.empty()) { ImGui::SameLine(); - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), - "(%s)", exec.error_message.c_str()); + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), "(%s)", + exec.error_message.c_str()); } - + ImGui::TableNextColumn(); ImGui::Text("%s", exec.category.c_str()); - + ImGui::TableNextColumn(); ImGui::Text("%d", summary.total_runs); - + ImGui::TableNextColumn(); if (summary.total_runs > 0) { - float pass_rate = static_cast(summary.pass_count) / - summary.total_runs; - ImVec4 rate_color = pass_rate >= 0.9f ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) - : pass_rate >= 0.7f ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) - : ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + float pass_rate = + static_cast(summary.pass_count) / summary.total_runs; + ImVec4 rate_color = + pass_rate >= 0.9f ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) + : pass_rate >= 0.7f ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) + : ImVec4(1.0f, 0.0f, 0.0f, 1.0f); ImGui::TextColored(rate_color, "%.0f%%", pass_rate * 100.0f); } else { ImGui::Text("-"); } - + ImGui::TableNextColumn(); - double duration_ms = absl::ToDoubleMilliseconds(summary.total_duration); + double duration_ms = + absl::ToDoubleMilliseconds(summary.total_duration); if (summary.total_runs > 0) { ImGui::Text("%.0f ms", duration_ms / summary.total_runs); } else { ImGui::Text("-"); } - + // Expandable details if (ImGui::TreeNode(("Details##" + exec.test_id).c_str())) { ImGui::Text("Test ID: %s", exec.test_id.c_str()); - ImGui::Text("Total Runs: %d (Pass: %d, Fail: %d)", - summary.total_runs, summary.pass_count, summary.fail_count); - + ImGui::Text("Total Runs: %d (Pass: %d, Fail: %d)", + summary.total_runs, summary.pass_count, + summary.fail_count); + if (!exec.logs.empty()) { ImGui::Separator(); ImGui::Text("%s Logs:", ICON_MD_DESCRIPTION); @@ -983,28 +994,28 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { ImGui::BulletText("%s", log.c_str()); } } - + if (!exec.assertion_failures.empty()) { ImGui::Separator(); - ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), - "%s Assertion Failures:", ICON_MD_ERROR); + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), + "%s Assertion Failures:", ICON_MD_ERROR); for (const auto& failure : exec.assertion_failures) { ImGui::BulletText("%s", failure.c_str()); } } - + if (!exec.screenshot_path.empty()) { ImGui::Separator(); - ImGui::Text("%s Screenshot: %s", - ICON_MD_CAMERA_ALT, exec.screenshot_path.c_str()); - ImGui::Text("Size: %.2f KB", - exec.screenshot_size_bytes / 1024.0); + ImGui::Text("%s Screenshot: %s", ICON_MD_CAMERA_ALT, + exec.screenshot_path.c_str()); + ImGui::Text("Size: %.2f KB", + exec.screenshot_size_bytes / 1024.0); } - + ImGui::TreePop(); } } - + ImGui::EndTable(); } } @@ -1013,7 +1024,7 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { ImGui::EndTabItem(); } #endif // defined(YAZE_WITH_GRPC) - + ImGui::EndTabBar(); } @@ -1213,9 +1224,8 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { if (ImGui::Button("Test Current File Dialog")) { // Test the current file dialog implementation LOG_INFO("TestManager", "Testing global file dialog mode: %s", - core::FeatureFlags::get().kUseNativeFileDialog - ? "NFD" - : "Bespoke"); + core::FeatureFlags::get().kUseNativeFileDialog ? "NFD" + : "Bespoke"); // Actually test the file dialog auto result = util::FileDialogWrapper::ShowOpenFileDialog(); @@ -1234,9 +1244,8 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { if (!result.empty()) { LOG_INFO("TestManager", "NFD test successful: %s", result.c_str()); } else { - LOG_INFO( - "TestManager", - "NFD test: No file selected, canceled, or error occurred"); + LOG_INFO("TestManager", + "NFD test: No file selected, canceled, or error occurred"); } } @@ -1365,7 +1374,8 @@ void TestManager::DrawTestDashboard(bool* show_dashboard) { for (const auto& [test_name, description] : known_tests) { EnableTest(test_name); } - LOG_INFO("TestManager", "Enabled all tests (including dangerous ones)"); + LOG_INFO("TestManager", + "Enabled all tests (including dangerous ones)"); } ImGui::SameLine(); @@ -1475,8 +1485,7 @@ void TestManager::RefreshCurrentRom() { LOG_INFO("TestManager", "ROM is_loaded(): %s", current_rom_->is_loaded() ? "true" : "false"); if (current_rom_->is_loaded()) { - LOG_INFO("TestManager", "ROM title: '%s'", - current_rom_->title().c_str()); + LOG_INFO("TestManager", "ROM title: '%s'", current_rom_->title().c_str()); LOG_INFO("TestManager", "ROM size: %.2f MB", current_rom_->size() / 1048576.0f); LOG_INFO("TestManager", "ROM dirty: %s", @@ -1484,15 +1493,14 @@ void TestManager::RefreshCurrentRom() { } } else { LOG_INFO("TestManager", "TestManager ROM pointer is null"); - LOG_INFO( - "TestManager", - "Note: ROM should be set by EditorManager when ROM is loaded"); + LOG_INFO("TestManager", + "Note: ROM should be set by EditorManager when ROM is loaded"); } LOG_INFO("TestManager", "==============================="); } -absl::Status TestManager::CreateTestRomCopy( - Rom* source_rom, std::unique_ptr& test_rom) { +absl::Status TestManager::CreateTestRomCopy(Rom* source_rom, + std::unique_ptr& test_rom) { if (!source_rom || !source_rom->is_loaded()) { return absl::FailedPreconditionError("Source ROM not loaded"); } @@ -1515,8 +1523,7 @@ absl::Status TestManager::CreateTestRomCopy( return absl::OkStatus(); } -std::string TestManager::GenerateTestRomFilename( - const std::string& base_name) { +std::string TestManager::GenerateTestRomFilename(const std::string& base_name) { // Generate filename with timestamp auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); @@ -1679,7 +1686,9 @@ absl::Status TestManager::TestRomDataIntegrity(Rom* rom) { std::string TestManager::RegisterHarnessTest(const std::string& name, const std::string& category) { absl::MutexLock lock(&harness_history_mutex_); - std::string test_id = absl::StrCat("harness_", absl::ToUnixMicros(absl::Now()), "_", harness_history_.size()); + std::string test_id = + absl::StrCat("harness_", absl::ToUnixMicros(absl::Now()), "_", + harness_history_.size()); HarnessTestExecution execution; execution.test_id = test_id; execution.name = name; @@ -1737,9 +1746,8 @@ void TestManager::MarkHarnessTestCompleted( execution.assertion_failures = assertion_failures; execution.logs.insert(execution.logs.end(), logs.begin(), logs.end()); - bool capture_failure_context = - status == HarnessTestStatus::kFailed || - status == HarnessTestStatus::kTimeout; + bool capture_failure_context = status == HarnessTestStatus::kFailed || + status == HarnessTestStatus::kTimeout; harness_aggregates_[execution.name].latest_execution = execution; harness_aggregates_[execution.name].total_runs += 1; diff --git a/src/app/test/test_manager.h b/src/app/test/test_manager.h index 5c973d86..f6723ce8 100644 --- a/src/app/test/test_manager.h +++ b/src/app/test/test_manager.h @@ -12,8 +12,8 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/synchronization/mutex.h" #include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" #include "absl/time/time.h" #include "app/rom.h" @@ -147,7 +147,7 @@ struct HarnessTestExecution { std::vector assertion_failures; std::vector logs; std::map metrics; - + // IT-08b: Failure diagnostics std::string screenshot_path; int64_t screenshot_size_bytes = 0; @@ -292,7 +292,7 @@ class TestManager { std::vector ListHarnessTestSummaries( const std::string& category_filter = "") const ABSL_LOCKS_EXCLUDED(harness_history_mutex_); - + // IT-08b: Capture failure diagnostics void CaptureFailureContext(const std::string& test_id) ABSL_LOCKS_EXCLUDED(harness_history_mutex_); @@ -307,7 +307,7 @@ class TestManager { void CaptureFailureContext(const std::string& test_id); absl::Status ReplayLastPlan(); #endif - + // These methods are always available absl::Status ShowHarnessDashboard(); absl::Status ShowHarnessActiveTests(); @@ -371,19 +371,19 @@ class TestManager { // Harness test tracking #if defined(YAZE_WITH_GRPC) struct HarnessAggregate { - int total_runs = 0; - int pass_count = 0; - int fail_count = 0; - absl::Duration total_duration = absl::ZeroDuration(); + int total_runs = 0; + int pass_count = 0; + int fail_count = 0; + absl::Duration total_duration = absl::ZeroDuration(); std::string category; absl::Time last_run; HarnessTestExecution latest_execution; }; std::unordered_map harness_history_ - ABSL_GUARDED_BY(harness_history_mutex_); + ABSL_GUARDED_BY(harness_history_mutex_); std::unordered_map harness_aggregates_ - ABSL_GUARDED_BY(harness_history_mutex_); + ABSL_GUARDED_BY(harness_history_mutex_); std::deque harness_history_order_; size_t harness_history_limit_ = 200; mutable absl::Mutex harness_history_mutex_; @@ -400,7 +400,6 @@ class TestManager { #endif absl::Mutex mutex_; - }; // Utility functions for test result formatting diff --git a/src/app/test/test_recorder.cc b/src/app/test/test_recorder.cc index c56648ba..6b156b26 100644 --- a/src/app/test/test_recorder.cc +++ b/src/app/test/test_recorder.cc @@ -7,8 +7,8 @@ #include "absl/strings/str_format.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "app/test/test_script_parser.h" #include "app/test/test_manager.h" +#include "app/test/test_script_parser.h" namespace yaze { namespace test { @@ -40,8 +40,8 @@ const char* HarnessStatusToString(test::HarnessTestStatus status) { } // namespace TestRecorder::ScopedSuspension::ScopedSuspension(TestRecorder* recorder, - bool active) - : recorder_(recorder), active_(active) {} + bool active) + : recorder_(recorder), active_(active) {} TestRecorder::ScopedSuspension::~ScopedSuspension() { if (!recorder_ || !active_) { @@ -155,7 +155,8 @@ absl::StatusOr TestRecorder::StopLocked( 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); + script_step.expect_status = + ::yaze::test::HarnessStatusToString(step.final_status); #else script_step.expect_status.clear(); #endif @@ -237,8 +238,8 @@ absl::Status TestRecorder::PopulateFinalStatusLocked() { std::string TestRecorder::GenerateRecordingId() { return absl::StrFormat( - "rec_%s", absl::FormatTime("%Y%m%dT%H%M%S", absl::Now(), - absl::UTCTimeZone())); + "rec_%s", + absl::FormatTime("%Y%m%dT%H%M%S", absl::Now(), absl::UTCTimeZone())); } const char* TestRecorder::ActionTypeToString(ActionType type) { diff --git a/src/app/test/test_recorder.h b/src/app/test/test_recorder.h index 2218433d..6ef8e400 100644 --- a/src/app/test/test_recorder.h +++ b/src/app/test/test_recorder.h @@ -95,14 +95,13 @@ class TestRecorder { private: absl::StatusOr StartLocked(const RecordingOptions& options) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); - absl::StatusOr StopLocked(const std::string& recording_id, - bool discard) + absl::StatusOr StopLocked( + const std::string& recording_id, bool discard) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); void RecordStepLocked(const RecordedStep& step) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); - absl::Status PopulateFinalStatusLocked() - ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); + absl::Status PopulateFinalStatusLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); static std::string GenerateRecordingId(); static const char* ActionTypeToString(ActionType type); diff --git a/src/app/test/test_script_parser.cc b/src/app/test/test_script_parser.cc index fb4cbd05..5793964d 100644 --- a/src/app/test/test_script_parser.cc +++ b/src/app/test/test_script_parser.cc @@ -179,8 +179,9 @@ absl::Status TestScriptParser::WriteToFile(const TestScript& script, auto parent = output_path.parent_path(); if (!parent.empty() && !std::filesystem::exists(parent)) { if (!std::filesystem::create_directories(parent, ec)) { - return absl::InternalError(absl::StrFormat( - "Failed to create directory '%s': %s", parent.string(), ec.message())); + return absl::InternalError( + absl::StrFormat("Failed to create directory '%s': %s", + parent.string(), ec.message())); } } @@ -228,8 +229,7 @@ absl::StatusOr TestScriptParser::ParseFromFile( script.description = root["description"].get(); } - ASSIGN_OR_RETURN(script.created_at, - ParseIsoTimestamp(root, "created_at")); + ASSIGN_OR_RETURN(script.created_at, ParseIsoTimestamp(root, "created_at")); if (root.contains("duration_ms")) { script.duration = absl::Milliseconds(root["duration_ms"].get()); } @@ -240,7 +240,7 @@ absl::StatusOr TestScriptParser::ParseFromFile( } for (const auto& step_node : root["steps"]) { - ASSIGN_OR_RETURN(auto step, ParseStep(step_node)); + ASSIGN_OR_RETURN(auto step, ParseStep(step_node)); script.steps.push_back(std::move(step)); } diff --git a/src/app/test/unit_test_suite.h b/src/app/test/unit_test_suite.h index 736fa39c..706e69f3 100644 --- a/src/app/test/unit_test_suite.h +++ b/src/app/test/unit_test_suite.h @@ -71,7 +71,8 @@ class TestResultCapture : public ::testing::TestEventListener { void OnEnvironmentsSetUpEnd(const ::testing::UnitTest&) override {} void OnTestCaseStart(const ::testing::TestCase&) override {} void OnTestCaseEnd(const ::testing::TestCase&) override {} - void OnTestPartResult(const ::testing::TestPartResult& test_part_result) override { + void OnTestPartResult( + const ::testing::TestPartResult& test_part_result) override { // Handle individual test part results (can be empty for our use case) } void OnEnvironmentsTearDownStart(const ::testing::UnitTest&) override {} @@ -142,7 +143,8 @@ class UnitTestSuite : public TestSuite { ImGui::Checkbox("Run disabled tests", &run_disabled_tests_); ImGui::Checkbox("Shuffle tests", &shuffle_tests_); ImGui::InputInt("Repeat count", &repeat_count_); - if (repeat_count_ < 1) repeat_count_ = 1; + if (repeat_count_ < 1) + repeat_count_ = 1; ImGui::InputText("Test filter", test_filter_, sizeof(test_filter_)); ImGui::SameLine(); diff --git a/src/app/test/z3ed_test_suite.cc b/src/app/test/z3ed_test_suite.cc index 4c5fd84d..00c9d56e 100644 --- a/src/app/test/z3ed_test_suite.cc +++ b/src/app/test/z3ed_test_suite.cc @@ -9,15 +9,15 @@ namespace test { void RegisterZ3edTestSuites() { #ifdef YAZE_WITH_GRPC LOG_INFO("Z3edTests", "Registering z3ed AI Agent test suites"); - + // Register AI Agent test suite TestManager::Get().RegisterTestSuite( std::make_unique()); - + // Register GUI Automation test suite TestManager::Get().RegisterTestSuite( std::make_unique()); - + LOG_INFO("Z3edTests", "z3ed test suites registered successfully"); #else LOG_INFO("Z3edTests", "z3ed test suites not available (YAZE_WITH_GRPC=OFF)"); diff --git a/src/app/test/z3ed_test_suite.h b/src/app/test/z3ed_test_suite.h index 58e58c70..f502694a 100644 --- a/src/app/test/z3ed_test_suite.h +++ b/src/app/test/z3ed_test_suite.h @@ -1,8 +1,8 @@ #ifndef YAZE_APP_TEST_Z3ED_TEST_SUITE_H #define YAZE_APP_TEST_Z3ED_TEST_SUITE_H -#include "app/test/test_manager.h" #include "absl/status/status.h" +#include "app/test/test_manager.h" #include "imgui.h" #ifdef YAZE_WITH_GRPC @@ -25,45 +25,47 @@ class Z3edAIAgentTestSuite : public TestSuite { ~Z3edAIAgentTestSuite() override = default; std::string GetName() const override { return "z3ed AI Agent"; } - TestCategory GetCategory() const override { return TestCategory::kIntegration; } + TestCategory GetCategory() const override { + return TestCategory::kIntegration; + } absl::Status RunTests(TestResults& results) override { // Test 1: Gemini AI Service connectivity RunGeminiConnectivityTest(results); - + // Test 2: Tile16 proposal generation RunTile16ProposalTest(results); - + // Test 3: Natural language command parsing RunCommandParsingTest(results); - + return absl::OkStatus(); } void DrawConfiguration() override { ImGui::Text("z3ed AI Agent Test Configuration"); ImGui::Separator(); - + ImGui::Checkbox("Test Gemini Connectivity", &test_gemini_connectivity_); ImGui::Checkbox("Test Proposal Generation", &test_proposal_generation_); ImGui::Checkbox("Test Command Parsing", &test_command_parsing_); - + ImGui::Separator(); ImGui::Text("Note: Tests require valid Gemini API key"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Set GEMINI_API_KEY environment variable"); } private: void RunGeminiConnectivityTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Gemini_AI_Connectivity"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { // Check if API key is available const char* api_key = std::getenv("GEMINI_API_KEY"); @@ -77,117 +79,118 @@ class Z3edAIAgentTestSuite : public TestSuite { } } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Connectivity test failed: " + std::string(e.what()); + result.error_message = + "Connectivity test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunTile16ProposalTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Tile16_Proposal_Generation"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { using namespace yaze::cli; - + // Create a tile16 proposal generator Tile16ProposalGenerator generator; - + // Test parsing a simple command std::vector commands = { - "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E" - }; - + "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E"}; + // Generate proposal (without actual ROM) // GenerateFromCommands(prompt, commands, ai_service, rom) - auto proposal_or = generator.GenerateFromCommands("", commands, "", nullptr); - + auto proposal_or = + generator.GenerateFromCommands("", commands, "", nullptr); + if (proposal_or.ok()) { result.status = TestStatus::kPassed; result.error_message = absl::StrFormat( - "Generated proposal with %zu changes", - proposal_or->changes.size()); + "Generated proposal with %zu changes", proposal_or->changes.size()); } else { result.status = TestStatus::kFailed; - result.error_message = "Proposal generation failed: " + - std::string(proposal_or.status().message()); + result.error_message = "Proposal generation failed: " + + std::string(proposal_or.status().message()); } } catch (const std::exception& e) { result.status = TestStatus::kFailed; result.error_message = "Proposal test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunCommandParsingTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Natural_Language_Command_Parsing"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { // Test parsing different command types std::vector test_commands = { - "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E", - "overworld set-area --map 0 --x 10 --y 20 --width 5 --height 3 --tile 0x02E", - "overworld replace-tile --map 0 --old-tile 0x02E --new-tile 0x030" - }; - + "overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E", + "overworld set-area --map 0 --x 10 --y 20 --width 5 --height 3 " + "--tile 0x02E", + "overworld replace-tile --map 0 --old-tile 0x02E --new-tile 0x030"}; + int passed = 0; int failed = 0; - + using namespace yaze::cli; Tile16ProposalGenerator generator; - + for (const auto& cmd : test_commands) { // GenerateFromCommands(prompt, commands, ai_service, rom) std::vector single_cmd = {cmd}; - auto proposal_or = generator.GenerateFromCommands("", single_cmd, "", nullptr); + auto proposal_or = + generator.GenerateFromCommands("", single_cmd, "", nullptr); if (proposal_or.ok()) { passed++; } else { failed++; } } - + if (failed == 0) { result.status = TestStatus::kPassed; - result.error_message = absl::StrFormat( - "All %d command types parsed successfully", passed); + result.error_message = + absl::StrFormat("All %d command types parsed successfully", passed); } else { result.status = TestStatus::kFailed; - result.error_message = absl::StrFormat( - "%d commands passed, %d failed", passed, failed); + result.error_message = + absl::StrFormat("%d commands passed, %d failed", passed, failed); } } catch (const std::exception& e) { result.status = TestStatus::kFailed; result.error_message = "Parsing test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + bool test_gemini_connectivity_ = true; bool test_proposal_generation_ = true; bool test_command_parsing_ = true; @@ -200,97 +203,101 @@ class GUIAutomationTestSuite : public TestSuite { ~GUIAutomationTestSuite() override = default; std::string GetName() const override { return "GUI Automation (gRPC)"; } - TestCategory GetCategory() const override { return TestCategory::kIntegration; } + TestCategory GetCategory() const override { + return TestCategory::kIntegration; + } absl::Status RunTests(TestResults& results) override { // Test 1: gRPC connection RunConnectionTest(results); - + // Test 2: Basic GUI actions RunBasicActionsTest(results); - + // Test 3: Screenshot capture RunScreenshotTest(results); - + return absl::OkStatus(); } void DrawConfiguration() override { ImGui::Text("GUI Automation Test Configuration"); ImGui::Separator(); - + ImGui::Checkbox("Test gRPC Connection", &test_connection_); ImGui::Checkbox("Test GUI Actions", &test_actions_); ImGui::Checkbox("Test Screenshot Capture", &test_screenshots_); - + ImGui::Separator(); - ImGui::InputText("gRPC Server", grpc_server_address_, sizeof(grpc_server_address_)); - + ImGui::InputText("gRPC Server", grpc_server_address_, + sizeof(grpc_server_address_)); + ImGui::Separator(); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Note: Requires ImGuiTestHarness server running"); } private: void RunConnectionTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "gRPC_Connection"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { using namespace yaze::cli; - + // Create GUI automation client GuiAutomationClient client(grpc_server_address_); - + // Attempt connection auto status = client.Connect(); - + if (status.ok()) { result.status = TestStatus::kPassed; result.error_message = "gRPC connection successful"; } else { result.status = TestStatus::kFailed; - result.error_message = "Connection failed: " + std::string(status.message()); + result.error_message = + "Connection failed: " + std::string(status.message()); } } catch (const std::exception& e) { result.status = TestStatus::kFailed; result.error_message = "Connection test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunBasicActionsTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "GUI_Basic_Actions"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { using namespace yaze::cli; - + GuiAutomationClient client(grpc_server_address_); auto conn_status = client.Connect(); - + if (!conn_status.ok()) { result.status = TestStatus::kSkipped; result.error_message = "Skipped: Cannot connect to gRPC server"; } else { // Test ping action auto ping_result = client.Ping("test"); - + if (ping_result.ok() && ping_result->success) { result.status = TestStatus::kPassed; result.error_message = "Basic GUI actions working"; @@ -303,23 +310,23 @@ class GUIAutomationTestSuite : public TestSuite { result.status = TestStatus::kFailed; result.error_message = "Actions test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + void RunScreenshotTest(TestResults& results) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Screenshot_Capture"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { // Screenshot capture test would go here // For now, mark as passed if we have the capability @@ -329,14 +336,14 @@ class GUIAutomationTestSuite : public TestSuite { result.status = TestStatus::kFailed; result.error_message = "Screenshot test failed: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } - + bool test_connection_ = true; bool test_actions_ = true; bool test_screenshots_ = true; diff --git a/src/app/test/zscustomoverworld_test_suite.h b/src/app/test/zscustomoverworld_test_suite.h index ddf35dc3..376eb6af 100644 --- a/src/app/test/zscustomoverworld_test_suite.h +++ b/src/app/test/zscustomoverworld_test_suite.h @@ -5,9 +5,9 @@ #include #include "absl/strings/str_format.h" -#include "app/test/test_manager.h" -#include "app/rom.h" #include "app/gui/core/icons.h" +#include "app/rom.h" +#include "app/test/test_manager.h" namespace yaze { namespace test { @@ -27,12 +27,16 @@ class ZSCustomOverworldTestSuite : public TestSuite { ZSCustomOverworldTestSuite() = default; ~ZSCustomOverworldTestSuite() override = default; - std::string GetName() const override { return "ZSCustomOverworld Upgrade Tests"; } - TestCategory GetCategory() const override { return TestCategory::kIntegration; } + std::string GetName() const override { + return "ZSCustomOverworld Upgrade Tests"; + } + TestCategory GetCategory() const override { + return TestCategory::kIntegration; + } absl::Status RunTests(TestResults& results) override { Rom* current_rom = TestManager::Get().GetCurrentRom(); - + // Check ROM availability if (!current_rom || !current_rom->is_loaded()) { AddSkippedTest(results, "ROM_Availability_Check", "No ROM loaded"); @@ -46,23 +50,23 @@ class ZSCustomOverworldTestSuite : public TestSuite { if (test_vanilla_baseline_) { RunVanillaBaselineTest(results, current_rom); } - + if (test_v2_upgrade_) { RunV2UpgradeTest(results, current_rom); } - + if (test_v3_upgrade_) { RunV3UpgradeTest(results, current_rom); } - + if (test_address_validation_) { RunAddressValidationTest(results, current_rom); } - + if (test_feature_toggle_) { RunFeatureToggleTest(results, current_rom); } - + if (test_data_integrity_) { RunDataIntegrityTest(results, current_rom); } @@ -72,29 +76,32 @@ class ZSCustomOverworldTestSuite : public TestSuite { void DrawConfiguration() override { Rom* current_rom = TestManager::Get().GetCurrentRom(); - + ImGui::Text("%s ZSCustomOverworld Test Configuration", ICON_MD_UPGRADE); - + if (current_rom && current_rom->is_loaded()) { - ImGui::TextColored(ImVec4(0.0F, 1.0F, 0.0F, 1.0F), - "%s Current ROM: %s", ICON_MD_CHECK_CIRCLE, current_rom->title().c_str()); - + ImGui::TextColored(ImVec4(0.0F, 1.0F, 0.0F, 1.0F), "%s Current ROM: %s", + ICON_MD_CHECK_CIRCLE, current_rom->title().c_str()); + // Check current version auto version_byte = current_rom->ReadByte(0x140145); if (version_byte.ok()) { std::string version_name = "Unknown"; - if (*version_byte == 0xFF) version_name = "Vanilla"; - else if (*version_byte == 0x02) version_name = "v2"; - else if (*version_byte == 0x03) version_name = "v3"; - - ImGui::Text("Current ZSCustomOverworld version: %s (0x%02X)", - version_name.c_str(), *version_byte); + if (*version_byte == 0xFF) + version_name = "Vanilla"; + else if (*version_byte == 0x02) + version_name = "v2"; + else if (*version_byte == 0x03) + version_name = "v3"; + + ImGui::Text("Current ZSCustomOverworld version: %s (0x%02X)", + version_name.c_str(), *version_byte); } } else { - ImGui::TextColored(ImVec4(1.0F, 0.5F, 0.0F, 1.0F), - "%s No ROM currently loaded", ICON_MD_WARNING); + ImGui::TextColored(ImVec4(1.0F, 0.5F, 0.0F, 1.0F), + "%s No ROM currently loaded", ICON_MD_WARNING); } - + ImGui::Separator(); ImGui::Checkbox("Test vanilla baseline", &test_vanilla_baseline_); ImGui::Checkbox("Test v2 upgrade", &test_v2_upgrade_); @@ -102,7 +109,7 @@ class ZSCustomOverworldTestSuite : public TestSuite { ImGui::Checkbox("Test address validation", &test_address_validation_); ImGui::Checkbox("Test feature toggle", &test_feature_toggle_); ImGui::Checkbox("Test data integrity", &test_data_integrity_); - + if (ImGui::CollapsingHeader("Version Settings")) { ImGui::Text("Version-specific addresses and features:"); ImGui::Text("Vanilla: 0x140145 = 0xFF"); @@ -115,36 +122,37 @@ class ZSCustomOverworldTestSuite : public TestSuite { void InitializeVersionData() { // Vanilla ROM addresses and values vanilla_data_ = { - {"version_flag", {0x140145, 0xFF}}, // OverworldCustomASMHasBeenApplied - {"message_ids", {0x3F51D, 0x00}}, // Message ID table start - {"area_graphics", {0x7C9C, 0x00}}, // Area graphics table - {"area_palettes", {0x7D1C, 0x00}}, // Area palettes table + {"version_flag", {0x140145, 0xFF}}, // OverworldCustomASMHasBeenApplied + {"message_ids", {0x3F51D, 0x00}}, // Message ID table start + {"area_graphics", {0x7C9C, 0x00}}, // Area graphics table + {"area_palettes", {0x7D1C, 0x00}}, // Area palettes table }; // v2 ROM addresses and values v2_data_ = { - {"version_flag", {0x140145, 0x02}}, // v2 version - {"message_ids", {0x1417F8, 0x00}}, // Expanded message ID table - {"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla - {"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla - {"main_palettes", {0x140160, 0x00}}, // New v2 feature + {"version_flag", {0x140145, 0x02}}, // v2 version + {"message_ids", {0x1417F8, 0x00}}, // Expanded message ID table + {"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla + {"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla + {"main_palettes", {0x140160, 0x00}}, // New v2 feature }; // v3 ROM addresses and values v3_data_ = { - {"version_flag", {0x140145, 0x03}}, // v3 version - {"message_ids", {0x1417F8, 0x00}}, // Same as v2 - {"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla - {"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla - {"main_palettes", {0x140160, 0x00}}, // Same as v2 - {"bg_colors", {0x140000, 0x00}}, // New v3 feature - {"subscreen_overlays", {0x140340, 0x00}}, // New v3 feature - {"animated_gfx", {0x1402A0, 0x00}}, // New v3 feature - {"custom_tiles", {0x140480, 0x00}}, // New v3 feature + {"version_flag", {0x140145, 0x03}}, // v3 version + {"message_ids", {0x1417F8, 0x00}}, // Same as v2 + {"area_graphics", {0x7C9C, 0x00}}, // Same as vanilla + {"area_palettes", {0x7D1C, 0x00}}, // Same as vanilla + {"main_palettes", {0x140160, 0x00}}, // Same as v2 + {"bg_colors", {0x140000, 0x00}}, // New v3 feature + {"subscreen_overlays", {0x140340, 0x00}}, // New v3 feature + {"animated_gfx", {0x1402A0, 0x00}}, // New v3 feature + {"custom_tiles", {0x140480, 0x00}}, // New v3 feature }; } - void AddSkippedTest(TestResults& results, const std::string& test_name, const std::string& reason) { + void AddSkippedTest(TestResults& results, const std::string& test_name, + const std::string& reason) { TestResult result; result.name = test_name; result.suite_name = GetName(); @@ -172,15 +180,18 @@ class ZSCustomOverworldTestSuite : public TestSuite { // Apply version-specific features if (version == "v2") { // Enable v2 features - RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes + RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes } else if (version == "v3") { // Enable v3 features - RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes - RETURN_IF_ERROR(rom.WriteByte(0x140147, 0x01)); // Enable area-specific BG - RETURN_IF_ERROR(rom.WriteByte(0x140148, 0x01)); // Enable subscreen overlay - RETURN_IF_ERROR(rom.WriteByte(0x140149, 0x01)); // Enable animated GFX - RETURN_IF_ERROR(rom.WriteByte(0x14014A, 0x01)); // Enable custom tile GFX groups - RETURN_IF_ERROR(rom.WriteByte(0x14014B, 0x01)); // Enable mosaic + RETURN_IF_ERROR(rom.WriteByte(0x140146, 0x01)); // Enable main palettes + RETURN_IF_ERROR( + rom.WriteByte(0x140147, 0x01)); // Enable area-specific BG + RETURN_IF_ERROR( + rom.WriteByte(0x140148, 0x01)); // Enable subscreen overlay + RETURN_IF_ERROR(rom.WriteByte(0x140149, 0x01)); // Enable animated GFX + RETURN_IF_ERROR( + rom.WriteByte(0x14014A, 0x01)); // Enable custom tile GFX groups + RETURN_IF_ERROR(rom.WriteByte(0x14014B, 0x01)); // Enable mosaic } return absl::OkStatus(); @@ -206,368 +217,405 @@ class ZSCustomOverworldTestSuite : public TestSuite { void RunVanillaBaselineTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Vanilla_Baseline_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Validate vanilla addresses - if (!ValidateVersionAddresses(*test_rom, "vanilla")) { - return absl::InternalError("Vanilla address validation failed"); - } - - // Verify version flag - auto version_byte = test_rom->ReadByte(0x140145); - if (!version_byte.ok()) { - return absl::InternalError("Failed to read version flag"); - } - - if (*version_byte != 0xFF) { - return absl::InternalError(absl::StrFormat( - "Expected vanilla version flag (0xFF), got 0x%02X", *version_byte)); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Validate vanilla addresses + if (!ValidateVersionAddresses(*test_rom, "vanilla")) { + return absl::InternalError("Vanilla address validation failed"); + } + + // Verify version flag + auto version_byte = test_rom->ReadByte(0x140145); + if (!version_byte.ok()) { + return absl::InternalError("Failed to read version flag"); + } + + if (*version_byte != 0xFF) { + return absl::InternalError(absl::StrFormat( + "Expected vanilla version flag (0xFF), got 0x%02X", + *version_byte)); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Vanilla baseline test passed - ROM is in vanilla state"; + result.error_message = + "Vanilla baseline test passed - ROM is in vanilla state"; } else { result.status = TestStatus::kFailed; - result.error_message = "Vanilla baseline test failed: " + test_status.ToString(); + result.error_message = + "Vanilla baseline test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Vanilla baseline test exception: " + std::string(e.what()); + result.error_message = + "Vanilla baseline test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunV2UpgradeTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "V2_Upgrade_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Apply v2 patch - RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v2")); - - // Validate v2 addresses - if (!ValidateVersionAddresses(*test_rom, "v2")) { - return absl::InternalError("v2 address validation failed"); - } - - // Verify version flag - auto version_byte = test_rom->ReadByte(0x140145); - if (!version_byte.ok()) { - return absl::InternalError("Failed to read version flag"); - } - - if (*version_byte != 0x02) { - return absl::InternalError(absl::StrFormat( - "Expected v2 version flag (0x02), got 0x%02X", *version_byte)); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Apply v2 patch + RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v2")); + + // Validate v2 addresses + if (!ValidateVersionAddresses(*test_rom, "v2")) { + return absl::InternalError("v2 address validation failed"); + } + + // Verify version flag + auto version_byte = test_rom->ReadByte(0x140145); + if (!version_byte.ok()) { + return absl::InternalError("Failed to read version flag"); + } + + if (*version_byte != 0x02) { + return absl::InternalError( + absl::StrFormat("Expected v2 version flag (0x02), got 0x%02X", + *version_byte)); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "v2 upgrade test passed - ROM successfully upgraded to v2"; + result.error_message = + "v2 upgrade test passed - ROM successfully upgraded to v2"; } else { result.status = TestStatus::kFailed; - result.error_message = "v2 upgrade test failed: " + test_status.ToString(); + result.error_message = + "v2 upgrade test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "v2 upgrade test exception: " + std::string(e.what()); + result.error_message = + "v2 upgrade test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunV3UpgradeTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "V3_Upgrade_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Apply v3 patch - RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); - - // Validate v3 addresses - if (!ValidateVersionAddresses(*test_rom, "v3")) { - return absl::InternalError("v3 address validation failed"); - } - - // Verify version flag - auto version_byte = test_rom->ReadByte(0x140145); - if (!version_byte.ok()) { - return absl::InternalError("Failed to read version flag"); - } - - if (*version_byte != 0x03) { - return absl::InternalError(absl::StrFormat( - "Expected v3 version flag (0x03), got 0x%02X", *version_byte)); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Apply v3 patch + RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); + + // Validate v3 addresses + if (!ValidateVersionAddresses(*test_rom, "v3")) { + return absl::InternalError("v3 address validation failed"); + } + + // Verify version flag + auto version_byte = test_rom->ReadByte(0x140145); + if (!version_byte.ok()) { + return absl::InternalError("Failed to read version flag"); + } + + if (*version_byte != 0x03) { + return absl::InternalError( + absl::StrFormat("Expected v3 version flag (0x03), got 0x%02X", + *version_byte)); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "v3 upgrade test passed - ROM successfully upgraded to v3"; + result.error_message = + "v3 upgrade test passed - ROM successfully upgraded to v3"; } else { result.status = TestStatus::kFailed; - result.error_message = "v3 upgrade test failed: " + test_status.ToString(); + result.error_message = + "v3 upgrade test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "v3 upgrade test exception: " + std::string(e.what()); + result.error_message = + "v3 upgrade test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunAddressValidationTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Address_Validation_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Test vanilla addresses - if (!ValidateVersionAddresses(*test_rom, "vanilla")) { - return absl::InternalError("Vanilla address validation failed"); - } - - // Test v2 addresses - RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v2")); - if (!ValidateVersionAddresses(*test_rom, "v2")) { - return absl::InternalError("v2 address validation failed"); - } - - // Test v3 addresses - RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); - if (!ValidateVersionAddresses(*test_rom, "v3")) { - return absl::InternalError("v3 address validation failed"); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Test vanilla addresses + if (!ValidateVersionAddresses(*test_rom, "vanilla")) { + return absl::InternalError("Vanilla address validation failed"); + } + + // Test v2 addresses + RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v2")); + if (!ValidateVersionAddresses(*test_rom, "v2")) { + return absl::InternalError("v2 address validation failed"); + } + + // Test v3 addresses + RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); + if (!ValidateVersionAddresses(*test_rom, "v3")) { + return absl::InternalError("v3 address validation failed"); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Address validation test passed - all version addresses valid"; + result.error_message = + "Address validation test passed - all version addresses valid"; } else { result.status = TestStatus::kFailed; - result.error_message = "Address validation test failed: " + test_status.ToString(); + result.error_message = + "Address validation test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Address validation test exception: " + std::string(e.what()); + result.error_message = + "Address validation test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunFeatureToggleTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Feature_Toggle_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Apply v3 patch - RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); - - // Test feature flags - auto main_palettes = test_rom->ReadByte(0x140146); - auto area_bg = test_rom->ReadByte(0x140147); - auto subscreen_overlay = test_rom->ReadByte(0x140148); - auto animated_gfx = test_rom->ReadByte(0x140149); - auto custom_tiles = test_rom->ReadByte(0x14014A); - auto mosaic = test_rom->ReadByte(0x14014B); - - if (!main_palettes.ok() || !area_bg.ok() || !subscreen_overlay.ok() || - !animated_gfx.ok() || !custom_tiles.ok() || !mosaic.ok()) { - return absl::InternalError("Failed to read feature flags"); - } - - if (*main_palettes != 0x01 || *area_bg != 0x01 || *subscreen_overlay != 0x01 || - *animated_gfx != 0x01 || *custom_tiles != 0x01 || *mosaic != 0x01) { - return absl::InternalError("Feature flags not properly enabled"); - } - - // Disable some features - RETURN_IF_ERROR(test_rom->WriteByte(0x140147, 0x00)); // Disable area-specific BG - RETURN_IF_ERROR(test_rom->WriteByte(0x140149, 0x00)); // Disable animated GFX - - // Verify features are disabled - auto disabled_area_bg = test_rom->ReadByte(0x140147); - auto disabled_animated_gfx = test_rom->ReadByte(0x140149); - - if (!disabled_area_bg.ok() || !disabled_animated_gfx.ok()) { - return absl::InternalError("Failed to read disabled feature flags"); - } - - if (*disabled_area_bg != 0x00 || *disabled_animated_gfx != 0x00) { - return absl::InternalError("Feature flags not properly disabled"); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Apply v3 patch + RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); + + // Test feature flags + auto main_palettes = test_rom->ReadByte(0x140146); + auto area_bg = test_rom->ReadByte(0x140147); + auto subscreen_overlay = test_rom->ReadByte(0x140148); + auto animated_gfx = test_rom->ReadByte(0x140149); + auto custom_tiles = test_rom->ReadByte(0x14014A); + auto mosaic = test_rom->ReadByte(0x14014B); + + if (!main_palettes.ok() || !area_bg.ok() || + !subscreen_overlay.ok() || !animated_gfx.ok() || + !custom_tiles.ok() || !mosaic.ok()) { + return absl::InternalError("Failed to read feature flags"); + } + + if (*main_palettes != 0x01 || *area_bg != 0x01 || + *subscreen_overlay != 0x01 || *animated_gfx != 0x01 || + *custom_tiles != 0x01 || *mosaic != 0x01) { + return absl::InternalError("Feature flags not properly enabled"); + } + + // Disable some features + RETURN_IF_ERROR(test_rom->WriteByte( + 0x140147, 0x00)); // Disable area-specific BG + RETURN_IF_ERROR( + test_rom->WriteByte(0x140149, 0x00)); // Disable animated GFX + + // Verify features are disabled + auto disabled_area_bg = test_rom->ReadByte(0x140147); + auto disabled_animated_gfx = test_rom->ReadByte(0x140149); + + if (!disabled_area_bg.ok() || !disabled_animated_gfx.ok()) { + return absl::InternalError( + "Failed to read disabled feature flags"); + } + + if (*disabled_area_bg != 0x00 || *disabled_animated_gfx != 0x00) { + return absl::InternalError("Feature flags not properly disabled"); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Feature toggle test passed - features can be enabled/disabled"; + result.error_message = + "Feature toggle test passed - features can be enabled/disabled"; } else { result.status = TestStatus::kFailed; - result.error_message = "Feature toggle test failed: " + test_status.ToString(); + result.error_message = + "Feature toggle test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Feature toggle test exception: " + std::string(e.what()); + result.error_message = + "Feature toggle test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } void RunDataIntegrityTest(TestResults& results, Rom* rom) { auto start_time = std::chrono::steady_clock::now(); - + TestResult result; result.name = "Data_Integrity_Test"; result.suite_name = GetName(); result.category = GetCategory(); result.timestamp = start_time; - + try { auto& test_manager = TestManager::Get(); - - auto test_status = test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { - // Store some original data - auto original_graphics = test_rom->ReadByte(0x7C9C); - auto original_palette = test_rom->ReadByte(0x7D1C); - auto original_sprite_set = test_rom->ReadByte(0x7A41); - - if (!original_graphics.ok() || !original_palette.ok() || !original_sprite_set.ok()) { - return absl::InternalError("Failed to read original data"); - } - - // Upgrade to v3 - RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); - - // Verify original data is preserved - auto preserved_graphics = test_rom->ReadByte(0x7C9C); - auto preserved_palette = test_rom->ReadByte(0x7D1C); - auto preserved_sprite_set = test_rom->ReadByte(0x7A41); - - if (!preserved_graphics.ok() || !preserved_palette.ok() || !preserved_sprite_set.ok()) { - return absl::InternalError("Failed to read preserved data"); - } - - if (*preserved_graphics != *original_graphics || - *preserved_palette != *original_palette || - *preserved_sprite_set != *original_sprite_set) { - return absl::InternalError("Original data not preserved during upgrade"); - } - - // Verify new v3 data is initialized - auto bg_colors = test_rom->ReadByte(0x140000); - auto subscreen_overlays = test_rom->ReadByte(0x140340); - auto animated_gfx = test_rom->ReadByte(0x1402A0); - auto custom_tiles = test_rom->ReadByte(0x140480); - - if (!bg_colors.ok() || !subscreen_overlays.ok() || - !animated_gfx.ok() || !custom_tiles.ok()) { - return absl::InternalError("Failed to read new v3 data"); - } - - if (*bg_colors != 0x00 || *subscreen_overlays != 0x00 || - *animated_gfx != 0x00 || *custom_tiles != 0x00) { - return absl::InternalError("New v3 data not properly initialized"); - } - - return absl::OkStatus(); - }); - + + auto test_status = + test_manager.TestRomWithCopy(rom, [&](Rom* test_rom) -> absl::Status { + // Store some original data + auto original_graphics = test_rom->ReadByte(0x7C9C); + auto original_palette = test_rom->ReadByte(0x7D1C); + auto original_sprite_set = test_rom->ReadByte(0x7A41); + + if (!original_graphics.ok() || !original_palette.ok() || + !original_sprite_set.ok()) { + return absl::InternalError("Failed to read original data"); + } + + // Upgrade to v3 + RETURN_IF_ERROR(ApplyVersionPatch(*test_rom, "v3")); + + // Verify original data is preserved + auto preserved_graphics = test_rom->ReadByte(0x7C9C); + auto preserved_palette = test_rom->ReadByte(0x7D1C); + auto preserved_sprite_set = test_rom->ReadByte(0x7A41); + + if (!preserved_graphics.ok() || !preserved_palette.ok() || + !preserved_sprite_set.ok()) { + return absl::InternalError("Failed to read preserved data"); + } + + if (*preserved_graphics != *original_graphics || + *preserved_palette != *original_palette || + *preserved_sprite_set != *original_sprite_set) { + return absl::InternalError( + "Original data not preserved during upgrade"); + } + + // Verify new v3 data is initialized + auto bg_colors = test_rom->ReadByte(0x140000); + auto subscreen_overlays = test_rom->ReadByte(0x140340); + auto animated_gfx = test_rom->ReadByte(0x1402A0); + auto custom_tiles = test_rom->ReadByte(0x140480); + + if (!bg_colors.ok() || !subscreen_overlays.ok() || + !animated_gfx.ok() || !custom_tiles.ok()) { + return absl::InternalError("Failed to read new v3 data"); + } + + if (*bg_colors != 0x00 || *subscreen_overlays != 0x00 || + *animated_gfx != 0x00 || *custom_tiles != 0x00) { + return absl::InternalError( + "New v3 data not properly initialized"); + } + + return absl::OkStatus(); + }); + if (test_status.ok()) { result.status = TestStatus::kPassed; - result.error_message = "Data integrity test passed - original data preserved, new data initialized"; + result.error_message = + "Data integrity test passed - original data preserved, new data " + "initialized"; } else { result.status = TestStatus::kFailed; - result.error_message = "Data integrity test failed: " + test_status.ToString(); + result.error_message = + "Data integrity test failed: " + test_status.ToString(); } - + } catch (const std::exception& e) { result.status = TestStatus::kFailed; - result.error_message = "Data integrity test exception: " + std::string(e.what()); + result.error_message = + "Data integrity test exception: " + std::string(e.what()); } - + auto end_time = std::chrono::steady_clock::now(); result.duration = std::chrono::duration_cast( end_time - start_time); - + results.AddResult(result); } @@ -578,7 +626,7 @@ class ZSCustomOverworldTestSuite : public TestSuite { bool test_address_validation_ = true; bool test_feature_toggle_ = true; bool test_data_integrity_ = true; - + // Version data std::map> vanilla_data_; std::map> v2_data_; diff --git a/src/app/transaction.h b/src/app/transaction.h index 0053b100..f7975178 100644 --- a/src/app/transaction.h +++ b/src/app/transaction.h @@ -21,10 +21,11 @@ namespace yaze { class Transaction { public: - explicit Transaction(Rom &rom) : rom_(rom) {} + explicit Transaction(Rom& rom) : rom_(rom) {} - Transaction &WriteByte(int address, uint8_t value) { - if (!status_.ok()) return *this; + Transaction& WriteByte(int address, uint8_t value) { + if (!status_.ok()) + return *this; auto original = rom_.ReadByte(address); if (!original.ok()) { status_ = original.status(); @@ -32,13 +33,15 @@ class Transaction { } status_ = rom_.WriteByte(address, value); if (status_.ok()) { - operations_.push_back({address, static_cast(*original), OperationType::kWriteByte}); + operations_.push_back({address, static_cast(*original), + OperationType::kWriteByte}); } return *this; } - Transaction &WriteWord(int address, uint16_t value) { - if (!status_.ok()) return *this; + Transaction& WriteWord(int address, uint16_t value) { + if (!status_.ok()) + return *this; auto original = rom_.ReadWord(address); if (!original.ok()) { status_ = original.status(); @@ -46,13 +49,15 @@ class Transaction { } status_ = rom_.WriteWord(address, value); if (status_.ok()) { - operations_.push_back({address, static_cast(*original), OperationType::kWriteWord}); + operations_.push_back({address, static_cast(*original), + OperationType::kWriteWord}); } return *this; } - Transaction &WriteLong(int address, uint32_t value) { - if (!status_.ok()) return *this; + Transaction& WriteLong(int address, uint32_t value) { + if (!status_.ok()) + return *this; auto original = rom_.ReadLong(address); if (!original.ok()) { status_ = original.status(); @@ -60,14 +65,17 @@ class Transaction { } status_ = rom_.WriteLong(address, value); if (status_.ok()) { - operations_.push_back({address, static_cast(*original), OperationType::kWriteLong}); + operations_.push_back({address, static_cast(*original), + OperationType::kWriteLong}); } return *this; } - Transaction &WriteVector(int address, const std::vector &data) { - if (!status_.ok()) return *this; - auto original = rom_.ReadByteVector(address, static_cast(data.size())); + Transaction& WriteVector(int address, const std::vector& data) { + if (!status_.ok()) + return *this; + auto original = + rom_.ReadByteVector(address, static_cast(data.size())); if (!original.ok()) { status_ = original.status(); return *this; @@ -79,8 +87,9 @@ class Transaction { return *this; } - Transaction &WriteColor(int address, const gfx::SnesColor &color) { - if (!status_.ok()) return *this; + Transaction& WriteColor(int address, const gfx::SnesColor& color) { + if (!status_.ok()) + return *this; // Store original raw 16-bit value for rollback via WriteWord. auto original_word = rom_.ReadWord(address); if (!original_word.ok()) { @@ -89,7 +98,8 @@ class Transaction { } status_ = rom_.WriteColor(address, color); if (status_.ok()) { - operations_.push_back({address, static_cast(*original_word), OperationType::kWriteColor}); + operations_.push_back({address, static_cast(*original_word), + OperationType::kWriteColor}); } return *this; } @@ -103,22 +113,27 @@ class Transaction { void Rollback() { for (auto it = operations_.rbegin(); it != operations_.rend(); ++it) { - const auto &op = *it; + const auto& op = *it; switch (op.type) { case OperationType::kWriteByte: - (void)rom_.WriteByte(op.address, std::get(op.original_value)); + (void)rom_.WriteByte(op.address, + std::get(op.original_value)); break; case OperationType::kWriteWord: - (void)rom_.WriteWord(op.address, std::get(op.original_value)); + (void)rom_.WriteWord(op.address, + std::get(op.original_value)); break; case OperationType::kWriteLong: - (void)rom_.WriteLong(op.address, std::get(op.original_value)); + (void)rom_.WriteLong(op.address, + std::get(op.original_value)); break; case OperationType::kWriteVector: - (void)rom_.WriteVector(op.address, std::get>(op.original_value)); + (void)rom_.WriteVector( + op.address, std::get>(op.original_value)); break; case OperationType::kWriteColor: - (void)rom_.WriteWord(op.address, std::get(op.original_value)); + (void)rom_.WriteWord(op.address, + std::get(op.original_value)); break; } } @@ -126,15 +141,22 @@ class Transaction { } private: - enum class OperationType { kWriteByte, kWriteWord, kWriteLong, kWriteVector, kWriteColor }; + enum class OperationType { + kWriteByte, + kWriteWord, + kWriteLong, + kWriteVector, + kWriteColor + }; struct Operation { int address; - std::variant> original_value; + std::variant> + original_value; OperationType type; }; - Rom &rom_; + Rom& rom_; absl::Status status_; std::vector operations_; }; diff --git a/src/cli/agent.cmake b/src/cli/agent.cmake index bfadee48..b41464e0 100644 --- a/src/cli/agent.cmake +++ b/src/cli/agent.cmake @@ -71,6 +71,9 @@ set(YAZE_AGENT_CORE_SOURCES cli/service/testing/test_suite_writer.cc cli/service/testing/test_workflow_generator.cc cli/service/ai/ai_service.cc + cli/service/ai/model_registry.cc + cli/service/api/http_server.cc + cli/service/api/api_handlers.cc # Advanced features # CommandHandler-based implementations @@ -123,12 +126,15 @@ set(_yaze_agent_link_targets yaze_zelda3 yaze_emulator ${ABSL_TARGETS} - yaml-cpp ftxui::screen ftxui::dom ftxui::component ) +if(YAZE_ENABLE_AI_RUNTIME) + list(APPEND _yaze_agent_link_targets yaml-cpp) +endif() + target_link_libraries(yaze_agent PUBLIC ${_yaze_agent_link_targets}) target_include_directories(yaze_agent diff --git a/src/cli/cli.cc b/src/cli/cli.cc index effa9ada..8f90c758 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -1,12 +1,12 @@ #include "cli/cli.h" -#include "cli/service/command_registry.h" -#include "cli/handlers/command_handlers.h" -#include "cli/z3ed_ascii_logo.h" -#include "absl/strings/str_join.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "cli/handlers/command_handlers.h" +#include "cli/service/command_registry.h" +#include "cli/tui/chat_tui.h" +#include "cli/z3ed_ascii_logo.h" #include "ftxui/dom/elements.hpp" #include "ftxui/dom/table.hpp" -#include "cli/tui/chat_tui.h" namespace yaze { namespace cli { @@ -37,10 +37,10 @@ absl::Status ModernCLI::Run(int argc, char* argv[]) { // Attempt to load a ROM from the current directory or a well-known path auto rom_status = rom.LoadFromFile("zelda3.sfc"); if (!rom_status.ok()) { - // Try assets directory as a fallback - rom_status = rom.LoadFromFile("assets/zelda3.sfc"); + // Try assets directory as a fallback + rom_status = rom.LoadFromFile("assets/zelda3.sfc"); } - + tui::ChatTUI chat_tui(rom.is_loaded() ? &rom : nullptr); chat_tui.Run(); return absl::OkStatus(); @@ -63,10 +63,10 @@ absl::Status ModernCLI::Run(int argc, char* argv[]) { // Use CommandRegistry for unified command execution auto& registry = CommandRegistry::Instance(); - + std::string command_name = args[0]; std::vector command_args(args.begin() + 1, args.end()); - + if (registry.HasCommand(command_name)) { return registry.Execute(command_name, command_args, nullptr); } @@ -75,150 +75,156 @@ absl::Status ModernCLI::Run(int argc, char* argv[]) { } void ModernCLI::ShowHelp() { - using namespace ftxui; - auto& registry = CommandRegistry::Instance(); - auto categories = registry.GetCategories(); - - auto banner = text("🎮 Z3ED - AI-Powered ROM Editor CLI") | bold | center; - - std::vector> rows; - rows.push_back({"Category", "Commands", "Description"}); - - // Add special "agent" category first - rows.push_back({"agent", "chat, learn, todo, emulator-*", "AI conversational agent + debugging tools"}); - - // Add registry categories - for (const auto& category : categories) { - auto commands = registry.GetCommandsInCategory(category); - std::string cmd_list = commands.size() > 3 - ? absl::StrCat(commands.size(), " commands") - : absl::StrJoin(commands, ", "); - - std::string desc; - if (category == "resource") desc = "ROM resource inspection"; - else if (category == "dungeon") desc = "Dungeon editing"; - else if (category == "overworld") desc = "Overworld editing"; - else if (category == "emulator") desc = "Emulator debugging"; - else if (category == "graphics") desc = "Graphics/palette/sprites"; - else if (category == "game") desc = "Messages/dialogue/music"; - else desc = category + " commands"; - - rows.push_back({category, cmd_list, desc}); - } - - Table summary(rows); - summary.SelectAll().Border(LIGHT); - summary.SelectRow(0).Decorate(bold); + using namespace ftxui; + auto& registry = CommandRegistry::Instance(); + auto categories = registry.GetCategories(); - auto layout = vbox({ - text(yaze::cli::GetColoredLogo()), - banner, - separator(), - summary.Render(), - separator(), - text(absl::StrFormat("Total: %zu commands across %zu categories", - registry.Count(), categories.size() + 1)) | center | dim, - text("Try `z3ed agent simple-chat` for AI-powered ROM inspection") | center, - text("Use `z3ed --list-commands` for complete list") | dim | center - }); + auto banner = text("🎮 Z3ED - AI-Powered ROM Editor CLI") | bold | center; - auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); - Render(screen, layout); - screen.Print(); + std::vector> rows; + rows.push_back({"Category", "Commands", "Description"}); + + // Add special "agent" category first + rows.push_back({"agent", "chat, learn, todo, emulator-*", + "AI conversational agent + debugging tools"}); + + // Add registry categories + for (const auto& category : categories) { + auto commands = registry.GetCommandsInCategory(category); + std::string cmd_list = commands.size() > 3 + ? absl::StrCat(commands.size(), " commands") + : absl::StrJoin(commands, ", "); + + std::string desc; + if (category == "resource") + desc = "ROM resource inspection"; + else if (category == "dungeon") + desc = "Dungeon editing"; + else if (category == "overworld") + desc = "Overworld editing"; + else if (category == "emulator") + desc = "Emulator debugging"; + else if (category == "graphics") + desc = "Graphics/palette/sprites"; + else if (category == "game") + desc = "Messages/dialogue/music"; + else + desc = category + " commands"; + + rows.push_back({category, cmd_list, desc}); + } + + Table summary(rows); + summary.SelectAll().Border(LIGHT); + summary.SelectRow(0).Decorate(bold); + + auto layout = vbox( + {text(yaze::cli::GetColoredLogo()), banner, separator(), summary.Render(), + separator(), + text(absl::StrFormat("Total: %zu commands across %zu categories", + registry.Count(), categories.size() + 1)) | + center | dim, + text("Try `z3ed agent simple-chat` for AI-powered ROM inspection") | + center, + text("Use `z3ed --list-commands` for complete list") | 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; - auto& registry = CommandRegistry::Instance(); - - std::vector> rows; - rows.push_back({"Command", "Description", "Requirements"}); + using namespace ftxui; + auto& registry = CommandRegistry::Instance(); - auto commands = registry.GetCommandsInCategory(category); - for (const auto& cmd_name : commands) { - auto* metadata = registry.GetMetadata(cmd_name); - if (metadata) { - std::string requirements; - if (metadata->requires_rom) requirements += "ROM "; - if (metadata->requires_grpc) requirements += "gRPC "; - if (requirements.empty()) requirements = "—"; - - rows.push_back({cmd_name, metadata->description, requirements}); - } + std::vector> rows; + rows.push_back({"Command", "Description", "Requirements"}); + + auto commands = registry.GetCommandsInCategory(category); + for (const auto& cmd_name : commands) { + auto* metadata = registry.GetMetadata(cmd_name); + if (metadata) { + std::string requirements; + if (metadata->requires_rom) + requirements += "ROM "; + if (metadata->requires_grpc) + requirements += "gRPC "; + if (requirements.empty()) + requirements = "—"; + + rows.push_back({cmd_name, metadata->description, requirements}); } + } - if (rows.size() == 1) { - rows.push_back({"—", "No commands in this category", "—"}); - } + if (rows.size() == 1) { + rows.push_back({"—", "No commands in this category", "—"}); + } - Table detail(rows); - detail.SelectAll().Border(LIGHT); - detail.SelectRow(0).Decorate(bold); + Table detail(rows); + detail.SelectAll().Border(LIGHT); + detail.SelectRow(0).Decorate(bold); - auto layout = vbox({ - text(absl::StrCat("Category: ", category)) | bold | center, - separator(), - detail.Render(), - separator(), - text("Commands are auto-registered from CommandRegistry") | dim | center - }); + auto layout = + vbox({text(absl::StrCat("Category: ", category)) | bold | center, + separator(), detail.Render(), separator(), + text("Commands are auto-registered from CommandRegistry") | dim | + center}); - auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); - Render(screen, layout); - screen.Print(); + auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); + Render(screen, layout); + screen.Print(); } void ModernCLI::ShowCommandSummary() const { - using namespace ftxui; - auto& registry = CommandRegistry::Instance(); - - std::vector> rows; - rows.push_back({"Command", "Category", "Description"}); - - auto categories = registry.GetCategories(); - for (const auto& category : categories) { - auto commands = registry.GetCommandsInCategory(category); - for (const auto& cmd_name : commands) { - auto* metadata = registry.GetMetadata(cmd_name); - if (metadata) { - rows.push_back({cmd_name, metadata->category, metadata->description}); - } - } + using namespace ftxui; + auto& registry = CommandRegistry::Instance(); + + std::vector> rows; + rows.push_back({"Command", "Category", "Description"}); + + auto categories = registry.GetCategories(); + for (const auto& category : categories) { + auto commands = registry.GetCommandsInCategory(category); + for (const auto& cmd_name : commands) { + auto* metadata = registry.GetMetadata(cmd_name); + if (metadata) { + rows.push_back({cmd_name, metadata->category, metadata->description}); + } } + } - if (rows.size() == 1) { - rows.push_back({"—", "—", "No commands registered"}); - } + if (rows.size() == 1) { + rows.push_back({"—", "—", "No commands registered"}); + } - Table command_table(rows); - command_table.SelectAll().Border(LIGHT); - command_table.SelectRow(0).Decorate(bold); + Table command_table(rows); + command_table.SelectAll().Border(LIGHT); + command_table.SelectRow(0).Decorate(bold); - auto layout = vbox({ - text("Z3ED Command Summary") | bold | center, - separator(), - command_table.Render(), - separator(), - text(absl::StrFormat("Total: %zu commands across %zu categories", - registry.Count(), categories.size())) | center | dim, - text("Use `z3ed --tui` for interactive command palette.") | center | dim - }); + auto layout = + vbox({text("Z3ED Command Summary") | bold | center, separator(), + command_table.Render(), separator(), + text(absl::StrFormat("Total: %zu commands across %zu categories", + registry.Count(), categories.size())) | + center | dim, + text("Use `z3ed --tui` for interactive command palette.") | center | + dim}); - auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); - Render(screen, layout); - screen.Print(); + auto screen = Screen::Create(Dimension::Full(), Dimension::Fit(layout)); + Render(screen, layout); + screen.Print(); } void ModernCLI::PrintTopLevelHelp() const { - const_cast(this)->ShowHelp(); + const_cast(this)->ShowHelp(); } void ModernCLI::PrintCategoryHelp(const std::string& category) const { - const_cast(this)->ShowCategoryHelp(category); + const_cast(this)->ShowCategoryHelp(category); } void ModernCLI::PrintCommandSummary() const { - const_cast(this)->ShowCommandSummary(); + const_cast(this)->ShowCommandSummary(); } } // namespace cli diff --git a/src/cli/cli_main.cc b/src/cli/cli_main.cc index 5a799ab2..cb75102e 100644 --- a/src/cli/cli_main.cc +++ b/src/cli/cli_main.cc @@ -24,8 +24,10 @@ ABSL_FLAG(bool, tui, false, "Launch interactive Text User Interface"); ABSL_FLAG(bool, quiet, false, "Suppress non-essential output"); ABSL_FLAG(bool, version, false, "Show version information"); #ifdef YAZE_HTTP_API_ENABLED -ABSL_FLAG(int, http_port, 0, "HTTP API server port (0 = disabled, default: 8080 when enabled)"); -ABSL_FLAG(std::string, http_host, "localhost", "HTTP API server host (default: localhost)"); +ABSL_FLAG(int, http_port, 0, + "HTTP API server port (0 = disabled, default: 8080 when enabled)"); +ABSL_FLAG(std::string, http_host, "localhost", + "HTTP API server host (default: localhost)"); #endif ABSL_DECLARE_FLAG(std::string, rom); ABSL_DECLARE_FLAG(std::string, ai_provider); @@ -39,35 +41,40 @@ namespace { void PrintVersion() { std::cout << yaze::cli::GetColoredLogo() << "\n"; - std::cout << absl::StrFormat(" Version %d.%d.%d\n", - YAZE_VERSION_MAJOR, - YAZE_VERSION_MINOR, - YAZE_VERSION_PATCH); + std::cout << absl::StrFormat(" Version %d.%d.%d\n", YAZE_VERSION_MAJOR, + YAZE_VERSION_MINOR, YAZE_VERSION_PATCH); std::cout << " Yet Another Zelda3 Editor - Command Line Interface\n"; std::cout << " https://github.com/scawful/yaze\n\n"; } void PrintCompactHelp() { std::cout << yaze::cli::GetColoredLogo() << "\n"; - std::cout << " \033[1;37mYet Another Zelda3 Editor - AI-Powered CLI\033[0m\n\n"; - + std::cout + << " \033[1;37mYet Another Zelda3 Editor - AI-Powered CLI\033[0m\n\n"; + std::cout << "\033[1;36mUSAGE:\033[0m\n"; std::cout << " z3ed [command] [flags]\n"; std::cout << " z3ed --tui # Interactive TUI mode\n"; std::cout << " z3ed --version # Show version\n"; std::cout << " z3ed --help # Category help\n\n"; - + std::cout << "\033[1;36mCOMMANDS:\033[0m\n"; - std::cout << " \033[1;33magent\033[0m AI conversational agent for ROM inspection\n"; - std::cout << " \033[1;33mrom\033[0m ROM operations (info, validate, diff)\n"; - std::cout << " \033[1;33mdungeon\033[0m Dungeon inspection and editing\n"; - std::cout << " \033[1;33moverworld\033[0m Overworld inspection and editing\n"; + std::cout << " \033[1;33magent\033[0m AI conversational agent for ROM " + "inspection\n"; + std::cout << " \033[1;33mrom\033[0m ROM operations (info, validate, " + "diff)\n"; + std::cout + << " \033[1;33mdungeon\033[0m Dungeon inspection and editing\n"; + std::cout + << " \033[1;33moverworld\033[0m Overworld inspection and editing\n"; std::cout << " \033[1;33mmessage\033[0m Message/dialogue inspection\n"; - std::cout << " \033[1;33mgfx\033[0m Graphics operations (export, import)\n"; + std::cout << " \033[1;33mgfx\033[0m Graphics operations (export, " + "import)\n"; std::cout << " \033[1;33mpalette\033[0m Palette operations\n"; std::cout << " \033[1;33mpatch\033[0m Apply patches (BPS, Asar)\n"; - std::cout << " \033[1;33mproject\033[0m Project management (init, build)\n\n"; - + std::cout + << " \033[1;33mproject\033[0m Project management (init, build)\n\n"; + std::cout << "\033[1;36mCOMMON FLAGS:\033[0m\n"; std::cout << " --rom= Path to ROM file\n"; std::cout << " --tui Launch interactive TUI\n"; @@ -76,16 +83,18 @@ void PrintCompactHelp() { std::cout << " --help Show category help\n"; #ifdef YAZE_HTTP_API_ENABLED std::cout << " --http-port= HTTP API server port (0=disabled)\n"; - std::cout << " --http-host= HTTP API server host (default: localhost)\n"; + std::cout + << " --http-host= HTTP API server host (default: localhost)\n"; #endif std::cout << "\n"; - + std::cout << "\033[1;36mEXAMPLES:\033[0m\n"; std::cout << " z3ed agent test-conversation --rom=zelda3.sfc\n"; std::cout << " z3ed rom info --rom=zelda3.sfc\n"; - std::cout << " z3ed agent message-search --rom=zelda3.sfc --query=\"Master Sword\"\n"; + std::cout << " z3ed agent message-search --rom=zelda3.sfc --query=\"Master " + "Sword\"\n"; std::cout << " z3ed dungeon export --rom=zelda3.sfc --id=1\n\n"; - + std::cout << "For detailed help: z3ed --help \n"; std::cout << "For all commands: z3ed --list-commands\n\n"; } @@ -183,10 +192,11 @@ ParsedGlobals ParseGlobalFlags(int argc, char* argv[]) { } // AI provider flags - if (absl::StartsWith(token, "--ai_provider=") || + if (absl::StartsWith(token, "--ai_provider=") || absl::StartsWith(token, "--ai-provider=")) { size_t eq_pos = token.find('='); - absl::SetFlag(&FLAGS_ai_provider, std::string(token.substr(eq_pos + 1))); + absl::SetFlag(&FLAGS_ai_provider, + std::string(token.substr(eq_pos + 1))); continue; } if (token == "--ai_provider" || token == "--ai-provider") { @@ -198,7 +208,7 @@ ParsedGlobals ParseGlobalFlags(int argc, char* argv[]) { continue; } - if (absl::StartsWith(token, "--ai_model=") || + if (absl::StartsWith(token, "--ai_model=") || absl::StartsWith(token, "--ai-model=")) { size_t eq_pos = token.find('='); absl::SetFlag(&FLAGS_ai_model, std::string(token.substr(eq_pos + 1))); @@ -213,10 +223,11 @@ ParsedGlobals ParseGlobalFlags(int argc, char* argv[]) { continue; } - if (absl::StartsWith(token, "--gemini_api_key=") || + if (absl::StartsWith(token, "--gemini_api_key=") || absl::StartsWith(token, "--gemini-api-key=")) { size_t eq_pos = token.find('='); - absl::SetFlag(&FLAGS_gemini_api_key, std::string(token.substr(eq_pos + 1))); + absl::SetFlag(&FLAGS_gemini_api_key, + std::string(token.substr(eq_pos + 1))); continue; } if (token == "--gemini_api_key" || token == "--gemini-api-key") { @@ -228,10 +239,11 @@ ParsedGlobals ParseGlobalFlags(int argc, char* argv[]) { continue; } - if (absl::StartsWith(token, "--ollama_host=") || + if (absl::StartsWith(token, "--ollama_host=") || absl::StartsWith(token, "--ollama-host=")) { size_t eq_pos = token.find('='); - absl::SetFlag(&FLAGS_ollama_host, std::string(token.substr(eq_pos + 1))); + absl::SetFlag(&FLAGS_ollama_host, + std::string(token.substr(eq_pos + 1))); continue; } if (token == "--ollama_host" || token == "--ollama-host") { @@ -243,10 +255,11 @@ ParsedGlobals ParseGlobalFlags(int argc, char* argv[]) { continue; } - if (absl::StartsWith(token, "--prompt_version=") || + if (absl::StartsWith(token, "--prompt_version=") || absl::StartsWith(token, "--prompt-version=")) { size_t eq_pos = token.find('='); - absl::SetFlag(&FLAGS_prompt_version, std::string(token.substr(eq_pos + 1))); + absl::SetFlag(&FLAGS_prompt_version, + std::string(token.substr(eq_pos + 1))); continue; } if (token == "--prompt_version" || token == "--prompt-version") { @@ -258,20 +271,23 @@ ParsedGlobals ParseGlobalFlags(int argc, char* argv[]) { continue; } - if (absl::StartsWith(token, "--use_function_calling=") || + if (absl::StartsWith(token, "--use_function_calling=") || absl::StartsWith(token, "--use-function-calling=")) { size_t eq_pos = token.find('='); std::string value(token.substr(eq_pos + 1)); - absl::SetFlag(&FLAGS_use_function_calling, value == "true" || value == "1"); + absl::SetFlag(&FLAGS_use_function_calling, + value == "true" || value == "1"); continue; } - if (token == "--use_function_calling" || token == "--use-function-calling") { + if (token == "--use_function_calling" || + token == "--use-function-calling") { if (i + 1 >= argc) { result.error = "--use-function-calling flag requires a value"; return result; } std::string value(argv[++i]); - absl::SetFlag(&FLAGS_use_function_calling, value == "true" || value == "1"); + absl::SetFlag(&FLAGS_use_function_calling, + value == "true" || value == "1"); continue; } @@ -356,13 +372,14 @@ int main(int argc, char* argv[]) { auto status = http_server->Start(http_port); if (!status.ok()) { - std::cerr << "\n\033[1;31mWarning:\033[0m Failed to start HTTP API server: " - << status.message() << "\n"; + std::cerr + << "\n\033[1;31mWarning:\033[0m Failed to start HTTP API server: " + << status.message() << "\n"; std::cerr << "Continuing without HTTP API...\n\n"; http_server.reset(); } else if (!absl::GetFlag(FLAGS_quiet)) { - std::cout << "\033[1;32m✓\033[0m HTTP API server started on " - << http_host << ":" << http_port << "\n"; + std::cout << "\033[1;32m✓\033[0m HTTP API server started on " << http_host + << ":" << http_port << "\n"; std::cout << " Health check: http://" << http_host << ":" << http_port << "/api/v1/health\n"; std::cout << " Models list: http://" << http_host << ":" << http_port @@ -380,7 +397,7 @@ int main(int argc, char* argv[]) { if (!rom_path.empty()) { auto status = yaze::cli::app_context.rom.LoadFromFile(rom_path); if (!status.ok()) { - std::cerr << "\n\033[1;31mError:\033[0m Failed to load ROM: " + std::cerr << "\n\033[1;31mError:\033[0m Failed to load ROM: " << status.message() << "\n"; // Continue to TUI anyway, user can load ROM from there } diff --git a/src/cli/flags.cc b/src/cli/flags.cc index 7ed4f79e..c5866521 100644 --- a/src/cli/flags.cc +++ b/src/cli/flags.cc @@ -9,7 +9,8 @@ ABSL_FLAG(bool, mock_rom, false, // AI Service Configuration Flags ABSL_FLAG(std::string, ai_provider, "auto", - "AI provider to use: 'auto' (try gemini→ollama→mock), 'gemini', 'ollama', or 'mock'"); + "AI provider to use: 'auto' (try gemini→ollama→mock), 'gemini', " + "'ollama', or 'mock'"); ABSL_FLAG(std::string, ai_model, "", "AI model to use (provider-specific, e.g., 'llama3' for Ollama, " "'gemini-1.5-flash' for Gemini)"); @@ -20,7 +21,8 @@ ABSL_FLAG(std::string, ollama_host, "http://localhost:11434", ABSL_FLAG(std::string, prompt_version, "default", "Prompt version to use: 'default' or 'v2'"); ABSL_FLAG(bool, use_function_calling, false, - "Enable native Gemini function calling (incompatible with JSON output mode)"); + "Enable native Gemini function calling (incompatible with JSON " + "output mode)"); // --- Agent Control Flags --- ABSL_FLAG(bool, agent_control, false, diff --git a/src/cli/handlers/agent.cc b/src/cli/handlers/agent.cc index 5140dbce..afd77e8b 100644 --- a/src/cli/handlers/agent.cc +++ b/src/cli/handlers/agent.cc @@ -1,5 +1,5 @@ -#include "cli/handlers/agent/todo_commands.h" #include "cli/cli.h" +#include "cli/handlers/agent/todo_commands.h" #include #include @@ -26,13 +26,15 @@ namespace agent { absl::Status HandleRunCommand(const std::vector& args, Rom& rom); absl::Status HandlePlanCommand(const std::vector& args); absl::Status HandleTestCommand(const std::vector& args); -absl::Status HandleTestConversationCommand(const std::vector& args); +absl::Status HandleTestConversationCommand( + const std::vector& args); absl::Status HandleLearnCommand(const std::vector& args); absl::Status HandleListCommand(); absl::Status HandleDiffCommand(Rom& rom, const std::vector& args); absl::Status HandleCommitCommand(Rom& rom); absl::Status HandleRevertCommand(Rom& rom); -absl::Status HandleAcceptCommand(const std::vector& args, Rom& rom); +absl::Status HandleAcceptCommand(const std::vector& args, + Rom& rom); absl::Status HandleDescribeCommand(const std::vector& args); } // namespace agent @@ -45,10 +47,10 @@ Rom& AgentRom() { std::string GenerateAgentHelp() { auto& registry = CommandRegistry::Instance(); - + std::ostringstream help; help << "Usage: agent [options]\n\n"; - + help << "AI-Powered Agent Commands:\n"; help << " simple-chat Interactive AI chat\n"; help << " test-conversation Automated test conversation\n"; @@ -62,7 +64,7 @@ std::string GenerateAgentHelp() { help << " todo Task management\n"; help << " test Run tests\n"; help << " list/describe List/describe proposals\n\n"; - + // Auto-list available tool commands from registry help << "Tool Commands (AI can call these):\n"; auto agent_commands = registry.GetAgentCommands(); @@ -71,7 +73,8 @@ std::string GenerateAgentHelp() { const auto& cmd = agent_commands[i]; if (auto* meta = registry.GetMetadata(cmd); meta != nullptr) { help << " " << cmd; - for (size_t pad = cmd.length(); pad < 24; ++pad) help << " "; + for (size_t pad = cmd.length(); pad < 24; ++pad) + help << " "; help << meta->description << "\n"; } } @@ -80,15 +83,15 @@ std::string GenerateAgentHelp() { << " more (see z3ed --list-commands)\n"; } help << "\n"; - + help << "Global Options:\n"; help << " --rom= Path to ROM file\n"; help << " --ai_provider= AI provider: ollama | gemini\n"; help << " --format= Output format: text | json\n\n"; - + help << "For detailed help: z3ed agent --help\n"; help << "For all commands: z3ed --list-commands\n"; - + return help.str(); } @@ -114,80 +117,81 @@ 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()); - + // === Special Agent Commands (not in registry) === - + if (subcommand == "simple-chat" || subcommand == "chat") { auto& registry = CommandRegistry::Instance(); return registry.Execute("simple-chat", subcommand_args, nullptr); } - + auto& agent_rom = AgentRom(); if (subcommand == "run") { return agent::HandleRunCommand(subcommand_args, agent_rom); } - + if (subcommand == "plan") { return agent::HandlePlanCommand(subcommand_args); } - + if (subcommand == "diff") { return agent::HandleDiffCommand(agent_rom, subcommand_args); } - + if (subcommand == "accept") { return agent::HandleAcceptCommand(subcommand_args, agent_rom); } - + if (subcommand == "commit") { return agent::HandleCommitCommand(agent_rom); } - + if (subcommand == "revert") { return agent::HandleRevertCommand(agent_rom); } - + if (subcommand == "test") { return agent::HandleTestCommand(subcommand_args); } - + if (subcommand == "test-conversation") { return agent::HandleTestConversationCommand(subcommand_args); } - + if (subcommand == "gui") { // GUI commands are in the registry (gui-place-tile, gui-click, etc.) // Route to registry instead return absl::InvalidArgumentError( - "Use 'z3ed gui-' or see 'z3ed --help gui' for available GUI automation commands"); + "Use 'z3ed gui-' or see 'z3ed --help gui' for available GUI " + "automation commands"); } - + if (subcommand == "learn") { return agent::HandleLearnCommand(subcommand_args); } - + if (subcommand == "todo") { return handlers::HandleTodoCommand(subcommand_args); } - + if (subcommand == "list") { return agent::HandleListCommand(); } - + if (subcommand == "describe") { return agent::HandleDescribeCommand(subcommand_args); } - + // === Registry Commands (resource, dungeon, overworld, emulator, etc.) === - + auto& registry = CommandRegistry::Instance(); - + // Check if this is a registered command if (registry.HasCommand(subcommand)) { return registry.Execute(subcommand, subcommand_args, nullptr); } - + // Not found std::cout << GenerateAgentHelp(); return absl::InvalidArgumentError( diff --git a/src/cli/handlers/agent/common.cc b/src/cli/handlers/agent/common.cc index aaef9072..bd94f44e 100644 --- a/src/cli/handlers/agent/common.cc +++ b/src/cli/handlers/agent/common.cc @@ -41,7 +41,8 @@ std::string JsonEscape(absl::string_view value) { break; default: if (c < 0x20) { - absl::StrAppend(&out, absl::StrFormat("\\u%04X", static_cast(c))); + absl::StrAppend(&out, + absl::StrFormat("\\u%04X", static_cast(c))); } else { out.push_back(static_cast(c)); } @@ -120,12 +121,18 @@ bool IsTerminalStatus(TestRunStatus status) { std::optional ParseStatusFilter(absl::string_view value) { std::string lower = std::string(absl::AsciiStrToLower(value)); - if (lower == "queued") return TestRunStatus::kQueued; - if (lower == "running") return TestRunStatus::kRunning; - if (lower == "passed") return TestRunStatus::kPassed; - if (lower == "failed") return TestRunStatus::kFailed; - if (lower == "timeout") return TestRunStatus::kTimeout; - if (lower == "unknown") return TestRunStatus::kUnknown; + if (lower == "queued") + return TestRunStatus::kQueued; + if (lower == "running") + return TestRunStatus::kRunning; + if (lower == "passed") + return TestRunStatus::kPassed; + if (lower == "failed") + return TestRunStatus::kFailed; + if (lower == "timeout") + return TestRunStatus::kTimeout; + if (lower == "unknown") + return TestRunStatus::kUnknown; return std::nullopt; } diff --git a/src/cli/handlers/agent/conversation_test.cc b/src/cli/handlers/agent/conversation_test.cc index 34b6d3cd..b20b91a7 100644 --- a/src/cli/handlers/agent/conversation_test.cc +++ b/src/cli/handlers/agent/conversation_test.cc @@ -1,13 +1,13 @@ #include "app/rom.h" -#include "core/project.h" #include "cli/handlers/rom/mock_rom.h" +#include "core/project.h" -#include "absl/flags/declare.h" -#include "absl/flags/flag.h" #include #include #include #include +#include "absl/flags/declare.h" +#include "absl/flags/flag.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" @@ -50,9 +50,8 @@ absl::Status LoadRomForAgent(Rom& rom) { auto status = rom.LoadFromFile(rom_path); if (!status.ok()) { - return ::absl::FailedPreconditionError( - ::absl::StrCat("Failed to load ROM from '", rom_path, - "': ", status.message())); + return ::absl::FailedPreconditionError(::absl::StrCat( + "Failed to load ROM from '", rom_path, "': ", status.message())); } return ::absl::OkStatus(); @@ -62,7 +61,8 @@ struct ConversationTestCase { std::string name; std::string description; std::vector user_prompts; - std::vector expected_keywords; // Keywords to look for in responses + std::vector + expected_keywords; // Keywords to look for in responses bool expect_tool_calls = false; bool expect_commands = false; }; @@ -120,10 +120,11 @@ std::vector GetDefaultTestCases() { { .name = "multi_step_query", .description = "Ask multiple questions in sequence", - .user_prompts = { - "What is the name of room 0?", - "What sprites are defined in the game?", - }, + .user_prompts = + { + "What is the name of room 0?", + "What sprites are defined in the game?", + }, .expected_keywords = {"Ganon", "sprite", "room"}, .expect_tool_calls = true, .expect_commands = false, @@ -160,7 +161,7 @@ void PrintAgentResponse(const ChatMessage& response, bool verbose) { if (response.table_data.has_value()) { std::cout << "📊 Table Output:\n"; const auto& table = response.table_data.value(); - + // Print headers std::cout << " "; for (size_t i = 0; i < table.headers.size(); ++i) { @@ -177,7 +178,7 @@ void PrintAgentResponse(const ChatMessage& response, bool verbose) { } } std::cout << "\n"; - + // Print rows (limit to 10 for readability) const size_t max_rows = std::min(10, table.rows.size()); for (size_t i = 0; i < max_rows; ++i) { @@ -190,9 +191,9 @@ void PrintAgentResponse(const ChatMessage& response, bool verbose) { } std::cout << "\n"; } - + if (!verbose && table.rows.size() > max_rows) { - std::cout << " ... (" << (table.rows.size() - max_rows) + std::cout << " ... (" << (table.rows.size() - max_rows) << " more rows)\n"; } @@ -215,65 +216,65 @@ void PrintAgentResponse(const ChatMessage& response, bool verbose) { bool ValidateResponse(const ChatMessage& response, const ConversationTestCase& test_case) { bool passed = true; - + // Check for expected keywords for (const auto& keyword : test_case.expected_keywords) { if (response.message.find(keyword) == std::string::npos) { - std::cout << "⚠️ Warning: Expected keyword '" << keyword + std::cout << "⚠️ Warning: Expected keyword '" << keyword << "' not found in response\n"; // Don't fail test, just warn } } - + // Check for tool calls (if we have table data, tools were likely called) if (test_case.expect_tool_calls && !response.table_data.has_value()) { std::cout << "⚠️ Warning: Expected tool calls but no table data found\n"; } - + // Check for commands if (test_case.expect_commands) { - bool has_commands = response.message.find("overworld") != std::string::npos || - response.message.find("dungeon") != std::string::npos || - response.message.find("set-tile") != std::string::npos; + bool has_commands = + response.message.find("overworld") != std::string::npos || + response.message.find("dungeon") != std::string::npos || + response.message.find("set-tile") != std::string::npos; if (!has_commands) { std::cout << "⚠️ Warning: Expected commands but none found\n"; } } - + return passed; } absl::Status RunTestCase(const ConversationTestCase& test_case, - ConversationalAgentService& service, - bool verbose) { + ConversationalAgentService& service, bool verbose) { PrintTestHeader(test_case); - + bool all_passed = true; service.ResetConversation(); - + for (const auto& prompt : test_case.user_prompts) { PrintUserPrompt(prompt); - + auto response_or = service.SendMessage(prompt); if (!response_or.ok()) { std::cout << "❌ FAILED: " << response_or.status().message() << "\n\n"; all_passed = false; continue; } - + const auto& response = response_or.value(); PrintAgentResponse(response, verbose); - + if (!ValidateResponse(response, test_case)) { all_passed = false; } } - + if (verbose) { const auto& history = service.GetHistory(); - std::cout << "🗂 Conversation Summary (" << history.size() - << " message" << (history.size() == 1 ? "" : "s") << ")\n"; + std::cout << "🗂 Conversation Summary (" << history.size() << " message" + << (history.size() == 1 ? "" : "s") << ")\n"; for (const auto& message : history) { const char* sender = message.sender == ChatMessage::Sender::kUser ? "User" : "Agent"; @@ -292,14 +293,15 @@ absl::Status RunTestCase(const ConversationTestCase& test_case, absl::StrCat("Conversation test failed validation: ", test_case.name)); } -absl::Status LoadTestCasesFromFile(const std::string& file_path, - std::vector* test_cases) { +absl::Status LoadTestCasesFromFile( + const std::string& file_path, + std::vector* test_cases) { std::ifstream file(file_path); if (!file.is_open()) { return absl::NotFoundError( absl::StrCat("Could not open test file: ", file_path)); } - + nlohmann::json test_json; try { file >> test_json; @@ -307,17 +309,17 @@ absl::Status LoadTestCasesFromFile(const std::string& file_path, return absl::InvalidArgumentError( absl::StrCat("Failed to parse test file: ", e.what())); } - + if (!test_json.is_array()) { return absl::InvalidArgumentError( "Test file must contain a JSON array of test cases"); } - + for (const auto& test_obj : test_json) { ConversationTestCase test_case; test_case.name = test_obj.value("name", "unnamed_test"); test_case.description = test_obj.value("description", ""); - + if (test_obj.contains("prompts") && test_obj["prompts"].is_array()) { for (const auto& prompt : test_obj["prompts"]) { if (prompt.is_string()) { @@ -325,8 +327,8 @@ absl::Status LoadTestCasesFromFile(const std::string& file_path, } } } - - if (test_obj.contains("expected_keywords") && + + if (test_obj.contains("expected_keywords") && test_obj["expected_keywords"].is_array()) { for (const auto& keyword : test_obj["expected_keywords"]) { if (keyword.is_string()) { @@ -334,13 +336,13 @@ absl::Status LoadTestCasesFromFile(const std::string& file_path, } } } - + test_case.expect_tool_calls = test_obj.value("expect_tool_calls", false); test_case.expect_commands = test_obj.value("expect_commands", false); - + test_cases->push_back(test_case); } - + return absl::OkStatus(); } @@ -351,7 +353,7 @@ absl::Status HandleTestConversationCommand( std::string test_file; bool use_defaults = true; bool verbose = false; - + for (size_t i = 0; i < arg_vec.size(); ++i) { const std::string& arg = arg_vec[i]; if (arg == "--file" && i + 1 < arg_vec.size()) { @@ -362,9 +364,9 @@ absl::Status HandleTestConversationCommand( verbose = true; } } - + std::cout << "🔍 Debug: Starting test-conversation handler...\n"; - + // Load ROM context Rom rom; std::cout << "🔍 Debug: Loading ROM...\n"; @@ -373,20 +375,20 @@ absl::Status HandleTestConversationCommand( std::cerr << "❌ Error loading ROM: " << load_status.message() << "\n"; return load_status; } - + std::cout << "✅ ROM loaded: " << rom.title() << "\n"; - + // Load embedded labels for natural language queries std::cout << "🔍 Debug: Initializing embedded labels...\n"; project::YazeProject project; auto labels_status = project.InitializeEmbeddedLabels(); if (!labels_status.ok()) { - std::cerr << "⚠️ Warning: Could not initialize embedded labels: " + std::cerr << "⚠️ Warning: Could not initialize embedded labels: " << labels_status.message() << "\n"; } else { std::cout << "✅ Embedded labels initialized successfully\n"; } - + // Associate labels with ROM if it has a resource label manager std::cout << "🔍 Debug: Checking resource label manager...\n"; if (rom.resource_label() && project.use_embedded_labels) { @@ -397,51 +399,52 @@ absl::Status HandleTestConversationCommand( } else { std::cout << "⚠️ ROM has no resource label manager\n"; } - + // Create conversational agent service std::cout << "🔍 Debug: Creating conversational agent service...\n"; std::cout << "🔍 Debug: About to construct service object...\n"; - + ConversationalAgentService service; std::cout << "✅ Service object created\n"; - + std::cout << "🔍 Debug: Setting ROM context...\n"; service.SetRomContext(&rom); std::cout << "✅ Service initialized\n"; - + // Load test cases std::vector test_cases; if (use_defaults) { test_cases = GetDefaultTestCases(); - std::cout << "Using default test cases (" << test_cases.size() << " tests)\n"; + std::cout << "Using default test cases (" << test_cases.size() + << " tests)\n"; } else { auto status = LoadTestCasesFromFile(test_file, &test_cases); if (!status.ok()) { return status; } - std::cout << "Loaded " << test_cases.size() << " test cases from " + std::cout << "Loaded " << test_cases.size() << " test cases from " << test_file << "\n"; } - + if (test_cases.empty()) { return absl::InvalidArgumentError("No test cases to run"); } - + // Run all test cases int passed = 0; int failed = 0; - + for (const auto& test_case : test_cases) { auto status = RunTestCase(test_case, service, verbose); if (status.ok()) { ++passed; } else { ++failed; - std::cerr << "Test case '" << test_case.name << "' failed: " - << status.message() << "\n"; + std::cerr << "Test case '" << test_case.name + << "' failed: " << status.message() << "\n"; } } - + // Print summary std::cout << "\n===========================================\n"; std::cout << "Test Summary\n"; @@ -449,13 +452,13 @@ absl::Status HandleTestConversationCommand( std::cout << "Total tests: " << test_cases.size() << "\n"; std::cout << "Passed: " << passed << "\n"; std::cout << "Failed: " << failed << "\n"; - + if (failed == 0) { std::cout << "\n✅ All tests passed!\n"; } else { std::cout << "\n⚠️ Some tests failed\n"; } - + if (failed == 0) { return absl::OkStatus(); } diff --git a/src/cli/handlers/agent/general_commands.cc b/src/cli/handlers/agent/general_commands.cc index a301c959..f9572b29 100644 --- a/src/cli/handlers/agent/general_commands.cc +++ b/src/cli/handlers/agent/general_commands.cc @@ -10,32 +10,30 @@ #include "absl/flags/declare.h" #include "absl/flags/flag.h" #include "absl/status/status.h" -#include "absl/status/status.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 "absl/strings/string_view.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "absl/strings/string_view.h" -#include "core/project.h" -#include "zelda3/dungeon/room.h" -#include "cli/handlers/agent/common.h" #include "cli/cli.h" +#include "cli/handlers/agent/common.h" +#include "cli/service/agent/learned_knowledge_service.h" +#include "cli/service/agent/proposal_executor.h" #include "cli/service/ai/ai_service.h" #include "cli/service/ai/gemini_ai_service.h" #include "cli/service/ai/ollama_ai_service.h" #include "cli/service/ai/service_factory.h" -#include "cli/service/agent/learned_knowledge_service.h" -#include "cli/service/agent/proposal_executor.h" #include "cli/service/planning/proposal_registry.h" #include "cli/service/planning/tile16_proposal_generator.h" #include "cli/service/resources/resource_catalog.h" #include "cli/service/resources/resource_context_builder.h" #include "cli/service/rom/rom_sandbox_manager.h" -#include "cli/cli.h" +#include "core/project.h" #include "util/macro.h" +#include "zelda3/dungeon/room.h" ABSL_DECLARE_FLAG(std::string, rom); ABSL_DECLARE_FLAG(std::string, ai_provider); @@ -54,43 +52,47 @@ struct DescribeOptions { std::optional last_updated; }; - // Helper to load project and labels if available absl::Status TryLoadProjectAndLabels(Rom& rom) { // Try to find and load a project file in current directory project::YazeProject project; auto project_status = project.Open("."); - + if (project_status.ok()) { std::cout << "📂 Loaded project: " << project.name << "\n"; - + // Initialize embedded labels (all default Zelda3 resource names) auto labels_status = project.InitializeEmbeddedLabels(); if (labels_status.ok()) { - std::cout << "✅ Embedded labels initialized (all Zelda3 resources available)\n"; + std::cout << "✅ Embedded labels initialized (all Zelda3 resources " + "available)\n"; } - + // Load labels from project (either embedded or external) if (!project.labels_filename.empty()) { auto* label_mgr = rom.resource_label(); if (label_mgr && label_mgr->LoadLabels(project.labels_filename)) { - std::cout << "🏷️ Loaded custom labels from: " << project.labels_filename << "\n"; + std::cout << "🏷️ Loaded custom labels from: " + << project.labels_filename << "\n"; } - } else if (!project.resource_labels.empty() || project.use_embedded_labels) { + } else if (!project.resource_labels.empty() || + project.use_embedded_labels) { // Use labels embedded in project or default Zelda3 labels auto* label_mgr = rom.resource_label(); if (label_mgr) { label_mgr->labels_ = project.resource_labels; label_mgr->labels_loaded_ = true; - std::cout << "🏷️ Using embedded Zelda3 labels (rooms, sprites, entrances, items, etc.)\n"; + std::cout << "🏷️ Using embedded Zelda3 labels (rooms, sprites, " + "entrances, items, etc.)\n"; } } } else { // No project found - use embedded defaults anyway - std::cout << "ℹ️ No project file found. Using embedded default Zelda3 labels.\n"; + std::cout + << "ℹ️ No project file found. Using embedded default Zelda3 labels.\n"; project.InitializeEmbeddedLabels(); } - + return absl::OkStatus(); } @@ -102,10 +104,9 @@ absl::Status EnsureRomLoaded(Rom& rom, const std::string& command) { std::string rom_path = absl::GetFlag(FLAGS_rom); if (rom_path.empty()) { return absl::FailedPreconditionError( - absl::StrFormat( - "No ROM loaded. Pass --rom= when running %s.\n" - "Example: z3ed %s --rom=zelda3.sfc", - command, command)); + absl::StrFormat("No ROM loaded. Pass --rom= when running %s.\n" + "Example: z3ed %s --rom=zelda3.sfc", + command, command)); } // Load the ROM @@ -223,8 +224,8 @@ absl::Status HandleRunCommand(const std::vector& arg_vec, std::cout << " Log file: " << metadata.log_path << std::endl; std::cout << " Proposal JSON: " << proposal_result.proposal_json_path << std::endl; - std::cout << " Commands executed: " - << proposal_result.executed_commands << std::endl; + std::cout << " Commands executed: " << proposal_result.executed_commands + << std::endl; std::cout << " Tile16 changes: " << proposal_result.change_count << std::endl; std::cout << "\nTo review the changes, run:\n"; @@ -262,13 +263,14 @@ absl::Status HandlePlanCommand(const std::vector& arg_vec) { std::error_code ec; std::filesystem::create_directories(plans_dir, ec); if (ec) { - return absl::InternalError(absl::StrCat("Failed to create plans directory: ", ec.message())); + return absl::InternalError( + absl::StrCat("Failed to create plans directory: ", ec.message())); } auto plan_path = plans_dir / (proposal.id + ".json"); auto save_status = generator.SaveProposal(proposal, plan_path.string()); if (!save_status.ok()) { - return save_status; + return save_status; } std::cout << "AI Agent Plan (Proposal ID: " << proposal.id << "):\n"; @@ -326,8 +328,8 @@ absl::Status HandleDiffCommand(Rom& rom, const std::vector& args) { if (!proposal.sandbox_rom_path.empty()) { std::cout << "Sandbox ROM: " << proposal.sandbox_rom_path << "\n"; } - std::cout << "Proposal directory: " - << proposal.log_path.parent_path() << "\n"; + std::cout << "Proposal directory: " << proposal.log_path.parent_path() + << "\n"; std::cout << "Diff file: " << proposal.diff_path << "\n"; std::cout << "Log file: " << proposal.log_path << "\n\n"; @@ -385,7 +387,8 @@ absl::Status HandleDiffCommand(Rom& rom, const std::vector& args) { } // TODO: Use new CommandHandler system for RomDiff // Reference: src/app/rom.cc (Rom comparison methods) - auto status = absl::UnimplementedError("RomDiff not yet implemented in new CommandHandler system"); + auto status = absl::UnimplementedError( + "RomDiff not yet implemented in new CommandHandler system"); if (!status.ok()) { return status; } @@ -398,17 +401,17 @@ absl::Status HandleDiffCommand(Rom& rom, const std::vector& args) { absl::Status HandleLearnCommand(const std::vector& args) { static yaze::cli::agent::LearnedKnowledgeService learn_service; static bool initialized = false; - + if (!initialized) { auto status = learn_service.Initialize(); if (!status.ok()) { - std::cerr << "Failed to initialize learned knowledge service: " + std::cerr << "Failed to initialize learned knowledge service: " << status.message() << std::endl; return status; } initialized = true; } - + if (args.empty()) { // Show usage std::cout << "\nUsage: z3ed agent learn [options]\n\n"; @@ -421,7 +424,8 @@ absl::Status HandleLearnCommand(const std::vector& args) { std::cout << " --project --context Save project context\n"; std::cout << " --get-project Get project context\n"; std::cout << " --list-projects List all projects\n"; - std::cout << " --memory --summary Store conversation memory\n"; + std::cout + << " --memory --summary Store conversation memory\n"; std::cout << " --search-memories Search memories\n"; std::cout << " --recent-memories [limit] Show recent memories\n"; std::cout << " --export Export all data to JSON\n"; @@ -430,15 +434,16 @@ absl::Status HandleLearnCommand(const std::vector& args) { std::cout << " --clear Clear all learned data\n"; return absl::OkStatus(); } - + // Parse arguments std::string command = args[0]; - + if (command == "--preference" && args.size() >= 2) { std::string pref = args[1]; size_t eq_pos = pref.find('='); if (eq_pos == std::string::npos) { - return absl::InvalidArgumentError("Preference must be in format key=value"); + return absl::InvalidArgumentError( + "Preference must be in format key=value"); } std::string key = pref.substr(0, eq_pos); std::string value = pref.substr(eq_pos + 1); @@ -448,7 +453,7 @@ absl::Status HandleLearnCommand(const std::vector& args) { } return status; } - + if (command == "--get-preference" && args.size() >= 2) { auto value = learn_service.GetPreference(args[1]); if (value) { @@ -458,7 +463,7 @@ absl::Status HandleLearnCommand(const std::vector& args) { } return absl::OkStatus(); } - + if (command == "--list-preferences") { auto prefs = learn_service.GetAllPreferences(); if (prefs.empty()) { @@ -471,7 +476,7 @@ absl::Status HandleLearnCommand(const std::vector& args) { } return absl::OkStatus(); } - + if (command == "--stats") { auto stats = learn_service.GetStats(); std::cout << "\n=== Learned Knowledge Statistics ===\n"; @@ -479,11 +484,15 @@ absl::Status HandleLearnCommand(const std::vector& args) { std::cout << " ROM Patterns: " << stats.pattern_count << "\n"; std::cout << " Projects: " << stats.project_count << "\n"; std::cout << " Memories: " << stats.memory_count << "\n"; - std::cout << " First learned: " << absl::FormatTime(absl::FromUnixMillis(stats.first_learned_at)) << "\n"; - std::cout << " Last updated: " << absl::FormatTime(absl::FromUnixMillis(stats.last_updated_at)) << "\n"; + std::cout << " First learned: " + << absl::FormatTime(absl::FromUnixMillis(stats.first_learned_at)) + << "\n"; + std::cout << " Last updated: " + << absl::FormatTime(absl::FromUnixMillis(stats.last_updated_at)) + << "\n"; return absl::OkStatus(); } - + if (command == "--export" && args.size() >= 2) { auto json = learn_service.ExportToJSON(); if (!json.ok()) { @@ -497,7 +506,7 @@ absl::Status HandleLearnCommand(const std::vector& args) { std::cout << "✓ Exported learned data to " << args[1] << "\n"; return absl::OkStatus(); } - + if (command == "--import" && args.size() >= 2) { std::ifstream file(args[1]); if (!file.is_open()) { @@ -511,7 +520,7 @@ absl::Status HandleLearnCommand(const std::vector& args) { } return status; } - + if (command == "--clear") { auto status = learn_service.ClearAll(); if (status.ok()) { @@ -519,7 +528,7 @@ absl::Status HandleLearnCommand(const std::vector& args) { } return status; } - + if (command == "--list-projects") { auto projects = learn_service.GetAllProjects(); if (projects.empty()) { @@ -529,12 +538,14 @@ absl::Status HandleLearnCommand(const std::vector& args) { for (const auto& proj : projects) { std::cout << " " << proj.project_name << "\n"; std::cout << " ROM Hash: " << proj.rom_hash.substr(0, 16) << "...\n"; - std::cout << " Last Accessed: " << absl::FormatTime(absl::FromUnixMillis(proj.last_accessed)) << "\n"; + std::cout << " Last Accessed: " + << absl::FormatTime(absl::FromUnixMillis(proj.last_accessed)) + << "\n"; } } return absl::OkStatus(); } - + if (command == "--recent-memories") { int limit = 10; if (args.size() >= 2) { @@ -549,14 +560,17 @@ absl::Status HandleLearnCommand(const std::vector& args) { std::cout << " Topic: " << mem.topic << "\n"; std::cout << " Summary: " << mem.summary << "\n"; std::cout << " Facts: " << mem.key_facts.size() << " key facts\n"; - std::cout << " Created: " << absl::FormatTime(absl::FromUnixMillis(mem.created_at)) << "\n"; + std::cout << " Created: " + << absl::FormatTime(absl::FromUnixMillis(mem.created_at)) + << "\n"; std::cout << "\n"; } } - return absl::OkStatus(); + return absl::OkStatus(); } - - return absl::InvalidArgumentError("Unknown learn command. Use 'z3ed agent learn' for usage."); + + return absl::InvalidArgumentError( + "Unknown learn command. Use 'z3ed agent learn' for usage."); } absl::Status HandleListCommand() { @@ -713,9 +727,9 @@ absl::Status HandleAcceptCommand(const std::vector& arg_vec, } if (metadata.sandbox_rom_path.empty()) { - return absl::FailedPreconditionError(absl::StrCat( - "Proposal '", *proposal_id, - "' is missing sandbox ROM metadata. Cannot accept.")); + return absl::FailedPreconditionError( + absl::StrCat("Proposal '", *proposal_id, + "' is missing sandbox ROM metadata. Cannot accept.")); } if (!std::filesystem::exists(metadata.sandbox_rom_path)) { @@ -730,8 +744,8 @@ absl::Status HandleAcceptCommand(const std::vector& arg_vec, auto sandbox_load_status = sandbox_rom.LoadFromFile( metadata.sandbox_rom_path.string(), RomLoadOptions::CliDefaults()); if (!sandbox_load_status.ok()) { - return absl::InternalError(absl::StrCat( - "Failed to load sandbox ROM: ", sandbox_load_status.message())); + return absl::InternalError(absl::StrCat("Failed to load sandbox ROM: ", + sandbox_load_status.message())); } if (rom.size() != sandbox_rom.size()) { @@ -740,8 +754,8 @@ absl::Status HandleAcceptCommand(const std::vector& arg_vec, auto copy_status = rom.WriteVector(0, sandbox_rom.vector()); if (!copy_status.ok()) { - return absl::InternalError(absl::StrCat( - "Failed to copy sandbox ROM data: ", copy_status.message())); + return absl::InternalError(absl::StrCat("Failed to copy sandbox ROM data: ", + copy_status.message())); } auto save_status = rom.SaveToFile({.save_new = false}); diff --git a/src/cli/handlers/agent/simple_chat_command.cc b/src/cli/handlers/agent/simple_chat_command.cc index c66c478d..4c51ba3a 100644 --- a/src/cli/handlers/agent/simple_chat_command.cc +++ b/src/cli/handlers/agent/simple_chat_command.cc @@ -26,24 +26,32 @@ absl::Status SimpleChatCommandHandler::Execute( // Determine desired output format std::optional format_arg = parser.GetString("format"); - if (parser.HasFlag("json")) format_arg = "json"; - if (parser.HasFlag("markdown") || parser.HasFlag("md")) format_arg = "markdown"; - if (parser.HasFlag("compact") || parser.HasFlag("raw")) format_arg = "compact"; + if (parser.HasFlag("json")) + format_arg = "json"; + if (parser.HasFlag("markdown") || parser.HasFlag("md")) + format_arg = "markdown"; + if (parser.HasFlag("compact") || parser.HasFlag("raw")) + format_arg = "compact"; - auto select_format = [](absl::string_view value) - -> std::optional { + auto select_format = + [](absl::string_view value) -> std::optional { std::string normalized = absl::AsciiStrToLower(value); - if (normalized == "json") return agent::AgentOutputFormat::kJson; - if (normalized == "markdown" || normalized == "md") return agent::AgentOutputFormat::kMarkdown; - if (normalized == "compact" || normalized == "raw") return agent::AgentOutputFormat::kCompact; - if (normalized == "text" || normalized == "friendly" || normalized == "pretty") { + if (normalized == "json") + return agent::AgentOutputFormat::kJson; + if (normalized == "markdown" || normalized == "md") + return agent::AgentOutputFormat::kMarkdown; + if (normalized == "compact" || normalized == "raw") + return agent::AgentOutputFormat::kCompact; + if (normalized == "text" || normalized == "friendly" || + normalized == "pretty") { return agent::AgentOutputFormat::kFriendly; } return std::nullopt; }; if (format_arg.has_value()) { - if (auto output_format = select_format(*format_arg); output_format.has_value()) { + if (auto output_format = select_format(*format_arg); + output_format.has_value()) { config.output_format = *output_format; } else { return absl::InvalidArgumentError( diff --git a/src/cli/handlers/agent/simple_chat_command.h b/src/cli/handlers/agent/simple_chat_command.h index 4400106b..e07e6688 100644 --- a/src/cli/handlers/agent/simple_chat_command.h +++ b/src/cli/handlers/agent/simple_chat_command.h @@ -10,9 +10,12 @@ 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 GetDescription() const { + return "Simple text-based chat with the AI agent."; + } std::string GetUsage() const override { - return "simple-chat [--prompt ] [--file ] [--format ]"; + return "simple-chat [--prompt ] [--file ] [--format " + "]"; } protected: diff --git a/src/cli/handlers/agent/test_commands.cc b/src/cli/handlers/agent/test_commands.cc index 5e360902..82920e45 100644 --- a/src/cli/handlers/agent/test_commands.cc +++ b/src/cli/handlers/agent/test_commands.cc @@ -37,8 +37,7 @@ struct RecordingState { std::filesystem::path RecordingStateFilePath() { std::error_code ec; - std::filesystem::path base = - std::filesystem::temp_directory_path(ec); + std::filesystem::path base = std::filesystem::temp_directory_path(ec); if (ec) { base = std::filesystem::current_path(); } @@ -57,8 +56,8 @@ absl::Status SaveRecordingState(const RecordingState& state) { std::ofstream out(path, std::ios::out | std::ios::trunc); if (!out.is_open()) { - return absl::InternalError(absl::StrCat("Failed to write recording state to ", - path.string())); + return absl::InternalError( + absl::StrCat("Failed to write recording state to ", path.string())); } out << json.dump(2); if (!out.good()) { @@ -72,7 +71,9 @@ absl::StatusOr LoadRecordingState() { auto path = RecordingStateFilePath(); std::ifstream in(path); if (!in.is_open()) { - return absl::NotFoundError("No active recording session found. Run 'z3ed agent test record start' first."); + return absl::NotFoundError( + "No active recording session found. Run 'z3ed agent test record start' " + "first."); } nlohmann::json json; @@ -80,8 +81,8 @@ absl::StatusOr LoadRecordingState() { in >> json; } catch (const nlohmann::json::parse_error& error) { return absl::InternalError( - absl::StrCat("Failed to parse recording state at ", path.string(), - ": ", error.what())); + absl::StrCat("Failed to parse recording state at ", path.string(), ": ", + error.what())); } RecordingState state; @@ -91,9 +92,8 @@ absl::StatusOr LoadRecordingState() { state.output_path = json.value("output_path", ""); if (state.recording_id.empty()) { - return absl::InvalidArgumentError( - absl::StrCat("Recording state at ", path.string(), - " is missing a recording_id")); + return absl::InvalidArgumentError(absl::StrCat( + "Recording state at ", path.string(), " is missing a recording_id")); } return state; @@ -104,18 +104,17 @@ absl::Status ClearRecordingState() { std::error_code ec; std::filesystem::remove(path, ec); if (ec && ec != std::errc::no_such_file_or_directory) { - return absl::InternalError(absl::StrCat("Failed to clear recording state: ", - ec.message())); + return absl::InternalError( + absl::StrCat("Failed to clear recording state: ", ec.message())); } return absl::OkStatus(); } std::string DefaultRecordingOutputPath() { absl::Time now = absl::Now(); - return absl::StrCat("tests/gui/recording-", - absl::FormatTime("%Y%m%dT%H%M%S", now, - absl::LocalTimeZone()), - ".json"); + return absl::StrCat( + "tests/gui/recording-", + absl::FormatTime("%Y%m%dT%H%M%S", now, absl::LocalTimeZone()), ".json"); } } // namespace @@ -131,8 +130,10 @@ absl::Status HandleTestRecordCommand(const std::vector& args); absl::Status HandleTestRunCommand(const std::vector& args) { if (args.empty() || args[0] != "--prompt") { return absl::InvalidArgumentError( - "Usage: agent test run --prompt [--host ] [--port ]\n" - "Example: agent test run --prompt \"Open the overworld editor and verify it loads\""); + "Usage: agent test run --prompt [--host ] [--port " + "]\n" + "Example: agent test run --prompt \"Open the overworld editor and " + "verify it loads\""); } std::string prompt = args.size() > 1 ? args[1] : ""; @@ -156,7 +157,8 @@ absl::Status HandleTestRunCommand(const std::vector& args) { TestWorkflowGenerator generator; auto workflow_or = generator.GenerateWorkflow(prompt); if (!workflow_or.ok()) { - std::cerr << "Failed to generate workflow: " << workflow_or.status().message() << std::endl; + std::cerr << "Failed to generate workflow: " + << workflow_or.status().message() << std::endl; return workflow_or.status(); } @@ -167,7 +169,8 @@ absl::Status HandleTestRunCommand(const std::vector& args) { GuiAutomationClient client(absl::StrCat(host, ":", port)); auto status = client.Connect(); if (!status.ok()) { - std::cerr << "Failed to connect to test harness: " << status.message() << std::endl; + std::cerr << "Failed to connect to test harness: " << status.message() + << std::endl; return status; } @@ -175,10 +178,11 @@ absl::Status HandleTestRunCommand(const std::vector& args) { for (size_t i = 0; i < workflow.steps.size(); ++i) { const auto& step = workflow.steps[i]; std::cout << "Step " << (i + 1) << ": " << step.ToString() << "... "; - + // Execute based on step type - absl::StatusOr result(absl::InternalError("Unknown step type")); - + absl::StatusOr result( + absl::InternalError("Unknown step type")); + switch (step.type) { case TestStepType::kClick: result = client.Click(step.target); @@ -196,7 +200,7 @@ absl::Status HandleTestRunCommand(const std::vector& args) { std::cout << "✗ SKIPPED (unknown type)\n"; continue; } - + if (!result.ok()) { std::cout << "✗ FAILED\n"; std::cerr << " Error: " << result.status().message() << "\n"; @@ -219,7 +223,8 @@ absl::Status HandleTestRunCommand(const std::vector& args) { absl::Status HandleTestReplayCommand(const std::vector& args) { if (args.empty()) { return absl::InvalidArgumentError( - "Usage: agent test replay [--host ] [--port ]\n" + "Usage: agent test replay [--host ] [--port " + "]\n" "Example: agent test replay tests/overworld_load.json"); } @@ -280,7 +285,8 @@ absl::Status HandleTestStatusCommand(const std::vector& args) { if (test_id.empty()) { return absl::InvalidArgumentError( - "Usage: agent test status --test-id [--host ] [--port ]"); + "Usage: agent test status --test-id [--host ] [--port " + "]"); } GuiAutomationClient client(absl::StrCat(host, ":", port)); @@ -296,10 +302,13 @@ absl::Status HandleTestStatusCommand(const std::vector& args) { std::cout << "\n=== Test Status ===\n"; std::cout << "Test ID: " << test_id << "\n"; - std::cout << "Status: " << TestRunStatusToString(details.value().status) << "\n"; - std::cout << "Started: " << FormatOptionalTime(details.value().started_at) << "\n"; - std::cout << "Completed: " << FormatOptionalTime(details.value().completed_at) << "\n"; - + std::cout << "Status: " << TestRunStatusToString(details.value().status) + << "\n"; + std::cout << "Started: " << FormatOptionalTime(details.value().started_at) + << "\n"; + std::cout << "Completed: " << FormatOptionalTime(details.value().completed_at) + << "\n"; + if (!details.value().error_message.empty()) { std::cout << "Error: " << details.value().error_message << "\n"; } @@ -337,7 +346,7 @@ absl::Status HandleTestListCommand(const std::vector& args) { std::cout << "• " << test.name << "\n"; std::cout << " ID: " << test.test_id << "\n"; std::cout << " Category: " << test.category << "\n"; - std::cout << " Runs: " << test.total_runs << " (" << test.pass_count + std::cout << " Runs: " << test.total_runs << " (" << test.pass_count << " passed, " << test.fail_count << " failed)\n\n"; } @@ -364,7 +373,8 @@ absl::Status HandleTestResultsCommand(const std::vector& args) { if (test_id.empty()) { return absl::InvalidArgumentError( - "Usage: agent test results --test-id [--include-logs] [--host ] [--port ]"); + "Usage: agent test results --test-id [--include-logs] [--host " + "] [--port ]"); } GuiAutomationClient client(absl::StrCat(host, ":", port)); @@ -387,7 +397,7 @@ absl::Status HandleTestResultsCommand(const std::vector& args) { if (!details.value().assertions.empty()) { std::cout << "Assertions:\n"; for (const auto& assertion : details.value().assertions) { - std::cout << " " << (assertion.passed ? "✓" : "✗") << " " + std::cout << " " << (assertion.passed ? "✓" : "✗") << " " << assertion.description << "\n"; if (!assertion.error_message.empty()) { std::cout << " Error: " << assertion.error_message << "\n"; @@ -416,7 +426,8 @@ absl::Status HandleTestRecordCommand(const std::vector& args) { std::string action = args[0]; if (action != "start" && action != "stop") { - return absl::InvalidArgumentError("Record action must be 'start' or 'stop'"); + return absl::InvalidArgumentError( + "Record action must be 'start' or 'stop'"); } if (action == "start") { @@ -465,11 +476,10 @@ absl::Status HandleTestRecordCommand(const std::vector& args) { ASSIGN_OR_RETURN(auto start_result, client.StartRecording(absolute_output.string(), - session_name, description)); + session_name, description)); if (!start_result.success) { - return absl::InternalError( - absl::StrCat("Harness rejected start-recording request: ", - start_result.message)); + return absl::InternalError(absl::StrCat( + "Harness rejected start-recording request: ", start_result.message)); } RecordingState state; @@ -489,8 +499,8 @@ absl::Status HandleTestRecordCommand(const std::vector& args) { if (start_result.started_at.has_value()) { std::cout << "Started: " << absl::FormatTime("%Y-%m-%d %H:%M:%S", - *start_result.started_at, - absl::LocalTimeZone()) + *start_result.started_at, + absl::LocalTimeZone()) << "\n"; } std::cout << "\nPress Ctrl+C to abort the recording session.\n"; @@ -560,7 +570,8 @@ absl::Status HandleTestRecordCommand(const std::vector& args) { } if (discard) { - std::cout << "Recording discarded; no script file was produced." << std::endl; + std::cout << "Recording discarded; no script file was produced." + << std::endl; return absl::OkStatus(); } @@ -621,7 +632,7 @@ absl::Status HandleTestCommand(const std::vector& args) { return HandleTestRecordCommand(tail); } else { return absl::InvalidArgumentError( - absl::StrCat("Unknown test subcommand: ", subcommand, + absl::StrCat("Unknown test subcommand: ", subcommand, "\nRun 'z3ed agent test' for usage.")); } #endif diff --git a/src/cli/handlers/agent/test_common.cc b/src/cli/handlers/agent/test_common.cc index ec83d078..ddf2ca14 100644 --- a/src/cli/handlers/agent/test_common.cc +++ b/src/cli/handlers/agent/test_common.cc @@ -82,8 +82,7 @@ int PromptInt(const std::string& prompt, int default_value, int min_value) { bool PromptYesNo(const std::string& prompt, bool default_value) { while (true) { - std::cout << prompt << " [" << (default_value ? "Y/n" : "y/N") - << "]: "; + std::cout << prompt << " [" << (default_value ? "Y/n" : "y/N") << "]: "; std::cout.flush(); std::string line; if (!std::getline(std::cin, line)) { @@ -93,7 +92,8 @@ bool PromptYesNo(const std::string& prompt, bool default_value) { if (trimmed.empty()) { return default_value; } - char c = static_cast(std::tolower(static_cast(trimmed[0]))); + char c = + static_cast(std::tolower(static_cast(trimmed[0]))); if (c == 'y') { return true; } @@ -122,12 +122,11 @@ bool ParseKeyValueEntry(const std::string& input, std::string* key, return false; } *key = TrimWhitespace(absl::string_view(input.data(), equals)); - *value = TrimWhitespace(absl::string_view(input.data() + equals + 1, - input.size() - equals - 1)); + *value = TrimWhitespace( + absl::string_view(input.data() + equals + 1, input.size() - equals - 1)); return !key->empty(); } } // namespace agent } // namespace cli } // namespace yaze - diff --git a/src/cli/handlers/agent/test_common.h b/src/cli/handlers/agent/test_common.h index 1e94b0b3..e4d06c32 100644 --- a/src/cli/handlers/agent/test_common.h +++ b/src/cli/handlers/agent/test_common.h @@ -17,8 +17,8 @@ std::string TrimWhitespace(absl::string_view value); bool IsInteractiveInput(); std::string PromptWithDefault(const std::string& prompt, - const std::string& default_value, - bool allow_empty = true); + const std::string& default_value, + bool allow_empty = true); std::string PromptRequired(const std::string& prompt, const std::string& default_value = std::string()); @@ -37,4 +37,3 @@ bool ParseKeyValueEntry(const std::string& input, std::string* key, } // namespace yaze #endif // YAZE_CLI_HANDLERS_AGENT_TEST_COMMON_H_ - diff --git a/src/cli/handlers/agent/todo_commands.cc b/src/cli/handlers/agent/todo_commands.cc index d4cfd433..6a03f7ef 100644 --- a/src/cli/handlers/agent/todo_commands.cc +++ b/src/cli/handlers/agent/todo_commands.cc @@ -24,7 +24,7 @@ TodoManager& GetTodoManager() { if (!initialized) { auto status = manager.Initialize(); if (!status.ok()) { - std::cerr << "Warning: Failed to initialize TODO manager: " + std::cerr << "Warning: Failed to initialize TODO manager: " << status.message() << std::endl; } initialized = true; @@ -35,41 +35,51 @@ TodoManager& GetTodoManager() { void PrintTodo(const TodoItem& item, bool detailed = false) { std::string status_emoji; switch (item.status) { - case TodoItem::Status::PENDING: status_emoji = "⏳"; break; - case TodoItem::Status::IN_PROGRESS: status_emoji = "🔄"; break; - case TodoItem::Status::COMPLETED: status_emoji = "✅"; break; - case TodoItem::Status::BLOCKED: status_emoji = "🚫"; break; - case TodoItem::Status::CANCELLED: status_emoji = "❌"; break; + case TodoItem::Status::PENDING: + status_emoji = "⏳"; + break; + case TodoItem::Status::IN_PROGRESS: + status_emoji = "🔄"; + break; + case TodoItem::Status::COMPLETED: + status_emoji = "✅"; + break; + case TodoItem::Status::BLOCKED: + status_emoji = "🚫"; + break; + case TodoItem::Status::CANCELLED: + status_emoji = "❌"; + break; } - - std::cout << absl::StreamFormat("[%s] %s %s", - item.id, - status_emoji, + + std::cout << absl::StreamFormat("[%s] %s %s", item.id, status_emoji, item.description); - + if (!item.category.empty()) { std::cout << absl::StreamFormat(" [%s]", item.category); } - + if (item.priority > 0) { std::cout << absl::StreamFormat(" (priority: %d)", item.priority); } - + std::cout << std::endl; - + if (detailed) { std::cout << " Status: " << item.StatusToString() << std::endl; std::cout << " Created: " << item.created_at << std::endl; std::cout << " Updated: " << item.updated_at << std::endl; - + if (!item.dependencies.empty()) { - std::cout << " Dependencies: " << absl::StrJoin(item.dependencies, ", ") << std::endl; + std::cout << " Dependencies: " << absl::StrJoin(item.dependencies, ", ") + << std::endl; } - + if (!item.tools_needed.empty()) { - std::cout << " Tools needed: " << absl::StrJoin(item.tools_needed, ", ") << std::endl; + std::cout << " Tools needed: " << absl::StrJoin(item.tools_needed, ", ") + << std::endl; } - + if (!item.notes.empty()) { std::cout << " Notes: " << item.notes << std::endl; } @@ -78,13 +88,15 @@ void PrintTodo(const TodoItem& item, bool detailed = false) { absl::Status HandleTodoCreate(const std::vector& args) { if (args.empty()) { - return absl::InvalidArgumentError("Usage: agent todo create [--category=] [--priority=]"); + return absl::InvalidArgumentError( + "Usage: agent todo create [--category=] " + "[--priority=]"); } - + std::string description = args[0]; std::string category; int priority = 0; - + for (size_t i = 1; i < args.size(); ++i) { if (args[i].find("--category=") == 0) { category = args[i].substr(11); @@ -92,24 +104,24 @@ absl::Status HandleTodoCreate(const std::vector& args) { priority = std::stoi(args[i].substr(11)); } } - + auto& manager = GetTodoManager(); auto result = manager.CreateTodo(description, category, priority); - + if (!result.ok()) { return result.status(); } - + std::cout << "Created TODO:" << std::endl; PrintTodo(*result, true); - + return absl::OkStatus(); } absl::Status HandleTodoList(const std::vector& args) { std::string status_filter; std::string category_filter; - + for (const auto& arg : args) { if (arg.find("--status=") == 0) { status_filter = arg.substr(9); @@ -117,10 +129,10 @@ absl::Status HandleTodoList(const std::vector& args) { category_filter = arg.substr(11); } } - + auto& manager = GetTodoManager(); std::vector todos; - + if (!status_filter.empty()) { auto status = TodoItem::StringToStatus(status_filter); todos = manager.GetTodosByStatus(status); @@ -129,47 +141,49 @@ absl::Status HandleTodoList(const std::vector& args) { } else { todos = manager.GetAllTodos(); } - + if (todos.empty()) { std::cout << "No TODOs found." << std::endl; return absl::OkStatus(); } - + std::cout << "TODOs (" << todos.size() << "):" << std::endl; for (const auto& item : todos) { PrintTodo(item); } - + return absl::OkStatus(); } absl::Status HandleTodoUpdate(const std::vector& args) { if (args.size() < 2) { - return absl::InvalidArgumentError("Usage: agent todo update --status="); + return absl::InvalidArgumentError( + "Usage: agent todo update --status="); } - + std::string id = args[0]; std::string new_status_str; - + for (size_t i = 1; i < args.size(); ++i) { if (args[i].find("--status=") == 0) { new_status_str = args[i].substr(9); } } - + if (new_status_str.empty()) { return absl::InvalidArgumentError("--status parameter is required"); } - + auto new_status = TodoItem::StringToStatus(new_status_str); auto& manager = GetTodoManager(); auto status = manager.UpdateStatus(id, new_status); - + if (!status.ok()) { return status; } - - std::cout << "Updated TODO " << id << " to status: " << new_status_str << std::endl; + + std::cout << "Updated TODO " << id << " to status: " << new_status_str + << std::endl; return absl::OkStatus(); } @@ -177,15 +191,15 @@ absl::Status HandleTodoShow(const std::vector& args) { if (args.empty()) { return absl::InvalidArgumentError("Usage: agent todo show "); } - + std::string id = args[0]; auto& manager = GetTodoManager(); auto result = manager.GetTodo(id); - + if (!result.ok()) { return result.status(); } - + PrintTodo(*result, true); return absl::OkStatus(); } @@ -194,15 +208,15 @@ absl::Status HandleTodoDelete(const std::vector& args) { if (args.empty()) { return absl::InvalidArgumentError("Usage: agent todo delete "); } - + std::string id = args[0]; auto& manager = GetTodoManager(); auto status = manager.DeleteTodo(id); - + if (!status.ok()) { return status; } - + std::cout << "Deleted TODO " << id << std::endl; return absl::OkStatus(); } @@ -210,11 +224,11 @@ absl::Status HandleTodoDelete(const std::vector& args) { absl::Status HandleTodoClearCompleted(const std::vector& args) { auto& manager = GetTodoManager(); auto status = manager.ClearCompleted(); - + if (!status.ok()) { return status; } - + std::cout << "Cleared all completed TODOs" << std::endl; return absl::OkStatus(); } @@ -222,37 +236,37 @@ absl::Status HandleTodoClearCompleted(const std::vector& args) { absl::Status HandleTodoNext(const std::vector& args) { auto& manager = GetTodoManager(); auto result = manager.GetNextActionableTodo(); - + if (!result.ok()) { return result.status(); } - + std::cout << "Next actionable TODO:" << std::endl; PrintTodo(*result, true); - + return absl::OkStatus(); } absl::Status HandleTodoPlan(const std::vector& args) { auto& manager = GetTodoManager(); auto result = manager.GenerateExecutionPlan(); - + if (!result.ok()) { return result.status(); } - + auto& plan = *result; if (plan.empty()) { std::cout << "No pending TODOs." << std::endl; return absl::OkStatus(); } - + std::cout << "Execution Plan (" << plan.size() << " tasks):" << std::endl; for (size_t i = 0; i < plan.size(); ++i) { std::cout << absl::StreamFormat("%2d. ", i + 1); PrintTodo(plan[i]); } - + return absl::OkStatus(); } @@ -272,10 +286,10 @@ absl::Status HandleTodoCommand(const std::vector& args) { std::cerr << " plan - Generate execution plan" << std::endl; return absl::InvalidArgumentError("No command specified"); } - + std::string subcommand = args[0]; std::vector subargs(args.begin() + 1, args.end()); - + if (subcommand == "create") { return HandleTodoCreate(subargs); } else if (subcommand == "list") { diff --git a/src/cli/handlers/command_handlers.cc b/src/cli/handlers/command_handlers.cc index ff38cfee..905b8fb3 100644 --- a/src/cli/handlers/command_handlers.cc +++ b/src/cli/handlers/command_handlers.cc @@ -1,15 +1,15 @@ #include "cli/handlers/command_handlers.h" -#include "cli/handlers/tools/resource_commands.h" #include "cli/handlers/tools/gui_commands.h" +#include "cli/handlers/tools/resource_commands.h" #ifdef YAZE_WITH_GRPC #include "cli/handlers/tools/emulator_commands.h" #endif -#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/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/hex_commands.h" #include "cli/handlers/graphics/palette_commands.h" #include "cli/handlers/graphics/sprite_commands.h" @@ -20,54 +20,56 @@ namespace yaze { namespace cli { namespace handlers { -std::vector> CreateCliCommandHandlers() { +std::vector> +CreateCliCommandHandlers() { std::vector> handlers; - + // Graphics commands handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // Palette commands handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // Sprite commands handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // Music commands handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // Dialogue commands handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // Message commands handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + return handlers; } #include "cli/handlers/agent/simple_chat_command.h" -std::vector> CreateAgentCommandHandlers() { +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()); - + // Dungeon inspection handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); @@ -75,7 +77,7 @@ std::vector> CreateAgentCommandHandle handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // Overworld inspection handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); @@ -83,13 +85,13 @@ std::vector> CreateAgentCommandHandle handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // GUI automation tools handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); - + // Emulator & debugger commands #ifdef YAZE_WITH_GRPC handlers.push_back(std::make_unique()); @@ -105,25 +107,26 @@ std::vector> CreateAgentCommandHandle handlers.push_back(std::make_unique()); handlers.push_back(std::make_unique()); #endif - + return handlers; } -std::vector> CreateAllCommandHandlers() { +std::vector> +CreateAllCommandHandlers() { std::vector> handlers; - + // Add CLI handlers auto cli_handlers = CreateCliCommandHandlers(); for (auto& handler : cli_handlers) { handlers.push_back(std::move(handler)); } - + // Add agent handlers auto agent_handlers = CreateAgentCommandHandlers(); for (auto& handler : agent_handlers) { handlers.push_back(std::move(handler)); } - + return handlers; } diff --git a/src/cli/handlers/command_handlers.h b/src/cli/handlers/command_handlers.h index 238cf98c..b766b14f 100644 --- a/src/cli/handlers/command_handlers.h +++ b/src/cli/handlers/command_handlers.h @@ -78,21 +78,24 @@ class EmulatorGetMetricsCommandHandler; * * @return Vector of unique pointers to command handler instances */ -std::vector> CreateCliCommandHandlers(); +std::vector> +CreateCliCommandHandlers(); /** * @brief Factory function to create all agent-specific command handlers * * @return Vector of unique pointers to command handler instances */ -std::vector> CreateAgentCommandHandlers(); +std::vector> +CreateAgentCommandHandlers(); /** * @brief Factory function to create all command handlers (CLI + agent) * * @return Vector of unique pointers to command handler instances */ -std::vector> CreateAllCommandHandlers(); +std::vector> +CreateAllCommandHandlers(); } // namespace handlers } // namespace cli diff --git a/src/cli/handlers/game/dialogue_commands.cc b/src/cli/handlers/game/dialogue_commands.cc index dcbe7f34..d3954655 100644 --- a/src/cli/handlers/game/dialogue_commands.cc +++ b/src/cli/handlers/game/dialogue_commands.cc @@ -6,52 +6,58 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status DialogueListCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DialogueListCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto limit = parser.GetInt("limit").value_or(10); - + formatter.BeginObject("Dialogue Messages"); formatter.AddField("total_messages", 0); formatter.AddField("limit", limit); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Dialogue listing requires dialogue system integration"); - + formatter.AddField("message", + "Dialogue listing requires dialogue system integration"); + formatter.BeginArray("messages"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status DialogueReadCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DialogueReadCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto message_id = parser.GetString("id").value(); - + formatter.BeginObject("Dialogue Message"); formatter.AddField("message_id", message_id); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Dialogue reading requires dialogue system integration"); + formatter.AddField("message", + "Dialogue reading requires dialogue system integration"); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status DialogueSearchCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DialogueSearchCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto query = parser.GetString("query").value(); auto limit = parser.GetInt("limit").value_or(10); - + formatter.BeginObject("Dialogue Search Results"); formatter.AddField("query", query); formatter.AddField("limit", limit); formatter.AddField("matches_found", 0); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Dialogue search requires dialogue system integration"); - + formatter.AddField("message", + "Dialogue search requires dialogue system integration"); + formatter.BeginArray("matches"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/game/dialogue_commands.h b/src/cli/handlers/game/dialogue_commands.h index d7df3057..b9c21a52 100644 --- a/src/cli/handlers/game/dialogue_commands.h +++ b/src/cli/handlers/game/dialogue_commands.h @@ -19,13 +19,13 @@ class DialogueListCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "dialogue-list [--limit ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -40,13 +40,13 @@ class DialogueReadCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "dialogue-read --id [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"id"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -59,15 +59,16 @@ class DialogueSearchCommandHandler : public resources::CommandHandler { return "Search dialogue messages by text content"; } std::string GetUsage() const { - return "dialogue-search --query [--limit ] [--format ]"; + return "dialogue-search --query [--limit ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"query"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/game/dungeon.cc b/src/cli/handlers/game/dungeon.cc index 7ed0797b..cd21e109 100644 --- a/src/cli/handlers/game/dungeon.cc +++ b/src/cli/handlers/game/dungeon.cc @@ -1,8 +1,8 @@ +#include "absl/flags/declare.h" +#include "absl/flags/flag.h" #include "cli/cli.h" #include "zelda3/dungeon/dungeon_editor_system.h" #include "zelda3/dungeon/room.h" -#include "absl/flags/flag.h" -#include "absl/flags/declare.h" ABSL_DECLARE_FLAG(std::string, rom); @@ -11,7 +11,8 @@ namespace cli { // Legacy DungeonExport class removed - using new CommandHandler system // This implementation should be moved to DungeonExportCommandHandler -absl::Status HandleDungeonExportLegacy(const std::vector& arg_vec) { +absl::Status HandleDungeonExportLegacy( + const std::vector& arg_vec) { if (arg_vec.size() < 1) { return absl::InvalidArgumentError("Usage: dungeon export "); } @@ -19,19 +20,20 @@ absl::Status HandleDungeonExportLegacy(const std::vector& arg_vec) int room_id = std::stoi(arg_vec[0]); std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; rom.LoadFromFile(rom_file); if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } zelda3::DungeonEditorSystem dungeon_editor(&rom); auto room_or = dungeon_editor.GetRoom(room_id); if (!room_or.ok()) { - return room_or.status(); + return room_or.status(); } zelda3::Room room = room_or.value(); @@ -46,7 +48,8 @@ absl::Status HandleDungeonExportLegacy(const std::vector& arg_vec) // Legacy DungeonListObjects class removed - using new CommandHandler system // This implementation should be moved to DungeonListObjectsCommandHandler -absl::Status HandleDungeonListObjectsLegacy(const std::vector& arg_vec) { +absl::Status HandleDungeonListObjectsLegacy( + const std::vector& arg_vec) { if (arg_vec.size() < 1) { return absl::InvalidArgumentError("Usage: dungeon list-objects "); } @@ -54,31 +57,33 @@ absl::Status HandleDungeonListObjectsLegacy(const std::vector& arg_ int room_id = std::stoi(arg_vec[0]); std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; rom.LoadFromFile(rom_file); if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } zelda3::DungeonEditorSystem dungeon_editor(&rom); auto room_or = dungeon_editor.GetRoom(room_id); if (!room_or.ok()) { - return room_or.status(); + return room_or.status(); } zelda3::Room room = room_or.value(); room.LoadObjects(); std::cout << "Objects in Room " << room_id << ":" << std::endl; for (const auto& obj : room.GetTileObjects()) { - std::cout << absl::StrFormat(" - ID: 0x%04X, Pos: (%d, %d), Size: 0x%02X, Layer: %d\n", - obj.id_, obj.x_, obj.y_, obj.size_, obj.layer_); + std::cout << absl::StrFormat( + " - ID: 0x%04X, Pos: (%d, %d), Size: 0x%02X, Layer: %d\n", obj.id_, + obj.x_, obj.y_, obj.size_, obj.layer_); } return absl::OkStatus(); } -} // namespace cli -} // namespace yaze +} // namespace cli +} // namespace yaze diff --git a/src/cli/handlers/game/dungeon_commands.cc b/src/cli/handlers/game/dungeon_commands.cc index 02aac640..8918ce89 100644 --- a/src/cli/handlers/game/dungeon_commands.cc +++ b/src/cli/handlers/game/dungeon_commands.cc @@ -8,19 +8,19 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status DungeonListSpritesCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DungeonListSpritesCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto room_id_str = parser.GetString("room").value(); - + int room_id; if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { - return absl::InvalidArgumentError( - "Invalid room ID format. Must be hex."); + return absl::InvalidArgumentError("Invalid room ID format. Must be hex."); } - + formatter.BeginObject("Dungeon Room Sprites"); formatter.AddField("room_id", room_id); - + // Use existing dungeon system zelda3::DungeonEditorSystem dungeon_editor(rom); auto room_or = dungeon_editor.GetRoom(room_id); @@ -30,34 +30,34 @@ absl::Status DungeonListSpritesCommandHandler::Execute(Rom* rom, const resources formatter.EndObject(); return room_or.status(); } - + auto room = room_or.value(); - + // TODO: Implement sprite listing from room data formatter.AddField("total_sprites", 0); formatter.AddField("status", "not_implemented"); formatter.AddField("message", "Sprite listing requires room sprite parsing"); - + formatter.BeginArray("sprites"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status DungeonDescribeRoomCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DungeonDescribeRoomCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto room_id_str = parser.GetString("room").value(); - + int room_id; if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { - return absl::InvalidArgumentError( - "Invalid room ID format. Must be hex."); + return absl::InvalidArgumentError("Invalid room ID format. Must be hex."); } - + formatter.BeginObject("Dungeon Room Description"); formatter.AddField("room_id", room_id); - + // Use existing dungeon system zelda3::DungeonEditorSystem dungeon_editor(rom); auto room_or = dungeon_editor.GetRoom(room_id); @@ -67,39 +67,39 @@ absl::Status DungeonDescribeRoomCommandHandler::Execute(Rom* rom, const resource formatter.EndObject(); return room_or.status(); } - + auto room = room_or.value(); - + formatter.AddField("status", "success"); formatter.AddField("name", absl::StrFormat("Room %d", room.id())); formatter.AddField("room_id", room.id()); formatter.AddField("room_type", "Dungeon Room"); - + // Add room properties formatter.BeginObject("properties"); formatter.AddField("has_doors", "Unknown"); formatter.AddField("has_sprites", "Unknown"); formatter.AddField("has_secrets", "Unknown"); formatter.EndObject(); - + formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status DungeonExportRoomCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DungeonExportRoomCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto room_id_str = parser.GetString("room").value(); - + int room_id; if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { - return absl::InvalidArgumentError( - "Invalid room ID format. Must be hex."); + return absl::InvalidArgumentError("Invalid room ID format. Must be hex."); } - + formatter.BeginObject("Dungeon Export"); formatter.AddField("room_id", room_id); - + // Use existing dungeon system zelda3::DungeonEditorSystem dungeon_editor(rom); auto room_or = dungeon_editor.GetRoom(room_id); @@ -109,40 +109,40 @@ absl::Status DungeonExportRoomCommandHandler::Execute(Rom* rom, const resources: formatter.EndObject(); return room_or.status(); } - + auto room = room_or.value(); - + // Export room data formatter.AddField("status", "success"); formatter.AddField("room_width", "Unknown"); formatter.AddField("room_height", "Unknown"); formatter.AddField("room_name", absl::StrFormat("Room %d", room.id())); - + // Add room data as JSON formatter.BeginObject("room_data"); formatter.AddField("tiles", "Room tile data would be exported here"); formatter.AddField("sprites", "Room sprite data would be exported here"); formatter.AddField("doors", "Room door data would be exported here"); formatter.EndObject(); - + formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status DungeonListObjectsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DungeonListObjectsCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto room_id_str = parser.GetString("room").value(); - + int room_id; if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { - return absl::InvalidArgumentError( - "Invalid room ID format. Must be hex."); + return absl::InvalidArgumentError("Invalid room ID format. Must be hex."); } - + formatter.BeginObject("Dungeon Room Objects"); formatter.AddField("room_id", room_id); - + // Use existing dungeon system zelda3::DungeonEditorSystem dungeon_editor(rom); auto room_or = dungeon_editor.GetRoom(room_id); @@ -152,34 +152,34 @@ absl::Status DungeonListObjectsCommandHandler::Execute(Rom* rom, const resources formatter.EndObject(); return room_or.status(); } - + auto room = room_or.value(); - + // TODO: Implement object listing from room data formatter.AddField("total_objects", 0); formatter.AddField("status", "not_implemented"); formatter.AddField("message", "Object listing requires room object parsing"); - + formatter.BeginArray("objects"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status DungeonGetRoomTilesCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DungeonGetRoomTilesCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto room_id_str = parser.GetString("room").value(); - + int room_id; if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { - return absl::InvalidArgumentError( - "Invalid room ID format. Must be hex."); + return absl::InvalidArgumentError("Invalid room ID format. Must be hex."); } - + formatter.BeginObject("Dungeon Room Tiles"); formatter.AddField("room_id", room_id); - + // Use existing dungeon system zelda3::DungeonEditorSystem dungeon_editor(rom); auto room_or = dungeon_editor.GetRoom(room_id); @@ -189,40 +189,41 @@ absl::Status DungeonGetRoomTilesCommandHandler::Execute(Rom* rom, const resource formatter.EndObject(); return room_or.status(); } - + auto room = room_or.value(); - + // TODO: Implement tile data retrieval from room formatter.AddField("room_width", "Unknown"); formatter.AddField("room_height", "Unknown"); formatter.AddField("total_tiles", "Unknown"); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Tile data retrieval requires room tile parsing"); - + formatter.AddField("message", + "Tile data retrieval requires room tile parsing"); + formatter.BeginArray("tiles"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status DungeonSetRoomPropertyCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status DungeonSetRoomPropertyCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto room_id_str = parser.GetString("room").value(); auto property = parser.GetString("property").value(); auto value = parser.GetString("value").value(); - + int room_id; if (!absl::SimpleHexAtoi(room_id_str, &room_id)) { - return absl::InvalidArgumentError( - "Invalid room ID format. Must be hex."); + return absl::InvalidArgumentError("Invalid room ID format. Must be hex."); } - + formatter.BeginObject("Dungeon Room Property Set"); formatter.AddField("room_id", room_id); formatter.AddField("property", property); formatter.AddField("value", value); - + // Use existing dungeon system zelda3::DungeonEditorSystem dungeon_editor(rom); auto room_or = dungeon_editor.GetRoom(room_id); @@ -232,12 +233,13 @@ absl::Status DungeonSetRoomPropertyCommandHandler::Execute(Rom* rom, const resou formatter.EndObject(); return room_or.status(); } - + // TODO: Implement property setting formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Property setting requires room property system"); + formatter.AddField("message", + "Property setting requires room property system"); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/game/dungeon_commands.h b/src/cli/handlers/game/dungeon_commands.h index 9224748d..7eda52ad 100644 --- a/src/cli/handlers/game/dungeon_commands.h +++ b/src/cli/handlers/game/dungeon_commands.h @@ -19,13 +19,13 @@ class DungeonListSpritesCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "dungeon-list-sprites --room [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"room"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -40,13 +40,13 @@ class DungeonDescribeRoomCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "dungeon-describe-room --room [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"room"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -61,13 +61,13 @@ class DungeonExportRoomCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "dungeon-export-room --room [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"room"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -82,13 +82,13 @@ class DungeonListObjectsCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "dungeon-list-objects --room [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"room"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -103,13 +103,13 @@ class DungeonGetRoomTilesCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "dungeon-get-room-tiles --room [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"room"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -122,15 +122,16 @@ class DungeonSetRoomPropertyCommandHandler : public resources::CommandHandler { return "Set a property on a dungeon room"; } std::string GetUsage() const { - return "dungeon-set-room-property --room --property --value [--format ]"; + return "dungeon-set-room-property --room --property " + "--value [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"room", "property", "value"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/game/message.cc b/src/cli/handlers/game/message.cc index 9f567070..01f07f02 100644 --- a/src/cli/handlers/game/message.cc +++ b/src/cli/handlers/game/message.cc @@ -44,13 +44,14 @@ absl::StatusOr LoadRomFromFlag() { std::vector LoadMessages(Rom* rom) { // Fix: Cast away constness for ReadAllTextData, which expects uint8_t* - return editor::ReadAllTextData(const_cast(rom->data()), editor::kTextData); + return editor::ReadAllTextData(const_cast(rom->data()), + editor::kTextData); } } // namespace absl::Status HandleMessageListCommand(const std::vector& arg_vec, - Rom* rom_context) { + Rom* rom_context) { std::string format = "json"; int start_id = 0; int end_id = -1; // -1 means all @@ -66,12 +67,14 @@ absl::Status HandleMessageListCommand(const std::vector& arg_vec, format = absl::AsciiStrToLower(token.substr(9)); } else if (token == "--range") { if (i + 1 >= arg_vec.size()) { - return absl::InvalidArgumentError("--range requires a value (start-end)."); + return absl::InvalidArgumentError( + "--range requires a value (start-end)."); } std::string range = arg_vec[++i]; size_t dash_pos = range.find('-'); if (dash_pos == std::string::npos) { - return absl::InvalidArgumentError("--range format must be start-end (e.g. 0-100)"); + return absl::InvalidArgumentError( + "--range format must be start-end (e.g. 0-100)"); } if (!absl::SimpleAtoi(range.substr(0, dash_pos), &start_id) || !absl::SimpleAtoi(range.substr(dash_pos + 1), &end_id)) { @@ -81,7 +84,8 @@ absl::Status HandleMessageListCommand(const std::vector& arg_vec, std::string range = token.substr(8); size_t dash_pos = range.find('-'); if (dash_pos == std::string::npos) { - return absl::InvalidArgumentError("--range format must be start-end (e.g. 0-100)"); + return absl::InvalidArgumentError( + "--range format must be start-end (e.g. 0-100)"); } if (!absl::SimpleAtoi(range.substr(0, dash_pos), &start_id) || !absl::SimpleAtoi(range.substr(dash_pos + 1), &end_id)) { @@ -108,28 +112,33 @@ absl::Status HandleMessageListCommand(const std::vector& arg_vec, } auto messages = LoadMessages(rom); - + if (end_id < 0) { end_id = static_cast(messages.size()) - 1; } - start_id = std::max(0, std::min(start_id, static_cast(messages.size()) - 1)); - end_id = std::max(start_id, std::min(end_id, static_cast(messages.size()) - 1)); + start_id = + std::max(0, std::min(start_id, static_cast(messages.size()) - 1)); + end_id = std::max(start_id, + std::min(end_id, static_cast(messages.size()) - 1)); if (format == "json") { std::cout << "{\n"; - std::cout << absl::StrFormat(" \"total_messages\": %zu,\n", messages.size()); + std::cout << absl::StrFormat(" \"total_messages\": %zu,\n", + messages.size()); std::cout << absl::StrFormat(" \"range\": [%d, %d],\n", start_id, end_id); std::cout << " \"messages\": [\n"; - + bool first = true; for (int i = start_id; i <= end_id; ++i) { const auto& msg = messages[i]; - if (!first) std::cout << ",\n"; + if (!first) + std::cout << ",\n"; std::cout << " {\n"; std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID); - std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", msg.Address); - + std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", + msg.Address); + // Escape quotes in the text std::string escaped_text = msg.ContentsParsed; size_t pos = 0; @@ -144,8 +153,8 @@ absl::Status HandleMessageListCommand(const std::vector& arg_vec, std::cout << "\n ]\n"; std::cout << "}\n"; } else { - std::cout << absl::StrFormat("📝 Messages %d-%d (Total: %zu)\n", - start_id, end_id, messages.size()); + std::cout << absl::StrFormat("📝 Messages %d-%d (Total: %zu)\n", start_id, + end_id, messages.size()); std::cout << std::string(60, '=') << "\n"; for (int i = start_id; i <= end_id; ++i) { const auto& msg = messages[i]; @@ -159,7 +168,7 @@ absl::Status HandleMessageListCommand(const std::vector& arg_vec, } absl::Status HandleMessageReadCommand(const std::vector& arg_vec, - Rom* rom_context) { + Rom* rom_context) { int message_id = -1; std::string format = "json"; @@ -211,9 +220,8 @@ absl::Status HandleMessageReadCommand(const std::vector& arg_vec, auto messages = LoadMessages(rom); if (message_id >= static_cast(messages.size())) { - return absl::NotFoundError( - absl::StrFormat("Message ID %d not found (max: %d)", - message_id, messages.size() - 1)); + return absl::NotFoundError(absl::StrFormat( + "Message ID %d not found (max: %d)", message_id, messages.size() - 1)); } const auto& msg = messages[message_id]; @@ -222,7 +230,7 @@ absl::Status HandleMessageReadCommand(const std::vector& arg_vec, std::cout << "{\n"; std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID); std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", msg.Address); - + // Escape quotes std::string escaped_text = msg.ContentsParsed; size_t pos = 0; @@ -245,7 +253,7 @@ absl::Status HandleMessageReadCommand(const std::vector& arg_vec, } absl::Status HandleMessageSearchCommand(const std::vector& arg_vec, - Rom* rom_context) { + Rom* rom_context) { std::string query; std::string format = "json"; @@ -306,31 +314,33 @@ absl::Status HandleMessageSearchCommand(const std::vector& arg_vec, std::cout << absl::StrFormat(" \"query\": \"%s\",\n", query); std::cout << absl::StrFormat(" \"match_count\": %zu,\n", matches.size()); std::cout << " \"matches\": [\n"; - + for (size_t i = 0; i < matches.size(); ++i) { const auto& msg = messages[matches[i]]; - if (i > 0) std::cout << ",\n"; - + if (i > 0) + std::cout << ",\n"; + std::string escaped_text = msg.ContentsParsed; size_t pos = 0; while ((pos = escaped_text.find('"', pos)) != std::string::npos) { escaped_text.insert(pos, "\\"); pos += 2; } - + std::cout << " {\n"; std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID); - std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", msg.Address); + std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", + msg.Address); std::cout << absl::StrFormat(" \"text\": \"%s\"\n", escaped_text); std::cout << " }"; } std::cout << "\n ]\n"; std::cout << "}\n"; } else { - std::cout << absl::StrFormat("🔍 Search: \"%s\" → %zu match(es)\n", - query, matches.size()); + std::cout << absl::StrFormat("🔍 Search: \"%s\" → %zu match(es)\n", query, + matches.size()); std::cout << std::string(60, '=') << "\n"; - + for (int match_id : matches) { const auto& msg = messages[match_id]; std::cout << absl::StrFormat("[%03d] @ 0x%06X\n", msg.ID, msg.Address); @@ -343,7 +353,7 @@ absl::Status HandleMessageSearchCommand(const std::vector& arg_vec, } absl::Status HandleMessageStatsCommand(const std::vector& arg_vec, - Rom* rom_context) { + Rom* rom_context) { std::string format = "json"; for (size_t i = 0; i < arg_vec.size(); ++i) { @@ -380,7 +390,7 @@ absl::Status HandleMessageStatsCommand(const std::vector& arg_vec, size_t total_bytes = 0; size_t max_length = 0; size_t min_length = SIZE_MAX; - + for (const auto& msg : messages) { size_t len = msg.Data.size(); total_bytes += len; @@ -388,12 +398,14 @@ absl::Status HandleMessageStatsCommand(const std::vector& arg_vec, min_length = std::min(min_length, len); } - double avg_length = messages.empty() ? 0.0 : - static_cast(total_bytes) / messages.size(); + double avg_length = messages.empty() + ? 0.0 + : static_cast(total_bytes) / messages.size(); if (format == "json") { std::cout << "{\n"; - std::cout << absl::StrFormat(" \"total_messages\": %zu,\n", messages.size()); + std::cout << absl::StrFormat(" \"total_messages\": %zu,\n", + messages.size()); std::cout << absl::StrFormat(" \"total_bytes\": %zu,\n", total_bytes); std::cout << absl::StrFormat(" \"average_length\": %.2f,\n", avg_length); std::cout << absl::StrFormat(" \"min_length\": %zu,\n", min_length); diff --git a/src/cli/handlers/game/message.h b/src/cli/handlers/game/message.h index 9440c416..1d50b450 100644 --- a/src/cli/handlers/game/message.h +++ b/src/cli/handlers/game/message.h @@ -19,36 +19,32 @@ namespace message { * @param arg_vec Command arguments: [--format ] [--range ] * @param rom_context Optional ROM context to avoid reloading */ -absl::Status HandleMessageListCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); +absl::Status HandleMessageListCommand(const std::vector& arg_vec, + Rom* rom_context = nullptr); /** * @brief Read a specific message by ID * @param arg_vec Command arguments: --id [--format ] * @param rom_context Optional ROM context to avoid reloading */ -absl::Status HandleMessageReadCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); +absl::Status HandleMessageReadCommand(const std::vector& arg_vec, + Rom* rom_context = nullptr); /** * @brief Search for messages containing specific text * @param arg_vec Command arguments: --query [--format ] * @param rom_context Optional ROM context to avoid reloading */ -absl::Status HandleMessageSearchCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); +absl::Status HandleMessageSearchCommand(const std::vector& arg_vec, + Rom* rom_context = nullptr); /** * @brief Get message statistics and overview * @param arg_vec Command arguments: [--format ] * @param rom_context Optional ROM context to avoid reloading */ -absl::Status HandleMessageStatsCommand( - const std::vector& arg_vec, - Rom* rom_context = nullptr); +absl::Status HandleMessageStatsCommand(const std::vector& arg_vec, + Rom* rom_context = nullptr); } // namespace message } // namespace cli diff --git a/src/cli/handlers/game/message_commands.cc b/src/cli/handlers/game/message_commands.cc index 4b1d9d00..6668eba4 100644 --- a/src/cli/handlers/game/message_commands.cc +++ b/src/cli/handlers/game/message_commands.cc @@ -6,57 +6,63 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status MessageListCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status MessageListCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto limit = parser.GetInt("limit").value_or(50); - + formatter.BeginObject("Message List"); formatter.AddField("limit", limit); formatter.AddField("total_messages", 0); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Message listing requires message system integration"); - + formatter.AddField("message", + "Message listing requires message system integration"); + formatter.BeginArray("messages"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status MessageReadCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status MessageReadCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto message_id = parser.GetString("id").value(); - + formatter.BeginObject("Message"); formatter.AddField("message_id", message_id); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Message reading requires message system integration"); - + formatter.AddField("message", + "Message reading requires message system integration"); + formatter.BeginObject("content"); formatter.AddField("text", "Message content would appear here"); formatter.AddField("length", 0); formatter.EndObject(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status MessageSearchCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status MessageSearchCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto query = parser.GetString("query").value(); auto limit = parser.GetInt("limit").value_or(10); - + formatter.BeginObject("Message Search Results"); formatter.AddField("query", query); formatter.AddField("limit", limit); formatter.AddField("matches_found", 0); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Message search requires message system integration"); - + formatter.AddField("message", + "Message search requires message system integration"); + formatter.BeginArray("matches"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/game/message_commands.h b/src/cli/handlers/game/message_commands.h index 9d7f43e1..c17584f4 100644 --- a/src/cli/handlers/game/message_commands.h +++ b/src/cli/handlers/game/message_commands.h @@ -13,19 +13,17 @@ namespace handlers { class MessageListCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "message-list"; } - std::string GetDescription() const { - return "List available messages"; - } + std::string GetDescription() const { return "List available messages"; } std::string GetUsage() const { return "message-list [--limit ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -34,19 +32,17 @@ class MessageListCommandHandler : public resources::CommandHandler { class MessageReadCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "message-read"; } - std::string GetDescription() const { - return "Read a specific message"; - } + std::string GetDescription() const { return "Read a specific message"; } std::string GetUsage() const { return "message-read --id [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"id"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -59,15 +55,16 @@ class MessageSearchCommandHandler : public resources::CommandHandler { return "Search messages by text content"; } std::string GetUsage() const { - return "message-search --query [--limit ] [--format ]"; + return "message-search --query [--limit ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"query"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/game/music_commands.cc b/src/cli/handlers/game/music_commands.cc index 039a5615..b970a380 100644 --- a/src/cli/handlers/game/music_commands.cc +++ b/src/cli/handlers/game/music_commands.cc @@ -6,47 +6,52 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status MusicListCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status MusicListCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Music Tracks"); formatter.AddField("total_tracks", 0); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Music listing requires music system integration"); - + formatter.AddField("message", + "Music listing requires music system integration"); + formatter.BeginArray("tracks"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status MusicInfoCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status MusicInfoCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto track_id = parser.GetString("id").value(); - + formatter.BeginObject("Music Track Info"); formatter.AddField("track_id", track_id); formatter.AddField("status", "not_implemented"); formatter.AddField("message", "Music info requires music system integration"); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status MusicTracksCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status MusicTracksCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto category = parser.GetString("category").value_or("all"); - + formatter.BeginObject("Music Track Data"); formatter.AddField("category", category); formatter.AddField("total_tracks", 0); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Music track data requires music system integration"); - + formatter.AddField("message", + "Music track data requires music system integration"); + formatter.BeginArray("tracks"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/game/music_commands.h b/src/cli/handlers/game/music_commands.h index a2d1c211..b0c1bc32 100644 --- a/src/cli/handlers/game/music_commands.h +++ b/src/cli/handlers/game/music_commands.h @@ -13,19 +13,15 @@ namespace handlers { class MusicListCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "music-list"; } - std::string GetDescription() const { - return "List available music tracks"; - } - std::string GetUsage() const { - return "music-list [--format ]"; - } - + std::string GetDescription() const { return "List available music tracks"; } + std::string GetUsage() const { return "music-list [--format ]"; } + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -40,13 +36,13 @@ class MusicInfoCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "music-info --id [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"id"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -61,13 +57,13 @@ class MusicTracksCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "music-tracks [--category ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/game/overworld.cc b/src/cli/handlers/game/overworld.cc index c08a46e9..fbfb25ac 100644 --- a/src/cli/handlers/game/overworld.cc +++ b/src/cli/handlers/game/overworld.cc @@ -1,5 +1,5 @@ -#include "cli/cli.h" #include "zelda3/overworld/overworld.h" +#include "cli/cli.h" #include "cli/handlers/game/overworld_inspect.h" #include @@ -12,14 +12,14 @@ #include #include -#include "absl/flags/flag.h" #include "absl/flags/declare.h" +#include "absl/flags/flag.h" #include "absl/status/statusor.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" -#include "absl/strings/str_join.h" #include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" ABSL_DECLARE_FLAG(std::string, rom); @@ -31,7 +31,8 @@ namespace cli { // Legacy OverworldGetTile class removed - using new CommandHandler system // TODO: Implement OverworldGetTileCommandHandler -absl::Status HandleOverworldGetTileLegacy(const std::vector& arg_vec) { +absl::Status HandleOverworldGetTileLegacy( + const std::vector& arg_vec) { int map_id = -1, x = -1, y = -1; for (size_t i = 0; i < arg_vec.size(); ++i) { @@ -46,12 +47,14 @@ absl::Status HandleOverworldGetTileLegacy(const std::vector& arg_ve } if (map_id == -1 || x == -1 || y == -1) { - return absl::InvalidArgumentError("Usage: overworld get-tile --map --x --y "); + return absl::InvalidArgumentError( + "Usage: overworld get-tile --map --x --y "); } std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; @@ -60,7 +63,7 @@ absl::Status HandleOverworldGetTileLegacy(const std::vector& arg_ve return load_status; } if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } zelda3::Overworld overworld(&rom); @@ -71,14 +74,16 @@ absl::Status HandleOverworldGetTileLegacy(const std::vector& arg_ve uint16_t tile = overworld.GetTile(x, y); - std::cout << "Tile at (" << x << ", " << y << ") on map " << map_id << " is: 0x" << std::hex << tile << std::endl; + std::cout << "Tile at (" << x << ", " << y << ") on map " << map_id + << " is: 0x" << std::hex << tile << std::endl; return absl::OkStatus(); } // Legacy OverworldSetTile class removed - using new CommandHandler system // TODO: Implement OverworldSetTileCommandHandler -absl::Status HandleOverworldSetTileLegacy(const std::vector& arg_vec) { +absl::Status HandleOverworldSetTileLegacy( + const std::vector& arg_vec) { int map_id = -1, x = -1, y = -1, tile_id = -1; for (size_t i = 0; i < arg_vec.size(); ++i) { @@ -95,12 +100,15 @@ absl::Status HandleOverworldSetTileLegacy(const std::vector& arg_ve } if (map_id == -1 || x == -1 || y == -1 || tile_id == -1) { - return absl::InvalidArgumentError("Usage: overworld set-tile --map --x --y --tile "); + return absl::InvalidArgumentError( + "Usage: overworld set-tile --map --x --y --tile " + ""); } std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; @@ -109,7 +117,7 @@ absl::Status HandleOverworldSetTileLegacy(const std::vector& arg_ve return load_status; } if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } zelda3::Overworld overworld(&rom); @@ -136,7 +144,7 @@ absl::Status HandleOverworldSetTileLegacy(const std::vector& arg_ve return save_status; } - std::cout << "✅ Set tile at (" << x << ", " << y << ") on map " << map_id + std::cout << "✅ Set tile at (" << x << ", " << y << ") on map " << map_id << " to: 0x" << std::hex << tile_id << std::dec << std::endl; return absl::OkStatus(); @@ -145,13 +153,15 @@ absl::Status HandleOverworldSetTileLegacy(const std::vector& arg_ve namespace { constexpr absl::string_view kFindTileUsage = - "Usage: overworld find-tile --tile [--map ] [--world ] [--format ]"; + "Usage: overworld find-tile --tile [--map ] [--world " + "] [--format ]"; } // namespace // Legacy OverworldFindTile class removed - using new CommandHandler system // TODO: Implement OverworldFindTileCommandHandler -absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_vec) { +absl::Status HandleOverworldFindTileLegacy( + const std::vector& arg_vec) { std::unordered_map options; std::vector positional; options.reserve(arg_vec.size()); @@ -184,9 +194,9 @@ absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_v } if (!positional.empty()) { - return absl::InvalidArgumentError( - absl::StrCat("Unexpected positional arguments: ", - absl::StrJoin(positional, ", "), "\n", kFindTileUsage)); + return absl::InvalidArgumentError(absl::StrCat( + "Unexpected positional arguments: ", absl::StrJoin(positional, ", "), + "\n", kFindTileUsage)); } auto tile_it = options.find("tile"); @@ -195,8 +205,7 @@ absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_v absl::StrCat("Missing required --tile argument\n", kFindTileUsage)); } - ASSIGN_OR_RETURN(int tile_value, - overworld::ParseNumeric(tile_it->second)); + ASSIGN_OR_RETURN(int tile_value, overworld::ParseNumeric(tile_it->second)); if (tile_value < 0 || tile_value > 0xFFFF) { return absl::InvalidArgumentError( absl::StrCat("Tile ID must be between 0x0000 and 0xFFFF (got ", @@ -206,8 +215,7 @@ absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_v std::optional map_filter; if (auto map_it = options.find("map"); map_it != options.end()) { - ASSIGN_OR_RETURN(int map_value, - overworld::ParseNumeric(map_it->second)); + ASSIGN_OR_RETURN(int map_value, overworld::ParseNumeric(map_it->second)); if (map_value < 0 || map_value >= 0xA0) { return absl::InvalidArgumentError( absl::StrCat("Map ID out of range: ", map_it->second)); @@ -217,22 +225,19 @@ absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_v std::optional world_filter; if (auto world_it = options.find("world"); world_it != options.end()) { - ASSIGN_OR_RETURN(int parsed_world, - overworld::ParseWorldSpecifier(world_it->second)); + ASSIGN_OR_RETURN(int parsed_world, + overworld::ParseWorldSpecifier(world_it->second)); world_filter = parsed_world; } if (map_filter.has_value()) { - ASSIGN_OR_RETURN(int inferred_world, - overworld::InferWorldFromMapId(*map_filter)); + ASSIGN_OR_RETURN(int inferred_world, + overworld::InferWorldFromMapId(*map_filter)); if (world_filter.has_value() && inferred_world != *world_filter) { - return absl::InvalidArgumentError( - absl::StrCat("Map 0x", - absl::StrFormat("%02X", *map_filter), - " belongs to the ", - overworld::WorldName(inferred_world), - " World but --world requested ", - overworld::WorldName(*world_filter))); + return absl::InvalidArgumentError(absl::StrCat( + "Map 0x", absl::StrFormat("%02X", *map_filter), " belongs to the ", + overworld::WorldName(inferred_world), " World but --world requested ", + overworld::WorldName(*world_filter))); } if (!world_filter.has_value()) { world_filter = inferred_world; @@ -277,16 +282,13 @@ absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_v search_options.map_id = map_filter; search_options.world = world_filter; - ASSIGN_OR_RETURN(auto matches, - overworld::FindTileMatches(overworld, tile_id, - search_options)); + ASSIGN_OR_RETURN(auto matches, overworld::FindTileMatches(overworld, tile_id, + search_options)); if (format == "json") { std::cout << "{\n"; - std::cout << absl::StrFormat( - " \"tile\": \"0x%04X\",\n", tile_id); - std::cout << absl::StrFormat( - " \"match_count\": %zu,\n", matches.size()); + std::cout << absl::StrFormat(" \"tile\": \"0x%04X\",\n", tile_id); + std::cout << absl::StrFormat(" \"match_count\": %zu,\n", matches.size()); std::cout << " \"matches\": [\n"; for (size_t i = 0; i < matches.size(); ++i) { const auto& match = matches[i]; @@ -295,15 +297,14 @@ absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_v "\"local\": {\"x\": %d, \"y\": %d}, " "\"global\": {\"x\": %d, \"y\": %d}}%s\n", match.map_id, overworld::WorldName(match.world), match.local_x, - match.local_y, - match.global_x, match.global_y, + match.local_y, match.global_x, match.global_y, (i + 1 == matches.size()) ? "" : ","); } std::cout << " ]\n"; std::cout << "}\n"; } else { - std::cout << absl::StrFormat( - "🔎 Tile 0x%04X → %zu match(es)\n", tile_id, matches.size()); + std::cout << absl::StrFormat("🔎 Tile 0x%04X → %zu match(es)\n", tile_id, + matches.size()); if (matches.empty()) { std::cout << " No matches found." << std::endl; return absl::OkStatus(); @@ -313,8 +314,7 @@ absl::Status HandleOverworldFindTileLegacy(const std::vector& arg_v std::cout << absl::StrFormat( " • Map 0x%02X (%s World) local(%2d,%2d) global(%3d,%3d)\n", match.map_id, overworld::WorldName(match.world), match.local_x, - match.local_y, - match.global_x, match.global_y); + match.local_y, match.global_x, match.global_y); } } @@ -360,9 +360,9 @@ absl::Status HandleOverworldDescribeMapLegacy( } if (!positional.empty()) { - return absl::InvalidArgumentError( - absl::StrCat("Unexpected positional arguments: ", - absl::StrJoin(positional, ", "), "\n", kUsage)); + return absl::InvalidArgumentError(absl::StrCat( + "Unexpected positional arguments: ", absl::StrJoin(positional, ", "), + "\n", kUsage)); } auto map_it = options.find("map"); @@ -370,8 +370,7 @@ absl::Status HandleOverworldDescribeMapLegacy( return absl::InvalidArgumentError(std::string(kUsage)); } - ASSIGN_OR_RETURN(int map_value, - overworld::ParseNumeric(map_it->second)); + ASSIGN_OR_RETURN(int map_value, overworld::ParseNumeric(map_it->second)); if (map_value < 0 || map_value >= zelda3::kNumOverworldMaps) { return absl::InvalidArgumentError( absl::StrCat("Map ID out of range: ", map_it->second)); @@ -438,37 +437,35 @@ absl::Status HandleOverworldDescribeMapLegacy( std::cout << absl::StrFormat(" \"world\": \"%s\",\n", overworld::WorldName(summary.world)); std::cout << absl::StrFormat( - " \"grid\": {\"x\": %d, \"y\": %d, \"index\": %d},\n", - summary.map_x, summary.map_y, summary.local_index); + " \"grid\": {\"x\": %d, \"y\": %d, \"index\": %d},\n", summary.map_x, + summary.map_y, summary.local_index); std::cout << absl::StrFormat( - " \"size\": {\"label\": \"%s\", \"is_large\": %s, \"parent\": \"0x%02X\", \"quadrant\": %d},\n", + " \"size\": {\"label\": \"%s\", \"is_large\": %s, \"parent\": " + "\"0x%02X\", \"quadrant\": %d},\n", summary.area_size, summary.is_large_map ? "true" : "false", summary.parent_map, summary.large_quadrant); - std::cout << absl::StrFormat( - " \"message\": \"0x%04X\",\n", summary.message_id); - std::cout << absl::StrFormat( - " \"area_graphics\": \"0x%02X\",\n", summary.area_graphics); - std::cout << absl::StrFormat( - " \"area_palette\": \"0x%02X\",\n", summary.area_palette); - std::cout << absl::StrFormat( - " \"main_palette\": \"0x%02X\",\n", summary.main_palette); - std::cout << absl::StrFormat( - " \"animated_gfx\": \"0x%02X\",\n", summary.animated_gfx); - std::cout << absl::StrFormat( - " \"subscreen_overlay\": \"0x%04X\",\n", - summary.subscreen_overlay); - std::cout << absl::StrFormat( - " \"area_specific_bg_color\": \"0x%04X\",\n", - summary.area_specific_bg_color); - std::cout << absl::StrFormat( - " \"sprite_graphics\": %s,\n", join_hex_json(summary.sprite_graphics)); - std::cout << absl::StrFormat( - " \"sprite_palettes\": %s,\n", join_hex_json(summary.sprite_palettes)); - std::cout << absl::StrFormat( - " \"area_music\": %s,\n", join_hex_json(summary.area_music)); - std::cout << absl::StrFormat( - " \"static_graphics\": %s,\n", - join_hex_json(summary.static_graphics)); + std::cout << absl::StrFormat(" \"message\": \"0x%04X\",\n", + summary.message_id); + std::cout << absl::StrFormat(" \"area_graphics\": \"0x%02X\",\n", + summary.area_graphics); + std::cout << absl::StrFormat(" \"area_palette\": \"0x%02X\",\n", + summary.area_palette); + std::cout << absl::StrFormat(" \"main_palette\": \"0x%02X\",\n", + summary.main_palette); + std::cout << absl::StrFormat(" \"animated_gfx\": \"0x%02X\",\n", + summary.animated_gfx); + std::cout << absl::StrFormat(" \"subscreen_overlay\": \"0x%04X\",\n", + summary.subscreen_overlay); + std::cout << absl::StrFormat(" \"area_specific_bg_color\": \"0x%04X\",\n", + summary.area_specific_bg_color); + std::cout << absl::StrFormat(" \"sprite_graphics\": %s,\n", + join_hex_json(summary.sprite_graphics)); + std::cout << absl::StrFormat(" \"sprite_palettes\": %s,\n", + join_hex_json(summary.sprite_palettes)); + std::cout << absl::StrFormat(" \"area_music\": %s,\n", + join_hex_json(summary.area_music)); + std::cout << absl::StrFormat(" \"static_graphics\": %s,\n", + join_hex_json(summary.static_graphics)); std::cout << absl::StrFormat( " \"overlay\": {\"enabled\": %s, \"id\": \"0x%04X\"}\n", summary.has_overlay ? "true" : "false", summary.overlay_id); @@ -480,14 +477,15 @@ absl::Status HandleOverworldDescribeMapLegacy( summary.map_x, summary.map_y, summary.local_index); std::cout << absl::StrFormat( - " Size: %s%s | Parent: 0x%02X | Quadrant: %d\n", - summary.area_size, summary.is_large_map ? " (large)" : "", - summary.parent_map, summary.large_quadrant); + " Size: %s%s | Parent: 0x%02X | Quadrant: %d\n", summary.area_size, + summary.is_large_map ? " (large)" : "", summary.parent_map, + summary.large_quadrant); std::cout << absl::StrFormat( " Message: 0x%04X | Area GFX: 0x%02X | Area Palette: 0x%02X\n", summary.message_id, summary.area_graphics, summary.area_palette); std::cout << absl::StrFormat( - " Main Palette: 0x%02X | Animated GFX: 0x%02X | Overlay: %s (0x%04X)\n", + " Main Palette: 0x%02X | Animated GFX: 0x%02X | Overlay: %s " + "(0x%04X)\n", summary.main_palette, summary.animated_gfx, summary.has_overlay ? "yes" : "no", summary.overlay_id); std::cout << absl::StrFormat( @@ -511,7 +509,8 @@ absl::Status HandleOverworldDescribeMapLegacy( absl::Status HandleOverworldListWarpsLegacy( const std::vector& arg_vec) { constexpr absl::string_view kUsage = - "Usage: overworld list-warps [--map ] [--world ] " + "Usage: overworld list-warps [--map ] [--world " + "] " "[--type ] [--format ]"; std::unordered_map options; @@ -546,15 +545,14 @@ absl::Status HandleOverworldListWarpsLegacy( } if (!positional.empty()) { - return absl::InvalidArgumentError( - absl::StrCat("Unexpected positional arguments: ", - absl::StrJoin(positional, ", "), "\n", kUsage)); + return absl::InvalidArgumentError(absl::StrCat( + "Unexpected positional arguments: ", absl::StrJoin(positional, ", "), + "\n", kUsage)); } std::optional map_filter; if (auto it = options.find("map"); it != options.end()) { - ASSIGN_OR_RETURN(int map_value, - overworld::ParseNumeric(it->second)); + ASSIGN_OR_RETURN(int map_value, overworld::ParseNumeric(it->second)); if (map_value < 0 || map_value >= zelda3::kNumOverworldMaps) { return absl::InvalidArgumentError( absl::StrCat("Map ID out of range: ", it->second)); @@ -590,13 +588,10 @@ absl::Status HandleOverworldListWarpsLegacy( ASSIGN_OR_RETURN(int inferred_world, overworld::InferWorldFromMapId(*map_filter)); if (world_filter.has_value() && inferred_world != *world_filter) { - return absl::InvalidArgumentError( - absl::StrCat("Map 0x", - absl::StrFormat("%02X", *map_filter), - " belongs to the ", - overworld::WorldName(inferred_world), - " World but --world requested ", - overworld::WorldName(*world_filter))); + return absl::InvalidArgumentError(absl::StrCat( + "Map 0x", absl::StrFormat("%02X", *map_filter), " belongs to the ", + overworld::WorldName(inferred_world), " World but --world requested ", + overworld::WorldName(*world_filter))); } if (!world_filter.has_value()) { world_filter = inferred_world; @@ -652,39 +647,33 @@ absl::Status HandleOverworldListWarpsLegacy( for (size_t i = 0; i < entries.size(); ++i) { const auto& entry = entries[i]; std::cout << " {\n"; - std::cout << absl::StrFormat( - " \"type\": \"%s\",\n", - overworld::WarpTypeName(entry.type)); - std::cout << absl::StrFormat( - " \"map\": \"0x%02X\",\n", entry.map_id); - std::cout << absl::StrFormat( - " \"world\": \"%s\",\n", - overworld::WorldName(entry.world)); + std::cout << absl::StrFormat(" \"type\": \"%s\",\n", + overworld::WarpTypeName(entry.type)); + std::cout << absl::StrFormat(" \"map\": \"0x%02X\",\n", + entry.map_id); + std::cout << absl::StrFormat(" \"world\": \"%s\",\n", + overworld::WorldName(entry.world)); std::cout << absl::StrFormat( " \"grid\": {\"x\": %d, \"y\": %d, \"index\": %d},\n", entry.map_x, entry.map_y, entry.local_index); std::cout << absl::StrFormat( - " \"tile16\": {\"x\": %d, \"y\": %d},\n", - entry.tile16_x, entry.tile16_y); - std::cout << absl::StrFormat( - " \"pixel\": {\"x\": %d, \"y\": %d},\n", - entry.pixel_x, entry.pixel_y); - std::cout << absl::StrFormat( - " \"map_pos\": \"0x%04X\",\n", entry.map_pos); - std::cout << absl::StrFormat( - " \"deleted\": %s,\n", entry.deleted ? "true" : "false"); - std::cout << absl::StrFormat( - " \"is_hole\": %s", - entry.is_hole ? "true" : "false"); + " \"tile16\": {\"x\": %d, \"y\": %d},\n", entry.tile16_x, + entry.tile16_y); + std::cout << absl::StrFormat(" \"pixel\": {\"x\": %d, \"y\": %d},\n", + entry.pixel_x, entry.pixel_y); + std::cout << absl::StrFormat(" \"map_pos\": \"0x%04X\",\n", + entry.map_pos); + std::cout << absl::StrFormat(" \"deleted\": %s,\n", + entry.deleted ? "true" : "false"); + std::cout << absl::StrFormat(" \"is_hole\": %s", + entry.is_hole ? "true" : "false"); if (entry.entrance_id.has_value()) { - std::cout << absl::StrFormat( - ",\n \"entrance_id\": \"0x%02X\"", - *entry.entrance_id); + std::cout << absl::StrFormat(",\n \"entrance_id\": \"0x%02X\"", + *entry.entrance_id); } if (entry.entrance_name.has_value()) { - std::cout << absl::StrFormat( - ",\n \"entrance_name\": \"%s\"", - *entry.entrance_name); + std::cout << absl::StrFormat(",\n \"entrance_name\": \"%s\"", + *entry.entrance_name); } std::cout << "\n }" << (i + 1 == entries.size() ? "" : ",") << "\n"; } @@ -692,7 +681,8 @@ absl::Status HandleOverworldListWarpsLegacy( std::cout << "}\n"; } else { if (entries.empty()) { - std::cout << "No overworld warps match the specified filters." << std::endl; + std::cout << "No overworld warps match the specified filters." + << std::endl; return absl::OkStatus(); } @@ -705,7 +695,7 @@ absl::Status HandleOverworldListWarpsLegacy( entry.pixel_x, entry.pixel_y); if (entry.entrance_id.has_value()) { line = absl::StrCat(line, - absl::StrFormat(" id=0x%02X", *entry.entrance_id)); + absl::StrFormat(" id=0x%02X", *entry.entrance_id)); } if (entry.entrance_name.has_value()) { line = absl::StrCat(line, " (", *entry.entrance_name, ")"); @@ -729,7 +719,8 @@ absl::Status HandleOverworldListWarpsLegacy( // Legacy OverworldSelectRect class removed - using new CommandHandler system // TODO: Implement OverworldSelectRectCommandHandler -absl::Status HandleOverworldSelectRectLegacy(const std::vector& arg_vec) { +absl::Status HandleOverworldSelectRectLegacy( + const std::vector& arg_vec) { int map_id = -1, x1 = -1, y1 = -1, x2 = -1, y2 = -1; for (size_t i = 0; i < arg_vec.size(); ++i) { @@ -749,15 +740,16 @@ absl::Status HandleOverworldSelectRectLegacy(const std::vector& arg if (map_id == -1 || x1 == -1 || y1 == -1 || x2 == -1 || y2 == -1) { return absl::InvalidArgumentError( - "Usage: overworld select-rect --map --x1 --y1 --x2 --y2 "); + "Usage: overworld select-rect --map --x1 --y1 --x2 " + " --y2 "); } - std::cout << "✅ Selected rectangle on map " << map_id - << " from (" << x1 << "," << y1 << ") to (" << x2 << "," << y2 << ")" << std::endl; - + std::cout << "✅ Selected rectangle on map " << map_id << " from (" << x1 + << "," << y1 << ") to (" << x2 << "," << y2 << ")" << std::endl; + int width = std::abs(x2 - x1) + 1; int height = std::abs(y2 - y1) + 1; - std::cout << " Selection size: " << width << "x" << height << " tiles (" + std::cout << " Selection size: " << width << "x" << height << " tiles (" << (width * height) << " total)" << std::endl; return absl::OkStatus(); @@ -765,7 +757,8 @@ absl::Status HandleOverworldSelectRectLegacy(const std::vector& arg // Legacy OverworldScrollTo class removed - using new CommandHandler system // TODO: Implement OverworldScrollToCommandHandler -absl::Status HandleOverworldScrollToLegacy(const std::vector& arg_vec) { +absl::Status HandleOverworldScrollToLegacy( + const std::vector& arg_vec) { int map_id = -1, x = -1, y = -1; bool center = false; @@ -787,7 +780,8 @@ absl::Status HandleOverworldScrollToLegacy(const std::vector& arg_v "Usage: overworld scroll-to --map --x --y [--center]"); } - std::cout << "✅ Scrolled to tile (" << x << "," << y << ") on map " << map_id; + std::cout << "✅ Scrolled to tile (" << x << "," << y << ") on map " + << map_id; if (center) { std::cout << " (centered)"; } @@ -798,7 +792,8 @@ absl::Status HandleOverworldScrollToLegacy(const std::vector& arg_v // Legacy OverworldSetZoom class removed - using new CommandHandler system // TODO: Implement OverworldSetZoomCommandHandler -absl::Status HandleOverworldSetZoomLegacy(const std::vector& arg_vec) { +absl::Status HandleOverworldSetZoomLegacy( + const std::vector& arg_vec) { float zoom = -1.0f; for (size_t i = 0; i < arg_vec.size(); ++i) { @@ -824,7 +819,8 @@ absl::Status HandleOverworldSetZoomLegacy(const std::vector& arg_ve // Legacy OverworldGetVisibleRegion class removed - using new CommandHandler system // TODO: Implement OverworldGetVisibleRegionCommandHandler -absl::Status HandleOverworldGetVisibleRegionLegacy(const std::vector& arg_vec) { +absl::Status HandleOverworldGetVisibleRegionLegacy( + const std::vector& arg_vec) { int map_id = -1; std::string format = "text"; @@ -839,14 +835,16 @@ absl::Status HandleOverworldGetVisibleRegionLegacy(const std::vector [--format json|text]"); + "Usage: overworld get-visible-region --map [--format " + "json|text]"); } // Note: This would query the canvas automation API in a live GUI context // For now, return placeholder data if (format == "json") { std::cout << R"({ - "map_id": )" << map_id << R"(, + "map_id": )" << map_id + << R"(, "visible_region": { "min_x": 0, "min_y": 0, @@ -865,5 +863,5 @@ absl::Status HandleOverworldGetVisibleRegionLegacy(const std::vector [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"tile"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -40,13 +40,13 @@ class OverworldDescribeMapCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "overworld-describe-map --screen [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"screen"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -61,13 +61,13 @@ class OverworldListWarpsCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "overworld-list-warps [--screen ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -80,15 +80,16 @@ class OverworldListSpritesCommandHandler : public resources::CommandHandler { return "List all sprites in overworld maps"; } std::string GetUsage() const { - return "overworld-list-sprites [--screen ] [--format ]"; + return "overworld-list-sprites [--screen ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -101,15 +102,16 @@ class OverworldGetEntranceCommandHandler : public resources::CommandHandler { return "Get entrance information from overworld"; } std::string GetUsage() const { - return "overworld-get-entrance --entrance [--format ]"; + return "overworld-get-entrance --entrance [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"entrance"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -124,13 +126,13 @@ class OverworldTileStatsCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "overworld-tile-stats [--screen ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/game/overworld_inspect.cc b/src/cli/handlers/game/overworld_inspect.cc index 93b5f9b8..66b11237 100644 --- a/src/cli/handlers/game/overworld_inspect.cc +++ b/src/cli/handlers/game/overworld_inspect.cc @@ -10,12 +10,12 @@ #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "util/macro.h" #include "zelda3/common.h" #include "zelda3/overworld/overworld.h" #include "zelda3/overworld/overworld_entrance.h" #include "zelda3/overworld/overworld_exit.h" #include "zelda3/overworld/overworld_map.h" -#include "util/macro.h" namespace yaze { namespace cli { @@ -102,17 +102,17 @@ void PopulateCommonWarpFields(WarpEntry& entry, uint16_t raw_map_id, absl::StatusOr ParseNumeric(std::string_view value, int base) { try { - size_t processed = 0; - int result = std::stoi(std::string(value), &processed, base); - if (processed != value.size()) { + size_t processed = 0; + int result = std::stoi(std::string(value), &processed, base); + if (processed != value.size()) { + return absl::InvalidArgumentError( + absl::StrCat("Invalid numeric value: ", std::string(value))); + } + return result; + } catch (const std::exception&) { return absl::InvalidArgumentError( absl::StrCat("Invalid numeric value: ", std::string(value))); } - return result; -} catch (const std::exception&) { - return absl::InvalidArgumentError( - absl::StrCat("Invalid numeric value: ", std::string(value))); -} } absl::StatusOr ParseWorldSpecifier(std::string_view value) { @@ -224,7 +224,7 @@ absl::StatusOr BuildMapSummary(zelda3::Overworld& overworld, } absl::StatusOr> CollectWarpEntries( - const zelda3::Overworld& overworld, const WarpQuery& query) { + const zelda3::Overworld& overworld, const WarpQuery& query) { std::vector entries; const auto& entrances = overworld.entrances(); @@ -275,22 +275,22 @@ absl::StatusOr> CollectWarpEntries( entries.push_back(std::move(entry)); } - std::sort(entries.begin(), entries.end(), [](const WarpEntry& a, - const WarpEntry& b) { - if (a.world != b.world) { - return a.world < b.world; - } - if (a.map_id != b.map_id) { - return a.map_id < b.map_id; - } - if (a.tile16_y != b.tile16_y) { - return a.tile16_y < b.tile16_y; - } - if (a.tile16_x != b.tile16_x) { - return a.tile16_x < b.tile16_x; - } - return static_cast(a.type) < static_cast(b.type); - }); + std::sort(entries.begin(), entries.end(), + [](const WarpEntry& a, const WarpEntry& b) { + if (a.world != b.world) { + return a.world < b.world; + } + if (a.map_id != b.map_id) { + return a.map_id < b.map_id; + } + if (a.tile16_y != b.tile16_y) { + return a.tile16_y < b.tile16_y; + } + if (a.tile16_x != b.tile16_x) { + return a.tile16_x < b.tile16_x; + } + return static_cast(a.type) < static_cast(b.type); + }); return entries; } @@ -309,14 +309,12 @@ absl::StatusOr> FindTileMatches( } if (options.map_id.has_value() && options.world.has_value()) { - ASSIGN_OR_RETURN(int inferred_world, - InferWorldFromMapId(*options.map_id)); + ASSIGN_OR_RETURN(int inferred_world, InferWorldFromMapId(*options.map_id)); if (inferred_world != *options.world) { - return absl::InvalidArgumentError( - absl::StrFormat( - "Map 0x%02X belongs to the %s World but --world requested %s", - *options.map_id, WorldName(inferred_world), - WorldName(*options.world))); + return absl::InvalidArgumentError(absl::StrFormat( + "Map 0x%02X belongs to the %s World but --world requested %s", + *options.map_id, WorldName(inferred_world), + WorldName(*options.world))); } } @@ -324,8 +322,7 @@ absl::StatusOr> FindTileMatches( if (options.world.has_value()) { worlds.push_back(*options.world); } else if (options.map_id.has_value()) { - ASSIGN_OR_RETURN(int inferred_world, - InferWorldFromMapId(*options.map_id)); + ASSIGN_OR_RETURN(int inferred_world, InferWorldFromMapId(*options.map_id)); worlds.push_back(inferred_world); } else { worlds = {0, 1, 2}; @@ -375,8 +372,8 @@ absl::StatusOr> FindTileMatches( uint16_t current_tile = overworld.GetTile(global_x, global_y); if (current_tile == tile_id) { - matches.push_back({map_id, world, local_x, local_y, global_x, - global_y}); + matches.push_back( + {map_id, world, local_x, local_y, global_x, global_y}); } } } @@ -393,26 +390,27 @@ absl::StatusOr> CollectOverworldSprites( // Iterate through all 3 game states (beginning, zelda, agahnim) for (int game_state = 0; game_state < 3; ++game_state) { const auto& sprites = overworld.sprites(game_state); - + for (const auto& sprite : sprites) { // Apply filters if (query.sprite_id.has_value() && sprite.id() != *query.sprite_id) { continue; } - + int map_id = sprite.map_id(); if (query.map_id.has_value() && map_id != *query.map_id) { continue; } - + // Determine world from map_id - int world = (map_id >= kSpecialWorldOffset) ? 2 - : (map_id >= kDarkWorldOffset ? 1 : 0); - + int world = (map_id >= kSpecialWorldOffset) + ? 2 + : (map_id >= kDarkWorldOffset ? 1 : 0); + if (query.world.has_value() && world != *query.world) { continue; } - + OverworldSprite entry; entry.sprite_id = sprite.id(); entry.map_id = map_id; @@ -421,7 +419,7 @@ absl::StatusOr> CollectOverworldSprites( entry.y = sprite.y(); // Sprite names would come from a label system if available // entry.sprite_name = GetSpriteName(sprite.id()); - + results.push_back(entry); } } @@ -432,47 +430,47 @@ absl::StatusOr> CollectOverworldSprites( absl::StatusOr GetEntranceDetails( const zelda3::Overworld& overworld, uint8_t entrance_id) { const auto& entrances = overworld.entrances(); - + if (entrance_id >= entrances.size()) { - return absl::NotFoundError( - absl::StrFormat("Entrance %d not found (max: %d)", - entrance_id, entrances.size() - 1)); + return absl::NotFoundError(absl::StrFormat( + "Entrance %d not found (max: %d)", entrance_id, entrances.size() - 1)); } - + const auto& entrance = entrances[entrance_id]; - + EntranceDetails details; details.entrance_id = entrance_id; details.map_id = entrance.map_id_; - + // Determine world from map_id - details.world = (details.map_id >= kSpecialWorldOffset) ? 2 - : (details.map_id >= kDarkWorldOffset ? 1 : 0); - + details.world = (details.map_id >= kSpecialWorldOffset) + ? 2 + : (details.map_id >= kDarkWorldOffset ? 1 : 0); + details.x = entrance.x_; details.y = entrance.y_; details.area_x = entrance.game_x_; details.area_y = entrance.game_y_; details.map_pos = entrance.map_pos_; details.is_hole = entrance.is_hole_; - + // Get entrance name if available details.entrance_name = EntranceLabel(entrance_id); - + return details; } absl::StatusOr AnalyzeTileUsage( zelda3::Overworld& overworld, uint16_t tile_id, const TileSearchOptions& options) { - + // Use FindTileMatches to get all occurrences ASSIGN_OR_RETURN(auto matches, FindTileMatches(overworld, tile_id, options)); - + TileStatistics stats; stats.tile_id = tile_id; stats.count = static_cast(matches.size()); - + // If scoped to a specific map, store that info if (options.map_id.has_value()) { stats.map_id = *options.map_id; @@ -485,13 +483,13 @@ absl::StatusOr AnalyzeTileUsage( stats.map_id = -1; // Indicates all maps stats.world = -1; } - + // Store positions (convert from TileMatch to pair) stats.positions.reserve(matches.size()); for (const auto& match : matches) { stats.positions.emplace_back(match.local_x, match.local_y); } - + return stats; } diff --git a/src/cli/handlers/game/overworld_inspect.h b/src/cli/handlers/game/overworld_inspect.h index 464e2d4e..39cdb54b 100644 --- a/src/cli/handlers/game/overworld_inspect.h +++ b/src/cli/handlers/game/overworld_inspect.h @@ -17,7 +17,7 @@ class Overworld; class OverworldEntrance; class OverworldExit; class OverworldMap; -} +} // namespace zelda3 namespace cli { namespace overworld { @@ -141,7 +141,7 @@ absl::StatusOr BuildMapSummary(zelda3::Overworld& overworld, int map_id); absl::StatusOr> CollectWarpEntries( - const zelda3::Overworld& overworld, const WarpQuery& query); + const zelda3::Overworld& overworld, const WarpQuery& query); absl::StatusOr> FindTileMatches( zelda3::Overworld& overworld, uint16_t tile_id, @@ -154,7 +154,7 @@ absl::StatusOr GetEntranceDetails( const zelda3::Overworld& overworld, uint8_t entrance_id); absl::StatusOr AnalyzeTileUsage( - zelda3::Overworld& overworld, uint16_t tile_id, + zelda3::Overworld& overworld, uint16_t tile_id, const TileSearchOptions& options = {}); } // namespace overworld diff --git a/src/cli/handlers/graphics/gfx.cc b/src/cli/handlers/graphics/gfx.cc index ab796951..752c8da6 100644 --- a/src/cli/handlers/graphics/gfx.cc +++ b/src/cli/handlers/graphics/gfx.cc @@ -1,8 +1,8 @@ -#include "cli/cli.h" -#include "app/gfx/util/scad_format.h" -#include "app/gfx/resource/arena.h" -#include "absl/flags/flag.h" #include "absl/flags/declare.h" +#include "absl/flags/flag.h" +#include "app/gfx/resource/arena.h" +#include "app/gfx/util/scad_format.h" +#include "cli/cli.h" ABSL_DECLARE_FLAG(std::string, rom); @@ -13,7 +13,8 @@ namespace cli { // TODO: Implement GfxExportCommandHandler absl::Status HandleGfxExportLegacy(const std::vector& arg_vec) { if (arg_vec.size() < 2) { - return absl::InvalidArgumentError("Usage: gfx export-sheet --to "); + return absl::InvalidArgumentError( + "Usage: gfx export-sheet --to "); } int sheet_id = std::stoi(arg_vec[0]); @@ -21,30 +22,33 @@ absl::Status HandleGfxExportLegacy(const std::vector& arg_vec) { std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; rom.LoadFromFile(rom_file); if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } auto& arena = gfx::Arena::Get(); auto sheet = arena.gfx_sheet(sheet_id); if (!sheet.is_active()) { - return absl::NotFoundError("Graphics sheet not found."); + return absl::NotFoundError("Graphics sheet not found."); } // For now, we will just save the raw 8bpp data. // TODO: Convert the 8bpp data to the correct SNES bpp format. - std::vector header; // Empty header for now - auto status = gfx::SaveCgx(sheet.depth(), output_file, sheet.vector(), header); + std::vector header; // Empty header for now + auto status = + gfx::SaveCgx(sheet.depth(), output_file, sheet.vector(), header); if (!status.ok()) { - return status; + return status; } - std::cout << "Successfully exported graphics sheet " << sheet_id << " to " << output_file << std::endl; + std::cout << "Successfully exported graphics sheet " << sheet_id << " to " + << output_file << std::endl; return absl::OkStatus(); } @@ -53,7 +57,8 @@ absl::Status HandleGfxExportLegacy(const std::vector& arg_vec) { // TODO: Implement GfxImportCommandHandler absl::Status HandleGfxImportLegacy(const std::vector& arg_vec) { if (arg_vec.size() < 2) { - return absl::InvalidArgumentError("Usage: gfx import-sheet --from "); + return absl::InvalidArgumentError( + "Usage: gfx import-sheet --from "); } int sheet_id = std::stoi(arg_vec[0]); @@ -61,25 +66,26 @@ absl::Status HandleGfxImportLegacy(const std::vector& arg_vec) { std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; rom.LoadFromFile(rom_file); if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } std::vector cgx_data, cgx_loaded, cgx_header; auto status = gfx::LoadCgx(8, input_file, cgx_data, cgx_loaded, cgx_header); if (!status.ok()) { - return status; + return status; } auto& arena = gfx::Arena::Get(); auto sheet = arena.gfx_sheet(sheet_id); if (!sheet.is_active()) { - return absl::NotFoundError("Graphics sheet not found."); + return absl::NotFoundError("Graphics sheet not found."); } // TODO: Convert the 8bpp data to the correct SNES bpp format before writing. @@ -92,11 +98,12 @@ absl::Status HandleGfxImportLegacy(const std::vector& arg_vec) { return save_status; } - std::cout << "Successfully imported graphics sheet " << sheet_id << " from " << input_file << std::endl; + std::cout << "Successfully imported graphics sheet " << sheet_id << " from " + << input_file << std::endl; std::cout << "✅ ROM saved to: " << rom.filename() << std::endl; return absl::OkStatus(); } -} // namespace cli -} // namespace yaze +} // namespace cli +} // namespace yaze diff --git a/src/cli/handlers/graphics/hex_commands.cc b/src/cli/handlers/graphics/hex_commands.cc index fc9078fb..24e8c63f 100644 --- a/src/cli/handlers/graphics/hex_commands.cc +++ b/src/cli/handlers/graphics/hex_commands.cc @@ -1,19 +1,20 @@ #include "cli/handlers/graphics/hex_commands.h" +#include #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" -#include namespace yaze { namespace cli { namespace handlers { -absl::Status HexReadCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status HexReadCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto address_str = parser.GetString("address").value(); auto length = parser.GetInt("length").value_or(16); auto format = parser.GetString("format").value_or("both"); - + // Parse address uint32_t address; try { @@ -23,33 +24,33 @@ absl::Status HexReadCommandHandler::Execute(Rom* rom, const resources::ArgumentP return absl::InvalidArgumentError("Invalid hex address format"); } } catch (const std::exception& e) { - return absl::InvalidArgumentError( - absl::StrFormat("Failed to parse address '%s': %s", address_str, e.what())); + return absl::InvalidArgumentError(absl::StrFormat( + "Failed to parse address '%s': %s", address_str, e.what())); } - + // Validate range if (address + length > rom->size()) { - return absl::OutOfRangeError( - absl::StrFormat("Read beyond ROM: 0x%X+%d > %zu", - address, length, rom->size())); + return absl::OutOfRangeError(absl::StrFormat( + "Read beyond ROM: 0x%X+%d > %zu", address, length, rom->size())); } - + // Read and format data const uint8_t* data = rom->data() + address; - + formatter.BeginObject("Hex Read"); formatter.AddHexField("address", address, 6); formatter.AddField("length", length); formatter.AddField("format", format); - + // Format hex data std::string hex_data; for (int i = 0; i < length; ++i) { absl::StrAppendFormat(&hex_data, "%02X", data[i]); - if (i < length - 1) hex_data += " "; + if (i < length - 1) + hex_data += " "; } formatter.AddField("hex_data", hex_data); - + // Format ASCII data std::string ascii_data; for (int i = 0; i < length; ++i) { @@ -58,15 +59,16 @@ absl::Status HexReadCommandHandler::Execute(Rom* rom, const resources::ArgumentP } formatter.AddField("ascii_data", ascii_data); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status HexWriteCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status HexWriteCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto address_str = parser.GetString("address").value(); auto data_str = parser.GetString("data").value(); - + // Parse address uint32_t address; try { @@ -76,16 +78,17 @@ absl::Status HexWriteCommandHandler::Execute(Rom* rom, const resources::Argument return absl::InvalidArgumentError("Invalid hex address format"); } } catch (const std::exception& e) { - return absl::InvalidArgumentError( - absl::StrFormat("Failed to parse address '%s': %s", address_str, e.what())); + return absl::InvalidArgumentError(absl::StrFormat( + "Failed to parse address '%s': %s", address_str, e.what())); } - + // Parse data bytes std::vector byte_strs = absl::StrSplit(data_str, ' '); std::vector bytes; - + for (const auto& byte_str : byte_strs) { - if (byte_str.empty()) continue; + if (byte_str.empty()) + continue; try { uint8_t value = static_cast(std::stoul(byte_str, nullptr, 16)); bytes.push_back(value); @@ -94,42 +97,44 @@ absl::Status HexWriteCommandHandler::Execute(Rom* rom, const resources::Argument absl::StrFormat("Invalid byte '%s': %s", byte_str, e.what())); } } - + // Validate range if (address + bytes.size() > rom->size()) { return absl::OutOfRangeError( - absl::StrFormat("Write beyond ROM: 0x%X+%zu > %zu", - address, bytes.size(), rom->size())); + absl::StrFormat("Write beyond ROM: 0x%X+%zu > %zu", address, + bytes.size(), rom->size())); } - + // Write data for (size_t i = 0; i < bytes.size(); ++i) { rom->WriteByte(address + i, bytes[i]); } - + // Format written data std::string hex_data; for (size_t i = 0; i < bytes.size(); ++i) { absl::StrAppendFormat(&hex_data, "%02X", bytes[i]); - if (i < bytes.size() - 1) hex_data += " "; + if (i < bytes.size() - 1) + hex_data += " "; } - + formatter.BeginObject("Hex Write"); formatter.AddHexField("address", address, 6); formatter.AddField("bytes_written", static_cast(bytes.size())); formatter.AddField("hex_data", hex_data); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status HexSearchCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status HexSearchCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto pattern_str = parser.GetString("pattern").value(); auto start_str = parser.GetString("start").value_or("0x000000"); auto end_str = parser.GetString("end").value_or( absl::StrFormat("0x%06X", static_cast(rom->size()))); - + // Parse addresses uint32_t start_address, end_address; try { @@ -139,13 +144,14 @@ absl::Status HexSearchCommandHandler::Execute(Rom* rom, const resources::Argumen return absl::InvalidArgumentError( absl::StrFormat("Invalid address format: %s", e.what())); } - + // Parse pattern std::vector byte_strs = absl::StrSplit(pattern_str, ' '); std::vector> pattern; // (value, is_wildcard) - + for (const auto& byte_str : byte_strs) { - if (byte_str.empty()) continue; + if (byte_str.empty()) + continue; if (byte_str == "??" || byte_str == "**") { pattern.push_back({0, true}); // Wildcard } else { @@ -153,20 +159,20 @@ absl::Status HexSearchCommandHandler::Execute(Rom* rom, const resources::Argumen uint8_t value = static_cast(std::stoul(byte_str, nullptr, 16)); pattern.push_back({value, false}); } catch (const std::exception& e) { - return absl::InvalidArgumentError( - absl::StrFormat("Invalid pattern byte '%s': %s", byte_str, e.what())); + return absl::InvalidArgumentError(absl::StrFormat( + "Invalid pattern byte '%s': %s", byte_str, e.what())); } } } - + if (pattern.empty()) { return absl::InvalidArgumentError("Empty pattern"); } - + // Search for pattern std::vector matches; const uint8_t* rom_data = rom->data(); - + for (uint32_t i = start_address; i <= end_address - pattern.size(); ++i) { bool match = true; for (size_t j = 0; j < pattern.size(); ++j) { @@ -180,20 +186,20 @@ absl::Status HexSearchCommandHandler::Execute(Rom* rom, const resources::Argumen matches.push_back(i); } } - + formatter.BeginObject("Hex Search Results"); formatter.AddField("pattern", pattern_str); formatter.AddHexField("start_address", start_address, 6); formatter.AddHexField("end_address", end_address, 6); formatter.AddField("matches_found", static_cast(matches.size())); - + formatter.BeginArray("matches"); for (uint32_t match : matches) { formatter.AddArrayItem(absl::StrFormat("0x%06X", match)); } formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/graphics/hex_commands.h b/src/cli/handlers/graphics/hex_commands.h index 2295669c..bc47fd08 100644 --- a/src/cli/handlers/graphics/hex_commands.h +++ b/src/cli/handlers/graphics/hex_commands.h @@ -17,15 +17,16 @@ class HexReadCommandHandler : public resources::CommandHandler { return "Read hex data from ROM at specified address"; } std::string GetUsage() const { - return "hex-read --address
[--length ] [--format ]"; + return "hex-read --address
[--length ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"address"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -40,13 +41,13 @@ class HexWriteCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "hex-write --address
--data "; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"address", "data"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -61,13 +62,13 @@ class HexSearchCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "hex-search --pattern [--start ] [--end ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"pattern"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/graphics/palette.cc b/src/cli/handlers/graphics/palette.cc index b274fb2a..609c4072 100644 --- a/src/cli/handlers/graphics/palette.cc +++ b/src/cli/handlers/graphics/palette.cc @@ -1,10 +1,10 @@ #include "cli/cli.h" #include "cli/tui/palette_editor.h" -#include "app/gfx/util/scad_format.h" -#include "app/gfx/types/snes_palette.h" -#include "absl/flags/flag.h" #include "absl/flags/declare.h" +#include "absl/flags/flag.h" +#include "app/gfx/types/snes_palette.h" +#include "app/gfx/util/scad_format.h" ABSL_DECLARE_FLAG(std::string, rom); @@ -20,30 +20,32 @@ absl::Status HandlePaletteImportLegacy(const std::vector& arg_vec); // Legacy Palette class removed - using new CommandHandler system // This implementation should be moved to PaletteCommandHandler absl::Status HandlePaletteLegacy(const std::vector& arg_vec) { - if (arg_vec.empty()) { - return absl::InvalidArgumentError("Usage: palette [options]"); - } + if (arg_vec.empty()) { + return absl::InvalidArgumentError( + "Usage: palette [options]"); + } - const std::string& action = arg_vec[0]; - std::vector new_args(arg_vec.begin() + 1, arg_vec.end()); + const std::string& action = arg_vec[0]; + std::vector new_args(arg_vec.begin() + 1, arg_vec.end()); - if (action == "export") { - return HandlePaletteExportLegacy(new_args); - } else if (action == "import") { - return HandlePaletteImportLegacy(new_args); - } + if (action == "export") { + return HandlePaletteExportLegacy(new_args); + } else if (action == "import") { + return HandlePaletteImportLegacy(new_args); + } - return absl::InvalidArgumentError("Invalid action for palette command."); + return absl::InvalidArgumentError("Invalid action for palette command."); } // Legacy Palette TUI removed - using new CommandHandler system void HandlePaletteTUI(ftxui::ScreenInteractive& screen) { - // TODO: Implement palette editor TUI - (void)screen; // Suppress unused parameter warning + // TODO: Implement palette editor TUI + (void)screen; // Suppress unused parameter warning } // Legacy PaletteExport class removed - using new CommandHandler system -absl::Status HandlePaletteExportLegacy(const std::vector& arg_vec) { +absl::Status HandlePaletteExportLegacy( + const std::vector& arg_vec) { std::string group_name; int palette_id = -1; std::string output_file; @@ -60,52 +62,56 @@ absl::Status HandlePaletteExportLegacy(const std::vector& arg_vec) } if (group_name.empty() || palette_id == -1 || output_file.empty()) { - return absl::InvalidArgumentError("Usage: palette export --group --id --to "); + return absl::InvalidArgumentError( + "Usage: palette export --group --id --to "); } std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; rom.LoadFromFile(rom_file); if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } auto palette_group = rom.palette_group().get_group(group_name); if (!palette_group) { - return absl::NotFoundError("Palette group not found."); + return absl::NotFoundError("Palette group not found."); } auto palette = palette_group->palette(palette_id); if (palette.empty()) { - return absl::NotFoundError("Palette not found."); + return absl::NotFoundError("Palette not found."); } std::vector sdl_palette; for (const auto& color : palette) { - SDL_Color sdl_color; - sdl_color.r = color.rgb().x; - sdl_color.g = color.rgb().y; - sdl_color.b = color.rgb().z; - sdl_color.a = 255; - sdl_palette.push_back(sdl_color); + SDL_Color sdl_color; + sdl_color.r = color.rgb().x; + sdl_color.g = color.rgb().y; + sdl_color.b = color.rgb().z; + sdl_color.a = 255; + sdl_palette.push_back(sdl_color); } auto status = gfx::SaveCol(output_file, sdl_palette); if (!status.ok()) { - return status; + return status; } - std::cout << "Successfully exported palette " << palette_id << " from group " << group_name << " to " << output_file << std::endl; + std::cout << "Successfully exported palette " << palette_id << " from group " + << group_name << " to " << output_file << std::endl; return absl::OkStatus(); } // Legacy PaletteImport class removed - using new CommandHandler system -absl::Status HandlePaletteImportLegacy(const std::vector& arg_vec) { +absl::Status HandlePaletteImportLegacy( + const std::vector& arg_vec) { std::string group_name; int palette_id = -1; std::string input_file; @@ -122,33 +128,36 @@ absl::Status HandlePaletteImportLegacy(const std::vector& arg_vec) } if (group_name.empty() || palette_id == -1 || input_file.empty()) { - return absl::InvalidArgumentError("Usage: palette import --group --id --from "); + return absl::InvalidArgumentError( + "Usage: palette import --group --id --from "); } std::string rom_file = absl::GetFlag(FLAGS_rom); if (rom_file.empty()) { - return absl::InvalidArgumentError("ROM file must be provided via --rom flag."); + return absl::InvalidArgumentError( + "ROM file must be provided via --rom flag."); } Rom rom; rom.LoadFromFile(rom_file); if (!rom.is_loaded()) { - return absl::AbortedError("Failed to load ROM."); + return absl::AbortedError("Failed to load ROM."); } auto sdl_palette = gfx::DecodeColFile(input_file); if (sdl_palette.empty()) { - return absl::AbortedError("Failed to load palette file."); + return absl::AbortedError("Failed to load palette file."); } gfx::SnesPalette snes_palette; for (const auto& sdl_color : sdl_palette) { - snes_palette.AddColor(gfx::SnesColor(sdl_color.r, sdl_color.g, sdl_color.b)); + snes_palette.AddColor( + gfx::SnesColor(sdl_color.r, sdl_color.g, sdl_color.b)); } auto palette_group = rom.palette_group().get_group(group_name); if (!palette_group) { - return absl::NotFoundError("Palette group not found."); + return absl::NotFoundError("Palette group not found."); } // Replace the palette at the specified index @@ -161,11 +170,12 @@ absl::Status HandlePaletteImportLegacy(const std::vector& arg_vec) return save_status; } - std::cout << "Successfully imported palette " << palette_id << " to group " << group_name << " from " << input_file << std::endl; + std::cout << "Successfully imported palette " << palette_id << " to group " + << group_name << " from " << input_file << std::endl; std::cout << "✅ ROM saved to: " << rom.filename() << std::endl; return absl::OkStatus(); } -} // namespace cli -} // namespace yaze +} // namespace cli +} // namespace yaze diff --git a/src/cli/handlers/graphics/palette_commands.cc b/src/cli/handlers/graphics/palette_commands.cc index 2ef5bc62..3d4a9981 100644 --- a/src/cli/handlers/graphics/palette_commands.cc +++ b/src/cli/handlers/graphics/palette_commands.cc @@ -7,68 +7,72 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status PaletteGetColorsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status PaletteGetColorsCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto palette_id_str = parser.GetString("palette").value(); - + int palette_id; if (!absl::SimpleHexAtoi(palette_id_str, &palette_id)) { return absl::InvalidArgumentError( "Invalid palette ID format. Must be hex."); } - + formatter.BeginObject("Palette Colors"); formatter.AddField("palette_id", absl::StrFormat("0x%02X", palette_id)); - + // TODO: Implement actual palette color retrieval // This would read from ROM and parse palette data formatter.AddField("total_colors", 16); formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Palette color retrieval requires ROM palette parsing"); - + formatter.AddField("message", + "Palette color retrieval requires ROM palette parsing"); + formatter.BeginArray("colors"); for (int i = 0; i < 16; ++i) { formatter.AddArrayItem(absl::StrFormat("Color %d: #000000", i)); } formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status PaletteSetColorCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status PaletteSetColorCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto palette_id_str = parser.GetString("palette").value(); auto index_str = parser.GetString("index").value(); auto color_str = parser.GetString("color").value(); - + int palette_id, color_index; if (!absl::SimpleHexAtoi(palette_id_str, &palette_id) || !absl::SimpleAtoi(index_str, &color_index)) { - return absl::InvalidArgumentError( - "Invalid palette ID or index format."); + return absl::InvalidArgumentError("Invalid palette ID or index format."); } - + formatter.BeginObject("Palette Color Set"); formatter.AddField("palette_id", absl::StrFormat("0x%02X", palette_id)); formatter.AddField("color_index", color_index); formatter.AddField("color_value", color_str); - + // TODO: Implement actual palette color setting // This would write to ROM and update palette data formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Palette color setting requires ROM palette writing"); + formatter.AddField("message", + "Palette color setting requires ROM palette writing"); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status PaletteAnalyzeCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status PaletteAnalyzeCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto palette_id_str = parser.GetString("palette").value_or("all"); - + formatter.BeginObject("Palette Analysis"); - + if (palette_id_str == "all") { formatter.AddField("analysis_type", "All Palettes"); formatter.AddField("total_palettes", 32); @@ -81,13 +85,14 @@ absl::Status PaletteAnalyzeCommandHandler::Execute(Rom* rom, const resources::Ar formatter.AddField("analysis_type", "Single Palette"); formatter.AddField("palette_id", absl::StrFormat("0x%02X", palette_id)); } - + // TODO: Implement actual palette analysis // This would analyze color usage, contrast, etc. formatter.AddField("status", "not_implemented"); - formatter.AddField("message", "Palette analysis requires color analysis algorithms"); + formatter.AddField("message", + "Palette analysis requires color analysis algorithms"); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/graphics/palette_commands.h b/src/cli/handlers/graphics/palette_commands.h index 451dfb50..f4d2d4c6 100644 --- a/src/cli/handlers/graphics/palette_commands.h +++ b/src/cli/handlers/graphics/palette_commands.h @@ -13,19 +13,17 @@ namespace handlers { class PaletteGetColorsCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "palette-get-colors"; } - std::string GetDescription() const { - return "Get colors from a palette"; - } + std::string GetDescription() const { return "Get colors from a palette"; } std::string GetUsage() const { return "palette-get-colors --palette [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"palette"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -34,19 +32,18 @@ class PaletteGetColorsCommandHandler : public resources::CommandHandler { class PaletteSetColorCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "palette-set-color"; } - std::string GetDescription() const { - return "Set a color in a palette"; - } + std::string GetDescription() const { return "Set a color in a palette"; } std::string GetUsage() const { - return "palette-set-color --palette --index --color [--format ]"; + return "palette-set-color --palette --index --color " + " [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"palette", "index", "color"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -61,13 +58,13 @@ class PaletteAnalyzeCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "palette-analyze [--palette ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/graphics/sprite_commands.cc b/src/cli/handlers/graphics/sprite_commands.cc index e199257f..8bd4fa91 100644 --- a/src/cli/handlers/graphics/sprite_commands.cc +++ b/src/cli/handlers/graphics/sprite_commands.cc @@ -11,112 +11,110 @@ namespace handlers { absl::Status SpriteListCommandHandler::Execute( Rom* /*rom*/, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { - + auto limit = parser.GetInt("limit").value_or(256); auto type = parser.GetString("type").value_or("all"); - + formatter.BeginObject("Sprite List"); formatter.AddField("total_sprites", 256); formatter.AddField("display_limit", limit); formatter.AddField("filter_type", type); - + formatter.BeginArray("sprites"); - + // Use the sprite names from the sprite system for (int i = 0; i < std::min(limit, 256); i++) { std::string sprite_name = zelda3::kSpriteDefaultNames[i]; std::string sprite_entry = absl::StrFormat("0x%02X: %s", i, sprite_name); formatter.AddArrayItem(sprite_entry); } - + formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } absl::Status SpritePropertiesCommandHandler::Execute( Rom* /*rom*/, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { - + auto id_str = parser.GetString("id").value(); - + int sprite_id; - if (!absl::SimpleHexAtoi(id_str, &sprite_id) && + if (!absl::SimpleHexAtoi(id_str, &sprite_id) && !absl::SimpleAtoi(id_str, &sprite_id)) { return absl::InvalidArgumentError( "Invalid sprite ID format. Must be hex (0xNN) or decimal."); } - + if (sprite_id < 0 || sprite_id > 255) { - return absl::InvalidArgumentError( - "Sprite ID must be between 0 and 255."); + return absl::InvalidArgumentError("Sprite ID must be between 0 and 255."); } - + formatter.BeginObject("Sprite Properties"); formatter.AddHexField("sprite_id", sprite_id, 2); - + // Get sprite name std::string sprite_name = zelda3::kSpriteDefaultNames[sprite_id]; formatter.AddField("name", sprite_name); - + // Add basic sprite properties // Note: Full sprite properties would require loading sprite data from ROM formatter.BeginObject("properties"); formatter.AddField("type", "standard"); - formatter.AddField("is_boss", sprite_id == 0x09 || sprite_id == 0x1A || - sprite_id == 0x1E || sprite_id == 0x1F || - sprite_id == 0xCE || sprite_id == 0xD6); + formatter.AddField("is_boss", sprite_id == 0x09 || sprite_id == 0x1A || + sprite_id == 0x1E || sprite_id == 0x1F || + sprite_id == 0xCE || sprite_id == 0xD6); formatter.AddField("is_overlord", sprite_id <= 0x1A); - formatter.AddField("description", "Sprite properties would be loaded from ROM data"); + formatter.AddField("description", + "Sprite properties would be loaded from ROM data"); formatter.EndObject(); - + formatter.EndObject(); - + return absl::OkStatus(); } absl::Status SpritePaletteCommandHandler::Execute( Rom* /*rom*/, const resources::ArgumentParser& parser, resources::OutputFormatter& formatter) { - + auto id_str = parser.GetString("id").value(); - + int sprite_id; - if (!absl::SimpleHexAtoi(id_str, &sprite_id) && + if (!absl::SimpleHexAtoi(id_str, &sprite_id) && !absl::SimpleAtoi(id_str, &sprite_id)) { return absl::InvalidArgumentError( "Invalid sprite ID format. Must be hex (0xNN) or decimal."); } - + if (sprite_id < 0 || sprite_id > 255) { - return absl::InvalidArgumentError( - "Sprite ID must be between 0 and 255."); + return absl::InvalidArgumentError("Sprite ID must be between 0 and 255."); } - + formatter.BeginObject("Sprite Palette"); formatter.AddHexField("sprite_id", sprite_id, 2); - + std::string sprite_name = zelda3::kSpriteDefaultNames[sprite_id]; formatter.AddField("name", sprite_name); - + // Note: Actual palette data would need to be loaded from ROM formatter.BeginObject("palette_info"); formatter.AddField("palette_group", "Unknown - requires ROM analysis"); formatter.AddField("palette_index", "Unknown - requires ROM analysis"); formatter.AddField("color_count", 16); formatter.EndObject(); - + formatter.BeginArray("colors"); formatter.AddArrayItem("Palette colors would be loaded from ROM data"); formatter.EndArray(); - + formatter.EndObject(); - + return absl::OkStatus(); } } // namespace handlers } // namespace cli } // namespace yaze - diff --git a/src/cli/handlers/graphics/sprite_commands.h b/src/cli/handlers/graphics/sprite_commands.h index 285e925e..7f4b2648 100644 --- a/src/cli/handlers/graphics/sprite_commands.h +++ b/src/cli/handlers/graphics/sprite_commands.h @@ -13,19 +13,18 @@ namespace handlers { class SpriteListCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "sprite-list"; } - std::string GetDescription() const { - return "List available sprites"; - } + std::string GetDescription() const { return "List available sprites"; } std::string GetUsage() const { - return "sprite-list [--type ] [--limit ] [--format ]"; + return "sprite-list [--type ] [--limit ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -40,13 +39,13 @@ class SpritePropertiesCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "sprite-properties --id [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"id"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -61,13 +60,13 @@ class SpritePaletteCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "sprite-palette --id [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"id"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/net/net_commands.cc b/src/cli/handlers/net/net_commands.cc index d56686f0..4bcdd49a 100644 --- a/src/cli/handlers/net/net_commands.cc +++ b/src/cli/handlers/net/net_commands.cc @@ -30,10 +30,10 @@ void EnsureClient() { absl::Status HandleNetConnect(const std::vector& args) { EnsureClient(); - + std::string host = "localhost"; int port = 8765; - + // Parse arguments for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--host" && i + 1 < args.size()) { @@ -44,31 +44,31 @@ absl::Status HandleNetConnect(const std::vector& args) { ++i; } } - + std::cout << "Connecting to " << host << ":" << port << "..." << std::endl; - + auto status = g_network_client->Connect(host, port); - + if (status.ok()) { std::cout << "✓ Connected to yaze-server" << std::endl; } else { std::cerr << "✗ Connection failed: " << status.message() << std::endl; } - + return status; } absl::Status HandleNetJoin(const std::vector& args) { EnsureClient(); - + if (!g_network_client->IsConnected()) { return absl::FailedPreconditionError( "Not connected. Run: z3ed net connect"); } - + std::string session_code; std::string username; - + // Parse arguments for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--code" && i + 1 < args.size()) { @@ -79,61 +79,62 @@ absl::Status HandleNetJoin(const std::vector& args) { ++i; } } - + if (session_code.empty() || username.empty()) { return absl::InvalidArgumentError( "Usage: z3ed net join --code --username "); } - - std::cout << "Joining session " << session_code << " as " << username << "..." + + std::cout << "Joining session " << session_code << " as " << username << "..." << std::endl; - + auto status = g_network_client->JoinSession(session_code, username); - + if (status.ok()) { std::cout << "✓ Joined session successfully" << std::endl; } else { std::cerr << "✗ Failed to join: " << status.message() << std::endl; } - + return status; } absl::Status HandleNetLeave(const std::vector& args) { EnsureClient(); - + if (!g_network_client->IsConnected()) { return absl::FailedPreconditionError("Not connected"); } - + std::cout << "Leaving session..." << std::endl; - + g_network_client->Disconnect(); - + std::cout << "✓ Left session" << std::endl; - + return absl::OkStatus(); } absl::Status HandleNetProposal(const std::vector& args) { EnsureClient(); - + if (!g_network_client->IsConnected()) { return absl::FailedPreconditionError( "Not connected. Run: z3ed net connect"); } - + if (args.empty()) { std::cout << "Usage:\n"; - std::cout << " z3ed net proposal submit --description --data \n"; + std::cout + << " z3ed net proposal submit --description --data \n"; std::cout << " z3ed net proposal status --id \n"; std::cout << " z3ed net proposal wait --id [--timeout ]\n"; return absl::OkStatus(); } - + std::string subcommand = args[0]; std::vector subargs(args.begin() + 1, args.end()); - + if (subcommand == "submit") { return HandleProposalSubmit(subargs); } else if (subcommand == "status") { @@ -150,7 +151,7 @@ absl::Status HandleProposalSubmit(const std::vector& args) { std::string description; std::string data_json; std::string username = "cli_user"; // Default - + for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--description" && i + 1 < args.size()) { description = args[i + 1]; @@ -163,63 +164,60 @@ absl::Status HandleProposalSubmit(const std::vector& args) { ++i; } } - + if (description.empty() || data_json.empty()) { return absl::InvalidArgumentError( "Usage: z3ed net proposal submit --description --data "); } - + std::cout << "Submitting proposal..." << std::endl; std::cout << " Description: " << description << std::endl; - - auto status = g_network_client->SubmitProposal( - description, - data_json, - username - ); - + + auto status = + g_network_client->SubmitProposal(description, data_json, username); + if (status.ok()) { std::cout << "✓ Proposal submitted" << std::endl; std::cout << " Waiting for approval from host..." << std::endl; } else { std::cerr << "✗ Failed to submit: " << status.message() << std::endl; } - + return status; } absl::Status HandleProposalStatus(const std::vector& args) { std::string proposal_id; - + for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--id" && i + 1 < args.size()) { proposal_id = args[i + 1]; ++i; } } - + if (proposal_id.empty()) { return absl::InvalidArgumentError( "Usage: z3ed net proposal status --id "); } - + auto status_result = g_network_client->GetProposalStatus(proposal_id); - + if (status_result.ok()) { std::cout << "Proposal " << proposal_id.substr(0, 8) << "..." << std::endl; std::cout << " Status: " << *status_result << std::endl; } else { - std::cerr << "✗ Failed to get status: " << status_result.status().message() + std::cerr << "✗ Failed to get status: " << status_result.status().message() << std::endl; } - + return status_result.status(); } absl::Status HandleProposalWait(const std::vector& args) { std::string proposal_id; int timeout_seconds = 60; - + for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "--id" && i + 1 < args.size()) { proposal_id = args[i + 1]; @@ -229,20 +227,18 @@ absl::Status HandleProposalWait(const std::vector& args) { ++i; } } - + if (proposal_id.empty()) { return absl::InvalidArgumentError( "Usage: z3ed net proposal wait --id [--timeout ]"); } - - std::cout << "Waiting for approval (timeout: " << timeout_seconds << "s)..." + + std::cout << "Waiting for approval (timeout: " << timeout_seconds << "s)..." << std::endl; - - auto approved_result = g_network_client->WaitForApproval( - proposal_id, - timeout_seconds - ); - + + auto approved_result = + g_network_client->WaitForApproval(proposal_id, timeout_seconds); + if (approved_result.ok()) { if (*approved_result) { std::cout << "✓ Proposal approved!" << std::endl; @@ -252,17 +248,17 @@ absl::Status HandleProposalWait(const std::vector& args) { } else { std::cerr << "✗ Error: " << approved_result.status().message() << std::endl; } - + return approved_result.status(); } absl::Status HandleNetStatus(const std::vector& args) { EnsureClient(); - + std::cout << "Network Status:" << std::endl; - std::cout << " Connected: " + std::cout << " Connected: " << (g_network_client->IsConnected() ? "Yes" : "No") << std::endl; - + return absl::OkStatus(); } diff --git a/src/cli/handlers/rom/mock_rom.cc b/src/cli/handlers/rom/mock_rom.cc index 77fd8319..1ea9836b 100644 --- a/src/cli/handlers/rom/mock_rom.cc +++ b/src/cli/handlers/rom/mock_rom.cc @@ -16,65 +16,63 @@ namespace cli { absl::Status InitializeMockRom(Rom& rom) { // Create a minimal but valid SNES ROM header // Zelda3 is a 1MB ROM (0x100000 bytes) in LoROM mapping - constexpr size_t kMockRomSize = 0x100000; // 1MB + constexpr size_t kMockRomSize = 0x100000; // 1MB std::vector mock_data(kMockRomSize, 0x00); - + // SNES header is at 0x7FC0 for LoROM constexpr size_t kHeaderOffset = 0x7FC0; - + // Set ROM title (21 bytes at 0x7FC0) - const char* title = "YAZE MOCK ROM TEST "; // 21 chars including spaces + const char* title = "YAZE MOCK ROM TEST "; // 21 chars including spaces for (size_t i = 0; i < 21; ++i) { mock_data[kHeaderOffset + i] = title[i]; } - + // ROM makeup byte (0x7FD5): $20 = LoROM, no special chips mock_data[kHeaderOffset + 0x15] = 0x20; - + // ROM type (0x7FD6): $00 = ROM only mock_data[kHeaderOffset + 0x16] = 0x00; - + // ROM size (0x7FD7): $09 = 1MB (2^9 KB = 512 KB = 1MB with header) mock_data[kHeaderOffset + 0x17] = 0x09; - + // SRAM size (0x7FD8): $03 = 8KB (Zelda3 standard) mock_data[kHeaderOffset + 0x18] = 0x03; - + // Country code (0x7FD9): $01 = USA mock_data[kHeaderOffset + 0x19] = 0x01; - + // Developer ID (0x7FDA): $33 = Extended header (Zelda3) mock_data[kHeaderOffset + 0x1A] = 0x33; - + // Version number (0x7FDB): $00 = 1.0 mock_data[kHeaderOffset + 0x1B] = 0x00; - + // Checksum complement (0x7FDC-0x7FDD): We'll leave as 0x0000 for mock // Checksum (0x7FDE-0x7FDF): We'll leave as 0x0000 for mock - + // Load the mock data into the ROM auto load_status = rom.LoadFromData(mock_data); if (!load_status.ok()) { - return absl::InternalError( - absl::StrFormat("Failed to initialize mock ROM: %s", - load_status.message())); + return absl::InternalError(absl::StrFormat( + "Failed to initialize mock ROM: %s", load_status.message())); } - + // Initialize embedded labels so queries work without actual ROM data project::YazeProject project; auto labels_status = project.InitializeEmbeddedLabels(); if (!labels_status.ok()) { - return absl::InternalError( - absl::StrFormat("Failed to initialize embedded labels: %s", - labels_status.message())); + return absl::InternalError(absl::StrFormat( + "Failed to initialize embedded labels: %s", labels_status.message())); } - + // Attach labels to ROM's resource label manager if (rom.resource_label()) { rom.resource_label()->labels_ = project.resource_labels; rom.resource_label()->labels_loaded_ = true; } - + return absl::OkStatus(); } @@ -82,6 +80,5 @@ bool ShouldUseMockRom() { return absl::GetFlag(FLAGS_mock_rom); } -} // namespace cli -} // namespace yaze - +} // namespace cli +} // namespace yaze diff --git a/src/cli/handlers/rom/mock_rom.h b/src/cli/handlers/rom/mock_rom.h index c9d2d7d8..7c29756c 100644 --- a/src/cli/handlers/rom/mock_rom.h +++ b/src/cli/handlers/rom/mock_rom.h @@ -28,8 +28,7 @@ absl::Status InitializeMockRom(Rom& rom); */ bool ShouldUseMockRom(); -} // namespace cli -} // namespace yaze - -#endif // YAZE_CLI_HANDLERS_MOCK_ROM_H +} // namespace cli +} // namespace yaze +#endif // YAZE_CLI_HANDLERS_MOCK_ROM_H diff --git a/src/cli/handlers/rom/project_commands.cc b/src/cli/handlers/rom/project_commands.cc index e9ae62ec..0a2dcde8 100644 --- a/src/cli/handlers/rom/project_commands.cc +++ b/src/cli/handlers/rom/project_commands.cc @@ -1,27 +1,28 @@ #include "cli/handlers/rom/project_commands.h" -#include "core/project.h" -#include "util/file_util.h" -#include "util/bps.h" -#include "util/macro.h" #include #include +#include "core/project.h" +#include "util/bps.h" +#include "util/file_util.h" +#include "util/macro.h" namespace yaze { namespace cli { namespace handlers { -absl::Status ProjectInitCommandHandler::Execute(Rom* rom, - const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status ProjectInitCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto project_opt = parser.GetString("project_name"); - + if (!project_opt.has_value()) { - return absl::InvalidArgumentError("Missing required argument: project_name"); + return absl::InvalidArgumentError( + "Missing required argument: project_name"); } - + std::string project_name = project_opt.value(); - + project::YazeProject project; auto status = project.Create(project_name, "."); if (!status.ok()) { @@ -29,15 +30,16 @@ absl::Status ProjectInitCommandHandler::Execute(Rom* rom, } formatter.AddField("status", "success"); - formatter.AddField("message", "Successfully initialized project: " + project_name); + formatter.AddField("message", + "Successfully initialized project: " + project_name); formatter.AddField("project_name", project_name); - + return absl::OkStatus(); } -absl::Status ProjectBuildCommandHandler::Execute(Rom* rom, - const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status ProjectBuildCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { project::YazeProject project; auto status = project.Open("."); if (!status.ok()) { @@ -53,7 +55,7 @@ absl::Status ProjectBuildCommandHandler::Execute(Rom* rom, // Apply BPS patches - cross-platform with std::filesystem namespace fs = std::filesystem; std::vector bps_files; - + try { for (const auto& entry : fs::directory_iterator(project.patches_folder)) { if (entry.path().extension() == ".bps") { @@ -63,7 +65,7 @@ absl::Status ProjectBuildCommandHandler::Execute(Rom* rom, } catch (const fs::filesystem_error& e) { // Patches folder doesn't exist or not accessible } - + for (const auto& patch_file : bps_files) { std::vector patch_data; auto patch_contents = util::LoadFile(patch_file); @@ -85,7 +87,7 @@ absl::Status ProjectBuildCommandHandler::Execute(Rom* rom, } catch (const fs::filesystem_error& e) { // No asm files } - + // TODO: Implement ASM patching functionality // for (const auto& asm_file : asm_files) { // // Apply ASM patches here @@ -101,7 +103,7 @@ absl::Status ProjectBuildCommandHandler::Execute(Rom* rom, formatter.AddField("message", "Successfully built project: " + project.name); formatter.AddField("project_name", project.name); formatter.AddField("output_file", output_file); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/rom/project_commands.h b/src/cli/handlers/rom/project_commands.h index 2bd88040..43221717 100644 --- a/src/cli/handlers/rom/project_commands.h +++ b/src/cli/handlers/rom/project_commands.h @@ -21,7 +21,7 @@ class ProjectInitCommandHandler : public resources::CommandHandler { } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -38,7 +38,7 @@ class ProjectBuildCommandHandler : public resources::CommandHandler { } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/rom/rom_commands.cc b/src/cli/handlers/rom/rom_commands.cc index bd394a7d..5e1db6df 100644 --- a/src/cli/handlers/rom/rom_commands.cc +++ b/src/cli/handlers/rom/rom_commands.cc @@ -8,9 +8,9 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status RomInfoCommandHandler::Execute(Rom* rom, - const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status RomInfoCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { if (!rom || !rom->is_loaded()) { return absl::FailedPreconditionError("ROM must be loaded"); } @@ -18,13 +18,13 @@ absl::Status RomInfoCommandHandler::Execute(Rom* rom, formatter.AddField("title", rom->title()); formatter.AddField("size", absl::StrFormat("0x%X", rom->size())); formatter.AddField("size_bytes", static_cast(rom->size())); - + return absl::OkStatus(); } -absl::Status RomValidateCommandHandler::Execute(Rom* rom, - const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status RomValidateCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { if (!rom || !rom->is_loaded()) { return absl::FailedPreconditionError("ROM must be loaded"); } @@ -44,34 +44,36 @@ absl::Status RomValidateCommandHandler::Execute(Rom* rom, if (rom->title() == "THE LEGEND OF ZELDA") { validation_results.push_back("header: PASSED"); } else { - validation_results.push_back("header: FAILED (Invalid title: " + rom->title() + ")"); + validation_results.push_back( + "header: FAILED (Invalid title: " + rom->title() + ")"); all_ok = false; } formatter.AddField("validation_passed", all_ok); std::string results_str; for (const auto& result : validation_results) { - if (!results_str.empty()) results_str += "; "; + if (!results_str.empty()) + results_str += "; "; results_str += result; } formatter.AddField("results", results_str); - + return absl::OkStatus(); } -absl::Status RomDiffCommandHandler::Execute(Rom* rom, - const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status RomDiffCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto rom_a_opt = parser.GetString("rom_a"); auto rom_b_opt = parser.GetString("rom_b"); - + if (!rom_a_opt.has_value()) { return absl::InvalidArgumentError("Missing required argument: rom_a"); } if (!rom_b_opt.has_value()) { return absl::InvalidArgumentError("Missing required argument: rom_b"); } - + std::string rom_a_path = rom_a_opt.value(); std::string rom_b_path = rom_b_opt.value(); @@ -96,13 +98,14 @@ absl::Status RomDiffCommandHandler::Execute(Rom* rom, int differences = 0; std::vector diff_details; - + for (size_t i = 0; i < rom_a.size(); ++i) { if (rom_a.vector()[i] != rom_b.vector()[i]) { differences++; if (differences <= 10) { // Limit output to first 10 differences - diff_details.push_back(absl::StrFormat("0x%08X: 0x%02X vs 0x%02X", - i, rom_a.vector()[i], rom_b.vector()[i])); + diff_details.push_back(absl::StrFormat("0x%08X: 0x%02X vs 0x%02X", i, + rom_a.vector()[i], + rom_b.vector()[i])); } } } @@ -112,49 +115,53 @@ absl::Status RomDiffCommandHandler::Execute(Rom* rom, if (!diff_details.empty()) { std::string diff_str; for (const auto& diff : diff_details) { - if (!diff_str.empty()) diff_str += "; "; + if (!diff_str.empty()) + diff_str += "; "; diff_str += diff; } formatter.AddField("differences", diff_str); } - + return absl::OkStatus(); } -absl::Status RomGenerateGoldenCommandHandler::Execute(Rom* rom, - const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status RomGenerateGoldenCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto rom_opt = parser.GetString("rom_file"); auto golden_opt = parser.GetString("golden_file"); - + if (!rom_opt.has_value()) { return absl::InvalidArgumentError("Missing required argument: rom_file"); } if (!golden_opt.has_value()) { return absl::InvalidArgumentError("Missing required argument: golden_file"); } - + std::string rom_path = rom_opt.value(); std::string golden_path = golden_opt.value(); Rom source_rom; - auto status = source_rom.LoadFromFile(rom_path, RomLoadOptions::CliDefaults()); + auto status = + source_rom.LoadFromFile(rom_path, RomLoadOptions::CliDefaults()); if (!status.ok()) { return status; } std::ofstream file(golden_path, std::ios::binary); if (!file.is_open()) { - return absl::NotFoundError("Could not open file for writing: " + golden_path); + return absl::NotFoundError("Could not open file for writing: " + + golden_path); } - file.write(reinterpret_cast(source_rom.vector().data()), source_rom.size()); + file.write(reinterpret_cast(source_rom.vector().data()), + source_rom.size()); formatter.AddField("status", "success"); formatter.AddField("golden_file", golden_path); formatter.AddField("source_file", rom_path); formatter.AddField("size", static_cast(source_rom.size())); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/rom/rom_commands.h b/src/cli/handlers/rom/rom_commands.h index 4a41aed1..e5edfb15 100644 --- a/src/cli/handlers/rom/rom_commands.h +++ b/src/cli/handlers/rom/rom_commands.h @@ -21,7 +21,7 @@ class RomInfoCommandHandler : public resources::CommandHandler { } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -38,7 +38,7 @@ class RomValidateCommandHandler : public resources::CommandHandler { } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -48,14 +48,16 @@ class RomDiffCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "rom-diff"; } std::string GetDescription() const { return "Compare two ROM files"; } - std::string GetUsage() const { return "rom-diff --rom_a --rom_b "; } + std::string GetUsage() const { + return "rom-diff --rom_a --rom_b "; + } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"rom_a", "rom_b"}); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -64,15 +66,19 @@ class RomDiffCommandHandler : public resources::CommandHandler { class RomGenerateGoldenCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "rom-generate-golden"; } - std::string GetDescription() const { return "Generate golden ROM file for testing"; } - std::string GetUsage() const { return "rom-generate-golden --rom_file --golden_file "; } + std::string GetDescription() const { + return "Generate golden ROM file for testing"; + } + std::string GetUsage() const { + return "rom-generate-golden --rom_file --golden_file "; + } absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"rom_file", "golden_file"}); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/tools/emulator_commands.cc b/src/cli/handlers/tools/emulator_commands.cc index df4ca0a3..5444b027 100644 --- a/src/cli/handlers/tools/emulator_commands.cc +++ b/src/cli/handlers/tools/emulator_commands.cc @@ -1,12 +1,12 @@ #include "cli/handlers/tools/emulator_commands.h" #include -#include "protos/emulator_service.grpc.pb.h" +#include "absl/status/statusor.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" -#include "absl/status/statusor.h" -#include "absl/strings/escaping.h" +#include "protos/emulator_service.grpc.pb.h" namespace yaze { namespace cli { @@ -16,286 +16,332 @@ namespace { // A simple client for the EmulatorService class EmulatorClient { -public: - EmulatorClient() { - auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()); - stub_ = agent::EmulatorService::NewStub(channel); + public: + EmulatorClient() { + auto channel = grpc::CreateChannel("localhost:50051", + grpc::InsecureChannelCredentials()); + stub_ = agent::EmulatorService::NewStub(channel); + } + + template + absl::StatusOr CallRpc( + grpc::Status (agent::EmulatorService::Stub::*rpc_method)( + grpc::ClientContext*, const TRequest&, TResponse*), + const TRequest& request) { + + TResponse response; + grpc::ClientContext context; + + auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(5); + context.set_deadline(deadline); + + grpc::Status status = + (stub_.get()->*rpc_method)(&context, request, &response); + + if (!status.ok()) { + return absl::UnavailableError(absl::StrFormat( + "RPC failed: (%d) %s", status.error_code(), status.error_message())); } + return response; + } - template - absl::StatusOr CallRpc( - grpc::Status (agent::EmulatorService::Stub::*rpc_method)(grpc::ClientContext*, const TRequest&, TResponse*), - const TRequest& request) { - - TResponse response; - grpc::ClientContext context; - - auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(5); - context.set_deadline(deadline); - - grpc::Status status = (stub_.get()->*rpc_method)(&context, request, &response); - - if (!status.ok()) { - return absl::UnavailableError(absl::StrFormat( - "RPC failed: (%d) %s", status.error_code(), status.error_message())); - } - return response; - } - -private: - std::unique_ptr stub_; + private: + std::unique_ptr stub_; }; // Helper to parse button from string absl::StatusOr StringToButton(absl::string_view s) { - if (s == "A") return agent::Button::A; - if (s == "B") return agent::Button::B; - if (s == "X") return agent::Button::X; - if (s == "Y") return agent::Button::Y; - if (s == "L") return agent::Button::L; - if (s == "R") return agent::Button::R; - if (s == "SELECT") return agent::Button::SELECT; - if (s == "START") return agent::Button::START; - if (s == "UP") return agent::Button::UP; - if (s == "DOWN") return agent::Button::DOWN; - if (s == "LEFT") return agent::Button::LEFT; - if (s == "RIGHT") return agent::Button::RIGHT; - return absl::InvalidArgumentError(absl::StrCat("Unknown button: ", s)); + if (s == "A") + return agent::Button::A; + if (s == "B") + return agent::Button::B; + if (s == "X") + return agent::Button::X; + if (s == "Y") + return agent::Button::Y; + if (s == "L") + return agent::Button::L; + if (s == "R") + return agent::Button::R; + if (s == "SELECT") + return agent::Button::SELECT; + if (s == "START") + return agent::Button::START; + if (s == "UP") + return agent::Button::UP; + if (s == "DOWN") + return agent::Button::DOWN; + if (s == "LEFT") + return agent::Button::LEFT; + if (s == "RIGHT") + return agent::Button::RIGHT; + return absl::InvalidArgumentError(absl::StrCat("Unknown button: ", s)); } -} // namespace +} // namespace // --- Command Implementations --- -absl::Status EmulatorResetCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - EmulatorClient client; - agent::Empty request; - auto response_or = client.CallRpc(&agent::EmulatorService::Stub::Reset, request); - if (!response_or.ok()) { - return response_or.status(); - } - auto response = response_or.value(); +absl::Status EmulatorResetCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::Empty request; + auto response_or = + client.CallRpc(&agent::EmulatorService::Stub::Reset, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); - formatter.BeginObject("EmulatorReset"); - formatter.AddField("success", response.success()); - formatter.AddField("message", response.message()); - formatter.EndObject(); - return absl::OkStatus(); + formatter.BeginObject("EmulatorReset"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); } -absl::Status EmulatorGetStateCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - EmulatorClient client; - agent::GameStateRequest request; - request.set_include_screenshot(parser.HasFlag("screenshot")); - - auto response_or = client.CallRpc(&agent::EmulatorService::Stub::GetGameState, request); - if (!response_or.ok()) { - return response_or.status(); - } - auto response = response_or.value(); +absl::Status EmulatorGetStateCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::GameStateRequest request; + request.set_include_screenshot(parser.HasFlag("screenshot")); - formatter.BeginObject("EmulatorState"); - formatter.AddField("game_mode", static_cast(response.game_mode())); - formatter.AddField("link_state", static_cast(response.link_state())); - formatter.AddField("link_pos_x", static_cast(response.link_pos_x())); - formatter.AddField("link_pos_y", static_cast(response.link_pos_y())); - formatter.AddField("link_health", static_cast(response.link_health())); - if (!response.screenshot_png().empty()) { - formatter.AddField("screenshot_size", static_cast(response.screenshot_png().size())); - } - formatter.EndObject(); - return absl::OkStatus(); + auto response_or = + client.CallRpc(&agent::EmulatorService::Stub::GetGameState, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("EmulatorState"); + formatter.AddField("game_mode", static_cast(response.game_mode())); + formatter.AddField("link_state", + static_cast(response.link_state())); + formatter.AddField("link_pos_x", + static_cast(response.link_pos_x())); + formatter.AddField("link_pos_y", + static_cast(response.link_pos_y())); + formatter.AddField("link_health", + static_cast(response.link_health())); + if (!response.screenshot_png().empty()) { + formatter.AddField("screenshot_size", + static_cast(response.screenshot_png().size())); + } + formatter.EndObject(); + return absl::OkStatus(); } -absl::Status EmulatorReadMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - EmulatorClient client; - agent::MemoryRequest request; - - uint32_t address; - if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) { - return absl::InvalidArgumentError("Invalid address format."); - } - request.set_address(address); - request.set_size(parser.GetInt("length").value_or(16)); +absl::Status EmulatorReadMemoryCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::MemoryRequest request; - auto response_or = client.CallRpc(&agent::EmulatorService::Stub::ReadMemory, request); - if (!response_or.ok()) { - return response_or.status(); - } - auto response = response_or.value(); + uint32_t address; + if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) { + return absl::InvalidArgumentError("Invalid address format."); + } + request.set_address(address); + request.set_size(parser.GetInt("length").value_or(16)); - formatter.BeginObject("MemoryRead"); - formatter.AddHexField("address", response.address()); - formatter.AddField("data_hex", absl::BytesToHexString(response.data())); - formatter.EndObject(); - return absl::OkStatus(); + auto response_or = + client.CallRpc(&agent::EmulatorService::Stub::ReadMemory, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("MemoryRead"); + formatter.AddHexField("address", response.address()); + formatter.AddField("data_hex", absl::BytesToHexString(response.data())); + formatter.EndObject(); + return absl::OkStatus(); } -absl::Status EmulatorWriteMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - EmulatorClient client; - agent::MemoryWriteRequest request; +absl::Status EmulatorWriteMemoryCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::MemoryWriteRequest request; - uint32_t address; - if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) { - return absl::InvalidArgumentError("Invalid address format."); - } - request.set_address(address); - - std::string data_hex = parser.GetString("data").value(); - request.set_data(absl::HexStringToBytes(data_hex)); + uint32_t address; + if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) { + return absl::InvalidArgumentError("Invalid address format."); + } + request.set_address(address); - auto response_or = client.CallRpc(&agent::EmulatorService::Stub::WriteMemory, request); - if (!response_or.ok()) { - return response_or.status(); - } - auto response = response_or.value(); + std::string data_hex = parser.GetString("data").value(); + request.set_data(absl::HexStringToBytes(data_hex)); - formatter.BeginObject("MemoryWrite"); - formatter.AddField("success", response.success()); - formatter.AddField("message", response.message()); - formatter.EndObject(); - return absl::OkStatus(); + auto response_or = + client.CallRpc(&agent::EmulatorService::Stub::WriteMemory, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); + + formatter.BeginObject("MemoryWrite"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); } +absl::Status EmulatorPressButtonsCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::ButtonRequest request; + std::vector buttons = + absl::StrSplit(parser.GetString("buttons").value(), ','); + for (const auto& btn_str : buttons) { + auto button_or = StringToButton(btn_str); + if (!button_or.ok()) + return button_or.status(); + request.add_buttons(button_or.value()); + } -absl::Status EmulatorPressButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - EmulatorClient client; - agent::ButtonRequest request; - std::vector buttons = absl::StrSplit(parser.GetString("buttons").value(), ','); - for (const auto& btn_str : buttons) { - auto button_or = StringToButton(btn_str); - if (!button_or.ok()) return button_or.status(); - request.add_buttons(button_or.value()); - } + auto response_or = + client.CallRpc(&agent::EmulatorService::Stub::PressButtons, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); - auto response_or = client.CallRpc(&agent::EmulatorService::Stub::PressButtons, request); - if (!response_or.ok()) { - return response_or.status(); - } - auto response = response_or.value(); - - formatter.BeginObject("PressButtons"); - formatter.AddField("success", response.success()); - formatter.AddField("message", response.message()); - formatter.EndObject(); - return absl::OkStatus(); + formatter.BeginObject("PressButtons"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); } -absl::Status EmulatorReleaseButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - EmulatorClient client; - agent::ButtonRequest request; - std::vector buttons = absl::StrSplit(parser.GetString("buttons").value(), ','); - for (const auto& btn_str : buttons) { - auto button_or = StringToButton(btn_str); - if (!button_or.ok()) return button_or.status(); - request.add_buttons(button_or.value()); - } +absl::Status EmulatorReleaseButtonsCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::ButtonRequest request; + std::vector buttons = + absl::StrSplit(parser.GetString("buttons").value(), ','); + for (const auto& btn_str : buttons) { + auto button_or = StringToButton(btn_str); + if (!button_or.ok()) + return button_or.status(); + request.add_buttons(button_or.value()); + } - auto response_or = client.CallRpc(&agent::EmulatorService::Stub::ReleaseButtons, request); - if (!response_or.ok()) { - return response_or.status(); - } - auto response = response_or.value(); + auto response_or = + client.CallRpc(&agent::EmulatorService::Stub::ReleaseButtons, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); - formatter.BeginObject("ReleaseButtons"); - formatter.AddField("success", response.success()); - formatter.AddField("message", response.message()); - formatter.EndObject(); - return absl::OkStatus(); + formatter.BeginObject("ReleaseButtons"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); } -absl::Status EmulatorHoldButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { - EmulatorClient client; - agent::ButtonHoldRequest request; - std::vector buttons = absl::StrSplit(parser.GetString("buttons").value(), ','); - for (const auto& btn_str : buttons) { - auto button_or = StringToButton(btn_str); - if (!button_or.ok()) return button_or.status(); - request.add_buttons(button_or.value()); - } - request.set_duration_ms(parser.GetInt("duration").value()); +absl::Status EmulatorHoldButtonsCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { + EmulatorClient client; + agent::ButtonHoldRequest request; + std::vector buttons = + absl::StrSplit(parser.GetString("buttons").value(), ','); + for (const auto& btn_str : buttons) { + auto button_or = StringToButton(btn_str); + if (!button_or.ok()) + return button_or.status(); + request.add_buttons(button_or.value()); + } + request.set_duration_ms(parser.GetInt("duration").value()); - auto response_or = client.CallRpc(&agent::EmulatorService::Stub::HoldButtons, request); - if (!response_or.ok()) { - return response_or.status(); - } - auto response = response_or.value(); + auto response_or = + client.CallRpc(&agent::EmulatorService::Stub::HoldButtons, request); + if (!response_or.ok()) { + return response_or.status(); + } + auto response = response_or.value(); - formatter.BeginObject("HoldButtons"); - formatter.AddField("success", response.success()); - formatter.AddField("message", response.message()); - formatter.EndObject(); - return absl::OkStatus(); + formatter.BeginObject("HoldButtons"); + formatter.AddField("success", response.success()); + formatter.AddField("message", response.message()); + formatter.EndObject(); + return absl::OkStatus(); } - // --- Placeholder Implementations for commands not yet migrated to gRPC --- -absl::Status EmulatorStepCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorStepCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Step"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); return absl::OkStatus(); } -absl::Status EmulatorRunCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorRunCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Run"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); return absl::OkStatus(); } -absl::Status EmulatorPauseCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorPauseCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Pause"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); return absl::OkStatus(); } -absl::Status EmulatorSetBreakpointCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorSetBreakpointCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Breakpoint Set"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); return absl::OkStatus(); } -absl::Status EmulatorClearBreakpointCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorClearBreakpointCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Breakpoint Cleared"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); return absl::OkStatus(); } -absl::Status EmulatorListBreakpointsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorListBreakpointsCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Breakpoints"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); return absl::OkStatus(); } -absl::Status EmulatorGetRegistersCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorGetRegistersCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Registers"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); return absl::OkStatus(); } -absl::Status EmulatorGetMetricsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status EmulatorGetMetricsCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { formatter.BeginObject("Emulator Metrics"); formatter.AddField("status", "not_implemented"); formatter.EndObject(); diff --git a/src/cli/handlers/tools/emulator_commands.h b/src/cli/handlers/tools/emulator_commands.h index 72e0b7b5..ad44f511 100644 --- a/src/cli/handlers/tools/emulator_commands.h +++ b/src/cli/handlers/tools/emulator_commands.h @@ -16,49 +16,45 @@ class EmulatorStepCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "emulator-step [--count ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorRunCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-run"; } - std::string GetDescription() const { - return "Run emulator execution"; - } + std::string GetDescription() const { return "Run emulator execution"; } std::string GetUsage() const { return "emulator-run [--until ] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorPauseCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-pause"; } - std::string GetDescription() const { - return "Pause emulator execution"; - } + std::string GetDescription() const { return "Pause emulator execution"; } std::string GetUsage() const { return "emulator-pause [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorResetCommandHandler : public resources::CommandHandler { @@ -70,31 +66,29 @@ class EmulatorResetCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "emulator-reset [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorGetStateCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-get-state"; } - std::string GetDescription() const { - return "Get current emulator state"; - } + std::string GetDescription() const { return "Get current emulator state"; } std::string GetUsage() const { return "emulator-get-state [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorSetBreakpointCommandHandler : public resources::CommandHandler { @@ -104,15 +98,16 @@ class EmulatorSetBreakpointCommandHandler : public resources::CommandHandler { return "Set a breakpoint at specified address"; } std::string GetUsage() const { - return "emulator-set-breakpoint --address
[--condition ] [--format ]"; + return "emulator-set-breakpoint --address
[--condition " + "] [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"address"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorClearBreakpointCommandHandler : public resources::CommandHandler { @@ -122,87 +117,82 @@ class EmulatorClearBreakpointCommandHandler : public resources::CommandHandler { return "Clear a breakpoint at specified address"; } std::string GetUsage() const { - return "emulator-clear-breakpoint --address
[--format ]"; + return "emulator-clear-breakpoint --address
[--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"address"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorListBreakpointsCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-list-breakpoints"; } - std::string GetDescription() const { - return "List all active breakpoints"; - } + std::string GetDescription() const { return "List all active breakpoints"; } std::string GetUsage() const { return "emulator-list-breakpoints [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorReadMemoryCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-read-memory"; } - std::string GetDescription() const { - return "Read memory from emulator"; - } + std::string GetDescription() const { return "Read memory from emulator"; } std::string GetUsage() const { - return "emulator-read-memory --address
[--length ] [--format ]"; + return "emulator-read-memory --address
[--length ] " + "[--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"address"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorWriteMemoryCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-write-memory"; } - std::string GetDescription() const { - return "Write memory to emulator"; - } + std::string GetDescription() const { return "Write memory to emulator"; } std::string GetUsage() const { - return "emulator-write-memory --address
--data [--format ]"; + return "emulator-write-memory --address
--data [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"address", "data"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorGetRegistersCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "emulator-get-registers"; } - std::string GetDescription() const { - return "Get emulator register values"; - } + std::string GetDescription() const { return "Get emulator register values"; } std::string GetUsage() const { return "emulator-get-registers [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorGetMetricsCommandHandler : public resources::CommandHandler { @@ -214,13 +204,13 @@ class EmulatorGetMetricsCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "emulator-get-metrics [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; class EmulatorPressButtonsCommandHandler : public resources::CommandHandler { @@ -232,8 +222,7 @@ class EmulatorPressButtonsCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "emulator-press-buttons --buttons ,,..."; } - absl::Status ValidateArgs( - const resources::ArgumentParser& parser) override { + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"buttons"}); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, @@ -249,8 +238,7 @@ class EmulatorReleaseButtonsCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "emulator-release-buttons --buttons ,,..."; } - absl::Status ValidateArgs( - const resources::ArgumentParser& parser) override { + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"buttons"}); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, @@ -267,8 +255,7 @@ class EmulatorHoldButtonsCommandHandler : public resources::CommandHandler { return "emulator-hold-buttons --buttons ,,... --duration " ""; } - absl::Status ValidateArgs( - const resources::ArgumentParser& parser) override { + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"buttons", "duration"}); } absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, diff --git a/src/cli/handlers/tools/gui_commands.cc b/src/cli/handlers/tools/gui_commands.cc index 16181f6e..4e08e15c 100644 --- a/src/cli/handlers/tools/gui_commands.cc +++ b/src/cli/handlers/tools/gui_commands.cc @@ -7,20 +7,19 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status GuiPlaceTileCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status GuiPlaceTileCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto tile_id_str = parser.GetString("tile").value(); auto x_str = parser.GetString("x").value(); auto y_str = parser.GetString("y").value(); - + int tile_id, x, y; if (!absl::SimpleHexAtoi(tile_id_str, &tile_id) || - !absl::SimpleAtoi(x_str, &x) || - !absl::SimpleAtoi(y_str, &y)) { - return absl::InvalidArgumentError( - "Invalid tile ID or coordinate format."); + !absl::SimpleAtoi(x_str, &x) || !absl::SimpleAtoi(y_str, &y)) { + return absl::InvalidArgumentError("Invalid tile ID or coordinate format."); } - + formatter.BeginObject("GUI Tile Placement"); formatter.AddField("tile_id", absl::StrFormat("0x%03X", tile_id)); formatter.AddField("x", x); @@ -28,37 +27,39 @@ absl::Status GuiPlaceTileCommandHandler::Execute(Rom* rom, const resources::Argu formatter.AddField("status", "GUI automation requires YAZE_WITH_GRPC=ON"); formatter.AddField("note", "Connect to running YAZE instance to execute"); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status GuiClickCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status GuiClickCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto target = parser.GetString("target").value(); auto click_type = parser.GetString("click-type").value_or("left"); - + formatter.BeginObject("GUI Click Action"); formatter.AddField("target", target); formatter.AddField("click_type", click_type); formatter.AddField("status", "GUI automation requires YAZE_WITH_GRPC=ON"); formatter.AddField("note", "Connect to running YAZE instance to execute"); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status GuiDiscoverToolCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status GuiDiscoverToolCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto window = parser.GetString("window").value_or("Overworld"); auto type = parser.GetString("type").value_or("all"); - + formatter.BeginObject("Widget Discovery"); formatter.AddField("window", window); formatter.AddField("type_filter", type); formatter.AddField("total_widgets", 4); formatter.AddField("status", "GUI automation requires YAZE_WITH_GRPC=ON"); formatter.AddField("note", "Connect to running YAZE instance for live data"); - + formatter.BeginArray("example_widgets"); formatter.AddArrayItem("ModeButton:Pan (1) - button"); formatter.AddArrayItem("ModeButton:Draw (2) - button"); @@ -66,15 +67,16 @@ absl::Status GuiDiscoverToolCommandHandler::Execute(Rom* rom, const resources::A formatter.AddArrayItem("ToolbarAction:Open Tile16 Editor - button"); formatter.EndArray(); formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status GuiScreenshotCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status GuiScreenshotCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto region = parser.GetString("region").value_or("full"); auto image_format = parser.GetString("format").value_or("PNG"); - + formatter.BeginObject("Screenshot Capture"); formatter.AddField("region", region); formatter.AddField("image_format", image_format); @@ -82,7 +84,7 @@ absl::Status GuiScreenshotCommandHandler::Execute(Rom* rom, const resources::Arg formatter.AddField("status", "GUI automation requires YAZE_WITH_GRPC=ON"); formatter.AddField("note", "Connect to running YAZE instance to execute"); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/tools/gui_commands.h b/src/cli/handlers/tools/gui_commands.h index 55a73dbb..b04ff3b8 100644 --- a/src/cli/handlers/tools/gui_commands.h +++ b/src/cli/handlers/tools/gui_commands.h @@ -17,15 +17,16 @@ class GuiPlaceTileCommandHandler : public resources::CommandHandler { return "Place a tile at specific coordinates using GUI automation"; } std::string GetUsage() const { - return "gui-place-tile --tile --x --y [--format ]"; + return "gui-place-tile --tile --x --y [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"tile", "x", "y"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -38,15 +39,16 @@ class GuiClickCommandHandler : public resources::CommandHandler { return "Click on a GUI element using automation"; } std::string GetUsage() const { - return "gui-click --target [--click-type ] [--format ]"; + return "gui-click --target [--click-type ] " + "[--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"target"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -59,15 +61,16 @@ class GuiDiscoverToolCommandHandler : public resources::CommandHandler { return "Discover available GUI tools and widgets"; } std::string GetUsage() const { - return "gui-discover-tool [--window ] [--type ] [--format ]"; + return "gui-discover-tool [--window ] [--type ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -76,19 +79,18 @@ class GuiDiscoverToolCommandHandler : public resources::CommandHandler { class GuiScreenshotCommandHandler : public resources::CommandHandler { public: std::string GetName() const { return "gui-screenshot"; } - std::string GetDescription() const { - return "Take a screenshot of the GUI"; - } + std::string GetDescription() const { return "Take a screenshot of the GUI"; } std::string GetUsage() const { - return "gui-screenshot [--region ] [--format ] [--format ]"; + return "gui-screenshot [--region ] [--format ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return absl::OkStatus(); // No required args } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/handlers/tools/resource_commands.cc b/src/cli/handlers/tools/resource_commands.cc index 482f975c..7ec961d9 100644 --- a/src/cli/handlers/tools/resource_commands.cc +++ b/src/cli/handlers/tools/resource_commands.cc @@ -11,61 +11,65 @@ namespace yaze { namespace cli { namespace handlers { -absl::Status ResourceListCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status ResourceListCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto type = parser.GetString("type").value(); - + ResourceContextBuilder builder(rom); ASSIGN_OR_RETURN(auto labels, builder.GetLabels(type)); - - formatter.BeginObject(absl::StrFormat("%s Labels", absl::AsciiStrToUpper(type))); + + formatter.BeginObject( + absl::StrFormat("%s Labels", absl::AsciiStrToUpper(type))); for (const auto& [key, value] : labels) { formatter.AddField(key, value); } formatter.EndObject(); - + return absl::OkStatus(); } -absl::Status ResourceSearchCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) { +absl::Status ResourceSearchCommandHandler::Execute( + Rom* rom, const resources::ArgumentParser& parser, + resources::OutputFormatter& formatter) { auto query = parser.GetString("query").value(); auto type = parser.GetString("type").value_or("all"); - + ResourceContextBuilder builder(rom); - - std::vector categories = {"overworld", "dungeon", "entrance", - "room", "sprite", "palette", "item"}; + + std::vector categories = { + "overworld", "dungeon", "entrance", "room", "sprite", "palette", "item"}; if (type != "all") { categories = {type}; } - + formatter.BeginObject("Resource Search Results"); formatter.AddField("query", query); formatter.AddField("search_type", type); - + int total_matches = 0; formatter.BeginArray("matches"); - + for (const auto& category : categories) { auto labels_or = builder.GetLabels(category); - if (!labels_or.ok()) continue; - + if (!labels_or.ok()) + continue; + auto labels = labels_or.value(); for (const auto& [key, value] : labels) { - if (absl::StrContains(absl::AsciiStrToLower(value), - absl::AsciiStrToLower(query))) { - formatter.AddArrayItem(absl::StrFormat("%s:%s = %s", - category, key, value)); + if (absl::StrContains(absl::AsciiStrToLower(value), + absl::AsciiStrToLower(query))) { + formatter.AddArrayItem( + absl::StrFormat("%s:%s = %s", category, key, value)); total_matches++; } } } - + formatter.EndArray(); formatter.AddField("total_matches", total_matches); formatter.EndObject(); - + return absl::OkStatus(); } diff --git a/src/cli/handlers/tools/resource_commands.h b/src/cli/handlers/tools/resource_commands.h index af5a3493..27d0ea14 100644 --- a/src/cli/handlers/tools/resource_commands.h +++ b/src/cli/handlers/tools/resource_commands.h @@ -19,13 +19,13 @@ class ResourceListCommandHandler : public resources::CommandHandler { std::string GetUsage() const { return "resource-list --type [--format ]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"type"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; /** @@ -38,15 +38,16 @@ class ResourceSearchCommandHandler : public resources::CommandHandler { return "Search resource labels across all categories"; } std::string GetUsage() const { - return "resource-search --query [--type ] [--format ]"; + return "resource-search --query [--type ] [--format " + "]"; } - + absl::Status ValidateArgs(const resources::ArgumentParser& parser) override { return parser.RequireArgs({"query"}); } - + absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser, - resources::OutputFormatter& formatter) override; + resources::OutputFormatter& formatter) override; }; } // namespace handlers diff --git a/src/cli/service/agent/advanced_routing.cc b/src/cli/service/agent/advanced_routing.cc index 18ec6a3d..6392b647 100644 --- a/src/cli/service/agent/advanced_routing.cc +++ b/src/cli/service/agent/advanced_routing.cc @@ -1,8 +1,8 @@ #include "cli/service/agent/advanced_routing.h" #include -#include #include +#include #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" @@ -11,21 +11,19 @@ namespace cli { namespace agent { AdvancedRouter::RoutedResponse AdvancedRouter::RouteHexAnalysis( - const std::vector& data, - uint32_t address, + const std::vector& data, uint32_t address, const RouteContext& ctx) { - + RoutedResponse response; - + // Infer data type std::string data_type = InferDataType(data); auto patterns = ExtractPatterns(data); - + // Summary for user - response.summary = absl::StrFormat( - "Address 0x%06X contains %s (%zu bytes)", - address, data_type, data.size()); - + response.summary = absl::StrFormat("Address 0x%06X contains %s (%zu bytes)", + address, data_type, data.size()); + // Detailed data for agent with structure hints std::ostringstream detailed; detailed << absl::StrFormat("Raw hex at 0x%06X:\n", address); @@ -41,63 +39,63 @@ AdvancedRouter::RoutedResponse AdvancedRouter::RouteHexAnalysis( } detailed << "\n"; } - + if (!patterns.empty()) { detailed << "\nDetected patterns:\n"; for (const auto& pattern : patterns) { detailed << "- " << pattern << "\n"; } } - + response.detailed_data = detailed.str(); - + // Next steps based on data type if (data_type.find("sprite") != std::string::npos) { - response.next_steps = "Use resource-list --type=sprite to identify sprite IDs"; + response.next_steps = + "Use resource-list --type=sprite to identify sprite IDs"; } else if (data_type.find("tile") != std::string::npos) { - response.next_steps = "Use overworld-find-tile to see where this tile appears"; + response.next_steps = + "Use overworld-find-tile to see where this tile appears"; } else if (data_type.find("palette") != std::string::npos) { response.next_steps = "Use palette-get-colors to see full palette"; } else { response.next_steps = "Use hex-search to find similar patterns in ROM"; } - + return response; } AdvancedRouter::RoutedResponse AdvancedRouter::RouteMapEdit( - const std::string& edit_intent, - const RouteContext& ctx) { - + const std::string& edit_intent, const RouteContext& ctx) { + RoutedResponse response; - + // Parse intent and generate action sequence response.summary = "Preparing map edit operation"; response.needs_approval = true; - + // Generate GUI automation steps response.gui_actions = { - "Click(\"Overworld Editor\")", - "Wait(500)", - "Click(canvas, x, y)", - "SelectTile(tile_id)", - "Click(target_x, target_y)", - "Wait(100)", - "Screenshot(\"after_edit.png\")", + "Click(\"Overworld Editor\")", + "Wait(500)", + "Click(canvas, x, y)", + "SelectTile(tile_id)", + "Click(target_x, target_y)", + "Wait(100)", + "Screenshot(\"after_edit.png\")", }; - + response.detailed_data = GenerateGUIScript(response.gui_actions); response.next_steps = "Review proposed changes, then approve or modify"; - + return response; } AdvancedRouter::RoutedResponse AdvancedRouter::RoutePaletteAnalysis( - const std::vector& colors, - const RouteContext& ctx) { - + const std::vector& colors, const RouteContext& ctx) { + RoutedResponse response; - + // Analyze color relationships int unique_colors = 0; std::map color_counts; @@ -105,11 +103,10 @@ AdvancedRouter::RoutedResponse AdvancedRouter::RoutePaletteAnalysis( color_counts[c]++; } unique_colors = color_counts.size(); - - response.summary = absl::StrFormat( - "Palette has %zu colors (%d unique)", - colors.size(), unique_colors); - + + response.summary = absl::StrFormat("Palette has %zu colors (%d unique)", + colors.size(), unique_colors); + // Detailed breakdown std::ostringstream detailed; detailed << "Color breakdown:\n"; @@ -118,32 +115,33 @@ AdvancedRouter::RoutedResponse AdvancedRouter::RoutePaletteAnalysis( uint8_t r = (snes & 0x1F) << 3; uint8_t g = ((snes >> 5) & 0x1F) << 3; uint8_t b = ((snes >> 10) & 0x1F) << 3; - detailed << absl::StrFormat(" [%zu] $%04X = #%02X%02X%02X\n", i, snes, r, g, b); + detailed << absl::StrFormat(" [%zu] $%04X = #%02X%02X%02X\n", i, snes, r, + g, b); } - + if (color_counts.size() < colors.size()) { detailed << "\nDuplicates found - optimization possible\n"; } - + response.detailed_data = detailed.str(); response.next_steps = "Use palette-set-color to modify colors"; - + return response; } AdvancedRouter::RoutedResponse AdvancedRouter::SynthesizeMultiToolResponse( - const std::vector& tool_results, - const RouteContext& ctx) { - + const std::vector& tool_results, const RouteContext& ctx) { + RoutedResponse response; - + // Combine results intelligently - response.summary = absl::StrFormat("Analyzed %zu data sources", tool_results.size()); + response.summary = + absl::StrFormat("Analyzed %zu data sources", tool_results.size()); response.detailed_data = absl::StrJoin(tool_results, "\n---\n"); - + // Generate insights response.next_steps = "Analysis complete. " + ctx.user_intent; - + return response; } @@ -160,17 +158,21 @@ std::string AdvancedRouter::GenerateGUIScript( } std::string AdvancedRouter::InferDataType(const std::vector& data) { - if (data.size() == 8) return "tile16 data"; - if (data.size() % 3 == 0 && data.size() <= 48) return "sprite data"; - if (data.size() == 32) return "palette data (16 colors)"; - if (data.size() > 1000) return "compressed data block"; + if (data.size() == 8) + return "tile16 data"; + if (data.size() % 3 == 0 && data.size() <= 48) + return "sprite data"; + if (data.size() == 32) + return "palette data (16 colors)"; + if (data.size() > 1000) + return "compressed data block"; return "unknown data"; } std::vector AdvancedRouter::ExtractPatterns( const std::vector& data) { std::vector patterns; - + // Check for repeating bytes if (data.size() > 2) { bool all_same = true; @@ -184,18 +186,22 @@ std::vector AdvancedRouter::ExtractPatterns( patterns.push_back(absl::StrFormat("Repeating byte: 0x%02X", data[0])); } } - + // Check for ascending/descending sequences if (data.size() > 3) { bool ascending = true, descending = true; for (size_t i = 1; i < data.size(); ++i) { - if (data[i] != data[i-1] + 1) ascending = false; - if (data[i] != data[i-1] - 1) descending = false; + if (data[i] != data[i - 1] + 1) + ascending = false; + if (data[i] != data[i - 1] - 1) + descending = false; } - if (ascending) patterns.push_back("Ascending sequence"); - if (descending) patterns.push_back("Descending sequence"); + if (ascending) + patterns.push_back("Ascending sequence"); + if (descending) + patterns.push_back("Descending sequence"); } - + return patterns; } diff --git a/src/cli/service/agent/advanced_routing.h b/src/cli/service/agent/advanced_routing.h index e16eadfc..0746468e 100644 --- a/src/cli/service/agent/advanced_routing.h +++ b/src/cli/service/agent/advanced_routing.h @@ -1,9 +1,9 @@ #ifndef YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_ #define YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_ +#include #include #include -#include #include "absl/status/statusor.h" namespace yaze { @@ -28,53 +28,49 @@ class AdvancedRouter { std::vector tool_calls_made; std::string accumulated_knowledge; }; - + struct RoutedResponse { - std::string summary; // High-level answer - std::string detailed_data; // Raw data for agent processing - std::string next_steps; // Suggested follow-up actions + std::string summary; // High-level answer + std::string detailed_data; // Raw data for agent processing + std::string next_steps; // Suggested follow-up actions std::vector gui_actions; // For test harness - bool needs_approval; // For proposals + bool needs_approval; // For proposals }; - + /** * @brief Route hex data analysis response */ - static RoutedResponse RouteHexAnalysis( - const std::vector& data, - uint32_t address, - const RouteContext& ctx); - + static RoutedResponse RouteHexAnalysis(const std::vector& data, + uint32_t address, + const RouteContext& ctx); + /** * @brief Route map editing response */ - static RoutedResponse RouteMapEdit( - const std::string& edit_intent, - const RouteContext& ctx); - + static RoutedResponse RouteMapEdit(const std::string& edit_intent, + const RouteContext& ctx); + /** * @brief Route palette analysis response */ static RoutedResponse RoutePaletteAnalysis( - const std::vector& colors, - const RouteContext& ctx); - + const std::vector& colors, const RouteContext& ctx); + /** * @brief Synthesize multi-tool response */ static RoutedResponse SynthesizeMultiToolResponse( - const std::vector& tool_results, - const RouteContext& ctx); - + const std::vector& tool_results, const RouteContext& ctx); + /** * @brief Generate GUI automation script */ - static std::string GenerateGUIScript( - const std::vector& actions); - + static std::string GenerateGUIScript(const std::vector& actions); + private: static std::string InferDataType(const std::vector& data); - static std::vector ExtractPatterns(const std::vector& data); + static std::vector ExtractPatterns( + const std::vector& data); static std::string FormatForAgent(const std::string& raw_data); }; diff --git a/src/cli/service/agent/agent_control_server.cc b/src/cli/service/agent/agent_control_server.cc index 88ea97cb..4e9467dd 100644 --- a/src/cli/service/agent/agent_control_server.cc +++ b/src/cli/service/agent/agent_control_server.cc @@ -1,8 +1,8 @@ #include "cli/service/agent/agent_control_server.h" -#include "cli/service/agent/emulator_service_impl.h" #include #include #include +#include "cli/service/agent/emulator_service_impl.h" namespace yaze::agent { @@ -10,37 +10,39 @@ AgentControlServer::AgentControlServer(yaze::emu::Emulator* emulator) : emulator_(emulator) {} AgentControlServer::~AgentControlServer() { - Stop(); + Stop(); } void AgentControlServer::Start() { - server_thread_ = std::thread(&AgentControlServer::Run, this); + server_thread_ = std::thread(&AgentControlServer::Run, this); } void AgentControlServer::Stop() { - if (server_) { - server_->Shutdown(); - } - if (server_thread_.joinable()) { - server_thread_.join(); - } + if (server_) { + server_->Shutdown(); + } + if (server_thread_.joinable()) { + server_thread_.join(); + } } void AgentControlServer::Run() { - std::string server_address("0.0.0.0:50051"); - EmulatorServiceImpl service(emulator_); + std::string server_address("0.0.0.0:50051"); + EmulatorServiceImpl service(emulator_); - grpc::ServerBuilder builder; - builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); - builder.RegisterService(&service); + grpc::ServerBuilder builder; + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + builder.RegisterService(&service); - server_ = builder.BuildAndStart(); - if (server_) { - std::cout << "AgentControlServer listening on " << server_address << std::endl; - server_->Wait(); - } else { - std::cerr << "Failed to start AgentControlServer on " << server_address << std::endl; - } + server_ = builder.BuildAndStart(); + if (server_) { + std::cout << "AgentControlServer listening on " << server_address + << std::endl; + server_->Wait(); + } else { + std::cerr << "Failed to start AgentControlServer on " << server_address + << std::endl; + } } -} // namespace yaze::agent +} // namespace yaze::agent diff --git a/src/cli/service/agent/agent_control_server.h b/src/cli/service/agent/agent_control_server.h index 3fbf23e4..2b96f1c7 100644 --- a/src/cli/service/agent/agent_control_server.h +++ b/src/cli/service/agent/agent_control_server.h @@ -14,19 +14,19 @@ class Emulator; namespace yaze::agent { class AgentControlServer { -public: - AgentControlServer(yaze::emu::Emulator* emulator); - ~AgentControlServer(); + public: + AgentControlServer(yaze::emu::Emulator* emulator); + ~AgentControlServer(); - void Start(); - void Stop(); + void Start(); + void Stop(); -private: - void Run(); + private: + void Run(); - yaze::emu::Emulator* emulator_; // Non-owning pointer - std::unique_ptr server_; - std::thread server_thread_; + yaze::emu::Emulator* emulator_; // Non-owning pointer + std::unique_ptr server_; + std::thread server_thread_; }; -} // namespace yaze::agent +} // namespace yaze::agent diff --git a/src/cli/service/agent/agent_pretraining.cc b/src/cli/service/agent/agent_pretraining.cc index 310642db..de463b36 100644 --- a/src/cli/service/agent/agent_pretraining.cc +++ b/src/cli/service/agent/agent_pretraining.cc @@ -10,10 +10,10 @@ namespace agent { std::vector AgentPretraining::GetModules() { return { - {"rom_structure", GetRomStructureKnowledge(nullptr), true}, - {"hex_analysis", GetHexAnalysisKnowledge(), true}, - {"map_editing", GetMapEditingKnowledge(), true}, - {"tool_usage", GetToolUsageExamples(), true}, + {"rom_structure", GetRomStructureKnowledge(nullptr), true}, + {"hex_analysis", GetHexAnalysisKnowledge(), true}, + {"map_editing", GetMapEditingKnowledge(), true}, + {"tool_usage", GetToolUsageExamples(), true}, }; } @@ -171,19 +171,19 @@ Steps: std::string AgentPretraining::GeneratePretrainingPrompt(Rom* rom) { std::string prompt = "# Agent Pre-Training Session\n\n"; prompt += "You are being initialized with deep knowledge about this ROM.\n\n"; - + if (rom && rom->is_loaded()) { prompt += absl::StrFormat("## Current ROM: %s\n", rom->title()); prompt += absl::StrFormat("Size: %zu bytes\n", rom->size()); // prompt += absl::StrFormat("Type: %s\n\n", rom->is_expanded() ? "Expanded" : "Vanilla"); } - + for (const auto& module : GetModules()) { prompt += absl::StrFormat("## Module: %s\n", module.name); prompt += module.content; prompt += "\n---\n\n"; } - + prompt += R"( ## Your Capabilities After Training @@ -197,7 +197,7 @@ You now understand: **Test your knowledge**: When I ask about sprites, dungeons, or tiles, use multiple tools in one response to give comprehensive answers. )"; - + return prompt; } diff --git a/src/cli/service/agent/agent_pretraining.h b/src/cli/service/agent/agent_pretraining.h index cf227c01..71ab09c2 100644 --- a/src/cli/service/agent/agent_pretraining.h +++ b/src/cli/service/agent/agent_pretraining.h @@ -24,32 +24,32 @@ class AgentPretraining { std::string content; bool required; }; - + /** * @brief Load all pre-training modules */ static std::vector GetModules(); - + /** * @brief Get ROM structure explanation */ static std::string GetRomStructureKnowledge(Rom* rom); - + /** * @brief Get hex data analysis patterns */ static std::string GetHexAnalysisKnowledge(); - + /** * @brief Get map editing workflow */ static std::string GetMapEditingKnowledge(); - + /** * @brief Get tool usage examples */ static std::string GetToolUsageExamples(); - + /** * @brief Generate pre-training prompt for agent */ diff --git a/src/cli/service/agent/conversational_agent_service.cc b/src/cli/service/agent/conversational_agent_service.cc index 5c0ddb3f..2e2a39c0 100644 --- a/src/cli/service/agent/conversational_agent_service.cc +++ b/src/cli/service/agent/conversational_agent_service.cc @@ -21,9 +21,9 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "app/rom.h" -#include "cli/service/agent/proposal_executor.h" #include "cli/service/agent/advanced_routing.h" #include "cli/service/agent/agent_pretraining.h" +#include "cli/service/agent/proposal_executor.h" #include "cli/service/ai/service_factory.h" #include "cli/util/terminal_colors.h" #include "nlohmann/json.hpp" @@ -41,11 +41,13 @@ namespace agent { namespace { std::string TrimWhitespace(const std::string& input) { - auto begin = std::find_if_not(input.begin(), input.end(), - [](unsigned char c) { return std::isspace(c); }); - auto end = std::find_if_not(input.rbegin(), input.rend(), - [](unsigned char c) { return std::isspace(c); }) - .base(); + auto begin = + std::find_if_not(input.begin(), input.end(), + [](unsigned char c) { return std::isspace(c); }); + auto end = + std::find_if_not(input.rbegin(), input.rend(), [](unsigned char c) { + return std::isspace(c); + }).base(); if (begin >= end) { return ""; } @@ -81,7 +83,8 @@ std::set CollectObjectKeys(const nlohmann::json& array) { return keys; } -std::optional BuildTableData(const nlohmann::json& data) { +std::optional BuildTableData( + const nlohmann::json& data) { using TableData = ChatMessage::TableData; if (data.is_object()) { @@ -101,9 +104,9 @@ std::optional BuildTableData(const nlohmann::json& data) return table; } - const bool all_objects = std::all_of(data.begin(), data.end(), [](const nlohmann::json& item) { - return item.is_object(); - }); + const bool all_objects = std::all_of( + data.begin(), data.end(), + [](const nlohmann::json& item) { return item.is_object(); }); if (all_objects) { auto keys = CollectObjectKeys(data); @@ -157,7 +160,8 @@ int CountExecutableCommands(const std::vector& commands) { return count; } -ChatMessage CreateMessage(ChatMessage::Sender sender, const std::string& content) { +ChatMessage CreateMessage(ChatMessage::Sender sender, + const std::string& content) { ChatMessage message; message.sender = sender; message.message = content; @@ -165,7 +169,8 @@ ChatMessage CreateMessage(ChatMessage::Sender sender, const std::string& content if (sender == ChatMessage::Sender::kAgent) { const std::string trimmed = TrimWhitespace(content); - if (!trimmed.empty() && (trimmed.front() == '{' || trimmed.front() == '[')) { + if (!trimmed.empty() && + (trimmed.front() == '{' || trimmed.front() == '[')) { try { nlohmann::json parsed = nlohmann::json::parse(trimmed); message.table_data = BuildTableData(parsed); @@ -185,40 +190,41 @@ ConversationalAgentService::ConversationalAgentService() { provider_config_.provider = "auto"; ai_service_ = CreateAIService(); tool_dispatcher_.SetToolPreferences(tool_preferences_); - + #ifdef Z3ED_AI // Initialize advanced features auto learn_status = learned_knowledge_.Initialize(); if (!learn_status.ok() && config_.verbose) { - std::cerr << "Warning: Failed to initialize learned knowledge: " + std::cerr << "Warning: Failed to initialize learned knowledge: " << learn_status.message() << std::endl; } - + auto todo_status = todo_manager_.Initialize(); if (!todo_status.ok() && config_.verbose) { - std::cerr << "Warning: Failed to initialize TODO manager: " + std::cerr << "Warning: Failed to initialize TODO manager: " << todo_status.message() << std::endl; } #endif } -ConversationalAgentService::ConversationalAgentService(const AgentConfig& config) +ConversationalAgentService::ConversationalAgentService( + const AgentConfig& config) : config_(config) { provider_config_.provider = "auto"; ai_service_ = CreateAIService(); tool_dispatcher_.SetToolPreferences(tool_preferences_); - + #ifdef Z3ED_AI // Initialize advanced features auto learn_status = learned_knowledge_.Initialize(); if (!learn_status.ok() && config_.verbose) { - std::cerr << "Warning: Failed to initialize learned knowledge: " + std::cerr << "Warning: Failed to initialize learned knowledge: " << learn_status.message() << std::endl; } - + auto todo_status = todo_manager_.Initialize(); if (!todo_status.ok() && config_.verbose) { - std::cerr << "Warning: Failed to initialize TODO manager: " + std::cerr << "Warning: Failed to initialize TODO manager: " << todo_status.message() << std::endl; } #endif @@ -248,7 +254,8 @@ void ConversationalAgentService::TrimHistoryIfNeeded() { } } -ChatMessage::SessionMetrics ConversationalAgentService::BuildMetricsSnapshot() const { +ChatMessage::SessionMetrics ConversationalAgentService::BuildMetricsSnapshot() + const { ChatMessage::SessionMetrics snapshot; snapshot.turn_index = metrics_.turns_completed; snapshot.total_user_messages = metrics_.user_messages; @@ -256,7 +263,8 @@ ChatMessage::SessionMetrics ConversationalAgentService::BuildMetricsSnapshot() c snapshot.total_tool_calls = metrics_.tool_calls; snapshot.total_commands = metrics_.commands_generated; snapshot.total_proposals = metrics_.proposals_created; - snapshot.total_elapsed_seconds = absl::ToDoubleSeconds(metrics_.total_latency); + snapshot.total_elapsed_seconds = + absl::ToDoubleSeconds(metrics_.total_latency); snapshot.average_latency_seconds = metrics_.turns_completed > 0 ? snapshot.total_elapsed_seconds / @@ -286,52 +294,56 @@ absl::StatusOr ConversationalAgentService::SendMessage( bool waiting_for_text_response = false; absl::Time turn_start = absl::Now(); std::vector executed_tools; - + if (config_.verbose) { - util::PrintInfo(absl::StrCat("Starting agent loop (max ", max_iterations, " iterations)")); - util::PrintInfo(absl::StrCat("History size: ", history_.size(), " messages")); + util::PrintInfo(absl::StrCat("Starting agent loop (max ", max_iterations, + " iterations)")); + util::PrintInfo( + absl::StrCat("History size: ", history_.size(), " messages")); } - + for (int iteration = 0; iteration < max_iterations; ++iteration) { if (config_.verbose) { util::PrintSeparator(); - std::cout << util::colors::kCyan << "Iteration " << (iteration + 1) - << "/" << max_iterations << util::colors::kReset << std::endl; + std::cout << util::colors::kCyan << "Iteration " << (iteration + 1) << "/" + << max_iterations << util::colors::kReset << std::endl; } - + // Show loading indicator while waiting for AI response util::LoadingIndicator loader( - waiting_for_text_response - ? "Generating final response..." - : "Thinking...", + waiting_for_text_response ? "Generating final response..." + : "Thinking...", !config_.verbose); // Hide spinner in verbose mode loader.Start(); - + auto response_or = ai_service_->GenerateResponse(history_); loader.Stop(); - + if (!response_or.ok()) { - util::PrintError(absl::StrCat( - "Failed to get AI response: ", response_or.status().message())); - return absl::InternalError(absl::StrCat( - "Failed to get AI response: ", response_or.status().message())); + util::PrintError(absl::StrCat("Failed to get AI response: ", + response_or.status().message())); + return absl::InternalError(absl::StrCat("Failed to get AI response: ", + response_or.status().message())); } const auto& agent_response = response_or.value(); if (config_.verbose) { util::PrintInfo("Received agent response:"); - std::cout << util::colors::kDim << " - Tool calls: " - << agent_response.tool_calls.size() << util::colors::kReset << std::endl; - std::cout << util::colors::kDim << " - Commands: " - << agent_response.commands.size() << util::colors::kReset << std::endl; + std::cout << util::colors::kDim + << " - Tool calls: " << agent_response.tool_calls.size() + << util::colors::kReset << std::endl; + std::cout << util::colors::kDim + << " - Commands: " << agent_response.commands.size() + << util::colors::kReset << std::endl; std::cout << util::colors::kDim << " - Text response: " << (agent_response.text_response.empty() ? "empty" : "present") << util::colors::kReset << std::endl; if (!agent_response.reasoning.empty() && config_.show_reasoning) { - std::cout << util::colors::kYellow << " 💭 Reasoning: " - << util::colors::kDim << agent_response.reasoning - << util::colors::kReset << std::endl; + std::cout << util::colors::kYellow + << " 💭 Reasoning: " << util::colors::kDim + << agent_response.reasoning << util::colors::kReset + << std::endl; } } @@ -339,10 +351,11 @@ absl::StatusOr ConversationalAgentService::SendMessage( // Check if we were waiting for a text response but got more tool calls instead if (waiting_for_text_response) { util::PrintWarning( - absl::StrCat("LLM called tools again instead of providing final response (Iteration: ", - iteration + 1, "/", max_iterations, ")")); + absl::StrCat("LLM called tools again instead of providing final " + "response (Iteration: ", + iteration + 1, "/", max_iterations, ")")); } - + bool executed_tool = false; for (const auto& tool_call : agent_response.tool_calls) { // Format tool arguments for display @@ -351,13 +364,13 @@ absl::StatusOr ConversationalAgentService::SendMessage( arg_parts.push_back(absl::StrCat(key, "=", value)); } std::string args_str = absl::StrJoin(arg_parts, ", "); - + util::PrintToolCall(tool_call.tool_name, args_str); - - auto tool_result_or = tool_dispatcher_.Dispatch(tool_call); + + auto tool_result_or = tool_dispatcher_.Dispatch(tool_call); if (!tool_result_or.ok()) { - util::PrintError(absl::StrCat( - "Tool execution failed: ", tool_result_or.status().message())); + util::PrintError(absl::StrCat("Tool execution failed: ", + tool_result_or.status().message())); return absl::InternalError(absl::StrCat( "Tool execution failed: ", tool_result_or.status().message())); } @@ -366,24 +379,30 @@ absl::StatusOr ConversationalAgentService::SendMessage( if (!tool_output.empty()) { util::PrintSuccess("Tool executed successfully"); ++metrics_.tool_calls; - + if (config_.verbose) { - std::cout << util::colors::kDim << "Tool output (truncated):" - << util::colors::kReset << std::endl; - std::string preview = tool_output.substr(0, std::min(size_t(200), tool_output.size())); - if (tool_output.size() > 200) preview += "..."; - std::cout << util::colors::kDim << preview << util::colors::kReset << std::endl; + std::cout << util::colors::kDim + << "Tool output (truncated):" << util::colors::kReset + << std::endl; + std::string preview = tool_output.substr( + 0, std::min(size_t(200), tool_output.size())); + if (tool_output.size() > 200) + preview += "..."; + std::cout << util::colors::kDim << preview << util::colors::kReset + << std::endl; } - + // Add tool result with a clear marker for the LLM // Format as plain text to avoid confusing the LLM with nested JSON std::string marked_output = absl::StrCat( "[TOOL RESULT for ", tool_call.tool_name, "]\n", - "The tool returned the following data:\n", - tool_output, "\n\n", - "Please provide a text_response field in your JSON to summarize this information for the user."); - auto tool_result_msg = CreateMessage(ChatMessage::Sender::kUser, marked_output); - tool_result_msg.is_internal = true; // Don't show this to the human user + "The tool returned the following data:\n", tool_output, "\n\n", + "Please provide a text_response field in your JSON to summarize " + "this information for the user."); + auto tool_result_msg = + CreateMessage(ChatMessage::Sender::kUser, marked_output); + tool_result_msg.is_internal = + true; // Don't show this to the human user history_.push_back(tool_result_msg); } executed_tool = true; @@ -399,11 +418,12 @@ absl::StatusOr ConversationalAgentService::SendMessage( } // Check if we received a text response after tool execution - if (waiting_for_text_response && agent_response.text_response.empty() && + if (waiting_for_text_response && agent_response.text_response.empty() && agent_response.commands.empty()) { util::PrintWarning( - absl::StrCat("LLM did not provide text_response after receiving tool results (Iteration: ", - iteration + 1, "/", max_iterations, ")")); + absl::StrCat("LLM did not provide text_response after receiving tool " + "results (Iteration: ", + iteration + 1, "/", max_iterations, ")")); // Continue to give it another chance continue; } @@ -421,8 +441,8 @@ absl::StatusOr ConversationalAgentService::SendMessage( util::PrintWarning( "Cannot create proposal because no ROM context is active."); } else if (!rom_context_->is_loaded()) { - proposal_status = absl::FailedPreconditionError( - "ROM context is not loaded"); + proposal_status = + absl::FailedPreconditionError("ROM context is not loaded"); util::PrintWarning( "Cannot create proposal because the ROM context is not loaded."); } else { @@ -436,14 +456,14 @@ absl::StatusOr ConversationalAgentService::SendMessage( auto creation_or = CreateProposalFromAgentResponse(request); if (!creation_or.ok()) { proposal_status = creation_or.status(); - util::PrintError(absl::StrCat( - "Failed to create proposal: ", proposal_status.message())); + util::PrintError(absl::StrCat("Failed to create proposal: ", + proposal_status.message())); } else { proposal_result = std::move(creation_or.value()); if (config_.verbose) { util::PrintSuccess(absl::StrCat( - "Created proposal ", proposal_result->metadata.id, - " with ", proposal_result->change_count, " change(s).")); + "Created proposal ", proposal_result->metadata.id, " with ", + proposal_result->change_count, " change(s).")); } } } @@ -475,22 +495,23 @@ absl::StatusOr ConversationalAgentService::SendMessage( } response_text.append(absl::StrFormat( "✅ Proposal %s ready with %d change%s (%d command%s).\n" - "Review it in the Proposal drawer or run `z3ed agent diff --proposal-id %s`.\n" + "Review it in the Proposal drawer or run `z3ed agent diff " + "--proposal-id %s`.\n" "Sandbox ROM: %s\nProposal JSON: %s", metadata.id, proposal_result->change_count, proposal_result->change_count == 1 ? "" : "s", proposal_result->executed_commands, - proposal_result->executed_commands == 1 ? "" : "s", - metadata.id, metadata.sandbox_rom_path.string(), + proposal_result->executed_commands == 1 ? "" : "s", metadata.id, + metadata.sandbox_rom_path.string(), proposal_result->proposal_json_path.string())); ++metrics_.proposals_created; } else if (attempted_proposal && !proposal_status.ok()) { if (!response_text.empty()) { response_text.append("\n\n"); } - response_text.append(absl::StrCat( - "⚠️ Failed to prepare a proposal automatically: ", - proposal_status.message())); + response_text.append( + absl::StrCat("⚠️ Failed to prepare a proposal automatically: ", + proposal_status.message())); } ChatMessage chat_response = CreateMessage(ChatMessage::Sender::kAgent, response_text); @@ -603,27 +624,29 @@ void ConversationalAgentService::RebuildMetricsFromHistory() { #ifdef Z3ED_AI // === Advanced Feature Integration === -std::string ConversationalAgentService::BuildEnhancedPrompt(const std::string& user_message) { +std::string ConversationalAgentService::BuildEnhancedPrompt( + const std::string& user_message) { std::ostringstream enhanced; - + // Inject pretraining on first message if (inject_pretraining_ && !pretraining_injected_ && rom_context_) { enhanced << InjectPretraining() << "\n\n"; pretraining_injected_ = true; } - + // Inject learned context if (inject_learned_context_) { enhanced << InjectLearnedContext(user_message) << "\n"; } - + enhanced << user_message; return enhanced.str(); } -std::string ConversationalAgentService::InjectLearnedContext(const std::string& message) { +std::string ConversationalAgentService::InjectLearnedContext( + const std::string& message) { std::ostringstream context; - + // Add relevant preferences auto prefs = learned_knowledge_.GetAllPreferences(); if (!prefs.empty() && prefs.size() <= 5) { // Don't overwhelm with too many @@ -634,13 +657,13 @@ std::string ConversationalAgentService::InjectLearnedContext(const std::string& } context << absl::StrJoin(pref_strings, ", ") << "]\n"; } - + // Add ROM-specific patterns if (rom_context_ && rom_context_->is_loaded()) { // TODO: Get ROM hash // auto patterns = learned_knowledge_.QueryPatterns("", rom_hash); } - + // Add recent relevant memories std::vector keywords; // Extract keywords from message (simple word splitting) @@ -649,7 +672,7 @@ std::string ConversationalAgentService::InjectLearnedContext(const std::string& keywords.push_back(std::string(word)); } } - + if (!keywords.empty()) { auto memories = learned_knowledge_.SearchMemories(keywords[0]); if (!memories.empty() && memories.size() <= 3) { @@ -660,7 +683,7 @@ std::string ConversationalAgentService::InjectLearnedContext(const std::string& context << "]\n"; } } - + return context.str(); } @@ -668,21 +691,20 @@ std::string ConversationalAgentService::InjectPretraining() { if (!rom_context_) { return ""; } - + std::ostringstream pretraining; pretraining << "[SYSTEM KNOWLEDGE INJECTION - Read this first]\n\n"; pretraining << AgentPretraining::GeneratePretrainingPrompt(rom_context_); pretraining << "\n[END KNOWLEDGE INJECTION]\n"; - + return pretraining.str(); } ChatMessage ConversationalAgentService::EnhanceResponse( - const ChatMessage& response, - const std::string& user_message) { + const ChatMessage& response, const std::string& user_message) { // Use AdvancedRouter to enhance tool-based responses // This would synthesize multi-tool results into coherent insights - + // For now, return response as-is // TODO: Integrate AdvancedRouter here return response; diff --git a/src/cli/service/agent/conversational_agent_service.h b/src/cli/service/agent/conversational_agent_service.h index b40ddef7..88408bce 100644 --- a/src/cli/service/agent/conversational_agent_service.h +++ b/src/cli/service/agent/conversational_agent_service.h @@ -10,16 +10,16 @@ #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/ai/service_factory.h" #include "cli/service/agent/proposal_executor.h" #include "cli/service/agent/tool_dispatcher.h" +#include "cli/service/ai/ai_service.h" +#include "cli/service/ai/service_factory.h" // Advanced features (only available when Z3ED_AI=ON) #ifdef Z3ED_AI -#include "cli/service/agent/learned_knowledge_service.h" -#include "cli/service/agent/todo_manager.h" #include "cli/service/agent/advanced_routing.h" #include "cli/service/agent/agent_pretraining.h" +#include "cli/service/agent/learned_knowledge_service.h" +#include "cli/service/agent/todo_manager.h" #endif #ifdef SendMessage @@ -51,7 +51,8 @@ struct ChatMessage { absl::Time timestamp; std::optional json_pretty; std::optional table_data; - bool is_internal = false; // True for tool results and other messages not meant for user display + bool is_internal = + false; // True for tool results and other messages not meant for user display std::vector warnings; struct ModelMetadata { std::string provider; @@ -76,21 +77,17 @@ struct ChatMessage { std::optional proposal; }; -enum class AgentOutputFormat { - kFriendly, - kCompact, - kMarkdown, - kJson -}; +enum class AgentOutputFormat { kFriendly, kCompact, kMarkdown, kJson }; struct AgentConfig { int max_tool_iterations = 4; // Maximum number of tool calling iterations int max_retry_attempts = 3; // Maximum retries on errors - bool verbose = false; // Enable verbose diagnostic output - bool show_reasoning = true; // Show LLM reasoning in output - size_t max_history_messages = 50; // Maximum stored history messages per session - bool trim_history = true; // Whether to trim history beyond the limit - bool enable_vim_mode = false; // Enable vim-style line editing in simple-chat + bool verbose = false; // Enable verbose diagnostic output + bool show_reasoning = true; // Show LLM reasoning in output + size_t max_history_messages = + 50; // Maximum stored history messages per session + bool trim_history = true; // Whether to trim history beyond the limit + bool enable_vim_mode = false; // Enable vim-style line editing in simple-chat AgentOutputFormat output_format = AgentOutputFormat::kFriendly; }; @@ -121,12 +118,12 @@ class ConversationalAgentService { ChatMessage::SessionMetrics GetMetrics() const; void ReplaceHistory(std::vector history); - + #ifdef Z3ED_AI // Advanced Features Access (only when Z3ED_AI=ON) LearnedKnowledgeService& learned_knowledge() { return learned_knowledge_; } TodoManager& todo_manager() { return todo_manager_; } - + // Inject learned context into next message void EnableContextInjection(bool enable) { inject_learned_context_ = enable; } void EnablePretraining(bool enable) { inject_pretraining_ = enable; } @@ -146,15 +143,16 @@ class ConversationalAgentService { void TrimHistoryIfNeeded(); ChatMessage::SessionMetrics BuildMetricsSnapshot() const; void RebuildMetricsFromHistory(); - + #ifdef Z3ED_AI // Context enhancement (only when Z3ED_AI=ON) std::string BuildEnhancedPrompt(const std::string& user_message); std::string InjectLearnedContext(const std::string& message); std::string InjectPretraining(); - + // Response enhancement - ChatMessage EnhanceResponse(const ChatMessage& response, const std::string& user_message); + ChatMessage EnhanceResponse(const ChatMessage& response, + const std::string& user_message); #endif std::vector history_; @@ -165,7 +163,7 @@ class ConversationalAgentService { Rom* rom_context_ = nullptr; AgentConfig config_; InternalMetrics metrics_; - + #ifdef Z3ED_AI // Advanced features (only when Z3ED_AI=ON) LearnedKnowledgeService learned_knowledge_; diff --git a/src/cli/service/agent/emulator_service_impl.cc b/src/cli/service/agent/emulator_service_impl.cc index 54c1382a..32cb4384 100644 --- a/src/cli/service/agent/emulator_service_impl.cc +++ b/src/cli/service/agent/emulator_service_impl.cc @@ -1,612 +1,704 @@ #include "cli/service/agent/emulator_service_impl.h" -#include "app/emu/emulator.h" -#include "app/service/screenshot_utils.h" -#include "app/emu/input/input_backend.h" // Required for SnesButton enum -#include "app/emu/debug/breakpoint_manager.h" -#include "app/emu/debug/watchpoint_manager.h" -#include "app/emu/debug/disassembly_viewer.h" -#include "absl/strings/escaping.h" -#include "absl/strings/str_format.h" #include #include +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" +#include "app/emu/debug/breakpoint_manager.h" +#include "app/emu/debug/disassembly_viewer.h" +#include "app/emu/debug/watchpoint_manager.h" +#include "app/emu/emulator.h" +#include "app/emu/input/input_backend.h" // Required for SnesButton enum +#include "app/service/screenshot_utils.h" namespace yaze::agent { namespace { // Helper to convert our gRPC Button enum to the emulator's SnesButton enum emu::input::SnesButton ToSnesButton(Button button) { - using emu::input::SnesButton; - switch (button) { - case A: return SnesButton::A; - case B: return SnesButton::B; - case X: return SnesButton::X; - case Y: return SnesButton::Y; - case L: return SnesButton::L; - case R: return SnesButton::R; - case SELECT: return SnesButton::SELECT; - case START: return SnesButton::START; - case UP: return SnesButton::UP; - case DOWN: return SnesButton::DOWN; - case LEFT: return SnesButton::LEFT; - case RIGHT: return SnesButton::RIGHT; - default: - return SnesButton::B; // Default fallback - } + using emu::input::SnesButton; + switch (button) { + case A: + return SnesButton::A; + case B: + return SnesButton::B; + case X: + return SnesButton::X; + case Y: + return SnesButton::Y; + case L: + return SnesButton::L; + case R: + return SnesButton::R; + case SELECT: + return SnesButton::SELECT; + case START: + return SnesButton::START; + case UP: + return SnesButton::UP; + case DOWN: + return SnesButton::DOWN; + case LEFT: + return SnesButton::LEFT; + case RIGHT: + return SnesButton::RIGHT; + default: + return SnesButton::B; // Default fallback + } } -} // namespace +} // namespace EmulatorServiceImpl::EmulatorServiceImpl(yaze::emu::Emulator* emulator) : emulator_(emulator) {} // --- Lifecycle --- -grpc::Status EmulatorServiceImpl::Start(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { - if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); - emulator_->set_running(true); - response->set_success(true); - response->set_message("Emulator started."); - return grpc::Status::OK; +grpc::Status EmulatorServiceImpl::Start(grpc::ServerContext* context, + const Empty* request, + CommandResponse* response) { + if (!emulator_) + return grpc::Status(grpc::StatusCode::UNAVAILABLE, + "Emulator not initialized."); + emulator_->set_running(true); + response->set_success(true); + response->set_message("Emulator started."); + return grpc::Status::OK; } -grpc::Status EmulatorServiceImpl::Stop(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { - if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); - emulator_->set_running(false); - response->set_success(true); - response->set_message("Emulator stopped."); - return grpc::Status::OK; +grpc::Status EmulatorServiceImpl::Stop(grpc::ServerContext* context, + const Empty* request, + CommandResponse* response) { + if (!emulator_) + return grpc::Status(grpc::StatusCode::UNAVAILABLE, + "Emulator not initialized."); + emulator_->set_running(false); + response->set_success(true); + response->set_message("Emulator stopped."); + return grpc::Status::OK; } -grpc::Status EmulatorServiceImpl::Pause(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { - if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); - emulator_->set_running(false); - response->set_success(true); - response->set_message("Emulator paused."); - return grpc::Status::OK; +grpc::Status EmulatorServiceImpl::Pause(grpc::ServerContext* context, + const Empty* request, + CommandResponse* response) { + if (!emulator_) + return grpc::Status(grpc::StatusCode::UNAVAILABLE, + "Emulator not initialized."); + emulator_->set_running(false); + response->set_success(true); + response->set_message("Emulator paused."); + return grpc::Status::OK; } -grpc::Status EmulatorServiceImpl::Resume(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { - if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); - emulator_->set_running(true); - response->set_success(true); - response->set_message("Emulator resumed."); - return grpc::Status::OK; +grpc::Status EmulatorServiceImpl::Resume(grpc::ServerContext* context, + const Empty* request, + CommandResponse* response) { + if (!emulator_) + return grpc::Status(grpc::StatusCode::UNAVAILABLE, + "Emulator not initialized."); + emulator_->set_running(true); + response->set_success(true); + response->set_message("Emulator resumed."); + return grpc::Status::OK; } -grpc::Status EmulatorServiceImpl::Reset(grpc::ServerContext* context, const Empty* request, CommandResponse* response) { - if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); - emulator_->snes().Reset(true); // Hard reset - response->set_success(true); - response->set_message("Emulator reset."); - return grpc::Status::OK; +grpc::Status EmulatorServiceImpl::Reset(grpc::ServerContext* context, + const Empty* request, + CommandResponse* response) { + if (!emulator_) + return grpc::Status(grpc::StatusCode::UNAVAILABLE, + "Emulator not initialized."); + emulator_->snes().Reset(true); // Hard reset + response->set_success(true); + response->set_message("Emulator reset."); + return grpc::Status::OK; } // --- Input Control --- -grpc::Status EmulatorServiceImpl::PressButtons(grpc::ServerContext* context, const ButtonRequest* request, CommandResponse* response) { - if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized."); - auto& input_manager = emulator_->input_manager(); - for (int i = 0; i < request->buttons_size(); i++) { - input_manager.PressButton(ToSnesButton(static_cast