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