diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index d0ede7f6..d2bbfea5 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -780,7 +780,8 @@ absl::Status EditorManager::Update() { } // Show welcome screen if no editors are active (ROM loaded but editors not opened) - if (!any_editor_active) { + // Only show if explicitly requested to avoid stacking with manual welcome screen + if (!any_editor_active && !show_welcome_screen_) { DrawWelcomeScreen(); return absl::OkStatus(); } @@ -1070,18 +1071,9 @@ void EditorManager::DrawMenuBar() { } if (show_display_settings) { - Begin("Display Settings", &show_display_settings, ImGuiWindowFlags_None); - gui::DrawDisplaySettings(); - gui::TextWithSeparators("Font Manager"); - gui::DrawFontManager(); - ImGuiIO &io = ImGui::GetIO(); - Separator(); - Text("Global Scale"); - if (SliderFloat("##global_scale", &font_global_scale_, 0.5f, 1.8f, "%.2f")) { - io.FontGlobalScale = font_global_scale_; - SaveUserSettings(); - } - End(); + // Use the popup manager instead of a separate window + popup_manager_->Show("Display Settings"); + show_display_settings = false; // Close the old-style window } if (show_imgui_demo_) ShowDemoWindow(&show_imgui_demo_); @@ -1111,60 +1103,247 @@ void EditorManager::DrawMenuBar() { End(); } - // Command Palette UI + // Enhanced Command Palette UI if (show_command_palette_) { - ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_Once); - if (Begin("Command Palette", &show_command_palette_, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) { + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_FirstUseEver); + + if (Begin(absl::StrFormat("%s Command Palette", ICON_MD_TERMINAL).c_str(), &show_command_palette_, + ImGuiWindowFlags_NoCollapse)) { + + // Search input with focus management static char query[256] = {}; - InputTextWithHint("##cmd_query", "Type a command or search...", query, IM_ARRAYSIZE(query)); + ImGui::SetNextItemWidth(-100); + if (ImGui::IsWindowAppearing()) { + ImGui::SetKeyboardFocusHere(); + } + + bool input_changed = InputTextWithHint("##cmd_query", + absl::StrFormat("%s Type a command or search...", ICON_MD_SEARCH).c_str(), + query, IM_ARRAYSIZE(query)); + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) { + query[0] = '\0'; + input_changed = true; + } + Separator(); - // List registered shortcuts as commands + + // Filter and categorize commands + std::vector> filtered_commands; for (const auto &entry : context_.shortcut_manager.GetShortcuts()) { const auto &name = entry.first; const auto &shortcut = entry.second; - if (query[0] != '\0' && name.find(query) == std::string::npos) continue; - if (Selectable(name.c_str())) { - if (shortcut.callback) shortcut.callback(); - show_command_palette_ = false; + + if (query[0] == '\0' || name.find(query) != std::string::npos) { + std::string shortcut_text = shortcut.keys.empty() ? "" : + absl::StrFormat("(%s)", PrintShortcut(shortcut.keys).c_str()); + filtered_commands.emplace_back(name, shortcut_text); } } + + // Display results in a table for better organization + if (ImGui::BeginTable("CommandPaletteTable", 2, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingStretchProp, + ImVec2(0, -30))) { // Reserve space for status bar + + ImGui::TableSetupColumn("Command", ImGuiTableColumnFlags_WidthStretch, 0.7f); + ImGui::TableSetupColumn("Shortcut", ImGuiTableColumnFlags_WidthStretch, 0.3f); + ImGui::TableHeadersRow(); + + for (size_t i = 0; i < filtered_commands.size(); ++i) { + const auto& [command_name, shortcut_text] = filtered_commands[i]; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::PushID(static_cast(i)); + if (Selectable(command_name.c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) { + // Execute the command + const auto& shortcuts = context_.shortcut_manager.GetShortcuts(); + auto it = shortcuts.find(command_name); + if (it != shortcuts.end() && it->second.callback) { + it->second.callback(); + show_command_palette_ = false; + } + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::TextDisabled("%s", shortcut_text.c_str()); + } + + ImGui::EndTable(); + } + + // Status bar + ImGui::Separator(); + ImGui::Text("%s %zu commands found", ICON_MD_INFO, filtered_commands.size()); + ImGui::SameLine(); + ImGui::TextDisabled("| Press Enter to execute selected command"); } End(); } - // Global Search UI (labels and recent files for now) + // Enhanced Global Search UI if (show_global_search_) { - ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_Once); - if (Begin("Global Search", &show_global_search_, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) { + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); + + if (Begin(absl::StrFormat("%s Global Search", ICON_MD_MANAGE_SEARCH).c_str(), &show_global_search_, + ImGuiWindowFlags_NoCollapse)) { + + // Enhanced search input with focus management static char query[256] = {}; - InputTextWithHint("##global_query", ICON_MD_SEARCH " Search labels, files...", query, IM_ARRAYSIZE(query)); + ImGui::SetNextItemWidth(-100); + if (ImGui::IsWindowAppearing()) { + ImGui::SetKeyboardFocusHere(); + } + + bool input_changed = InputTextWithHint("##global_query", + absl::StrFormat("%s Search everything...", ICON_MD_SEARCH).c_str(), + query, IM_ARRAYSIZE(query)); + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) { + query[0] = '\0'; + input_changed = true; + } + Separator(); - if (current_rom_ && current_rom_->resource_label()) { - Text(ICON_MD_LABEL " Labels"); - Indent(); - auto &labels = current_rom_->resource_label()->labels_; - for (const auto &type_pair : labels) { - for (const auto &kv : type_pair.second) { - if (query[0] != '\0' && kv.first.find(query) == std::string::npos && kv.second.find(query) == std::string::npos) continue; - if (Selectable((type_pair.first + ": " + kv.first + " -> " + kv.second).c_str())) { - // Future: navigate to related editor/location + + // Tabbed search results for better organization + if (ImGui::BeginTabBar("SearchResultTabs")) { + + // Recent Files Tab + if (ImGui::BeginTabItem(absl::StrFormat("%s Recent Files", ICON_MD_HISTORY).c_str())) { + static core::RecentFilesManager manager("recent_files.txt"); + manager.Load(); + auto recent_files = manager.GetRecentFiles(); + + if (ImGui::BeginTable("RecentFilesTable", 3, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingStretchProp)) { + + ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableHeadersRow(); + + for (const auto &file : recent_files) { + if (query[0] != '\0' && file.find(query) == std::string::npos) continue; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", core::GetFileName(file).c_str()); + + ImGui::TableNextColumn(); + std::string ext = core::GetFileExtension(file); + if (ext == "sfc" || ext == "smc") { + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s ROM", ICON_MD_VIDEOGAME_ASSET); + } else if (ext == "yaze") { + ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.8f, 1.0f), "%s Project", ICON_MD_FOLDER); + } else { + ImGui::Text("%s File", ICON_MD_DESCRIPTION); + } + + ImGui::TableNextColumn(); + ImGui::PushID(file.c_str()); + if (ImGui::Button("Open")) { + status_ = OpenRomOrProject(file); + show_global_search_ = false; + } + ImGui::PopID(); } + + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } + + // Labels Tab (only if ROM is loaded) + if (current_rom_ && current_rom_->resource_label()) { + if (ImGui::BeginTabItem(absl::StrFormat("%s Labels", ICON_MD_LABEL).c_str())) { + auto &labels = current_rom_->resource_label()->labels_; + + if (ImGui::BeginTable("LabelsTable", 3, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingStretchProp)) { + + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableHeadersRow(); + + for (const auto &type_pair : labels) { + for (const auto &kv : type_pair.second) { + if (query[0] != '\0' && + kv.first.find(query) == std::string::npos && + kv.second.find(query) == std::string::npos) continue; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", type_pair.first.c_str()); + + ImGui::TableNextColumn(); + if (Selectable(kv.first.c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) { + // Future: navigate to related editor/location + } + + ImGui::TableNextColumn(); + ImGui::TextDisabled("%s", kv.second.c_str()); + } + } + + ImGui::EndTable(); + } + ImGui::EndTabItem(); } } - Unindent(); - } - Text(ICON_MD_HISTORY " Recent Files"); - Indent(); - static core::RecentFilesManager manager("recent_files.txt"); - manager.Load(); - for (const auto &file : manager.GetRecentFiles()) { - if (query[0] != '\0' && file.find(query) == std::string::npos) continue; - if (Selectable(file.c_str())) { - status_ = OpenRomOrProject(file); - show_global_search_ = false; + + // Sessions Tab + if (GetActiveSessionCount() > 1) { + if (ImGui::BeginTabItem(absl::StrFormat("%s Sessions", ICON_MD_TAB).c_str())) { + ImGui::Text("Search and switch between active sessions:"); + + for (size_t i = 0; i < sessions_.size(); ++i) { + auto& session = sessions_[i]; + if (session.custom_name == "[CLOSED SESSION]") continue; + + std::string session_info = session.GetDisplayName(); + if (query[0] != '\0' && session_info.find(query) == std::string::npos) continue; + + bool is_current = (&session.rom == current_rom_); + if (is_current) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f)); + } + + if (Selectable(absl::StrFormat("%s %s %s", + ICON_MD_TAB, + session_info.c_str(), + is_current ? "(Current)" : "").c_str())) { + if (!is_current) { + SwitchToSession(i); + show_global_search_ = false; + } + } + + if (is_current) { + ImGui::PopStyleColor(); + } + } + ImGui::EndTabItem(); + } } + + ImGui::EndTabBar(); } - Unindent(); + + // Status bar + ImGui::Separator(); + ImGui::Text("%s Global search across all YAZE data", ICON_MD_INFO); } End(); } @@ -2450,18 +2629,22 @@ void EditorManager::DrawSessionRenameDialog() { } void EditorManager::DrawWelcomeScreen() { - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(750, 550), ImGuiCond_Always); + // Make welcome screen moveable but with a good default position + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); // Create a subtle animated background effect static float animation_time = 0.0f; animation_time += ImGui::GetIO().DeltaTime; - ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoBackground; + // Make it moveable and resizable but keep the custom styling + ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground; - if (ImGui::Begin("Welcome to Yaze", nullptr, flags)) { + // Use a unique window name to prevent stacking + static int welcome_window_id = 0; + std::string window_name = absl::StrFormat("Welcome to YAZE##welcome_%d", welcome_window_id); + + if (ImGui::Begin(window_name.c_str(), &show_welcome_screen_, flags)) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_size = ImGui::GetWindowSize(); @@ -2489,18 +2672,39 @@ void EditorManager::DrawWelcomeScreen() { ImVec2(window_pos.x + window_size.x, window_pos.y + window_size.y), themed_border, 12.0f, 0, border_thickness); - // 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; - ImVec2 particle_pos = ImVec2( - window_pos.x + 50 + (i * 80) + offset_x, - window_pos.y + 100 + offset_y); + // Enhanced floating particles effect with multiple layers + for (int layer = 0; layer < 2; ++layer) { + int particle_count = layer == 0 ? 12 : 8; + float layer_speed = layer == 0 ? 1.0f : 0.6f; + float layer_alpha = layer == 0 ? 0.4f : 0.2f; - float alpha = 0.3f + 0.2f * sinf(animation_time * 1.5f + i); - 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); + for (int i = 0; i < particle_count; ++i) { + float time_offset = layer * 3.14159f + i * 0.8f; + float offset_x = sinf(animation_time * 0.5f * layer_speed + time_offset) * (30.0f + layer * 10.0f); + float offset_y = cosf(animation_time * 0.3f * layer_speed + time_offset) * (20.0f + layer * 8.0f); + + // Distribute particles across the window + float base_x = window_pos.x + (window_size.x / particle_count) * i + 40; + float base_y = window_pos.y + 80 + layer * 30; + + ImVec2 particle_pos = ImVec2(base_x + offset_x, base_y + offset_y); + + // Pulsing alpha effect + float alpha = layer_alpha + 0.3f * sinf(animation_time * 1.5f + time_offset); + ImU32 particle_color = ImGui::ColorConvertFloat4ToU32(ImVec4( + accent_color.red, accent_color.green, accent_color.blue, alpha)); + + // Varying particle sizes + float radius = 1.5f + layer * 0.5f + sinf(animation_time * 2.0f + time_offset) * 0.8f; + draw_list->AddCircleFilled(particle_pos, radius, particle_color); + + // Add subtle glow effect for layer 0 + if (layer == 0) { + ImU32 glow_color = ImGui::ColorConvertFloat4ToU32(ImVec4( + accent_color.red, accent_color.green, accent_color.blue, alpha * 0.3f)); + draw_list->AddCircleFilled(particle_pos, radius + 1.0f, glow_color); + } + } } // Header with themed styling @@ -2520,7 +2724,7 @@ void EditorManager::DrawWelcomeScreen() { ImGui::Spacing(); // Themed decorative line with glow effect (positioned closer to header) - float line_y = window_pos.y + 65; // Move even higher for tighter header integration + float line_y = window_pos.y + 35; // Move even higher for tighter header integration float line_margin = 80; // Maintain good horizontal balance ImVec2 line_start = ImVec2(window_pos.x + line_margin, line_y); ImVec2 line_end = ImVec2(window_pos.x + window_size.x - line_margin, line_y); @@ -2698,15 +2902,32 @@ void EditorManager::DrawWelcomeScreen() { ImGui::Spacing(); ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), ICON_MD_TIPS_AND_UPDATES " Tip: Drag and drop ROM files onto the window"); - // Add close button in the bottom right corner + // Add settings and customization section (accessible before ROM loading) ImGui::Spacing(); ImGui::Separator(); - float close_button_width = 100.0f; - float offset = ImGui::GetContentRegionAvail().x - close_button_width; - if (offset > 0) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset); + ImGui::Text("%s Customization & Settings", ICON_MD_SETTINGS); - if (ImGui::Button(absl::StrFormat("%s Close Welcome", ICON_MD_CLOSE).c_str(), ImVec2(close_button_width, 0))) { - show_welcome_screen_ = false; + // Theme and display settings buttons (always accessible) + static bool show_welcome_theme_selector = false; + if (ImGui::Button(absl::StrFormat("%s Theme Settings", ICON_MD_PALETTE).c_str(), ImVec2(180, 35))) { + show_welcome_theme_selector = true; + } + + // Show theme selector if requested + if (show_welcome_theme_selector) { + auto& theme_manager = gui::ThemeManager::Get(); + theme_manager.ShowThemeSelector(&show_welcome_theme_selector); + } + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Display Settings", ICON_MD_DISPLAY_SETTINGS).c_str(), ImVec2(180, 35))) { + // Open display settings popup (make it accessible without ROM) + popup_manager_->Show("Display Settings"); + } + + ImGui::SameLine(); + if (ImGui::Button(absl::StrFormat("%s Command Palette", ICON_MD_TERMINAL).c_str(), ImVec2(180, 35))) { + show_command_palette_ = true; } } ImGui::End(); diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index 23a50a80..394b14da 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -7,6 +7,7 @@ #include #include "absl/status/status.h" +#include "app/core/features.h" #include "app/core/project.h" #include "app/editor/code/assembly_editor.h" #include "app/editor/code/memory_editor.h" @@ -19,10 +20,9 @@ #include "app/editor/overworld/overworld_editor.h" #include "app/editor/sprite/sprite_editor.h" #include "app/editor/system/popup_manager.h" -#include "app/editor/system/toast_manager.h" #include "app/editor/system/settings_editor.h" +#include "app/editor/system/toast_manager.h" #include "app/emu/emulator.h" -#include "app/core/features.h" #include "app/rom.h" #include "yaze_config.h" @@ -100,14 +100,20 @@ class EditorManager { absl::Status SetCurrentRom(Rom* rom); auto GetCurrentRom() -> Rom* { return current_rom_; } auto GetCurrentEditorSet() -> EditorSet* { return current_editor_set_; } - + // Get current session's feature flags (falls back to global if no session) core::FeatureFlags::Flags* GetCurrentFeatureFlags() { size_t current_index = GetCurrentSessionIndex(); if (current_index < sessions_.size()) { return &sessions_[current_index].feature_flags; } - return &core::FeatureFlags::get(); // Fallback to global + return &core::FeatureFlags::get(); // Fallback to global + } + + void SetFontGlobalScale(float scale) { + font_global_scale_ = scale; + ImGui::GetIO().FontGlobalScale = scale; + SaveUserSettings(); } private: @@ -119,16 +125,17 @@ class EditorManager { absl::Status SaveRom(); absl::Status SaveRomAs(const std::string& filename); absl::Status OpenRomOrProject(const std::string& filename); - + // Enhanced project management - absl::Status CreateNewProject(const std::string& template_name = "Basic ROM Hack"); + absl::Status CreateNewProject( + const std::string& template_name = "Basic ROM Hack"); absl::Status OpenProject(); absl::Status SaveProject(); absl::Status SaveProjectAs(); absl::Status ImportProject(const std::string& project_path); absl::Status RepairCurrentProject(); void ShowProjectHelp(); - + // Testing system void InitializeTestSuites(); @@ -160,7 +167,7 @@ class EditorManager { bool show_welcome_screen_ = false; size_t session_to_rename_ = 0; char session_rename_buffer_[256] = {}; - + // Testing interface bool show_test_dashboard_ = false; @@ -178,18 +185,17 @@ class EditorManager { struct RomSession { Rom rom; EditorSet editors; - std::string custom_name; // User-defined session name - std::string filepath; // ROM filepath for duplicate detection - core::FeatureFlags::Flags feature_flags; // Per-session feature flags + std::string custom_name; // User-defined session name + std::string filepath; // ROM filepath for duplicate detection + core::FeatureFlags::Flags feature_flags; // Per-session feature flags RomSession() = default; - explicit RomSession(Rom&& r) - : rom(std::move(r)), editors(&rom) { + explicit RomSession(Rom&& r) : rom(std::move(r)), editors(&rom) { filepath = rom.filename(); // Initialize with default feature flags feature_flags = core::FeatureFlags::Flags{}; } - + // Get display name (custom name or ROM title) std::string GetDisplayName() const { if (!custom_name.empty()) { @@ -213,10 +219,11 @@ class EditorManager { // Settings helpers void LoadUserSettings(); void SaveUserSettings(); + void RefreshWorkspacePresets(); void SaveWorkspacePreset(const std::string& name); void LoadWorkspacePreset(const std::string& name); - + // Workspace management void CreateNewSession(); void DuplicateCurrentSession(); @@ -226,9 +233,10 @@ class EditorManager { size_t GetCurrentSessionIndex() const; size_t GetActiveSessionCount() const; void ResetWorkspaceLayout(); - + // Multi-session editor management - std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const; + std::string GenerateUniqueEditorTitle(EditorType type, + size_t session_index) const; void SaveWorkspaceLayout(); void LoadWorkspaceLayout(); void ShowAllWindows(); @@ -239,12 +247,12 @@ class EditorManager { void LoadDeveloperLayout(); void LoadDesignerLayout(); void LoadModderLayout(); - + // Session management helpers bool HasDuplicateSession(const std::string& filepath); void RenameSession(size_t index, const std::string& new_name); std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index); - + // UI drawing helpers void DrawSessionSwitcher(); void DrawSessionManager(); diff --git a/src/app/editor/system/popup_manager.cc b/src/app/editor/system/popup_manager.cc index 01e7ad3c..b8562890 100644 --- a/src/app/editor/system/popup_manager.cc +++ b/src/app/editor/system/popup_manager.cc @@ -38,6 +38,9 @@ void PopupManager::Initialize() { popups_["Workspace Help"] = {"Workspace Help", false, [this]() { DrawWorkspaceHelpPopup(); }}; popups_["Session Limit Warning"] = {"Session Limit Warning", false, [this]() { DrawSessionLimitWarningPopup(); }}; popups_["Layout Reset Confirm"] = {"Reset Layout Confirmation", false, [this]() { DrawLayoutResetConfirmPopup(); }}; + + // Settings popups (accessible without ROM) + popups_["Display Settings"] = {"Display Settings", false, [this]() { DrawDisplaySettingsPopup(); }}; } void PopupManager::DrawPopups() { @@ -48,7 +51,14 @@ void PopupManager::DrawPopups() { for (auto& [name, params] : popups_) { if (params.is_visible) { OpenPopup(name.c_str()); - if (BeginPopupModal(name.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + + // Special handling for Display Settings popup to make it resizable + ImGuiWindowFlags popup_flags = ImGuiWindowFlags_AlwaysAutoResize; + if (name == "Display Settings") { + popup_flags = ImGuiWindowFlags_None; // Allow resizing for display settings + } + + if (BeginPopupModal(name.c_str(), nullptr, popup_flags)) { params.draw_function(); EndPopup(); } @@ -491,5 +501,46 @@ void PopupManager::DrawLayoutResetConfirmPopup() { } } +void PopupManager::DrawDisplaySettingsPopup() { + // Set a comfortable default size with natural constraints + SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver); + SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(FLT_MAX, FLT_MAX)); + + Text("%s Display & Theme Settings", ICON_MD_DISPLAY_SETTINGS); + TextWrapped("Customize your YAZE experience - accessible anytime!"); + Separator(); + + // Create a child window for scrollable content to avoid table conflicts + // Use remaining space minus the close button area + float available_height = GetContentRegionAvail().y - 60; // Reserve space for close button + if (BeginChild("DisplaySettingsContent", ImVec2(0, available_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + // Use the popup-safe version to avoid table conflicts + gui::DrawDisplaySettingsForPopup(); + + Separator(); + gui::TextWithSeparators("Font Manager"); + gui::DrawFontManager(); + + // Global font scale (moved from the old display settings window) + ImGuiIO &io = GetIO(); + Separator(); + Text("Global Font Scale"); + static float font_global_scale = io.FontGlobalScale; + if (SliderFloat("##global_scale", &font_global_scale, 0.5f, 1.8f, "%.2f")) { + if (editor_manager_) { + editor_manager_->SetFontGlobalScale(font_global_scale); + } else { + io.FontGlobalScale = font_global_scale; + } + } + } + EndChild(); + + Separator(); + if (Button("Close", gui::kDefaultModalSize)) { + Hide("Display Settings"); + } +} + } // namespace editor } // namespace yaze diff --git a/src/app/editor/system/popup_manager.h b/src/app/editor/system/popup_manager.h index 15f2d3c8..5ea41b03 100644 --- a/src/app/editor/system/popup_manager.h +++ b/src/app/editor/system/popup_manager.h @@ -86,6 +86,9 @@ class PopupManager { void DrawWorkspaceHelpPopup(); void DrawSessionLimitWarningPopup(); void DrawLayoutResetConfirmPopup(); + + // Settings popups (accessible without ROM) + void DrawDisplaySettingsPopup(); EditorManager* editor_manager_; std::unordered_map popups_; diff --git a/src/app/gui/style.cc b/src/app/gui/style.cc index 8126a235..5af44c4e 100644 --- a/src/app/gui/style.cc +++ b/src/app/gui/style.cc @@ -925,6 +925,444 @@ void DrawDisplaySettings(ImGuiStyle *ref) { ImGui::PopItemWidth(); } +void DrawDisplaySettingsForPopup(ImGuiStyle *ref) { + // Popup-safe version of DrawDisplaySettings without problematic tables + ImGuiStyle &style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) ref_saved_style = style; + init = false; + if (ref == NULL) ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + // Enhanced theme management section (simplified for popup) + if (ImGui::CollapsingHeader("Theme Management", ImGuiTreeNodeFlags_DefaultOpen)) { + auto& theme_manager = ThemeManager::Get(); + + ImGui::Text("%s Current Theme:", ICON_MD_PALETTE); + ImGui::SameLine(); + + std::string current_theme_name = theme_manager.GetCurrentThemeName(); + bool is_classic_active = (current_theme_name == "Classic YAZE"); + + // Current theme display with color preview + if (is_classic_active) { + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", current_theme_name.c_str()); + } else { + ImGui::Text("%s", current_theme_name.c_str()); + } + + // Theme color preview + auto current_theme = theme_manager.GetCurrentTheme(); + ImGui::SameLine(); + ImGui::ColorButton("##primary_preview", gui::ConvertColorToImVec4(current_theme.primary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::SameLine(); + ImGui::ColorButton("##secondary_preview", gui::ConvertColorToImVec4(current_theme.secondary), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + ImGui::SameLine(); + ImGui::ColorButton("##accent_preview", gui::ConvertColorToImVec4(current_theme.accent), + ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); + + ImGui::Spacing(); + + // Simplified theme selection (no table to avoid popup conflicts) + if (is_classic_active) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f)); + } + + if (ImGui::Button("Classic YAZE")) { + theme_manager.ApplyClassicYazeTheme(); + ref_saved_style = style; + } + + if (is_classic_active) { + ImGui::PopStyleColor(); + } + + ImGui::SameLine(); + if (ImGui::Button("Reset ColorsYaze")) { + gui::ColorsYaze(); + ref_saved_style = style; + } + + // File themes dropdown + auto available_themes = theme_manager.GetAvailableThemes(); + const char* current_file_theme = ""; + + // Find current file theme for display + for (const auto& theme_name : available_themes) { + if (theme_name == current_theme_name) { + current_file_theme = theme_name.c_str(); + break; + } + } + + ImGui::Text("File Themes:"); + ImGui::SetNextItemWidth(-1); + if (ImGui::BeginCombo("##FileThemes", current_file_theme)) { + for (const auto& theme_name : available_themes) { + bool is_selected = (theme_name == current_theme_name); + if (ImGui::Selectable(theme_name.c_str(), is_selected)) { + theme_manager.LoadTheme(theme_name); + ref_saved_style = style; + } + } + ImGui::EndCombo(); + } + + if (ImGui::Button("Refresh Themes")) { + theme_manager.RefreshAvailableThemes(); + } + ImGui::SameLine(); + if (ImGui::Button("Open Theme Editor")) { + static bool show_theme_editor = true; + theme_manager.ShowSimpleThemeEditor(&show_theme_editor); + } + } + + ImGui::Separator(); + + // Background effects settings + auto& bg_renderer = gui::BackgroundRenderer::Get(); + bg_renderer.DrawSettingsUI(); + + ImGui::Separator(); + + if (ImGui::ShowStyleSelector("Colors##Selector")) ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Quick style controls before the tabbed section + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; + + // Border checkboxes (simplified layout) + bool window_border = (style.WindowBorderSize > 0.0f); + if (ImGui::Checkbox("WindowBorder", &window_border)) { + style.WindowBorderSize = window_border ? 1.0f : 0.0f; + } + ImGui::SameLine(); + + bool frame_border = (style.FrameBorderSize > 0.0f); + if (ImGui::Checkbox("FrameBorder", &frame_border)) { + style.FrameBorderSize = frame_border ? 1.0f : 0.0f; + } + ImGui::SameLine(); + + bool popup_border = (style.PopupBorderSize > 0.0f); + if (ImGui::Checkbox("PopupBorder", &popup_border)) { + style.PopupBorderSize = popup_border ? 1.0f : 0.0f; + } + + // Save/Revert buttons + if (ImGui::Button("Save Ref")) *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) style = *ref; + + ImGui::Separator(); + + // Add the comprehensive tabbed settings from the original DrawDisplaySettings + if (ImGui::BeginTabBar("DisplaySettingsTabs", ImGuiTabBarFlags_None)) { + + if (ImGui::BeginTabItem("Sizes")) { + ImGui::SeparatorText("Main"); + ImGui::SliderFloat2("WindowPadding", (float *)&style.WindowPadding, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float *)&style.FramePadding, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float *)&style.ItemSpacing, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float *)&style.ItemInnerSpacing, + 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", + (float *)&style.TouchExtraPadding, 0.0f, 10.0f, + "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, + "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, + "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, + "%.0f"); + + ImGui::SeparatorText("Borders"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, + 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, + "%.0f"); + ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, + 2.0f, "%.0f"); + + ImGui::SeparatorText("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, + 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, + "%.0f"); + ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, + "%.0f"); + + ImGui::SeparatorText("Tables"); + ImGui::SliderFloat2("CellPadding", (float *)&style.CellPadding, 0.0f, + 20.0f, "%.0f"); + ImGui::SliderAngle("TableAngledHeadersAngle", + &style.TableAngledHeadersAngle, -50.0f, +50.0f); + + ImGui::SeparatorText("Widgets"); + ImGui::SliderFloat2("WindowTitleAlign", (float *)&style.WindowTitleAlign, + 0.0f, 1.0f, "%.2f"); + ImGui::Combo("ColorButtonPosition", (int *)&style.ColorButtonPosition, + "Left\0Right\0"); + ImGui::SliderFloat2("ButtonTextAlign", (float *)&style.ButtonTextAlign, + 0.0f, 1.0f, "%.2f"); + ImGui::SameLine(); + + ImGui::SliderFloat2("SelectableTextAlign", + (float *)&style.SelectableTextAlign, 0.0f, 1.0f, + "%.2f"); + ImGui::SameLine(); + + ImGui::SliderFloat("SeparatorTextBorderSize", + &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("SeparatorTextAlign", + (float *)&style.SeparatorTextAlign, 0.0f, 1.0f, + "%.2f"); + ImGui::SliderFloat2("SeparatorTextPadding", + (float *)&style.SeparatorTextPadding, 0.0f, 40.0f, + "%.0f"); + ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, + 12.0f, "%.0f"); + + ImGui::SeparatorText("Tooltips"); + for (int n = 0; n < 2; n++) + if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" + : "HoverFlagsForTooltipNav")) { + ImGuiHoveredFlags *p = (n == 0) ? &style.HoverFlagsForTooltipMouse + : &style.HoverFlagsForTooltipNav; + ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, + ImGuiHoveredFlags_DelayNone); + ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayShort", p, + ImGuiHoveredFlags_DelayShort); + ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNormal", p, + ImGuiHoveredFlags_DelayNormal); + ImGui::CheckboxFlags("ImGuiHoveredFlags_Stationary", p, + ImGuiHoveredFlags_Stationary); + ImGui::CheckboxFlags("ImGuiHoveredFlags_NoSharedDelay", p, + ImGuiHoveredFlags_NoSharedDelay); + ImGui::TreePop(); + } + + ImGui::SeparatorText("Misc"); + ImGui::SliderFloat2("DisplaySafeAreaPadding", + (float *)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, + "%.0f"); + ImGui::SameLine(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Colors")) { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export")) { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const ImVec4 &col = style.Colors[i]; + const char *name = ImGui::GetStyleColorName(i); + if (!output_only_modified || + memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText( + "colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, " + "%.2ff);" IM_NEWLINE, + name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(120); + ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + ImGui::SameLine(); + ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + + static ImGuiColorEditFlags alpha_flags = 0; + if (ImGui::RadioButton("Opaque", + alpha_flags == ImGuiColorEditFlags_None)) { + alpha_flags = ImGuiColorEditFlags_None; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Alpha", + alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { + alpha_flags = ImGuiColorEditFlags_AlphaPreview; + } + ImGui::SameLine(); + if (ImGui::RadioButton( + "Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { + alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; + } + ImGui::SameLine(); + + ImGui::SetNextWindowSizeConstraints( + ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), + ImVec2(FLT_MAX, FLT_MAX)); + ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Border, + ImGuiWindowFlags_AlwaysVerticalScrollbar | + ImGuiWindowFlags_AlwaysHorizontalScrollbar | + ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char *name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float *)&style.Colors[i], + ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { + // Tips: in a real user application, you may want to merge and use + // an icon font into the main font, so instead of "Save"/"Revert" + // you'd use icons! Read the FAQ and docs/FONTS.md about using icon + // fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + if (ImGui::Button("Save")) { + ref->Colors[i] = style.Colors[i]; + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + if (ImGui::Button("Revert")) { + style.Colors[i] = ref->Colors[i]; + } + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Fonts")) { + ImGuiIO &io = ImGui::GetIO(); + ImFontAtlas *atlas = io.Fonts; + ImGui::ShowFontAtlas(atlas); + + // Post-baking font scaling. Note that this is NOT the nice way of + // scaling fonts, read below. (we enforce hard clamping manually as by + // default DragFloat/SliderFloat allows CTRL+Click text to get out of + // bounds). + const float MIN_SCALE = 0.3f; + const float MAX_SCALE = 2.0f; + + static float window_scale = 1.0f; + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); + if (ImGui::DragFloat( + "window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, + "%.2f", + ImGuiSliderFlags_AlwaysClamp)) // Scale only this window + ImGui::SetWindowFontScale(window_scale); + ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, + MAX_SCALE, "%.2f", + ImGuiSliderFlags_AlwaysClamp); // Scale everything + ImGui::PopItemWidth(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Rendering")) { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); + ImGui::SameLine(); + + ImGui::Checkbox("Anti-aliased lines use texture", + &style.AntiAliasedLinesUseTex); + ImGui::SameLine(); + + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragFloat("Curve Tessellation Tolerance", + &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, + "%.2f"); + if (style.CurveTessellationTol < 0.10f) + style.CurveTessellationTol = 0.10f; + + // When editing the "Circle Segment Max Error" value, draw a preview of + // its effect on auto-tessellated circles. + ImGui::DragFloat("Circle Tessellation Max Error", + &style.CircleTessellationMaxError, 0.005f, 0.10f, 5.0f, + "%.2f", ImGuiSliderFlags_AlwaysClamp); + const bool show_samples = ImGui::IsItemActive(); + if (show_samples) ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); + if (show_samples && ImGui::BeginTooltip()) { + ImGui::TextUnformatted("(R = radius, N = number of segments)"); + ImGui::Spacing(); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; + for (int n = 0; n < 8; n++) { + const float RAD_MIN = 5.0f; + const float RAD_MAX = 70.0f; + const float rad = + RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f); + + ImGui::BeginGroup(); + + ImGui::Text("R: %.f\nN: %d", rad, + draw_list->_CalcCircleAutoSegmentCount(rad)); + + const float canvas_width = std::max(min_widget_width, rad * 2.0f); + const float offset_x = floorf(canvas_width * 0.5f); + const float offset_y = floorf(RAD_MAX); + + const ImVec2 p1 = ImGui::GetCursorScreenPos(); + draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, + ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + + ImGui::EndGroup(); + ImGui::SameLine(); + } + ImGui::EndTooltip(); + } + ImGui::SameLine(); + + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, + "%.2f"); // Not exposing zero here so user doesn't + // "lose" the UI (zero alpha clips all + // widgets). But application code could have a + // toggle to switch between zero and non-zero. + ImGui::DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f, + 1.0f, "%.2f"); + ImGui::SameLine(); + + ImGui::PopItemWidth(); + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::PopItemWidth(); +} + void TextWithSeparators(const absl::string_view &text) { ImGui::Separator(); ImGui::Text("%s", text.data()); diff --git a/src/app/gui/style.h b/src/app/gui/style.h index 00f75ea7..b8a4b62f 100644 --- a/src/app/gui/style.h +++ b/src/app/gui/style.h @@ -73,6 +73,7 @@ void SetupCanvasTableColumn(const char* label, float width_ratio = 0.0f); void BeginCanvasTableCell(ImVec2 min_size = ImVec2(0, 0)); void DrawDisplaySettings(ImGuiStyle *ref = nullptr); +void DrawDisplaySettingsForPopup(ImGuiStyle *ref = nullptr); // Popup-safe version void TextWithSeparators(const absl::string_view &text); diff --git a/src/app/gui/theme_manager.cc b/src/app/gui/theme_manager.cc index c632611d..25195aee 100644 --- a/src/app/gui/theme_manager.cc +++ b/src/app/gui/theme_manager.cc @@ -356,6 +356,29 @@ 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)) { + + // Add subtle particle effects to theme selector + static float theme_animation_time = 0.0f; + theme_animation_time += ImGui::GetIO().DeltaTime; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 window_pos = ImGui::GetWindowPos(); + ImVec2 window_size = ImGui::GetWindowSize(); + + // Subtle corner particles for theme selector + for (int i = 0; i < 4; ++i) { + float corner_offset = i * 1.57f; // 90 degrees apart + float x = window_pos.x + window_size.x * 0.5f + cosf(theme_animation_time * 0.8f + corner_offset) * (window_size.x * 0.4f); + float y = window_pos.y + window_size.y * 0.5f + sinf(theme_animation_time * 0.8f + corner_offset) * (window_size.y * 0.4f); + + float alpha = 0.1f + 0.1f * sinf(theme_animation_time * 1.2f + corner_offset); + auto current_theme = GetCurrentTheme(); + ImU32 particle_color = ImGui::ColorConvertFloat4ToU32(ImVec4( + current_theme.accent.red, current_theme.accent.green, current_theme.accent.blue, alpha)); + + draw_list->AddCircleFilled(ImVec2(x, y), 3.0f, particle_color); + } + ImGui::Text("%s Available Themes", ICON_MD_COLOR_LENS); ImGui::Separator(); @@ -901,6 +924,35 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) { if (ImGui::Begin(absl::StrFormat("%s Theme Editor", ICON_MD_PALETTE).c_str(), p_open, ImGuiWindowFlags_MenuBar)) { + // Add gentle particle effects to theme editor background + static float editor_animation_time = 0.0f; + editor_animation_time += ImGui::GetIO().DeltaTime; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 window_pos = ImGui::GetWindowPos(); + ImVec2 window_size = ImGui::GetWindowSize(); + + // Floating color orbs representing different color categories + auto current_theme = GetCurrentTheme(); + std::vector theme_colors = { + current_theme.primary, current_theme.secondary, current_theme.accent, + current_theme.success, current_theme.warning, current_theme.error + }; + + for (size_t i = 0; i < theme_colors.size(); ++i) { + float time_offset = i * 1.0f; + float orbit_radius = 60.0f + i * 8.0f; + float x = window_pos.x + window_size.x * 0.8f + cosf(editor_animation_time * 0.3f + time_offset) * orbit_radius; + float y = window_pos.y + window_size.y * 0.3f + sinf(editor_animation_time * 0.3f + time_offset) * orbit_radius; + + float alpha = 0.15f + 0.1f * sinf(editor_animation_time * 1.5f + time_offset); + ImU32 orb_color = ImGui::ColorConvertFloat4ToU32(ImVec4( + theme_colors[i].red, theme_colors[i].green, theme_colors[i].blue, alpha)); + + float radius = 4.0f + sinf(editor_animation_time * 2.0f + time_offset) * 1.0f; + draw_list->AddCircleFilled(ImVec2(x, y), radius, orb_color); + } + // Menu bar for theme operations if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) {