yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
theme_manager.cc
Go to the documentation of this file.
1#include "theme_manager.h"
2
3#include <algorithm>
4#include <cctype>
5#include <fstream>
6#include <set>
7#include <sstream>
8#include <cstring>
9
10#include "absl/strings/str_format.h"
11#include "absl/strings/str_split.h"
12#include "util/file_util.h"
13#include "util/platform_paths.h"
14#include "app/gui/core/icons.h"
15#include "app/gui/core/style.h" // For ColorsYaze function
16#include "imgui/imgui.h"
17#include "util/log.h"
18#include "nlohmann/json.hpp"
19
20namespace yaze {
21namespace gui {
22
23// Helper function to create Color from RGB values
24Color RGB(float r, float g, float b, float a = 1.0f) {
25 return {r / 255.0f, g / 255.0f, b / 255.0f, a};
26}
27
28Color RGBA(int r, int g, int b, int a = 255) {
29 return {r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f};
30}
31
32// Theme Implementation
34 ImGuiStyle* style = &ImGui::GetStyle();
35 ImVec4* colors = style->Colors;
36
37 // Apply colors
38 colors[ImGuiCol_Text] = ConvertColorToImVec4(text_primary);
39 colors[ImGuiCol_TextDisabled] = ConvertColorToImVec4(text_disabled);
40 colors[ImGuiCol_WindowBg] = ConvertColorToImVec4(window_bg);
41 colors[ImGuiCol_ChildBg] = ConvertColorToImVec4(child_bg);
42 colors[ImGuiCol_PopupBg] = ConvertColorToImVec4(popup_bg);
43 colors[ImGuiCol_Border] = ConvertColorToImVec4(border);
44 colors[ImGuiCol_BorderShadow] = ConvertColorToImVec4(border_shadow);
45 colors[ImGuiCol_FrameBg] = ConvertColorToImVec4(frame_bg);
46 colors[ImGuiCol_FrameBgHovered] = ConvertColorToImVec4(frame_bg_hovered);
47 colors[ImGuiCol_FrameBgActive] = ConvertColorToImVec4(frame_bg_active);
48 colors[ImGuiCol_TitleBg] = ConvertColorToImVec4(title_bg);
49 colors[ImGuiCol_TitleBgActive] = ConvertColorToImVec4(title_bg_active);
50 colors[ImGuiCol_TitleBgCollapsed] = ConvertColorToImVec4(title_bg_collapsed);
51 colors[ImGuiCol_MenuBarBg] = ConvertColorToImVec4(menu_bar_bg);
52 colors[ImGuiCol_ScrollbarBg] = ConvertColorToImVec4(scrollbar_bg);
53 colors[ImGuiCol_ScrollbarGrab] = ConvertColorToImVec4(scrollbar_grab);
54 colors[ImGuiCol_ScrollbarGrabHovered] = ConvertColorToImVec4(scrollbar_grab_hovered);
55 colors[ImGuiCol_ScrollbarGrabActive] = ConvertColorToImVec4(scrollbar_grab_active);
56 colors[ImGuiCol_Button] = ConvertColorToImVec4(button);
57 colors[ImGuiCol_ButtonHovered] = ConvertColorToImVec4(button_hovered);
58 colors[ImGuiCol_ButtonActive] = ConvertColorToImVec4(button_active);
59 colors[ImGuiCol_Header] = ConvertColorToImVec4(header);
60 colors[ImGuiCol_HeaderHovered] = ConvertColorToImVec4(header_hovered);
61 colors[ImGuiCol_HeaderActive] = ConvertColorToImVec4(header_active);
62 colors[ImGuiCol_Separator] = ConvertColorToImVec4(separator);
63 colors[ImGuiCol_SeparatorHovered] = ConvertColorToImVec4(separator_hovered);
64 colors[ImGuiCol_SeparatorActive] = ConvertColorToImVec4(separator_active);
65 colors[ImGuiCol_ResizeGrip] = ConvertColorToImVec4(resize_grip);
66 colors[ImGuiCol_ResizeGripHovered] = ConvertColorToImVec4(resize_grip_hovered);
67 colors[ImGuiCol_ResizeGripActive] = ConvertColorToImVec4(resize_grip_active);
68 colors[ImGuiCol_Tab] = ConvertColorToImVec4(tab);
69 colors[ImGuiCol_TabHovered] = ConvertColorToImVec4(tab_hovered);
70 colors[ImGuiCol_TabSelected] = ConvertColorToImVec4(tab_active);
71 colors[ImGuiCol_TabUnfocused] = ConvertColorToImVec4(tab_unfocused);
72 colors[ImGuiCol_TabUnfocusedActive] = ConvertColorToImVec4(tab_unfocused_active);
73 colors[ImGuiCol_DockingPreview] = ConvertColorToImVec4(docking_preview);
74 colors[ImGuiCol_DockingEmptyBg] = ConvertColorToImVec4(docking_empty_bg);
75
76 // Complete ImGui color support
77 colors[ImGuiCol_CheckMark] = ConvertColorToImVec4(check_mark);
78 colors[ImGuiCol_SliderGrab] = ConvertColorToImVec4(slider_grab);
79 colors[ImGuiCol_SliderGrabActive] = ConvertColorToImVec4(slider_grab_active);
80 colors[ImGuiCol_InputTextCursor] = ConvertColorToImVec4(input_text_cursor);
81 colors[ImGuiCol_NavCursor] = ConvertColorToImVec4(nav_cursor);
82 colors[ImGuiCol_NavWindowingHighlight] = ConvertColorToImVec4(nav_windowing_highlight);
83 colors[ImGuiCol_NavWindowingDimBg] = ConvertColorToImVec4(nav_windowing_dim_bg);
84 colors[ImGuiCol_ModalWindowDimBg] = ConvertColorToImVec4(modal_window_dim_bg);
85 colors[ImGuiCol_TextSelectedBg] = ConvertColorToImVec4(text_selected_bg);
86 colors[ImGuiCol_DragDropTarget] = ConvertColorToImVec4(drag_drop_target);
87 colors[ImGuiCol_TableHeaderBg] = ConvertColorToImVec4(table_header_bg);
88 colors[ImGuiCol_TableBorderStrong] = ConvertColorToImVec4(table_border_strong);
89 colors[ImGuiCol_TableBorderLight] = ConvertColorToImVec4(table_border_light);
90 colors[ImGuiCol_TableRowBg] = ConvertColorToImVec4(table_row_bg);
91 colors[ImGuiCol_TableRowBgAlt] = ConvertColorToImVec4(table_row_bg_alt);
92 colors[ImGuiCol_TextLink] = ConvertColorToImVec4(text_link);
93 colors[ImGuiCol_PlotLines] = ConvertColorToImVec4(plot_lines);
94 colors[ImGuiCol_PlotLinesHovered] = ConvertColorToImVec4(plot_lines_hovered);
95 colors[ImGuiCol_PlotHistogram] = ConvertColorToImVec4(plot_histogram);
96 colors[ImGuiCol_PlotHistogramHovered] = ConvertColorToImVec4(plot_histogram_hovered);
97 colors[ImGuiCol_TreeLines] = ConvertColorToImVec4(tree_lines);
98
99 // Additional ImGui colors for complete coverage
100 colors[ImGuiCol_TabDimmed] = ConvertColorToImVec4(tab_dimmed);
101 colors[ImGuiCol_TabDimmedSelected] = ConvertColorToImVec4(tab_dimmed_selected);
102 colors[ImGuiCol_TabDimmedSelectedOverline] = ConvertColorToImVec4(tab_dimmed_selected_overline);
103 colors[ImGuiCol_TabSelectedOverline] = ConvertColorToImVec4(tab_selected_overline);
104
105 // Apply style parameters
106 style->WindowRounding = window_rounding;
107 style->FrameRounding = frame_rounding;
108 style->ScrollbarRounding = scrollbar_rounding;
109 style->GrabRounding = grab_rounding;
110 style->TabRounding = tab_rounding;
111 style->WindowBorderSize = window_border_size;
112 style->FrameBorderSize = frame_border_size;
113}
114
115
116// ThemeManager Implementation
118 static ThemeManager instance;
119 return instance;
120}
121
123 // Always create fallback theme first
125
126 // Create the Classic YAZE theme during initialization
128
129 // Load all available theme files dynamically
130 auto status = LoadAllAvailableThemes();
131 if (!status.ok()) {
132 LOG_ERROR("Theme Manager", "Failed to load some theme files");
133 }
134
135 // Ensure we have a valid current theme (Classic is already set above)
136 // Only fallback to file themes if Classic creation failed
137 if (current_theme_name_ != "Classic YAZE") {
138 if (themes_.find("YAZE Tre") != themes_.end()) {
139 current_theme_ = themes_["YAZE Tre"];
140 current_theme_name_ = "YAZE Tre";
141 }
142 }
143}
144
146 // Fallback theme that matches the original ColorsYaze() function colors but in theme format
147 EnhancedTheme theme;
148 theme.name = "YAZE Tre";
149 theme.description = "YAZE theme resource edition";
150 theme.author = "YAZE Team";
151
152 // Use the exact original ColorsYaze colors
153 theme.primary = RGBA(92, 115, 92); // allttpLightGreen
154 theme.secondary = RGBA(71, 92, 71); // alttpMidGreen
155 theme.accent = RGBA(89, 119, 89); // TabActive
156 theme.background = RGBA(8, 8, 8); // Very dark gray for better grid visibility
157
158 theme.text_primary = RGBA(230, 230, 230); // 0.90f, 0.90f, 0.90f
159 theme.text_disabled = RGBA(153, 153, 153); // 0.60f, 0.60f, 0.60f
160 theme.window_bg = RGBA(8, 8, 8, 217); // Very dark gray with same alpha
161 theme.child_bg = RGBA(0, 0, 0, 0); // Transparent
162 theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f
163
164 theme.button = RGBA(71, 92, 71); // alttpMidGreen
165 theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen
166 theme.button_active = RGBA(92, 115, 92); // allttpLightGreen
167
168 theme.header = RGBA(46, 66, 46); // alttpDarkGreen
169 theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen
170 theme.header_active = RGBA(71, 92, 71); // alttpMidGreen
171
172 theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen
173 theme.tab = RGBA(46, 66, 46); // alttpDarkGreen
174 theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen
175 theme.tab_active = RGBA(89, 119, 89); // TabActive
176 theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab
177 theme.tab_unfocused_active = RGBA(62, 83, 62); // Darker version of tab_active
178
179 // Complete all remaining ImGui colors from original ColorsYaze() function
180 theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen
181 theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen
182 theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen
183
184 // Initialize missing fields that were added to the struct
185 theme.surface = theme.background;
186 theme.error = RGBA(220, 50, 50);
187 theme.warning = RGBA(255, 200, 50);
188 theme.success = theme.primary;
189 theme.info = RGBA(70, 170, 255);
190 theme.text_secondary = RGBA(200, 200, 200);
191 theme.modal_bg = theme.popup_bg;
192
193 // Borders and separators
194 theme.border = RGBA(92, 115, 92); // allttpLightGreen
195 theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent
196 theme.separator = RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f
197 theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f
198 theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f
199
200 // Scrollbars
201 theme.scrollbar_bg = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
202 theme.scrollbar_grab = RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f
203 theme.scrollbar_grab_hovered = RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f
204 theme.scrollbar_grab_active = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
205
206 // Resize grips (from original - light blue highlights)
207 theme.resize_grip = RGBA(255, 255, 255, 26); // 1.00f, 1.00f, 1.00f, 0.10f
208 theme.resize_grip_hovered = RGBA(199, 209, 255, 153); // 0.78f, 0.82f, 1.00f, 0.60f
209 theme.resize_grip_active = RGBA(199, 209, 255, 230); // 0.78f, 0.82f, 1.00f, 0.90f
210
211 // ENHANCED: Complete ImGui colors with theme-aware smart defaults
212 // Use theme colors instead of hardcoded values for consistency
213 theme.check_mark = RGBA(125, 255, 125, 255); // Bright green checkmark (highly visible!)
214 theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid)
215 theme.slider_grab_active = RGBA(125, 146, 125, 255); // Lighter green when active
216 theme.input_text_cursor = RGBA(255, 255, 255, 255); // White cursor (always visible)
217 theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green for navigation
218 theme.nav_windowing_highlight = RGBA(89, 119, 89, 200); // Accent with high visibility
219 theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay for better contrast
220 theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha for modals
221 theme.text_selected_bg = RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible selection!)
222 theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green for drop zones
223
224 // Table colors (from original)
225 theme.table_header_bg = RGBA(46, 66, 46); // alttpDarkGreen
226 theme.table_border_strong = RGBA(71, 92, 71); // alttpMidGreen
227 theme.table_border_light = RGBA(66, 66, 71); // 0.26f, 0.26f, 0.28f
228 theme.table_row_bg = RGBA(0, 0, 0, 0); // Transparent
229 theme.table_row_bg_alt = RGBA(255, 255, 255, 18); // 1.00f, 1.00f, 1.00f, 0.07f
230
231 // Links and plots - use accent colors intelligently
232 theme.text_link = theme.accent; // Accent for links
233 theme.plot_lines = RGBA(255, 255, 255); // White for plots
234 theme.plot_lines_hovered = RGBA(230, 178, 0); // 0.90f, 0.70f, 0.00f
235 theme.plot_histogram = RGBA(230, 178, 0); // Same as above
236 theme.plot_histogram_hovered = RGBA(255, 153, 0); // 1.00f, 0.60f, 0.00f
237
238 // Docking colors
239 theme.docking_preview = RGBA(92, 115, 92, 180); // Light green with transparency
240 theme.docking_empty_bg = RGBA(46, 66, 46, 255); // Dark green
241
242 // Apply original style settings
243 theme.window_rounding = 0.0f;
244 theme.frame_rounding = 5.0f;
245 theme.scrollbar_rounding = 5.0f;
246 theme.tab_rounding = 0.0f;
247 theme.enable_glow_effects = false;
248
249 themes_["YAZE Tre"] = theme;
250 current_theme_ = theme;
251 current_theme_name_ = "YAZE Tre";
252}
253
254absl::Status ThemeManager::LoadTheme(const std::string& theme_name) {
255 auto it = themes_.find(theme_name);
256 if (it == themes_.end()) {
257 return absl::NotFoundError(absl::StrFormat("Theme '%s' not found", theme_name));
258 }
259
260 current_theme_ = it->second;
261 current_theme_name_ = theme_name;
263
264 return absl::OkStatus();
265}
266
267absl::Status ThemeManager::LoadThemeFromFile(const std::string& filepath) {
268 // Try multiple possible paths where theme files might be located
269 std::vector<std::string> possible_paths = {
270 filepath, // Absolute path
271 "assets/themes/" + filepath, // Relative from build dir
272 "../assets/themes/" + filepath, // Relative from bin dir
273 util::GetResourcePath("assets/themes/" + filepath), // Platform-specific resource path
274 };
275
276 std::ifstream file;
277 std::string successful_path;
278
279 for (const auto& path : possible_paths) {
280 file.open(path);
281 if (file.is_open()) {
282 successful_path = path;
283 break;
284 } else {
285 file.clear(); // Clear any error flags before trying next path
286 }
287 }
288
289 if (!file.is_open()) {
290 return absl::InvalidArgumentError(absl::StrFormat("Cannot open theme file: %s (tried %zu paths)",
291 filepath, possible_paths.size()));
292 }
293
294 std::string content((std::istreambuf_iterator<char>(file)),
295 std::istreambuf_iterator<char>());
296 file.close();
297
298 if (content.empty()) {
299 return absl::InvalidArgumentError(absl::StrFormat("Theme file is empty: %s", successful_path));
300 }
301
302 EnhancedTheme theme;
303 auto parse_status = ParseThemeFile(content, theme);
304 if (!parse_status.ok()) {
305 return absl::InvalidArgumentError(absl::StrFormat("Failed to parse theme file %s: %s",
306 successful_path, parse_status.message()));
307 }
308
309 if (theme.name.empty()) {
310 return absl::InvalidArgumentError(absl::StrFormat("Theme file missing name: %s", successful_path));
311 }
312
313 themes_[theme.name] = theme;
314 return absl::OkStatus();
315}
316
317std::vector<std::string> ThemeManager::GetAvailableThemes() const {
318 std::vector<std::string> theme_names;
319 for (const auto& [name, theme] : themes_) {
320 theme_names.push_back(name);
321 }
322 return theme_names;
323}
324
325const EnhancedTheme* ThemeManager::GetTheme(const std::string& name) const {
326 auto it = themes_.find(name);
327 return (it != themes_.end()) ? &it->second : nullptr;
328}
329
330void ThemeManager::ApplyTheme(const std::string& theme_name) {
331 auto status = LoadTheme(theme_name);
332 if (!status.ok()) {
333 // Fallback to YAZE Tre if theme not found
334 auto fallback_status = LoadTheme("YAZE Tre");
335 if (!fallback_status.ok()) {
336 LOG_ERROR("Theme Manager", "Failed to load fallback theme");
337 }
338 }
339}
340
342 current_theme_ = theme;
343 current_theme_name_ = theme.name; // CRITICAL: Update the name tracking
345}
346
348 // Create a darker version of the window background for welcome screen
350 return {bg.red * 0.8f, bg.green * 0.8f, bg.blue * 0.8f, bg.alpha};
351}
352
356
360
362 if (!p_open || !*p_open) return;
363
364 if (ImGui::Begin(absl::StrFormat("%s Theme Selector", ICON_MD_PALETTE).c_str(), p_open)) {
365
366 // Add subtle particle effects to theme selector
367 static float theme_animation_time = 0.0f;
368 theme_animation_time += ImGui::GetIO().DeltaTime;
369
370 ImDrawList* draw_list = ImGui::GetWindowDrawList();
371 ImVec2 window_pos = ImGui::GetWindowPos();
372 ImVec2 window_size = ImGui::GetWindowSize();
373
374 // Subtle corner particles for theme selector
375 for (int i = 0; i < 4; ++i) {
376 float corner_offset = i * 1.57f; // 90 degrees apart
377 float x = window_pos.x + window_size.x * 0.5f + cosf(theme_animation_time * 0.8f + corner_offset) * (window_size.x * 0.4f);
378 float y = window_pos.y + window_size.y * 0.5f + sinf(theme_animation_time * 0.8f + corner_offset) * (window_size.y * 0.4f);
379
380 float alpha = 0.1f + 0.1f * sinf(theme_animation_time * 1.2f + corner_offset);
381 auto current_theme = GetCurrentTheme();
382 ImU32 particle_color = ImGui::ColorConvertFloat4ToU32(ImVec4(
383 current_theme.accent.red, current_theme.accent.green, current_theme.accent.blue, alpha));
384
385 draw_list->AddCircleFilled(ImVec2(x, y), 3.0f, particle_color);
386 }
387
388 ImGui::Text("%s Available Themes", ICON_MD_COLOR_LENS);
389 ImGui::Separator();
390
391 // Add Classic YAZE button first (direct ColorsYaze() application)
392 bool is_classic_active = (current_theme_name_ == "Classic YAZE");
393 if (is_classic_active) {
394 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.36f, 0.45f, 0.36f, 1.0f)); // allttpLightGreen
395 }
396
397 if (ImGui::Button(absl::StrFormat("%s YAZE Classic (Original)",
398 is_classic_active ? ICON_MD_CHECK : ICON_MD_STAR).c_str(),
399 ImVec2(-1, 50))) {
401 }
402
403 if (is_classic_active) {
404 ImGui::PopStyleColor();
405 }
406
407 if (ImGui::IsItemHovered()) {
408 ImGui::BeginTooltip();
409 ImGui::Text("Original YAZE theme using ColorsYaze() function");
410 ImGui::Text("This is the authentic classic look - direct function call");
411 ImGui::EndTooltip();
412 }
413
414 ImGui::Separator();
415
416 // Sort themes alphabetically for consistent ordering (by name only)
417 std::vector<std::string> sorted_theme_names;
418 for (const auto& [name, theme] : themes_) {
419 sorted_theme_names.push_back(name);
420 }
421 std::sort(sorted_theme_names.begin(), sorted_theme_names.end());
422
423 for (const auto& name : sorted_theme_names) {
424 const auto& theme = themes_.at(name);
425 bool is_current = (name == current_theme_name_);
426
427 if (is_current) {
428 ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.accent));
429 }
430
431 if (ImGui::Button(absl::StrFormat("%s %s",
432 is_current ? ICON_MD_CHECK : ICON_MD_CIRCLE,
433 name.c_str()).c_str(), ImVec2(-1, 40))) {
434 auto status = LoadTheme(name); // Use LoadTheme instead of ApplyTheme to ensure correct tracking
435 if (!status.ok()) {
436 LOG_ERROR("Theme Manager", "Failed to load theme %s", name.c_str());
437 }
438 }
439
440 if (is_current) {
441 ImGui::PopStyleColor();
442 }
443
444 // Show theme preview colors
445 ImGui::SameLine();
446 ImGui::ColorButton(absl::StrFormat("##primary_%s", name.c_str()).c_str(),
447 ConvertColorToImVec4(theme.primary),
448 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
449 ImGui::SameLine();
450 ImGui::ColorButton(absl::StrFormat("##secondary_%s", name.c_str()).c_str(),
451 ConvertColorToImVec4(theme.secondary),
452 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
453 ImGui::SameLine();
454 ImGui::ColorButton(absl::StrFormat("##accent_%s", name.c_str()).c_str(),
455 ConvertColorToImVec4(theme.accent),
456 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
457
458 if (ImGui::IsItemHovered()) {
459 ImGui::BeginTooltip();
460 ImGui::Text("%s", theme.description.c_str());
461 ImGui::Text("Author: %s", theme.author.c_str());
462 ImGui::EndTooltip();
463 }
464 }
465
466 ImGui::Separator();
467 if (ImGui::Button(absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) {
468 auto status = RefreshAvailableThemes();
469 if (!status.ok()) {
470 LOG_ERROR("Theme Manager", "Failed to refresh themes");
471 }
472 }
473
474 ImGui::SameLine();
475 if (ImGui::Button(absl::StrFormat("%s Load Custom Theme", ICON_MD_FOLDER_OPEN).c_str())) {
477 if (!file_path.empty()) {
478 auto status = LoadThemeFromFile(file_path);
479 if (!status.ok()) {
480 // Show error toast (would need access to toast manager)
481 }
482 }
483 }
484
485 ImGui::SameLine();
486 static bool show_simple_editor = false;
487 if (ImGui::Button(absl::StrFormat("%s Theme Editor", ICON_MD_EDIT).c_str())) {
488 show_simple_editor = true;
489 }
490
491 if (ImGui::IsItemHovered()) {
492 ImGui::BeginTooltip();
493 ImGui::Text("Edit and save custom themes");
494 ImGui::Text("Includes 'Save to File' functionality");
495 ImGui::EndTooltip();
496 }
497
498 if (show_simple_editor) {
499 ShowSimpleThemeEditor(&show_simple_editor);
500 }
501 }
502 ImGui::End();
503}
504
505absl::Status ThemeManager::ParseThemeFile(const std::string& content, EnhancedTheme& theme) {
506 std::istringstream stream(content);
507 std::string line;
508 std::string current_section = "";
509
510 while (std::getline(stream, line)) {
511 // Skip empty lines and comments
512 if (line.empty() || line[0] == '#') continue;
513
514 // Check for section headers [section_name]
515 if (line[0] == '[' && line.back() == ']') {
516 current_section = line.substr(1, line.length() - 2);
517 continue;
518 }
519
520 size_t eq_pos = line.find('=');
521 if (eq_pos == std::string::npos) continue;
522
523 std::string key = line.substr(0, eq_pos);
524 std::string value = line.substr(eq_pos + 1);
525
526 // Trim whitespace and comments
527 key.erase(0, key.find_first_not_of(" \t"));
528 key.erase(key.find_last_not_of(" \t") + 1);
529 value.erase(0, value.find_first_not_of(" \t"));
530
531 // Remove inline comments
532 size_t comment_pos = value.find('#');
533 if (comment_pos != std::string::npos) {
534 value = value.substr(0, comment_pos);
535 }
536 value.erase(value.find_last_not_of(" \t") + 1);
537
538 // Parse based on section
539 if (current_section == "colors") {
540 Color color = ParseColorFromString(value);
541
542 if (key == "primary") theme.primary = color;
543 else if (key == "secondary") theme.secondary = color;
544 else if (key == "accent") theme.accent = color;
545 else if (key == "background") theme.background = color;
546 else if (key == "surface") theme.surface = color;
547 else if (key == "error") theme.error = color;
548 else if (key == "warning") theme.warning = color;
549 else if (key == "success") theme.success = color;
550 else if (key == "info") theme.info = color;
551 else if (key == "text_primary") theme.text_primary = color;
552 else if (key == "text_secondary") theme.text_secondary = color;
553 else if (key == "text_disabled") theme.text_disabled = color;
554 else if (key == "window_bg") theme.window_bg = color;
555 else if (key == "child_bg") theme.child_bg = color;
556 else if (key == "popup_bg") theme.popup_bg = color;
557 else if (key == "button") theme.button = color;
558 else if (key == "button_hovered") theme.button_hovered = color;
559 else if (key == "button_active") theme.button_active = color;
560 else if (key == "frame_bg") theme.frame_bg = color;
561 else if (key == "frame_bg_hovered") theme.frame_bg_hovered = color;
562 else if (key == "frame_bg_active") theme.frame_bg_active = color;
563 else if (key == "header") theme.header = color;
564 else if (key == "header_hovered") theme.header_hovered = color;
565 else if (key == "header_active") theme.header_active = color;
566 else if (key == "tab") theme.tab = color;
567 else if (key == "tab_hovered") theme.tab_hovered = color;
568 else if (key == "tab_active") theme.tab_active = color;
569 else if (key == "menu_bar_bg") theme.menu_bar_bg = color;
570 else if (key == "title_bg") theme.title_bg = color;
571 else if (key == "title_bg_active") theme.title_bg_active = color;
572 else if (key == "title_bg_collapsed") theme.title_bg_collapsed = color;
573 else if (key == "separator") theme.separator = color;
574 else if (key == "separator_hovered") theme.separator_hovered = color;
575 else if (key == "separator_active") theme.separator_active = color;
576 else if (key == "scrollbar_bg") theme.scrollbar_bg = color;
577 else if (key == "scrollbar_grab") theme.scrollbar_grab = color;
578 else if (key == "scrollbar_grab_hovered") theme.scrollbar_grab_hovered = color;
579 else if (key == "scrollbar_grab_active") theme.scrollbar_grab_active = color;
580 else if (key == "border") theme.border = color;
581 else if (key == "border_shadow") theme.border_shadow = color;
582 else if (key == "resize_grip") theme.resize_grip = color;
583 else if (key == "resize_grip_hovered") theme.resize_grip_hovered = color;
584 else if (key == "resize_grip_active") theme.resize_grip_active = color;
585 else if (key == "check_mark") theme.check_mark = color;
586 else if (key == "slider_grab") theme.slider_grab = color;
587 else if (key == "slider_grab_active") theme.slider_grab_active = color;
588 else if (key == "input_text_cursor") theme.input_text_cursor = color;
589 else if (key == "nav_cursor") theme.nav_cursor = color;
590 else if (key == "nav_windowing_highlight") theme.nav_windowing_highlight = color;
591 else if (key == "nav_windowing_dim_bg") theme.nav_windowing_dim_bg = color;
592 else if (key == "modal_window_dim_bg") theme.modal_window_dim_bg = color;
593 else if (key == "text_selected_bg") theme.text_selected_bg = color;
594 else if (key == "drag_drop_target") theme.drag_drop_target = color;
595 else if (key == "table_header_bg") theme.table_header_bg = color;
596 else if (key == "table_border_strong") theme.table_border_strong = color;
597 else if (key == "table_border_light") theme.table_border_light = color;
598 else if (key == "table_row_bg") theme.table_row_bg = color;
599 else if (key == "table_row_bg_alt") theme.table_row_bg_alt = color;
600 else if (key == "text_link") theme.text_link = color;
601 else if (key == "plot_lines") theme.plot_lines = color;
602 else if (key == "plot_lines_hovered") theme.plot_lines_hovered = color;
603 else if (key == "plot_histogram") theme.plot_histogram = color;
604 else if (key == "plot_histogram_hovered") theme.plot_histogram_hovered = color;
605 else if (key == "tree_lines") theme.tree_lines = color;
606 else if (key == "tab_dimmed") theme.tab_dimmed = color;
607 else if (key == "tab_dimmed_selected") theme.tab_dimmed_selected = color;
608 else if (key == "tab_dimmed_selected_overline") theme.tab_dimmed_selected_overline = color;
609 else if (key == "tab_selected_overline") theme.tab_selected_overline = color;
610 else if (key == "docking_preview") theme.docking_preview = color;
611 else if (key == "docking_empty_bg") theme.docking_empty_bg = color;
612 }
613 else if (current_section == "style") {
614 if (key == "window_rounding") theme.window_rounding = std::stof(value);
615 else if (key == "frame_rounding") theme.frame_rounding = std::stof(value);
616 else if (key == "scrollbar_rounding") theme.scrollbar_rounding = std::stof(value);
617 else if (key == "grab_rounding") theme.grab_rounding = std::stof(value);
618 else if (key == "tab_rounding") theme.tab_rounding = std::stof(value);
619 else if (key == "window_border_size") theme.window_border_size = std::stof(value);
620 else if (key == "frame_border_size") theme.frame_border_size = std::stof(value);
621 else if (key == "enable_animations") theme.enable_animations = (value == "true");
622 else if (key == "enable_glow_effects") theme.enable_glow_effects = (value == "true");
623 else if (key == "animation_speed") theme.animation_speed = std::stof(value);
624 }
625 else if (current_section == "" || current_section == "metadata") {
626 // Top-level metadata
627 if (key == "name") theme.name = value;
628 else if (key == "description") theme.description = value;
629 else if (key == "author") theme.author = value;
630 }
631 }
632
633 return absl::OkStatus();
634}
635
636Color ThemeManager::ParseColorFromString(const std::string& color_str) const {
637 std::vector<std::string> components = absl::StrSplit(color_str, ',');
638 if (components.size() != 4) {
639 return RGBA(255, 255, 255, 255); // White fallback
640 }
641
642 try {
643 int r = std::stoi(components[0]);
644 int g = std::stoi(components[1]);
645 int b = std::stoi(components[2]);
646 int a = std::stoi(components[3]);
647 return RGBA(r, g, b, a);
648 } catch (...) {
649 return RGBA(255, 255, 255, 255); // White fallback
650 }
651}
652
653std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const {
654 std::ostringstream ss;
655
656 // Helper function to convert color to RGB string
657 auto colorToString = [](const Color& c) -> std::string {
658 int r = static_cast<int>(c.red * 255.0f);
659 int g = static_cast<int>(c.green * 255.0f);
660 int b = static_cast<int>(c.blue * 255.0f);
661 int a = static_cast<int>(c.alpha * 255.0f);
662 return std::to_string(r) + "," + std::to_string(g) + "," + std::to_string(b) + "," + std::to_string(a);
663 };
664
665 ss << "# yaze Theme File\n";
666 ss << "# Generated by YAZE Theme Editor\n";
667 ss << "name=" << theme.name << "\n";
668 ss << "description=" << theme.description << "\n";
669 ss << "author=" << theme.author << "\n";
670 ss << "version=1.0\n";
671 ss << "\n[colors]\n";
672
673 // Primary colors
674 ss << "# Primary colors\n";
675 ss << "primary=" << colorToString(theme.primary) << "\n";
676 ss << "secondary=" << colorToString(theme.secondary) << "\n";
677 ss << "accent=" << colorToString(theme.accent) << "\n";
678 ss << "background=" << colorToString(theme.background) << "\n";
679 ss << "surface=" << colorToString(theme.surface) << "\n";
680 ss << "\n";
681
682 // Status colors
683 ss << "# Status colors\n";
684 ss << "error=" << colorToString(theme.error) << "\n";
685 ss << "warning=" << colorToString(theme.warning) << "\n";
686 ss << "success=" << colorToString(theme.success) << "\n";
687 ss << "info=" << colorToString(theme.info) << "\n";
688 ss << "\n";
689
690 // Text colors
691 ss << "# Text colors\n";
692 ss << "text_primary=" << colorToString(theme.text_primary) << "\n";
693 ss << "text_secondary=" << colorToString(theme.text_secondary) << "\n";
694 ss << "text_disabled=" << colorToString(theme.text_disabled) << "\n";
695 ss << "\n";
696
697 // Window colors
698 ss << "# Window colors\n";
699 ss << "window_bg=" << colorToString(theme.window_bg) << "\n";
700 ss << "child_bg=" << colorToString(theme.child_bg) << "\n";
701 ss << "popup_bg=" << colorToString(theme.popup_bg) << "\n";
702 ss << "\n";
703
704 // Interactive elements
705 ss << "# Interactive elements\n";
706 ss << "button=" << colorToString(theme.button) << "\n";
707 ss << "button_hovered=" << colorToString(theme.button_hovered) << "\n";
708 ss << "button_active=" << colorToString(theme.button_active) << "\n";
709 ss << "frame_bg=" << colorToString(theme.frame_bg) << "\n";
710 ss << "frame_bg_hovered=" << colorToString(theme.frame_bg_hovered) << "\n";
711 ss << "frame_bg_active=" << colorToString(theme.frame_bg_active) << "\n";
712 ss << "\n";
713
714 // Navigation
715 ss << "# Navigation\n";
716 ss << "header=" << colorToString(theme.header) << "\n";
717 ss << "header_hovered=" << colorToString(theme.header_hovered) << "\n";
718 ss << "header_active=" << colorToString(theme.header_active) << "\n";
719 ss << "tab=" << colorToString(theme.tab) << "\n";
720 ss << "tab_hovered=" << colorToString(theme.tab_hovered) << "\n";
721 ss << "tab_active=" << colorToString(theme.tab_active) << "\n";
722 ss << "menu_bar_bg=" << colorToString(theme.menu_bar_bg) << "\n";
723 ss << "title_bg=" << colorToString(theme.title_bg) << "\n";
724 ss << "title_bg_active=" << colorToString(theme.title_bg_active) << "\n";
725 ss << "title_bg_collapsed=" << colorToString(theme.title_bg_collapsed) << "\n";
726 ss << "\n";
727
728 // Borders and separators
729 ss << "# Borders and separators\n";
730 ss << "border=" << colorToString(theme.border) << "\n";
731 ss << "border_shadow=" << colorToString(theme.border_shadow) << "\n";
732 ss << "separator=" << colorToString(theme.separator) << "\n";
733 ss << "separator_hovered=" << colorToString(theme.separator_hovered) << "\n";
734 ss << "separator_active=" << colorToString(theme.separator_active) << "\n";
735 ss << "\n";
736
737 // Scrollbars and controls
738 ss << "# Scrollbars and controls\n";
739 ss << "scrollbar_bg=" << colorToString(theme.scrollbar_bg) << "\n";
740 ss << "scrollbar_grab=" << colorToString(theme.scrollbar_grab) << "\n";
741 ss << "scrollbar_grab_hovered=" << colorToString(theme.scrollbar_grab_hovered) << "\n";
742 ss << "scrollbar_grab_active=" << colorToString(theme.scrollbar_grab_active) << "\n";
743 ss << "resize_grip=" << colorToString(theme.resize_grip) << "\n";
744 ss << "resize_grip_hovered=" << colorToString(theme.resize_grip_hovered) << "\n";
745 ss << "resize_grip_active=" << colorToString(theme.resize_grip_active) << "\n";
746 ss << "check_mark=" << colorToString(theme.check_mark) << "\n";
747 ss << "slider_grab=" << colorToString(theme.slider_grab) << "\n";
748 ss << "slider_grab_active=" << colorToString(theme.slider_grab_active) << "\n";
749 ss << "\n";
750
751 // Navigation and special elements
752 ss << "# Navigation and special elements\n";
753 ss << "input_text_cursor=" << colorToString(theme.input_text_cursor) << "\n";
754 ss << "nav_cursor=" << colorToString(theme.nav_cursor) << "\n";
755 ss << "nav_windowing_highlight=" << colorToString(theme.nav_windowing_highlight) << "\n";
756 ss << "nav_windowing_dim_bg=" << colorToString(theme.nav_windowing_dim_bg) << "\n";
757 ss << "modal_window_dim_bg=" << colorToString(theme.modal_window_dim_bg) << "\n";
758 ss << "text_selected_bg=" << colorToString(theme.text_selected_bg) << "\n";
759 ss << "drag_drop_target=" << colorToString(theme.drag_drop_target) << "\n";
760 ss << "docking_preview=" << colorToString(theme.docking_preview) << "\n";
761 ss << "docking_empty_bg=" << colorToString(theme.docking_empty_bg) << "\n";
762 ss << "\n";
763
764 // Table colors
765 ss << "# Table colors\n";
766 ss << "table_header_bg=" << colorToString(theme.table_header_bg) << "\n";
767 ss << "table_border_strong=" << colorToString(theme.table_border_strong) << "\n";
768 ss << "table_border_light=" << colorToString(theme.table_border_light) << "\n";
769 ss << "table_row_bg=" << colorToString(theme.table_row_bg) << "\n";
770 ss << "table_row_bg_alt=" << colorToString(theme.table_row_bg_alt) << "\n";
771 ss << "\n";
772
773 // Links and plots
774 ss << "# Links and plots\n";
775 ss << "text_link=" << colorToString(theme.text_link) << "\n";
776 ss << "plot_lines=" << colorToString(theme.plot_lines) << "\n";
777 ss << "plot_lines_hovered=" << colorToString(theme.plot_lines_hovered) << "\n";
778 ss << "plot_histogram=" << colorToString(theme.plot_histogram) << "\n";
779 ss << "plot_histogram_hovered=" << colorToString(theme.plot_histogram_hovered) << "\n";
780 ss << "tree_lines=" << colorToString(theme.tree_lines) << "\n";
781 ss << "\n";
782
783 // Tab variations
784 ss << "# Tab variations\n";
785 ss << "tab_dimmed=" << colorToString(theme.tab_dimmed) << "\n";
786 ss << "tab_dimmed_selected=" << colorToString(theme.tab_dimmed_selected) << "\n";
787 ss << "tab_dimmed_selected_overline=" << colorToString(theme.tab_dimmed_selected_overline) << "\n";
788 ss << "tab_selected_overline=" << colorToString(theme.tab_selected_overline) << "\n";
789 ss << "\n";
790
791 // Enhanced semantic colors
792 ss << "# Enhanced semantic colors\n";
793 ss << "text_highlight=" << colorToString(theme.text_highlight) << "\n";
794 ss << "link_hover=" << colorToString(theme.link_hover) << "\n";
795 ss << "code_background=" << colorToString(theme.code_background) << "\n";
796 ss << "success_light=" << colorToString(theme.success_light) << "\n";
797 ss << "warning_light=" << colorToString(theme.warning_light) << "\n";
798 ss << "error_light=" << colorToString(theme.error_light) << "\n";
799 ss << "info_light=" << colorToString(theme.info_light) << "\n";
800 ss << "\n";
801
802 // UI state colors
803 ss << "# UI state colors\n";
804 ss << "active_selection=" << colorToString(theme.active_selection) << "\n";
805 ss << "hover_highlight=" << colorToString(theme.hover_highlight) << "\n";
806 ss << "focus_border=" << colorToString(theme.focus_border) << "\n";
807 ss << "disabled_overlay=" << colorToString(theme.disabled_overlay) << "\n";
808 ss << "\n";
809
810 // Editor-specific colors
811 ss << "# Editor-specific colors\n";
812 ss << "editor_background=" << colorToString(theme.editor_background) << "\n";
813 ss << "editor_grid=" << colorToString(theme.editor_grid) << "\n";
814 ss << "editor_cursor=" << colorToString(theme.editor_cursor) << "\n";
815 ss << "editor_selection=" << colorToString(theme.editor_selection) << "\n";
816 ss << "\n";
817
818 // Style settings
819 ss << "[style]\n";
820 ss << "window_rounding=" << theme.window_rounding << "\n";
821 ss << "frame_rounding=" << theme.frame_rounding << "\n";
822 ss << "scrollbar_rounding=" << theme.scrollbar_rounding << "\n";
823 ss << "tab_rounding=" << theme.tab_rounding << "\n";
824 ss << "enable_animations=" << (theme.enable_animations ? "true" : "false") << "\n";
825 ss << "enable_glow_effects=" << (theme.enable_glow_effects ? "true" : "false") << "\n";
826
827 return ss.str();
828}
829
830absl::Status ThemeManager::SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const {
831 std::string theme_content = SerializeTheme(theme);
832
833 std::ofstream file(filepath);
834 if (!file.is_open()) {
835 return absl::InternalError(absl::StrFormat("Failed to open file for writing: %s", filepath));
836 }
837
838 file << theme_content;
839 file.close();
840
841 if (file.fail()) {
842 return absl::InternalError(absl::StrFormat("Failed to write theme file: %s", filepath));
843 }
844
845 return absl::OkStatus();
846}
847
849 // Apply the original ColorsYaze() function directly
850 ColorsYaze();
851 current_theme_name_ = "Classic YAZE";
852
853 // Create a complete Classic theme object that matches what ColorsYaze() sets
854 EnhancedTheme classic_theme;
855 classic_theme.name = "Classic YAZE";
856 classic_theme.description = "Original YAZE theme (direct ColorsYaze() function)";
857 classic_theme.author = "YAZE Team";
858
859 // Extract ALL the colors that ColorsYaze() sets (copy from CreateFallbackYazeClassic)
860 classic_theme.primary = RGBA(92, 115, 92); // allttpLightGreen
861 classic_theme.secondary = RGBA(71, 92, 71); // alttpMidGreen
862 classic_theme.accent = RGBA(89, 119, 89); // TabActive
863 classic_theme.background = RGBA(8, 8, 8); // Very dark gray for better grid visibility
864
865 classic_theme.text_primary = RGBA(230, 230, 230); // 0.90f, 0.90f, 0.90f
866 classic_theme.text_disabled = RGBA(153, 153, 153); // 0.60f, 0.60f, 0.60f
867 classic_theme.window_bg = RGBA(8, 8, 8, 217); // Very dark gray with same alpha
868 classic_theme.child_bg = RGBA(0, 0, 0, 0); // Transparent
869 classic_theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f
870
871 classic_theme.button = RGBA(71, 92, 71); // alttpMidGreen
872 classic_theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen
873 classic_theme.button_active = RGBA(92, 115, 92); // allttpLightGreen
874
875 classic_theme.header = RGBA(46, 66, 46); // alttpDarkGreen
876 classic_theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen
877 classic_theme.header_active = RGBA(71, 92, 71); // alttpMidGreen
878
879 classic_theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen
880 classic_theme.tab = RGBA(46, 66, 46); // alttpDarkGreen
881 classic_theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen
882 classic_theme.tab_active = RGBA(89, 119, 89); // TabActive
883 classic_theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab
884 classic_theme.tab_unfocused_active = RGBA(62, 83, 62); // Darker version of tab_active
885
886 // Complete all remaining ImGui colors from original ColorsYaze() function
887 classic_theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen
888 classic_theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen
889 classic_theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen
890
891 // Borders and separators
892 classic_theme.border = RGBA(92, 115, 92); // allttpLightGreen
893 classic_theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent
894 classic_theme.separator = RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f
895 classic_theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f
896 classic_theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f
897
898 // Scrollbars
899 classic_theme.scrollbar_bg = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
900 classic_theme.scrollbar_grab = RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f
901 classic_theme.scrollbar_grab_hovered = RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f
902 classic_theme.scrollbar_grab_active = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
903
904 // ENHANCED: Frame colors for inputs/widgets
905 classic_theme.frame_bg = RGBA(46, 66, 46, 140); // Darker green with some transparency
906 classic_theme.frame_bg_hovered = RGBA(71, 92, 71, 170); // Mid green when hovered
907 classic_theme.frame_bg_active = RGBA(92, 115, 92, 200); // Light green when active
908
909 // FIXED: Resize grips with better visibility
910 classic_theme.resize_grip = RGBA(92, 115, 92, 80); // Theme green, subtle
911 classic_theme.resize_grip_hovered = RGBA(125, 146, 125, 180); // Brighter when hovered
912 classic_theme.resize_grip_active = RGBA(125, 146, 125, 255); // Solid when active
913
914 // FIXED: Checkmark - bright green for high visibility!
915 classic_theme.check_mark = RGBA(125, 255, 125, 255); // Bright green (clearly visible)
916
917 // FIXED: Sliders with theme colors
918 classic_theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid)
919 classic_theme.slider_grab_active = RGBA(125, 146, 125, 255); // Lighter when grabbed
920
921 // FIXED: Input cursor - white for maximum visibility
922 classic_theme.input_text_cursor = RGBA(255, 255, 255, 255); // White cursor (always visible)
923
924 // FIXED: Navigation with theme colors
925 classic_theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green navigation
926 classic_theme.nav_windowing_highlight = RGBA(92, 115, 92, 200); // Theme green highlight
927 classic_theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay
928
929 // FIXED: Modals with better dimming
930 classic_theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha
931
932 // FIXED: Text selection - visible and theme-appropriate!
933 classic_theme.text_selected_bg = RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible!)
934
935 // FIXED: Drag/drop target with high visibility
936 classic_theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green
937 classic_theme.table_header_bg = RGBA(46, 66, 46);
938 classic_theme.table_border_strong = RGBA(71, 92, 71);
939 classic_theme.table_border_light = RGBA(66, 66, 71);
940 classic_theme.table_row_bg = RGBA(0, 0, 0, 0);
941 classic_theme.table_row_bg_alt = RGBA(255, 255, 255, 18);
942 classic_theme.text_link = classic_theme.accent;
943 classic_theme.plot_lines = RGBA(255, 255, 255);
944 classic_theme.plot_lines_hovered = RGBA(230, 178, 0);
945 classic_theme.plot_histogram = RGBA(230, 178, 0);
946 classic_theme.plot_histogram_hovered = RGBA(255, 153, 0);
947 classic_theme.docking_preview = RGBA(92, 115, 92, 180);
948 classic_theme.docking_empty_bg = RGBA(46, 66, 46, 255);
949 classic_theme.tree_lines = classic_theme.separator; // Use separator color for tree lines
950
951 // Tab dimmed colors (for unfocused tabs)
952 classic_theme.tab_dimmed = RGBA(37, 52, 37); // Darker version of tab
953 classic_theme.tab_dimmed_selected = RGBA(62, 83, 62); // Darker version of tab_active
954 classic_theme.tab_dimmed_selected_overline = classic_theme.accent;
955 classic_theme.tab_selected_overline = classic_theme.accent;
956
957 // Enhanced semantic colors for better theming
958 classic_theme.text_highlight = RGBA(255, 255, 150); // Light yellow for highlights
959 classic_theme.link_hover = RGBA(140, 220, 255); // Brighter blue for link hover
960 classic_theme.code_background = RGBA(40, 60, 40); // Slightly darker green for code
961 classic_theme.success_light = RGBA(140, 195, 140); // Light green
962 classic_theme.warning_light = RGBA(255, 220, 100); // Light yellow
963 classic_theme.error_light = RGBA(255, 150, 150); // Light red
964 classic_theme.info_light = RGBA(150, 200, 255); // Light blue
965
966 // UI state colors
967 classic_theme.active_selection = classic_theme.accent; // Use accent color for active selection
968 classic_theme.hover_highlight = RGBA(92, 115, 92, 100); // Semi-transparent green
969 classic_theme.focus_border = classic_theme.primary; // Use primary for focus
970 classic_theme.disabled_overlay = RGBA(50, 50, 50, 128); // Gray overlay
971
972 // Editor-specific colors
973 classic_theme.editor_background = RGBA(30, 45, 30); // Dark green background
974 classic_theme.editor_grid = RGBA(80, 100, 80, 100); // Subtle grid lines
975 classic_theme.editor_cursor = RGBA(255, 255, 255); // White cursor
976 classic_theme.editor_selection = RGBA(110, 145, 110, 100); // Semi-transparent selection
977
978 // Apply original style settings
979 classic_theme.window_rounding = 0.0f;
980 classic_theme.frame_rounding = 5.0f;
981 classic_theme.scrollbar_rounding = 5.0f;
982 classic_theme.tab_rounding = 0.0f;
983 classic_theme.enable_glow_effects = false;
984
985 // DON'T add Classic theme to themes map - keep it as a special case
986 // themes_["Classic YAZE"] = classic_theme; // REMOVED to prevent off-by-one
987 current_theme_ = classic_theme;
988}
989
991 if (!p_open || !*p_open) return;
992
993 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
994
995 if (ImGui::Begin(absl::StrFormat("%s Theme Editor", ICON_MD_PALETTE).c_str(), p_open,
996 ImGuiWindowFlags_MenuBar)) {
997
998 // Add gentle particle effects to theme editor background
999 static float editor_animation_time = 0.0f;
1000 editor_animation_time += ImGui::GetIO().DeltaTime;
1001
1002 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1003 ImVec2 window_pos = ImGui::GetWindowPos();
1004 ImVec2 window_size = ImGui::GetWindowSize();
1005
1006 // Floating color orbs representing different color categories
1007 auto current_theme = GetCurrentTheme();
1008 std::vector<gui::Color> theme_colors = {
1009 current_theme.primary, current_theme.secondary, current_theme.accent,
1010 current_theme.success, current_theme.warning, current_theme.error
1011 };
1012
1013 for (size_t i = 0; i < theme_colors.size(); ++i) {
1014 float time_offset = i * 1.0f;
1015 float orbit_radius = 60.0f + i * 8.0f;
1016 float x = window_pos.x + window_size.x * 0.8f + cosf(editor_animation_time * 0.3f + time_offset) * orbit_radius;
1017 float y = window_pos.y + window_size.y * 0.3f + sinf(editor_animation_time * 0.3f + time_offset) * orbit_radius;
1018
1019 float alpha = 0.15f + 0.1f * sinf(editor_animation_time * 1.5f + time_offset);
1020 ImU32 orb_color = ImGui::ColorConvertFloat4ToU32(ImVec4(
1021 theme_colors[i].red, theme_colors[i].green, theme_colors[i].blue, alpha));
1022
1023 float radius = 4.0f + sinf(editor_animation_time * 2.0f + time_offset) * 1.0f;
1024 draw_list->AddCircleFilled(ImVec2(x, y), radius, orb_color);
1025 }
1026
1027 // Menu bar for theme operations
1028 if (ImGui::BeginMenuBar()) {
1029 if (ImGui::BeginMenu("File")) {
1030 if (ImGui::MenuItem(absl::StrFormat("%s New Theme", ICON_MD_ADD).c_str())) {
1031 // Reset to default theme
1033 }
1034 if (ImGui::MenuItem(absl::StrFormat("%s Load Theme", ICON_MD_FOLDER_OPEN).c_str())) {
1036 if (!file_path.empty()) {
1037 LoadThemeFromFile(file_path);
1038 }
1039 }
1040 ImGui::Separator();
1041 if (ImGui::MenuItem(absl::StrFormat("%s Save Theme", ICON_MD_SAVE).c_str())) {
1042 // Save current theme to its existing file
1043 std::string current_file_path = GetCurrentThemeFilePath();
1044 if (!current_file_path.empty()) {
1045 auto status = SaveThemeToFile(current_theme_, current_file_path);
1046 if (!status.ok()) {
1047 LOG_ERROR("Theme Manager", "Failed to save theme");
1048 }
1049 } else {
1050 // No existing file, prompt for new location
1052 if (!file_path.empty()) {
1053 auto status = SaveThemeToFile(current_theme_, file_path);
1054 if (!status.ok()) {
1055 LOG_ERROR("Theme Manager", "Failed to save theme");
1056 }
1057 }
1058 }
1059 }
1060 if (ImGui::MenuItem(absl::StrFormat("%s Save As...", ICON_MD_SAVE_AS).c_str())) {
1061 // Save theme to new file
1063 if (!file_path.empty()) {
1064 auto status = SaveThemeToFile(current_theme_, file_path);
1065 if (!status.ok()) {
1066 LOG_ERROR("Theme Manager", "Failed to save theme");
1067 }
1068 }
1069 }
1070 ImGui::EndMenu();
1071 }
1072
1073 if (ImGui::BeginMenu("Presets")) {
1074 if (ImGui::MenuItem("YAZE Classic")) {
1076 }
1077
1078 auto available_themes = GetAvailableThemes();
1079 if (!available_themes.empty()) {
1080 ImGui::Separator();
1081 for (const auto& theme_name : available_themes) {
1082 if (ImGui::MenuItem(theme_name.c_str())) {
1083 LoadTheme(theme_name);
1084 }
1085 }
1086 }
1087 ImGui::EndMenu();
1088 }
1089
1090 ImGui::EndMenuBar();
1091 }
1092
1093 static EnhancedTheme edit_theme = current_theme_;
1094 static char theme_name[128];
1095 static char theme_description[256];
1096 static char theme_author[128];
1097 static bool live_preview = true;
1098 static EnhancedTheme original_theme; // Store original theme for restoration
1099 static bool theme_backup_made = false;
1100
1101 // Helper lambda for live preview application
1102 auto apply_live_preview = [&]() {
1103 if (live_preview) {
1104 if (!theme_backup_made) {
1105 original_theme = current_theme_;
1106 theme_backup_made = true;
1107 }
1108 // Apply the edit theme directly to ImGui without changing theme manager state
1109 edit_theme.ApplyToImGui();
1110 }
1111 };
1112
1113 // Live preview toggle
1114 ImGui::Checkbox("Live Preview", &live_preview);
1115 ImGui::SameLine();
1116 ImGui::Text("| Changes apply immediately when enabled");
1117
1118 // If live preview was just disabled, restore original theme
1119 static bool prev_live_preview = live_preview;
1120 if (prev_live_preview && !live_preview && theme_backup_made) {
1121 ApplyTheme(original_theme);
1122 theme_backup_made = false;
1123 }
1124 prev_live_preview = live_preview;
1125
1126 ImGui::Separator();
1127
1128 // Theme metadata in a table for better layout
1129 if (ImGui::BeginTable("ThemeMetadata", 2, ImGuiTableFlags_SizingStretchProp)) {
1130 ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, 100.0f);
1131 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1132
1133 ImGui::TableNextRow();
1134 ImGui::TableNextColumn();
1135 ImGui::AlignTextToFramePadding();
1136 ImGui::Text("Name:");
1137 ImGui::TableNextColumn();
1138 if (ImGui::InputText("##theme_name", theme_name, sizeof(theme_name))) {
1139 edit_theme.name = std::string(theme_name);
1140 }
1141
1142 ImGui::TableNextRow();
1143 ImGui::TableNextColumn();
1144 ImGui::AlignTextToFramePadding();
1145 ImGui::Text("Description:");
1146 ImGui::TableNextColumn();
1147 if (ImGui::InputText("##theme_description", theme_description, sizeof(theme_description))) {
1148 edit_theme.description = std::string(theme_description);
1149 }
1150
1151 ImGui::TableNextRow();
1152 ImGui::TableNextColumn();
1153 ImGui::AlignTextToFramePadding();
1154 ImGui::Text("Author:");
1155 ImGui::TableNextColumn();
1156 if (ImGui::InputText("##theme_author", theme_author, sizeof(theme_author))) {
1157 edit_theme.author = std::string(theme_author);
1158 }
1159
1160 ImGui::EndTable();
1161 }
1162
1163 ImGui::Separator();
1164
1165 // Enhanced theme editing with tabs for better organization
1166 if (ImGui::BeginTabBar("ThemeEditorTabs", ImGuiTabBarFlags_None)) {
1167
1168 // Apply live preview on first frame if enabled
1169 static bool first_frame = true;
1170 if (first_frame && live_preview) {
1171 apply_live_preview();
1172 first_frame = false;
1173 }
1174
1175 // Primary Colors Tab
1176 if (ImGui::BeginTabItem(absl::StrFormat("%s Primary", ICON_MD_COLOR_LENS).c_str())) {
1177 if (ImGui::BeginTable("PrimaryColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1178 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1179 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1180 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1181 ImGui::TableHeadersRow();
1182
1183 // Primary color
1184 ImGui::TableNextRow();
1185 ImGui::TableNextColumn();
1186 ImGui::AlignTextToFramePadding();
1187 ImGui::Text("Primary:");
1188 ImGui::TableNextColumn();
1189 ImVec4 primary = ConvertColorToImVec4(edit_theme.primary);
1190 if (ImGui::ColorEdit3("##primary", &primary.x)) {
1191 edit_theme.primary = {primary.x, primary.y, primary.z, primary.w};
1192 apply_live_preview();
1193 }
1194 ImGui::TableNextColumn();
1195 ImGui::Button("Primary Preview", ImVec2(-1, 30));
1196
1197 // Secondary color
1198 ImGui::TableNextRow();
1199 ImGui::TableNextColumn();
1200 ImGui::AlignTextToFramePadding();
1201 ImGui::Text("Secondary:");
1202 ImGui::TableNextColumn();
1203 ImVec4 secondary = ConvertColorToImVec4(edit_theme.secondary);
1204 if (ImGui::ColorEdit3("##secondary", &secondary.x)) {
1205 edit_theme.secondary = {secondary.x, secondary.y, secondary.z, secondary.w};
1206 apply_live_preview();
1207 }
1208 ImGui::TableNextColumn();
1209 ImGui::PushStyleColor(ImGuiCol_Button, secondary);
1210 ImGui::Button("Secondary Preview", ImVec2(-1, 30));
1211 ImGui::PopStyleColor();
1212
1213 // Accent color
1214 ImGui::TableNextRow();
1215 ImGui::TableNextColumn();
1216 ImGui::AlignTextToFramePadding();
1217 ImGui::Text("Accent:");
1218 ImGui::TableNextColumn();
1219 ImVec4 accent = ConvertColorToImVec4(edit_theme.accent);
1220 if (ImGui::ColorEdit3("##accent", &accent.x)) {
1221 edit_theme.accent = {accent.x, accent.y, accent.z, accent.w};
1222 apply_live_preview();
1223 }
1224 ImGui::TableNextColumn();
1225 ImGui::PushStyleColor(ImGuiCol_Button, accent);
1226 ImGui::Button("Accent Preview", ImVec2(-1, 30));
1227 ImGui::PopStyleColor();
1228
1229 // Background color
1230 ImGui::TableNextRow();
1231 ImGui::TableNextColumn();
1232 ImGui::AlignTextToFramePadding();
1233 ImGui::Text("Background:");
1234 ImGui::TableNextColumn();
1235 ImVec4 background = ConvertColorToImVec4(edit_theme.background);
1236 if (ImGui::ColorEdit4("##background", &background.x)) {
1237 edit_theme.background = {background.x, background.y, background.z, background.w};
1238 apply_live_preview();
1239 }
1240 ImGui::TableNextColumn();
1241 ImGui::Text("Background preview shown in window");
1242
1243 ImGui::EndTable();
1244 }
1245 ImGui::EndTabItem();
1246 }
1247
1248 // Text Colors Tab
1249 if (ImGui::BeginTabItem(absl::StrFormat("%s Text", ICON_MD_TEXT_FIELDS).c_str())) {
1250 if (ImGui::BeginTable("TextColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1251 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1252 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1253 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1254 ImGui::TableHeadersRow();
1255
1256 // Text colors with live preview
1257 auto text_colors = std::vector<std::pair<const char*, Color*>>{
1258 {"Primary Text", &edit_theme.text_primary},
1259 {"Secondary Text", &edit_theme.text_secondary},
1260 {"Disabled Text", &edit_theme.text_disabled},
1261 {"Link Text", &edit_theme.text_link},
1262 {"Text Highlight", &edit_theme.text_highlight},
1263 {"Link Hover", &edit_theme.link_hover},
1264 {"Text Selected BG", &edit_theme.text_selected_bg},
1265 {"Input Text Cursor", &edit_theme.input_text_cursor}
1266 };
1267
1268 for (auto& [label, color_ptr] : text_colors) {
1269 ImGui::TableNextRow();
1270 ImGui::TableNextColumn();
1271 ImGui::AlignTextToFramePadding();
1272 ImGui::Text("%s:", label);
1273
1274 ImGui::TableNextColumn();
1275 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1276 std::string id = absl::StrFormat("##%s", label);
1277 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
1278 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1279 apply_live_preview();
1280 }
1281
1282 ImGui::TableNextColumn();
1283 ImGui::PushStyleColor(ImGuiCol_Text, color_vec);
1284 ImGui::Text("Sample %s", label);
1285 ImGui::PopStyleColor();
1286 }
1287
1288 ImGui::EndTable();
1289 }
1290 ImGui::EndTabItem();
1291 }
1292
1293 // Interactive Elements Tab
1294 if (ImGui::BeginTabItem(absl::StrFormat("%s Interactive", ICON_MD_TOUCH_APP).c_str())) {
1295 if (ImGui::BeginTable("InteractiveColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1296 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1297 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1298 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1299 ImGui::TableHeadersRow();
1300
1301 // Interactive element colors
1302 auto interactive_colors = std::vector<std::tuple<const char*, Color*, ImGuiCol>>{
1303 {"Button", &edit_theme.button, ImGuiCol_Button},
1304 {"Button Hovered", &edit_theme.button_hovered, ImGuiCol_ButtonHovered},
1305 {"Button Active", &edit_theme.button_active, ImGuiCol_ButtonActive},
1306 {"Frame Background", &edit_theme.frame_bg, ImGuiCol_FrameBg},
1307 {"Frame BG Hovered", &edit_theme.frame_bg_hovered, ImGuiCol_FrameBgHovered},
1308 {"Frame BG Active", &edit_theme.frame_bg_active, ImGuiCol_FrameBgActive},
1309 {"Check Mark", &edit_theme.check_mark, ImGuiCol_CheckMark},
1310 {"Slider Grab", &edit_theme.slider_grab, ImGuiCol_SliderGrab},
1311 {"Slider Grab Active", &edit_theme.slider_grab_active, ImGuiCol_SliderGrabActive}
1312 };
1313
1314 for (auto& [label, color_ptr, imgui_col] : interactive_colors) {
1315 ImGui::TableNextRow();
1316 ImGui::TableNextColumn();
1317 ImGui::AlignTextToFramePadding();
1318 ImGui::Text("%s:", label);
1319
1320 ImGui::TableNextColumn();
1321 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1322 std::string id = absl::StrFormat("##%s", label);
1323 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
1324 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1325 apply_live_preview();
1326 }
1327
1328 ImGui::TableNextColumn();
1329 ImGui::PushStyleColor(imgui_col, color_vec);
1330 ImGui::Button(absl::StrFormat("Preview %s", label).c_str(), ImVec2(-1, 30));
1331 ImGui::PopStyleColor();
1332 }
1333
1334 ImGui::EndTable();
1335 }
1336 ImGui::EndTabItem();
1337 }
1338
1339 // Style Parameters Tab
1340 if (ImGui::BeginTabItem(absl::StrFormat("%s Style", ICON_MD_TUNE).c_str())) {
1341 ImGui::Text("Rounding and Border Settings:");
1342
1343 if (ImGui::SliderFloat("Window Rounding", &edit_theme.window_rounding, 0.0f, 20.0f)) {
1344 if (live_preview) ApplyTheme(edit_theme);
1345 }
1346 if (ImGui::SliderFloat("Frame Rounding", &edit_theme.frame_rounding, 0.0f, 20.0f)) {
1347 if (live_preview) ApplyTheme(edit_theme);
1348 }
1349 if (ImGui::SliderFloat("Scrollbar Rounding", &edit_theme.scrollbar_rounding, 0.0f, 20.0f)) {
1350 if (live_preview) ApplyTheme(edit_theme);
1351 }
1352 if (ImGui::SliderFloat("Tab Rounding", &edit_theme.tab_rounding, 0.0f, 20.0f)) {
1353 if (live_preview) ApplyTheme(edit_theme);
1354 }
1355 if (ImGui::SliderFloat("Grab Rounding", &edit_theme.grab_rounding, 0.0f, 20.0f)) {
1356 if (live_preview) ApplyTheme(edit_theme);
1357 }
1358
1359 ImGui::Separator();
1360 ImGui::Text("Border Sizes:");
1361
1362 if (ImGui::SliderFloat("Window Border Size", &edit_theme.window_border_size, 0.0f, 3.0f)) {
1363 if (live_preview) ApplyTheme(edit_theme);
1364 }
1365 if (ImGui::SliderFloat("Frame Border Size", &edit_theme.frame_border_size, 0.0f, 3.0f)) {
1366 if (live_preview) ApplyTheme(edit_theme);
1367 }
1368
1369 ImGui::Separator();
1370 ImGui::Text("Animation & Effects:");
1371
1372 if (ImGui::Checkbox("Enable Animations", &edit_theme.enable_animations)) {
1373 if (live_preview) ApplyTheme(edit_theme);
1374 }
1375 if (edit_theme.enable_animations) {
1376 if (ImGui::SliderFloat("Animation Speed", &edit_theme.animation_speed, 0.1f, 3.0f)) {
1377 apply_live_preview();
1378 }
1379 }
1380 if (ImGui::Checkbox("Enable Glow Effects", &edit_theme.enable_glow_effects)) {
1381 if (live_preview) ApplyTheme(edit_theme);
1382 }
1383
1384 ImGui::EndTabItem();
1385 }
1386
1387 // Navigation & Windows Tab
1388 if (ImGui::BeginTabItem(absl::StrFormat("%s Navigation", ICON_MD_NAVIGATION).c_str())) {
1389 if (ImGui::BeginTable("NavigationTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1390 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1391 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1392 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1393 ImGui::TableHeadersRow();
1394
1395 // Window colors
1396 auto window_colors = std::vector<std::tuple<const char*, Color*, const char*>>{
1397 {"Window Background", &edit_theme.window_bg, "Main window background"},
1398 {"Child Background", &edit_theme.child_bg, "Child window background"},
1399 {"Popup Background", &edit_theme.popup_bg, "Popup window background"},
1400 {"Modal Background", &edit_theme.modal_bg, "Modal window background"},
1401 {"Menu Bar BG", &edit_theme.menu_bar_bg, "Menu bar background"}
1402 };
1403
1404 for (auto& [label, color_ptr, description] : window_colors) {
1405 ImGui::TableNextRow();
1406 ImGui::TableNextColumn();
1407 ImGui::AlignTextToFramePadding();
1408 ImGui::Text("%s:", label);
1409
1410 ImGui::TableNextColumn();
1411 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1412 std::string id = absl::StrFormat("##window_%s", label);
1413 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
1414 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1415 apply_live_preview();
1416 }
1417
1418 ImGui::TableNextColumn();
1419 ImGui::TextWrapped("%s", description);
1420 }
1421
1422 ImGui::EndTable();
1423 }
1424
1425 ImGui::Separator();
1426
1427 // Header and Tab colors
1428 if (ImGui::CollapsingHeader("Headers & Tabs", ImGuiTreeNodeFlags_DefaultOpen)) {
1429 if (ImGui::BeginTable("HeaderTabTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1430 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1431 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1432 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1433 ImGui::TableHeadersRow();
1434
1435 auto header_tab_colors = std::vector<std::pair<const char*, Color*>>{
1436 {"Header", &edit_theme.header},
1437 {"Header Hovered", &edit_theme.header_hovered},
1438 {"Header Active", &edit_theme.header_active},
1439 {"Tab", &edit_theme.tab},
1440 {"Tab Hovered", &edit_theme.tab_hovered},
1441 {"Tab Active", &edit_theme.tab_active},
1442 {"Tab Unfocused", &edit_theme.tab_unfocused},
1443 {"Tab Unfocused Active", &edit_theme.tab_unfocused_active},
1444 {"Tab Dimmed", &edit_theme.tab_dimmed},
1445 {"Tab Dimmed Selected", &edit_theme.tab_dimmed_selected},
1446 {"Title Background", &edit_theme.title_bg},
1447 {"Title BG Active", &edit_theme.title_bg_active},
1448 {"Title BG Collapsed", &edit_theme.title_bg_collapsed}
1449 };
1450
1451 for (auto& [label, color_ptr] : header_tab_colors) {
1452 ImGui::TableNextRow();
1453 ImGui::TableNextColumn();
1454 ImGui::AlignTextToFramePadding();
1455 ImGui::Text("%s:", label);
1456
1457 ImGui::TableNextColumn();
1458 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1459 std::string id = absl::StrFormat("##header_%s", label);
1460 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
1461 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1462 apply_live_preview();
1463 }
1464
1465 ImGui::TableNextColumn();
1466 ImGui::PushStyleColor(ImGuiCol_Button, color_vec);
1467 ImGui::Button(absl::StrFormat("Preview %s", label).c_str(), ImVec2(-1, 25));
1468 ImGui::PopStyleColor();
1469 }
1470
1471 ImGui::EndTable();
1472 }
1473 }
1474
1475 // Navigation and Special Elements
1476 if (ImGui::CollapsingHeader("Navigation & Special")) {
1477 if (ImGui::BeginTable("NavSpecialTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1478 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1479 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1480 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1481 ImGui::TableHeadersRow();
1482
1483 auto nav_special_colors = std::vector<std::tuple<const char*, Color*, const char*>>{
1484 {"Nav Cursor", &edit_theme.nav_cursor, "Navigation cursor color"},
1485 {"Nav Win Highlight", &edit_theme.nav_windowing_highlight, "Window selection highlight"},
1486 {"Nav Win Dim BG", &edit_theme.nav_windowing_dim_bg, "Background dimming for navigation"},
1487 {"Modal Win Dim BG", &edit_theme.modal_window_dim_bg, "Background dimming for modals"},
1488 {"Drag Drop Target", &edit_theme.drag_drop_target, "Drag and drop target highlight"},
1489 {"Docking Preview", &edit_theme.docking_preview, "Docking area preview"},
1490 {"Docking Empty BG", &edit_theme.docking_empty_bg, "Empty docking space background"}
1491 };
1492
1493 for (auto& [label, color_ptr, description] : nav_special_colors) {
1494 ImGui::TableNextRow();
1495 ImGui::TableNextColumn();
1496 ImGui::AlignTextToFramePadding();
1497 ImGui::Text("%s:", label);
1498
1499 ImGui::TableNextColumn();
1500 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1501 std::string id = absl::StrFormat("##nav_%s", label);
1502 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
1503 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1504 apply_live_preview();
1505 }
1506
1507 ImGui::TableNextColumn();
1508 ImGui::TextWrapped("%s", description);
1509 }
1510
1511 ImGui::EndTable();
1512 }
1513 }
1514
1515 ImGui::EndTabItem();
1516 }
1517
1518 // Tables & Data Tab
1519 if (ImGui::BeginTabItem(absl::StrFormat("%s Tables", ICON_MD_TABLE_CHART).c_str())) {
1520 if (ImGui::BeginTable("TablesDataTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1521 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1522 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1523 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1524 ImGui::TableHeadersRow();
1525
1526 auto table_colors = std::vector<std::tuple<const char*, Color*, const char*>>{
1527 {"Table Header BG", &edit_theme.table_header_bg, "Table column headers"},
1528 {"Table Border Strong", &edit_theme.table_border_strong, "Outer table borders"},
1529 {"Table Border Light", &edit_theme.table_border_light, "Inner table borders"},
1530 {"Table Row BG", &edit_theme.table_row_bg, "Normal table rows"},
1531 {"Table Row BG Alt", &edit_theme.table_row_bg_alt, "Alternating table rows"},
1532 {"Tree Lines", &edit_theme.tree_lines, "Tree view connection lines"}
1533 };
1534
1535 for (auto& [label, color_ptr, description] : table_colors) {
1536 ImGui::TableNextRow();
1537 ImGui::TableNextColumn();
1538 ImGui::AlignTextToFramePadding();
1539 ImGui::Text("%s:", label);
1540
1541 ImGui::TableNextColumn();
1542 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1543 std::string id = absl::StrFormat("##table_%s", label);
1544 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
1545 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1546 apply_live_preview();
1547 }
1548
1549 ImGui::TableNextColumn();
1550 ImGui::TextWrapped("%s", description);
1551 }
1552
1553 ImGui::EndTable();
1554 }
1555
1556 ImGui::Separator();
1557
1558 // Plots and Graphs
1559 if (ImGui::CollapsingHeader("Plots & Graphs")) {
1560 if (ImGui::BeginTable("PlotsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1561 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1562 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1563 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1564 ImGui::TableHeadersRow();
1565
1566 auto plot_colors = std::vector<std::tuple<const char*, Color*, const char*>>{
1567 {"Plot Lines", &edit_theme.plot_lines, "Line plot color"},
1568 {"Plot Lines Hovered", &edit_theme.plot_lines_hovered, "Line plot hover color"},
1569 {"Plot Histogram", &edit_theme.plot_histogram, "Histogram fill color"},
1570 {"Plot Histogram Hovered", &edit_theme.plot_histogram_hovered, "Histogram hover color"}
1571 };
1572
1573 for (auto& [label, color_ptr, description] : plot_colors) {
1574 ImGui::TableNextRow();
1575 ImGui::TableNextColumn();
1576 ImGui::AlignTextToFramePadding();
1577 ImGui::Text("%s:", label);
1578
1579 ImGui::TableNextColumn();
1580 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1581 std::string id = absl::StrFormat("##plot_%s", label);
1582 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
1583 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1584 apply_live_preview();
1585 }
1586
1587 ImGui::TableNextColumn();
1588 ImGui::TextWrapped("%s", description);
1589 }
1590
1591 ImGui::EndTable();
1592 }
1593 }
1594
1595 ImGui::EndTabItem();
1596 }
1597
1598 // Borders & Controls Tab
1599 if (ImGui::BeginTabItem(absl::StrFormat("%s Borders", ICON_MD_BORDER_ALL).c_str())) {
1600 if (ImGui::BeginTable("BordersControlsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1601 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1602 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1603 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1604 ImGui::TableHeadersRow();
1605
1606 auto border_control_colors = std::vector<std::tuple<const char*, Color*, const char*>>{
1607 {"Border", &edit_theme.border, "General border color"},
1608 {"Border Shadow", &edit_theme.border_shadow, "Border shadow/depth"},
1609 {"Separator", &edit_theme.separator, "Horizontal/vertical separators"},
1610 {"Separator Hovered", &edit_theme.separator_hovered, "Separator hover state"},
1611 {"Separator Active", &edit_theme.separator_active, "Separator active/dragged state"},
1612 {"Scrollbar BG", &edit_theme.scrollbar_bg, "Scrollbar track background"},
1613 {"Scrollbar Grab", &edit_theme.scrollbar_grab, "Scrollbar handle"},
1614 {"Scrollbar Grab Hovered", &edit_theme.scrollbar_grab_hovered, "Scrollbar handle hover"},
1615 {"Scrollbar Grab Active", &edit_theme.scrollbar_grab_active, "Scrollbar handle active"},
1616 {"Resize Grip", &edit_theme.resize_grip, "Window resize grip"},
1617 {"Resize Grip Hovered", &edit_theme.resize_grip_hovered, "Resize grip hover"},
1618 {"Resize Grip Active", &edit_theme.resize_grip_active, "Resize grip active"}
1619 };
1620
1621 for (auto& [label, color_ptr, description] : border_control_colors) {
1622 ImGui::TableNextRow();
1623 ImGui::TableNextColumn();
1624 ImGui::AlignTextToFramePadding();
1625 ImGui::Text("%s:", label);
1626
1627 ImGui::TableNextColumn();
1628 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1629 std::string id = absl::StrFormat("##border_%s", label);
1630 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
1631 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1632 apply_live_preview();
1633 }
1634
1635 ImGui::TableNextColumn();
1636 ImGui::TextWrapped("%s", description);
1637 }
1638
1639 ImGui::EndTable();
1640 }
1641
1642 ImGui::EndTabItem();
1643 }
1644
1645 // Enhanced Colors Tab
1646 if (ImGui::BeginTabItem(absl::StrFormat("%s Enhanced", ICON_MD_AUTO_AWESOME).c_str())) {
1647 ImGui::Text("Enhanced semantic colors and editor-specific customization");
1648 ImGui::Separator();
1649
1650 // Enhanced semantic colors section
1651 if (ImGui::CollapsingHeader("Enhanced Semantic Colors", ImGuiTreeNodeFlags_DefaultOpen)) {
1652 if (ImGui::BeginTable("EnhancedSemanticTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1653 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1654 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1655 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1656 ImGui::TableHeadersRow();
1657
1658 auto enhanced_colors = std::vector<std::tuple<const char*, Color*, const char*>>{
1659 {"Code Background", &edit_theme.code_background, "Code blocks background"},
1660 {"Success Light", &edit_theme.success_light, "Light success variant"},
1661 {"Warning Light", &edit_theme.warning_light, "Light warning variant"},
1662 {"Error Light", &edit_theme.error_light, "Light error variant"},
1663 {"Info Light", &edit_theme.info_light, "Light info variant"}
1664 };
1665
1666 for (auto& [label, color_ptr, description] : enhanced_colors) {
1667 ImGui::TableNextRow();
1668 ImGui::TableNextColumn();
1669 ImGui::AlignTextToFramePadding();
1670 ImGui::Text("%s:", label);
1671
1672 ImGui::TableNextColumn();
1673 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1674 std::string id = absl::StrFormat("##enhanced_%s", label);
1675 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
1676 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1677 apply_live_preview();
1678 }
1679
1680 ImGui::TableNextColumn();
1681 ImGui::TextWrapped("%s", description);
1682 }
1683
1684 ImGui::EndTable();
1685 }
1686 }
1687
1688 // UI State colors section
1689 if (ImGui::CollapsingHeader("UI State Colors")) {
1690 if (ImGui::BeginTable("UIStateTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1691 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1692 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1693 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1694 ImGui::TableHeadersRow();
1695
1696 // UI state colors with alpha support where needed
1697 ImGui::TableNextRow();
1698 ImGui::TableNextColumn();
1699 ImGui::AlignTextToFramePadding();
1700 ImGui::Text("Active Selection:");
1701 ImGui::TableNextColumn();
1702 ImVec4 active_selection = ConvertColorToImVec4(edit_theme.active_selection);
1703 if (ImGui::ColorEdit4("##active_selection", &active_selection.x)) {
1704 edit_theme.active_selection = {active_selection.x, active_selection.y, active_selection.z, active_selection.w};
1705 apply_live_preview();
1706 }
1707 ImGui::TableNextColumn();
1708 ImGui::TextWrapped("Active/selected UI elements");
1709
1710 ImGui::TableNextRow();
1711 ImGui::TableNextColumn();
1712 ImGui::AlignTextToFramePadding();
1713 ImGui::Text("Hover Highlight:");
1714 ImGui::TableNextColumn();
1715 ImVec4 hover_highlight = ConvertColorToImVec4(edit_theme.hover_highlight);
1716 if (ImGui::ColorEdit4("##hover_highlight", &hover_highlight.x)) {
1717 edit_theme.hover_highlight = {hover_highlight.x, hover_highlight.y, hover_highlight.z, hover_highlight.w};
1718 apply_live_preview();
1719 }
1720 ImGui::TableNextColumn();
1721 ImGui::TextWrapped("General hover state highlighting");
1722
1723 ImGui::TableNextRow();
1724 ImGui::TableNextColumn();
1725 ImGui::AlignTextToFramePadding();
1726 ImGui::Text("Focus Border:");
1727 ImGui::TableNextColumn();
1728 ImVec4 focus_border = ConvertColorToImVec4(edit_theme.focus_border);
1729 if (ImGui::ColorEdit3("##focus_border", &focus_border.x)) {
1730 edit_theme.focus_border = {focus_border.x, focus_border.y, focus_border.z, focus_border.w};
1731 apply_live_preview();
1732 }
1733 ImGui::TableNextColumn();
1734 ImGui::TextWrapped("Border for focused input elements");
1735
1736 ImGui::TableNextRow();
1737 ImGui::TableNextColumn();
1738 ImGui::AlignTextToFramePadding();
1739 ImGui::Text("Disabled Overlay:");
1740 ImGui::TableNextColumn();
1741 ImVec4 disabled_overlay = ConvertColorToImVec4(edit_theme.disabled_overlay);
1742 if (ImGui::ColorEdit4("##disabled_overlay", &disabled_overlay.x)) {
1743 edit_theme.disabled_overlay = {disabled_overlay.x, disabled_overlay.y, disabled_overlay.z, disabled_overlay.w};
1744 apply_live_preview();
1745 }
1746 ImGui::TableNextColumn();
1747 ImGui::TextWrapped("Semi-transparent overlay for disabled elements");
1748
1749 ImGui::EndTable();
1750 }
1751 }
1752
1753 // Editor-specific colors section
1754 if (ImGui::CollapsingHeader("Editor-Specific Colors")) {
1755 if (ImGui::BeginTable("EditorColorsTable", 3, ImGuiTableFlags_SizingStretchProp)) {
1756 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed, 120.0f);
1757 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch, 0.6f);
1758 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
1759 ImGui::TableHeadersRow();
1760
1761 auto editor_colors = std::vector<std::tuple<const char*, Color*, const char*, bool>>{
1762 {"Editor Background", &edit_theme.editor_background, "Main editor canvas background", false},
1763 {"Editor Grid", &edit_theme.editor_grid, "Grid lines in map/graphics editors", true},
1764 {"Editor Cursor", &edit_theme.editor_cursor, "Cursor color in editors", false},
1765 {"Editor Selection", &edit_theme.editor_selection, "Selection highlight in editors", true}
1766 };
1767
1768 for (auto& [label, color_ptr, description, use_alpha] : editor_colors) {
1769 ImGui::TableNextRow();
1770 ImGui::TableNextColumn();
1771 ImGui::AlignTextToFramePadding();
1772 ImGui::Text("%s:", label);
1773
1774 ImGui::TableNextColumn();
1775 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
1776 std::string id = absl::StrFormat("##editor_%s", label);
1777 if (use_alpha ? ImGui::ColorEdit4(id.c_str(), &color_vec.x) : ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
1778 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
1779 apply_live_preview();
1780 }
1781
1782 ImGui::TableNextColumn();
1783 ImGui::TextWrapped("%s", description);
1784 }
1785
1786 ImGui::EndTable();
1787 }
1788 }
1789
1790 ImGui::EndTabItem();
1791 }
1792
1793 ImGui::EndTabBar();
1794 }
1795
1796 ImGui::Separator();
1797
1798 if (ImGui::Button("Preview Theme")) {
1799 ApplyTheme(edit_theme);
1800 }
1801
1802 ImGui::SameLine();
1803 if (ImGui::Button("Reset to Current")) {
1804 edit_theme = current_theme_;
1805 // Safe string copy with bounds checking
1806 size_t name_len = std::min(current_theme_.name.length(), sizeof(theme_name) - 1);
1807 std::memcpy(theme_name, current_theme_.name.c_str(), name_len);
1808 theme_name[name_len] = '\0';
1809
1810 size_t desc_len = std::min(current_theme_.description.length(), sizeof(theme_description) - 1);
1811 std::memcpy(theme_description, current_theme_.description.c_str(), desc_len);
1812 theme_description[desc_len] = '\0';
1813
1814 size_t author_len = std::min(current_theme_.author.length(), sizeof(theme_author) - 1);
1815 std::memcpy(theme_author, current_theme_.author.c_str(), author_len);
1816 theme_author[author_len] = '\0';
1817
1818 // Reset backup state since we're back to current theme
1819 if (theme_backup_made) {
1820 theme_backup_made = false;
1821 current_theme_.ApplyToImGui(); // Apply current theme to clear any preview changes
1822 }
1823 }
1824
1825 ImGui::SameLine();
1826 if (ImGui::Button("Save Theme")) {
1827 edit_theme.name = std::string(theme_name);
1828 edit_theme.description = std::string(theme_description);
1829 edit_theme.author = std::string(theme_author);
1830
1831 // Add to themes map and apply
1832 themes_[edit_theme.name] = edit_theme;
1833 ApplyTheme(edit_theme);
1834
1835 // Reset backup state since theme is now applied
1836 theme_backup_made = false;
1837 }
1838
1839 ImGui::SameLine();
1840
1841 // Save Over Current button - overwrites the current theme file
1842 std::string current_file_path = GetCurrentThemeFilePath();
1843 bool can_save_over = !current_file_path.empty();
1844
1845 if (!can_save_over) {
1846 ImGui::BeginDisabled();
1847 }
1848
1849 if (ImGui::Button("Save Over Current")) {
1850 edit_theme.name = std::string(theme_name);
1851 edit_theme.description = std::string(theme_description);
1852 edit_theme.author = std::string(theme_author);
1853
1854 auto status = SaveThemeToFile(edit_theme, current_file_path);
1855 if (status.ok()) {
1856 // Update themes map and apply
1857 themes_[edit_theme.name] = edit_theme;
1858 ApplyTheme(edit_theme);
1859 theme_backup_made = false; // Reset backup state since theme is now applied
1860 } else {
1861 LOG_ERROR("Theme Manager", "Failed to save over current theme");
1862 }
1863 }
1864
1865 if (!can_save_over) {
1866 ImGui::EndDisabled();
1867 }
1868
1869 if (ImGui::IsItemHovered() && can_save_over) {
1870 ImGui::BeginTooltip();
1871 ImGui::Text("Save over current theme file:");
1872 ImGui::Text("%s", current_file_path.c_str());
1873 ImGui::EndTooltip();
1874 } else if (ImGui::IsItemHovered()) {
1875 ImGui::BeginTooltip();
1876 ImGui::Text("No current theme file to overwrite");
1877 ImGui::Text("Use 'Save to File...' to create a new theme file");
1878 ImGui::EndTooltip();
1879 }
1880
1881 ImGui::SameLine();
1882 if (ImGui::Button("Save to File...")) {
1883 edit_theme.name = std::string(theme_name);
1884 edit_theme.description = std::string(theme_description);
1885 edit_theme.author = std::string(theme_author);
1886
1887 // Use save file dialog with proper defaults
1888 std::string safe_name = edit_theme.name.empty() ? "custom_theme" : edit_theme.name;
1889 auto file_path = util::FileDialogWrapper::ShowSaveFileDialog(safe_name, "theme");
1890
1891 if (!file_path.empty()) {
1892 // Ensure .theme extension
1893 if (file_path.find(".theme") == std::string::npos) {
1894 file_path += ".theme";
1895 }
1896
1897 auto status = SaveThemeToFile(edit_theme, file_path);
1898 if (status.ok()) {
1899 // Also add to themes map for immediate use
1900 themes_[edit_theme.name] = edit_theme;
1901 ApplyTheme(edit_theme);
1902 } else {
1903 LOG_ERROR("Theme Manager", "Failed to save theme");
1904 }
1905 }
1906 }
1907
1908 if (ImGui::IsItemHovered()) {
1909 ImGui::BeginTooltip();
1910 ImGui::Text("Save theme to a .theme file");
1911 ImGui::Text("Saved themes can be shared and loaded later");
1912 ImGui::EndTooltip();
1913 }
1914 }
1915 ImGui::End();
1916}
1917
1918std::vector<std::string> ThemeManager::GetThemeSearchPaths() const {
1919 std::vector<std::string> search_paths;
1920
1921 // Development path (relative to build directory)
1922 search_paths.push_back("assets/themes/");
1923 search_paths.push_back("../assets/themes/");
1924
1925 // Platform-specific resource paths
1926#ifdef __APPLE__
1927 // macOS bundle resource path (this should be the primary path for bundled apps)
1928 std::string bundle_themes = util::GetResourcePath("assets/themes/");
1929 if (!bundle_themes.empty()) {
1930 search_paths.push_back(bundle_themes);
1931 }
1932
1933 // Alternative bundle locations
1934 std::string bundle_root = util::GetBundleResourcePath();
1935
1936 search_paths.push_back(bundle_root + "Contents/Resources/themes/");
1937 search_paths.push_back(bundle_root + "Contents/Resources/assets/themes/");
1938 search_paths.push_back(bundle_root + "assets/themes/");
1939 search_paths.push_back(bundle_root + "themes/");
1940#else
1941 // Linux/Windows relative paths
1942 search_paths.push_back("./assets/themes/");
1943 search_paths.push_back("./themes/");
1944#endif
1945
1946 // User config directory
1947 auto config_dir = util::PlatformPaths::GetConfigDirectory();
1948 if (config_dir.ok()) {
1949 search_paths.push_back((*config_dir / "themes/").string());
1950 }
1951
1952 return search_paths;
1953}
1954
1956 auto search_paths = GetThemeSearchPaths();
1957
1958 // Try each search path and return the first one that exists
1959 for (const auto& path : search_paths) {
1960 std::ifstream test_file(path + "."); // Test if directory exists by trying to access it
1961 if (test_file.good()) {
1962 return path;
1963 }
1964
1965 // Also try with platform-specific directory separators
1966 std::string normalized_path = path;
1967 if (!normalized_path.empty() && normalized_path.back() != '/' && normalized_path.back() != '\\') {
1968 normalized_path += "/";
1969 }
1970
1971 std::ifstream test_file2(normalized_path + ".");
1972 if (test_file2.good()) {
1973 return normalized_path;
1974 }
1975 }
1976
1977 return search_paths.empty() ? "assets/themes/" : search_paths[0];
1978}
1979
1980std::vector<std::string> ThemeManager::DiscoverAvailableThemeFiles() const {
1981 std::vector<std::string> theme_files;
1982 auto search_paths = GetThemeSearchPaths();
1983
1984 for (const auto& search_path : search_paths) {
1985
1986 try {
1987 // Use platform-specific file discovery instead of glob
1988#ifdef __APPLE__
1989 auto files_in_folder = util::FileDialogWrapper::GetFilesInFolder(search_path);
1990 for (const auto& file : files_in_folder) {
1991 if (file.length() > 6 && file.substr(file.length() - 6) == ".theme") {
1992 std::string full_path = search_path + file;
1993 theme_files.push_back(full_path);
1994 }
1995 }
1996#else
1997 // For Linux/Windows, use filesystem directory iteration
1998 // (could be extended with platform-specific implementations if needed)
1999 std::vector<std::string> known_themes = {
2000 "yaze_tre.theme", "cyberpunk.theme", "sunset.theme",
2001 "forest.theme", "midnight.theme"
2002 };
2003
2004 for (const auto& theme_name : known_themes) {
2005 std::string full_path = search_path + theme_name;
2006 std::ifstream test_file(full_path);
2007 if (test_file.good()) {
2008 theme_files.push_back(full_path);
2009 }
2010 }
2011#endif
2012 } catch (const std::exception& e) {
2013 LOG_ERROR("Theme Manager", "Error scanning directory %s", search_path.c_str());
2014 }
2015 }
2016
2017 // Remove duplicates while preserving order
2018 std::vector<std::string> unique_files;
2019 std::set<std::string> seen_basenames;
2020
2021 for (const auto& file : theme_files) {
2022 std::string basename = util::GetFileName(file);
2023 if (seen_basenames.find(basename) == seen_basenames.end()) {
2024 unique_files.push_back(file);
2025 seen_basenames.insert(basename);
2026 }
2027 }
2028
2029 return unique_files;
2030}
2031
2033 auto theme_files = DiscoverAvailableThemeFiles();
2034
2035 int successful_loads = 0;
2036 int failed_loads = 0;
2037
2038 for (const auto& theme_file : theme_files) {
2039 auto status = LoadThemeFromFile(theme_file);
2040 if (status.ok()) {
2041 successful_loads++;
2042 } else {
2043 failed_loads++;
2044 }
2045 }
2046
2047
2048 if (successful_loads == 0 && failed_loads > 0) {
2049 return absl::InternalError(absl::StrFormat("Failed to load any themes (%d failures)", failed_loads));
2050 }
2051
2052 return absl::OkStatus();
2053}
2054
2058
2060 if (current_theme_name_ == "Classic YAZE") {
2061 return ""; // Classic theme doesn't have a file
2062 }
2063
2064 // Try to find the current theme file in the search paths
2065 auto search_paths = GetThemeSearchPaths();
2066 std::string theme_filename = current_theme_name_ + ".theme";
2067
2068 // Convert theme name to safe filename (replace spaces and special chars)
2069 for (char& c : theme_filename) {
2070 if (!std::isalnum(c) && c != '.' && c != '_') {
2071 c = '_';
2072 }
2073 }
2074
2075 for (const auto& search_path : search_paths) {
2076 std::string full_path = search_path + theme_filename;
2077 std::ifstream test_file(full_path);
2078 if (test_file.good()) {
2079 return full_path;
2080 }
2081 }
2082
2083 // If not found, return path in the first search directory (for new saves)
2084 return search_paths.empty() ? theme_filename : search_paths[0] + theme_filename;
2085}
2086
2087} // namespace gui
2088} // namespace yaze
Manages themes, loading, saving, and switching.
Color ParseColorFromString(const std::string &color_str) const
const EnhancedTheme * GetTheme(const std::string &name) const
absl::Status ParseThemeFile(const std::string &content, EnhancedTheme &theme)
Color GetWelcomeScreenBackground() const
std::string GetCurrentThemeFilePath() const
EnhancedTheme current_theme_
absl::Status LoadTheme(const std::string &theme_name)
absl::Status LoadThemeFromFile(const std::string &filepath)
void ApplyTheme(const std::string &theme_name)
std::string GetThemesDirectory() const
std::string current_theme_name_
Color GetWelcomeScreenAccent() const
std::vector< std::string > DiscoverAvailableThemeFiles() const
absl::Status LoadAllAvailableThemes()
std::vector< std::string > GetThemeSearchPaths() const
std::string SerializeTheme(const EnhancedTheme &theme) const
static ThemeManager & Get()
absl::Status SaveThemeToFile(const EnhancedTheme &theme, const std::string &filepath) const
void ShowThemeSelector(bool *p_open)
absl::Status RefreshAvailableThemes()
const EnhancedTheme & GetCurrentTheme() const
Color GetWelcomeScreenBorder() const
void ShowSimpleThemeEditor(bool *p_open)
std::vector< std::string > GetAvailableThemes() const
std::map< std::string, EnhancedTheme > themes_
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static std::vector< std::string > GetFilesInFolder(const std::string &folder_path)
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
#define ICON_MD_FOLDER_OPEN
Definition icons.h:811
#define ICON_MD_STAR
Definition icons.h:1846
#define ICON_MD_CHECK
Definition icons.h:395
#define ICON_MD_SAVE_AS
Definition icons.h:1644
#define ICON_MD_CIRCLE
Definition icons.h:409
#define ICON_MD_TEXT_FIELDS
Definition icons.h:1952
#define ICON_MD_TUNE
Definition icons.h:2020
#define ICON_MD_REFRESH
Definition icons.h:1570
#define ICON_MD_TABLE_CHART
Definition icons.h:1931
#define ICON_MD_AUTO_AWESOME
Definition icons.h:212
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_ADD
Definition icons.h:84
#define ICON_MD_BORDER_ALL
Definition icons.h:290
#define ICON_MD_TOUCH_APP
Definition icons.h:1998
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_PALETTE
Definition icons.h:1368
#define ICON_MD_COLOR_LENS
Definition icons.h:438
#define ICON_MD_NAVIGATION
Definition icons.h:1274
#define LOG_ERROR(category, format,...)
Definition log.h:110
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:21
Color RGBA(int r, int g, int b, int a=255)
void ColorsYaze()
Definition style.cc:32
Color RGB(float r, float g, float b, float a=1.0f)
std::string GetFileName(const std::string &filename)
Gets the filename from a full path.
Definition file_util.cc:19
std::string GetResourcePath(const std::string &resource_path)
Definition file_util.cc:70
std::string GetBundleResourcePath()
GetBundleResourcePath returns the path to the bundle resource directory. Specific to MacOS.
Main namespace for the application.
Definition controller.cc:20
float alpha
Definition color.h:18
float green
Definition color.h:16
Comprehensive theme structure for YAZE.