From 997092390a7c13a891794d46fa7e79f3efcaf5df Mon Sep 17 00:00:00 2001 From: scawful Date: Fri, 26 Sep 2025 19:32:19 -0400 Subject: [PATCH] Add theme management and background rendering features - Introduced a comprehensive theme management system, allowing users to load, save, and switch between multiple themes. - Added support for various built-in themes, enhancing the visual customization of the application. - Implemented a background renderer for improved visual effects in docking windows, including grid backgrounds and subtle animations. - Enhanced the EditorManager UI with themed elements, providing a more cohesive and engaging user experience. - Updated CMake configuration to include new theme and background renderer source files, ensuring proper integration into the build system. --- CMakeLists.txt | 4 + assets/themes/cyberpunk.theme | 62 ++ assets/themes/forest.theme | 73 +++ assets/themes/midnight.theme | 73 +++ assets/themes/sunset.theme | 62 ++ assets/themes/yaze_tre.theme | 92 +++ src/app/core/controller.cc | 8 +- src/app/core/platform/file_dialog.cc | 13 + src/app/core/platform/file_dialog.h | 1 + src/app/core/window.cc | 11 + src/app/editor/editor_manager.cc | 69 ++- src/app/gui/background_renderer.cc | 380 ++++++++++++ src/app/gui/background_renderer.h | 96 +++ src/app/gui/gui.cmake | 2 + src/app/gui/style.cc | 64 ++ src/app/gui/theme_manager.cc | 879 +++++++++++++++++++++++++++ src/app/gui/theme_manager.h | 179 ++++++ src/app/main.cc | 19 + src/ios/main.mm | 11 + src/util/log.h | 21 +- 20 files changed, 2093 insertions(+), 26 deletions(-) create mode 100644 assets/themes/cyberpunk.theme create mode 100644 assets/themes/forest.theme create mode 100644 assets/themes/midnight.theme create mode 100644 assets/themes/sunset.theme create mode 100644 assets/themes/yaze_tre.theme create mode 100644 src/app/gui/background_renderer.cc create mode 100644 src/app/gui/background_renderer.h create mode 100644 src/app/gui/theme_manager.cc create mode 100644 src/app/gui/theme_manager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb26889a..81090548 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,10 @@ endif() include(cmake/imgui.cmake) # Project Files +# Copy theme files to build directory +file(GLOB THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme") +file(COPY ${THEME_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/themes/") + add_subdirectory(src) # Tests diff --git a/assets/themes/cyberpunk.theme b/assets/themes/cyberpunk.theme new file mode 100644 index 00000000..64d696af --- /dev/null +++ b/assets/themes/cyberpunk.theme @@ -0,0 +1,62 @@ +# Cyberpunk Theme +# Neon-inspired futuristic theme +name=Cyberpunk +description=Neon-inspired futuristic theme +author=YAZE Team +version=1.0 + +[colors] +# Primary colors (neon cyberpunk) +primary=255,20,147,255 +secondary=0,255,255,255 +accent=255,0,128,255 +background=10,10,20,255 +surface=20,20,40,255 + +# Status colors +error=255,50,100,255 +warning=255,255,0,255 +success=0,255,100,255 +info=100,200,255,255 + +# Text colors +text_primary=255,255,255,255 +text_secondary=200,200,255,255 +text_disabled=100,100,150,255 + +# Window colors +window_bg=15,15,30,240 +child_bg=10,10,25,200 +popup_bg=20,20,40,250 + +# Interactive elements +button=40,20,60,255 +button_hovered=120,20,120,255 +button_active=160,40,160,255 +frame_bg=30,30,50,255 +frame_bg_hovered=40,40,70,255 +frame_bg_active=60,20,80,255 + +# Navigation +header=30,10,50,255 +header_hovered=80,20,100,255 +header_active=120,40,140,255 +tab=25,15,45,255 +tab_hovered=60,30,80,255 +tab_active=100,20,120,255 +menu_bar_bg=20,10,40,255 +title_bg=25,15,45,255 +title_bg_active=30,10,50,255 +title_bg_collapsed=25,15,45,255 + +[style] +window_rounding=10.0 +frame_rounding=8.0 +scrollbar_rounding=10.0 +grab_rounding=6.0 +tab_rounding=6.0 +window_border_size=1.0 +frame_border_size=1.0 +enable_animations=true +enable_glow_effects=true +animation_speed=1.5 diff --git a/assets/themes/forest.theme b/assets/themes/forest.theme new file mode 100644 index 00000000..cc78bcfc --- /dev/null +++ b/assets/themes/forest.theme @@ -0,0 +1,73 @@ +# Forest Theme +# Enhanced forest theme with better readability +name=Forest +description=Deep forest theme with enhanced readability +author=YAZE Team +version=1.0 + +[colors] +# Primary colors (enhanced forest with better contrast) +primary=100,180,120,255 # Brighter forest green +secondary=70,130,85,255 # Mid forest green +accent=130,220,150,255 # Light accent green +background=15,25,15,255 # Darker background for contrast +surface=25,35,25,255 + +# Status colors +error=255,120,120,255 +warning=255,220,120,255 +success=100,180,120,255 +info=120,200,180,255 + +# Text colors (enhanced for readability) +text_primary=250,255,250,255 # Very light for contrast +text_secondary=220,240,220,255 # Light green tint +text_disabled=150,170,150,255 # Brighter disabled text + +# Window colors (better contrast) +window_bg=20,30,20,240 +child_bg=15,25,15,200 +popup_bg=25,35,25,250 + +# Interactive elements (better visibility) +button=70,110,80,255 +button_hovered=100,150,115,255 +button_active=130,180,145,255 +frame_bg=40,60,45,200 +frame_bg_hovered=55,80,60,220 +frame_bg_active=70,110,80,240 + +# Navigation (better contrast) +header=55,85,60,255 +header_hovered=100,150,115,255 +header_active=130,180,145,255 +tab=45,75,50,255 +tab_hovered=70,110,80,255 +tab_active=100,150,115,255 +menu_bar_bg=40,70,45,255 +title_bg=50,80,55,255 +title_bg_active=55,85,60,255 +title_bg_collapsed=45,75,50,255 + +# Separators (better visibility) +separator=120,160,130,180 +separator_hovered=150,200,160,220 +separator_active=180,240,190,255 + +# Scrollbars (better visibility) +scrollbar_bg=40,60,45,180 +scrollbar_grab=80,120,90,200 +scrollbar_grab_hovered=100,150,115,230 +scrollbar_grab_active=130,180,145,255 + +[style] +window_rounding=6.0 +frame_rounding=4.0 +scrollbar_rounding=6.0 +grab_rounding=3.0 +tab_rounding=3.0 +window_border_size=0.0 +frame_border_size=0.0 +enable_animations=true +enable_glow_effects=false +animation_speed=1.0 diff --git a/assets/themes/midnight.theme b/assets/themes/midnight.theme new file mode 100644 index 00000000..6a87fb6c --- /dev/null +++ b/assets/themes/midnight.theme @@ -0,0 +1,73 @@ +# Midnight Theme +# Enhanced midnight theme with better readability +name=Midnight +description=Deep blue midnight theme with enhanced readability +author=YAZE Team +version=1.0 + +[colors] +# Primary colors (enhanced midnight with better contrast) +primary=100,160,230,255 # Brighter blue +secondary=70,120,180,255 # Mid blue +accent=140,200,255,255 # Light blue accent +background=10,15,25,255 # Darker background for contrast +surface=20,25,35,255 + +# Status colors +error=255,120,120,255 +warning=255,220,120,255 +success=120,255,180,255 +info=140,200,255,255 + +# Text colors (enhanced for readability) +text_primary=245,250,255,255 # Very light blue-white +text_secondary=200,220,240,255 # Light blue tint +text_disabled=140,160,180,255 # Brighter disabled text + +# Window colors (better contrast) +window_bg=15,20,30,240 +child_bg=10,15,25,200 +popup_bg=20,25,35,250 + +# Interactive elements (better visibility) +button=50,80,120,255 +button_hovered=80,120,160,255 +button_active=110,150,190,255 +frame_bg=30,45,65,200 +frame_bg_hovered=40,60,85,220 +frame_bg_active=60,90,130,240 + +# Navigation (better contrast) +header=40,65,100,255 +header_hovered=70,110,150,255 +header_active=100,140,180,255 +tab=30,55,90,255 +tab_hovered=50,80,120,255 +tab_active=70,110,150,255 +menu_bar_bg=25,50,85,255 +title_bg=35,60,95,255 +title_bg_active=40,65,100,255 +title_bg_collapsed=30,55,90,255 + +# Separators (better visibility) +separator=100,140,180,180 +separator_hovered=130,170,210,220 +separator_active=160,200,240,255 + +# Scrollbars (better visibility) +scrollbar_bg=30,45,65,180 +scrollbar_grab=70,110,150,200 +scrollbar_grab_hovered=100,140,180,230 +scrollbar_grab_active=130,170,210,255 + +[style] +window_rounding=8.0 +frame_rounding=6.0 +scrollbar_rounding=8.0 +grab_rounding=4.0 +tab_rounding=6.0 +window_border_size=0.0 +frame_border_size=0.0 +enable_animations=true +enable_glow_effects=true +animation_speed=1.2 diff --git a/assets/themes/sunset.theme b/assets/themes/sunset.theme new file mode 100644 index 00000000..be902ffa --- /dev/null +++ b/assets/themes/sunset.theme @@ -0,0 +1,62 @@ +# Sunset Theme +# Warm orange and purple sunset theme +name=Sunset +description=Warm orange and purple sunset theme +author=YAZE Team +version=1.0 + +[colors] +# Primary colors (sunset) +primary=255,140,60,255 +secondary=200,100,150,255 +accent=255,180,100,255 +background=30,20,35,255 +surface=40,30,45,255 + +# Status colors +error=255,100,120,255 +warning=255,200,100,255 +success=150,255,150,255 +info=150,200,255,255 + +# Text colors +text_primary=255,245,235,255 +text_secondary=220,200,180,255 +text_disabled=150,130,120,255 + +# Window colors +window_bg=35,25,40,240 +child_bg=30,20,35,200 +popup_bg=40,30,45,250 + +# Interactive elements +button=60,40,70,255 +button_hovered=120,80,100,255 +button_active=150,100,120,255 +frame_bg=45,35,50,255 +frame_bg_hovered=55,45,60,255 +frame_bg_active=80,60,90,255 + +# Navigation +header=50,35,60,255 +header_hovered=100,70,90,255 +header_active=130,90,110,255 +tab=40,30,50,255 +tab_hovered=70,50,70,255 +tab_active=100,70,90,255 +menu_bar_bg=35,25,45,255 +title_bg=40,30,50,255 +title_bg_active=50,35,60,255 +title_bg_collapsed=40,30,50,255 + +[style] +window_rounding=12.0 +frame_rounding=8.0 +scrollbar_rounding=12.0 +grab_rounding=6.0 +tab_rounding=8.0 +window_border_size=0.0 +frame_border_size=0.0 +enable_animations=true +enable_glow_effects=false +animation_speed=1.0 diff --git a/assets/themes/yaze_tre.theme b/assets/themes/yaze_tre.theme new file mode 100644 index 00000000..a6d1a817 --- /dev/null +++ b/assets/themes/yaze_tre.theme @@ -0,0 +1,92 @@ +# YAZE Tre Theme +# Theme resource edition based on original ColorsYaze() function +name=YAZE Tre +description=YAZE theme resource edition (file-based) +author=YAZE Team +version=1.0 + +[colors] +# Primary colors (exact ALTTP colors from original code) +primary=92,115,92,255 # 0.36f, 0.45f, 0.36f - allttpLightGreen +secondary=71,92,71,255 # 0.28f, 0.36f, 0.28f - alttpMidGreen +accent=89,119,89,255 # 0.347f, 0.466f, 0.347f - TabActive exact +background=8,8,8,255 # Very dark gray for better grid visibility +surface=12,12,12,255 # Slightly lighter dark gray surface + +# Status colors +error=220,50,47,255 +warning=255,193,7,255 +success=92,115,92,255 +info=52,152,219,255 + +# Text colors (exact from original) +text_primary=230,230,230,255 # 0.90f, 0.90f, 0.90f +text_secondary=180,180,180,255 +text_disabled=153,153,153,255 # 0.60f, 0.60f, 0.60f + +# Window colors (exact from original) +window_bg=8,8,8,217 # Very dark gray with same alpha +child_bg=0,0,0,0 # 0.00f, 0.00f, 0.00f, 0.00f - transparent +popup_bg=28,28,36,235 # 0.11f, 0.11f, 0.14f, 0.92f + +# Interactive elements (exact from original) +button=71,92,71,255 # alttpMidGreen +button_hovered=125,145,125,255 # allttpLightestGreen (exact) +button_active=92,115,92,255 # allttpLightGreen +frame_bg=110,110,110,99 # 0.43f, 0.43f, 0.43f, 0.39f +frame_bg_hovered=71,92,71,102 # 0.28f, 0.36f, 0.28f, 0.40f +frame_bg_active=71,92,71,176 # 0.28f, 0.36f, 0.28f, 0.69f + +# Navigation (exact from original) +header=46,66,46,255 # alttpDarkGreen +header_hovered=92,115,92,255 # allttpLightGreen +header_active=71,92,71,255 # alttpMidGreen +tab=46,66,46,255 # alttpDarkGreen +tab_hovered=71,92,71,255 # alttpMidGreen +tab_active=89,119,89,255 # TabActive exact color +menu_bar_bg=46,66,46,255 # alttpDarkGreen +title_bg=71,92,71,255 # alttpMidGreen +title_bg_active=46,66,46,255 # alttpDarkGreen +title_bg_collapsed=71,92,71,255 # alttpMidGreen + +# Borders and separators (exact from original) +border=92,115,92,255 # allttpLightGreen +border_shadow=0,0,0,0 # No shadow in original +separator=127,127,127,153 # 0.50f, 0.50f, 0.50f, 0.60f +separator_hovered=153,153,178,255 # 0.60f, 0.60f, 0.70f +separator_active=178,178,230,255 # 0.70f, 0.70f, 0.90f + +# Scrollbars (exact from original) +scrollbar_bg=92,115,92,153 # 0.36f, 0.45f, 0.36f, 0.60f +scrollbar_grab=92,115,92,76 # 0.36f, 0.45f, 0.36f, 0.30f (exact) +scrollbar_grab_hovered=92,115,92,102 # 0.36f, 0.45f, 0.36f, 0.40f +scrollbar_grab_active=92,115,92,153 # 0.36f, 0.45f, 0.36f, 0.60f + +# Resize grips (exact from original - these are light blue, not white!) +resize_grip=255,255,255,26 # 1.00f, 1.00f, 1.00f, 0.10f +resize_grip_hovered=199,209,255,153 # 0.78f, 0.82f, 1.00f, 0.60f (light blue!) +resize_grip_active=199,209,255,230 # 0.78f, 0.82f, 1.00f, 0.90f (light blue!) + +# Additional controls (missing from theme) +check_mark=230,230,230,128 # 0.90f, 0.90f, 0.90f, 0.50f +slider_grab=255,255,255,77 # 1.00f, 1.00f, 1.00f, 0.30f +slider_grab_active=92,115,92,153 # 0.36f, 0.45f, 0.36f, 0.60f + +# Table colors (from original) +table_header_bg=46,66,46,255 # alttpDarkGreen +table_border_strong=71,92,71,255 # alttpMidGreen +table_border_light=66,66,71,255 # 0.26f, 0.26f, 0.28f +table_row_bg=0,0,0,0 # Transparent +table_row_bg_alt=255,255,255,18 # 1.00f, 1.00f, 1.00f, 0.07f + +[style] +window_rounding=0.0 +frame_rounding=5.0 +scrollbar_rounding=5.0 +grab_rounding=3.0 +tab_rounding=0.0 +window_border_size=0.0 +frame_border_size=0.0 +enable_animations=true +enable_glow_effects=false +animation_speed=1.0 diff --git a/src/app/core/controller.cc b/src/app/core/controller.cc index 11dc82ec..0d5b866d 100644 --- a/src/app/core/controller.cc +++ b/src/app/core/controller.cc @@ -5,6 +5,8 @@ #include "absl/status/status.h" #include "app/core/window.h" #include "app/editor/editor_manager.h" +#include "app/gui/background_renderer.h" +#include "app/gui/theme_manager.h" #include "imgui/backends/imgui_impl_sdl2.h" #include "imgui/backends/imgui_impl_sdlrenderer2.h" #include "imgui/imgui.h" @@ -54,10 +56,10 @@ absl::Status Controller::OnLoad() { ImGui::Begin("DockSpaceWindow", nullptr, window_flags); ImGui::PopStyleVar(3); - // Create DockSpace + // Create DockSpace first ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); - ImGui::DockSpace(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 diff --git a/src/app/core/platform/file_dialog.cc b/src/app/core/platform/file_dialog.cc index 077de22e..d059054e 100644 --- a/src/app/core/platform/file_dialog.cc +++ b/src/app/core/platform/file_dialog.cc @@ -78,6 +78,19 @@ void SaveFile(const std::string &filename, const std::string &contents) { } } +std::string GetResourcePath(const std::string &resource_path) { +#ifdef __APPLE__ +#if TARGET_OS_IOS == 1 + const std::string kBundlePath = GetBundleResourcePath(); + return kBundlePath + resource_path; +#else + return GetBundleResourcePath() + "Contents/Resources/" + resource_path; +#endif +#else + return resource_path; // On Linux/Windows, resources are relative to executable +#endif +} + std::string GetConfigDirectory() { std::string config_directory = ".yaze"; Platform platform; diff --git a/src/app/core/platform/file_dialog.h b/src/app/core/platform/file_dialog.h index 6787cbe4..744f9126 100644 --- a/src/app/core/platform/file_dialog.h +++ b/src/app/core/platform/file_dialog.h @@ -45,6 +45,7 @@ std::string GetFileName(const std::string &filename); std::string LoadFile(const std::string &filename); std::string LoadConfigFile(const std::string &filename); std::string GetConfigDirectory(); +std::string GetResourcePath(const std::string &resource_path); void SaveFile(const std::string &filename, const std::string &data); } // namespace core diff --git a/src/app/core/window.cc b/src/app/core/window.cc index 985c159e..c8fd3867 100644 --- a/src/app/core/window.cc +++ b/src/app/core/window.cc @@ -6,7 +6,9 @@ #include "app/core/platform/sdl_deleter.h" #include "app/gfx/arena.h" #include "app/gui/style.h" +#include "app/gui/theme_manager.h" #include "app/test/test_manager.h" +#include "util/log.h" #include "imgui/backends/imgui_impl_sdl2.h" #include "imgui/backends/imgui_impl_sdlrenderer2.h" #include "imgui/imgui.h" @@ -49,7 +51,16 @@ absl::Status CreateWindow(Window& window, int flags) { RETURN_IF_ERROR(LoadPackageFonts()); + // Apply original YAZE colors as fallback, then try to load theme system gui::ColorsYaze(); + + // Try to initialize theme system (will fallback to ColorsYaze if files fail) + auto& theme_manager = gui::ThemeManager::Get(); + auto status = theme_manager.LoadTheme("YAZE Classic"); + if (!status.ok()) { + // Theme system failed, stick with original ColorsYaze() + util::logf("Theme system failed, using original ColorsYaze(): %s", status.message().data()); + } const int audio_frequency = 48000; SDL_AudioSpec want, have; diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index b4e7e565..35624151 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -21,6 +21,8 @@ #include "app/gui/icons.h" #include "app/gui/input.h" #include "app/gui/style.h" +#include "app/gui/theme_manager.h" +#include "app/gui/background_renderer.h" #include "app/rom.h" #include "app/zelda3/overworld/overworld_map.h" #include "app/test/test_manager.h" @@ -704,6 +706,21 @@ absl::Status EditorManager::Update() { ExecuteShortcuts(context_.shortcut_manager); toast_manager_.Draw(); + // Draw background grid effects for the entire viewport + if (ImGui::GetCurrentContext()) { + ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList(); + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + + auto& theme_manager = gui::ThemeManager::Get(); + auto current_theme = theme_manager.GetCurrentTheme(); + auto& bg_renderer = gui::BackgroundRenderer::Get(); + + // Draw grid covering the entire main viewport + ImVec2 grid_pos = viewport->WorkPos; + ImVec2 grid_size = viewport->WorkSize; + bg_renderer.RenderDockingBackground(bg_draw_list, grid_pos, grid_size, current_theme.primary); + } + // Ensure TestManager always has the current ROM static Rom* last_test_rom = nullptr; if (last_test_rom != current_rom_) { @@ -938,9 +955,6 @@ void EditorManager::DrawMenuBar() { // Enhanced ROM status with metadata popup if (current_rom_ && current_rom_->is_loaded()) { std::string rom_display = current_rom_->title(); - if (rom_display.length() > 16) { - rom_display = rom_display.substr(0, 16) + ".."; - } ImVec4 status_color = current_rom_->dirty() ? ImVec4(1.0f, 0.5f, 0.0f, 1.0f) : // Orange for modified @@ -2155,23 +2169,30 @@ void EditorManager::DrawWelcomeScreen() { ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_size = ImGui::GetWindowSize(); - // Draw animated gradient background - ImU32 bg_color_top = IM_COL32(25, 35, 50, 240); - ImU32 bg_color_bottom = IM_COL32(15, 25, 40, 240); + // Get theme colors for welcome screen + auto& theme_manager = gui::ThemeManager::Get(); + auto bg_color = theme_manager.GetWelcomeScreenBackground(); + auto border_color = theme_manager.GetWelcomeScreenBorder(); + auto accent_color = theme_manager.GetWelcomeScreenAccent(); + + // Draw themed gradient background + ImU32 bg_top = ImGui::ColorConvertFloat4ToU32(ImVec4(bg_color.red, bg_color.green, bg_color.blue, bg_color.alpha)); + ImU32 bg_bottom = ImGui::ColorConvertFloat4ToU32(ImVec4(bg_color.red * 0.8f, bg_color.green * 0.8f, bg_color.blue * 0.8f, bg_color.alpha)); draw_list->AddRectFilledMultiColor( window_pos, ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), - bg_color_top, bg_color_top, bg_color_bottom, bg_color_bottom); + bg_top, bg_top, bg_bottom, bg_bottom); - // Animated border with gradient effect + // Themed animated border float border_thickness = 3.0f; float pulse = 0.8f + 0.2f * sinf(animation_time * 2.0f); - ImU32 border_color = IM_COL32(70, 130, 200, (int)(255 * pulse)); + ImU32 themed_border = ImGui::ColorConvertFloat4ToU32(ImVec4( + border_color.red, border_color.green, border_color.blue, pulse * border_color.alpha)); draw_list->AddRect(window_pos, ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), - border_color, 12.0f, 0, border_thickness); + themed_border, 12.0f, 0, border_thickness); - // Subtle floating particles effect + // Themed floating particles effect for (int i = 0; i < 8; ++i) { float offset_x = sinf(animation_time * 0.5f + i * 0.8f) * 20.0f; float offset_y = cosf(animation_time * 0.3f + i * 1.2f) * 15.0f; @@ -2180,29 +2201,34 @@ void EditorManager::DrawWelcomeScreen() { window_pos.y + 100 + offset_y); float alpha = 0.3f + 0.2f * sinf(animation_time * 1.5f + i); - ImU32 particle_color = IM_COL32(100, 150, 255, (int)(alpha * 100)); + ImU32 particle_color = ImGui::ColorConvertFloat4ToU32(ImVec4( + accent_color.red, accent_color.green, accent_color.blue, alpha * 0.4f)); draw_list->AddCircleFilled(particle_pos, 2.0f + sinf(animation_time + i) * 0.5f, particle_color); } - // Header with enhanced styling + // Header with themed styling ImGui::Spacing(); ImGui::SetCursorPosX((window_size.x - ImGui::CalcTextSize("Welcome to Yet Another Zelda3 Editor").x) * 0.5f); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.95f, 1.0f, 1.0f)); + auto text_color = theme_manager.GetCurrentTheme().text_primary; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(text_color.red, text_color.green, text_color.blue, text_color.alpha)); ImGui::Text("Welcome to Yet Another Zelda3 Editor"); ImGui::PopStyleColor(); ImGui::SetCursorPosX((window_size.x - ImGui::CalcTextSize("The Legend of Zelda: A Link to the Past").x) * 0.5f); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.8f, 0.9f, 0.9f)); + auto subtitle_color = theme_manager.GetCurrentTheme().text_secondary; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(subtitle_color.red, subtitle_color.green, subtitle_color.blue, subtitle_color.alpha * 0.9f)); ImGui::Text("The Legend of Zelda: A Link to the Past"); ImGui::PopStyleColor(); ImGui::Spacing(); - // Decorative line with glow effect + // Themed decorative line with glow effect ImVec2 line_start = ImVec2(window_pos.x + 50, window_pos.y + 120); ImVec2 line_end = ImVec2(window_pos.x + window_size.x - 50, window_pos.y + 120); float glow_alpha = 0.5f + 0.3f * sinf(animation_time * 1.5f); - draw_list->AddLine(line_start, line_end, IM_COL32(100, 150, 255, (int)(glow_alpha * 255)), 2.0f); + ImU32 line_color = ImGui::ColorConvertFloat4ToU32(ImVec4( + accent_color.red, accent_color.green, accent_color.blue, glow_alpha)); + draw_list->AddLine(line_start, line_end, line_color, 2.0f); ImGui::Spacing(); ImGui::Spacing(); @@ -2226,10 +2252,11 @@ void EditorManager::DrawWelcomeScreen() { ImGui::Text("Get Started:"); ImGui::Spacing(); - // Stylish primary buttons with hover effects - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.9f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 0.9f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f)); + // Themed primary buttons with enhanced effects + auto current_theme = theme_manager.GetCurrentTheme(); + ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(current_theme.primary)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(current_theme.accent)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(current_theme.secondary)); if (ImGui::Button(ICON_MD_FILE_OPEN " Open ROM File", ImVec2(200, 40))) { status_ = LoadRom(); diff --git a/src/app/gui/background_renderer.cc b/src/app/gui/background_renderer.cc new file mode 100644 index 00000000..354b4cc3 --- /dev/null +++ b/src/app/gui/background_renderer.cc @@ -0,0 +1,380 @@ +#include "background_renderer.h" + +#include +#include + +#include "app/gui/theme_manager.h" +#include "imgui/imgui.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace yaze { +namespace gui { + +// BackgroundRenderer Implementation +BackgroundRenderer& BackgroundRenderer::Get() { + static BackgroundRenderer instance; + return instance; +} + +void BackgroundRenderer::RenderDockingBackground(ImDrawList* draw_list, const ImVec2& window_pos, + const ImVec2& window_size, const Color& theme_color) { + if (!draw_list) return; + + UpdateAnimation(ImGui::GetIO().DeltaTime); + + // Get current theme colors + auto& theme_manager = ThemeManager::Get(); + auto current_theme = theme_manager.GetCurrentTheme(); + + // Create a subtle tinted background + Color bg_tint = { + current_theme.background.red * 1.1f, + current_theme.background.green * 1.1f, + current_theme.background.blue * 1.1f, + 0.3f + }; + + ImU32 bg_color = ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(bg_tint)); + draw_list->AddRectFilled(window_pos, + ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), + bg_color); + + // Render the grid if enabled + if (grid_settings_.grid_size > 0) { + RenderGridBackground(draw_list, window_pos, window_size, theme_color); + } + + // Add subtle corner accents + if (current_theme.enable_glow_effects) { + float corner_size = 60.0f; + Color accent_faded = current_theme.accent; + accent_faded.alpha = 0.1f + 0.05f * sinf(animation_time_ * 2.0f); + + ImU32 corner_color = ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(accent_faded)); + + // Top-left corner + draw_list->AddRectFilledMultiColor( + window_pos, + ImVec2(window_pos.x + corner_size, window_pos.y + corner_size), + corner_color, IM_COL32(0,0,0,0), IM_COL32(0,0,0,0), corner_color); + + // Bottom-right corner + draw_list->AddRectFilledMultiColor( + ImVec2(window_pos.x + window_size.x - corner_size, window_pos.y + window_size.y - corner_size), + ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), + IM_COL32(0,0,0,0), corner_color, corner_color, IM_COL32(0,0,0,0)); + } +} + +void BackgroundRenderer::RenderGridBackground(ImDrawList* draw_list, const ImVec2& window_pos, + const ImVec2& window_size, const Color& grid_color) { + if (!draw_list || grid_settings_.grid_size <= 0) return; + + // Grid parameters with optional animation + float grid_size = grid_settings_.grid_size; + float offset_x = 0.0f; + float offset_y = 0.0f; + + // Apply animation if enabled + if (grid_settings_.enable_animation) { + float animation_offset = animation_time_ * grid_settings_.animation_speed * 10.0f; + offset_x = fmodf(animation_offset, grid_size); + offset_y = fmodf(animation_offset * 0.7f, grid_size); // Different speed for interesting effect + } + + // Window center for radial calculations + ImVec2 center = ImVec2(window_pos.x + window_size.x * 0.5f, + window_pos.y + window_size.y * 0.5f); + float max_distance = sqrtf(window_size.x * window_size.x + window_size.y * window_size.y) * 0.5f; + + // Apply breathing effect to color if enabled + Color themed_grid_color = grid_color; + themed_grid_color.alpha = grid_settings_.opacity; + + if (grid_settings_.enable_breathing) { + float breathing_factor = 1.0f + grid_settings_.breathing_intensity * + sinf(animation_time_ * grid_settings_.breathing_speed); + themed_grid_color.red = std::min(1.0f, themed_grid_color.red * breathing_factor); + themed_grid_color.green = std::min(1.0f, themed_grid_color.green * breathing_factor); + themed_grid_color.blue = std::min(1.0f, themed_grid_color.blue * breathing_factor); + } + + if (grid_settings_.enable_dots) { + // Render grid as dots + for (float x = window_pos.x - offset_x; x < window_pos.x + window_size.x + grid_size; x += grid_size) { + for (float y = window_pos.y - offset_y; y < window_pos.y + window_size.y + grid_size; y += grid_size) { + ImVec2 dot_pos(x, y); + + // Calculate radial fade + float fade_factor = 1.0f; + if (grid_settings_.radial_fade) { + float distance = sqrtf((dot_pos.x - center.x) * (dot_pos.x - center.x) + + (dot_pos.y - center.y) * (dot_pos.y - center.y)); + fade_factor = 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); + fade_factor = fade_factor * fade_factor; // Square for smoother falloff + } + + if (fade_factor > 0.01f) { + ImU32 dot_color = BlendColorWithFade(themed_grid_color, fade_factor); + DrawGridDot(draw_list, dot_pos, dot_color, grid_settings_.dot_size); + } + } + } + } else { + // Render grid as lines + // Vertical lines + for (float x = window_pos.x - offset_x; x < window_pos.x + window_size.x + grid_size; x += grid_size) { + ImVec2 line_start(x, window_pos.y); + ImVec2 line_end(x, window_pos.y + window_size.y); + + // Calculate average fade for this line + float avg_fade = 0.0f; + if (grid_settings_.radial_fade) { + for (float y = window_pos.y; y < window_pos.y + window_size.y; y += grid_size * 0.5f) { + float distance = sqrtf((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y)); + float fade = 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); + avg_fade += fade * fade; + } + avg_fade /= (window_size.y / (grid_size * 0.5f)); + } else { + avg_fade = 1.0f; + } + + if (avg_fade > 0.01f) { + ImU32 line_color = BlendColorWithFade(themed_grid_color, avg_fade); + DrawGridLine(draw_list, line_start, line_end, line_color, grid_settings_.line_thickness); + } + } + + // Horizontal lines + for (float y = window_pos.y - offset_y; y < window_pos.y + window_size.y + grid_size; y += grid_size) { + ImVec2 line_start(window_pos.x, y); + ImVec2 line_end(window_pos.x + window_size.x, y); + + // Calculate average fade for this line + float avg_fade = 0.0f; + if (grid_settings_.radial_fade) { + for (float x = window_pos.x; x < window_pos.x + window_size.x; x += grid_size * 0.5f) { + float distance = sqrtf((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y)); + float fade = 1.0f - std::min(distance / grid_settings_.fade_distance, 1.0f); + avg_fade += fade * fade; + } + avg_fade /= (window_size.x / (grid_size * 0.5f)); + } else { + avg_fade = 1.0f; + } + + if (avg_fade > 0.01f) { + ImU32 line_color = BlendColorWithFade(themed_grid_color, avg_fade); + DrawGridLine(draw_list, line_start, line_end, line_color, grid_settings_.line_thickness); + } + } + } +} + +void BackgroundRenderer::RenderRadialGradient(ImDrawList* draw_list, const ImVec2& center, + float radius, const Color& inner_color, const Color& outer_color) { + if (!draw_list) return; + + const int segments = 32; + const int rings = 8; + + for (int ring = 0; ring < rings; ++ring) { + float ring_radius = radius * (ring + 1) / rings; + float inner_ring_radius = radius * ring / rings; + + // Interpolate colors for this ring + float t = static_cast(ring) / rings; + Color ring_color = { + inner_color.red * (1.0f - t) + outer_color.red * t, + inner_color.green * (1.0f - t) + outer_color.green * t, + inner_color.blue * (1.0f - t) + outer_color.blue * t, + inner_color.alpha * (1.0f - t) + outer_color.alpha * t + }; + + ImU32 color = ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(ring_color)); + + if (ring == 0) { + // Center circle + draw_list->AddCircleFilled(center, ring_radius, color, segments); + } else { + // Ring + for (int i = 0; i < segments; ++i) { + float angle1 = (2.0f * M_PI * i) / segments; + float angle2 = (2.0f * M_PI * (i + 1)) / segments; + + ImVec2 p1_inner = ImVec2(center.x + cosf(angle1) * inner_ring_radius, + center.y + sinf(angle1) * inner_ring_radius); + ImVec2 p2_inner = ImVec2(center.x + cosf(angle2) * inner_ring_radius, + center.y + sinf(angle2) * inner_ring_radius); + ImVec2 p1_outer = ImVec2(center.x + cosf(angle1) * ring_radius, + center.y + sinf(angle1) * ring_radius); + ImVec2 p2_outer = ImVec2(center.x + cosf(angle2) * ring_radius, + center.y + sinf(angle2) * ring_radius); + + draw_list->AddQuadFilled(p1_inner, p2_inner, p2_outer, p1_outer, color); + } + } + } +} + +void BackgroundRenderer::UpdateAnimation(float delta_time) { + if (grid_settings_.enable_animation) { + animation_time_ += delta_time; + } +} + +void BackgroundRenderer::UpdateForTheme(const Color& primary_color, const Color& background_color) { + // Create a grid color that's a subtle blend of the theme's primary and background + cached_grid_color_ = { + (primary_color.red * 0.3f + background_color.red * 0.7f), + (primary_color.green * 0.3f + background_color.green * 0.7f), + (primary_color.blue * 0.3f + background_color.blue * 0.7f), + grid_settings_.opacity + }; +} + +void BackgroundRenderer::DrawSettingsUI() { + if (ImGui::CollapsingHeader("Background Grid Settings")) { + ImGui::Indent(); + + ImGui::SliderFloat("Grid Size", &grid_settings_.grid_size, 8.0f, 128.0f, "%.0f px"); + ImGui::SliderFloat("Line Thickness", &grid_settings_.line_thickness, 0.5f, 3.0f, "%.1f px"); + ImGui::SliderFloat("Opacity", &grid_settings_.opacity, 0.01f, 0.3f, "%.3f"); + ImGui::SliderFloat("Fade Distance", &grid_settings_.fade_distance, 50.0f, 500.0f, "%.0f px"); + + ImGui::Separator(); + ImGui::Text("Visual Effects:"); + ImGui::Checkbox("Enable Animation", &grid_settings_.enable_animation); + ImGui::SameLine(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Makes the grid move slowly across the screen"); + } + + ImGui::Checkbox("Color Breathing", &grid_settings_.enable_breathing); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Grid color pulses with a breathing effect"); + } + + ImGui::Checkbox("Radial Fade", &grid_settings_.radial_fade); + ImGui::Checkbox("Use Dots Instead of Lines", &grid_settings_.enable_dots); + + // Animation settings (only show if animation is enabled) + if (grid_settings_.enable_animation) { + ImGui::Indent(); + ImGui::SliderFloat("Animation Speed", &grid_settings_.animation_speed, 0.1f, 3.0f, "%.1fx"); + ImGui::Unindent(); + } + + // Breathing settings (only show if breathing is enabled) + if (grid_settings_.enable_breathing) { + ImGui::Indent(); + ImGui::SliderFloat("Breathing Speed", &grid_settings_.breathing_speed, 0.5f, 3.0f, "%.1fx"); + ImGui::SliderFloat("Breathing Intensity", &grid_settings_.breathing_intensity, 0.1f, 0.8f, "%.1f"); + ImGui::Unindent(); + } + + if (grid_settings_.enable_dots) { + ImGui::SliderFloat("Dot Size", &grid_settings_.dot_size, 1.0f, 8.0f, "%.1f px"); + } + + // Preview + ImGui::Spacing(); + ImGui::Text("Preview:"); + ImVec2 preview_size(200, 100); + ImVec2 preview_pos = ImGui::GetCursorScreenPos(); + + ImDrawList* preview_draw_list = ImGui::GetWindowDrawList(); + auto& theme_manager = ThemeManager::Get(); + auto theme_color = theme_manager.GetCurrentTheme().primary; + + // Draw preview background + preview_draw_list->AddRectFilled(preview_pos, + ImVec2(preview_pos.x + preview_size.x, preview_pos.y + preview_size.y), + IM_COL32(30, 30, 30, 255)); + + // Draw preview grid + RenderGridBackground(preview_draw_list, preview_pos, preview_size, theme_color); + + // Advance cursor + ImGui::Dummy(preview_size); + + ImGui::Unindent(); + } +} + +float BackgroundRenderer::CalculateRadialFade(const ImVec2& pos, const ImVec2& center, float max_distance) const { + float distance = sqrtf((pos.x - center.x) * (pos.x - center.x) + + (pos.y - center.y) * (pos.y - center.y)); + float fade = 1.0f - std::min(distance / max_distance, 1.0f); + return fade * fade; // Square for smoother falloff +} + +ImU32 BackgroundRenderer::BlendColorWithFade(const Color& base_color, float fade_factor) const { + Color faded_color = { + base_color.red, + base_color.green, + base_color.blue, + base_color.alpha * fade_factor + }; + return ImGui::ColorConvertFloat4ToU32(ConvertColorToImVec4(faded_color)); +} + +void BackgroundRenderer::DrawGridLine(ImDrawList* draw_list, const ImVec2& start, const ImVec2& end, + ImU32 color, float thickness) const { + draw_list->AddLine(start, end, color, thickness); +} + +void BackgroundRenderer::DrawGridDot(ImDrawList* draw_list, const ImVec2& pos, ImU32 color, float size) const { + draw_list->AddCircleFilled(pos, size, color); +} + +// DockSpaceRenderer Implementation +bool DockSpaceRenderer::background_enabled_ = true; +bool DockSpaceRenderer::grid_enabled_ = true; +bool DockSpaceRenderer::effects_enabled_ = true; +ImVec2 DockSpaceRenderer::last_dockspace_pos_{}; +ImVec2 DockSpaceRenderer::last_dockspace_size_{}; + +void DockSpaceRenderer::BeginEnhancedDockSpace(ImGuiID dockspace_id, const ImVec2& size, + ImGuiDockNodeFlags flags) { + // Store window info + last_dockspace_pos_ = ImGui::GetWindowPos(); + last_dockspace_size_ = ImGui::GetWindowSize(); + + // Create the actual dockspace first + ImGui::DockSpace(dockspace_id, size, flags); + + // NOW draw the background effects on the foreground draw list so they're visible + if (background_enabled_) { + ImDrawList* fg_draw_list = ImGui::GetForegroundDrawList(); + auto& theme_manager = ThemeManager::Get(); + auto current_theme = theme_manager.GetCurrentTheme(); + + if (grid_enabled_) { + auto& bg_renderer = BackgroundRenderer::Get(); + // Use the main viewport for full-screen grid + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImVec2 grid_pos = viewport->WorkPos; + ImVec2 grid_size = viewport->WorkSize; + + // Use subtle grid color that doesn't distract + Color subtle_grid_color = current_theme.primary; + // Use the grid settings opacity for consistency + subtle_grid_color.alpha = bg_renderer.GetGridSettings().opacity; + + bg_renderer.RenderGridBackground(fg_draw_list, grid_pos, grid_size, subtle_grid_color); + } + } +} + +void DockSpaceRenderer::EndEnhancedDockSpace() { + // Additional post-processing effects could go here + // For now, this is just for API consistency +} + +} // namespace gui +} // namespace yaze diff --git a/src/app/gui/background_renderer.h b/src/app/gui/background_renderer.h new file mode 100644 index 00000000..77bad64d --- /dev/null +++ b/src/app/gui/background_renderer.h @@ -0,0 +1,96 @@ +#ifndef YAZE_APP_GUI_BACKGROUND_RENDERER_H +#define YAZE_APP_GUI_BACKGROUND_RENDERER_H + +#include "imgui/imgui.h" +#include "app/gui/color.h" + +namespace yaze { +namespace gui { + +/** + * @class BackgroundRenderer + * @brief Renders themed background effects for docking windows + */ +class BackgroundRenderer { +public: + struct GridSettings { + float grid_size = 32.0f; // Size of grid cells + float line_thickness = 1.0f; // Thickness of grid lines + float opacity = 0.12f; // Subtle but visible opacity + float fade_distance = 400.0f; // Distance over which grid fades + bool enable_animation = false; // Animation toggle (default off) + bool enable_breathing = false; // Color breathing effect toggle (default off) + bool radial_fade = true; // Re-enable subtle radial fade + bool enable_dots = false; // Use dots instead of lines + float dot_size = 2.0f; // Size of grid dots + float animation_speed = 1.0f; // Animation speed multiplier + float breathing_speed = 1.5f; // Breathing effect speed + float breathing_intensity = 0.3f; // How much color changes during breathing + }; + + static BackgroundRenderer& Get(); + + // Main rendering functions + void RenderDockingBackground(ImDrawList* draw_list, const ImVec2& window_pos, + const ImVec2& window_size, const Color& theme_color); + void RenderGridBackground(ImDrawList* draw_list, const ImVec2& window_pos, + const ImVec2& window_size, const Color& grid_color); + void RenderRadialGradient(ImDrawList* draw_list, const ImVec2& center, + float radius, const Color& inner_color, const Color& outer_color); + + // Configuration + void SetGridSettings(const GridSettings& settings) { grid_settings_ = settings; } + const GridSettings& GetGridSettings() const { return grid_settings_; } + + // Animation + void UpdateAnimation(float delta_time); + void SetAnimationEnabled(bool enabled) { grid_settings_.enable_animation = enabled; } + + // Theme integration + void UpdateForTheme(const Color& primary_color, const Color& background_color); + + // UI for settings + void DrawSettingsUI(); + +private: + BackgroundRenderer() = default; + + GridSettings grid_settings_; + float animation_time_ = 0.0f; + Color cached_grid_color_{0.5f, 0.5f, 0.5f, 0.1f}; + + // Helper functions + float CalculateRadialFade(const ImVec2& pos, const ImVec2& center, float max_distance) const; + ImU32 BlendColorWithFade(const Color& base_color, float fade_factor) const; + void DrawGridLine(ImDrawList* draw_list, const ImVec2& start, const ImVec2& end, + ImU32 color, float thickness) const; + void DrawGridDot(ImDrawList* draw_list, const ImVec2& pos, ImU32 color, float size) const; +}; + +/** + * @class DockSpaceRenderer + * @brief Enhanced docking space with themed background effects + */ +class DockSpaceRenderer { +public: + static void BeginEnhancedDockSpace(ImGuiID dockspace_id, const ImVec2& size = ImVec2(0, 0), + ImGuiDockNodeFlags flags = 0); + static void EndEnhancedDockSpace(); + + // Configuration + static void SetBackgroundEnabled(bool enabled) { background_enabled_ = enabled; } + static void SetGridEnabled(bool enabled) { grid_enabled_ = enabled; } + static void SetEffectsEnabled(bool enabled) { effects_enabled_ = enabled; } + +private: + static bool background_enabled_; + static bool grid_enabled_; + static bool effects_enabled_; + static ImVec2 last_dockspace_pos_; + static ImVec2 last_dockspace_size_; +}; + +} // namespace gui +} // namespace yaze + +#endif // YAZE_APP_GUI_BACKGROUND_RENDERER_H diff --git a/src/app/gui/gui.cmake b/src/app/gui/gui.cmake index e3d99d9a..407d897f 100644 --- a/src/app/gui/gui.cmake +++ b/src/app/gui/gui.cmake @@ -7,4 +7,6 @@ set( app/gui/style.cc app/gui/color.cc app/gui/zeml.cc + app/gui/theme_manager.cc + app/gui/background_renderer.cc ) diff --git a/src/app/gui/style.cc b/src/app/gui/style.cc index 4b7cb1ff..96712a01 100644 --- a/src/app/gui/style.cc +++ b/src/app/gui/style.cc @@ -1,6 +1,8 @@ #include "style.h" #include "app/core/platform/file_dialog.h" +#include "app/gui/theme_manager.h" +#include "app/gui/background_renderer.h" #include "core/platform/font_loader.h" #include "gui/color.h" #include "imgui/imgui.h" @@ -408,6 +410,68 @@ void DrawDisplaySettings(ImGuiStyle *ref) { ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + // Enhanced theme selector + auto& theme_manager = ThemeManager::Get(); + static bool show_theme_selector = false; + + ImGui::Text("Theme Selection:"); + + // Add special "Classic YAZE" option first + std::string current_display_name = theme_manager.GetCurrentTheme().name; + if (current_display_name == "Classic YAZE") { + current_display_name = "Classic YAZE (Original)"; + } + + if (ImGui::BeginCombo("##ThemeSelector", current_display_name.c_str())) { + // Classic YAZE option (direct ColorsYaze() function) + bool is_classic_selected = (theme_manager.GetCurrentTheme().name == "Classic YAZE"); + if (ImGui::Selectable("Classic YAZE (Original)", is_classic_selected)) { + theme_manager.ApplyClassicYazeTheme(); + ref_saved_style = style; // Update reference when theme changes + } + if (is_classic_selected) { + ImGui::SetItemDefaultFocus(); + } + + ImGui::Separator(); + + // File-based themes (sorted) + auto available_themes = theme_manager.GetAvailableThemes(); + std::sort(available_themes.begin(), available_themes.end()); + + for (const auto& theme_name : available_themes) { + bool is_selected = (theme_name == theme_manager.GetCurrentTheme().name); + if (ImGui::Selectable(theme_name.c_str(), is_selected)) { + auto status = theme_manager.LoadTheme(theme_name); // Use LoadTheme for consistency + if (!status.ok()) { + // Could show error message to user here + } + ref_saved_style = style; // Update reference when theme changes + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + if (ImGui::Button("Theme Settings")) { + show_theme_selector = true; + } + + if (show_theme_selector) { + theme_manager.ShowThemeSelector(&show_theme_selector); + } + + ImGui::Separator(); + + // Background effects settings + auto& bg_renderer = gui::BackgroundRenderer::Get(); + bg_renderer.DrawSettingsUI(); + + ImGui::Separator(); + if (ImGui::ShowStyleSelector("Colors##Selector")) ref_saved_style = style; ImGui::ShowFontSelector("Fonts##Selector"); diff --git a/src/app/gui/theme_manager.cc b/src/app/gui/theme_manager.cc new file mode 100644 index 00000000..a30a905f --- /dev/null +++ b/src/app/gui/theme_manager.cc @@ -0,0 +1,879 @@ +#include "theme_manager.h" + +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "app/core/platform/file_dialog.h" +#include "app/gui/icons.h" +#include "app/gui/style.h" // For ColorsYaze function +#include "imgui/imgui.h" +#include "util/log.h" + +namespace yaze { +namespace gui { + +// Helper function to create Color from RGB values +Color RGB(float r, float g, float b, float a = 1.0f) { + return {r / 255.0f, g / 255.0f, b / 255.0f, a}; +} + +Color RGBA(int r, int g, int b, int a = 255) { + return {r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; +} + +// Theme Implementation +void EnhancedTheme::ApplyToImGui() const { + ImGuiStyle* style = &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + // Apply colors + colors[ImGuiCol_Text] = ConvertColorToImVec4(text_primary); + colors[ImGuiCol_TextDisabled] = ConvertColorToImVec4(text_disabled); + colors[ImGuiCol_WindowBg] = ConvertColorToImVec4(window_bg); + colors[ImGuiCol_ChildBg] = ConvertColorToImVec4(child_bg); + colors[ImGuiCol_PopupBg] = ConvertColorToImVec4(popup_bg); + colors[ImGuiCol_Border] = ConvertColorToImVec4(border); + colors[ImGuiCol_BorderShadow] = ConvertColorToImVec4(border_shadow); + colors[ImGuiCol_FrameBg] = ConvertColorToImVec4(frame_bg); + colors[ImGuiCol_FrameBgHovered] = ConvertColorToImVec4(frame_bg_hovered); + colors[ImGuiCol_FrameBgActive] = ConvertColorToImVec4(frame_bg_active); + colors[ImGuiCol_TitleBg] = ConvertColorToImVec4(title_bg); + colors[ImGuiCol_TitleBgActive] = ConvertColorToImVec4(title_bg_active); + colors[ImGuiCol_TitleBgCollapsed] = ConvertColorToImVec4(title_bg_collapsed); + colors[ImGuiCol_MenuBarBg] = ConvertColorToImVec4(menu_bar_bg); + colors[ImGuiCol_ScrollbarBg] = ConvertColorToImVec4(scrollbar_bg); + colors[ImGuiCol_ScrollbarGrab] = ConvertColorToImVec4(scrollbar_grab); + colors[ImGuiCol_ScrollbarGrabHovered] = ConvertColorToImVec4(scrollbar_grab_hovered); + colors[ImGuiCol_ScrollbarGrabActive] = ConvertColorToImVec4(scrollbar_grab_active); + colors[ImGuiCol_Button] = ConvertColorToImVec4(button); + colors[ImGuiCol_ButtonHovered] = ConvertColorToImVec4(button_hovered); + colors[ImGuiCol_ButtonActive] = ConvertColorToImVec4(button_active); + colors[ImGuiCol_Header] = ConvertColorToImVec4(header); + colors[ImGuiCol_HeaderHovered] = ConvertColorToImVec4(header_hovered); + colors[ImGuiCol_HeaderActive] = ConvertColorToImVec4(header_active); + colors[ImGuiCol_Separator] = ConvertColorToImVec4(separator); + colors[ImGuiCol_SeparatorHovered] = ConvertColorToImVec4(separator_hovered); + colors[ImGuiCol_SeparatorActive] = ConvertColorToImVec4(separator_active); + colors[ImGuiCol_ResizeGrip] = ConvertColorToImVec4(resize_grip); + colors[ImGuiCol_ResizeGripHovered] = ConvertColorToImVec4(resize_grip_hovered); + colors[ImGuiCol_ResizeGripActive] = ConvertColorToImVec4(resize_grip_active); + colors[ImGuiCol_Tab] = ConvertColorToImVec4(tab); + colors[ImGuiCol_TabHovered] = ConvertColorToImVec4(tab_hovered); + colors[ImGuiCol_TabSelected] = ConvertColorToImVec4(tab_active); + colors[ImGuiCol_DockingPreview] = ConvertColorToImVec4(docking_preview); + colors[ImGuiCol_DockingEmptyBg] = ConvertColorToImVec4(docking_empty_bg); + + // Complete ImGui color support + colors[ImGuiCol_CheckMark] = ConvertColorToImVec4(check_mark); + colors[ImGuiCol_SliderGrab] = ConvertColorToImVec4(slider_grab); + colors[ImGuiCol_SliderGrabActive] = ConvertColorToImVec4(slider_grab_active); + colors[ImGuiCol_InputTextCursor] = ConvertColorToImVec4(input_text_cursor); + colors[ImGuiCol_NavCursor] = ConvertColorToImVec4(nav_cursor); + colors[ImGuiCol_NavWindowingHighlight] = ConvertColorToImVec4(nav_windowing_highlight); + colors[ImGuiCol_NavWindowingDimBg] = ConvertColorToImVec4(nav_windowing_dim_bg); + colors[ImGuiCol_ModalWindowDimBg] = ConvertColorToImVec4(modal_window_dim_bg); + colors[ImGuiCol_TextSelectedBg] = ConvertColorToImVec4(text_selected_bg); + colors[ImGuiCol_DragDropTarget] = ConvertColorToImVec4(drag_drop_target); + colors[ImGuiCol_TableHeaderBg] = ConvertColorToImVec4(table_header_bg); + colors[ImGuiCol_TableBorderStrong] = ConvertColorToImVec4(table_border_strong); + colors[ImGuiCol_TableBorderLight] = ConvertColorToImVec4(table_border_light); + colors[ImGuiCol_TableRowBg] = ConvertColorToImVec4(table_row_bg); + colors[ImGuiCol_TableRowBgAlt] = ConvertColorToImVec4(table_row_bg_alt); + colors[ImGuiCol_TextLink] = ConvertColorToImVec4(text_link); + colors[ImGuiCol_PlotLines] = ConvertColorToImVec4(plot_lines); + colors[ImGuiCol_PlotLinesHovered] = ConvertColorToImVec4(plot_lines_hovered); + colors[ImGuiCol_PlotHistogram] = ConvertColorToImVec4(plot_histogram); + colors[ImGuiCol_PlotHistogramHovered] = ConvertColorToImVec4(plot_histogram_hovered); + + // Apply style parameters + style->WindowRounding = window_rounding; + style->FrameRounding = frame_rounding; + style->ScrollbarRounding = scrollbar_rounding; + style->GrabRounding = grab_rounding; + style->TabRounding = tab_rounding; + style->WindowBorderSize = window_border_size; + style->FrameBorderSize = frame_border_size; +} + + +// ThemeManager Implementation +ThemeManager& ThemeManager::Get() { + static ThemeManager instance; + return instance; +} + +void ThemeManager::InitializeBuiltInThemes() { + // Always create fallback theme first + CreateFallbackYazeClassic(); + + // Try to load themes from files (will override fallback if successful) + std::vector theme_files = { + "yaze_tre.theme", + "cyberpunk.theme", + "sunset.theme", + "forest.theme", + "midnight.theme" + }; + + for (const auto& theme_file : theme_files) { + auto status = LoadThemeFromFile(theme_file); + if (!status.ok()) { + util::logf("Failed to load theme file %s: %s", theme_file.c_str(), status.message().data()); + } + } + + // Ensure we have a valid current theme (prefer file-based theme) + if (themes_.find("YAZE Classic") != themes_.end()) { + current_theme_ = themes_["YAZE Classic"]; + current_theme_name_ = "YAZE Classic"; + } else if (themes_.find("YAZE Tre") != themes_.end()) { + current_theme_ = themes_["YAZE Tre"]; + current_theme_name_ = "YAZE Tre"; + } +} + +void ThemeManager::CreateFallbackYazeClassic() { + // Fallback theme that matches the original ColorsYaze() function colors but in theme format + EnhancedTheme theme; + theme.name = "YAZE Tre"; + theme.description = "YAZE theme resource edition"; + theme.author = "YAZE Team"; + + // Use the exact original ColorsYaze colors + theme.primary = RGBA(92, 115, 92); // allttpLightGreen + theme.secondary = RGBA(71, 92, 71); // alttpMidGreen + theme.accent = RGBA(89, 119, 89); // TabActive + theme.background = RGBA(8, 8, 8); // Very dark gray for better grid visibility + + theme.text_primary = RGBA(230, 230, 230); // 0.90f, 0.90f, 0.90f + theme.text_disabled = RGBA(153, 153, 153); // 0.60f, 0.60f, 0.60f + theme.window_bg = RGBA(8, 8, 8, 217); // Very dark gray with same alpha + theme.child_bg = RGBA(0, 0, 0, 0); // Transparent + theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f + + theme.button = RGBA(71, 92, 71); // alttpMidGreen + theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen + theme.button_active = RGBA(92, 115, 92); // allttpLightGreen + + theme.header = RGBA(46, 66, 46); // alttpDarkGreen + theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen + theme.header_active = RGBA(71, 92, 71); // alttpMidGreen + + theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen + theme.tab = RGBA(46, 66, 46); // alttpDarkGreen + theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen + theme.tab_active = RGBA(89, 119, 89); // TabActive + + // Complete all remaining ImGui colors from original ColorsYaze() function + theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen + theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen + theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen + + // Borders and separators + theme.border = RGBA(92, 115, 92); // allttpLightGreen + theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent + theme.separator = RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f + theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f + theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f + + // Scrollbars + theme.scrollbar_bg = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f + theme.scrollbar_grab = RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f + theme.scrollbar_grab_hovered = RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f + theme.scrollbar_grab_active = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f + + // Resize grips (from original - light blue highlights) + theme.resize_grip = RGBA(255, 255, 255, 26); // 1.00f, 1.00f, 1.00f, 0.10f + theme.resize_grip_hovered = RGBA(199, 209, 255, 153); // 0.78f, 0.82f, 1.00f, 0.60f + theme.resize_grip_active = RGBA(199, 209, 255, 230); // 0.78f, 0.82f, 1.00f, 0.90f + + // Complete ImGui colors with smart defaults using accent colors + theme.check_mark = RGBA(230, 230, 230, 128); // 0.90f, 0.90f, 0.90f, 0.50f + theme.slider_grab = RGBA(255, 255, 255, 77); // 1.00f, 1.00f, 1.00f, 0.30f + theme.slider_grab_active = RGBA(92, 115, 92, 153); // Same as scrollbar for consistency + theme.input_text_cursor = theme.text_primary; // Use primary text color + theme.nav_cursor = theme.accent; // Use accent color for navigation + theme.nav_windowing_highlight = theme.accent; // Accent for window switching + theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 128); // Semi-transparent overlay + theme.modal_window_dim_bg = RGBA(0, 0, 0, 89); // 0.35f alpha + theme.text_selected_bg = RGBA(89, 119, 89, 89); // Accent color with transparency + theme.drag_drop_target = theme.accent; // Use accent for drag targets + + // Table colors (from original) + theme.table_header_bg = RGBA(46, 66, 46); // alttpDarkGreen + theme.table_border_strong = RGBA(71, 92, 71); // alttpMidGreen + theme.table_border_light = RGBA(66, 66, 71); // 0.26f, 0.26f, 0.28f + theme.table_row_bg = RGBA(0, 0, 0, 0); // Transparent + theme.table_row_bg_alt = RGBA(255, 255, 255, 18); // 1.00f, 1.00f, 1.00f, 0.07f + + // Links and plots - use accent colors intelligently + theme.text_link = theme.accent; // Accent for links + theme.plot_lines = RGBA(255, 255, 255); // White for plots + theme.plot_lines_hovered = RGBA(230, 178, 0); // 0.90f, 0.70f, 0.00f + theme.plot_histogram = RGBA(230, 178, 0); // Same as above + theme.plot_histogram_hovered = RGBA(255, 153, 0); // 1.00f, 0.60f, 0.00f + + // Docking colors + theme.docking_preview = RGBA(92, 115, 92, 180); // Light green with transparency + theme.docking_empty_bg = RGBA(46, 66, 46, 255); // Dark green + + // Apply original style settings + theme.window_rounding = 0.0f; + theme.frame_rounding = 5.0f; + theme.scrollbar_rounding = 5.0f; + theme.tab_rounding = 0.0f; + theme.enable_glow_effects = false; + + themes_["YAZE Tre"] = theme; + current_theme_ = theme; + current_theme_name_ = "YAZE Tre"; +} + +absl::Status ThemeManager::LoadTheme(const std::string& theme_name) { + auto it = themes_.find(theme_name); + if (it == themes_.end()) { + return absl::NotFoundError(absl::StrFormat("Theme '%s' not found", theme_name)); + } + + current_theme_ = it->second; + current_theme_name_ = theme_name; + current_theme_.ApplyToImGui(); + + return absl::OkStatus(); +} + +absl::Status ThemeManager::LoadThemeFromFile(const std::string& filepath) { + // Try multiple possible paths where theme files might be located + std::vector possible_paths = { + filepath, // Absolute path + "assets/themes/" + filepath, // Relative from build dir + "../assets/themes/" + filepath, // Relative from bin dir + core::GetResourcePath("assets/themes/" + filepath), // Platform-specific resource path + }; + + std::ifstream file; + std::string successful_path; + + for (const auto& path : possible_paths) { + util::logf("Trying to open theme file: %s", path.c_str()); + file.open(path); + if (file.is_open()) { + successful_path = path; + util::logf("✅ Successfully opened theme file: %s", path.c_str()); + break; + } else { + util::logf("❌ Failed to open theme file: %s", path.c_str()); + file.clear(); // Clear any error flags before trying next path + } + } + + if (!file.is_open()) { + return absl::InvalidArgumentError(absl::StrFormat("Cannot open theme file: %s (tried %zu paths)", + filepath, possible_paths.size())); + } + + std::string content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + file.close(); + + if (content.empty()) { + return absl::InvalidArgumentError(absl::StrFormat("Theme file is empty: %s", successful_path)); + } + + EnhancedTheme theme; + auto parse_status = ParseThemeFile(content, theme); + if (!parse_status.ok()) { + return absl::InvalidArgumentError(absl::StrFormat("Failed to parse theme file %s: %s", + successful_path, parse_status.message())); + } + + if (theme.name.empty()) { + return absl::InvalidArgumentError(absl::StrFormat("Theme file missing name: %s", successful_path)); + } + + themes_[theme.name] = theme; + return absl::OkStatus(); +} + +std::vector ThemeManager::GetAvailableThemes() const { + std::vector theme_names; + for (const auto& [name, theme] : themes_) { + theme_names.push_back(name); + } + return theme_names; +} + +const EnhancedTheme* ThemeManager::GetTheme(const std::string& name) const { + auto it = themes_.find(name); + return (it != themes_.end()) ? &it->second : nullptr; +} + +void ThemeManager::ApplyTheme(const std::string& theme_name) { + auto status = LoadTheme(theme_name); + if (!status.ok()) { + // 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()); + } + } +} + +void ThemeManager::ApplyTheme(const EnhancedTheme& theme) { + current_theme_ = theme; + current_theme_.ApplyToImGui(); +} + +Color ThemeManager::GetWelcomeScreenBackground() const { + // Create a darker version of the window background for welcome screen + Color bg = current_theme_.window_bg; + return {bg.red * 0.8f, bg.green * 0.8f, bg.blue * 0.8f, bg.alpha}; +} + +Color ThemeManager::GetWelcomeScreenBorder() const { + return current_theme_.accent; +} + +Color ThemeManager::GetWelcomeScreenAccent() const { + return current_theme_.primary; +} + +void ThemeManager::ShowThemeSelector(bool* p_open) { + if (!p_open || !*p_open) return; + + if (ImGui::Begin(absl::StrFormat("%s Theme Selector", ICON_MD_PALETTE).c_str(), p_open)) { + ImGui::Text("%s Available Themes", ICON_MD_COLOR_LENS); + ImGui::Separator(); + + // Add Classic YAZE button first (direct ColorsYaze() application) + bool is_classic_active = (current_theme_name_ == "Classic YAZE"); + if (is_classic_active) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.36f, 0.45f, 0.36f, 1.0f)); // allttpLightGreen + } + + if (ImGui::Button(absl::StrFormat("%s YAZE Classic (Original)", + is_classic_active ? ICON_MD_CHECK : ICON_MD_STAR).c_str(), + ImVec2(-1, 50))) { + ApplyClassicYazeTheme(); + } + + if (is_classic_active) { + ImGui::PopStyleColor(); + } + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Original YAZE theme using ColorsYaze() function"); + ImGui::Text("This is the authentic classic look - direct function call"); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + + // Sort themes alphabetically for consistent ordering (by name only) + std::vector sorted_theme_names; + for (const auto& [name, theme] : themes_) { + sorted_theme_names.push_back(name); + } + std::sort(sorted_theme_names.begin(), sorted_theme_names.end()); + + for (const auto& name : sorted_theme_names) { + const auto& theme = themes_.at(name); + bool is_current = (name == current_theme_name_); + + if (is_current) { + ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.accent)); + } + + if (ImGui::Button(absl::StrFormat("%s %s", + is_current ? ICON_MD_CHECK : ICON_MD_CIRCLE, + name.c_str()).c_str(), ImVec2(-1, 40))) { + auto status = LoadTheme(name); // Use LoadTheme instead of ApplyTheme to ensure correct tracking + if (!status.ok()) { + util::logf("Failed to load theme %s: %s", name.c_str(), status.message().data()); + } + } + + if (is_current) { + ImGui::PopStyleColor(); + } + + // Show theme preview colors + ImGui::SameLine(); + ImGui::ColorButton(absl::StrFormat("##primary_%s", name.c_str()).c_str(), + ConvertColorToImVec4(theme.primary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::SameLine(); + ImGui::ColorButton(absl::StrFormat("##secondary_%s", name.c_str()).c_str(), + ConvertColorToImVec4(theme.secondary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::SameLine(); + ImGui::ColorButton(absl::StrFormat("##accent_%s", name.c_str()).c_str(), + ConvertColorToImVec4(theme.accent), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", theme.description.c_str()); + ImGui::Text("Author: %s", theme.author.c_str()); + ImGui::EndTooltip(); + } + } + + ImGui::Separator(); + if (ImGui::Button(absl::StrFormat("%s Load Custom Theme", ICON_MD_FOLDER_OPEN).c_str())) { + auto file_path = core::FileDialogWrapper::ShowOpenFileDialog(); + if (!file_path.empty()) { + auto status = LoadThemeFromFile(file_path); + if (!status.ok()) { + // Show error toast (would need access to toast manager) + } + } + } + + ImGui::SameLine(); + static bool show_simple_editor = false; + if (ImGui::Button(absl::StrFormat("%s Theme Editor", ICON_MD_EDIT).c_str())) { + show_simple_editor = true; + } + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Edit and save custom themes"); + ImGui::Text("Includes 'Save to File' functionality"); + ImGui::EndTooltip(); + } + + if (show_simple_editor) { + ShowSimpleThemeEditor(&show_simple_editor); + } + } + ImGui::End(); +} + +absl::Status ThemeManager::ParseThemeFile(const std::string& content, EnhancedTheme& theme) { + std::istringstream stream(content); + std::string line; + std::string current_section = ""; + + while (std::getline(stream, line)) { + // Skip empty lines and comments + if (line.empty() || line[0] == '#') continue; + + // Check for section headers [section_name] + if (line[0] == '[' && line.back() == ']') { + current_section = line.substr(1, line.length() - 2); + continue; + } + + size_t eq_pos = line.find('='); + if (eq_pos == std::string::npos) continue; + + std::string key = line.substr(0, eq_pos); + std::string value = line.substr(eq_pos + 1); + + // Trim whitespace and comments + key.erase(0, key.find_first_not_of(" \t")); + key.erase(key.find_last_not_of(" \t") + 1); + value.erase(0, value.find_first_not_of(" \t")); + + // Remove inline comments + size_t comment_pos = value.find('#'); + if (comment_pos != std::string::npos) { + value = value.substr(0, comment_pos); + } + value.erase(value.find_last_not_of(" \t") + 1); + + // Parse based on section + if (current_section == "colors") { + Color color = ParseColorFromString(value); + + if (key == "primary") theme.primary = color; + else if (key == "secondary") theme.secondary = color; + else if (key == "accent") theme.accent = color; + else if (key == "background") theme.background = color; + else if (key == "surface") theme.surface = color; + else if (key == "error") theme.error = color; + else if (key == "warning") theme.warning = color; + else if (key == "success") theme.success = color; + else if (key == "info") theme.info = color; + else if (key == "text_primary") theme.text_primary = color; + else if (key == "text_secondary") theme.text_secondary = color; + else if (key == "text_disabled") theme.text_disabled = color; + else if (key == "window_bg") theme.window_bg = color; + else if (key == "child_bg") theme.child_bg = color; + else if (key == "popup_bg") theme.popup_bg = color; + else if (key == "button") theme.button = color; + else if (key == "button_hovered") theme.button_hovered = color; + else if (key == "button_active") theme.button_active = color; + else if (key == "frame_bg") theme.frame_bg = color; + else if (key == "frame_bg_hovered") theme.frame_bg_hovered = color; + else if (key == "frame_bg_active") theme.frame_bg_active = color; + else if (key == "header") theme.header = color; + else if (key == "header_hovered") theme.header_hovered = color; + else if (key == "header_active") theme.header_active = color; + else if (key == "tab") theme.tab = color; + else if (key == "tab_hovered") theme.tab_hovered = color; + else if (key == "tab_active") theme.tab_active = color; + else if (key == "menu_bar_bg") theme.menu_bar_bg = color; + else if (key == "title_bg") theme.title_bg = color; + else if (key == "title_bg_active") theme.title_bg_active = color; + else if (key == "title_bg_collapsed") theme.title_bg_collapsed = color; + else if (key == "separator") theme.separator = color; + else if (key == "separator_hovered") theme.separator_hovered = color; + else if (key == "separator_active") theme.separator_active = color; + else if (key == "scrollbar_bg") theme.scrollbar_bg = color; + else if (key == "scrollbar_grab") theme.scrollbar_grab = color; + else if (key == "scrollbar_grab_hovered") theme.scrollbar_grab_hovered = color; + else if (key == "scrollbar_grab_active") theme.scrollbar_grab_active = color; + else if (key == "border") theme.border = color; + else if (key == "border_shadow") theme.border_shadow = color; + else if (key == "resize_grip") theme.resize_grip = color; + else if (key == "resize_grip_hovered") theme.resize_grip_hovered = color; + else if (key == "resize_grip_active") theme.resize_grip_active = color; + // Note: Additional colors like check_mark, slider_grab, table colors + // are handled by the fallback or can be added to EnhancedTheme struct as needed + } + else if (current_section == "style") { + if (key == "window_rounding") theme.window_rounding = std::stof(value); + else if (key == "frame_rounding") theme.frame_rounding = std::stof(value); + else if (key == "scrollbar_rounding") theme.scrollbar_rounding = std::stof(value); + else if (key == "grab_rounding") theme.grab_rounding = std::stof(value); + else if (key == "tab_rounding") theme.tab_rounding = std::stof(value); + else if (key == "window_border_size") theme.window_border_size = std::stof(value); + else if (key == "frame_border_size") theme.frame_border_size = std::stof(value); + else if (key == "enable_animations") theme.enable_animations = (value == "true"); + else if (key == "enable_glow_effects") theme.enable_glow_effects = (value == "true"); + else if (key == "animation_speed") theme.animation_speed = std::stof(value); + } + else if (current_section == "" || current_section == "metadata") { + // Top-level metadata + if (key == "name") theme.name = value; + else if (key == "description") theme.description = value; + else if (key == "author") theme.author = value; + } + } + + return absl::OkStatus(); +} + +Color ThemeManager::ParseColorFromString(const std::string& color_str) const { + std::vector components = absl::StrSplit(color_str, ','); + if (components.size() != 4) { + return RGBA(255, 255, 255, 255); // White fallback + } + + try { + int r = std::stoi(components[0]); + int g = std::stoi(components[1]); + int b = std::stoi(components[2]); + int a = std::stoi(components[3]); + return RGBA(r, g, b, a); + } catch (...) { + return RGBA(255, 255, 255, 255); // White fallback + } +} + +std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const { + std::ostringstream ss; + + // Helper function to convert color to RGB string + auto colorToString = [](const Color& c) -> std::string { + int r = static_cast(c.red * 255.0f); + int g = static_cast(c.green * 255.0f); + int b = static_cast(c.blue * 255.0f); + int a = static_cast(c.alpha * 255.0f); + return std::to_string(r) + "," + std::to_string(g) + "," + std::to_string(b) + "," + std::to_string(a); + }; + + ss << "# YAZE Theme File\n"; + ss << "# Generated by YAZE Theme Editor\n"; + ss << "name=" << theme.name << "\n"; + ss << "description=" << theme.description << "\n"; + ss << "author=" << theme.author << "\n"; + ss << "version=1.0\n"; + ss << "\n[colors]\n"; + + // Primary colors + ss << "# Primary colors\n"; + ss << "primary=" << colorToString(theme.primary) << "\n"; + ss << "secondary=" << colorToString(theme.secondary) << "\n"; + ss << "accent=" << colorToString(theme.accent) << "\n"; + ss << "background=" << colorToString(theme.background) << "\n"; + ss << "surface=" << colorToString(theme.surface) << "\n"; + ss << "\n"; + + // Status colors + ss << "# Status colors\n"; + ss << "error=" << colorToString(theme.error) << "\n"; + ss << "warning=" << colorToString(theme.warning) << "\n"; + ss << "success=" << colorToString(theme.success) << "\n"; + ss << "info=" << colorToString(theme.info) << "\n"; + ss << "\n"; + + // Text colors + ss << "# Text colors\n"; + ss << "text_primary=" << colorToString(theme.text_primary) << "\n"; + ss << "text_secondary=" << colorToString(theme.text_secondary) << "\n"; + ss << "text_disabled=" << colorToString(theme.text_disabled) << "\n"; + ss << "\n"; + + // Window colors + ss << "# Window colors\n"; + ss << "window_bg=" << colorToString(theme.window_bg) << "\n"; + ss << "child_bg=" << colorToString(theme.child_bg) << "\n"; + ss << "popup_bg=" << colorToString(theme.popup_bg) << "\n"; + ss << "\n"; + + // Interactive elements + ss << "# Interactive elements\n"; + ss << "button=" << colorToString(theme.button) << "\n"; + ss << "button_hovered=" << colorToString(theme.button_hovered) << "\n"; + ss << "button_active=" << colorToString(theme.button_active) << "\n"; + ss << "frame_bg=" << colorToString(theme.frame_bg) << "\n"; + ss << "frame_bg_hovered=" << colorToString(theme.frame_bg_hovered) << "\n"; + ss << "frame_bg_active=" << colorToString(theme.frame_bg_active) << "\n"; + ss << "\n"; + + // Navigation + ss << "# Navigation\n"; + ss << "header=" << colorToString(theme.header) << "\n"; + ss << "header_hovered=" << colorToString(theme.header_hovered) << "\n"; + ss << "header_active=" << colorToString(theme.header_active) << "\n"; + ss << "tab=" << colorToString(theme.tab) << "\n"; + ss << "tab_hovered=" << colorToString(theme.tab_hovered) << "\n"; + ss << "tab_active=" << colorToString(theme.tab_active) << "\n"; + ss << "menu_bar_bg=" << colorToString(theme.menu_bar_bg) << "\n"; + ss << "title_bg=" << colorToString(theme.title_bg) << "\n"; + ss << "title_bg_active=" << colorToString(theme.title_bg_active) << "\n"; + ss << "title_bg_collapsed=" << colorToString(theme.title_bg_collapsed) << "\n"; + ss << "\n"; + + // Borders and separators + ss << "# Borders and separators\n"; + ss << "border=" << colorToString(theme.border) << "\n"; + ss << "border_shadow=" << colorToString(theme.border_shadow) << "\n"; + ss << "separator=" << colorToString(theme.separator) << "\n"; + ss << "separator_hovered=" << colorToString(theme.separator_hovered) << "\n"; + ss << "separator_active=" << colorToString(theme.separator_active) << "\n"; + ss << "\n"; + + // Scrollbars + ss << "# Scrollbars\n"; + ss << "scrollbar_bg=" << colorToString(theme.scrollbar_bg) << "\n"; + ss << "scrollbar_grab=" << colorToString(theme.scrollbar_grab) << "\n"; + ss << "scrollbar_grab_hovered=" << colorToString(theme.scrollbar_grab_hovered) << "\n"; + ss << "scrollbar_grab_active=" << colorToString(theme.scrollbar_grab_active) << "\n"; + ss << "\n"; + + // Style settings + ss << "[style]\n"; + ss << "window_rounding=" << theme.window_rounding << "\n"; + ss << "frame_rounding=" << theme.frame_rounding << "\n"; + ss << "scrollbar_rounding=" << theme.scrollbar_rounding << "\n"; + ss << "tab_rounding=" << theme.tab_rounding << "\n"; + ss << "enable_animations=" << (theme.enable_animations ? "true" : "false") << "\n"; + ss << "enable_glow_effects=" << (theme.enable_glow_effects ? "true" : "false") << "\n"; + + return ss.str(); +} + +absl::Status ThemeManager::SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const { + std::string theme_content = SerializeTheme(theme); + + std::ofstream file(filepath); + if (!file.is_open()) { + return absl::InternalError(absl::StrFormat("Failed to open file for writing: %s", filepath)); + } + + file << theme_content; + file.close(); + + if (file.fail()) { + return absl::InternalError(absl::StrFormat("Failed to write theme file: %s", filepath)); + } + + util::logf("✅ Successfully saved theme '%s' to file: %s", theme.name.c_str(), filepath.c_str()); + return absl::OkStatus(); +} + +void ThemeManager::ApplyClassicYazeTheme() { + // Apply the original ColorsYaze() function directly + ColorsYaze(); + current_theme_name_ = "Classic YAZE"; + + // Update current_theme_ to reflect the applied colors for consistency + // (This creates a temporary theme object that matches what ColorsYaze() sets) + EnhancedTheme classic_theme; + classic_theme.name = "Classic YAZE"; + classic_theme.description = "Original YAZE theme (direct ColorsYaze() function)"; + classic_theme.author = "YAZE Team"; + + // Extract the basic colors that ColorsYaze() sets (adjusted for grid visibility) + classic_theme.primary = RGBA(92, 115, 92); // allttpLightGreen + classic_theme.secondary = RGBA(71, 92, 71); // alttpMidGreen + classic_theme.accent = RGBA(89, 119, 89); // TabActive color + classic_theme.background = RGBA(8, 8, 8); // Very dark gray for better grid visibility + + current_theme_ = classic_theme; +} + +void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { + if (!p_open || !*p_open) return; + + if (ImGui::Begin(absl::StrFormat("%s Simple Theme Editor", ICON_MD_PALETTE).c_str(), p_open)) { + ImGui::Text("%s Create or modify themes with basic controls", ICON_MD_EDIT); + ImGui::Separator(); + + static EnhancedTheme edit_theme = current_theme_; + static char theme_name[128]; + static char theme_description[256]; + static char theme_author[128]; + + // Basic theme info + ImGui::InputText("Theme Name", theme_name, sizeof(theme_name)); + ImGui::InputText("Description", theme_description, sizeof(theme_description)); + ImGui::InputText("Author", theme_author, sizeof(theme_author)); + + ImGui::Separator(); + + // Primary Colors + if (ImGui::CollapsingHeader("Primary Colors", ImGuiTreeNodeFlags_DefaultOpen)) { + ImVec4 primary = ConvertColorToImVec4(edit_theme.primary); + ImVec4 secondary = ConvertColorToImVec4(edit_theme.secondary); + ImVec4 accent = ConvertColorToImVec4(edit_theme.accent); + ImVec4 background = ConvertColorToImVec4(edit_theme.background); + + if (ImGui::ColorEdit3("Primary", &primary.x)) { + edit_theme.primary = {primary.x, primary.y, primary.z, primary.w}; + } + if (ImGui::ColorEdit3("Secondary", &secondary.x)) { + edit_theme.secondary = {secondary.x, secondary.y, secondary.z, secondary.w}; + } + if (ImGui::ColorEdit3("Accent", &accent.x)) { + edit_theme.accent = {accent.x, accent.y, accent.z, accent.w}; + } + if (ImGui::ColorEdit3("Background", &background.x)) { + edit_theme.background = {background.x, background.y, background.z, background.w}; + } + } + + // Text Colors + if (ImGui::CollapsingHeader("Text Colors")) { + ImVec4 text_primary = ConvertColorToImVec4(edit_theme.text_primary); + ImVec4 text_secondary = ConvertColorToImVec4(edit_theme.text_secondary); + ImVec4 text_disabled = ConvertColorToImVec4(edit_theme.text_disabled); + + if (ImGui::ColorEdit3("Primary Text", &text_primary.x)) { + edit_theme.text_primary = {text_primary.x, text_primary.y, text_primary.z, text_primary.w}; + } + if (ImGui::ColorEdit3("Secondary Text", &text_secondary.x)) { + edit_theme.text_secondary = {text_secondary.x, text_secondary.y, text_secondary.z, text_secondary.w}; + } + if (ImGui::ColorEdit3("Disabled Text", &text_disabled.x)) { + edit_theme.text_disabled = {text_disabled.x, text_disabled.y, text_disabled.z, text_disabled.w}; + } + } + + // Window Colors + if (ImGui::CollapsingHeader("Window Colors")) { + ImVec4 window_bg = ConvertColorToImVec4(edit_theme.window_bg); + ImVec4 popup_bg = ConvertColorToImVec4(edit_theme.popup_bg); + + if (ImGui::ColorEdit4("Window Background", &window_bg.x)) { + edit_theme.window_bg = {window_bg.x, window_bg.y, window_bg.z, window_bg.w}; + } + if (ImGui::ColorEdit4("Popup Background", &popup_bg.x)) { + edit_theme.popup_bg = {popup_bg.x, popup_bg.y, popup_bg.z, popup_bg.w}; + } + } + + // Interactive Elements + if (ImGui::CollapsingHeader("Interactive Elements")) { + ImVec4 button = ConvertColorToImVec4(edit_theme.button); + ImVec4 button_hovered = ConvertColorToImVec4(edit_theme.button_hovered); + ImVec4 button_active = ConvertColorToImVec4(edit_theme.button_active); + + if (ImGui::ColorEdit3("Button", &button.x)) { + edit_theme.button = {button.x, button.y, button.z, button.w}; + } + if (ImGui::ColorEdit3("Button Hovered", &button_hovered.x)) { + edit_theme.button_hovered = {button_hovered.x, button_hovered.y, button_hovered.z, button_hovered.w}; + } + if (ImGui::ColorEdit3("Button Active", &button_active.x)) { + edit_theme.button_active = {button_active.x, button_active.y, button_active.z, button_active.w}; + } + } + + ImGui::Separator(); + + if (ImGui::Button("Preview Theme")) { + ApplyTheme(edit_theme); + } + + ImGui::SameLine(); + if (ImGui::Button("Reset to Current")) { + edit_theme = current_theme_; + strncpy(theme_name, current_theme_.name.c_str(), sizeof(theme_name)); + strncpy(theme_description, current_theme_.description.c_str(), sizeof(theme_description)); + strncpy(theme_author, current_theme_.author.c_str(), sizeof(theme_author)); + } + + ImGui::SameLine(); + if (ImGui::Button("Save Theme")) { + edit_theme.name = std::string(theme_name); + edit_theme.description = std::string(theme_description); + edit_theme.author = std::string(theme_author); + + // Add to themes map and apply + themes_[edit_theme.name] = edit_theme; + ApplyTheme(edit_theme); + } + + ImGui::SameLine(); + if (ImGui::Button("Save to File...")) { + edit_theme.name = std::string(theme_name); + edit_theme.description = std::string(theme_description); + edit_theme.author = std::string(theme_author); + + // Use folder dialog to choose save location + auto folder_path = core::FileDialogWrapper::ShowOpenFolderDialog(); + if (!folder_path.empty()) { + // Create filename from theme name (sanitize it) + std::string safe_name = edit_theme.name; + // Replace spaces and special chars with underscores + for (char& c : safe_name) { + if (!std::isalnum(c)) { + c = '_'; + } + } + + std::string file_path = folder_path + "/" + safe_name + ".theme"; + + auto status = SaveThemeToFile(edit_theme, file_path); + if (status.ok()) { + // Also add to themes map for immediate use + themes_[edit_theme.name] = edit_theme; + ApplyTheme(edit_theme); + util::logf("Theme saved successfully to: %s", file_path.c_str()); + } else { + util::logf("Failed to save theme: %s", status.message().data()); + } + } + } + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Save theme to a .theme file"); + ImGui::Text("Saved themes can be shared and loaded later"); + ImGui::EndTooltip(); + } + } + ImGui::End(); +} + +} // namespace gui +} // namespace yaze diff --git a/src/app/gui/theme_manager.h b/src/app/gui/theme_manager.h new file mode 100644 index 00000000..2ad2c12b --- /dev/null +++ b/src/app/gui/theme_manager.h @@ -0,0 +1,179 @@ +#ifndef YAZE_APP_GUI_THEME_MANAGER_H +#define YAZE_APP_GUI_THEME_MANAGER_H + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "app/gui/color.h" +#include "imgui/imgui.h" + +namespace yaze { +namespace gui { + +/** + * @struct EnhancedTheme + * @brief Comprehensive theme structure for YAZE + */ +struct EnhancedTheme { + std::string name; + std::string description; + std::string author; + + // Primary colors + Color primary; + Color secondary; + Color accent; + Color background; + Color surface; + Color error; + Color warning; + Color success; + Color info; + + // Text colors + Color text_primary; + Color text_secondary; + Color text_disabled; + + // Window colors + Color window_bg; + Color child_bg; + Color popup_bg; + Color modal_bg; + + // Interactive elements + Color button; + Color button_hovered; + Color button_active; + Color frame_bg; + Color frame_bg_hovered; + Color frame_bg_active; + + // Navigation and selection + Color header; + Color header_hovered; + Color header_active; + Color tab; + Color tab_hovered; + Color tab_active; + Color menu_bar_bg; + Color title_bg; + Color title_bg_active; + Color title_bg_collapsed; + + // Borders and separators + Color border; + Color border_shadow; + Color separator; + Color separator_hovered; + Color separator_active; + + // Scrollbars and controls + Color scrollbar_bg; + Color scrollbar_grab; + Color scrollbar_grab_hovered; + Color scrollbar_grab_active; + + // Special elements + Color resize_grip; + Color resize_grip_hovered; + Color resize_grip_active; + Color docking_preview; + Color docking_empty_bg; + + // Complete ImGui color support + Color check_mark; + Color slider_grab; + Color slider_grab_active; + Color input_text_cursor; + Color nav_cursor; + Color nav_windowing_highlight; + Color nav_windowing_dim_bg; + Color modal_window_dim_bg; + Color text_selected_bg; + Color drag_drop_target; + Color table_header_bg; + Color table_border_strong; + Color table_border_light; + Color table_row_bg; + Color table_row_bg_alt; + Color text_link; + Color plot_lines; + Color plot_lines_hovered; + Color plot_histogram; + Color plot_histogram_hovered; + + // Style parameters + float window_rounding = 0.0f; + float frame_rounding = 5.0f; + float scrollbar_rounding = 5.0f; + float grab_rounding = 3.0f; + float tab_rounding = 0.0f; + float window_border_size = 0.0f; + float frame_border_size = 0.0f; + + // Animation and effects + bool enable_animations = true; + float animation_speed = 1.0f; + bool enable_glow_effects = false; + + // Helper methods + void ApplyToImGui() const; +}; + +/** + * @class ThemeManager + * @brief Manages themes, loading, saving, and switching + */ +class ThemeManager { +public: + static ThemeManager& Get(); + + // Theme management + absl::Status LoadTheme(const std::string& theme_name); + absl::Status SaveTheme(const EnhancedTheme& theme, const std::string& filename); + absl::Status LoadThemeFromFile(const std::string& filepath); + absl::Status SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const; + + // Built-in themes + void InitializeBuiltInThemes(); + std::vector GetAvailableThemes() const; + const EnhancedTheme* GetTheme(const std::string& name) const; + const EnhancedTheme& GetCurrentTheme() const { return current_theme_; } + + // Theme application + void ApplyTheme(const std::string& theme_name); + void ApplyTheme(const EnhancedTheme& theme); + void ApplyClassicYazeTheme(); // Apply original ColorsYaze() function + + // Theme creation and editing + EnhancedTheme CreateCustomTheme(const std::string& name); + void ShowThemeEditor(bool* p_open); + void ShowThemeSelector(bool* p_open); + void ShowSimpleThemeEditor(bool* p_open); + + // Integration with welcome screen + Color GetWelcomeScreenBackground() const; + Color GetWelcomeScreenBorder() const; + Color GetWelcomeScreenAccent() const; + +private: + ThemeManager() { InitializeBuiltInThemes(); } + + std::map themes_; + EnhancedTheme current_theme_; + std::string current_theme_name_ = "YAZE Classic"; + + void CreateFallbackYazeClassic(); + absl::Status ParseThemeFile(const std::string& content, EnhancedTheme& theme); + Color ParseColorFromString(const std::string& color_str) const; + std::string SerializeTheme(const EnhancedTheme& theme) const; +}; + +} // namespace gui +} // namespace yaze + +#endif // YAZE_APP_GUI_THEME_MANAGER_H diff --git a/src/app/main.cc b/src/app/main.cc index cdc2fc1b..8bcdce2b 100644 --- a/src/app/main.cc +++ b/src/app/main.cc @@ -5,7 +5,9 @@ #include "absl/debugging/failure_signal_handler.h" #include "absl/debugging/symbolize.h" #include "app/core/controller.h" +#include "app/core/features.h" #include "util/flag.h" +#include "util/log.h" /** * @namespace yaze @@ -13,7 +15,10 @@ */ using namespace yaze; +// Enhanced flags for debugging 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."); int main(int argc, char **argv) { absl::InitializeSymbolizer(argv[0]); @@ -30,8 +35,22 @@ int main(int argc, char **argv) { options.writerfn = nullptr; // Use default writer to avoid custom handling issues absl::InstallFailureSignalHandler(options); + + // Parse command line flags with custom parser yaze::util::FlagParser parser(yaze::util::global_flag_registry()); RETURN_IF_EXCEPTION(parser.Parse(argc, argv)); + + // Set up logging and debug flags (using custom flag system) + if (!FLAGS_log_file->Get().empty()) { + yaze::util::SetLogFile(FLAGS_log_file->Get()); + } + + // Enable debugging if requested + if (FLAGS_debug->Get()) { + yaze::core::FeatureFlags::get().kLogToConsole = true; + yaze::util::logf("🚀 YAZE started in debug mode"); + } + std::string rom_filename = ""; if (!FLAGS_rom_file->Get().empty()) { rom_filename = FLAGS_rom_file->Get(); diff --git a/src/ios/main.mm b/src/ios/main.mm index dc1871f6..abb98a44 100644 --- a/src/ios/main.mm +++ b/src/ios/main.mm @@ -128,6 +128,8 @@ } - (void)drawInMTKView:(MTKView *)view { + if (!_controller->active()) return; + ImGuiIO &io = ImGui::GetIO(); io.DisplaySize.x = view.bounds.size.width; io.DisplaySize.y = view.bounds.size.height; @@ -235,6 +237,9 @@ ImGuiIO &io = ImGui::GetIO(); io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); io.AddMouseWheelEvent(0.0f, gesture.scale); + UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture; + io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x, + [gestureRecognizer locationInView:self.view].y); } - (void)HandleSwipe:(UISwipeGestureRecognizer *)gesture { @@ -245,12 +250,18 @@ } else if (gesture.direction == UISwipeGestureRecognizerDirectionLeft) { io.AddMouseWheelEvent(-1.0f, 0.0f); // Swipe Left } + UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture; + io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x, + [gestureRecognizer locationInView:self.view].y); } - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture { ImGuiIO &io = ImGui::GetIO(); io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); io.AddMouseButtonEvent(1, gesture.state == UIGestureRecognizerStateBegan); + UIGestureRecognizer *gestureRecognizer = (UIGestureRecognizer *)gesture; + io.AddMousePosEvent([gestureRecognizer locationInView:self.view].x, + [gestureRecognizer locationInView:self.view].y); } #endif diff --git a/src/util/log.h b/src/util/log.h index 81bd5a7a..9da1b7b9 100644 --- a/src/util/log.h +++ b/src/util/log.h @@ -14,7 +14,12 @@ namespace yaze { namespace util { -static const std::string kLogFileOut = "yaze_log.txt"; +static std::string g_log_file_path = "yaze_log.txt"; + +// Set custom log file path +inline void SetLogFile(const std::string& filepath) { + g_log_file_path = filepath; +} template static void logf(const absl::FormatSpec &format, const Args &...args) { @@ -29,8 +34,20 @@ static void logf(const absl::FormatSpec &format, const Args &...args) { if (core::FeatureFlags::get().kLogToConsole) { std::cout << message; } - static std::ofstream fout(kLogFileOut, std::ios::out | std::ios::app); + + // Use the configurable log file path + static std::ofstream fout; + static std::string last_log_path = ""; + + // Reopen file if path changed + if (g_log_file_path != last_log_path) { + fout.close(); + fout.open(g_log_file_path, std::ios::out | std::ios::app); + last_log_path = g_log_file_path; + } + fout << message; + fout.flush(); // Ensure immediate write for debugging } } // namespace util