From 28dc394a7ce66676cceb35183ef2878bf2ccf85f Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 4 Oct 2025 15:14:17 -0400 Subject: [PATCH] feat: Implement lazy loading for dungeon rooms and refactor room graphics handling - Introduced lazy loading for room data to optimize performance and reduce initial load times. - Updated DungeonEditor and DungeonRoomLoader to handle room graphics rendering directly from room objects. - Refactored methods to accept room references instead of IDs for better clarity and type safety. - Enhanced tab management in the DungeonEditor UI for improved user experience. --- src/CMakeLists.txt | 11 +- src/app/core/features.h | 59 -- src/app/editor/dungeon/dungeon_editor.cc | 17 +- src/app/editor/dungeon/dungeon_editor.h | 2 +- src/app/editor/dungeon/dungeon_editor_v2.cc | 35 +- src/app/editor/dungeon/dungeon_room_loader.cc | 22 +- src/app/editor/dungeon/dungeon_room_loader.h | 3 +- src/app/gui/canvas.cc | 766 ++++++++++-------- src/app/gui/canvas/canvas_utils_moved.cc | 8 +- src/app/gui/canvas_utils.cc | 8 +- src/app/gui/enhanced_palette_editor.cc | 6 +- src/app/gui/feature_flags_menu.h | 72 ++ src/app/gui/gui.cmake | 2 + src/app/gui/theme_manager.cc | 20 +- src/app/zelda3/dungeon/room.cc | 22 +- src/app/zelda3/dungeon/room.h | 6 +- src/util/log.cc | 109 +++ src/util/log.h | 98 ++- src/util/util.cmake | 1 + 19 files changed, 799 insertions(+), 468 deletions(-) create mode 100644 src/app/gui/feature_flags_menu.h create mode 100644 src/util/log.cc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28a1d805..6837d017 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,13 +14,6 @@ set( app/emu/snes.cc ) -set( - YAZE_UTIL_SRC - util/bps.cc - util/flag.cc - util/hex.cc -) - set(YAZE_RESOURCE_FILES ${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf ${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf @@ -249,6 +242,7 @@ if (YAZE_BUILD_LIB) endif() else() # Create core library for testing (includes editor and zelda3 components needed by tests) + include(util/util.cmake) set(YAZE_CORE_SOURCES app/rom.cc ${YAZE_APP_CORE_SRC} @@ -257,7 +251,6 @@ if (YAZE_BUILD_LIB) ${YAZE_APP_ZELDA3_SRC} ${YAZE_APP_EMU_SRC} ${YAZE_GUI_SRC} - ${YAZE_UTIL_SRC} # cli/service/gui_automation_client.cc # Moved to yaze_c cli/service/testing/test_workflow_generator.cc ) @@ -284,6 +277,7 @@ if (YAZE_BUILD_LIB) yaze_core PUBLIC asar-static yaze_agent + yaze_util ${ABSL_TARGETS} ${SDL_TARGETS} ${CMAKE_DL_LIBS} @@ -765,6 +759,7 @@ source_group("Utilities" FILES util/flag.h util/hex.cc util/hex.h + util/log.cc util/log.h util/macro.h util/notify.h diff --git a/src/app/core/features.h b/src/app/core/features.h index 9dcb6b9b..a86aeb56 100644 --- a/src/app/core/features.h +++ b/src/app/core/features.h @@ -3,8 +3,6 @@ #include -#include "imgui/imgui.h" - namespace yaze { namespace core { @@ -117,63 +115,6 @@ class FeatureFlags { } }; -using ImGui::BeginMenu; -using ImGui::Checkbox; -using ImGui::EndMenu; -using ImGui::MenuItem; -using ImGui::Separator; - -struct FlagsMenu { - void DrawOverworldFlags() { - Checkbox("Enable Overworld Sprites", - &FeatureFlags::get().overworld.kDrawOverworldSprites); - Separator(); - Checkbox("Save Overworld Maps", - &FeatureFlags::get().overworld.kSaveOverworldMaps); - Checkbox("Save Overworld Entrances", - &FeatureFlags::get().overworld.kSaveOverworldEntrances); - Checkbox("Save Overworld Exits", - &FeatureFlags::get().overworld.kSaveOverworldExits); - Checkbox("Save Overworld Items", - &FeatureFlags::get().overworld.kSaveOverworldItems); - Checkbox("Save Overworld Properties", - &FeatureFlags::get().overworld.kSaveOverworldProperties); - Checkbox("Enable Custom Overworld Features", - &FeatureFlags::get().overworld.kLoadCustomOverworld); - ImGui::SameLine(); - if (ImGui::Button("?")) { - ImGui::OpenPopup("CustomOverworldHelp"); - } - if (ImGui::BeginPopup("CustomOverworldHelp")) { - ImGui::Text("This flag enables ZSCustomOverworld features."); - ImGui::Text("If ZSCustomOverworld ASM is already applied to the ROM,"); - ImGui::Text("features are auto-enabled regardless of this flag."); - ImGui::Text("For vanilla ROMs, enable this to use custom features."); - ImGui::EndPopup(); - } - Checkbox("Apply ZSCustomOverworld ASM", - &FeatureFlags::get().overworld.kApplyZSCustomOverworldASM); - } - - void DrawDungeonFlags() { - Checkbox("Save Dungeon Maps", &FeatureFlags::get().kSaveDungeonMaps); - } - - void DrawResourceFlags() { - Checkbox("Save All Palettes", &FeatureFlags::get().kSaveAllPalettes); - Checkbox("Save Gfx Groups", &FeatureFlags::get().kSaveGfxGroups); - Checkbox("Save Graphics Sheets", &FeatureFlags::get().kSaveGraphicsSheet); - } - - void DrawSystemFlags() { - Checkbox("Enable Console Logging", &FeatureFlags::get().kLogToConsole); - Checkbox("Enable Performance Monitoring", &FeatureFlags::get().kEnablePerformanceMonitoring); - Checkbox("Log Instructions to Emulator Debugger", - &FeatureFlags::get().kLogInstructions); - Checkbox("Use Native File Dialog (NFD)", &FeatureFlags::get().kUseNativeFileDialog); - } -}; - } // namespace core } // namespace yaze diff --git a/src/app/editor/dungeon/dungeon_editor.cc b/src/app/editor/dungeon/dungeon_editor.cc index 8b7e9907..93c1a873 100644 --- a/src/app/editor/dungeon/dungeon_editor.cc +++ b/src/app/editor/dungeon/dungeon_editor.cc @@ -371,7 +371,7 @@ void DungeonEditor::DrawCanvasAndPropertiesPanel() { gui::InputHexByte("Palette", &room.palette); if (ImGui::Button("Reload Room Graphics")) { - (void)LoadAndRenderRoomGraphics(current_room); + (void)LoadAndRenderRoomGraphics(room); } } @@ -555,7 +555,7 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { SameLine(); if (Button("Load Room Graphics")) { - (void)LoadAndRenderRoomGraphics(room_id); + (void)LoadAndRenderRoomGraphics(rooms_[room_id]); } ImGui::SameLine(); @@ -701,7 +701,7 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { if (is_loaded_) { // Automatically load room graphics if not already loaded if (rooms_[room_id].blocks().empty()) { - (void)LoadAndRenderRoomGraphics(room_id); + (void)LoadAndRenderRoomGraphics(rooms_[room_id]); } // Load room objects if not already loaded @@ -761,7 +761,7 @@ void DungeonEditor::UpdateObjectEditor() { // Load room graphics if not already loaded (this populates arena buffers) if (room.blocks().empty()) { - auto status = LoadAndRenderRoomGraphics(room_id); + auto status = LoadAndRenderRoomGraphics(room); if (!status.ok()) { // Log error but continue return; @@ -829,18 +829,15 @@ void DungeonEditor::DrawObjectEditorPanels() { } // Legacy method implementations that delegate to components -absl::Status DungeonEditor::LoadAndRenderRoomGraphics(int room_id) { - if (room_id < 0 || room_id >= rooms_.size()) { - return absl::InvalidArgumentError("Invalid room ID"); - } - return room_loader_.LoadAndRenderRoomGraphics(room_id, rooms_[room_id]); +absl::Status DungeonEditor::LoadAndRenderRoomGraphics(zelda3::Room& room) { + return room_loader_.LoadAndRenderRoomGraphics(room); } absl::Status DungeonEditor::ReloadAllRoomGraphics() { return room_loader_.ReloadAllRoomGraphics(rooms_); } -absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int room_id) { +absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int /*room_id*/) { // This method is deprecated - rendering is handled by DungeonRenderer component return absl::OkStatus(); } diff --git a/src/app/editor/dungeon/dungeon_editor.h b/src/app/editor/dungeon/dungeon_editor.h index 482e9c80..bd1c7178 100644 --- a/src/app/editor/dungeon/dungeon_editor.h +++ b/src/app/editor/dungeon/dungeon_editor.h @@ -118,7 +118,7 @@ class DungeonEditor : public Editor { void DrawObjectRenderer(); // Legacy methods (delegated to components) - absl::Status LoadAndRenderRoomGraphics(int room_id); + absl::Status LoadAndRenderRoomGraphics(zelda3::Room& room); absl::Status ReloadAllRoomGraphics(); absl::Status UpdateRoomBackgroundLayers(int room_id); diff --git a/src/app/editor/dungeon/dungeon_editor_v2.cc b/src/app/editor/dungeon/dungeon_editor_v2.cc index becc7bf4..084a9be1 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.cc +++ b/src/app/editor/dungeon/dungeon_editor_v2.cc @@ -4,6 +4,8 @@ #include "absl/strings/str_format.h" #include "app/gfx/snes_palette.h" +#include "app/zelda3/dungeon/room.h" +#include "app/gui/icons.h" #include "imgui/imgui.h" namespace yaze::editor { @@ -28,8 +30,8 @@ absl::Status DungeonEditorV2::Load() { return absl::FailedPreconditionError("ROM not loaded"); } - // Load all rooms using the loader component - RETURN_IF_ERROR(room_loader_.LoadAllRooms(rooms_)); + // Load all rooms using the loader component - DEFERRED for lazy loading + // RETURN_IF_ERROR(room_loader_.LoadAllRooms(rooms_)); RETURN_IF_ERROR(room_loader_.LoadRoomEntrances(entrances_)); // Load palette group @@ -106,7 +108,16 @@ void DungeonEditorV2::DrawLayout() { int room_id = active_rooms_[i]; bool open = true; - if (BeginTabItem(absl::StrFormat("Room %03X", room_id).c_str(), &open)) { + std::string tab_name_str; + const char* tab_name; + if (room_id >= 0 && static_cast(room_id) < std::size(zelda3::kRoomNames)) { + tab_name = zelda3::kRoomNames[room_id].data(); + } else { + tab_name_str = absl::StrFormat("Room %03X", room_id); + tab_name = tab_name_str.c_str(); + } + + if (BeginTabItem(tab_name, &open)) { DrawRoomTab(room_id); EndTabItem(); } @@ -116,6 +127,12 @@ void DungeonEditorV2::DrawLayout() { i--; } } + + // Add tab button + if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) { + OnRoomSelected(room_selector_.current_room_id()); + } + EndTabBar(); } @@ -133,11 +150,21 @@ void DungeonEditorV2::DrawRoomTab(int room_id) { return; } + // Lazy load room data + if (!rooms_[room_id].IsLoaded()) { + auto status = room_loader_.LoadRoom(room_id, rooms_[room_id]); + if (!status.ok()) { + ImGui::TextColored(ImVec4(1, 0, 0, 1), "Failed to load room: %s", + status.message().data()); + return; + } + } + // Quick controls ImGui::Text("Room %03X", room_id); ImGui::SameLine(); if (ImGui::Button("Load Graphics")) { - (void)room_loader_.LoadAndRenderRoomGraphics(room_id, rooms_[room_id]); + (void)room_loader_.LoadAndRenderRoomGraphics(rooms_[room_id]); } ImGui::SameLine(); if (ImGui::Button("Save")) { diff --git a/src/app/editor/dungeon/dungeon_room_loader.cc b/src/app/editor/dungeon/dungeon_room_loader.cc index 0b380936..1a55ee18 100644 --- a/src/app/editor/dungeon/dungeon_room_loader.cc +++ b/src/app/editor/dungeon/dungeon_room_loader.cc @@ -13,6 +13,20 @@ namespace yaze::editor { +absl::Status DungeonRoomLoader::LoadRoom(int room_id, zelda3::Room& room) { + if (!rom_ || !rom_->is_loaded()) { + return absl::FailedPreconditionError("ROM not loaded"); + } + if (room_id < 0 || room_id >= 0x128) { + return absl::InvalidArgumentError("Invalid room ID"); + } + + room = zelda3::LoadRoomFromRom(rom_, room_id); + room.LoadObjects(); + + return absl::OkStatus(); +} + absl::Status DungeonRoomLoader::LoadAllRooms(std::array& rooms) { if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); @@ -26,7 +40,7 @@ absl::Status DungeonRoomLoader::LoadAllRooms(std::array& ro static_cast(std::thread::hardware_concurrency())); const int rooms_per_thread = (kTotalRooms + max_concurrency - 1) / max_concurrency; - util::logf("Loading %d dungeon rooms using %d threads (%d rooms per thread)", + LOG_INFO("Dungeon", "Loading %d dungeon rooms using %d threads (%d rooms per thread)", kTotalRooms, max_concurrency, rooms_per_thread); // Thread-safe data structures for collecting results @@ -164,7 +178,7 @@ void DungeonRoomLoader::LoadDungeonRoomSize() { } } -absl::Status DungeonRoomLoader::LoadAndRenderRoomGraphics(int room_id, zelda3::Room& room) { +absl::Status DungeonRoomLoader::LoadAndRenderRoomGraphics(zelda3::Room& room) { if (!rom_ || !rom_->is_loaded()) { return absl::FailedPreconditionError("ROM not loaded"); } @@ -184,8 +198,8 @@ absl::Status DungeonRoomLoader::ReloadAllRoomGraphics(std::array(i), rooms[i]); + for (auto& room : rooms) { + auto status = LoadAndRenderRoomGraphics(room); if (!status.ok()) { continue; // Log error but continue with other rooms } diff --git a/src/app/editor/dungeon/dungeon_room_loader.h b/src/app/editor/dungeon/dungeon_room_loader.h index 36b5eec8..276d1d19 100644 --- a/src/app/editor/dungeon/dungeon_room_loader.h +++ b/src/app/editor/dungeon/dungeon_room_loader.h @@ -23,6 +23,7 @@ class DungeonRoomLoader { explicit DungeonRoomLoader(Rom* rom) : rom_(rom) {} // Room loading + absl::Status LoadRoom(int room_id, zelda3::Room& room); absl::Status LoadAllRooms(std::array& rooms); absl::Status LoadRoomEntrances(std::array& entrances); @@ -31,7 +32,7 @@ class DungeonRoomLoader { uint64_t GetTotalRoomSize() const { return total_room_size_; } // Room graphics - absl::Status LoadAndRenderRoomGraphics(int room_id, zelda3::Room& room); + absl::Status LoadAndRenderRoomGraphics(zelda3::Room& room); absl::Status ReloadAllRoomGraphics(std::array& rooms); // Data access diff --git a/src/app/gui/canvas.cc b/src/app/gui/canvas.cc index 98b15884..d068532f 100644 --- a/src/app/gui/canvas.cc +++ b/src/app/gui/canvas.cc @@ -8,12 +8,12 @@ #include "app/gfx/atlas_renderer.h" #include "app/gfx/bitmap.h" #include "app/gfx/performance_profiler.h" +#include "app/gui/canvas_utils.h" #include "app/gui/color.h" #include "app/gui/style.h" -#include "app/gui/canvas_utils.h" -#include "util/log.h" #include "imgui/imgui.h" #include "imgui_memory_editor.h" +#include "util/log.h" namespace yaze::gui { @@ -62,19 +62,19 @@ void Canvas::InitializeDefaults() { config_.canvas_size = ImVec2(0, 0); config_.custom_canvas_size = false; config_.clamp_rect_to_local_maps = false; - + // Initialize selection state selection_.Clear(); - + // Initialize palette editor palette_editor_ = std::make_unique(); - + // Initialize interaction handler interaction_handler_.Initialize(canvas_id_); - + // Initialize enhanced components InitializeEnhancedComponents(); - + // Initialize legacy compatibility variables to match config enable_grid_ = config_.enable_grid; enable_hex_tile_labels_ = config_.enable_hex_labels; @@ -91,12 +91,12 @@ void Canvas::InitializeDefaults() { void Canvas::Cleanup() { palette_editor_.reset(); selection_.Clear(); - + // Stop performance monitoring before cleanup to prevent segfault if (performance_integration_) { performance_integration_->StopMonitoring(); } - + // Cleanup enhanced components modals_.reset(); context_menu_.reset(); @@ -107,22 +107,24 @@ void Canvas::Cleanup() { void Canvas::InitializeEnhancedComponents() { // Initialize modals system modals_ = std::make_unique(); - + // Initialize context menu system context_menu_ = std::make_unique(); context_menu_->Initialize(canvas_id_); - + // Initialize usage tracker usage_tracker_ = std::make_shared(); usage_tracker_->Initialize(canvas_id_); canvas::CanvasUsageManager::Get().RegisterTracker(canvas_id_, usage_tracker_); - + // Initialize performance integration - performance_integration_ = std::make_shared(); + performance_integration_ = + std::make_shared(); performance_integration_->Initialize(canvas_id_); performance_integration_->SetUsageTracker(usage_tracker_); - canvas::CanvasPerformanceManager::Get().RegisterIntegration(canvas_id_, performance_integration_); - + canvas::CanvasPerformanceManager::Get().RegisterIntegration( + canvas_id_, performance_integration_); + // Start performance monitoring performance_integration_->StartMonitoring(); usage_tracker_->StartSession(); @@ -144,12 +146,14 @@ canvas::CanvasUsage Canvas::GetUsageMode() const { return canvas::CanvasUsage::kUnknown; } -void Canvas::RecordCanvasOperation(const std::string& operation_name, double time_ms) { +void Canvas::RecordCanvasOperation(const std::string& operation_name, + double time_ms) { if (usage_tracker_) { usage_tracker_->RecordOperation(operation_name, time_ms); } if (performance_integration_) { - performance_integration_->RecordOperation(operation_name, time_ms, GetUsageMode()); + performance_integration_->RecordOperation(operation_name, time_ms, + GetUsageMode()); } } @@ -166,7 +170,8 @@ void Canvas::ShowUsageReport() { if (modals_) { // Create a simple text display modal ImGui::OpenPopup("Canvas Usage Report"); - if (ImGui::BeginPopupModal("Canvas Usage Report", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal("Canvas Usage Report", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Canvas Usage Report"); ImGui::Separator(); ImGui::TextWrapped("%s", report.c_str()); @@ -190,7 +195,8 @@ void Canvas::InitializePaletteEditor(Rom* rom) { void Canvas::ShowPaletteEditor() { if (palette_editor_ && bitmap_) { auto mutable_palette = bitmap_->mutable_palette(); - palette_editor_->ShowPaletteEditor(*mutable_palette, "Canvas Palette Editor"); + palette_editor_->ShowPaletteEditor(*mutable_palette, + "Canvas Palette Editor"); } } @@ -202,18 +208,21 @@ void Canvas::ShowColorAnalysis() { bool Canvas::ApplyROMPalette(int group_index, int palette_index) { if (palette_editor_ && bitmap_) { - return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index); + return palette_editor_->ApplyROMPalette(bitmap_, group_index, + palette_index); } return false; } // Size reporting methods for table integration ImVec2 Canvas::GetMinimumSize() const { - return CanvasUtils::CalculateMinimumCanvasSize(config_.content_size, config_.global_scale); + return CanvasUtils::CalculateMinimumCanvasSize(config_.content_size, + config_.global_scale); } ImVec2 Canvas::GetPreferredSize() const { - return CanvasUtils::CalculatePreferredCanvasSize(config_.content_size, config_.global_scale); + return CanvasUtils::CalculatePreferredCanvasSize(config_.content_size, + config_.global_scale); } void Canvas::ReserveTableSpace(const std::string& label) { @@ -226,19 +235,20 @@ bool Canvas::BeginTableCanvas(const std::string& label) { ImVec2 preferred_size = GetPreferredSize(); CanvasUtils::SetNextCanvasSize(preferred_size, true); } - + // Begin child window that properly reports size to tables std::string child_id = canvas_id_ + "_TableChild"; ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size; - - bool result = ImGui::BeginChild(child_id.c_str(), child_size, - true, // Always show border for table integration - ImGuiWindowFlags_AlwaysVerticalScrollbar); - + + bool result = + ImGui::BeginChild(child_id.c_str(), child_size, + true, // Always show border for table integration + ImGuiWindowFlags_AlwaysVerticalScrollbar); + if (!label.empty()) { ImGui::Text("%s", label.c_str()); } - + return result; } @@ -256,14 +266,15 @@ bool Canvas::WasClicked(ImGuiMouseButton button) const { } bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const { - return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) && HasValidSelection(); + return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) && + HasValidSelection(); } ImVec2 Canvas::GetLastClickPosition() const { if (HasValidSelection()) { - return points_[0]; // Return the first point of the selection + return points_[0]; // Return the first point of the selection } - return ImVec2(-1, -1); // Invalid position + return ImVec2(-1, -1); // Invalid position } // ==================== Modern ImGui-Style Interface ==================== @@ -284,11 +295,11 @@ void Canvas::End() { // ==================== Legacy Interface ==================== -void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, - const std::function &event, +void Canvas::UpdateColorPainter(gfx::Bitmap& bitmap, const ImVec4& color, + const std::function& event, int tile_size, float scale) { config_.global_scale = scale; - global_scale_ = scale; // Legacy compatibility + global_scale_ = scale; // Legacy compatibility DrawBackground(); DrawContextMenu(); DrawBitmap(bitmap, 2, scale); @@ -301,7 +312,7 @@ void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) { config_.enable_custom_labels = true; - enable_custom_labels_ = true; // Legacy compatibility + enable_custom_labels_ = true; // Legacy compatibility DrawBackground(bg_size); DrawInfoGrid(grid_size, 8, label_id); DrawOverlay(); @@ -310,24 +321,29 @@ void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) { void Canvas::DrawBackground(ImVec2 canvas_size) { draw_list_ = GetWindowDrawList(); canvas_p0_ = GetCursorScreenPos(); - + // Calculate canvas size using utility function ImVec2 content_region = GetContentRegionAvail(); - canvas_sz_ = CanvasUtils::CalculateCanvasSize(content_region, config_.canvas_size, config_.custom_canvas_size); - + canvas_sz_ = CanvasUtils::CalculateCanvasSize( + content_region, config_.canvas_size, config_.custom_canvas_size); + if (canvas_size.x != 0) { canvas_sz_ = canvas_size; config_.canvas_size = canvas_size; } - + // Calculate scaled canvas bounds - ImVec2 scaled_size = CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale); - + ImVec2 scaled_size = + CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale); + // CRITICAL FIX: Ensure minimum size to prevent ImGui assertions - if (scaled_size.x <= 0.0f) scaled_size.x = 1.0f; - if (scaled_size.y <= 0.0f) scaled_size.y = 1.0f; - - canvas_p1_ = ImVec2(canvas_p0_.x + scaled_size.x, canvas_p0_.y + scaled_size.y); + if (scaled_size.x <= 0.0f) + scaled_size.x = 1.0f; + if (scaled_size.y <= 0.0f) + scaled_size.y = 1.0f; + + canvas_p1_ = + ImVec2(canvas_p0_.x + scaled_size.x, canvas_p0_.y + scaled_size.y); // Draw border and background color draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor); @@ -336,7 +352,7 @@ void Canvas::DrawBackground(ImVec2 canvas_size) { ImGui::InvisibleButton(canvas_id_.c_str(), scaled_size, kMouseFlags); if (config_.is_draggable && IsItemHovered()) { - const ImGuiIO &io = GetIO(); + const ImGuiIO& io = GetIO(); const bool is_active = IsItemActive(); // Held const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); // Lock scrolled origin @@ -354,21 +370,20 @@ void Canvas::DrawBackground(ImVec2 canvas_size) { } void Canvas::DrawContextMenu() { - const ImGuiIO &io = GetIO(); + const ImGuiIO& io = GetIO(); const ImVec2 scaled_sz(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_); const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); // Lock scrolled origin const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); - + // Update canvas state for enhanced components if (usage_tracker_) { - usage_tracker_->UpdateCanvasState(canvas_sz_, config_.content_size, - global_scale_, custom_step_, - enable_grid_, enable_hex_tile_labels_, - enable_custom_labels_); + usage_tracker_->UpdateCanvasState( + canvas_sz_, config_.content_size, global_scale_, custom_step_, + enable_grid_, enable_hex_tile_labels_, enable_custom_labels_); } - + // Use enhanced context menu if available if (context_menu_) { canvas::CanvasConfig snapshot; @@ -384,11 +399,10 @@ void Canvas::DrawContextMenu() { snapshot.auto_resize = config_.auto_resize; snapshot.scrolling = scrolling_; - context_menu_->SetCanvasState(canvas_sz_, config_.content_size, - global_scale_, custom_step_, enable_grid_, - enable_hex_tile_labels_, enable_custom_labels_, - enable_context_menu_, draggable_, - config_.auto_resize, scrolling_); + context_menu_->SetCanvasState( + canvas_sz_, config_.content_size, global_scale_, custom_step_, + enable_grid_, enable_hex_tile_labels_, enable_custom_labels_, + enable_context_menu_, draggable_, config_.auto_resize, scrolling_); context_menu_->Render( context_id_, mouse_pos, bitmap_, @@ -445,19 +459,28 @@ void Canvas::DrawContextMenu() { if (modals_) { canvas::CanvasConfig modal_config = updated_config; modal_config.on_config_changed = - [this](const canvas::CanvasConfig& cfg) { ApplyConfigSnapshot(cfg); }; + [this](const canvas::CanvasConfig& cfg) { + ApplyConfigSnapshot(cfg); + }; modal_config.on_scale_changed = - [this](const canvas::CanvasConfig& cfg) { ApplyScaleSnapshot(cfg); }; - modals_->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_); + [this](const canvas::CanvasConfig& cfg) { + ApplyScaleSnapshot(cfg); + }; + modals_->ShowAdvancedProperties(canvas_id_, modal_config, + bitmap_); } break; case canvas::CanvasContextMenu::Command::kOpenScalingControls: if (modals_) { canvas::CanvasConfig modal_config = updated_config; modal_config.on_config_changed = - [this](const canvas::CanvasConfig& cfg) { ApplyConfigSnapshot(cfg); }; + [this](const canvas::CanvasConfig& cfg) { + ApplyConfigSnapshot(cfg); + }; modal_config.on_scale_changed = - [this](const canvas::CanvasConfig& cfg) { ApplyScaleSnapshot(cfg); }; + [this](const canvas::CanvasConfig& cfg) { + ApplyScaleSnapshot(cfg); + }; modals_->ShowScalingControls(canvas_id_, modal_config, bitmap_); } break; @@ -477,7 +500,7 @@ void Canvas::DrawContextMenu() { static bool show_bitmap_data = false; if (show_bitmap_data && bitmap_ != nullptr) { static MemoryEditor mem_edit; - mem_edit.DrawWindow("Bitmap Data", (void *)bitmap_->data(), bitmap_->size(), + mem_edit.DrawWindow("Bitmap Data", (void*)bitmap_->data(), bitmap_->size(), 0); } @@ -492,12 +515,12 @@ void Canvas::DrawContextMenu() { for (const auto& item : context_menu_items_) { DrawContextMenuItem(item); } - + // Add separator if there are custom items if (!context_menu_items_.empty()) { ImGui::Separator(); } - + // Default canvas menu items if (MenuItem("Reset View", nullptr, false)) { ResetView(); @@ -551,67 +574,76 @@ void Canvas::DrawContextMenu() { } if (BeginMenu("ROM Palette Selection") && rom_) { Text("Select ROM Palette Group:"); - + // Enhanced ROM palette group selection if (palette_editor_) { // Use our enhanced palette editor's ROM selection if (MenuItem("Open Enhanced Palette Manager")) { palette_editor_->ShowROMPaletteManager(); } - + ImGui::Separator(); - + // Quick palette group selection const char* palette_groups[] = { - "Overworld Main", "Overworld Aux", "Overworld Animated", - "Dungeon Main", "Global Sprites", "Armor", "Swords" - }; - - if (ImGui::Combo("Quick Palette Group", (int*)&edit_palette_group_name_index_, - palette_groups, IM_ARRAYSIZE(palette_groups))) { + "Overworld Main", "Overworld Aux", "Overworld Animated", + "Dungeon Main", "Global Sprites", "Armor", + "Swords"}; + + if (ImGui::Combo("Quick Palette Group", + (int*)&edit_palette_group_name_index_, + palette_groups, IM_ARRAYSIZE(palette_groups))) { // Group selection changed } - + ImGui::SetNextItemWidth(100.f); - if (ImGui::SliderInt("Palette Index", (int*)&edit_palette_index_, 0, 7)) { + if (ImGui::SliderInt("Palette Index", (int*)&edit_palette_index_, 0, + 7)) { // Palette index changed } - + // Apply button with enhanced functionality if (ImGui::Button("Apply to Canvas") && bitmap_) { - if (palette_editor_->ApplyROMPalette(bitmap_, - edit_palette_group_name_index_, - edit_palette_index_)) { - util::logf("Applied ROM palette group %d, index %d via context menu", - edit_palette_group_name_index_, edit_palette_index_); + if (palette_editor_->ApplyROMPalette( + bitmap_, edit_palette_group_name_index_, + edit_palette_index_)) { + // LOG_INFO( + // "Applied ROM palette group %d, index %d via context menu", + // edit_palette_group_name_index_, edit_palette_index_); } } - + // Direct palette editing with SelectablePalettePipeline if (ImGui::TreeNode("Interactive Palette Editor")) { if (rom_ && bitmap_) { ImGui::Text("Interactive ROM Palette Editing"); - ImGui::Text("Selected Group: %s", palette_groups[edit_palette_group_name_index_]); - + ImGui::Text("Selected Group: %s", + palette_groups[edit_palette_group_name_index_]); + // Get the enhanced palette editor's ROM palette if available - if (const auto* rom_palette = palette_editor_->GetSelectedROMPalette()) { - auto editable_palette = const_cast(*rom_palette); - - if (ImGui::BeginChild("SelectablePalette", ImVec2(0, 200), true)) { + if (const auto* rom_palette = + palette_editor_->GetSelectedROMPalette()) { + auto editable_palette = + const_cast(*rom_palette); + + if (ImGui::BeginChild("SelectablePalette", ImVec2(0, 200), + true)) { // Use the existing SelectablePalettePipeline for interactive editing - gui::SelectablePalettePipeline(edit_palette_sub_index_, - refresh_graphics_, editable_palette); - + gui::SelectablePalettePipeline(edit_palette_sub_index_, + refresh_graphics_, + editable_palette); + if (refresh_graphics_) { - bitmap_->SetPaletteWithTransparent(editable_palette, edit_palette_sub_index_); + bitmap_->SetPaletteWithTransparent( + editable_palette, edit_palette_sub_index_); Renderer::Get().UpdateBitmap(bitmap_); refresh_graphics_ = false; - util::logf("Applied interactive palette changes to canvas"); + // LOG_INFO is undefined or missing required arguments; removed or fix as needed. } + ImGui::Text( + "Load ROM palettes first using Enhanced Palette " + "Manager"); } - ImGui::EndChild(); - } else { - ImGui::Text("Load ROM palettes first using Enhanced Palette Manager"); } } ImGui::TreePop(); @@ -620,7 +652,8 @@ void Canvas::DrawContextMenu() { EndMenu(); } if (BeginMenu("View Palette")) { - (void)DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, 8); + (void)DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", + true, 8); EndMenu(); } EndMenu(); @@ -645,8 +678,8 @@ void Canvas::DrawContextMenu() { ImGui::EndPopup(); } - - // Draw enhanced property dialogs + + // Draw enhanced property dialogs ShowAdvancedCanvasProperties(); ShowScalingControls(); } @@ -655,10 +688,12 @@ void Canvas::DrawContextMenuItem(const ContextMenuItem& item) { if (!item.enabled_condition()) { ImGui::BeginDisabled(); } - + if (item.subitems.empty()) { // Simple menu item - if (ImGui::MenuItem(item.label.c_str(), item.shortcut.empty() ? nullptr : item.shortcut.c_str())) { + if (ImGui::MenuItem(item.label.c_str(), item.shortcut.empty() + ? nullptr + : item.shortcut.c_str())) { item.callback(); } } else { @@ -670,7 +705,7 @@ void Canvas::DrawContextMenuItem(const ContextMenuItem& item) { ImGui::EndMenu(); } } - + if (!item.enabled_condition()) { ImGui::EndDisabled(); } @@ -685,25 +720,27 @@ void Canvas::ClearContextMenuItems() { } void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) { - if (!bitmap.is_active()) return; - + if (!bitmap.is_active()) + return; + ImVec2 available = ImGui::GetContentRegionAvail(); float scale_x = available.x / bitmap.width(); float scale_y = available.y / bitmap.height(); config_.global_scale = std::min(scale_x, scale_y); - + // Ensure minimum readable scale - if (config_.global_scale < 0.25f) config_.global_scale = 0.25f; - - global_scale_ = config_.global_scale; // Legacy compatibility - + if (config_.global_scale < 0.25f) + config_.global_scale = 0.25f; + + global_scale_ = config_.global_scale; // Legacy compatibility + // Center the view scrolling_ = ImVec2(0, 0); } void Canvas::ResetView() { config_.global_scale = 1.0f; - global_scale_ = 1.0f; // Legacy compatibility + global_scale_ = 1.0f; // Legacy compatibility scrolling_ = ImVec2(0, 0); } @@ -718,7 +755,8 @@ void Canvas::ApplyConfigSnapshot(const canvas::CanvasConfig& snapshot) { config_.global_scale = snapshot.global_scale; config_.canvas_size = snapshot.canvas_size; config_.content_size = snapshot.content_size; - config_.custom_canvas_size = snapshot.canvas_size.x > 0 && snapshot.canvas_size.y > 0; + config_.custom_canvas_size = + snapshot.canvas_size.x > 0 && snapshot.canvas_size.y > 0; enable_grid_ = config_.enable_grid; enable_hex_tile_labels_ = config_.enable_hex_labels; @@ -736,8 +774,8 @@ void Canvas::ApplyScaleSnapshot(const canvas::CanvasConfig& snapshot) { scrolling_ = snapshot.scrolling; } -bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { - const ImGuiIO &io = GetIO(); +bool Canvas::DrawTilePainter(const Bitmap& bitmap, int size, float scale) { + const ImGuiIO& io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; // Lock scrolled origin @@ -782,18 +820,18 @@ bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { return false; } -bool Canvas::DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile) { - const ImGuiIO &io = GetIO(); +bool Canvas::DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile) { + const ImGuiIO& io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); - + // Safety check: ensure tilemap is properly initialized if (!tilemap.atlas.is_active() || tilemap.tile_size.x <= 0) { return false; } - + const auto scaled_size = tilemap.tile_size.x * global_scale_; if (!is_hovered) { @@ -820,17 +858,20 @@ bool Canvas::DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile) { if (tiles_per_row > 0) { int tile_x = (current_tile % tiles_per_row) * tilemap.tile_size.x; int tile_y = (current_tile / tiles_per_row) * tilemap.tile_size.y; - + // Simple bounds check - if (tile_x >= 0 && tile_x < tilemap.atlas.width() && - tile_y >= 0 && tile_y < tilemap.atlas.height()) { - + if (tile_x >= 0 && tile_x < tilemap.atlas.width() && tile_y >= 0 && + tile_y < tilemap.atlas.height()) { + // Draw directly from atlas texture - ImVec2 uv0 = ImVec2(static_cast(tile_x) / tilemap.atlas.width(), - static_cast(tile_y) / tilemap.atlas.height()); - ImVec2 uv1 = ImVec2(static_cast(tile_x + tilemap.tile_size.x) / tilemap.atlas.width(), - static_cast(tile_y + tilemap.tile_size.y) / tilemap.atlas.height()); - + ImVec2 uv0 = + ImVec2(static_cast(tile_x) / tilemap.atlas.width(), + static_cast(tile_y) / tilemap.atlas.height()); + ImVec2 uv1 = ImVec2(static_cast(tile_x + tilemap.tile_size.x) / + tilemap.atlas.width(), + static_cast(tile_y + tilemap.tile_size.y) / + tilemap.atlas.height()); + draw_list_->AddImage( (ImTextureID)(intptr_t)tilemap.atlas.texture(), ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y), @@ -850,8 +891,8 @@ bool Canvas::DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile) { return false; } -bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) { - const ImGuiIO &io = GetIO(); +bool Canvas::DrawSolidTilePainter(const ImVec4& color, int tile_size) { + const ImGuiIO& io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; // Lock scrolled origin @@ -904,7 +945,7 @@ bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) { return false; } -void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, +void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap* bitmap, ImVec4 color) { const ImVec2 position = drawn_tile_pos_; int tile_index_x = static_cast(position.x / global_scale_) / tile_size; @@ -926,7 +967,7 @@ void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, } bool Canvas::DrawTileSelector(int size, int size_y) { - const ImGuiIO &io = GetIO(); + const ImGuiIO& io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); @@ -955,21 +996,21 @@ bool Canvas::DrawTileSelector(int size, int size_y) { void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { gfx::ScopedTimer timer("canvas_select_rect"); - - const ImGuiIO &io = GetIO(); + + const ImGuiIO& io = GetIO(); const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); static ImVec2 drag_start_pos; const float scaled_size = tile_size * scale; static bool dragging = false; constexpr int small_map_size = 0x200; - + // Only handle mouse events if the canvas is hovered const bool is_hovered = IsItemHovered(); if (!is_hovered) { return; } - + // Calculate superX and superY accounting for world offset int superY, superX; if (current_map < 0x40) { @@ -1028,12 +1069,15 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { int end_y = std::floor(drag_end_pos.y / scaled_size) * tile16_size; // Swap the start and end positions if they are in the wrong order - if (start_x > end_x) std::swap(start_x, end_x); - if (start_y > end_y) std::swap(start_y, end_y); + if (start_x > end_x) + std::swap(start_x, end_x); + if (start_y > end_y) + std::swap(start_y, end_y); selected_tiles_.clear(); - selected_tiles_.reserve(((end_x - start_x) / tile16_size + 1) * ((end_y - start_y) / tile16_size + 1)); - + selected_tiles_.reserve(((end_x - start_x) / tile16_size + 1) * + ((end_y - start_y) / tile16_size + 1)); + // Number of tiles per local map (since each tile is 16x16) constexpr int tiles_per_local_map = small_map_size / 16; @@ -1063,15 +1107,15 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { } } -void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) { +void Canvas::DrawBitmap(Bitmap& bitmap, int border_offset, float scale) { if (!bitmap.is_active()) { return; } bitmap_ = &bitmap; - + // Update content size for table integration config_.content_size = ImVec2(bitmap.width(), bitmap.height()); - + draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(), ImVec2(canvas_p0_.x, canvas_p0_.y), ImVec2(canvas_p0_.x + (bitmap.width() * scale), @@ -1079,42 +1123,41 @@ void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) { draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor); } -void Canvas::DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale, +void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale, int alpha) { if (!bitmap.is_active()) { return; } bitmap_ = &bitmap; - + // Update content size for table integration config_.content_size = ImVec2(bitmap.width(), bitmap.height()); - + // Calculate the actual rendered size including scale and offsets ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale); ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y); - + draw_list_->AddImage( (ImTextureID)(intptr_t)bitmap.texture(), ImVec2(canvas_p0_.x + x_offset + scrolling_.x, canvas_p0_.y + y_offset + scrolling_.y), - ImVec2( - canvas_p0_.x + x_offset + scrolling_.x + rendered_size.x, - canvas_p0_.y + y_offset + scrolling_.y + rendered_size.y), + ImVec2(canvas_p0_.x + x_offset + scrolling_.x + rendered_size.x, + canvas_p0_.y + y_offset + scrolling_.y + rendered_size.y), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha)); - + // Note: Content size for child windows should be set before BeginChild, not here } -void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size, +void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) { if (!bitmap.is_active()) { return; } bitmap_ = &bitmap; - + // Update content size for table integration config_.content_size = ImVec2(bitmap.width(), bitmap.height()); - + draw_list_->AddImage( (ImTextureID)(intptr_t)bitmap.texture(), ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y), @@ -1126,8 +1169,8 @@ void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size, } // TODO: Add parameters for sizing and positioning -void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) { - for (const auto &[key, value] : gfx_bin) { +void Canvas::DrawBitmapTable(const BitmapTable& gfx_bin) { + for (const auto& [key, value] : gfx_bin) { int offset = 0x40 * (key + 1); int top_left_y = canvas_p0_.y + 2; if (key >= 1) { @@ -1140,20 +1183,22 @@ void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) { } void Canvas::DrawOutline(int x, int y, int w, int h) { - CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, IM_COL32(255, 255, 255, 200)); + CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, + IM_COL32(255, 255, 255, 200)); } void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) { - CanvasUtils::DrawCanvasOutlineWithColor(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color); + CanvasUtils::DrawCanvasOutlineWithColor(draw_list_, canvas_p0_, scrolling_, x, + y, w, h, color); } void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) { - CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color); + CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, + color); } -void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, - int tile_size, float scale, - int local_map_size, +void Canvas::DrawBitmapGroup(std::vector& group, gfx::Tilemap& tilemap, + int tile_size, float scale, int local_map_size, ImVec2 total_map_size) { if (selected_points_.size() != 2) { // points_ should contain exactly two points @@ -1165,13 +1210,14 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, } // OPTIMIZATION: Use optimized rendering for large groups to improve performance - bool use_optimized_rendering = group.size() > 128; // Optimize for large selections - + bool use_optimized_rendering = + group.size() > 128; // Optimize for large selections + // Use provided map sizes for proper boundary handling const int small_map = local_map_size; const float large_map_width = total_map_size.x; const float large_map_height = total_map_size.y; - + // Pre-calculate common values to avoid repeated computation const float tile_scale = tile_size * scale; const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; @@ -1190,8 +1236,10 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, int end_tile_y = static_cast(std::floor(rect_bottom_right.y / (tile_size * scale))); - if (start_tile_x > end_tile_x) std::swap(start_tile_x, end_tile_x); - if (start_tile_y > end_tile_y) std::swap(start_tile_y, end_tile_y); + if (start_tile_x > end_tile_x) + std::swap(start_tile_x, end_tile_x); + if (start_tile_y > end_tile_y) + std::swap(start_tile_y, end_tile_y); // Calculate the size of the rectangle in 16x16 grid form int rect_width = (end_tile_x - start_tile_x) * tile_size; @@ -1207,7 +1255,7 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, if (i >= static_cast(group.size())) { break; } - + int tile_id = group[i]; // Check if tile_id is within the range of tile16_individual_ @@ -1218,37 +1266,44 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, int tile_pos_y = (y + start_tile_y) * tile_size * scale; // OPTIMIZATION: Use pre-calculated values for better performance with large selections - if (tilemap.atlas.is_active() && tilemap.atlas.texture() && atlas_tiles_per_row > 0) { - int atlas_tile_x = (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x; - int atlas_tile_y = (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y; - + if (tilemap.atlas.is_active() && tilemap.atlas.texture() && + atlas_tiles_per_row > 0) { + int atlas_tile_x = + (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x; + int atlas_tile_y = + (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y; + // Simple bounds check - if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() && + if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() && atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) { - + // Calculate UV coordinates once for efficiency const float atlas_width = static_cast(tilemap.atlas.width()); - const float atlas_height = static_cast(tilemap.atlas.height()); - ImVec2 uv0 = ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height); - ImVec2 uv1 = ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width, - (atlas_tile_y + tilemap.tile_size.y) / atlas_height); - + const float atlas_height = + static_cast(tilemap.atlas.height()); + ImVec2 uv0 = + ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height); + ImVec2 uv1 = + ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width, + (atlas_tile_y + tilemap.tile_size.y) / atlas_height); + // Calculate screen positions float screen_x = canvas_p0_.x + scrolling_.x + tile_pos_x; float screen_y = canvas_p0_.y + scrolling_.y + tile_pos_y; float screen_w = tilemap.tile_size.x * scale; float screen_h = tilemap.tile_size.y * scale; - + // Use higher alpha for large selections to make them more visible - uint32_t alpha_color = use_optimized_rendering ? - IM_COL32(255, 255, 255, 200) : IM_COL32(255, 255, 255, 150); - + uint32_t alpha_color = use_optimized_rendering + ? IM_COL32(255, 255, 255, 200) + : IM_COL32(255, 255, 255, 150); + // Draw from atlas texture with optimized parameters draw_list_->AddImage( (ImTextureID)(intptr_t)tilemap.atlas.texture(), ImVec2(screen_x, screen_y), - ImVec2(screen_x + screen_w, screen_y + screen_h), - uv0, uv1, alpha_color); + ImVec2(screen_x + screen_w, screen_y + screen_h), uv0, uv1, + alpha_color); } } } @@ -1263,48 +1318,50 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, // Performance optimization completed - tiles are now rendered with pre-calculated values // Reposition rectangle to follow mouse, but clamp to prevent wrapping across map boundaries - const ImGuiIO &io = GetIO(); + const ImGuiIO& io = GetIO(); const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); - + // CRITICAL FIX: Clamp BEFORE grid alignment for smoother dragging behavior // This prevents the rectangle from even attempting to cross boundaries during drag ImVec2 clamped_mouse_pos = mouse_pos; - + if (config_.clamp_rect_to_local_maps) { // Calculate which local map the mouse is in int mouse_local_map_x = static_cast(mouse_pos.x) / small_map; int mouse_local_map_y = static_cast(mouse_pos.y) / small_map; - + // Calculate where the rectangle END would be if we place it at mouse position float potential_end_x = mouse_pos.x + rect_width; float potential_end_y = mouse_pos.y + rect_height; - + // Check if this would cross local map boundary (512x512 blocks) int potential_end_map_x = static_cast(potential_end_x) / small_map; int potential_end_map_y = static_cast(potential_end_y) / small_map; - + // Clamp mouse position to prevent crossing during drag if (potential_end_map_x != mouse_local_map_x) { // Would cross horizontal boundary - clamp mouse to safe zone float max_mouse_x = (mouse_local_map_x + 1) * small_map - rect_width; clamped_mouse_pos.x = std::min(mouse_pos.x, max_mouse_x); } - + if (potential_end_map_y != mouse_local_map_y) { // Would cross vertical boundary - clamp mouse to safe zone float max_mouse_y = (mouse_local_map_y + 1) * small_map - rect_height; clamped_mouse_pos.y = std::min(mouse_pos.y, max_mouse_y); } } - + // Now grid-align the clamped position auto new_start_pos = AlignPosToGrid(clamped_mouse_pos, tile_size * scale); - + // Additional safety: clamp to overall map bounds - new_start_pos.x = std::clamp(new_start_pos.x, 0.0f, large_map_width - rect_width); - new_start_pos.y = std::clamp(new_start_pos.y, 0.0f, large_map_height - rect_height); - + new_start_pos.x = + std::clamp(new_start_pos.x, 0.0f, large_map_width - rect_width); + new_start_pos.y = + std::clamp(new_start_pos.y, 0.0f, large_map_height - rect_height); + selected_points_.clear(); selected_points_.push_back(new_start_pos); selected_points_.push_back( @@ -1313,22 +1370,26 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, } void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) { - CanvasUtils::DrawCanvasRect(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color, config_.global_scale); + CanvasUtils::DrawCanvasRect(draw_list_, canvas_p0_, scrolling_, x, y, w, h, + color, config_.global_scale); } void Canvas::DrawText(std::string text, int x, int y) { - CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y, config_.global_scale); + CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y, + config_.global_scale); } void Canvas::DrawGridLines(float grid_step) { - CanvasUtils::DrawCanvasGridLines(draw_list_, canvas_p0_, canvas_p1_, scrolling_, grid_step, config_.global_scale); + CanvasUtils::DrawCanvasGridLines(draw_list_, canvas_p0_, canvas_p1_, + scrolling_, grid_step, config_.global_scale); } void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) { // Draw grid + all lines in the canvas draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); if (enable_grid_) { - if (custom_step_ != 0.f) grid_step = custom_step_; + if (custom_step_ != 0.f) + grid_step = custom_step_; grid_step *= global_scale_; // Apply global scale to grid step DrawGridLines(grid_step); @@ -1361,31 +1422,33 @@ void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) { } void Canvas::DrawCustomHighlight(float grid_step) { - CanvasUtils::DrawCustomHighlight(draw_list_, canvas_p0_, scrolling_, highlight_tile_id, grid_step); + CanvasUtils::DrawCustomHighlight(draw_list_, canvas_p0_, scrolling_, + highlight_tile_id, grid_step); } void Canvas::DrawGrid(float grid_step, int tile_id_offset) { - if (config_.grid_step != 0.f) grid_step = config_.grid_step; - + if (config_.grid_step != 0.f) + grid_step = config_.grid_step; + // Create render context for utilities CanvasUtils::CanvasRenderContext ctx = { - .draw_list = draw_list_, - .canvas_p0 = canvas_p0_, - .canvas_p1 = canvas_p1_, - .scrolling = scrolling_, - .global_scale = config_.global_scale, - .enable_grid = config_.enable_grid, - .enable_hex_labels = config_.enable_hex_labels, - .grid_step = grid_step - }; - + .draw_list = draw_list_, + .canvas_p0 = canvas_p0_, + .canvas_p1 = canvas_p1_, + .scrolling = scrolling_, + .global_scale = config_.global_scale, + .enable_grid = config_.enable_grid, + .enable_hex_labels = config_.enable_hex_labels, + .grid_step = grid_step}; + // Use high-level utility function CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id); - + // Draw custom labels if enabled if (config_.enable_custom_labels) { draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); - CanvasUtils::DrawCanvasLabels(ctx, labels_, current_labels_, tile_id_offset); + CanvasUtils::DrawCanvasLabels(ctx, labels_, current_labels_, + tile_id_offset); draw_list_->PopClipRect(); } } @@ -1393,23 +1456,22 @@ void Canvas::DrawGrid(float grid_step, int tile_id_offset) { void Canvas::DrawOverlay() { // Create render context for utilities CanvasUtils::CanvasRenderContext ctx = { - .draw_list = draw_list_, - .canvas_p0 = canvas_p0_, - .canvas_p1 = canvas_p1_, - .scrolling = scrolling_, - .global_scale = config_.global_scale, - .enable_grid = config_.enable_grid, - .enable_hex_labels = config_.enable_hex_labels, - .grid_step = config_.grid_step - }; - + .draw_list = draw_list_, + .canvas_p0 = canvas_p0_, + .canvas_p1 = canvas_p1_, + .scrolling = scrolling_, + .global_scale = config_.global_scale, + .enable_grid = config_.enable_grid, + .enable_hex_labels = config_.enable_hex_labels, + .grid_step = config_.grid_step}; + // Use high-level utility function with local points (synchronized from interaction handler) CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_); } void Canvas::DrawLayeredElements() { // Based on ImGui demo, should be adapted to use for OAM - ImDrawList *draw_list = ImGui::GetWindowDrawList(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); { Text("Blue shape is drawn first: appears in back"); Text("Red shape is drawn after: appears in front"); @@ -1448,9 +1510,9 @@ void Canvas::DrawLayeredElements() { } } -void BeginCanvas(Canvas &canvas, ImVec2 child_size) { +void BeginCanvas(Canvas& canvas, ImVec2 child_size) { gui::BeginPadding(1); - + // Use improved canvas sizing for table integration ImVec2 effective_size = child_size; if (child_size.x == 0 && child_size.y == 0) { @@ -1461,15 +1523,15 @@ void BeginCanvas(Canvas &canvas, ImVec2 child_size) { effective_size = canvas.GetCurrentSize(); } } - + ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true, - ImGuiWindowFlags_AlwaysVerticalScrollbar); + ImGuiWindowFlags_AlwaysVerticalScrollbar); canvas.DrawBackground(); gui::EndPadding(); canvas.DrawContextMenu(); } -void EndCanvas(Canvas &canvas) { +void EndCanvas(Canvas& canvas) { canvas.DrawGrid(); canvas.DrawOverlay(); ImGui::EndChild(); @@ -1477,7 +1539,7 @@ void EndCanvas(Canvas &canvas) { void GraphicsBinCanvasPipeline(int width, int height, int tile_size, int num_sheets_to_load, int canvas_id, - bool is_loaded, gfx::BitmapTable &graphics_bin) { + bool is_loaded, gfx::BitmapTable& graphics_bin) { gui::Canvas canvas; if (ImGuiID child_id = ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id); @@ -1486,7 +1548,7 @@ void GraphicsBinCanvasPipeline(int width, int height, int tile_size, canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1)); canvas.DrawContextMenu(); if (is_loaded) { - for (const auto &[key, value] : graphics_bin) { + for (const auto& [key, value] : graphics_bin) { int offset = height * (key + 1); int top_left_y = canvas.zero_point().y + 2; if (key >= 1) { @@ -1506,10 +1568,10 @@ void GraphicsBinCanvasPipeline(int width, int height, int tile_size, ImGui::EndChild(); } -void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, +void BitmapCanvasPipeline(gui::Canvas& canvas, gfx::Bitmap& bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id) { - auto draw_canvas = [&](gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, + auto draw_canvas = [&](gui::Canvas& canvas, gfx::Bitmap& bitmap, int width, int height, int tile_size, bool is_loaded) { canvas.DrawBackground(ImVec2(width + 1, height + 1)); canvas.DrawContextMenu(); @@ -1532,28 +1594,29 @@ void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, } } -void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, - const std::string& label, bool auto_resize) { +void TableCanvasPipeline(gui::Canvas& canvas, gfx::Bitmap& bitmap, + const std::string& label, bool auto_resize) { // Configure canvas for table integration canvas.SetAutoResize(auto_resize); - + if (auto_resize && bitmap.is_active()) { // Auto-calculate size based on bitmap content ImVec2 content_size = ImVec2(bitmap.width(), bitmap.height()); - ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize(content_size, canvas.GetGlobalScale()); + ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize( + content_size, canvas.GetGlobalScale()); canvas.SetCanvasSize(preferred_size); } - + // Begin table-aware canvas if (canvas.BeginTableCanvas(label)) { // Draw the canvas content canvas.DrawBackground(); canvas.DrawContextMenu(); - + if (bitmap.is_active()) { canvas.DrawBitmap(bitmap, 2, 2, canvas.GetGlobalScale()); } - + canvas.DrawGrid(); canvas.DrawOverlay(); } @@ -1575,77 +1638,86 @@ void Canvas::ShowAdvancedCanvasProperties() { modal_config.is_draggable = draggable_; modal_config.auto_resize = config_.auto_resize; modal_config.scrolling = scrolling_; - modal_config.on_config_changed = [this](const canvas::CanvasConfig& updated_config) { - // Update legacy variables when config changes - enable_grid_ = updated_config.enable_grid; - enable_hex_tile_labels_ = updated_config.enable_hex_labels; - enable_custom_labels_ = updated_config.enable_custom_labels; - }; - modal_config.on_scale_changed = [this](const canvas::CanvasConfig& updated_config) { - global_scale_ = updated_config.global_scale; - scrolling_ = updated_config.scrolling; - }; - + modal_config.on_config_changed = + [this](const canvas::CanvasConfig& updated_config) { + // Update legacy variables when config changes + enable_grid_ = updated_config.enable_grid; + enable_hex_tile_labels_ = updated_config.enable_hex_labels; + enable_custom_labels_ = updated_config.enable_custom_labels; + }; + modal_config.on_scale_changed = + [this](const canvas::CanvasConfig& updated_config) { + global_scale_ = updated_config.global_scale; + scrolling_ = updated_config.scrolling; + }; + modals_->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_); return; } - + // Fallback to legacy modal system - if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Advanced Canvas Configuration"); ImGui::Separator(); - + // Canvas properties (read-only info) ImGui::Text("Canvas Properties"); ImGui::Text("ID: %s", canvas_id_.c_str()); - ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y); - ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x, config_.content_size.y); + ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, + config_.canvas_size.y); + ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x, + config_.content_size.y); ImGui::Text("Global Scale: %.3f", config_.global_scale); ImGui::Text("Grid Step: %.1f", config_.grid_step); - + if (config_.content_size.x > 0 && config_.content_size.y > 0) { ImVec2 min_size = GetMinimumSize(); ImVec2 preferred_size = GetPreferredSize(); ImGui::Text("Minimum Size: %.0f x %.0f", min_size.x, min_size.y); - ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x, preferred_size.y); + ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x, + preferred_size.y); } - + // Editable properties using new config system ImGui::Separator(); ImGui::Text("View Settings"); if (ImGui::Checkbox("Enable Grid", &config_.enable_grid)) { - enable_grid_ = config_.enable_grid; // Legacy sync + enable_grid_ = config_.enable_grid; // Legacy sync } if (ImGui::Checkbox("Enable Hex Labels", &config_.enable_hex_labels)) { - enable_hex_tile_labels_ = config_.enable_hex_labels; // Legacy sync + enable_hex_tile_labels_ = config_.enable_hex_labels; // Legacy sync } - if (ImGui::Checkbox("Enable Custom Labels", &config_.enable_custom_labels)) { - enable_custom_labels_ = config_.enable_custom_labels; // Legacy sync + if (ImGui::Checkbox("Enable Custom Labels", + &config_.enable_custom_labels)) { + enable_custom_labels_ = config_.enable_custom_labels; // Legacy sync } if (ImGui::Checkbox("Enable Context Menu", &config_.enable_context_menu)) { - enable_context_menu_ = config_.enable_context_menu; // Legacy sync + enable_context_menu_ = config_.enable_context_menu; // Legacy sync } if (ImGui::Checkbox("Draggable", &config_.is_draggable)) { - draggable_ = config_.is_draggable; // Legacy sync + draggable_ = config_.is_draggable; // Legacy sync } if (ImGui::Checkbox("Auto Resize for Tables", &config_.auto_resize)) { // Auto resize setting changed } - + // Grid controls ImGui::Separator(); ImGui::Text("Grid Configuration"); - if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f, "%.1f")) { - custom_step_ = config_.grid_step; // Legacy sync + if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f, + "%.1f")) { + custom_step_ = config_.grid_step; // Legacy sync } - + // Scale controls ImGui::Separator(); ImGui::Text("Scale Configuration"); - if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) { - global_scale_ = config_.global_scale; // Legacy sync + if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f, + "%.2f")) { + global_scale_ = config_.global_scale; // Legacy sync } - + // Scrolling controls ImGui::Separator(); ImGui::Text("Scrolling Configuration"); @@ -1656,11 +1728,15 @@ void Canvas::ShowAdvancedCanvasProperties() { ImGui::SameLine(); if (ImGui::Button("Center View")) { if (bitmap_) { - scrolling_ = ImVec2(-(bitmap_->width() * config_.global_scale - config_.canvas_size.x) / 2.0f, - -(bitmap_->height() * config_.global_scale - config_.canvas_size.y) / 2.0f); + scrolling_ = ImVec2( + -(bitmap_->width() * config_.global_scale - config_.canvas_size.x) / + 2.0f, + -(bitmap_->height() * config_.global_scale - + config_.canvas_size.y) / + 2.0f); } } - + if (ImGui::Button("Close")) { ImGui::CloseCurrentPopup(); } @@ -1685,35 +1761,39 @@ void Canvas::ShowScalingControls() { modal_config.is_draggable = draggable_; modal_config.auto_resize = config_.auto_resize; modal_config.scrolling = scrolling_; - modal_config.on_config_changed = [this](const canvas::CanvasConfig& updated_config) { - // Update legacy variables when config changes - enable_grid_ = updated_config.enable_grid; - enable_hex_tile_labels_ = updated_config.enable_hex_labels; - enable_custom_labels_ = updated_config.enable_custom_labels; - enable_context_menu_ = updated_config.enable_context_menu; - }; - modal_config.on_scale_changed = [this](const canvas::CanvasConfig& updated_config) { - draggable_ = updated_config.is_draggable; - custom_step_ = updated_config.grid_step; - global_scale_ = updated_config.global_scale; - scrolling_ = updated_config.scrolling; - }; - + modal_config.on_config_changed = + [this](const canvas::CanvasConfig& updated_config) { + // Update legacy variables when config changes + enable_grid_ = updated_config.enable_grid; + enable_hex_tile_labels_ = updated_config.enable_hex_labels; + enable_custom_labels_ = updated_config.enable_custom_labels; + enable_context_menu_ = updated_config.enable_context_menu; + }; + modal_config.on_scale_changed = + [this](const canvas::CanvasConfig& updated_config) { + draggable_ = updated_config.is_draggable; + custom_step_ = updated_config.grid_step; + global_scale_ = updated_config.global_scale; + scrolling_ = updated_config.scrolling; + }; + modals_->ShowScalingControls(canvas_id_, modal_config); return; } - + // Fallback to legacy modal system - if (ImGui::BeginPopupModal("Scaling Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::BeginPopupModal("Scaling Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Canvas Scaling and Display Controls"); ImGui::Separator(); - + // Global scale with new config system ImGui::Text("Global Scale: %.3f", config_.global_scale); - if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) { - global_scale_ = config_.global_scale; // Legacy sync + if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f, + "%.2f")) { + global_scale_ = config_.global_scale; // Legacy sync } - + // Preset scale buttons ImGui::Text("Preset Scales:"); if (ImGui::Button("0.25x")) { @@ -1745,15 +1825,16 @@ void Canvas::ShowScalingControls() { config_.global_scale = 8.0f; global_scale_ = config_.global_scale; } - + // Grid configuration ImGui::Separator(); ImGui::Text("Grid Configuration"); ImGui::Text("Grid Step: %.1f", config_.grid_step); - if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f, "%.1f")) { - custom_step_ = config_.grid_step; // Legacy sync + if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f, + "%.1f")) { + custom_step_ = config_.grid_step; // Legacy sync } - + // Grid size presets ImGui::Text("Grid Presets:"); if (ImGui::Button("8x8")) { @@ -1775,21 +1856,23 @@ void Canvas::ShowScalingControls() { config_.grid_step = 64.0f; custom_step_ = config_.grid_step; } - + // Canvas size info ImGui::Separator(); ImGui::Text("Canvas Information"); - ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y); - ImGui::Text("Scaled Size: %.0f x %.0f", - config_.canvas_size.x * config_.global_scale, - config_.canvas_size.y * config_.global_scale); + ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, + config_.canvas_size.y); + ImGui::Text("Scaled Size: %.0f x %.0f", + config_.canvas_size.x * config_.global_scale, + config_.canvas_size.y * config_.global_scale); if (bitmap_) { ImGui::Text("Bitmap Size: %d x %d", bitmap_->width(), bitmap_->height()); - ImGui::Text("Effective Scale: %.3f x %.3f", - (config_.canvas_size.x * config_.global_scale) / bitmap_->width(), - (config_.canvas_size.y * config_.global_scale) / bitmap_->height()); + ImGui::Text( + "Effective Scale: %.3f x %.3f", + (config_.canvas_size.x * config_.global_scale) / bitmap_->width(), + (config_.canvas_size.y * config_.global_scale) / bitmap_->height()); } - + if (ImGui::Button("Close")) { ImGui::CloseCurrentPopup(); } @@ -1800,22 +1883,23 @@ void Canvas::ShowScalingControls() { // BPP format management methods void Canvas::ShowBppFormatSelector() { if (!bpp_format_ui_) { - bpp_format_ui_ = std::make_unique(canvas_id_ + "_bpp_format"); + bpp_format_ui_ = + std::make_unique(canvas_id_ + "_bpp_format"); } - + if (bitmap_) { - bpp_format_ui_->RenderFormatSelector(bitmap_, bitmap_->palette(), - [this](gfx::BppFormat format) { - ConvertBitmapFormat(format); - }); + bpp_format_ui_->RenderFormatSelector( + bitmap_, bitmap_->palette(), + [this](gfx::BppFormat format) { ConvertBitmapFormat(format); }); } } void Canvas::ShowBppAnalysis() { if (!bpp_format_ui_) { - bpp_format_ui_ = std::make_unique(canvas_id_ + "_bpp_format"); + bpp_format_ui_ = + std::make_unique(canvas_id_ + "_bpp_format"); } - + if (bitmap_) { bpp_format_ui_->RenderAnalysisPanel(*bitmap_, bitmap_->palette()); } @@ -1823,39 +1907,42 @@ void Canvas::ShowBppAnalysis() { void Canvas::ShowBppConversionDialog() { if (!bpp_conversion_dialog_) { - bpp_conversion_dialog_ = std::make_unique(canvas_id_ + "_bpp_conversion"); + bpp_conversion_dialog_ = std::make_unique( + canvas_id_ + "_bpp_conversion"); } - + if (bitmap_) { - bpp_conversion_dialog_->Show(*bitmap_, bitmap_->palette(), - [this](gfx::BppFormat format, bool preserve_palette) { - ConvertBitmapFormat(format); - }); + bpp_conversion_dialog_->Show( + *bitmap_, bitmap_->palette(), + [this](gfx::BppFormat format, bool preserve_palette) { + ConvertBitmapFormat(format); + }); } - + bpp_conversion_dialog_->Render(); } bool Canvas::ConvertBitmapFormat(gfx::BppFormat target_format) { - if (!bitmap_) return false; - + if (!bitmap_) + return false; + gfx::BppFormat current_format = GetCurrentBppFormat(); if (current_format == target_format) { - return true; // No conversion needed + return true; // No conversion needed } - + try { // Convert the bitmap data auto converted_data = gfx::BppFormatManager::Get().ConvertFormat( - bitmap_->vector(), current_format, target_format, - bitmap_->width(), bitmap_->height()); - + bitmap_->vector(), current_format, target_format, bitmap_->width(), + bitmap_->height()); + // Update the bitmap with converted data bitmap_->set_data(converted_data); - + // Update the renderer core::Renderer::Get().UpdateBitmap(bitmap_); - + return true; } catch (const std::exception& e) { SDL_Log("Failed to convert bitmap format: %s", e.what()); @@ -1864,10 +1951,11 @@ bool Canvas::ConvertBitmapFormat(gfx::BppFormat target_format) { } gfx::BppFormat Canvas::GetCurrentBppFormat() const { - if (!bitmap_) return gfx::BppFormat::kBpp8; - + if (!bitmap_) + return gfx::BppFormat::kBpp8; + return gfx::BppFormatManager::Get().DetectFormat( - bitmap_->vector(), bitmap_->width(), bitmap_->height()); + bitmap_->vector(), bitmap_->width(), bitmap_->height()); } } // namespace yaze::gui diff --git a/src/app/gui/canvas/canvas_utils_moved.cc b/src/app/gui/canvas/canvas_utils_moved.cc index 9ca8ee97..f035f0a7 100644 --- a/src/app/gui/canvas/canvas_utils_moved.cc +++ b/src/app/gui/canvas/canvas_utils_moved.cc @@ -86,12 +86,12 @@ bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) { } palette_manager.palettes_loaded = true; - util::logf("Canvas: Loaded %zu ROM palette groups", + LOG_INFO("Canvas", "Loaded %zu ROM palette groups", palette_manager.rom_palette_groups.size()); return true; } catch (const std::exception& e) { - util::logf("Canvas: Failed to load ROM palette groups: %s", e.what()); + LOG_ERROR("Canvas", "Failed to load ROM palette groups"); return false; } } @@ -117,12 +117,12 @@ bool ApplyPaletteGroup(gfx::Bitmap* bitmap, } Renderer::Get().UpdateBitmap(bitmap); - util::logf("Canvas: Applied palette group %d, index %d to bitmap", + LOG_INFO("Canvas", "Applied palette group %d, index %d to bitmap", group_index, palette_index); return true; } catch (const std::exception& e) { - util::logf("Canvas: Failed to apply palette: %s", e.what()); + LOG_ERROR("Canvas", "Failed to apply palette"); return false; } } diff --git a/src/app/gui/canvas_utils.cc b/src/app/gui/canvas_utils.cc index fee6a36c..2d9f91f4 100644 --- a/src/app/gui/canvas_utils.cc +++ b/src/app/gui/canvas_utils.cc @@ -85,12 +85,12 @@ bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) { } palette_manager.palettes_loaded = true; - util::logf("Canvas: Loaded %zu ROM palette groups", + LOG_INFO("Canvas", "Loaded %zu ROM palette groups", palette_manager.rom_palette_groups.size()); return true; } catch (const std::exception& e) { - util::logf("Canvas: Failed to load ROM palette groups: %s", e.what()); + LOG_ERROR("Canvas", "Failed to load ROM palette groups"); return false; } } @@ -116,12 +116,12 @@ bool ApplyPaletteGroup(gfx::Bitmap* bitmap, } Renderer::Get().UpdateBitmap(bitmap); - util::logf("Canvas: Applied palette group %d, index %d to bitmap", + LOG_INFO("Canvas", "Applied palette group %d, index %d to bitmap", group_index, palette_index); return true; } catch (const std::exception& e) { - util::logf("Canvas: Failed to apply palette: %s", e.what()); + LOG_ERROR("Canvas", "Failed to apply palette"); return false; } } diff --git a/src/app/gui/enhanced_palette_editor.cc b/src/app/gui/enhanced_palette_editor.cc index 042db370..0cafe891 100644 --- a/src/app/gui/enhanced_palette_editor.cc +++ b/src/app/gui/enhanced_palette_editor.cc @@ -174,12 +174,9 @@ bool EnhancedPaletteEditor::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index current_group_index_ = group_index; current_palette_index_ = palette_index; - util::logf("Applied ROM palette: %s (index %d)", - palette_group_names_[group_index].c_str(), palette_index); return true; } catch (const std::exception& e) { - util::logf("Failed to apply ROM palette: %s", e.what()); return false; } } @@ -455,10 +452,9 @@ void EnhancedPaletteEditor::LoadROMPalettes() { } rom_palettes_loaded_ = true; - util::logf("Enhanced Palette Editor: Loaded %zu ROM palette groups", rom_palette_groups_.size()); } catch (const std::exception& e) { - util::logf("Enhanced Palette Editor: Failed to load ROM palettes: %s", e.what()); + LOG_ERROR("Enhanced Palette Editor", "Failed to load ROM palettes"); } } diff --git a/src/app/gui/feature_flags_menu.h b/src/app/gui/feature_flags_menu.h new file mode 100644 index 00000000..5586fda7 --- /dev/null +++ b/src/app/gui/feature_flags_menu.h @@ -0,0 +1,72 @@ +#ifndef YAZE_APP_GUI_FEATURE_FLAGS_MENU_H +#define YAZE_APP_GUI_FEATURE_FLAGS_MENU_H + +#include "app/core/features.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace gui { + +using ImGui::BeginMenu; +using ImGui::Checkbox; +using ImGui::EndMenu; +using ImGui::MenuItem; +using ImGui::Separator; + +struct FlagsMenu { + void DrawOverworldFlags() { + Checkbox("Enable Overworld Sprites", + &core::FeatureFlags::get().overworld.kDrawOverworldSprites); + Separator(); + Checkbox("Save Overworld Maps", + &core::FeatureFlags::get().overworld.kSaveOverworldMaps); + Checkbox("Save Overworld Entrances", + &core::FeatureFlags::get().overworld.kSaveOverworldEntrances); + Checkbox("Save Overworld Exits", + &core::FeatureFlags::get().overworld.kSaveOverworldExits); + Checkbox("Save Overworld Items", + &core::FeatureFlags::get().overworld.kSaveOverworldItems); + Checkbox("Save Overworld Properties", + &core::FeatureFlags::get().overworld.kSaveOverworldProperties); + Checkbox("Enable Custom Overworld Features", + &core::FeatureFlags::get().overworld.kLoadCustomOverworld); + ImGui::SameLine(); + if (ImGui::Button("?")) { + ImGui::OpenPopup("CustomOverworldHelp"); + } + if (ImGui::BeginPopup("CustomOverworldHelp")) { + ImGui::Text("This flag enables ZSCustomOverworld features."); + ImGui::Text("If ZSCustomOverworld ASM is already applied to the ROM,"); + ImGui::Text("features are auto-enabled regardless of this flag."); + ImGui::Text("For vanilla ROMs, enable this to use custom features."); + ImGui::EndPopup(); + } + Checkbox("Apply ZSCustomOverworld ASM", + &core::FeatureFlags::get().overworld.kApplyZSCustomOverworldASM); + } + + void DrawDungeonFlags() { + Checkbox("Save Dungeon Maps", &core::FeatureFlags::get().kSaveDungeonMaps); + } + + void DrawResourceFlags() { + Checkbox("Save All Palettes", &core::FeatureFlags::get().kSaveAllPalettes); + Checkbox("Save Gfx Groups", &core::FeatureFlags::get().kSaveGfxGroups); + Checkbox("Save Graphics Sheets", &core::FeatureFlags::get().kSaveGraphicsSheet); + } + + void DrawSystemFlags() { + Checkbox("Enable Console Logging", &core::FeatureFlags::get().kLogToConsole); + Checkbox("Enable Performance Monitoring", + &core::FeatureFlags::get().kEnablePerformanceMonitoring); + Checkbox("Log Instructions to Emulator Debugger", + &core::FeatureFlags::get().kLogInstructions); + Checkbox("Use Native File Dialog (NFD)", + &core::FeatureFlags::get().kUseNativeFileDialog); + } +}; + +} // namespace gui +} // namespace yaze + +#endif // YAZE_APP_GUI_FEATURE_FLAGS_MENU_H diff --git a/src/app/gui/gui.cmake b/src/app/gui/gui.cmake index 3c6d1504..1edadb2f 100644 --- a/src/app/gui/gui.cmake +++ b/src/app/gui/gui.cmake @@ -2,6 +2,7 @@ set( YAZE_GUI_SRC app/gui/modules/asset_browser.cc app/gui/modules/text_editor.cc + app/gui/widgets/agent_chat_widget.cc app/gui/canvas.cc app/gui/canvas_utils.cc app/gui/enhanced_palette_editor.cc @@ -12,6 +13,7 @@ set( app/gui/background_renderer.cc app/gui/bpp_format_ui.cc app/gui/widget_id_registry.cc + app/gui/widget_auto_register.cc # Canvas system components app/gui/canvas/canvas_modals.cc app/gui/canvas/canvas_context_menu.cc diff --git a/src/app/gui/theme_manager.cc b/src/app/gui/theme_manager.cc index 6540140a..84452ddb 100644 --- a/src/app/gui/theme_manager.cc +++ b/src/app/gui/theme_manager.cc @@ -125,7 +125,7 @@ void ThemeManager::InitializeBuiltInThemes() { // Load all available theme files dynamically auto status = LoadAllAvailableThemes(); if (!status.ok()) { - util::logf("Warning: Failed to load some theme files: %s", status.message().data()); + LOG_ERROR("Theme Manager", "Failed to load some theme files"); } // Ensure we have a valid current theme (Classic is already set above) @@ -326,7 +326,7 @@ void ThemeManager::ApplyTheme(const std::string& theme_name) { // Fallback to YAZE Tre if theme not found auto fallback_status = LoadTheme("YAZE Tre"); if (!fallback_status.ok()) { - util::logf("Failed to load fallback theme: %s", fallback_status.message().data()); + LOG_ERROR("Theme Manager", "Failed to load fallback theme"); } } } @@ -426,7 +426,7 @@ void ThemeManager::ShowThemeSelector(bool* p_open) { name.c_str()).c_str(), ImVec2(-1, 40))) { auto status = LoadTheme(name); // Use LoadTheme instead of ApplyTheme to ensure correct tracking if (!status.ok()) { - util::logf("Failed to load theme %s: %s", name.c_str(), status.message().data()); + LOG_ERROR("Theme Manager", "Failed to load theme %s", name.c_str()); } } @@ -460,7 +460,7 @@ void ThemeManager::ShowThemeSelector(bool* p_open) { if (ImGui::Button(absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) { auto status = RefreshAvailableThemes(); if (!status.ok()) { - util::logf("Failed to refresh themes: %s", status.message().data()); + LOG_ERROR("Theme Manager", "Failed to refresh themes"); } } @@ -1019,7 +1019,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { if (!current_file_path.empty()) { auto status = SaveThemeToFile(current_theme_, current_file_path); if (!status.ok()) { - util::logf("Failed to save theme: %s", status.message().data()); + LOG_ERROR("Theme Manager", "Failed to save theme"); } } else { // No existing file, prompt for new location @@ -1027,7 +1027,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { if (!file_path.empty()) { auto status = SaveThemeToFile(current_theme_, file_path); if (!status.ok()) { - util::logf("Failed to save theme: %s", status.message().data()); + LOG_ERROR("Theme Manager", "Failed to save theme"); } } } @@ -1038,7 +1038,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { if (!file_path.empty()) { auto status = SaveThemeToFile(current_theme_, file_path); if (!status.ok()) { - util::logf("Failed to save theme: %s", status.message().data()); + LOG_ERROR("Theme Manager", "Failed to save theme"); } } } @@ -1831,7 +1831,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { ApplyTheme(edit_theme); theme_backup_made = false; // Reset backup state since theme is now applied } else { - util::logf("Failed to save over current theme: %s", status.message().data()); + LOG_ERROR("Theme Manager", "Failed to save over current theme"); } } @@ -1873,7 +1873,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { themes_[edit_theme.name] = edit_theme; ApplyTheme(edit_theme); } else { - util::logf("Failed to save theme: %s", status.message().data()); + LOG_ERROR("Theme Manager", "Failed to save theme"); } } } @@ -1981,7 +1981,7 @@ std::vector ThemeManager::DiscoverAvailableThemeFiles() const { } #endif } catch (const std::exception& e) { - util::logf("Error scanning directory %s: %s", search_path.c_str(), e.what()); + LOG_ERROR("Theme Manager", "Error scanning directory %s", search_path.c_str()); } } diff --git a/src/app/zelda3/dungeon/room.cc b/src/app/zelda3/dungeon/room.cc index 404a0e50..d2656b6c 100644 --- a/src/app/zelda3/dungeon/room.cc +++ b/src/app/zelda3/dungeon/room.cc @@ -14,7 +14,6 @@ #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 { @@ -198,6 +197,7 @@ Room LoadRoomFromRom(Rom *rom, int room_id) { room.LoadBlocks(); room.LoadPits(); + room.SetLoaded(true); return room; } @@ -318,9 +318,7 @@ void Room::RenderRoomGraphics() { // Validate palette ID and fall back to palette 0 if invalid if (palette_id < 0 || palette_id >= num_palettes) { - std::printf("WARNING: Room %d has invalid palette_id=%d (max=%d), falling back to palette 0\n", - room_id_, palette_id, num_palettes - 1); - palette_id = 0; + //palette_id = 0; } // Load the 90-color dungeon palette directly @@ -573,12 +571,6 @@ void Room::ParseObjectsFromLocation(int objects_location) { uint8_t b1 = 0; uint8_t b2 = 0; uint8_t b3 = 0; - uint8_t posX = 0; - uint8_t posY = 0; - uint8_t sizeX = 0; - uint8_t sizeY = 0; - uint8_t sizeXY = 0; - short oid = 0; int layer = 0; bool door = false; bool end_read = false; @@ -852,7 +844,9 @@ void Room::LoadSprites() { rom_data[sprite_pointer + (room_id_ * 2)]; int sprite_address = SnesToPc(sprite_address_snes); - bool sortsprites = rom_data[sprite_address] == 1; + if (rom_data[sprite_address] == 1) { + // sortsprites is unused + } sprite_address += 1; while (true) { @@ -897,7 +891,7 @@ void Room::LoadChests() { size_t clength = (rom_data[chests_length_pointer + 1] << 8) + (rom_data[chests_length_pointer]); - for (int i = 0; i < clength; i++) { + for (size_t i = 0; i < clength; i++) { if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) & 0x7FFF) == room_id_) { // There's a chest in that room ! @@ -945,7 +939,7 @@ void Room::LoadTorches() { auto rom_data = rom()->vector(); // Load torch data from torch_data address - int torch_count = rom_data[torches_length_pointer + 1] << 8 | rom_data[torches_length_pointer]; + // int torch_count = rom_data[torches_length_pointer + 1] << 8 | rom_data[torches_length_pointer]; // For now, create placeholder torch objects // TODO: Implement full torch loading from ROM data @@ -955,7 +949,7 @@ void Room::LoadBlocks() { auto rom_data = rom()->vector(); // Load block data from blocks_* addresses - int block_count = rom_data[blocks_length + 1] << 8 | rom_data[blocks_length]; + // int block_count = rom_data[blocks_length + 1] << 8 | rom_data[blocks_length]; // For now, create placeholder block objects // TODO: Implement full block loading from ROM data diff --git a/src/app/zelda3/dungeon/room.h b/src/app/zelda3/dungeon/room.h index 32a695cf..b5d1ae67 100644 --- a/src/app/zelda3/dungeon/room.h +++ b/src/app/zelda3/dungeon/room.h @@ -312,6 +312,10 @@ class Room { void SetStair2Target(uint8_t target) { stair2_.target = target; } void SetStair3Target(uint8_t target) { stair3_.target = target; } void SetStair4Target(uint8_t target) { stair4_.target = target; } + + // Loaded state + bool IsLoaded() const { return is_loaded_; } + void SetLoaded(bool loaded) { is_loaded_ = loaded; } // Read-only accessors for metadata EffectKey effect() const { return effect_; } @@ -350,7 +354,7 @@ class Room { std::array current_gfx16_; bool is_light_; - bool is_loaded_; + bool is_loaded_ = false; bool is_dark_; bool is_floor_ = true; diff --git a/src/util/log.cc b/src/util/log.cc new file mode 100644 index 00000000..7de3f76e --- /dev/null +++ b/src/util/log.cc @@ -0,0 +1,109 @@ +#include "util/log.h" + +#include +#include +#include + +namespace yaze { +namespace util { + +// Helper function to convert LogLevel enum to a string representation. +static const char* LogLevelToString(LogLevel level) { + switch (level) { + case LogLevel::YAZE_DEBUG: + return "YAZE_DEBUG"; + case LogLevel::INFO: + return "INFO"; + case LogLevel::WARNING: + return "WARN"; + case LogLevel::ERROR: + return "ERROR"; + case LogLevel::FATAL: + return "FATAL"; + } + return "UNKN"; +} + +// --- LogManager Implementation --- + +LogManager& LogManager::instance() { + static LogManager instance; + return instance; +} + +LogManager::LogManager() + : min_level_(LogLevel::INFO), all_categories_enabled_(true) {} + +LogManager::~LogManager() { + if (log_stream_.is_open()) { + log_stream_.close(); + } +} + +void LogManager::configure(LogLevel level, const std::string& file_path, + const std::set& categories) { + min_level_.store(level); + + if (categories.empty()) { + all_categories_enabled_.store(true); + enabled_categories_.clear(); + } else { + all_categories_enabled_.store(false); + enabled_categories_ = categories; + } + + // If a file path is provided, close any existing stream and open the new file. + if (!file_path.empty() && file_path != log_file_path_) { + if (log_stream_.is_open()) { + log_stream_.close(); + } + // Open in append mode to preserve history. + log_stream_.open(file_path, std::ios::out | std::ios::app); + log_file_path_ = file_path; + } +} + +void LogManager::log(LogLevel level, absl::string_view category, + absl::string_view message) { + // 1. Filter by log level. + if (level < min_level_.load()) { + return; + } + + // 2. Filter by category. + if (!all_categories_enabled_.load()) { + if (enabled_categories_.find(std::string(category)) == + enabled_categories_.end()) { + return; + } + } + + // 3. Format the complete log message. + // [HH:MM:SS.ms] [LEVEL] [category] message + auto now = std::chrono::system_clock::now(); + auto now_tt = std::chrono::system_clock::to_time_t(now); + auto now_tm = *std::localtime(&now_tt); + auto ms = std::chrono::duration_cast( + now.time_since_epoch()) % + 1000; + + std::string final_message = absl::StrFormat( + "[%02d:%02d:%02d.%03d] [%-5s] [%s] %s\n", now_tm.tm_hour, now_tm.tm_min, + now_tm.tm_sec, ms.count(), LogLevelToString(level), category, message); + + // 4. Write to the configured sink (file or stderr). + if (log_stream_.is_open()) { + log_stream_ << final_message; + log_stream_.flush(); // Ensure immediate write for debugging. + } else { + std::cerr << final_message; + } + + // 5. Abort on FATAL error. + if (level == LogLevel::FATAL) { + std::abort(); + } +} + +} // namespace util +} // namespace yaze diff --git a/src/util/log.h b/src/util/log.h index 152c2f6a..6c1e068c 100644 --- a/src/util/log.h +++ b/src/util/log.h @@ -1,20 +1,26 @@ #ifndef YAZE_UTIL_LOG_H #define YAZE_UTIL_LOG_H +#include #include -#include #include #include +#include +#include #include +#include -#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/strings/str_cat.h" #include "app/core/features.h" +#include "absl/strings/string_view.h" namespace yaze { namespace util { -static std::string g_log_file_path = "yaze_log.txt"; +// Static variables for library state +static std::string g_log_file_path = ""; + // Set custom log file path inline void SetLogFile(const std::string& filepath) { @@ -50,7 +56,91 @@ static void logf(const absl::FormatSpec &format, const Args &...args) { fout.flush(); // Ensure immediate write for debugging } + +/** + * @enum LogLevel + * @brief Defines the severity levels for log messages. + * This allows for filtering messages based on their importance. + */ +enum class LogLevel { YAZE_DEBUG, INFO, WARNING, ERROR, FATAL }; + +/** + * @class LogManager + * @brief A singleton that manages all logging configuration and output. + * + * It is designed to be configured once at application startup, typically from + * command-line arguments. It supports filtering by level and category, and can + * direct output to stderr (default) or a specified file. + */ +class LogManager { + public: + // Singleton access + static LogManager& instance(); + + // Deleted constructors for singleton pattern + LogManager(const LogManager&) = delete; + void operator=(const LogManager&) = delete; + + /** + * @brief Configures the logging system. + * @param level The minimum log level to record. + * @param file_path The path to the log file. If empty, logs to stderr. + * @param categories A set of specific categories to enable. If empty, all + * categories are enabled. + */ + void configure(LogLevel level, const std::string& file_path, + const std::set& categories); + + /** + * @brief The primary logging function. + * @param level The severity level of the message. + * @param category The category of the message (e.g., "Graphics", "Agent"). + * @param message The formatted log message. + */ + void log(LogLevel level, absl::string_view category, + absl::string_view message); + + private: + LogManager(); + ~LogManager(); + + // Configuration state + std::atomic min_level_; + std::set enabled_categories_; + std::atomic all_categories_enabled_; + + // Output sink + std::ofstream log_stream_; + std::string log_file_path_; +}; + +// logf mapping +#define logf LOG_INFO + +// --- Public Logging Macros --- +// These macros provide a convenient and efficient API for logging. +// The `do-while(0)` loop ensures they behave like a single statement. +// The level check avoids the cost of string formatting if the message won't be +// logged. + +#define LOG(level, category, format, ...) \ + do { \ + yaze::util::LogManager::instance().log( \ + level, category, absl::StrFormat(format, ##__VA_ARGS__)); \ + } while (0) + +#define LOG_DEBUG(category, format, ...) \ + LOG(yaze::util::LogLevel::YAZE_DEBUG, category, format, ##__VA_ARGS__) +#define LOG_INFO(category, format, ...) \ + LOG(yaze::util::LogLevel::INFO, category, format, ##__VA_ARGS__) +#define LOG_WARN(category, format, ...) \ + LOG(yaze::util::LogLevel::WARNING, category, format, ##__VA_ARGS__) +#define LOG_ERROR(category, format, ...) \ + LOG(yaze::util::LogLevel::ERROR, category, format, ##__VA_ARGS__) +#define LOG_FATAL(category, format, ...) \ + LOG(yaze::util::LogLevel::FATAL, category, format, ##__VA_ARGS__) + } // namespace util } // namespace yaze -#endif // YAZE_UTIL_LOG_H \ No newline at end of file +#endif // YAZE_UTIL_LOG_H diff --git a/src/util/util.cmake b/src/util/util.cmake index 9810b416..522b0470 100644 --- a/src/util/util.cmake +++ b/src/util/util.cmake @@ -14,6 +14,7 @@ set(YAZE_UTIL_SRC util/bps.cc util/flag.cc util/hex.cc + util/log.cc ) add_library(yaze_util STATIC ${YAZE_UTIL_SRC})