diff --git a/docs/E2-development-guide.md b/docs/E2-development-guide.md index 7449ee19..26e4af13 100644 --- a/docs/E2-development-guide.md +++ b/docs/E2-development-guide.md @@ -104,7 +104,37 @@ To ensure a consistent and polished look and feel, all new UI components must ad - **Items**: Bright red - **Sprites**: Bright magenta -### 3.5. Bitmap and Texture Synchronization +## 4. Debugging and Testing + +### 4.1. Quick Debugging with Startup Flags + +To accelerate your debugging workflow, use command-line flags to jump directly to specific editors and open relevant UI cards: + +```bash +# Quick dungeon room testing +./yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0" + +# Compare multiple rooms +./yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0,Room 1,Room 105" + +# Full dungeon workspace +./yaze --rom_file=zelda3.sfc --editor=Dungeon \ + --cards="Rooms List,Room Matrix,Object Editor,Palette Editor" + +# Enable debug logging +./yaze --debug --log_file=debug.log --rom_file=zelda3.sfc --editor=Dungeon +``` + +**Available Editors**: Assembly, Dungeon, Graphics, Music, Overworld, Palette, Screen, Sprite, Message, Hex, Agent, Settings + +**Dungeon Editor Cards**: Rooms List, Room Matrix, Entrances List, Room Graphics, Object Editor, Palette Editor, Room N (where N is room ID 0-319) + +See [debugging-startup-flags.md](debugging-startup-flags.md) for complete documentation. + +### 4.2. Testing Strategies + +For a comprehensive overview of debugging tools and testing strategies, including how to use the logging framework, command-line test runners, and the GUI automation harness for AI agents, please refer to the [Debugging and Testing Guide](E5-debugging-guide.md). + When working with bitmaps and textures, understand that two memory locations must stay synchronized: diff --git a/docs/E5-debugging-guide.md b/docs/E5-debugging-guide.md new file mode 100644 index 00000000..4cb1dac9 --- /dev/null +++ b/docs/E5-debugging-guide.md @@ -0,0 +1,224 @@ +# E5 - Debugging and Testing Guide + +**Last Updated**: October 9, 2025 +**Status**: Active + +This document provides a comprehensive guide to debugging and testing the `yaze` application. It covers strategies for developers and provides the necessary information for AI agents to interact with, test, and validate the application. + +--- + +## 1. Standardized Logging for Print Debugging + +For all print-based debugging, `yaze` uses a structured logging system defined in `util/log.h`. This is the **only** approved method for logging; direct use of `printf` or `std::cout` should be avoided and replaced with the appropriate `LOG_*` macro. + +### Log Levels and Usage + +- `LOG_DEBUG(category, "message", ...)`: For verbose, development-only information. +- `LOG_INFO(category, "message", ...)`: For general, informational messages. +- `LOG_WARN(category, "message", ...)`: For potential issues that don't break functionality. +- `LOG_ERROR(category, "message", ...)`: For errors that cause a specific operation to fail. + +### Log Categories + +Categories allow you to filter logs to focus on a specific subsystem. Common categories include: +- `"Main"` +- `"TestManager"` +- `"EditorManager"` +- `"APU"`, `"CPU"`, `"SNES"` (for the emulator) + +### Enabling and Configuring Logs via CLI + +You can control logging behavior using command-line flags when launching `yaze` or `yaze_test`. + +- **Enable Verbose Debug Logging**: + ```bash + ./build/bin/yaze --debug + ``` + +- **Log to a File**: + ```bash + ./build/bin/yaze --log_file=yaze_debug.log + ``` + +- **Filter by Category**: + ```bash + # Only show logs from the APU and CPU emulator components + ./build/bin/yaze_emu --emu_debug_apu=true --emu_debug_cpu=true + ``` + +**Best Practice**: When debugging a specific component, add detailed `LOG_DEBUG` statements with a unique category. Then, run `yaze` with the appropriate flags to isolate the output. + +--- + +## 2. Command-Line Workflows for Testing + +The `yaze` ecosystem provides several executables and flags to streamline testing and debugging. + +### Launching the GUI for Specific Tasks + +- **Load a ROM on Startup**: To immediately test a specific ROM, use the `--rom_file` flag. This bypasses the welcome screen. + ```bash + ./build/bin/yaze --rom_file /path/to/your/zelda3.sfc + ``` + +- **Enable the GUI Test Harness**: To allow the `z3ed` CLI to automate the GUI, you must start `yaze` with the gRPC server enabled. + ```bash + ./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness + ``` + +- **Open a Specific Editor and Cards**: To quickly test a specific editor and its components, use the `--editor` and `--cards` flags. This is especially useful for debugging complex UIs like the Dungeon Editor. + ```bash + # Open the Dungeon Editor with the Room Matrix and two specific room cards + ./build/bin/yaze --rom_file zelda3.sfc --editor=Dungeon --cards="Room Matrix,Room 0,Room 105" + + # Available editors: Assembly, Dungeon, Graphics, Music, Overworld, Palette, + # Screen, Sprite, Message, Hex, Agent, Settings + + # Dungeon editor cards: Rooms List, Room Matrix, Entrances List, Room Graphics, + # Object Editor, Palette Editor, Room N (where N is room ID) + ``` + + **Quick Examples**: + ```bash + # Fast dungeon room testing + ./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0" + + # Compare multiple rooms side-by-side + ./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0,Room 1,Room 105" + + # Full dungeon workspace with all tools + ./build/bin/yaze --rom_file=zelda3.sfc --editor=Dungeon \ + --cards="Rooms List,Room Matrix,Object Editor,Palette Editor" + + # Jump straight to overworld editing + ./build/bin/yaze --rom_file=zelda3.sfc --editor=Overworld + ``` + + For a complete reference, see [docs/debugging-startup-flags.md](debugging-startup-flags.md). + +### Running Automated C++ Tests + +The `yaze_test` executable is used to run the project's suite of unit, integration, and E2E tests. + +- **Run All Tests**: + ```bash + ./build_ai/bin/yaze_test + ``` + +- **Run Specific Categories**: + ```bash + # Run only fast, dependency-free unit tests + ./build_ai/bin/yaze_test --unit + + # Run tests that require a ROM file + ./build_ai/bin/yaze_test --rom-dependent --rom-path /path/to/zelda3.sfc + ``` + +- **Run GUI-based E2E Tests**: + ```bash + # Run E2E tests and watch the GUI interactions + ./build_ai/bin/yaze_test --e2e --show-gui + ``` + +### Inspecting ROMs with `z3ed` + +The `z3ed` CLI is a powerful tool for inspecting ROM data without launching the full GUI. This is ideal for quick checks and scripting. + +- **Get ROM Information**: + ```bash + z3ed rom info --rom zelda3.sfc + ``` + +- **Inspect Dungeon Sprites**: + ```bash + z3ed dungeon list-sprites --rom zelda3.sfc --dungeon 2 + ``` + +--- + +## 3. GUI Automation for AI Agents + +The primary way for an AI agent to test its changes and interact with `yaze` is through the GUI automation framework. This system consists of the `yaze` gRPC server (Test Harness) and the `z3ed` CLI client. + +### Architecture Overview + +1. **`yaze` (Server)**: When launched with `--enable_test_harness`, it starts a gRPC server that exposes the UI for automation. +2. **`z3ed` (Client)**: The `z3ed agent test` commands connect to the gRPC server to send commands and receive information. +3. **AI Agent**: The agent generates `z3ed` commands to drive the UI and verify its actions. + +### Step-by-Step Workflow for AI + +#### Step 1: Launch `yaze` with the Test Harness + +The AI must first ensure the `yaze` GUI is running and ready for automation. + +```bash +./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness --test_harness_port 50051 +``` + +#### Step 2: Discover UI Elements + +Before interacting with the UI, the agent needs to know the stable IDs of the widgets. + +```bash +# Discover all widgets in the Dungeon editor window +z3ed agent test discover --window "Dungeon" --grpc localhost:50051 +``` +This will return a list of widget IDs (e.g., `Dungeon/Canvas/Map`) that can be used in scripts. + +**Tip**: You can also launch `yaze` with the `--editor` flag to automatically open a specific editor: +```bash +./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness --editor=Dungeon --cards="Room 0" +``` + +#### Step 3: Record or Write a Test Script + +An agent can either generate a test script from scratch or use a pre-recorded one. + +- **Recording a human interaction**: + ```bash + z3ed agent test record --suite my_test.jsonl + ``` +- **A generated script might look like this**: + ```json + // my_test.jsonl + {"action": "click", "target": "Dungeon/Toolbar/Open Room"} + {"action": "wait", "duration_ms": 500} + {"action": "type", "target": "Room Selector/Filter", "text": "Room 105"} + {"action": "click", "target": "Room Selector/List/Room 105"} + {"action": "assert_visible", "target": "Room Card 105"} + ``` + +- **Or use startup flags to prepare the environment**: + ```bash + # Start yaze with the room already open + ./build/bin/yaze --rom_file zelda3.sfc --enable_test_harness \ + --editor=Dungeon --cards="Room 105" + + # Then your test script just needs to validate the state + {"action": "assert_visible", "target": "Room Card 105"} + {"action": "assert_visible", "target": "Dungeon/Canvas"} + ``` + +#### Step 4: Replay the Test and Verify + +The agent executes the script to perform the actions and validate the outcome. + +```bash +z3ed agent test replay my_test.jsonl --watch +``` +The `--watch` flag streams results back to the CLI in real-time. The agent can parse this output to confirm its actions were successful. + +--- + +## 4. Advanced Debugging Tools + +For more complex issues, especially within the emulator, `yaze` provides several advanced debugging windows. These are covered in detail in the [Emulator Development Guide](E4-Emulator-Development-Guide.md). + +- **Disassembly Viewer**: A live, interactive view of the 65816 and SPC700 CPU execution. +- **Breakpoint Manager**: Set breakpoints on code execution, memory reads, or memory writes. +- **Memory Viewer**: Inspect WRAM, SRAM, VRAM, and ROM. +- **APU Inspector**: A dedicated debugger for the audio subsystem. +- **Event Viewer**: A timeline of all hardware events (NMI, IRQ, DMA). + +These tools are accessible from the **Debug** menu in the main application. diff --git a/docs/index.md b/docs/index.md index ed903409..e3e04f68 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,8 @@ Welcome to the official documentation for yaze, a comprehensive ROM editor for T - [E1: Assembly Style Guide](E1-asm-style-guide.md) - 65816 assembly coding standards. - [E2: Development Guide](E2-development-guide.md) - Core architectural patterns, UI systems, and best practices. - [E3: API Reference](E3-api-reference.md) - C/C++ API documentation for extensions. -- [E4: Emulator Development Guide](E4-Emulator-Development-Guide.md) - A master guide to the SNES emulator subsystem. +- [E4: Emulator Development Guide](E4-Emulator-Development-Guide.md) +- [E5: Debugging and Testing Guide](E5-debugging-guide.md) - A master guide to the SNES emulator subsystem. ## F: Technical Documentation - [F1: Dungeon Editor Guide](F1-dungeon-editor-guide.md) - A master guide to the dungeon editing system. diff --git a/src/app/core/controller.cc b/src/app/core/controller.cc index 814e515f..aab082fa 100644 --- a/src/app/core/controller.cc +++ b/src/app/core/controller.cc @@ -2,13 +2,15 @@ #include +#include + #include "absl/status/status.h" #include "app/core/timing.h" #include "app/core/window.h" #include "app/editor/editor_manager.h" #include "app/editor/ui/background_renderer.h" -#include "app/gfx/arena.h" // Add include for Arena -#include "app/gfx/backend/sdl2_renderer.h" // Add include for new renderer +#include "app/gfx/arena.h" // Add include for Arena +#include "app/gfx/backend/sdl2_renderer.h" // Add include for new renderer #include "app/gui/theme_manager.h" #include "app/gui/widgets/widget_id_registry.h" #include "imgui/backends/imgui_impl_sdl2.h" @@ -21,23 +23,33 @@ namespace core { absl::Status Controller::OnEntry(std::string filename) { // Create renderer FIRST renderer_ = std::make_unique(); - + // Call CreateWindow with our renderer RETURN_IF_ERROR(CreateWindow(window_, renderer_.get(), SDL_WINDOW_RESIZABLE)); - + // Initialize the graphics Arena with the renderer gfx::Arena::Get().Initialize(renderer_.get()); // Set up audio for emulator editor_manager_.emulator().set_audio_buffer(window_.audio_buffer_.get()); editor_manager_.emulator().set_audio_device_id(window_.audio_device_); - + // Initialize editor manager with renderer editor_manager_.Initialize(renderer_.get(), filename); + active_ = true; return absl::OkStatus(); } +void Controller::SetStartupEditor(const std::string& editor_name, + 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()) { + editor_manager_.OpenEditorAndCardsFromFlags(editor_name, cards); + } +} + void Controller::OnInput() { PRINT_IF_ERROR(HandleEvents(window_)); } @@ -53,7 +65,7 @@ absl::Status Controller::OnLoad() { ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); - const ImGuiViewport *viewport = ImGui::GetMainViewport(); + const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->WorkPos); ImGui::SetNextWindowSize(viewport->WorkSize); ImGui::SetNextWindowViewport(viewport->ID); @@ -73,8 +85,8 @@ absl::Status Controller::OnLoad() { // Create DockSpace first ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); - gui::DockSpaceRenderer::BeginEnhancedDockSpace(dockspace_id, ImVec2(0.0f, 0.0f), - ImGuiDockNodeFlags_PassthruCentralNode); + gui::DockSpaceRenderer::BeginEnhancedDockSpace( + dockspace_id, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode); editor_manager_.DrawMenuBar(); // Draw the fixed menu bar at the top @@ -93,13 +105,14 @@ void Controller::DoRender() const { ImGui::Render(); renderer_->Clear(); - ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), - static_cast(renderer_->GetBackendRenderer())); + ImGui_ImplSDLRenderer2_RenderDrawData( + ImGui::GetDrawData(), + static_cast(renderer_->GetBackendRenderer())); renderer_->Present(); - + // Use TimingManager for accurate frame timing in sync with SDL float delta_time = TimingManager::Get().Update(); - + // Gentle frame rate cap to prevent excessive CPU usage // Only delay if we're rendering faster than 144 FPS (< 7ms per frame) if (delta_time < 0.007f) { @@ -107,11 +120,10 @@ void Controller::DoRender() const { } } -void Controller::OnExit() { +void Controller::OnExit() { renderer_->Shutdown(); PRINT_IF_ERROR(ShutdownWindow(window_)); } } // namespace core } // namespace yaze - diff --git a/src/app/core/controller.h b/src/app/core/controller.h index 9cd0dc76..08d28705 100644 --- a/src/app/core/controller.h +++ b/src/app/core/controller.h @@ -29,6 +29,9 @@ 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); auto window() -> SDL_Window* { return window_.window_.get(); } void set_active(bool active) { active_ = active; } diff --git a/src/app/editor/code/assembly_editor.cc b/src/app/editor/code/assembly_editor.cc index 10b9fb8e..82b795aa 100644 --- a/src/app/editor/code/assembly_editor.cc +++ b/src/app/editor/code/assembly_editor.cc @@ -181,11 +181,6 @@ absl::Status AssemblyEditor::Load() { // Note: Assembly editor uses dynamic file tabs, so we register the main editor window auto& card_manager = gui::EditorCardManager::Get(); - // The assembly editor itself acts as a card when shown - // Individual files are tabs within it, not separate cards - - printf("[AssemblyEditor] Assembly editor uses dynamic file tabs\n"); - return absl::OkStatus(); } diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.cc b/src/app/editor/dungeon/dungeon_canvas_viewer.cc index dc8a630a..02421745 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.cc +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.cc @@ -9,6 +9,7 @@ #include "app/zelda3/dungeon/room.h" #include "app/zelda3/sprite/sprite.h" #include "imgui/imgui.h" +#include "util/log.h" namespace yaze::editor { @@ -337,30 +338,30 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje // Room graphics management methods absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) { - printf("[LoadAndRender] START room_id=%d\n", room_id); + LOG_DEBUG("[LoadAndRender]", "START room_id=%d", room_id); if (room_id < 0 || room_id >= 128) { - printf("[LoadAndRender] ERROR: Invalid room ID\n"); + LOG_DEBUG("[LoadAndRender]", "ERROR: Invalid room ID"); return absl::InvalidArgumentError("Invalid room ID"); } if (!rom_ || !rom_->is_loaded()) { - printf("[LoadAndRender] ERROR: ROM not loaded\n"); + LOG_DEBUG("[LoadAndRender]", "ERROR: ROM not loaded"); return absl::FailedPreconditionError("ROM not loaded"); } if (!rooms_) { - printf("[LoadAndRender] ERROR: Room data not available\n"); + LOG_DEBUG("[LoadAndRender]", "ERROR: Room data not available"); return absl::FailedPreconditionError("Room data not available"); } auto& room = (*rooms_)[room_id]; - printf("[LoadAndRender] Got room reference\n"); + LOG_DEBUG("[LoadAndRender]", "Got room reference"); // Load room graphics with proper blockset - printf("[LoadAndRender] Loading graphics for blockset %d\n", room.blockset); + LOG_DEBUG("[LoadAndRender]", "Loading graphics for blockset %d", room.blockset); room.LoadRoomGraphics(room.blockset); - printf("[LoadAndRender] Graphics loaded\n"); + LOG_DEBUG("[LoadAndRender]", "Graphics loaded"); // Load the room's palette with bounds checking if (room.palette < rom_->paletteset_ids.size() && @@ -373,22 +374,22 @@ absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) { auto full_palette = rom_->palette_group().dungeon_main[current_palette_group_id_]; ASSIGN_OR_RETURN(current_palette_group_, gfx::CreatePaletteGroupFromLargePalette(full_palette)); - printf("[LoadAndRender] Palette loaded: group_id=%zu\n", current_palette_group_id_); + LOG_DEBUG("[LoadAndRender]", "Palette loaded: group_id=%zu", current_palette_group_id_); } } } // Render the room graphics to the graphics arena - printf("[LoadAndRender] Calling room.RenderRoomGraphics()...\n"); + LOG_DEBUG("[LoadAndRender]", "Calling room.RenderRoomGraphics()..."); room.RenderRoomGraphics(); - printf("[LoadAndRender] RenderRoomGraphics() complete\n"); + LOG_DEBUG("[LoadAndRender]", "RenderRoomGraphics() complete"); // Update the background layers with proper palette - printf("[LoadAndRender] Updating background layers...\n"); + LOG_DEBUG("[LoadAndRender]", "Updating background layers..."); RETURN_IF_ERROR(UpdateRoomBackgroundLayers(room_id)); - printf("[LoadAndRender] UpdateRoomBackgroundLayers() complete\n"); + LOG_DEBUG("[LoadAndRender]", "UpdateRoomBackgroundLayers() complete"); - printf("[LoadAndRender] SUCCESS\n"); + LOG_DEBUG("[LoadAndRender]", "SUCCESS"); return absl::OkStatus(); } @@ -475,10 +476,10 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) { // Only draw if texture was successfully created if (bg1_bitmap.texture()) { - printf("[RenderRoomBackgroundLayers] Drawing BG1 bitmap to canvas with texture %p\n", bg1_bitmap.texture()); + LOG_DEBUG("DungeonCanvasViewer", "Drawing BG1 bitmap to canvas with texture %p", bg1_bitmap.texture()); canvas_.DrawBitmap(bg1_bitmap, 0, 0, 1.0f, 255); } else { - printf("[RenderRoomBackgroundLayers] ERROR: BG1 bitmap has no texture!\n"); + LOG_DEBUG("DungeonCanvasViewer", "ERROR: BG1 bitmap has no texture!"); } } @@ -498,16 +499,16 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) { // 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(bg2_layer_type_, 4)]; - printf("[RenderRoomBackgroundLayers] Drawing BG2 bitmap to canvas with texture %p, alpha=%d\n", bg2_bitmap.texture(), alpha_value); + LOG_DEBUG("DungeonCanvasViewer", "Drawing BG2 bitmap to canvas with texture %p, alpha=%d", bg2_bitmap.texture(), alpha_value); canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, alpha_value); } else { - printf("[RenderRoomBackgroundLayers] ERROR: BG2 bitmap has no texture!\n"); + 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) { - printf("[RenderRoomBackgroundLayers] BG1 bitmap: %dx%d, active=%d, visible=%d, texture=%p\n", + LOG_DEBUG("DungeonCanvasViewer", "BG1 bitmap: %dx%d, active=%d, visible=%d, texture=%p", bg1_bitmap.width(), bg1_bitmap.height(), bg1_bitmap.is_active(), bg1_visible_, bg1_bitmap.texture()); // Check bitmap data content @@ -516,11 +517,11 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) { for (size_t i = 0; i < bg1_data.size(); i += 100) { // Sample every 100th pixel if (bg1_data[i] != 0) non_zero_pixels++; } - printf("[RenderRoomBackgroundLayers] BG1 bitmap data: %zu pixels, ~%d non-zero samples\n", + 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) { - printf("[RenderRoomBackgroundLayers] BG2 bitmap: %dx%d, active=%d, visible=%d, layer_type=%d, texture=%p\n", + 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(), bg2_visible_, bg2_layer_type_, bg2_bitmap.texture()); // Check bitmap data content @@ -529,7 +530,7 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) { for (size_t i = 0; i < bg2_data.size(); i += 100) { // Sample every 100th pixel if (bg2_data[i] != 0) non_zero_pixels++; } - printf("[RenderRoomBackgroundLayers] BG2 bitmap data: %zu pixels, ~%d non-zero samples\n", + LOG_DEBUG("DungeonCanvasViewer", "BG2 bitmap data: %zu pixels, ~%d non-zero samples", bg2_data.size(), non_zero_pixels); } @@ -542,9 +543,9 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) { IM_COL32(255, 0, 0, 255)); // Bright red // DEBUG: Show canvas and bitmap info - printf("[RenderRoomBackgroundLayers] Canvas pos: (%.1f, %.1f), Canvas size: (%.1f, %.1f)\n", + LOG_DEBUG("DungeonCanvasViewer", "Canvas pos: (%.1f, %.1f), Canvas size: (%.1f, %.1f)", canvas_pos.x, canvas_pos.y, canvas_.canvas_size().x, canvas_.canvas_size().y); - printf("[RenderRoomBackgroundLayers] BG1 bitmap size: %dx%d, BG2 bitmap size: %dx%d\n", + 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()); } diff --git a/src/app/editor/dungeon/dungeon_editor.cc b/src/app/editor/dungeon/dungeon_editor.cc index fd54c0c7..e206bbaa 100644 --- a/src/app/editor/dungeon/dungeon_editor.cc +++ b/src/app/editor/dungeon/dungeon_editor.cc @@ -70,9 +70,6 @@ void DungeonEditor::Initialize() { config.grid_size = 16; // 16x16 tiles object_editor_->SetConfig(config); } - - // Initialize manual renderer for debugging - printf("[DungeonEditor] Manual renderer initialized\n"); } absl::Status DungeonEditor::Load() { diff --git a/src/app/editor/dungeon/dungeon_editor_v2.cc b/src/app/editor/dungeon/dungeon_editor_v2.cc index ddfbe12e..b5460be5 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.cc +++ b/src/app/editor/dungeon/dungeon_editor_v2.cc @@ -10,6 +10,7 @@ #include "app/gui/icons.h" #include "app/gui/input.h" #include "imgui/imgui.h" +#include "util/log.h" namespace yaze::editor { @@ -97,8 +98,6 @@ void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) { .visibility_flag = &show_palette_editor_, .priority = 70 }); - - printf("[DungeonEditorV2] Registered 7 cards with EditorCardManager\n"); } void DungeonEditorV2::Initialize() {} @@ -214,7 +213,7 @@ absl::Status DungeonEditorV2::Save() { auto status = room.SaveObjects(); if (!status.ok()) { // Log error but continue with other rooms - std::printf("Failed to save room: %s\n", status.message().data()); + LOG_ERROR("DungeonEditorV2", "Failed to save room: %s", status.message().data()); } } diff --git a/src/app/editor/dungeon/dungeon_editor_v2.h b/src/app/editor/dungeon/dungeon_editor_v2.h index e081bc59..2530722c 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.h +++ b/src/app/editor/dungeon/dungeon_editor_v2.h @@ -87,6 +87,15 @@ class DungeonEditorV2 : public Editor { 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_control_panel_ = true; // Control panel (visible by default) + private: gfx::IRenderer* renderer_ = nullptr; // Simple UI layout @@ -119,16 +128,6 @@ class DungeonEditorV2 : public Editor { std::unordered_map> room_cards_; int current_room_id_ = 0; - // Card visibility flags - Start with only control panel visible - // Other cards hidden by default to prevent crash on ROM load - // User can open them via View menu or shortcuts - bool show_room_selector_ = false; - bool show_room_matrix_ = false; - bool show_entrances_list_ = false; - bool show_room_graphics_ = false; - bool show_object_editor_ = false; - bool show_palette_editor_ = false; - bool show_control_panel_ = true; // Only control panel visible on start bool control_panel_minimized_ = false; // Palette management diff --git a/src/app/editor/editor.h b/src/app/editor/editor.h index c220df60..389d27d9 100644 --- a/src/app/editor/editor.h +++ b/src/app/editor/editor.h @@ -51,6 +51,7 @@ struct EditorContext { }; enum class EditorType { + kUnknown, kAssembly, kDungeon, kEmulator, @@ -66,7 +67,8 @@ enum class EditorType { kSettings, }; -constexpr std::array kEditorNames = { +constexpr std::array kEditorNames = { + "Unknown", "Assembly", "Dungeon", "Emulator", "Graphics", "Music", "Overworld", "Palette", "Screen", "Sprite", "Message", "Hex", "Agent", "Settings", }; diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 0d422108..c7fd3f3b 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -13,6 +13,9 @@ #include "app/core/features.h" #include "app/core/timing.h" #include "util/file_util.h" +#include "app/gui/widgets/widget_id_registry.h" +#include "imgui/imgui.h" +#include "util/log.h" #include "util/platform_paths.h" #include "app/core/project.h" #include "app/editor/code/assembly_editor.h" @@ -81,126 +84,7 @@ std::string GetEditorName(EditorType type) { } // namespace -// Settings + preset helpers -void EditorManager::LoadUserSettings() { - auto config_dir = util::PlatformPaths::GetConfigDirectory(); - if (!config_dir.ok()) { - LOG_WARN("EditorManager", "Could not determine config directory for settings."); - return; - } - - std::string settings_path = (*config_dir / settings_filename_).string(); - try { - auto data = util::LoadFile(settings_path); - if (!data.empty()) { - std::istringstream ss(data); - std::string line; - while (std::getline(ss, line)) { - auto eq = line.find('='); - if (eq == std::string::npos) - continue; - auto key = line.substr(0, eq); - auto val = line.substr(eq + 1); - if (key == "font_global_scale") - font_global_scale_ = std::stof(val); - if (key == "autosave_enabled") - autosave_enabled_ = (val == "1"); - if (key == "autosave_interval_secs") - autosave_interval_secs_ = std::stof(val); - } - ImGui::GetIO().FontGlobalScale = font_global_scale_; - } - } catch (...) { - // Could not load file, just use defaults. - } -} - -void EditorManager::SaveUserSettings() { - std::ostringstream ss; - ss << "font_global_scale=" << font_global_scale_ << "\n"; - ss << "autosave_enabled=" << (autosave_enabled_ ? 1 : 0) << "\n"; - ss << "autosave_interval_secs=" << autosave_interval_secs_ << "\n"; - util::SaveFile(settings_filename_, ss.str()); -} - -void EditorManager::RefreshWorkspacePresets() { - // Safe clearing with error handling - try { - // Create a new vector instead of clearing to avoid corruption - std::vector new_presets; - - // Try to read a simple index file of presets - try { - auto config_dir = util::PlatformPaths::GetConfigDirectory(); - if (config_dir.ok()) { - std::string presets_path = (*config_dir / "workspace_presets.txt").string(); - auto data = util::LoadFile(presets_path); - if (!data.empty()) { - std::istringstream ss(data); - std::string name; - while (std::getline(ss, name)) { - // Trim whitespace and validate - name.erase(0, name.find_first_not_of(" \t\r\n")); - name.erase(name.find_last_not_of(" \t\r\n") + 1); - if (!name.empty() && - name.length() < 256) { // Reasonable length limit - new_presets.emplace_back(std::move(name)); - } - } - } - } - } catch (const std::exception& e) { - LOG_WARN("EditorManager", "Failed to load workspace presets: %s", e.what()); - } - - // Safely replace the vector - workspace_presets_ = std::move(new_presets); - workspace_presets_loaded_ = true; - - } catch (const std::exception& e) { - LOG_ERROR("EditorManager", "Error in RefreshWorkspacePresets: %s", e.what()); - // Ensure we have a valid empty vector - workspace_presets_ = std::vector(); - workspace_presets_loaded_ = - true; // Mark as loaded even if empty to avoid retry - } -} - -void EditorManager::SaveWorkspacePreset(const std::string& name) { - if (name.empty()) - return; - std::string ini_name = absl::StrCat("yaze_workspace_", name, ".ini"); - ImGui::SaveIniSettingsToDisk(ini_name.c_str()); - - // Ensure presets are loaded before updating - if (!workspace_presets_loaded_) { - RefreshWorkspacePresets(); - } - - // Update index - if (std::find(workspace_presets_.begin(), workspace_presets_.end(), name) == - workspace_presets_.end()) { - workspace_presets_.emplace_back(name); - try { - std::ostringstream ss; - for (const auto& n : workspace_presets_) - ss << n << "\n"; - util::SaveFile("workspace_presets.txt", ss.str()); - } catch (const std::exception& e) { - LOG_WARN("EditorManager", "Failed to save workspace presets: %s", e.what()); - } - } - last_workspace_preset_ = name; -} - -void EditorManager::LoadWorkspacePreset(const std::string& name) { - if (name.empty()) - return; - std::string ini_name = absl::StrCat("yaze_workspace_", name, ".ini"); - ImGui::LoadIniSettingsFromDisk(ini_name.c_str()); - last_workspace_preset_ = name; -} void EditorManager::InitializeTestSuites() { auto& test_manager = test::TestManager::Get(); @@ -424,6 +308,11 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file #endif // Load critical user settings first + status_ = user_settings_.Load(); + if (!status_.ok()) { + LOG_WARN("EditorManager", "Failed to load user settings: %s", status_.ToString().c_str()); + } + // Initialize welcome screen callbacks welcome_screen_.SetOpenRomCallback([this]() { status_ = LoadRom(); @@ -499,6 +388,7 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file } }); + // Load user settings - this must happen after context is initialized LoadUserSettings(); // Defer workspace presets loading to avoid initialization crashes @@ -682,7 +572,79 @@ void EditorManager::Initialize(gfx::IRenderer* renderer, const std::string& file "Maximize Window", ImGuiKey_F11, [this]() { MaximizeCurrentWindow(); }); } +void EditorManager::OpenEditorAndCardsFromFlags( + const std::string& editor_name, const std::string& cards_str) { + if (editor_name.empty()) { + return; + } + + LOG_INFO("EditorManager", "Processing startup flags: editor='%s', cards='%s'", + editor_name.c_str(), cards_str.c_str()); + + EditorType editor_type_to_open = EditorType::kUnknown; + for (int i = 0; i < static_cast(EditorType::kSettings); ++i) { + if (GetEditorName(static_cast(i)) == editor_name) { + editor_type_to_open = static_cast(i); + break; + } + } + + if (editor_type_to_open == EditorType::kUnknown) { + LOG_WARN("EditorManager", "Unknown editor specified via flag: %s", + editor_name.c_str()); + return; + } + + // Activate the main editor window + if (current_editor_set_) { + auto* editor = current_editor_set_->active_editors_[static_cast(editor_type_to_open)]; + if (editor) { + editor->set_active(true); + } + } + + // Handle specific cards for the Dungeon Editor + if (editor_type_to_open == EditorType::kDungeon && !cards_str.empty()) { + 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()); + + if (card_name == "Rooms List") { + current_editor_set_->dungeon_editor_.show_room_selector_ = true; + } else if (card_name == "Room Matrix") { + current_editor_set_->dungeon_editor_.show_room_matrix_ = true; + } else if (card_name == "Entrances List") { + current_editor_set_->dungeon_editor_.show_entrances_list_ = true; + } else if (card_name == "Room Graphics") { + current_editor_set_->dungeon_editor_.show_room_graphics_ = true; + } else if (card_name == "Object Editor") { + current_editor_set_->dungeon_editor_.show_object_editor_ = true; + } else if (card_name == "Palette Editor") { + current_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)); + current_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()); + } + } + } +} + absl::Status EditorManager::Update() { + // Update timing manager for accurate delta time across the application // This fixes animation timing issues that occur when mouse isn't moving core::TimingManager::Get().Update(); @@ -739,9 +701,9 @@ absl::Status EditorManager::Update() { } // Autosave timer - if (autosave_enabled_ && current_rom_ && current_rom_->dirty()) { + if (user_settings_.prefs().autosave_enabled && current_rom_ && current_rom_->dirty()) { autosave_timer_ += ImGui::GetIO().DeltaTime; - if (autosave_timer_ >= autosave_interval_secs_) { + if (autosave_timer_ >= user_settings_.prefs().autosave_interval) { autosave_timer_ = 0.0f; Rom::SaveSettings s; s.backup = true; @@ -2122,18 +2084,18 @@ void EditorManager::DrawMenuBar() { ImGuiWindowFlags_AlwaysAutoResize); // Lazy load workspace presets when UI is accessed - if (!workspace_presets_loaded_) { + if (!workspace_manager_.workspace_presets_loaded()) { RefreshWorkspacePresets(); } - for (const auto& name : workspace_presets_) { + for (const auto& name : workspace_manager_.workspace_presets()) { if (Selectable(name.c_str())) { LoadWorkspacePreset(name); toast_manager_.Show("Preset loaded", editor::ToastType::kSuccess); show_load_workspace_preset_ = false; } } - if (workspace_presets_.empty()) + if (workspace_manager_.workspace_presets().empty()) Text("No presets found"); End(); } @@ -2180,7 +2142,7 @@ absl::Status EditorManager::LoadRom() { current_editor_set_ = &target_session->editors; } else { // Create new session only if no empty ones exist - sessions_.emplace_back(std::move(temp_rom)); + sessions_.emplace_back(std::move(temp_rom), &user_settings_); RomSession& session = sessions_.back(); session.filepath = file_name; // Store filepath for duplicate detection @@ -2269,8 +2231,8 @@ absl::Status EditorManager::SaveRom() { SaveAllGraphicsData(*current_rom_, gfx::Arena::Get().gfx_sheets())); Rom::SaveSettings settings; - settings.backup = backup_rom_; - settings.save_new = save_new_auto_; + settings.backup = user_settings_.prefs().backup_rom; + settings.save_new = user_settings_.prefs().save_new_auto; return current_rom_->SaveToFile(settings); } @@ -2297,7 +2259,7 @@ absl::Status EditorManager::SaveRomAs(const std::string& filename) { // Create save settings with custom filename Rom::SaveSettings settings; - settings.backup = backup_rom_; + settings.backup = user_settings_.prefs().backup_rom; settings.save_new = false; // Don't auto-generate name, use provided filename settings.filename = filename; @@ -2331,7 +2293,7 @@ absl::Status EditorManager::OpenRomOrProject(const std::string& filename) { } else { Rom temp_rom; RETURN_IF_ERROR(temp_rom.LoadFromFile(filename)); - sessions_.emplace_back(std::move(temp_rom)); + sessions_.emplace_back(std::move(temp_rom), &user_settings_); RomSession& session = sessions_.back(); for (auto* editor : session.editors.active_editors_) { editor->set_context(&context_); @@ -2394,7 +2356,7 @@ absl::Status EditorManager::OpenProject() { } } - sessions_.emplace_back(std::move(temp_rom)); + sessions_.emplace_back(std::move(temp_rom), &user_settings_); RomSession& session = sessions_.back(); for (auto* editor : session.editors.active_editors_) { editor->set_context(&context_); @@ -2427,11 +2389,11 @@ absl::Status EditorManager::OpenProject() { } // Apply workspace settings - font_global_scale_ = current_project_.workspace_settings.font_global_scale; - autosave_enabled_ = current_project_.workspace_settings.autosave_enabled; - autosave_interval_secs_ = + user_settings_.prefs().font_global_scale = current_project_.workspace_settings.font_global_scale; + user_settings_.prefs().autosave_enabled = current_project_.workspace_settings.autosave_enabled; + user_settings_.prefs().autosave_interval = current_project_.workspace_settings.autosave_interval_secs; - ImGui::GetIO().FontGlobalScale = font_global_scale_; + ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale; // Add to recent files auto& manager = core::RecentFilesManager::GetInstance(); @@ -2457,10 +2419,10 @@ absl::Status EditorManager::SaveProject() { current_project_.feature_flags = sessions_[session_idx].feature_flags; } - current_project_.workspace_settings.font_global_scale = font_global_scale_; - current_project_.workspace_settings.autosave_enabled = autosave_enabled_; + current_project_.workspace_settings.font_global_scale = user_settings_.prefs().font_global_scale; + current_project_.workspace_settings.autosave_enabled = user_settings_.prefs().autosave_enabled; current_project_.workspace_settings.autosave_interval_secs = - autosave_interval_secs_; + user_settings_.prefs().autosave_interval; // Save recent files auto& manager = core::RecentFilesManager::GetInstance(); @@ -2578,6 +2540,9 @@ void EditorManager::CreateNewSession() { // Create a blank session sessions_.emplace_back(); RomSession& session = sessions_.back(); + + // Set user settings for the blank session + session.editors.set_user_settings(&user_settings_); // Wire editor contexts for new session for (auto* editor : session.editors.active_editors_) { @@ -2606,7 +2571,7 @@ void EditorManager::DuplicateCurrentSession() { // Create a copy of the current ROM Rom rom_copy = *current_rom_; - sessions_.emplace_back(std::move(rom_copy)); + sessions_.emplace_back(std::move(rom_copy), &user_settings_); RomSession& session = sessions_.back(); // Wire editor contexts @@ -3282,11 +3247,11 @@ void EditorManager::DrawLayoutPresets() { ImGui::Text("%s Custom Presets", ICON_MD_BOOKMARK); // Lazy load workspace presets when UI is accessed - if (!workspace_presets_loaded_) { + if (!workspace_manager_.workspace_presets_loaded()) { RefreshWorkspacePresets(); } - for (const auto& preset : workspace_presets_) { + for (const auto& preset : workspace_manager_.workspace_presets()) { if (ImGui::Button( absl::StrFormat("%s %s", ICON_MD_BOOKMARK, preset.c_str()) .c_str(), @@ -3298,7 +3263,7 @@ void EditorManager::DrawLayoutPresets() { } } - if (workspace_presets_.empty()) { + if (workspace_manager_.workspace_presets().empty()) { ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "No custom presets saved"); } @@ -3330,26 +3295,6 @@ void EditorManager::RenameSession(size_t index, const std::string& new_name) { } } -std::string EditorManager::GenerateUniqueEditorTitle(EditorType type, - size_t session_index) { - std::string base_name = GetEditorName(type); - - if (sessions_.size() <= 1) { - return base_name; // No need for session identifier with single session - } - - // Add session identifier - const auto& session = sessions_[session_index]; - std::string session_name = session.GetDisplayName(); - - // Truncate long session names - if (session_name.length() > 15) { - session_name = session_name.substr(0, 12) + "..."; - } - - return absl::StrFormat("%s (%s)", base_name.c_str(), session_name.c_str()); -} - void EditorManager::DrawSessionRenameDialog() { if (!show_session_rename_dialog_) return; @@ -3438,5 +3383,27 @@ void EditorManager::SwitchToEditor(EditorType editor_type) { } } +// ============================================================================ +// User Settings Management +// ============================================================================ + +void EditorManager::LoadUserSettings() { + // Apply font scale after loading + ImGui::GetIO().FontGlobalScale = user_settings_.prefs().font_global_scale; + + // Apply welcome screen preference + if (!user_settings_.prefs().show_welcome_on_startup) { + show_welcome_screen_ = false; + welcome_screen_manually_closed_ = true; + } +} + +void EditorManager::SaveUserSettings() { + auto status = user_settings_.Save(); + if (!status.ok()) { + LOG_WARN("EditorManager", "Failed to save user settings: %s", status.ToString().c_str()); + } +} + } // namespace editor } // namespace yaze diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index 0147223f..1ae6e741 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -1,6 +1,8 @@ #ifndef YAZE_APP_EDITOR_EDITOR_MANAGER_H #define YAZE_APP_EDITOR_EDITOR_MANAGER_H +#include "editor/system/user_settings.h" +#include "editor/ui/workspace_manager.h" #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui/imgui.h" @@ -51,7 +53,7 @@ namespace editor { */ class EditorSet { public: - explicit EditorSet(Rom* rom = nullptr) + explicit EditorSet(Rom* rom = nullptr, UserSettings* user_settings = nullptr) : assembly_editor_(rom), dungeon_editor_(rom), graphics_editor_(rom), @@ -60,7 +62,7 @@ class EditorSet { palette_editor_(rom), screen_editor_(rom), sprite_editor_(rom), - settings_editor_(rom), + settings_editor_(rom, user_settings), message_editor_(rom), memory_editor_(rom) { active_editors_ = {&overworld_editor_, &dungeon_editor_, &graphics_editor_, @@ -68,6 +70,10 @@ class EditorSet { &music_editor_, &screen_editor_, &settings_editor_, &assembly_editor_}; } + + void set_user_settings(UserSettings* settings) { + settings_editor_.set_user_settings(settings); + } AssemblyEditor assembly_editor_; DungeonEditorV2 dungeon_editor_; @@ -97,7 +103,7 @@ class EditorSet { */ class EditorManager { public: - EditorManager() { + EditorManager() : blank_editor_set_(nullptr, &user_settings_) { std::stringstream ss; ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "." << YAZE_VERSION_PATCH; @@ -106,6 +112,10 @@ class EditorManager { } void Initialize(gfx::IRenderer* renderer, const std::string& filename = ""); + + // Processes startup flags to open a specific editor and cards. + void OpenEditorAndCardsFromFlags(const std::string& editor_name, + const std::string& cards_str); absl::Status Update(); void DrawMenuBar(); @@ -118,6 +128,9 @@ class EditorManager { auto GetCurrentEditorSet() -> EditorSet* { return current_editor_set_; } auto overworld() -> yaze::zelda3::Overworld* { return ¤t_editor_set_->overworld_editor_.overworld(); } + // Session management helpers + size_t GetCurrentSessionIndex() const; + // Get current session's feature flags (falls back to global if no session) core::FeatureFlags::Flags* GetCurrentFeatureFlags() { size_t current_index = GetCurrentSessionIndex(); @@ -128,29 +141,71 @@ class EditorManager { } void SetFontGlobalScale(float scale) { - font_global_scale_ = scale; + user_settings_.prefs().font_global_scale = scale; ImGui::GetIO().FontGlobalScale = scale; SaveUserSettings(); } void BuildModernMenu(); + // User settings helpers + void LoadUserSettings(); + void SaveUserSettings(); + + // Workspace management (delegates to WorkspaceManager) + void RefreshWorkspacePresets() { workspace_manager_.RefreshPresets(); } + void SaveWorkspacePreset(const std::string& name) { workspace_manager_.SaveWorkspacePreset(name); } + void LoadWorkspacePreset(const std::string& name) { workspace_manager_.LoadWorkspacePreset(name); } + // Jump-to functionality for cross-editor navigation void JumpToDungeonRoom(int room_id); void JumpToOverworldMap(int map_id); void SwitchToEditor(EditorType editor_type); + + // Session management + void CreateNewSession(); + void DuplicateCurrentSession(); + void CloseCurrentSession(); + void RemoveSession(size_t index); + void SwitchToSession(size_t index); + size_t GetActiveSessionCount() const; + + // Workspace layout management + void SaveWorkspaceLayout(); + void LoadWorkspaceLayout(); + void ResetWorkspaceLayout(); + void ShowAllWindows(); + void HideAllWindows(); + void MaximizeCurrentWindow(); + void RestoreAllWindows(); + void CloseAllFloatingWindows(); + + // Layout presets + void LoadDeveloperLayout(); + void LoadDesignerLayout(); + void LoadModderLayout(); + + // Helper methods + std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const; + bool HasDuplicateSession(const std::string& filepath); + void RenameSession(size_t index, const std::string& new_name); private: void DrawWelcomeScreen(); absl::Status DrawRomSelector(); void DrawContextSensitiveCardControl(); // Card control for current editor + void DrawSessionSwitcher(); + void DrawSessionManager(); + void DrawLayoutPresets(); + void DrawSessionRenameDialog(); + absl::Status LoadRom(); absl::Status LoadAssets(); absl::Status SaveRom(); absl::Status SaveRomAs(const std::string& filename); absl::Status OpenRomOrProject(const std::string& filename); - // Enhanced project management + // Project and session management absl::Status CreateNewProject( const std::string& template_name = "Basic ROM Hack"); absl::Status OpenProject(); @@ -164,11 +219,6 @@ class EditorManager { void InitializeTestSuites(); bool quit_ = false; - bool backup_rom_ = false; - bool save_new_auto_ = true; - bool autosave_enabled_ = false; - float autosave_interval_secs_ = 120.0f; - float autosave_timer_ = 0.0f; bool new_project_menu = false; bool show_emulator_ = false; @@ -226,13 +276,6 @@ class EditorManager { #endif std::string version_ = ""; - std::string settings_filename_ = "settings.ini"; - float font_global_scale_ = 1.0f; - std::vector workspace_presets_; - std::string last_workspace_preset_ = ""; - std::string status_message_ = ""; - bool workspace_presets_loaded_ = false; - absl::Status status_; emu::Emulator emulator_; @@ -244,7 +287,8 @@ class EditorManager { core::FeatureFlags::Flags feature_flags; // Per-session feature flags RomSession() = default; - explicit RomSession(Rom&& r) : rom(std::move(r)), editors(&rom) { + explicit RomSession(Rom&& r, UserSettings* user_settings = nullptr) + : rom(std::move(r)), editors(&rom, user_settings) { filepath = rom.filename(); // Initialize with default feature flags feature_flags = core::FeatureFlags::Flags{}; @@ -272,49 +316,10 @@ class EditorManager { std::unique_ptr popup_manager_; ToastManager toast_manager_; MenuBuilder menu_builder_; - - // Settings helpers - void LoadUserSettings(); - void SaveUserSettings(); - - void RefreshWorkspacePresets(); - void SaveWorkspacePreset(const std::string& name); - void LoadWorkspacePreset(const std::string& name); - - // Workspace management - void CreateNewSession(); - void DuplicateCurrentSession(); - void CloseCurrentSession(); - void RemoveSession(size_t index); - void SwitchToSession(size_t index); - size_t GetCurrentSessionIndex() const; - size_t GetActiveSessionCount() const; - void ResetWorkspaceLayout(); - - // Multi-session editor management - std::string GenerateUniqueEditorTitle(EditorType type, - size_t session_index) const; - void SaveWorkspaceLayout(); - void LoadWorkspaceLayout(); - void ShowAllWindows(); - void HideAllWindows(); - void MaximizeCurrentWindow(); - void RestoreAllWindows(); - void CloseAllFloatingWindows(); - void LoadDeveloperLayout(); - void LoadDesignerLayout(); - void LoadModderLayout(); - - // Session management helpers - bool HasDuplicateSession(const std::string& filepath); - void RenameSession(size_t index, const std::string& new_name); - std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index); - - // UI drawing helpers - void DrawSessionSwitcher(); - void DrawSessionManager(); - void DrawLayoutPresets(); - void DrawSessionRenameDialog(); + UserSettings user_settings_; + WorkspaceManager workspace_manager_{&toast_manager_}; + + float autosave_timer_ = 0.0f; }; } // namespace editor diff --git a/src/app/editor/graphics/graphics_editor.cc b/src/app/editor/graphics/graphics_editor.cc index 0c18e0e9..72a557a7 100644 --- a/src/app/editor/graphics/graphics_editor.cc +++ b/src/app/editor/graphics/graphics_editor.cc @@ -85,8 +85,6 @@ void GraphicsEditor::Initialize() { .visibility_flag = &show_prototype_viewer_, .priority = 40 }); - - printf("[GraphicsEditor] Registered 4 cards with EditorCardManager\n"); } absl::Status GraphicsEditor::Load() { diff --git a/src/app/editor/graphics/palette_editor.cc b/src/app/editor/graphics/palette_editor.cc index 55f3a19b..2c3402c8 100644 --- a/src/app/editor/graphics/palette_editor.cc +++ b/src/app/editor/graphics/palette_editor.cc @@ -187,9 +187,6 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) { } void PaletteEditor::Initialize() { - // PaletteEditor uses tabs within a single window, not separate cards - // So we don't register individual cards - the whole editor is one unit - printf("[PaletteEditor] No cards to register (uses internal tabs)\n"); } absl::Status PaletteEditor::Load() { diff --git a/src/app/editor/graphics/screen_editor.cc b/src/app/editor/graphics/screen_editor.cc index ae7369ac..fa1e4616 100644 --- a/src/app/editor/graphics/screen_editor.cc +++ b/src/app/editor/graphics/screen_editor.cc @@ -82,8 +82,6 @@ void ScreenEditor::Initialize() { .visibility_flag = &show_naming_screen_, .priority = 50 }); - - printf("[ScreenEditor] Registered 5 cards with EditorCardManager\n"); } absl::Status ScreenEditor::Load() { diff --git a/src/app/editor/message/message_editor.cc b/src/app/editor/message/message_editor.cc index 242d679a..e4799464 100644 --- a/src/app/editor/message/message_editor.cc +++ b/src/app/editor/message/message_editor.cc @@ -100,8 +100,6 @@ void MessageEditor::Initialize() { .priority = 40 }); - printf("[MessageEditor] Registered 4 cards with EditorCardManager\n"); - for (int i = 0; i < kWidthArraySize; i++) { message_preview_.width_array[i] = rom()->data()[kCharactersWidth + i]; } diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 43415160..30e6626b 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -120,8 +120,6 @@ void OverworldEditor::Initialize() { .priority = 70 }); - printf("[OverworldEditor] Registered 7 cards with EditorCardManager\n"); - // Original initialization code below: // Initialize MapPropertiesSystem with canvas and bitmap data map_properties_system_ = std::make_unique( diff --git a/src/app/editor/sprite/sprite_editor.cc b/src/app/editor/sprite/sprite_editor.cc index a7c8bbcd..899f407c 100644 --- a/src/app/editor/sprite/sprite_editor.cc +++ b/src/app/editor/sprite/sprite_editor.cc @@ -47,8 +47,6 @@ void SpriteEditor::Initialize() { .visibility_flag = &show_custom_editor_, .priority = 20 }); - - printf("[SpriteEditor] Registered 2 cards with EditorCardManager\n"); } absl::Status SpriteEditor::Load() { diff --git a/src/app/editor/system/settings_editor.cc b/src/app/editor/system/settings_editor.cc index 24afdd5b..4fb82dbe 100644 --- a/src/app/editor/system/settings_editor.cc +++ b/src/app/editor/system/settings_editor.cc @@ -162,60 +162,81 @@ void SettingsEditor::DrawThemeSettings() { 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)) { - static bool autosave_enabled = true; - Checkbox("Enable Auto-Save", &autosave_enabled); + if (Checkbox("Enable Auto-Save", &user_settings_->prefs().autosave_enabled)) { + user_settings_->Save(); + } - if (autosave_enabled) { - static int autosave_interval = 300; - SliderInt("Interval (seconds)", &autosave_interval, 60, 600); + if (user_settings_->prefs().autosave_enabled) { + 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_->Save(); + } - static bool backup_before_save = true; - Checkbox("Create Backup Before Save", &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")) { - static int recent_files_limit = 10; - SliderInt("Recent Files Limit", &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:"); - static int default_editor = 0; const char* editors[] = { "None", "Overworld", "Dungeon", "Graphics" }; - Combo("##DefaultEditor", &default_editor, editors, IM_ARRAYSIZE(editors)); + if (Combo("##DefaultEditor", &user_settings_->prefs().default_editor, editors, IM_ARRAYSIZE(editors))) { + user_settings_->Save(); + } } } 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)) { - static bool vsync = true; - Checkbox("V-Sync", &vsync); + if (Checkbox("V-Sync", &user_settings_->prefs().vsync)) { + user_settings_->Save(); + } - static int target_fps = 60; - SliderInt("Target FPS", &target_fps, 30, 144); + if (SliderInt("Target FPS", &user_settings_->prefs().target_fps, 30, 144)) { + user_settings_->Save(); + } } // Memory settings if (CollapsingHeader(ICON_MD_MEMORY " Memory")) { - static int cache_size = 512; - SliderInt("Cache Size (MB)", &cache_size, 128, 2048); + if (SliderInt("Cache Size (MB)", &user_settings_->prefs().cache_size_mb, 128, 2048)) { + user_settings_->Save(); + } - static int undo_size = 50; - SliderInt("Undo History", &undo_size, 10, 200); + if (SliderInt("Undo History", &user_settings_->prefs().undo_history_size, 10, 200)) { + user_settings_->Save(); + } } Separator(); @@ -226,46 +247,67 @@ 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)) { - static int provider = 0; const char* providers[] = { "Ollama (Local)", "Gemini (Cloud)", "Mock (Testing)" }; - Combo("Provider", &provider, providers, IM_ARRAYSIZE(providers)); + if (Combo("Provider", &user_settings_->prefs().ai_provider, providers, IM_ARRAYSIZE(providers))) { + user_settings_->Save(); + } Spacing(); - if (provider == 0) { // Ollama - static char ollama_url[256] = "http://localhost:11434"; - InputText("URL", ollama_url, IM_ARRAYSIZE(ollama_url)); - } else if (provider == 1) { // Gemini - static char api_key[128] = ""; - InputText("API Key", api_key, IM_ARRAYSIZE(api_key), ImGuiInputTextFlags_Password); + 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); + url_buffer[sizeof(url_buffer) - 1] = '\0'; + if (InputText("URL", url_buffer, IM_ARRAYSIZE(url_buffer))) { + user_settings_->prefs().ollama_url = url_buffer; + user_settings_->Save(); + } + } 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); + key_buffer[sizeof(key_buffer) - 1] = '\0'; + 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")) { - static float temperature = 0.7f; - SliderFloat("Temperature", &temperature, 0.0f, 2.0f); + if (SliderFloat("Temperature", &user_settings_->prefs().ai_temperature, 0.0f, 2.0f)) { + user_settings_->Save(); + } TextDisabled("Higher = more creative"); - static int max_tokens = 2048; - SliderInt("Max Tokens", &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")) { - static bool proactive = true; - Checkbox("Proactive Suggestions", &proactive); + if (Checkbox("Proactive Suggestions", &user_settings_->prefs().ai_proactive)) { + user_settings_->Save(); + } - static bool auto_learn = true; - Checkbox("Auto-Learn Preferences", &auto_learn); + if (Checkbox("Auto-Learn Preferences", &user_settings_->prefs().ai_auto_learn)) { + user_settings_->Save(); + } - static bool multimodal = true; - Checkbox("Enable Vision/Multimodal", &multimodal); + if (Checkbox("Enable Vision/Multimodal", &user_settings_->prefs().ai_multimodal)) { + user_settings_->Save(); + } } // z3ed CLI logging settings @@ -273,21 +315,12 @@ void SettingsEditor::DrawAIAgentSettings() { Text("Configure z3ed command-line logging behavior"); Spacing(); - // Declare all static variables first - static int log_level = 1; // 0=Debug, 1=Info, 2=Warning, 3=Error, 4=Fatal - static bool log_to_file = false; - static char log_file_path[512] = ""; - static bool log_ai_requests = true; - static bool log_rom_operations = true; - static bool log_gui_automation = true; - static bool log_proposals = true; - // Log level selection const char* log_levels[] = { "Debug (Verbose)", "Info (Normal)", "Warning (Quiet)", "Error (Critical)", "Fatal Only" }; - if (Combo("Log Level", &log_level, log_levels, IM_ARRAYSIZE(log_levels))) { + 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 (log_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; @@ -298,13 +331,14 @@ void SettingsEditor::DrawAIAgentSettings() { // Get current categories std::set categories; - if (log_ai_requests) categories.insert("AI"); - if (log_rom_operations) categories.insert("ROM"); - if (log_gui_automation) categories.insert("GUI"); - if (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, std::string(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"); @@ -312,36 +346,41 @@ void SettingsEditor::DrawAIAgentSettings() { Spacing(); // Logging targets - - if (Checkbox("Log to File", &log_to_file)) { - if (log_to_file) { + if (Checkbox("Log to File", &user_settings_->prefs().log_to_file)) { + if (user_settings_->prefs().log_to_file) { // Set default path if empty - if (strlen(log_file_path) == 0) { + if (user_settings_->prefs().log_file_path.empty()) { const char* home = std::getenv("HOME"); if (home) { - snprintf(log_file_path, sizeof(log_file_path), "%s/.yaze/logs/yaze.log", home); + user_settings_->prefs().log_file_path = std::string(home) + "/.yaze/logs/yaze.log"; } } // Enable file logging std::set categories; - util::LogLevel level = static_cast(log_level); - util::LogManager::instance().configure(level, std::string(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(log_level); + util::LogLevel level = static_cast(user_settings_->prefs().log_level); util::LogManager::instance().configure(level, "", categories); } + user_settings_->Save(); } - if (log_to_file) { + if (user_settings_->prefs().log_to_file) { Indent(); - if (InputText("Log File", log_file_path, IM_ARRAYSIZE(log_file_path))) { + char path_buffer[512]; + 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(log_level); - util::LogManager::instance().configure(level, std::string(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)"); @@ -358,40 +397,43 @@ void SettingsEditor::DrawAIAgentSettings() { bool categories_changed = false; - categories_changed |= Checkbox("AI API Requests", &log_ai_requests); - categories_changed |= Checkbox("ROM Operations", &log_rom_operations); - categories_changed |= Checkbox("GUI Automation", &log_gui_automation); - categories_changed |= Checkbox("Proposal Generation", &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 (log_ai_requests) categories.insert("AI"); - if (log_rom_operations) categories.insert("ROM"); - if (log_gui_automation) categories.insert("GUI"); - if (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(log_level); - util::LogManager::instance().configure(level, log_to_file ? std::string(log_file_path) : "", categories); + 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 (log_to_file && strlen(log_file_path) > 0) { - std::filesystem::path path(log_file_path); + 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", log_file_path); + 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 (log_to_file && strlen(log_file_path) > 0) { - std::filesystem::path path(log_file_path); + 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 diff --git a/src/app/editor/system/settings_editor.h b/src/app/editor/system/settings_editor.h index ae8e97ea..333f479a 100644 --- a/src/app/editor/system/settings_editor.h +++ b/src/app/editor/system/settings_editor.h @@ -4,6 +4,7 @@ #include "absl/status/status.h" #include "app/editor/editor.h" #include "app/rom.h" +#include "editor/system/user_settings.h" #include "imgui/imgui.h" namespace yaze { @@ -207,7 +208,8 @@ static void ShowExampleAppPropertyEditor(bool* p_open) { class SettingsEditor : public Editor { public: - explicit SettingsEditor(Rom* rom = nullptr) : rom_(rom) { + explicit SettingsEditor(Rom* rom = nullptr, UserSettings* user_settings = nullptr) + : rom_(rom), user_settings_(user_settings) { type_ = EditorType::kSettings; } @@ -215,6 +217,8 @@ class SettingsEditor : public Editor { 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"); } absl::Status Paste() override { return absl::UnimplementedError("Paste"); } @@ -232,6 +236,7 @@ class SettingsEditor : public Editor { private: Rom* rom_; + UserSettings* user_settings_; void DrawGeneralSettings(); void DrawKeyboardShortcuts(); void DrawThemeSettings(); diff --git a/src/app/editor/system/user_settings.cc b/src/app/editor/system/user_settings.cc index c5672391..bf64b6d4 100644 --- a/src/app/editor/system/user_settings.cc +++ b/src/app/editor/system/user_settings.cc @@ -1,24 +1,177 @@ #include "app/editor/system/user_settings.h" + #include +#include + +#include "absl/strings/str_format.h" +#include "app/gui/style.h" +#include "imgui/imgui.h" #include "util/file_util.h" +#include "util/log.h" +#include "util/platform_paths.h" namespace yaze { namespace editor { UserSettings::UserSettings() { - // TODO: Get platform-specific settings path - settings_file_path_ = "yaze_settings.json"; + auto config_dir_status = util::PlatformPaths::GetConfigDirectory(); + 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."); + settings_file_path_ = "yaze_settings.ini"; + } } absl::Status UserSettings::Load() { - // TODO: Load from JSON file + try { + auto data = util::LoadFile(settings_file_path_); + if (data.empty()) { + 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; + + std::string key = line.substr(0, eq_pos); + std::string val = line.substr(eq_pos + 1); + + // General + if (key == "font_global_scale") { + prefs_.font_global_scale = std::stof(val); + } else if (key == "backup_rom") { + prefs_.backup_rom = (val == "1"); + } else if (key == "save_new_auto") { + prefs_.save_new_auto = (val == "1"); + } else if (key == "autosave_enabled") { + prefs_.autosave_enabled = (val == "1"); + } else if (key == "autosave_interval") { + prefs_.autosave_interval = std::stof(val); + } else if (key == "recent_files_limit") { + prefs_.recent_files_limit = std::stoi(val); + } else if (key == "last_rom_path") { + prefs_.last_rom_path = val; + } else if (key == "last_project_path") { + prefs_.last_project_path = val; + } else if (key == "show_welcome_on_startup") { + prefs_.show_welcome_on_startup = (val == "1"); + } else if (key == "restore_last_session") { + prefs_.restore_last_session = (val == "1"); + } + // Editor Behavior + else if (key == "backup_before_save") { + prefs_.backup_before_save = (val == "1"); + } else if (key == "default_editor") { + prefs_.default_editor = std::stoi(val); + } + // Performance + else if (key == "vsync") { + prefs_.vsync = (val == "1"); + } else if (key == "target_fps") { + prefs_.target_fps = std::stoi(val); + } else if (key == "cache_size_mb") { + prefs_.cache_size_mb = std::stoi(val); + } else if (key == "undo_history_size") { + prefs_.undo_history_size = std::stoi(val); + } + // AI Agent + else if (key == "ai_provider") { + prefs_.ai_provider = std::stoi(val); + } else if (key == "ollama_url") { + prefs_.ollama_url = val; + } else if (key == "gemini_api_key") { + prefs_.gemini_api_key = val; + } else if (key == "ai_temperature") { + prefs_.ai_temperature = std::stof(val); + } else if (key == "ai_max_tokens") { + prefs_.ai_max_tokens = std::stoi(val); + } else if (key == "ai_proactive") { + prefs_.ai_proactive = (val == "1"); + } else if (key == "ai_auto_learn") { + prefs_.ai_auto_learn = (val == "1"); + } else if (key == "ai_multimodal") { + prefs_.ai_multimodal = (val == "1"); + } + // CLI Logging + else if (key == "log_level") { + prefs_.log_level = std::stoi(val); + } else if (key == "log_to_file") { + prefs_.log_to_file = (val == "1"); + } else if (key == "log_file_path") { + prefs_.log_file_path = val; + } else if (key == "log_ai_requests") { + prefs_.log_ai_requests = (val == "1"); + } else if (key == "log_rom_operations") { + prefs_.log_rom_operations = (val == "1"); + } else if (key == "log_gui_automation") { + prefs_.log_gui_automation = (val == "1"); + } else if (key == "log_proposals") { + prefs_.log_proposals = (val == "1"); + } + } + ImGui::GetIO().FontGlobalScale = prefs_.font_global_scale; + } catch (const std::exception& e) { + return absl::InternalError( + absl::StrFormat("Failed to load user settings: %s", e.what())); + } return absl::OkStatus(); } absl::Status UserSettings::Save() { - // TODO: Save to JSON file + try { + std::ostringstream ss; + // General + ss << "font_global_scale=" << prefs_.font_global_scale << "\n"; + ss << "backup_rom=" << (prefs_.backup_rom ? 1 : 0) << "\n"; + ss << "save_new_auto=" << (prefs_.save_new_auto ? 1 : 0) << "\n"; + ss << "autosave_enabled=" << (prefs_.autosave_enabled ? 1 : 0) << "\n"; + ss << "autosave_interval=" << prefs_.autosave_interval << "\n"; + 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"; + + // 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"; + ss << "gemini_api_key=" << prefs_.gemini_api_key << "\n"; + ss << "ai_temperature=" << prefs_.ai_temperature << "\n"; + ss << "ai_max_tokens=" << prefs_.ai_max_tokens << "\n"; + 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"; + ss << "log_file_path=" << prefs_.log_file_path << "\n"; + ss << "log_ai_requests=" << (prefs_.log_ai_requests ? 1 : 0) << "\n"; + 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( + absl::StrFormat("Failed to save user settings: %s", e.what())); + } return absl::OkStatus(); } } // namespace editor } // namespace yaze + diff --git a/src/app/editor/system/user_settings.h b/src/app/editor/system/user_settings.h index d944b7e7..881d1993 100644 --- a/src/app/editor/system/user_settings.h +++ b/src/app/editor/system/user_settings.h @@ -13,6 +13,7 @@ namespace editor { class UserSettings { public: struct Preferences { + // General float font_global_scale = 1.0f; bool backup_rom = false; bool save_new_auto = true; @@ -23,6 +24,35 @@ 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"; + std::string gemini_api_key; + float ai_temperature = 0.7f; + int ai_max_tokens = 2048; + 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; + std::string log_file_path; + bool log_ai_requests = true; + bool log_rom_operations = true; + bool log_gui_automation = true; + bool log_proposals = true; }; UserSettings(); diff --git a/src/app/editor/ui/menu_manager.cc b/src/app/editor/ui/menu_manager.cc new file mode 100644 index 00000000..07d19e45 --- /dev/null +++ b/src/app/editor/ui/menu_manager.cc @@ -0,0 +1,76 @@ +#include "app/editor/ui/menu_manager.h" + +#include "app/editor/editor_manager.h" +#include "app/gui/icons.h" +#include "absl/strings/str_format.h" + +namespace yaze { +namespace editor { + +MenuManager::MenuManager(EditorManager* editor_manager) + : editor_manager_(editor_manager) {} + +void MenuManager::BuildAndDraw() { + if (ImGui::BeginMenuBar()) { + editor_manager_->BuildModernMenu(); // This contains the menu_builder_ logic + editor_manager_->menu_builder_.Draw(); + + // This is the logic from the second half of DrawMenuBar + auto* current_rom = editor_manager_->GetCurrentRom(); + std::string version_text = absl::StrFormat("v%s", editor_manager_->version().c_str()); + float version_width = ImGui::CalcTextSize(version_text.c_str()).x; + float session_rom_area_width = 280.0f; + ImGui::SameLine(ImGui::GetWindowWidth() - version_width - 10 - session_rom_area_width); + + if (editor_manager_->GetActiveSessionCount() > 1) { + if (ImGui::SmallButton(absl::StrFormat("%s%zu", ICON_MD_TAB, editor_manager_->GetActiveSessionCount()).c_str())) { + editor_manager_->show_session_switcher_ = true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Sessions: %zu active\nClick to switch", editor_manager_->GetActiveSessionCount()); + } + ImGui::SameLine(); + } + + if (current_rom && current_rom->is_loaded()) { + if (ImGui::SmallButton(ICON_MD_APPS)) { + editor_manager_->show_editor_selection_ = true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(ICON_MD_DASHBOARD " Editor Selection (Ctrl+E)"); + } + ImGui::SameLine(); + if (ImGui::SmallButton(ICON_MD_DISPLAY_SETTINGS)) { + editor_manager_->popup_manager_->Show("Display Settings"); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(ICON_MD_TUNE " Display Settings"); + } + ImGui::SameLine(); + ImGui::Separator(); + ImGui::SameLine(); + } + + if (current_rom && current_rom->is_loaded()) { + std::string rom_display = current_rom->title(); + if (rom_display.length() > 22) { + rom_display = rom_display.substr(0, 19) + "..."; + } + ImVec4 status_color = current_rom->dirty() ? ImVec4(1.0f, 0.5f, 0.0f, 1.0f) : ImVec4(0.0f, 0.8f, 0.0f, 1.0f); + if (ImGui::SmallButton(absl::StrFormat("%s%s", rom_display.c_str(), current_rom->dirty() ? "*" : "").c_str())) { + ImGui::OpenPopup("ROM Details"); + } + // ... (rest of the popup logic from DrawMenuBar) + } else { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No ROM"); + ImGui::SameLine(); + } + + ImGui::SameLine(ImGui::GetWindowWidth() - version_width - 10); + ImGui::Text("%s", version_text.c_str()); + ImGui::EndMenuBar(); + } +} + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/ui/menu_manager.h b/src/app/editor/ui/menu_manager.h new file mode 100644 index 00000000..fa09a1a1 --- /dev/null +++ b/src/app/editor/ui/menu_manager.h @@ -0,0 +1,25 @@ +#ifndef YAZE_APP_EDITOR_UI_MENU_MANAGER_H_ +#define YAZE_APP_EDITOR_UI_MENU_MANAGER_H_ + +#include "app/editor/ui/menu_builder.h" + +namespace yaze { +namespace editor { + +class EditorManager; + +class MenuManager { + public: + explicit MenuManager(EditorManager* editor_manager); + + void BuildAndDraw(); + + private: + EditorManager* editor_manager_; + MenuBuilder menu_builder_; +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_UI_MENU_MANAGER_H_ \ No newline at end of file diff --git a/src/app/editor/ui/workspace_manager.cc b/src/app/editor/ui/workspace_manager.cc index fbc14e9d..e776d085 100644 --- a/src/app/editor/ui/workspace_manager.cc +++ b/src/app/editor/ui/workspace_manager.cc @@ -2,6 +2,8 @@ #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" namespace yaze { namespace editor { @@ -31,6 +33,27 @@ absl::Status WorkspaceManager::ResetWorkspaceLayout() { } void WorkspaceManager::SaveWorkspacePreset(const std::string& name) { + if (name.empty()) return; + std::string ini_name = absl::StrFormat("yaze_workspace_%s.ini", name.c_str()); + ImGui::SaveIniSettingsToDisk(ini_name.c_str()); + + if (!workspace_presets_loaded_) { + // RefreshWorkspacePresets(); // This will be implemented next + } + + if (std::find(workspace_presets_.begin(), workspace_presets_.end(), name) == + workspace_presets_.end()) { + workspace_presets_.emplace_back(name); + try { + std::ostringstream ss; + for (const auto& n : workspace_presets_) + ss << n << "\n"; + // This should use a platform-agnostic path + util::SaveFile("workspace_presets.txt", ss.str()); + } catch (const std::exception& e) { + // LOG_WARN("WorkspaceManager", "Failed to save presets: %s", e.what()); + } + } last_workspace_preset_ = name; if (toast_manager_) { toast_manager_->Show(absl::StrFormat("Preset '%s' saved", name), @@ -39,6 +62,9 @@ void WorkspaceManager::SaveWorkspacePreset(const std::string& name) { } void WorkspaceManager::LoadWorkspacePreset(const std::string& name) { + 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), @@ -46,6 +72,34 @@ void WorkspaceManager::LoadWorkspacePreset(const std::string& name) { } } +void WorkspaceManager::RefreshPresets() { + try { + std::vector new_presets; + auto config_dir = util::PlatformPaths::GetConfigDirectory(); + if (config_dir.ok()) { + std::string presets_path = (*config_dir / "workspace_presets.txt").string(); + auto data = util::LoadFile(presets_path); + if (!data.empty()) { + std::istringstream ss(data); + std::string name; + while (std::getline(ss, name)) { + name.erase(0, name.find_first_not_of(" \t\r\n")); + name.erase(name.find_last_not_of(" \t\r\n") + 1); + if (!name.empty() && name.length() < 256) { + new_presets.emplace_back(std::move(name)); + } + } + } + } + workspace_presets_ = std::move(new_presets); + workspace_presets_loaded_ = true; + } catch (const std::exception& e) { + // LOG_ERROR("WorkspaceManager", "Error refreshing presets: %s", e.what()); + workspace_presets_.clear(); + workspace_presets_loaded_ = true; + } +} + void WorkspaceManager::LoadDeveloperLayout() { // TODO: Load preset with all debug tools if (toast_manager_) { diff --git a/src/app/editor/ui/workspace_manager.h b/src/app/editor/ui/workspace_manager.h index b04dfbf6..feb36215 100644 --- a/src/app/editor/ui/workspace_manager.h +++ b/src/app/editor/ui/workspace_manager.h @@ -36,6 +36,7 @@ class WorkspaceManager { // Preset management void SaveWorkspacePreset(const std::string& name); void LoadWorkspacePreset(const std::string& name); + void RefreshPresets(); void LoadDeveloperLayout(); void LoadDesignerLayout(); void LoadModderLayout(); @@ -53,10 +54,15 @@ class WorkspaceManager { void set_sessions(std::deque* sessions) { sessions_ = sessions; } + const std::vector& workspace_presets() const { return workspace_presets_; } + bool workspace_presets_loaded() const { return workspace_presets_loaded_; } + private: ToastManager* toast_manager_; std::deque* sessions_ = nullptr; std::string last_workspace_preset_; + std::vector workspace_presets_; + bool workspace_presets_loaded_ = false; }; } // namespace editor diff --git a/src/app/gfx/background_buffer.cc b/src/app/gfx/background_buffer.cc index addd9a92..f6b91685 100644 --- a/src/app/gfx/background_buffer.cc +++ b/src/app/gfx/background_buffer.cc @@ -6,6 +6,7 @@ #include "app/gfx/bitmap.h" #include "app/gfx/snes_tile.h" +#include "util/log.h" namespace yaze::gfx { @@ -43,19 +44,18 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas, // 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)) { - printf("[DrawTile] Floor tile 0x%02X at sheet pos (%d,%d), palette=%d, mirror=(%d,%d)\n", + 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_); - printf("[DrawTile] First row (8 pixels): "); + LOG_DEBUG("[DrawTile]", "First row (8 pixels): "); for (int i = 0; i < 8; i++) { int src_index = tile_y * 128 + (tile_x + i); - printf("%d ", tiledata[src_index]); + LOG_DEBUG("[DrawTile]", "%d ", tiledata[src_index]); } - printf("\n[DrawTile] Second row (8 pixels): "); + LOG_DEBUG("[DrawTile]", "Second row (8 pixels): "); for (int i = 0; i < 8; i++) { int src_index = (tile_y + 1) * 128 + (tile_x + i); - printf("%d ", tiledata[src_index]); + LOG_DEBUG("[DrawTile]", "%d ", tiledata[src_index]); } - printf("\n"); debug_count++; } @@ -146,13 +146,13 @@ 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) { - printf("[DrawFloor] Creating bitmap: %dx%d, active=%d, width=%d\n", + 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)); - printf("[DrawFloor] After Create: active=%d, width=%d, height=%d\n", + LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d", bitmap_.is_active(), bitmap_.width(), bitmap_.height()); } else { - printf("[DrawFloor] Bitmap already exists: active=%d, width=%d, height=%d\n", + LOG_DEBUG("[DrawFloor]", "Bitmap already exists: active=%d, width=%d, height=%d", bitmap_.is_active(), bitmap_.width(), bitmap_.height()); } diff --git a/src/app/main.cc b/src/app/main.cc index 8261a1fc..8bd51943 100644 --- a/src/app/main.cc +++ b/src/app/main.cc @@ -25,6 +25,16 @@ 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, 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\""); #ifdef YAZE_WITH_GRPC // gRPC test harness flags @@ -102,6 +112,11 @@ 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()); + } while (controller->IsActive()) { controller->OnInput(); diff --git a/src/app/zelda3/dungeon/object_drawer.cc b/src/app/zelda3/dungeon/object_drawer.cc index fd305fd7..07d75b01 100644 --- a/src/app/zelda3/dungeon/object_drawer.cc +++ b/src/app/zelda3/dungeon/object_drawer.cc @@ -4,6 +4,7 @@ #include "absl/strings/str_format.h" #include "app/gfx/snes_tile.h" +#include "util/log.h" namespace yaze { namespace zelda3 { @@ -26,27 +27,27 @@ absl::Status ObjectDrawer::DrawObject(const RoomObject& object, // Ensure object has tiles loaded auto mutable_obj = const_cast(object); - printf("[DrawObject] Setting ROM for object ID=0x%02X\n", object.id_); + LOG_DEBUG("ObjectDrawer", "Setting ROM for object ID=0x%02X", object.id_); mutable_obj.set_rom(rom_); - printf("[DrawObject] Calling EnsureTilesLoaded for object ID=0x%02X\n", object.id_); + LOG_DEBUG("ObjectDrawer", "Calling EnsureTilesLoaded for object ID=0x%02X", object.id_); mutable_obj.EnsureTilesLoaded(); // Check if tiles were actually loaded on the mutable object - printf("[DrawObject] After EnsureTilesLoaded: mutable object has %zu tiles\n", + LOG_DEBUG("ObjectDrawer", "After EnsureTilesLoaded: mutable object has %zu tiles", mutable_obj.tiles().size()); // Select buffer based on layer auto& target_bg = (object.layer_ == RoomObject::LayerType::BG2) ? bg2 : bg1; - printf("[DrawObject] Object ID=0x%02X using %s buffer (layer=%d)\n", + LOG_DEBUG("ObjectDrawer", "Object ID=0x%02X using %s buffer (layer=%d)", object.id_, (object.layer_ == RoomObject::LayerType::BG2) ? "BG2" : "BG1", object.layer_); // Skip objects that don't have tiles loaded - check mutable object if (mutable_obj.tiles().empty()) { - printf("[DrawObject] Object ID=0x%02X has no tiles loaded, skipping\n", object.id_); + LOG_DEBUG("ObjectDrawer", "Object ID=0x%02X has no tiles loaded, skipping", object.id_); return absl::OkStatus(); } - printf("[DrawObject] Object ID=0x%02X has %zu tiles, proceeding with drawing\n", + LOG_DEBUG("ObjectDrawer", "Object ID=0x%02X has %zu tiles, proceeding with drawing", object.id_, mutable_obj.tiles().size()); // Look up draw routine for this object @@ -70,7 +71,7 @@ absl::Status ObjectDrawer::DrawObjectList( gfx::BackgroundBuffer& bg2, const gfx::PaletteGroup& palette_group) { - printf("[DrawObjectList] Drawing %zu objects\n", objects.size()); + LOG_DEBUG("ObjectDrawer", "Drawing %zu objects", objects.size()); int drawn_count = 0; int skipped_count = 0; @@ -90,7 +91,7 @@ absl::Status ObjectDrawer::DrawObjectList( // Only log if there are failures if (skipped_count > 0) { - printf("[ObjectDrawer] Drew %d objects, skipped %d\n", drawn_count, skipped_count); + LOG_DEBUG("ObjectDrawer", "Drew %d objects, skipped %d", drawn_count, skipped_count); } return absl::OkStatus(); @@ -575,14 +576,14 @@ void ObjectDrawer::DrawRightwardsDecor2x2spaced12_1to16(const RoomObject& obj, g void ObjectDrawer::WriteTile16(gfx::BackgroundBuffer& bg, int tile_x, int tile_y, const gfx::Tile16& tile) { - printf("[WriteTile16] Writing Tile16 at tile pos (%d,%d) to bitmap\n", tile_x, tile_y); + LOG_DEBUG("ObjectDrawer", "Writing Tile16 at tile pos (%d,%d) to bitmap", tile_x, tile_y); // Draw directly to bitmap instead of tile buffer to avoid being overwritten auto& bitmap = bg.bitmap(); - printf("[WriteTile16] Bitmap status: active=%d, width=%d, height=%d, surface=%p\n", + LOG_DEBUG("ObjectDrawer", "Bitmap status: active=%d, width=%d, height=%d, surface=%p", bitmap.is_active(), bitmap.width(), bitmap.height(), bitmap.surface()); if (!bitmap.is_active() || bitmap.width() == 0) { - printf("[WriteTile16] Bitmap not ready: active=%d, width=%d\n", bitmap.is_active(), bitmap.width()); + LOG_DEBUG("ObjectDrawer", "Bitmap not ready: active=%d, width=%d", bitmap.is_active(), bitmap.width()); return; // Bitmap not ready } @@ -615,7 +616,7 @@ void ObjectDrawer::DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& ti // DEBUG: Check if bitmap is valid if (!bitmap.is_active() || bitmap.width() == 0 || bitmap.height() == 0) { - printf("[DrawTileToBitmap] ERROR: Invalid bitmap - active=%d, size=%dx%d\n", + LOG_DEBUG("ObjectDrawer", "ERROR: Invalid bitmap - active=%d, size=%dx%d", bitmap.is_active(), bitmap.width(), bitmap.height()); return; } @@ -658,7 +659,7 @@ void ObjectDrawer::DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& ti // Debug first pixel of each tile if (py == 0 && px == 0) { - printf("[DrawTileToBitmap] Tile ID=0x%02X at (%d,%d): palette=%d, pixel_index=%d, final_color=%d\n", + LOG_DEBUG("ObjectDrawer", "Tile ID=0x%02X at (%d,%d): palette=%d, pixel_index=%d, final_color=%d", tile_info.id_, pixel_x, pixel_y, palette_id, pixel_index, final_color); } } diff --git a/src/app/zelda3/dungeon/room.cc b/src/app/zelda3/dungeon/room.cc index ecba62b9..9a51bf27 100644 --- a/src/app/zelda3/dungeon/room.cc +++ b/src/app/zelda3/dungeon/room.cc @@ -16,6 +16,7 @@ #include "app/zelda3/dungeon/room_diagnostic.h" #include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/sprite/sprite.h" +#include "util/log.h" namespace yaze { namespace zelda3 { @@ -312,7 +313,7 @@ void Room::RenderRoomGraphics() { int palette_id = palette; if (palette_id < 0 || palette_id >= num_palettes) { - std::printf("5. WARNING: palette_id %d is out of bounds [0, %d), using palette %d\n", + LOG_DEBUG("[RenderRoomGraphics]", "5. WARNING: palette_id %d is out of bounds [0, %d), using palette %d", palette_id, num_palettes, palette_id % num_palettes); palette_id = palette_id % num_palettes; // Use modulo to wrap around instead of defaulting to 0 } @@ -334,10 +335,10 @@ void Room::RenderRoomGraphics() { } void Room::RenderObjectsToBackground() { - printf("[RenderObjectsToBackground] Starting object rendering\n"); + LOG_DEBUG("[RenderObjectsToBackground]", "Starting object rendering"); if (!rom_ || !rom_->is_loaded()) { - printf("[RenderObjectsToBackground] ROM not loaded, aborting\n"); + LOG_DEBUG("[RenderObjectsToBackground]", "ROM not loaded, aborting"); return; } @@ -368,7 +369,7 @@ void Room::RenderObjectsToBackground() { // Log only failures, not successes if (!status.ok()) { - printf("[RenderObjectsToBackground] ObjectDrawer failed: %s\n", std::string(status.message()).c_str()); + LOG_DEBUG("[RenderObjectsToBackground]", "ObjectDrawer failed: %s", std::string(status.message()).c_str()); } } diff --git a/src/app/zelda3/dungeon/room_object.cc b/src/app/zelda3/dungeon/room_object.cc index 53e815ae..31741660 100644 --- a/src/app/zelda3/dungeon/room_object.cc +++ b/src/app/zelda3/dungeon/room_object.cc @@ -2,6 +2,7 @@ #include "absl/status/status.h" #include "app/zelda3/dungeon/object_parser.h" +#include "util/log.h" namespace yaze { namespace zelda3 { @@ -153,28 +154,28 @@ void RoomObject::DrawTile(gfx::Tile16 t, int xx, int yy, } void RoomObject::EnsureTilesLoaded() { - printf("[EnsureTilesLoaded] Object ID=0x%02X, tiles_loaded=%d\n", id_, tiles_loaded_); + LOG_DEBUG("RoomObject", "Object ID=0x%02X, tiles_loaded=%d", id_, tiles_loaded_); if (tiles_loaded_) { - printf("[EnsureTilesLoaded] Tiles already loaded for object 0x%02X\n", id_); + LOG_DEBUG("RoomObject", "Tiles already loaded for object 0x%02X", id_); return; } if (rom_ == nullptr) { - printf("[EnsureTilesLoaded] ERROR: ROM not set for object 0x%02X\n", id_); + LOG_DEBUG("RoomObject", "ERROR: ROM not set for object 0x%02X", id_); return; } // Try the new parser first - this is more efficient and accurate - printf("[EnsureTilesLoaded] Trying parser for object 0x%02X\n", id_); + LOG_DEBUG("RoomObject", "Trying parser for object 0x%02X", id_); auto parser_status = LoadTilesWithParser(); if (parser_status.ok()) { - printf("[EnsureTilesLoaded] Parser succeeded for object 0x%02X, loaded %zu tiles\n", id_, tiles_.size()); + LOG_DEBUG("RoomObject", "Parser succeeded for object 0x%02X, loaded %zu tiles", id_, tiles_.size()); tiles_loaded_ = true; return; } - printf("[EnsureTilesLoaded] Parser failed for object 0x%02X: %s\n", id_, parser_status.message().data()); + LOG_DEBUG("RoomObject", "Parser failed for object 0x%02X: %s", id_, parser_status.message().data()); // Fallback to legacy method for compatibility with enhanced validation auto rom_data = rom_->data();