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