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 <filesystem>
7#include <fstream>
8#include <set>
9#include <sstream>
10
11#include "absl/strings/str_format.h"
12#include "absl/strings/str_split.h"
14#include "app/gui/core/icons.h"
15#include "app/gui/core/style.h" // For ColorsYaze function
16#include "imgui/imgui.h"
17#include "nlohmann/json.hpp"
18#include "util/file_util.h"
19#include "util/log.h"
20#include "util/platform_paths.h"
21
22namespace yaze {
23namespace gui {
24
25// Helper function to create Color from RGB values
26Color RGB(float r, float g, float b, float a = 1.0f) {
27 return {r / 255.0f, g / 255.0f, b / 255.0f, a};
28}
29
30Color RGBA(int r, int g, int b, int a = 255) {
31 return {r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f};
32}
33
34// Theme Implementation
35void Theme::ApplyToImGui() const {
36 ImGuiStyle* style = &ImGui::GetStyle();
37 ImVec4* colors = style->Colors;
38
39 // Apply colors
40 colors[ImGuiCol_Text] = ConvertColorToImVec4(text_primary);
41 colors[ImGuiCol_TextDisabled] = ConvertColorToImVec4(text_disabled);
42 colors[ImGuiCol_WindowBg] = ConvertColorToImVec4(window_bg);
43 colors[ImGuiCol_ChildBg] = ConvertColorToImVec4(child_bg);
44 colors[ImGuiCol_PopupBg] = ConvertColorToImVec4(popup_bg);
45 colors[ImGuiCol_Border] = ConvertColorToImVec4(border);
46 colors[ImGuiCol_BorderShadow] = ConvertColorToImVec4(border_shadow);
47 colors[ImGuiCol_FrameBg] = ConvertColorToImVec4(frame_bg);
48 colors[ImGuiCol_FrameBgHovered] = ConvertColorToImVec4(frame_bg_hovered);
49 colors[ImGuiCol_FrameBgActive] = ConvertColorToImVec4(frame_bg_active);
50 colors[ImGuiCol_TitleBg] = ConvertColorToImVec4(title_bg);
51 colors[ImGuiCol_TitleBgActive] = ConvertColorToImVec4(title_bg_active);
52 colors[ImGuiCol_TitleBgCollapsed] = ConvertColorToImVec4(title_bg_collapsed);
53 colors[ImGuiCol_MenuBarBg] = ConvertColorToImVec4(menu_bar_bg);
54 colors[ImGuiCol_ScrollbarBg] = ConvertColorToImVec4(scrollbar_bg);
55 colors[ImGuiCol_ScrollbarGrab] = ConvertColorToImVec4(scrollbar_grab);
56 colors[ImGuiCol_ScrollbarGrabHovered] =
58 colors[ImGuiCol_ScrollbarGrabActive] =
60 colors[ImGuiCol_Button] = ConvertColorToImVec4(button);
61 colors[ImGuiCol_ButtonHovered] = ConvertColorToImVec4(button_hovered);
62 colors[ImGuiCol_ButtonActive] = ConvertColorToImVec4(button_active);
63 colors[ImGuiCol_Header] = ConvertColorToImVec4(header);
64 colors[ImGuiCol_HeaderHovered] = ConvertColorToImVec4(header_hovered);
65 colors[ImGuiCol_HeaderActive] = ConvertColorToImVec4(header_active);
66 colors[ImGuiCol_Separator] = ConvertColorToImVec4(separator);
67 colors[ImGuiCol_SeparatorHovered] = ConvertColorToImVec4(separator_hovered);
68 colors[ImGuiCol_SeparatorActive] = ConvertColorToImVec4(separator_active);
69 colors[ImGuiCol_ResizeGrip] = ConvertColorToImVec4(resize_grip);
70 colors[ImGuiCol_ResizeGripHovered] =
72 colors[ImGuiCol_ResizeGripActive] = ConvertColorToImVec4(resize_grip_active);
73 colors[ImGuiCol_Tab] = ConvertColorToImVec4(tab);
74 colors[ImGuiCol_TabHovered] = ConvertColorToImVec4(tab_hovered);
75 colors[ImGuiCol_TabSelected] = ConvertColorToImVec4(tab_active);
76 colors[ImGuiCol_TabUnfocused] = ConvertColorToImVec4(tab_unfocused);
77 colors[ImGuiCol_TabUnfocusedActive] =
79 colors[ImGuiCol_DockingPreview] = ConvertColorToImVec4(docking_preview);
80 colors[ImGuiCol_DockingEmptyBg] = ConvertColorToImVec4(docking_empty_bg);
81
82 // Complete ImGui color support
83 colors[ImGuiCol_CheckMark] = ConvertColorToImVec4(check_mark);
84 colors[ImGuiCol_SliderGrab] = ConvertColorToImVec4(slider_grab);
85 colors[ImGuiCol_SliderGrabActive] = ConvertColorToImVec4(slider_grab_active);
86 colors[ImGuiCol_InputTextCursor] = ConvertColorToImVec4(input_text_cursor);
87 colors[ImGuiCol_NavCursor] = ConvertColorToImVec4(nav_cursor);
88 colors[ImGuiCol_NavWindowingHighlight] =
90 colors[ImGuiCol_NavWindowingDimBg] =
92 colors[ImGuiCol_ModalWindowDimBg] = ConvertColorToImVec4(modal_window_dim_bg);
93 colors[ImGuiCol_TextSelectedBg] = ConvertColorToImVec4(text_selected_bg);
94 colors[ImGuiCol_DragDropTarget] = ConvertColorToImVec4(drag_drop_target);
95 colors[ImGuiCol_TableHeaderBg] = ConvertColorToImVec4(table_header_bg);
96 colors[ImGuiCol_TableBorderStrong] =
98 colors[ImGuiCol_TableBorderLight] = ConvertColorToImVec4(table_border_light);
99 colors[ImGuiCol_TableRowBg] = ConvertColorToImVec4(table_row_bg);
100 colors[ImGuiCol_TableRowBgAlt] = ConvertColorToImVec4(table_row_bg_alt);
101 colors[ImGuiCol_TextLink] = ConvertColorToImVec4(text_link);
102 colors[ImGuiCol_PlotLines] = ConvertColorToImVec4(plot_lines);
103 colors[ImGuiCol_PlotLinesHovered] = ConvertColorToImVec4(plot_lines_hovered);
104 colors[ImGuiCol_PlotHistogram] = ConvertColorToImVec4(plot_histogram);
105 colors[ImGuiCol_PlotHistogramHovered] =
107 colors[ImGuiCol_TreeLines] = ConvertColorToImVec4(tree_lines);
108
109 // Additional ImGui colors for complete coverage
110 colors[ImGuiCol_TabDimmed] = ConvertColorToImVec4(tab_dimmed);
111 colors[ImGuiCol_TabDimmedSelected] =
113 colors[ImGuiCol_TabDimmedSelectedOverline] =
115 colors[ImGuiCol_TabSelectedOverline] =
117
118 // Apply style parameters
119 style->WindowRounding = window_rounding;
120 style->ChildRounding =
121 window_rounding * 0.5f; // Consistent with window rounding
122 style->FrameRounding = frame_rounding;
123 style->PopupRounding = frame_rounding;
124 style->ScrollbarRounding = scrollbar_rounding;
125 style->GrabRounding = grab_rounding;
126 style->TabRounding = tab_rounding;
127 style->WindowBorderSize = window_border_size;
128 style->ChildBorderSize = frame_border_size;
129 style->PopupBorderSize = window_border_size;
130 style->FrameBorderSize = frame_border_size;
131 style->TabBorderSize = frame_border_size;
132
133 // Apply density-based sizing
134 float base_spacing = 8.0f * compact_factor;
135 style->WindowPadding = ImVec2(base_spacing, base_spacing);
136 style->FramePadding = ImVec2(base_spacing * 0.5f, base_spacing * 0.375f);
137 style->CellPadding = ImVec2(base_spacing * 0.5f, base_spacing * 0.25f);
138 style->ItemSpacing = ImVec2(base_spacing, base_spacing * 0.5f);
139 style->ItemInnerSpacing = ImVec2(base_spacing * 0.5f, base_spacing * 0.5f);
140 style->IndentSpacing = base_spacing * 2.5f;
141 style->ScrollbarSize = 14.0f * compact_factor;
142 style->GrabMinSize = 12.0f * compact_factor;
143}
144
146 density_preset = preset;
147
148 switch (preset) {
150 compact_factor = 0.75f;
152 spacing_multiplier = 0.7f;
158 break;
159
161 compact_factor = 1.0f;
163 spacing_multiplier = 1.0f;
169 break;
170
172 compact_factor = 1.25f;
174 spacing_multiplier = 1.3f;
180 break;
181 }
182}
183
184// ThemeManager Implementation
186 static ThemeManager instance;
187 return instance;
188}
189
191 // Always create fallback theme first
193
194 // Create the Classic YAZE theme during initialization
196
197 // Load all available theme files dynamically
198 auto status = LoadAllAvailableThemes();
199 if (!status.ok()) {
200 LOG_ERROR("Theme Manager", "Failed to load some theme files");
201 }
202
203 // Ensure we have a valid current theme (Classic is already set above)
204 // Only fallback to file themes if Classic creation failed
205 if (current_theme_name_ != "Classic YAZE") {
206 if (themes_.find("YAZE Tre") != themes_.end()) {
207 current_theme_ = themes_["YAZE Tre"];
208 current_theme_name_ = "YAZE Tre";
209 }
210 }
211}
212
214 // Fallback theme that matches the original ColorsYaze() function colors but
215 // in theme format
216 Theme theme;
217 theme.name = "YAZE Tre";
218 theme.description = "YAZE theme resource edition";
219 theme.author = "YAZE Team";
220
221 // Use the exact original ColorsYaze colors
222 theme.primary = RGBA(92, 115, 92); // allttpLightGreen
223 theme.secondary = RGBA(71, 92, 71); // alttpMidGreen
224 theme.accent = RGBA(89, 119, 89); // TabActive
225 theme.background =
226 RGBA(8, 8, 8); // Very dark gray for better grid visibility
227
228 theme.text_primary = RGBA(230, 230, 230); // 0.90f, 0.90f, 0.90f
229 theme.text_disabled = RGBA(153, 153, 153); // 0.60f, 0.60f, 0.60f
230 theme.window_bg = RGBA(8, 8, 8, 217); // Very dark gray with same alpha
231 theme.child_bg = RGBA(0, 0, 0, 0); // Transparent
232 theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f
233
234 theme.button = RGBA(71, 92, 71); // alttpMidGreen
235 theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen
236 theme.button_active = RGBA(92, 115, 92); // allttpLightGreen
237
238 theme.header = RGBA(46, 66, 46); // alttpDarkGreen
239 theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen
240 theme.header_active = RGBA(71, 92, 71); // alttpMidGreen
241
242 theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen
243 theme.tab = RGBA(46, 66, 46); // alttpDarkGreen
244 theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen
245 theme.tab_active = RGBA(89, 119, 89); // TabActive
246 theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab
248 RGBA(62, 83, 62); // Darker version of tab_active
249
250 // Complete all remaining ImGui colors from original ColorsYaze() function
251 theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen
252 theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen
253 theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen
254
255 // Initialize missing fields that were added to the struct
256 theme.surface = theme.background;
257 theme.error = RGBA(220, 50, 50);
258 theme.warning = RGBA(255, 200, 50);
259 theme.success = theme.primary;
260 theme.info = RGBA(70, 170, 255);
261 theme.text_secondary = RGBA(200, 200, 200);
262 theme.modal_bg = theme.popup_bg;
263
264 // Borders and separators
265 theme.border = RGBA(92, 115, 92); // allttpLightGreen
266 theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent
267 theme.separator = RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f
268 theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f
269 theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f
270
271 // Scrollbars
272 theme.scrollbar_bg = RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
273 theme.scrollbar_grab = RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f
275 RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f
277 RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
278
279 // Resize grips (from original - light blue highlights)
280 theme.resize_grip = RGBA(255, 255, 255, 26); // 1.00f, 1.00f, 1.00f, 0.10f
281 theme.resize_grip_hovered =
282 RGBA(199, 209, 255, 153); // 0.78f, 0.82f, 1.00f, 0.60f
283 theme.resize_grip_active =
284 RGBA(199, 209, 255, 230); // 0.78f, 0.82f, 1.00f, 0.90f
285
286 // ENHANCED: Complete ImGui colors with theme-aware smart defaults
287 // Use theme colors instead of hardcoded values for consistency
288 theme.check_mark =
289 RGBA(125, 255, 125, 255); // Bright green checkmark (highly visible!)
290 theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid)
291 theme.slider_grab_active =
292 RGBA(125, 146, 125, 255); // Lighter green when active
293 theme.input_text_cursor =
294 RGBA(255, 255, 255, 255); // White cursor (always visible)
295 theme.nav_cursor = RGBA(125, 146, 125, 255); // Light green for navigation
297 RGBA(89, 119, 89, 200); // Accent with high visibility
299 RGBA(0, 0, 0, 150); // Darker overlay for better contrast
300 theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha for modals
301 theme.text_selected_bg = RGBA(
302 92, 115, 92, 128); // Theme green with 50% alpha (visible selection!)
303 theme.drag_drop_target =
304 RGBA(125, 146, 125, 200); // Bright green for drop zones
305
306 // Table colors (from original)
307 theme.table_header_bg = RGBA(46, 66, 46); // alttpDarkGreen
308 theme.table_border_strong = RGBA(71, 92, 71); // alttpMidGreen
309 theme.table_border_light = RGBA(66, 66, 71); // 0.26f, 0.26f, 0.28f
310 theme.table_row_bg = RGBA(0, 0, 0, 0); // Transparent
311 theme.table_row_bg_alt =
312 RGBA(255, 255, 255, 18); // 1.00f, 1.00f, 1.00f, 0.07f
313
314 // Links and plots - use accent colors intelligently
315 theme.text_link = theme.accent; // Accent for links
316 theme.plot_lines = RGBA(255, 255, 255); // White for plots
317 theme.plot_lines_hovered = RGBA(230, 178, 0); // 0.90f, 0.70f, 0.00f
318 theme.plot_histogram = RGBA(230, 178, 0); // Same as above
319 theme.plot_histogram_hovered = RGBA(255, 153, 0); // 1.00f, 0.60f, 0.00f
320
321 // Docking colors
322 theme.docking_preview =
323 RGBA(92, 115, 92, 180); // Light green with transparency
324 theme.docking_empty_bg = RGBA(46, 66, 46, 255); // Dark green
325
326 // Editor-specific colors
327 theme.editor_background = RGBA(30, 45, 30); // Dark green background
328 theme.editor_grid = RGBA(80, 100, 80, 100); // Subtle grid lines
329 theme.editor_cursor = RGBA(255, 255, 255); // White cursor
330 theme.editor_selection =
331 RGBA(110, 145, 110, 100); // Semi-transparent selection
332
333 // Unified selection and interaction colors
334 theme.selection_primary = RGBA(255, 230, 51, 204); // Yellow
335 theme.selection_secondary = RGBA(51, 230, 255, 204); // Cyan
336 theme.selection_hover = RGBA(255, 255, 255, 100); // Semi-transparent white
337 theme.selection_pulsing = RGBA(255, 255, 255, 204); // White pulse
338 theme.selection_handle = RGBA(255, 255, 255, 255); // White handle
339 theme.drag_preview = RGBA(128, 128, 255, 102); // Blueish
340 theme.drag_preview_outline = RGBA(153, 153, 255, 204);
341
342 // Common entity colors
343 theme.entrance_color = RGBA(51, 255, 51, 200); // Green
344 theme.hole_color = RGBA(255, 51, 255, 200); // Magenta
345 theme.exit_color = RGBA(255, 51, 51, 200); // Red
346 theme.item_color = RGBA(255, 214, 0, 200); // Gold
347 theme.sprite_color = RGBA(51, 153, 255, 200); // Blue
348 theme.transport_color = RGBA(153, 102, 255, 200); // Purple
349 theme.music_zone_color = RGBA(255, 153, 51, 200); // Orange
350
351 // Dungeon editor colors
352 theme.dungeon.selection_primary = RGBA(255, 230, 51, 153); // Yellow
353 theme.dungeon.selection_secondary = RGBA(51, 230, 255, 153); // Cyan
354 theme.dungeon.selection_pulsing = RGBA(255, 255, 255, 204); // White pulse
355 theme.dungeon.selection_handle = RGBA(255, 255, 255, 255); // White handle
356 theme.dungeon.drag_preview = RGBA(128, 128, 255, 102); // Blueish
357 theme.dungeon.drag_preview_outline = RGBA(153, 153, 255, 204);
358 theme.dungeon.object_wall = RGBA(153, 153, 153, 255);
359 theme.dungeon.object_floor = RGBA(102, 102, 102, 255);
360 theme.dungeon.object_chest = RGBA(255, 214, 0, 255); // Gold
361 theme.dungeon.object_door = RGBA(140, 69, 18, 255);
362 theme.dungeon.object_pot = RGBA(204, 102, 51, 255);
363 theme.dungeon.object_stairs = RGBA(230, 230, 77, 255);
364 theme.dungeon.object_decoration = RGBA(153, 204, 153, 255);
365 theme.dungeon.object_default = RGBA(204, 204, 204, 255);
366 theme.dungeon.grid_cell_highlight = RGBA(77, 204, 77, 77);
367 theme.dungeon.grid_cell_selected = RGBA(51, 179, 51, 128);
368 theme.dungeon.grid_cell_border = RGBA(102, 102, 102, 128);
369 theme.dungeon.grid_text = RGBA(255, 255, 255, 204);
370 theme.dungeon.room_border = RGBA(128, 128, 128, 255);
371 theme.dungeon.room_border_dark = RGBA(51, 51, 51, 255);
372 theme.dungeon.sprite_layer0 = RGBA(77, 204, 77, 255); // Green
373 theme.dungeon.sprite_layer1 = RGBA(77, 77, 204, 255); // Blue
374 theme.dungeon.sprite_layer2 = RGBA(77, 77, 204, 255);
375 theme.dungeon.outline_layer0 = RGBA(255, 51, 51, 255); // Red
376 theme.dungeon.outline_layer1 = RGBA(51, 255, 51, 255); // Green
377 theme.dungeon.outline_layer2 = RGBA(51, 51, 255, 255); // Blue
378
379 // Chat/agent colors
380 theme.agent.user_message = RGBA(102, 179, 255, 255);
381 theme.agent.agent_message = RGBA(102, 230, 102, 255);
382 theme.agent.system_message = RGBA(179, 179, 179, 255);
383 theme.agent.text_secondary = theme.text_secondary;
384 theme.agent.json_text = RGBA(230, 179, 102, 255);
385 theme.agent.command_text = RGBA(230, 102, 102, 255);
386 theme.agent.code_background = RGBA(26, 26, 31, 255);
387
388 theme.agent.panel_bg = theme.child_bg;
389 theme.agent.panel_bg_darker = RGBA(0, 0, 0, 50);
390 theme.agent.panel_border = theme.border;
391 theme.agent.accent = theme.accent;
392
393 theme.agent.status_active = theme.success;
394 theme.agent.status_inactive = theme.text_disabled;
395 theme.agent.status_success = theme.success;
396 theme.agent.status_warning = theme.warning;
397 theme.agent.status_error = theme.error;
398
399 theme.agent.provider_ollama = RGBA(230, 230, 230, 255);
400 theme.agent.provider_gemini = RGBA(77, 153, 230, 255);
401 theme.agent.provider_mock = RGBA(128, 128, 128, 255);
402 theme.agent.provider_openai =
403 RGBA(51, 204, 153, 255); // Teal/green for OpenAI
404
405 theme.agent.collaboration_active = theme.success;
407
408 theme.agent.proposal_panel_bg = RGBA(38, 38, 46, 255);
409 theme.agent.proposal_accent = RGBA(102, 153, 230, 255);
410 theme.agent.button_copy = RGBA(77, 77, 89, 255);
411 theme.agent.button_copy_hover = RGBA(102, 102, 115, 255);
412 theme.agent.gradient_top = theme.primary;
413 theme.agent.gradient_bottom = theme.secondary;
414
415 // Apply original style settings
416 theme.window_rounding = 0.0f;
417 theme.frame_rounding = 5.0f;
418 theme.scrollbar_rounding = 5.0f;
419 theme.tab_rounding = 0.0f;
420 theme.enable_glow_effects = false;
421
422 themes_["YAZE Tre"] = theme;
423 current_theme_ = theme;
424 current_theme_name_ = "YAZE Tre";
425}
426
427absl::Status ThemeManager::LoadTheme(const std::string& theme_name) {
428 auto it = themes_.find(theme_name);
429 if (it == themes_.end()) {
430 return absl::NotFoundError(
431 absl::StrFormat("Theme '%s' not found", theme_name));
432 }
433
434 current_theme_ = it->second;
435 current_theme_name_ = theme_name;
437 // Keep AgentUI-derived palettes in lockstep with active theme selection.
439 // Cancel any in-progress color transition so the requested theme applies
440 // immediately and doesn't get overridden by UpdateTransition().
441 transitioning_ = false;
443
444 return absl::OkStatus();
445}
446
447absl::Status ThemeManager::LoadThemeFromFile(const std::string& filepath) {
448 // Try multiple possible paths where theme files might be located
449 std::vector<std::string> possible_paths = {
450 filepath, // Absolute path
451 "assets/themes/" + filepath, // Relative from build dir
452 "../assets/themes/" + filepath, // Relative from bin dir
453 util::GetResourcePath("assets/themes/" +
454 filepath), // Platform-specific resource path
455 };
456
457 std::ifstream file;
458 std::string successful_path;
459
460 for (const auto& path : possible_paths) {
461 file.open(path);
462 if (file.is_open()) {
463 successful_path = path;
464 break;
465 } else {
466 file.clear(); // Clear any error flags before trying next path
467 }
468 }
469
470 if (!file.is_open()) {
471 return absl::InvalidArgumentError(
472 absl::StrFormat("Cannot open theme file: %s (tried %zu paths)",
473 filepath, possible_paths.size()));
474 }
475
476 std::string content((std::istreambuf_iterator<char>(file)),
477 std::istreambuf_iterator<char>());
478 file.close();
479
480 if (content.empty()) {
481 return absl::InvalidArgumentError(
482 absl::StrFormat("Theme file is empty: %s", successful_path));
483 }
484
485 Theme theme;
486 auto parse_status = ParseThemeFile(content, theme);
487 if (!parse_status.ok()) {
488 return absl::InvalidArgumentError(
489 absl::StrFormat("Failed to parse theme file %s: %s", successful_path,
490 parse_status.message()));
491 }
492
493 if (theme.name.empty()) {
494 return absl::InvalidArgumentError(
495 absl::StrFormat("Theme file missing name: %s", successful_path));
496 }
497
498 // Fill in any missing properties with smart defaults
499 ApplySmartDefaults(theme);
500
501 themes_[theme.name] = theme;
502 return absl::OkStatus();
503}
504
505std::vector<std::string> ThemeManager::GetAvailableThemes() const {
506 std::vector<std::string> theme_names;
507 for (const auto& [name, theme] : themes_) {
508 theme_names.push_back(name);
509 }
510 return theme_names;
511}
512
513const Theme* ThemeManager::GetTheme(const std::string& name) const {
514 auto it = themes_.find(name);
515 return (it != themes_.end()) ? &it->second : nullptr;
516}
517
518void ThemeManager::ApplyTheme(const std::string& theme_name) {
519 auto status = LoadTheme(theme_name);
520 if (!status.ok()) {
521 // Fallback to YAZE Tre if theme not found
522 auto fallback_status = LoadTheme("YAZE Tre");
523 if (!fallback_status.ok()) {
524 LOG_ERROR("Theme Manager", "Failed to load fallback theme");
525 }
526 }
527}
528
529void ThemeManager::ApplyTheme(const Theme& theme) {
530 // Capture current ImGui colors as transition start
531 const bool should_transition =
533
534 if (should_transition) {
535 ImVec4* colors = ImGui::GetStyle().Colors;
536 for (int idx = 0; idx < ImGuiCol_COUNT; ++idx) {
537 transition_from_[idx] = colors[idx];
538 }
539 }
540
541 current_theme_ = theme;
542 current_theme_name_ = theme.name; // CRITICAL: Update the name tracking
544
545 if (should_transition) {
546 // Capture the target colors after applying
547 ImVec4* colors = ImGui::GetStyle().Colors;
548 for (int idx = 0; idx < ImGuiCol_COUNT; ++idx) {
549 transition_to_[idx] = colors[idx];
550 }
551 // Restore the starting colors — UpdateTransition() will lerp toward target
552 for (int idx = 0; idx < ImGuiCol_COUNT; ++idx) {
553 colors[idx] = transition_from_[idx];
554 }
555 transitioning_ = true;
557 } else {
558 // Ensure non-transition theme changes are not overridden by a previous
559 // transition in progress (e.g. selecting a theme while a density transition
560 // is running, or starting a live preview).
561 transitioning_ = false;
563 }
564
565 // Keep AgentUI theme cache in sync with the new theme colors.
567}
568
570 if (!transitioning_) {
571 return;
572 }
573
574 const float delta_time = ImGui::GetIO().DeltaTime;
575 constexpr float kTransitionSpeed = 4.0f; // ~250ms to complete
576
577 transition_progress_ += delta_time * kTransitionSpeed;
578 if (transition_progress_ >= 1.0f) {
580 transitioning_ = false;
581 }
582
583 // Smooth ease-out curve: t' = 1 - (1 - t)^2
584 const float eased =
585 1.0f - (1.0f - transition_progress_) * (1.0f - transition_progress_);
586
587 ImVec4* colors = ImGui::GetStyle().Colors;
588 for (int idx = 0; idx < ImGuiCol_COUNT; ++idx) {
589 colors[idx].x = transition_from_[idx].x +
590 (transition_to_[idx].x - transition_from_[idx].x) * eased;
591 colors[idx].y = transition_from_[idx].y +
592 (transition_to_[idx].y - transition_from_[idx].y) * eased;
593 colors[idx].z = transition_from_[idx].z +
594 (transition_to_[idx].z - transition_from_[idx].z) * eased;
595 colors[idx].w = transition_from_[idx].w +
596 (transition_to_[idx].w - transition_from_[idx].w) * eased;
597 }
598}
599
601 // Create a darker version of the window background for welcome screen
603 return {bg.red * 0.8f, bg.green * 0.8f, bg.blue * 0.8f, bg.alpha};
604}
605
609
613
615 if (!p_open || !*p_open)
616 return;
617
618 if (ImGui::Begin(
619 absl::StrFormat("%s Theme Selector", ICON_MD_PALETTE).c_str(),
620 p_open)) {
621 // Add subtle particle effects to theme selector
622 static float theme_animation_time = 0.0f;
623 theme_animation_time += ImGui::GetIO().DeltaTime;
624
625 ImDrawList* draw_list = ImGui::GetWindowDrawList();
626 ImVec2 window_pos = ImGui::GetWindowPos();
627 ImVec2 window_size = ImGui::GetWindowSize();
628
629 // Subtle corner particles for theme selector
630 for (int i = 0; i < 4; ++i) {
631 float corner_offset = i * 1.57f; // 90 degrees apart
632 float x = window_pos.x + window_size.x * 0.5f +
633 cosf(theme_animation_time * 0.8f + corner_offset) *
634 (window_size.x * 0.4f);
635 float y = window_pos.y + window_size.y * 0.5f +
636 sinf(theme_animation_time * 0.8f + corner_offset) *
637 (window_size.y * 0.4f);
638
639 float alpha =
640 0.1f + 0.1f * sinf(theme_animation_time * 1.2f + corner_offset);
641 auto current_theme = GetCurrentTheme();
642 ImU32 particle_color = ImGui::ColorConvertFloat4ToU32(
643 ImVec4(current_theme.accent.red, current_theme.accent.green,
644 current_theme.accent.blue, alpha));
645
646 draw_list->AddCircleFilled(ImVec2(x, y), 3.0f, particle_color);
647 }
648
649 ImGui::Text("%s Available Themes", ICON_MD_COLOR_LENS);
650 ImGui::Separator();
651
652 // Add Classic YAZE button first (direct ColorsYaze() application)
653 bool is_classic_active = (current_theme_name_ == "Classic YAZE");
654 if (is_classic_active) {
655 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.36f, 0.45f, 0.36f,
656 1.0f)); // allttpLightGreen
657 }
658
659 if (ImGui::Button(
660 absl::StrFormat("%s YAZE Classic (Original)",
661 is_classic_active ? ICON_MD_CHECK : ICON_MD_STAR)
662 .c_str(),
663 ImVec2(-1, 50))) {
665 }
666
667 if (is_classic_active) {
668 ImGui::PopStyleColor();
669 }
670
671 if (ImGui::IsItemHovered()) {
672 ImGui::BeginTooltip();
673 ImGui::Text("Original YAZE theme using ColorsYaze() function");
674 ImGui::Text("This is the authentic classic look - direct function call");
675 ImGui::EndTooltip();
676 }
677
678 ImGui::Separator();
679
680 // Sort themes alphabetically for consistent ordering (by name only)
681 std::vector<std::string> sorted_theme_names;
682 for (const auto& [name, theme] : themes_) {
683 sorted_theme_names.push_back(name);
684 }
685 std::sort(sorted_theme_names.begin(), sorted_theme_names.end());
686
687 // Track if any theme item is hovered for live preview
688 bool any_theme_hovered = false;
689 static std::string hovered_theme_name;
690
691 for (const auto& name : sorted_theme_names) {
692 const auto& theme = themes_.at(name);
693 bool is_current = (name == current_theme_name_) ||
694 (IsPreviewActive() && name == hovered_theme_name);
695
696 if (is_current) {
697 ImGui::PushStyleColor(ImGuiCol_Button,
698 ConvertColorToImVec4(theme.accent));
699 }
700
701 if (ImGui::Button(
702 absl::StrFormat("%s %s",
703 is_current ? ICON_MD_CHECK : ICON_MD_CIRCLE,
704 name.c_str())
705 .c_str(),
706 ImVec2(-1, 40))) {
707 // End preview before applying (to restore original first)
708 if (IsPreviewActive()) {
709 EndPreview();
710 }
711 auto status = LoadTheme(name); // Use LoadTheme instead of ApplyTheme
712 // to ensure correct tracking
713 if (!status.ok()) {
714 LOG_ERROR("Theme Manager", "Failed to load theme %s", name.c_str());
715 }
716 }
717
718 // Check hover state for live preview
719 bool button_hovered = ImGui::IsItemHovered();
720
721 if (is_current) {
722 ImGui::PopStyleColor();
723 }
724
725 // Show theme preview colors
726 ImGui::SameLine();
727 ImGui::ColorButton(absl::StrFormat("##primary_%s", name.c_str()).c_str(),
728 ConvertColorToImVec4(theme.primary),
729 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
730 bool color1_hovered = ImGui::IsItemHovered();
731 ImGui::SameLine();
732 ImGui::ColorButton(
733 absl::StrFormat("##secondary_%s", name.c_str()).c_str(),
734 ConvertColorToImVec4(theme.secondary), ImGuiColorEditFlags_NoTooltip,
735 ImVec2(20, 20));
736 bool color2_hovered = ImGui::IsItemHovered();
737 ImGui::SameLine();
738 ImGui::ColorButton(absl::StrFormat("##accent_%s", name.c_str()).c_str(),
739 ConvertColorToImVec4(theme.accent),
740 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
741 bool color3_hovered = ImGui::IsItemHovered();
742
743 // If hovering any part of this theme row, show tooltip and trigger preview
744 bool row_hovered =
745 button_hovered || color1_hovered || color2_hovered || color3_hovered;
746
747 if (row_hovered) {
748 any_theme_hovered = true;
749 hovered_theme_name = name;
750
751 // Start preview if not already previewing this theme
752 if (!IsPreviewActive() || current_theme_name_ != name) {
753 StartPreview(name);
754 }
755
756 ImGui::BeginTooltip();
757 ImGui::Text("%s %s", ICON_MD_PREVIEW, "Live Preview Active");
758 ImGui::Separator();
759 ImGui::Text("%s", theme.description.c_str());
760 ImGui::Text("Author: %s", theme.author.c_str());
761 ImGui::EndTooltip();
762 }
763 }
764
765 // End preview if no theme is hovered
766 if (!any_theme_hovered && IsPreviewActive()) {
767 EndPreview();
768 hovered_theme_name.clear();
769 }
770
771 ImGui::Separator();
772 if (ImGui::Button(
773 absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) {
774 auto status = RefreshAvailableThemes();
775 if (!status.ok()) {
776 LOG_ERROR("Theme Manager", "Failed to refresh themes");
777 }
778 }
779
780 ImGui::SameLine();
781 if (ImGui::Button(
782 absl::StrFormat("%s Load Custom Theme", ICON_MD_FOLDER_OPEN)
783 .c_str())) {
785 if (!file_path.empty()) {
786 auto status = LoadThemeFromFile(file_path);
787 if (!status.ok()) {
788 // Show error toast (would need access to toast manager)
789 }
790 }
791 }
792
793 ImGui::SameLine();
794 static bool show_simple_editor = false;
795 if (ImGui::Button(
796 absl::StrFormat("%s Theme Editor", ICON_MD_EDIT).c_str())) {
797 show_simple_editor = true;
798 }
799
800 if (ImGui::IsItemHovered()) {
801 ImGui::BeginTooltip();
802 ImGui::Text("Edit and save custom themes");
803 ImGui::Text("Includes 'Save to File' functionality");
804 ImGui::EndTooltip();
805 }
806
807 if (show_simple_editor) {
808 ShowSimpleThemeEditor(&show_simple_editor);
809 }
810 }
811 ImGui::End();
812}
813
814absl::Status ThemeManager::ParseThemeFile(const std::string& content,
815 Theme& theme) {
816 std::istringstream stream(content);
817 std::string line;
818 std::string current_section;
819
820 while (std::getline(stream, line)) {
821 // Skip empty lines and comments
822 if (line.empty() || line[0] == '#') {
823 continue;
824 }
825
826 // Check for section headers [section_name]
827 if (line[0] == '[' && line.back() == ']') {
828 current_section = line.substr(1, line.length() - 2);
829 continue;
830 }
831
832 size_t eq_pos = line.find('=');
833 if (eq_pos == std::string::npos) {
834 continue;
835 }
836
837 std::string key = line.substr(0, eq_pos);
838 std::string value = line.substr(eq_pos + 1);
839
840 // Trim whitespace and comments
841 key.erase(0, key.find_first_not_of(" \t"));
842 key.erase(key.find_last_not_of(" \t") + 1);
843 value.erase(0, value.find_first_not_of(" \t"));
844
845 // Remove inline comments
846 size_t comment_pos = value.find('#');
847 if (comment_pos != std::string::npos) {
848 value = value.substr(0, comment_pos);
849 }
850 value.erase(value.find_last_not_of(" \t") + 1);
851
852 // Parse based on section
853 if (current_section == "colors") {
854 Color color = ParseColorFromString(value);
855
856 if (key == "primary")
857 theme.primary = color;
858 else if (key == "secondary")
859 theme.secondary = color;
860 else if (key == "accent")
861 theme.accent = color;
862 else if (key == "background")
863 theme.background = color;
864 else if (key == "surface")
865 theme.surface = color;
866 else if (key == "error")
867 theme.error = color;
868 else if (key == "warning")
869 theme.warning = color;
870 else if (key == "success")
871 theme.success = color;
872 else if (key == "info")
873 theme.info = color;
874 else if (key == "text_primary")
875 theme.text_primary = color;
876 else if (key == "text_secondary")
877 theme.text_secondary = color;
878 else if (key == "text_disabled")
879 theme.text_disabled = color;
880 else if (key == "window_bg")
881 theme.window_bg = color;
882 else if (key == "child_bg")
883 theme.child_bg = color;
884 else if (key == "popup_bg")
885 theme.popup_bg = color;
886 else if (key == "button")
887 theme.button = color;
888 else if (key == "button_hovered")
889 theme.button_hovered = color;
890 else if (key == "button_active")
891 theme.button_active = color;
892 else if (key == "frame_bg")
893 theme.frame_bg = color;
894 else if (key == "frame_bg_hovered")
895 theme.frame_bg_hovered = color;
896 else if (key == "frame_bg_active")
897 theme.frame_bg_active = color;
898 else if (key == "header")
899 theme.header = color;
900 else if (key == "header_hovered")
901 theme.header_hovered = color;
902 else if (key == "header_active")
903 theme.header_active = color;
904 else if (key == "tab")
905 theme.tab = color;
906 else if (key == "tab_hovered")
907 theme.tab_hovered = color;
908 else if (key == "tab_active")
909 theme.tab_active = color;
910 else if (key == "menu_bar_bg")
911 theme.menu_bar_bg = color;
912 else if (key == "title_bg")
913 theme.title_bg = color;
914 else if (key == "title_bg_active")
915 theme.title_bg_active = color;
916 else if (key == "title_bg_collapsed")
917 theme.title_bg_collapsed = color;
918 else if (key == "separator")
919 theme.separator = color;
920 else if (key == "separator_hovered")
921 theme.separator_hovered = color;
922 else if (key == "separator_active")
923 theme.separator_active = color;
924 else if (key == "scrollbar_bg")
925 theme.scrollbar_bg = color;
926 else if (key == "scrollbar_grab")
927 theme.scrollbar_grab = color;
928 else if (key == "scrollbar_grab_hovered")
929 theme.scrollbar_grab_hovered = color;
930 else if (key == "scrollbar_grab_active")
931 theme.scrollbar_grab_active = color;
932 else if (key == "border")
933 theme.border = color;
934 else if (key == "border_shadow")
935 theme.border_shadow = color;
936 else if (key == "resize_grip")
937 theme.resize_grip = color;
938 else if (key == "resize_grip_hovered")
939 theme.resize_grip_hovered = color;
940 else if (key == "resize_grip_active")
941 theme.resize_grip_active = color;
942 else if (key == "check_mark")
943 theme.check_mark = color;
944 else if (key == "slider_grab")
945 theme.slider_grab = color;
946 else if (key == "slider_grab_active")
947 theme.slider_grab_active = color;
948 else if (key == "input_text_cursor")
949 theme.input_text_cursor = color;
950 else if (key == "nav_cursor")
951 theme.nav_cursor = color;
952 else if (key == "nav_windowing_highlight")
953 theme.nav_windowing_highlight = color;
954 else if (key == "nav_windowing_dim_bg")
955 theme.nav_windowing_dim_bg = color;
956 else if (key == "modal_window_dim_bg")
957 theme.modal_window_dim_bg = color;
958 else if (key == "text_selected_bg")
959 theme.text_selected_bg = color;
960 else if (key == "drag_drop_target")
961 theme.drag_drop_target = color;
962 else if (key == "table_header_bg")
963 theme.table_header_bg = color;
964 else if (key == "table_border_strong")
965 theme.table_border_strong = color;
966 else if (key == "table_border_light")
967 theme.table_border_light = color;
968 else if (key == "table_row_bg")
969 theme.table_row_bg = color;
970 else if (key == "table_row_bg_alt")
971 theme.table_row_bg_alt = color;
972 else if (key == "text_link")
973 theme.text_link = color;
974 else if (key == "plot_lines")
975 theme.plot_lines = color;
976 else if (key == "plot_lines_hovered")
977 theme.plot_lines_hovered = color;
978 else if (key == "plot_histogram")
979 theme.plot_histogram = color;
980 else if (key == "plot_histogram_hovered")
981 theme.plot_histogram_hovered = color;
982 else if (key == "tree_lines")
983 theme.tree_lines = color;
984 else if (key == "tab_dimmed")
985 theme.tab_dimmed = color;
986 else if (key == "tab_dimmed_selected")
987 theme.tab_dimmed_selected = color;
988 else if (key == "tab_dimmed_selected_overline")
989 theme.tab_dimmed_selected_overline = color;
990 else if (key == "tab_selected_overline")
991 theme.tab_selected_overline = color;
992 else if (key == "docking_preview")
993 theme.docking_preview = color;
994 else if (key == "docking_empty_bg")
995 theme.docking_empty_bg = color;
996 } else if (current_section == "style") {
997 if (key == "window_rounding")
998 theme.window_rounding = std::stof(value);
999 else if (key == "frame_rounding")
1000 theme.frame_rounding = std::stof(value);
1001 else if (key == "scrollbar_rounding")
1002 theme.scrollbar_rounding = std::stof(value);
1003 else if (key == "grab_rounding")
1004 theme.grab_rounding = std::stof(value);
1005 else if (key == "tab_rounding")
1006 theme.tab_rounding = std::stof(value);
1007 else if (key == "window_border_size")
1008 theme.window_border_size = std::stof(value);
1009 else if (key == "frame_border_size")
1010 theme.frame_border_size = std::stof(value);
1011 else if (key == "enable_animations")
1012 theme.enable_animations = (value == "true");
1013 else if (key == "enable_glow_effects")
1014 theme.enable_glow_effects = (value == "true");
1015 else if (key == "animation_speed")
1016 theme.animation_speed = std::stof(value);
1017 } else if (current_section == "" || current_section == "metadata") {
1018 // Top-level metadata
1019 if (key == "name")
1020 theme.name = value;
1021 else if (key == "description")
1022 theme.description = value;
1023 else if (key == "author")
1024 theme.author = value;
1025 }
1026 }
1027
1028 return absl::OkStatus();
1029}
1030
1032 // Helper to check if a color is uninitialized (all zeros)
1033 auto is_unset = [](const Color& color) {
1034 return color.red == 0.0f && color.green == 0.0f && color.blue == 0.0f &&
1035 color.alpha == 0.0f;
1036 };
1037 // Legacy theme files often omit newer semantic fields, which then remain at
1038 // default-constructed opaque black (0,0,0,1). Treat that as "missing" for
1039 // semantic/interaction colors to avoid black overlays and handles.
1040 auto needs_semantic_default = [&](const Color& color) {
1041 const bool default_opaque_black = color.red == 0.0f &&
1042 color.green == 0.0f &&
1043 color.blue == 0.0f && color.alpha == 1.0f;
1044 return is_unset(color) || default_opaque_black;
1045 };
1046
1047 // Helper to create a color with modified alpha
1048 auto with_alpha = [](const Color& color, float alpha) {
1049 return Color{color.red, color.green, color.blue, alpha};
1050 };
1051
1052 // Helper to lighten a color
1053 auto lighten = [](const Color& color, float amount) {
1054 return Color{std::min(1.0f, color.red + amount),
1055 std::min(1.0f, color.green + amount),
1056 std::min(1.0f, color.blue + amount), color.alpha};
1057 };
1058
1059 // Helper to darken a color
1060 auto darken = [](const Color& color, float amount) {
1061 return Color{std::max(0.0f, color.red - amount),
1062 std::max(0.0f, color.green - amount),
1063 std::max(0.0f, color.blue - amount), color.alpha};
1064 };
1065
1066 // Borders and separators
1067 if (is_unset(theme.border)) {
1068 theme.border = theme.primary;
1069 }
1070 if (is_unset(theme.border_shadow)) {
1071 theme.border_shadow = RGBA(0, 0, 0, 0);
1072 }
1073 if (is_unset(theme.separator)) {
1074 theme.separator = with_alpha(theme.secondary, 0.6f);
1075 }
1076 if (is_unset(theme.separator_hovered)) {
1077 theme.separator_hovered = with_alpha(theme.primary, 0.8f);
1078 }
1079 if (is_unset(theme.separator_active)) {
1080 theme.separator_active = theme.accent;
1081 }
1082
1083 // Scrollbars
1084 if (is_unset(theme.scrollbar_bg)) {
1085 theme.scrollbar_bg = with_alpha(theme.surface, 0.6f);
1086 }
1087 if (is_unset(theme.scrollbar_grab)) {
1088 theme.scrollbar_grab = with_alpha(theme.secondary, 0.5f);
1089 }
1090 if (is_unset(theme.scrollbar_grab_hovered)) {
1091 theme.scrollbar_grab_hovered = with_alpha(theme.secondary, 0.7f);
1092 }
1093 if (is_unset(theme.scrollbar_grab_active)) {
1094 theme.scrollbar_grab_active = with_alpha(theme.secondary, 0.9f);
1095 }
1096
1097 // Resize grips
1098 if (is_unset(theme.resize_grip)) {
1099 theme.resize_grip = RGBA(255, 255, 255, 26);
1100 }
1101 if (is_unset(theme.resize_grip_hovered)) {
1102 theme.resize_grip_hovered = with_alpha(theme.accent, 0.6f);
1103 }
1104 if (is_unset(theme.resize_grip_active)) {
1105 theme.resize_grip_active = with_alpha(theme.accent, 0.9f);
1106 }
1107
1108 // Controls
1109 if (is_unset(theme.check_mark)) {
1110 theme.check_mark = lighten(theme.accent, 0.2f);
1111 }
1112 if (is_unset(theme.slider_grab)) {
1113 theme.slider_grab = theme.primary;
1114 }
1115 if (is_unset(theme.slider_grab_active)) {
1116 theme.slider_grab_active = theme.accent;
1117 }
1118
1119 // Tables
1120 if (is_unset(theme.table_header_bg)) {
1121 theme.table_header_bg = theme.header;
1122 }
1123 if (is_unset(theme.table_border_strong)) {
1124 theme.table_border_strong = theme.secondary;
1125 }
1126 if (is_unset(theme.table_border_light)) {
1127 theme.table_border_light = with_alpha(theme.surface, 0.5f);
1128 }
1129 if (is_unset(theme.table_row_bg)) {
1130 theme.table_row_bg = RGBA(0, 0, 0, 0);
1131 }
1132 if (is_unset(theme.table_row_bg_alt)) {
1133 theme.table_row_bg_alt = RGBA(255, 255, 255, 20);
1134 }
1135
1136 // Links
1137 if (is_unset(theme.text_link)) {
1138 theme.text_link = theme.info;
1139 }
1140
1141 // Navigation and special elements
1142 if (is_unset(theme.input_text_cursor)) {
1143 theme.input_text_cursor = theme.text_primary;
1144 }
1145 if (is_unset(theme.nav_cursor)) {
1146 theme.nav_cursor = theme.accent;
1147 }
1148 if (is_unset(theme.nav_windowing_highlight)) {
1149 theme.nav_windowing_highlight = with_alpha(theme.accent, 0.8f);
1150 }
1151 if (is_unset(theme.nav_windowing_dim_bg)) {
1152 theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 128);
1153 }
1154 if (is_unset(theme.modal_window_dim_bg)) {
1155 theme.modal_window_dim_bg = RGBA(0, 0, 0, 100);
1156 }
1157 if (is_unset(theme.text_selected_bg)) {
1158 theme.text_selected_bg = with_alpha(theme.primary, 0.4f);
1159 }
1160 if (is_unset(theme.drag_drop_target)) {
1161 theme.drag_drop_target = with_alpha(theme.accent, 0.8f);
1162 }
1163
1164 // Docking
1165 if (is_unset(theme.docking_preview)) {
1166 theme.docking_preview = with_alpha(theme.primary, 0.7f);
1167 }
1168 if (is_unset(theme.docking_empty_bg)) {
1169 theme.docking_empty_bg = theme.header;
1170 }
1171
1172 // Tree
1173 if (is_unset(theme.tree_lines)) {
1174 theme.tree_lines = with_alpha(theme.separator, 0.6f);
1175 }
1176
1177 // Tab variations
1178 if (is_unset(theme.tab_dimmed)) {
1179 theme.tab_dimmed = darken(theme.tab, 0.1f);
1180 }
1181 if (is_unset(theme.tab_dimmed_selected)) {
1182 theme.tab_dimmed_selected = darken(theme.tab_active, 0.1f);
1183 }
1184 if (is_unset(theme.tab_dimmed_selected_overline)) {
1186 }
1187 if (is_unset(theme.tab_selected_overline)) {
1188 theme.tab_selected_overline = theme.accent;
1189 }
1190
1191 // Surface variants (if missing)
1192 if (is_unset(theme.surface)) {
1193 theme.surface = theme.background;
1194 }
1195 if (is_unset(theme.modal_bg)) {
1196 theme.modal_bg = theme.popup_bg;
1197 }
1198
1199 // Editor-specific defaults
1200 if (is_unset(theme.editor_background)) {
1201 theme.editor_background = theme.background;
1202 }
1203 if (is_unset(theme.editor_grid)) {
1204 theme.editor_grid = with_alpha(theme.text_secondary, 0.2f);
1205 }
1206 if (is_unset(theme.editor_cursor)) {
1207 theme.editor_cursor = theme.text_primary;
1208 }
1209 if (is_unset(theme.editor_selection)) {
1210 theme.editor_selection = with_alpha(theme.primary, 0.3f);
1211 }
1212
1213 // Interaction defaults
1214 if (needs_semantic_default(theme.selection_primary)) {
1215 theme.selection_primary = theme.warning;
1216 }
1217 if (needs_semantic_default(theme.selection_secondary)) {
1218 theme.selection_secondary = theme.info;
1219 }
1220 if (needs_semantic_default(theme.selection_hover)) {
1221 theme.selection_hover = with_alpha(theme.text_primary, 0.2f);
1222 }
1223 if (needs_semantic_default(theme.selection_pulsing)) {
1224 theme.selection_pulsing = with_alpha(theme.text_primary, 0.8f);
1225 }
1226 if (needs_semantic_default(theme.selection_handle)) {
1227 theme.selection_handle = theme.text_primary;
1228 }
1229 if (needs_semantic_default(theme.drag_preview)) {
1230 theme.drag_preview = with_alpha(theme.secondary, 0.4f);
1231 }
1232 if (needs_semantic_default(theme.drag_preview_outline)) {
1233 theme.drag_preview_outline = with_alpha(theme.secondary, 0.8f);
1234 }
1235
1236 // Entity defaults
1237 if (needs_semantic_default(theme.entrance_color)) {
1238 theme.entrance_color = theme.success;
1239 }
1240 if (needs_semantic_default(theme.hole_color)) {
1241 theme.hole_color = theme.secondary;
1242 }
1243 if (needs_semantic_default(theme.exit_color)) {
1244 theme.exit_color = theme.error;
1245 }
1246 if (needs_semantic_default(theme.item_color)) {
1247 theme.item_color = theme.warning;
1248 }
1249 if (needs_semantic_default(theme.sprite_color)) {
1250 theme.sprite_color = theme.info;
1251 }
1252 if (needs_semantic_default(theme.transport_color)) {
1253 theme.transport_color = lighten(theme.secondary, 0.2f);
1254 }
1255 if (needs_semantic_default(theme.music_zone_color)) {
1256 theme.music_zone_color = darken(theme.warning, 0.1f);
1257 }
1258
1259 // Dungeon semantic defaults
1260 if (needs_semantic_default(theme.dungeon.selection_primary)) {
1262 }
1263 if (needs_semantic_default(theme.dungeon.selection_secondary)) {
1265 }
1266 if (needs_semantic_default(theme.dungeon.selection_pulsing)) {
1268 }
1269 if (needs_semantic_default(theme.dungeon.selection_handle)) {
1271 }
1272 if (needs_semantic_default(theme.dungeon.drag_preview)) {
1273 theme.dungeon.drag_preview = theme.drag_preview;
1274 }
1275 if (needs_semantic_default(theme.dungeon.drag_preview_outline)) {
1277 }
1278 if (needs_semantic_default(theme.dungeon.object_wall)) {
1279 theme.dungeon.object_wall = with_alpha(theme.text_secondary, 1.0f);
1280 }
1281 if (needs_semantic_default(theme.dungeon.object_floor)) {
1282 theme.dungeon.object_floor =
1283 darken(with_alpha(theme.text_secondary, 1.0f), 0.2f);
1284 }
1285 if (needs_semantic_default(theme.dungeon.object_chest)) {
1286 theme.dungeon.object_chest = theme.warning;
1287 }
1288 if (needs_semantic_default(theme.dungeon.object_door)) {
1289 theme.dungeon.object_door = darken(theme.warning, 0.45f);
1290 }
1291 if (needs_semantic_default(theme.dungeon.object_pot)) {
1292 theme.dungeon.object_pot = darken(theme.warning, 0.25f);
1293 }
1294 if (needs_semantic_default(theme.dungeon.object_stairs)) {
1295 theme.dungeon.object_stairs = lighten(theme.warning, 0.1f);
1296 }
1297 if (needs_semantic_default(theme.dungeon.object_decoration)) {
1298 theme.dungeon.object_decoration = theme.success;
1299 }
1300 if (needs_semantic_default(theme.dungeon.object_default)) {
1302 }
1303 if (needs_semantic_default(theme.dungeon.grid_cell_highlight)) {
1304 theme.dungeon.grid_cell_highlight = with_alpha(theme.success, 0.3f);
1305 }
1306 if (needs_semantic_default(theme.dungeon.grid_cell_selected)) {
1307 theme.dungeon.grid_cell_selected = with_alpha(theme.success, 0.5f);
1308 }
1309 if (needs_semantic_default(theme.dungeon.grid_cell_border)) {
1310 theme.dungeon.grid_cell_border = with_alpha(theme.border, 0.5f);
1311 }
1312 if (needs_semantic_default(theme.dungeon.grid_text)) {
1313 theme.dungeon.grid_text = with_alpha(theme.text_primary, 0.8f);
1314 }
1315 if (needs_semantic_default(theme.dungeon.room_border)) {
1316 theme.dungeon.room_border = with_alpha(theme.text_secondary, 1.0f);
1317 }
1318 if (needs_semantic_default(theme.dungeon.room_border_dark)) {
1320 darken(with_alpha(theme.text_secondary, 1.0f), 0.3f);
1321 }
1322 if (needs_semantic_default(theme.dungeon.sprite_layer0)) {
1323 theme.dungeon.sprite_layer0 = theme.success;
1324 }
1325 if (needs_semantic_default(theme.dungeon.sprite_layer1)) {
1326 theme.dungeon.sprite_layer1 = theme.info;
1327 }
1328 if (needs_semantic_default(theme.dungeon.sprite_layer2)) {
1329 theme.dungeon.sprite_layer2 = theme.info;
1330 }
1331 if (needs_semantic_default(theme.dungeon.outline_layer0)) {
1332 theme.dungeon.outline_layer0 = theme.error;
1333 }
1334 if (needs_semantic_default(theme.dungeon.outline_layer1)) {
1335 theme.dungeon.outline_layer1 = theme.success;
1336 }
1337 if (needs_semantic_default(theme.dungeon.outline_layer2)) {
1338 theme.dungeon.outline_layer2 = theme.info;
1339 }
1340
1341 // Agent semantic defaults
1342 if (needs_semantic_default(theme.agent.user_message)) {
1343 theme.agent.user_message = theme.info;
1344 }
1345 if (needs_semantic_default(theme.agent.agent_message)) {
1346 theme.agent.agent_message = theme.success;
1347 }
1348 if (needs_semantic_default(theme.agent.system_message)) {
1349 theme.agent.system_message = theme.text_secondary;
1350 }
1351 if (needs_semantic_default(theme.agent.text_secondary)) {
1352 theme.agent.text_secondary = theme.text_secondary;
1353 }
1354 if (needs_semantic_default(theme.agent.json_text)) {
1355 theme.agent.json_text = theme.warning;
1356 }
1357 if (needs_semantic_default(theme.agent.command_text)) {
1358 theme.agent.command_text = theme.error;
1359 }
1360 if (needs_semantic_default(theme.agent.code_background)) {
1361 theme.agent.code_background = darken(theme.surface, 0.1f);
1362 }
1363 if (needs_semantic_default(theme.agent.panel_bg)) {
1364 theme.agent.panel_bg = theme.child_bg;
1365 }
1366 if (needs_semantic_default(theme.agent.panel_bg_darker)) {
1367 theme.agent.panel_bg_darker = with_alpha(theme.surface, 0.2f);
1368 }
1369 if (needs_semantic_default(theme.agent.panel_border)) {
1370 theme.agent.panel_border = theme.border;
1371 }
1372 if (needs_semantic_default(theme.agent.accent)) {
1373 theme.agent.accent = theme.accent;
1374 }
1375 if (needs_semantic_default(theme.agent.status_active)) {
1376 theme.agent.status_active = theme.success;
1377 }
1378 if (needs_semantic_default(theme.agent.status_inactive)) {
1379 theme.agent.status_inactive = theme.text_disabled;
1380 }
1381 if (needs_semantic_default(theme.agent.status_success)) {
1382 theme.agent.status_success = theme.success;
1383 }
1384 if (needs_semantic_default(theme.agent.status_warning)) {
1385 theme.agent.status_warning = theme.warning;
1386 }
1387 if (needs_semantic_default(theme.agent.status_error)) {
1388 theme.agent.status_error = theme.error;
1389 }
1390 if (needs_semantic_default(theme.agent.provider_ollama)) {
1391 theme.agent.provider_ollama = theme.text_primary;
1392 }
1393 if (needs_semantic_default(theme.agent.provider_gemini)) {
1394 theme.agent.provider_gemini = theme.info;
1395 }
1396 if (needs_semantic_default(theme.agent.provider_mock)) {
1397 theme.agent.provider_mock = theme.text_disabled;
1398 }
1399 if (needs_semantic_default(theme.agent.provider_openai)) {
1400 theme.agent.provider_openai = theme.success;
1401 }
1402 if (needs_semantic_default(theme.agent.collaboration_active)) {
1403 theme.agent.collaboration_active = theme.success;
1404 }
1405 if (needs_semantic_default(theme.agent.collaboration_inactive)) {
1407 }
1408 if (needs_semantic_default(theme.agent.proposal_panel_bg)) {
1409 theme.agent.proposal_panel_bg = with_alpha(theme.surface, 1.0f);
1410 }
1411 if (needs_semantic_default(theme.agent.proposal_accent)) {
1412 theme.agent.proposal_accent = theme.info;
1413 }
1414 if (needs_semantic_default(theme.agent.button_copy)) {
1415 theme.agent.button_copy = with_alpha(theme.secondary, 1.0f);
1416 }
1417 if (needs_semantic_default(theme.agent.button_copy_hover)) {
1418 theme.agent.button_copy_hover = lighten(theme.secondary, 0.1f);
1419 }
1420 if (needs_semantic_default(theme.agent.gradient_top)) {
1421 theme.agent.gradient_top = theme.primary;
1422 }
1423 if (needs_semantic_default(theme.agent.gradient_bottom)) {
1424 theme.agent.gradient_bottom = theme.secondary;
1425 }
1426}
1427
1429 bool dark_mode) {
1430 Theme theme;
1431 theme.name = "Custom Accent Theme";
1432 theme.description = "Generated from accent color";
1433 theme.author = "YAZE Theme Generator";
1434
1435 // Get HSL of accent for derivation
1436 Color::HSL accent_hsl = accent.ToHSL();
1437
1438 // Generate primary as a slightly darker/less saturated version of accent
1439 theme.primary = accent;
1440 theme.accent = accent;
1441
1442 // Generate secondary with complementary hue shift (30 degrees)
1443 theme.secondary = accent.ShiftHue(30.0f).Desaturate(0.1f);
1444
1445 if (dark_mode) {
1446 // Dark theme: very dark backgrounds, light text
1447 theme.background = Color::FromHSL(accent_hsl.h, 0.15f, 0.08f);
1448 theme.surface = Color::FromHSL(accent_hsl.h, 0.12f, 0.12f);
1449 theme.window_bg = theme.background.WithAlpha(0.95f);
1450 theme.child_bg = Color::FromHSL(accent_hsl.h, 0.10f, 0.10f).WithAlpha(0.8f);
1451 theme.popup_bg =
1452 Color::FromHSL(accent_hsl.h, 0.12f, 0.14f).WithAlpha(0.98f);
1453 theme.modal_bg = theme.popup_bg;
1454
1455 // Light text on dark backgrounds
1456 theme.text_primary = Color{0.95f, 0.95f, 0.95f, 1.0f};
1457 theme.text_secondary = Color{0.75f, 0.75f, 0.78f, 1.0f};
1458 theme.text_disabled = Color{0.45f, 0.45f, 0.48f, 1.0f};
1459
1460 // Buttons derived from accent
1461 theme.button = accent.Darker(0.15f).Desaturate(0.1f);
1462 theme.button_hovered = accent;
1463 theme.button_active = accent.Lighter(0.1f);
1464
1465 // Frame backgrounds (inputs, etc.)
1466 theme.frame_bg = Color::FromHSL(accent_hsl.h, 0.08f, 0.15f);
1467 theme.frame_bg_hovered = Color::FromHSL(accent_hsl.h, 0.12f, 0.20f);
1468 theme.frame_bg_active = Color::FromHSL(accent_hsl.h, 0.15f, 0.25f);
1469
1470 // Headers
1471 theme.header = Color::FromHSL(accent_hsl.h, 0.20f, 0.18f);
1472 theme.header_hovered = accent.Darker(0.2f);
1473 theme.header_active = accent.Darker(0.1f);
1474
1475 // Tabs
1476 theme.tab = Color::FromHSL(accent_hsl.h, 0.12f, 0.12f);
1477 theme.tab_hovered = accent.Darker(0.25f);
1478 theme.tab_active = accent.Darker(0.15f);
1479
1480 // Title bars
1481 theme.title_bg = Color::FromHSL(accent_hsl.h, 0.15f, 0.10f);
1482 theme.title_bg_active = Color::FromHSL(accent_hsl.h, 0.20f, 0.14f);
1483 theme.title_bg_collapsed = theme.title_bg;
1484
1485 // Menu bar
1486 theme.menu_bar_bg = Color::FromHSL(accent_hsl.h, 0.10f, 0.12f);
1487
1488 } else {
1489 // Light theme: light backgrounds, dark text
1490 theme.background = Color::FromHSL(accent_hsl.h, 0.05f, 0.96f);
1491 theme.surface = Color::FromHSL(accent_hsl.h, 0.08f, 0.94f);
1492 theme.window_bg = theme.background.WithAlpha(0.98f);
1493 theme.child_bg = Color::FromHSL(accent_hsl.h, 0.05f, 0.92f).WithAlpha(0.8f);
1494 theme.popup_bg = Color{1.0f, 1.0f, 1.0f, 0.98f};
1495 theme.modal_bg = theme.popup_bg;
1496
1497 // Dark text on light backgrounds
1498 theme.text_primary = Color{0.12f, 0.12f, 0.15f, 1.0f};
1499 theme.text_secondary = Color{0.35f, 0.35f, 0.40f, 1.0f};
1500 theme.text_disabled = Color{0.60f, 0.60f, 0.65f, 1.0f};
1501
1502 // Buttons derived from accent
1503 theme.button = accent.Lighter(0.1f);
1504 theme.button_hovered = accent;
1505 theme.button_active = accent.Darker(0.1f);
1506
1507 // Frame backgrounds (inputs, etc.)
1508 theme.frame_bg = Color::FromHSL(accent_hsl.h, 0.05f, 0.90f);
1509 theme.frame_bg_hovered = Color::FromHSL(accent_hsl.h, 0.08f, 0.85f);
1510 theme.frame_bg_active = Color::FromHSL(accent_hsl.h, 0.12f, 0.80f);
1511
1512 // Headers
1513 theme.header = Color::FromHSL(accent_hsl.h, 0.08f, 0.88f);
1514 theme.header_hovered = accent.Lighter(0.2f);
1515 theme.header_active = accent.Lighter(0.1f);
1516
1517 // Tabs
1518 theme.tab = Color::FromHSL(accent_hsl.h, 0.05f, 0.90f);
1519 theme.tab_hovered = accent.Lighter(0.25f);
1520 theme.tab_active = accent.Lighter(0.15f);
1521
1522 // Title bars
1523 theme.title_bg = Color::FromHSL(accent_hsl.h, 0.05f, 0.92f);
1524 theme.title_bg_active = Color::FromHSL(accent_hsl.h, 0.10f, 0.88f);
1525 theme.title_bg_collapsed = theme.title_bg;
1526
1527 // Menu bar
1528 theme.menu_bar_bg = Color::FromHSL(accent_hsl.h, 0.05f, 0.94f);
1529 }
1530
1531 // Status colors (independent of dark/light mode)
1532 theme.error = Color{0.90f, 0.30f, 0.30f, 1.0f};
1533 theme.warning = Color{0.95f, 0.75f, 0.25f, 1.0f};
1534 theme.success = Color{0.30f, 0.85f, 0.45f, 1.0f};
1535 theme.info = Color{0.35f, 0.70f, 0.95f, 1.0f};
1536
1537 // Borders using accent hue
1538 theme.border = accent.WithAlpha(0.6f);
1539 theme.border_shadow = Color{0.0f, 0.0f, 0.0f, 0.0f};
1540 theme.separator = accent.Desaturate(0.3f).WithAlpha(0.4f);
1541 theme.separator_hovered = accent.WithAlpha(0.7f);
1542 theme.separator_active = accent;
1543
1544 // Scrollbars
1545 theme.scrollbar_bg = dark_mode ? Color::FromHSL(accent_hsl.h, 0.08f, 0.12f)
1546 : Color::FromHSL(accent_hsl.h, 0.05f, 0.88f);
1547 theme.scrollbar_grab = accent.Darker(0.2f).WithAlpha(0.6f);
1548 theme.scrollbar_grab_hovered = accent.WithAlpha(0.8f);
1549 theme.scrollbar_grab_active = accent;
1550
1551 // Resize grips
1552 theme.resize_grip = Color{1.0f, 1.0f, 1.0f, 0.1f};
1553 theme.resize_grip_hovered = accent.WithAlpha(0.6f);
1554 theme.resize_grip_active = accent;
1555
1556 // Controls
1557 theme.check_mark = accent.Lighter(0.2f);
1558 theme.slider_grab = accent;
1559 theme.slider_grab_active = accent.Lighter(0.15f);
1560
1561 // Tables
1562 theme.table_header_bg = theme.header;
1563 theme.table_border_strong = accent.Desaturate(0.2f);
1564 theme.table_border_light = accent.Desaturate(0.4f).WithAlpha(0.5f);
1565 theme.table_row_bg = Color{0.0f, 0.0f, 0.0f, 0.0f};
1566 theme.table_row_bg_alt = accent.WithAlpha(0.05f);
1567
1568 // Links
1569 theme.text_link = accent.Lighter(0.1f);
1570
1571 // Navigation
1572 theme.input_text_cursor = theme.text_primary;
1573 theme.nav_cursor = accent;
1574 theme.nav_windowing_highlight = accent.WithAlpha(0.7f);
1575 theme.nav_windowing_dim_bg = Color{0.0f, 0.0f, 0.0f, 0.5f};
1576 theme.modal_window_dim_bg = Color{0.0f, 0.0f, 0.0f, 0.4f};
1577 theme.text_selected_bg = accent.WithAlpha(0.35f);
1578 theme.drag_drop_target = accent.WithAlpha(0.8f);
1579
1580 // Docking
1581 theme.docking_preview = accent.WithAlpha(0.7f);
1582 theme.docking_empty_bg = theme.menu_bar_bg;
1583
1584 // Tree lines
1585 theme.tree_lines = accent.Desaturate(0.3f).WithAlpha(0.4f);
1586
1587 // Tab variations
1588 theme.tab_unfocused = theme.tab.Darker(0.05f);
1589 theme.tab_unfocused_active = theme.tab_active.Darker(0.1f);
1590 theme.tab_dimmed = theme.tab.Darker(0.1f);
1591 theme.tab_dimmed_selected = theme.tab_active.Darker(0.05f);
1592 theme.tab_dimmed_selected_overline = accent;
1593 theme.tab_selected_overline = accent;
1594
1595 // Style parameters
1596 theme.window_rounding = 8.0f;
1597 theme.frame_rounding = 6.0f;
1598 theme.scrollbar_rounding = 8.0f;
1599 theme.grab_rounding = 4.0f;
1600 theme.tab_rounding = 6.0f;
1601 theme.window_border_size = 1.0f;
1602 theme.frame_border_size = 0.0f;
1603
1604 // Animation settings
1605 theme.enable_animations = true;
1606 theme.animation_speed = 1.0f;
1607 theme.enable_glow_effects = false;
1608
1609 return theme;
1610}
1611
1612void ThemeManager::ApplyAccentColor(const Color& accent, bool dark_mode) {
1613 Theme generated = GenerateThemeFromAccent(accent, dark_mode);
1614 ApplyTheme(generated);
1615 current_theme_ = generated;
1616 current_theme_name_ = "Custom Accent";
1617}
1618
1619Color ThemeManager::ParseColorFromString(const std::string& color_str) const {
1620 std::vector<std::string> components = absl::StrSplit(color_str, ',');
1621 if (components.size() != 4) {
1622 return RGBA(255, 255, 255, 255); // White fallback
1623 }
1624
1625 try {
1626 int r = std::stoi(components[0]);
1627 int g = std::stoi(components[1]);
1628 int b = std::stoi(components[2]);
1629 int a = std::stoi(components[3]);
1630 return RGBA(r, g, b, a);
1631 } catch (...) {
1632 return RGBA(255, 255, 255, 255); // White fallback
1633 }
1634}
1635
1636std::string ThemeManager::SerializeTheme(const Theme& theme) const {
1637 std::ostringstream ss;
1638
1639 // Helper function to convert color to RGB string
1640 auto colorToString = [](const Color& c) -> std::string {
1641 int r = static_cast<int>(c.red * 255.0f);
1642 int g = static_cast<int>(c.green * 255.0f);
1643 int b = static_cast<int>(c.blue * 255.0f);
1644 int a = static_cast<int>(c.alpha * 255.0f);
1645 return std::to_string(r) + "," + std::to_string(g) + "," +
1646 std::to_string(b) + "," + std::to_string(a);
1647 };
1648
1649 ss << "# yaze Theme File\n";
1650 ss << "# Generated by YAZE Theme Editor\n";
1651 ss << "name=" << theme.name << "\n";
1652 ss << "description=" << theme.description << "\n";
1653 ss << "author=" << theme.author << "\n";
1654 ss << "version=1.0\n";
1655 ss << "\n[colors]\n";
1656
1657 // Primary colors
1658 ss << "# Primary colors\n";
1659 ss << "primary=" << colorToString(theme.primary) << "\n";
1660 ss << "secondary=" << colorToString(theme.secondary) << "\n";
1661 ss << "accent=" << colorToString(theme.accent) << "\n";
1662 ss << "background=" << colorToString(theme.background) << "\n";
1663 ss << "surface=" << colorToString(theme.surface) << "\n";
1664 ss << "\n";
1665
1666 // Status colors
1667 ss << "# Status colors\n";
1668 ss << "error=" << colorToString(theme.error) << "\n";
1669 ss << "warning=" << colorToString(theme.warning) << "\n";
1670 ss << "success=" << colorToString(theme.success) << "\n";
1671 ss << "info=" << colorToString(theme.info) << "\n";
1672 ss << "\n";
1673
1674 // Text colors
1675 ss << "# Text colors\n";
1676 ss << "text_primary=" << colorToString(theme.text_primary) << "\n";
1677 ss << "text_secondary=" << colorToString(theme.text_secondary) << "\n";
1678 ss << "text_disabled=" << colorToString(theme.text_disabled) << "\n";
1679 ss << "\n";
1680
1681 // Window colors
1682 ss << "# Window colors\n";
1683 ss << "window_bg=" << colorToString(theme.window_bg) << "\n";
1684 ss << "child_bg=" << colorToString(theme.child_bg) << "\n";
1685 ss << "popup_bg=" << colorToString(theme.popup_bg) << "\n";
1686 ss << "\n";
1687
1688 // Interactive elements
1689 ss << "# Interactive elements\n";
1690 ss << "button=" << colorToString(theme.button) << "\n";
1691 ss << "button_hovered=" << colorToString(theme.button_hovered) << "\n";
1692 ss << "button_active=" << colorToString(theme.button_active) << "\n";
1693 ss << "frame_bg=" << colorToString(theme.frame_bg) << "\n";
1694 ss << "frame_bg_hovered=" << colorToString(theme.frame_bg_hovered) << "\n";
1695 ss << "frame_bg_active=" << colorToString(theme.frame_bg_active) << "\n";
1696 ss << "\n";
1697
1698 // Navigation
1699 ss << "# Navigation\n";
1700 ss << "header=" << colorToString(theme.header) << "\n";
1701 ss << "header_hovered=" << colorToString(theme.header_hovered) << "\n";
1702 ss << "header_active=" << colorToString(theme.header_active) << "\n";
1703 ss << "tab=" << colorToString(theme.tab) << "\n";
1704 ss << "tab_hovered=" << colorToString(theme.tab_hovered) << "\n";
1705 ss << "tab_active=" << colorToString(theme.tab_active) << "\n";
1706 ss << "menu_bar_bg=" << colorToString(theme.menu_bar_bg) << "\n";
1707 ss << "title_bg=" << colorToString(theme.title_bg) << "\n";
1708 ss << "title_bg_active=" << colorToString(theme.title_bg_active) << "\n";
1709 ss << "title_bg_collapsed=" << colorToString(theme.title_bg_collapsed)
1710 << "\n";
1711 ss << "\n";
1712
1713 // Borders and separators
1714 ss << "# Borders and separators\n";
1715 ss << "border=" << colorToString(theme.border) << "\n";
1716 ss << "border_shadow=" << colorToString(theme.border_shadow) << "\n";
1717 ss << "separator=" << colorToString(theme.separator) << "\n";
1718 ss << "separator_hovered=" << colorToString(theme.separator_hovered) << "\n";
1719 ss << "separator_active=" << colorToString(theme.separator_active) << "\n";
1720 ss << "\n";
1721
1722 // Scrollbars and controls
1723 ss << "# Scrollbars and controls\n";
1724 ss << "scrollbar_bg=" << colorToString(theme.scrollbar_bg) << "\n";
1725 ss << "scrollbar_grab=" << colorToString(theme.scrollbar_grab) << "\n";
1726 ss << "scrollbar_grab_hovered=" << colorToString(theme.scrollbar_grab_hovered)
1727 << "\n";
1728 ss << "scrollbar_grab_active=" << colorToString(theme.scrollbar_grab_active)
1729 << "\n";
1730 ss << "resize_grip=" << colorToString(theme.resize_grip) << "\n";
1731 ss << "resize_grip_hovered=" << colorToString(theme.resize_grip_hovered)
1732 << "\n";
1733 ss << "resize_grip_active=" << colorToString(theme.resize_grip_active)
1734 << "\n";
1735 ss << "check_mark=" << colorToString(theme.check_mark) << "\n";
1736 ss << "slider_grab=" << colorToString(theme.slider_grab) << "\n";
1737 ss << "slider_grab_active=" << colorToString(theme.slider_grab_active)
1738 << "\n";
1739 ss << "\n";
1740
1741 // Navigation and special elements
1742 ss << "# Navigation and special elements\n";
1743 ss << "input_text_cursor=" << colorToString(theme.input_text_cursor) << "\n";
1744 ss << "nav_cursor=" << colorToString(theme.nav_cursor) << "\n";
1745 ss << "nav_windowing_highlight="
1746 << colorToString(theme.nav_windowing_highlight) << "\n";
1747 ss << "nav_windowing_dim_bg=" << colorToString(theme.nav_windowing_dim_bg)
1748 << "\n";
1749 ss << "modal_window_dim_bg=" << colorToString(theme.modal_window_dim_bg)
1750 << "\n";
1751 ss << "text_selected_bg=" << colorToString(theme.text_selected_bg) << "\n";
1752 ss << "drag_drop_target=" << colorToString(theme.drag_drop_target) << "\n";
1753 ss << "docking_preview=" << colorToString(theme.docking_preview) << "\n";
1754 ss << "docking_empty_bg=" << colorToString(theme.docking_empty_bg) << "\n";
1755 ss << "\n";
1756
1757 // Table colors
1758 ss << "# Table colors\n";
1759 ss << "table_header_bg=" << colorToString(theme.table_header_bg) << "\n";
1760 ss << "table_border_strong=" << colorToString(theme.table_border_strong)
1761 << "\n";
1762 ss << "table_border_light=" << colorToString(theme.table_border_light)
1763 << "\n";
1764 ss << "table_row_bg=" << colorToString(theme.table_row_bg) << "\n";
1765 ss << "table_row_bg_alt=" << colorToString(theme.table_row_bg_alt) << "\n";
1766 ss << "\n";
1767
1768 // Links and plots
1769 ss << "# Links and plots\n";
1770 ss << "text_link=" << colorToString(theme.text_link) << "\n";
1771 ss << "plot_lines=" << colorToString(theme.plot_lines) << "\n";
1772 ss << "plot_lines_hovered=" << colorToString(theme.plot_lines_hovered)
1773 << "\n";
1774 ss << "plot_histogram=" << colorToString(theme.plot_histogram) << "\n";
1775 ss << "plot_histogram_hovered=" << colorToString(theme.plot_histogram_hovered)
1776 << "\n";
1777 ss << "tree_lines=" << colorToString(theme.tree_lines) << "\n";
1778 ss << "\n";
1779
1780 // Tab variations
1781 ss << "# Tab variations\n";
1782 ss << "tab_dimmed=" << colorToString(theme.tab_dimmed) << "\n";
1783 ss << "tab_dimmed_selected=" << colorToString(theme.tab_dimmed_selected)
1784 << "\n";
1785 ss << "tab_dimmed_selected_overline="
1786 << colorToString(theme.tab_dimmed_selected_overline) << "\n";
1787 ss << "tab_selected_overline=" << colorToString(theme.tab_selected_overline)
1788 << "\n";
1789 ss << "\n";
1790
1791 // Enhanced semantic colors
1792 ss << "# Enhanced semantic colors\n";
1793 ss << "text_highlight=" << colorToString(theme.text_highlight) << "\n";
1794 ss << "link_hover=" << colorToString(theme.link_hover) << "\n";
1795 ss << "code_background=" << colorToString(theme.code_background) << "\n";
1796 ss << "success_light=" << colorToString(theme.success_light) << "\n";
1797 ss << "warning_light=" << colorToString(theme.warning_light) << "\n";
1798 ss << "error_light=" << colorToString(theme.error_light) << "\n";
1799 ss << "info_light=" << colorToString(theme.info_light) << "\n";
1800 ss << "\n";
1801
1802 // UI state colors
1803 ss << "# UI state colors\n";
1804 ss << "active_selection=" << colorToString(theme.active_selection) << "\n";
1805 ss << "hover_highlight=" << colorToString(theme.hover_highlight) << "\n";
1806 ss << "focus_border=" << colorToString(theme.focus_border) << "\n";
1807 ss << "disabled_overlay=" << colorToString(theme.disabled_overlay) << "\n";
1808 ss << "\n";
1809
1810 // Editor-specific colors
1811 ss << "# Editor-specific colors\n";
1812 ss << "editor_background=" << colorToString(theme.editor_background) << "\n";
1813 ss << "editor_grid=" << colorToString(theme.editor_grid) << "\n";
1814 ss << "editor_cursor=" << colorToString(theme.editor_cursor) << "\n";
1815 ss << "editor_selection=" << colorToString(theme.editor_selection) << "\n";
1816 ss << "\n";
1817
1818 // Style settings
1819 ss << "[style]\n";
1820 ss << "window_rounding=" << theme.window_rounding << "\n";
1821 ss << "frame_rounding=" << theme.frame_rounding << "\n";
1822 ss << "scrollbar_rounding=" << theme.scrollbar_rounding << "\n";
1823 ss << "tab_rounding=" << theme.tab_rounding << "\n";
1824 ss << "enable_animations=" << (theme.enable_animations ? "true" : "false")
1825 << "\n";
1826 ss << "enable_glow_effects=" << (theme.enable_glow_effects ? "true" : "false")
1827 << "\n";
1828
1829 return ss.str();
1830}
1831
1833 nlohmann::json j;
1834 const auto& t = current_theme_;
1835
1836 j["name"] = t.name;
1837 j["description"] = t.description;
1838 j["author"] = t.author;
1839
1840 // Helper to convert Color to hex string (#RRGGBB or #RRGGBBAA)
1841 auto colorToHex = [](const Color& c) -> std::string {
1842 int r = static_cast<int>(c.red * 255.0f);
1843 int g = static_cast<int>(c.green * 255.0f);
1844 int b = static_cast<int>(c.blue * 255.0f);
1845 int a = static_cast<int>(c.alpha * 255.0f);
1846 if (a == 255) {
1847 return absl::StrFormat("#%02X%02X%02X", r, g, b);
1848 }
1849 return absl::StrFormat("#%02X%02X%02X%02X", r, g, b, a);
1850 };
1851
1852 j["colors"] = {{"primary", colorToHex(t.primary)},
1853 {"secondary", colorToHex(t.secondary)},
1854 {"accent", colorToHex(t.accent)},
1855 {"background", colorToHex(t.background)},
1856 {"surface", colorToHex(t.surface)},
1857 {"error", colorToHex(t.error)},
1858 {"warning", colorToHex(t.warning)},
1859 {"success", colorToHex(t.success)},
1860 {"info", colorToHex(t.info)},
1861 {"text_primary", colorToHex(t.text_primary)},
1862 {"text_secondary", colorToHex(t.text_secondary)},
1863 {"text_disabled", colorToHex(t.text_disabled)},
1864 {"window_bg", colorToHex(t.window_bg)},
1865 {"child_bg", colorToHex(t.child_bg)},
1866 {"popup_bg", colorToHex(t.popup_bg)},
1867 {"modal_bg", colorToHex(t.modal_bg)},
1868 {"button", colorToHex(t.button)},
1869 {"button_hovered", colorToHex(t.button_hovered)},
1870 {"button_active", colorToHex(t.button_active)},
1871 {"header", colorToHex(t.header)},
1872 {"header_hovered", colorToHex(t.header_hovered)},
1873 {"header_active", colorToHex(t.header_active)},
1874 {"border", colorToHex(t.border)},
1875 {"border_shadow", colorToHex(t.border_shadow)},
1876 {"separator", colorToHex(t.separator)},
1877 // Editor semantic colors
1878 {"editor_background", colorToHex(t.editor_background)},
1879 {"editor_grid", colorToHex(t.editor_grid)},
1880 {"editor_cursor", colorToHex(t.editor_cursor)},
1881 {"editor_selection", colorToHex(t.editor_selection)},
1882 // Enhanced semantic colors
1883 {"code_background", colorToHex(t.code_background)},
1884 {"text_highlight", colorToHex(t.text_highlight)},
1885 {"link_hover", colorToHex(t.link_hover)}};
1886
1887 j["style"] = {{"window_rounding", t.window_rounding},
1888 {"frame_rounding", t.frame_rounding},
1889 {"compact_factor", t.compact_factor}};
1890
1891 return j.dump();
1892}
1893
1894absl::Status ThemeManager::SaveThemeToFile(const Theme& theme,
1895 const std::string& filepath) const {
1896 std::string theme_content = SerializeTheme(theme);
1897
1898 std::ofstream file(filepath);
1899 if (!file.is_open()) {
1900 return absl::InternalError(
1901 absl::StrFormat("Failed to open file for writing: %s", filepath));
1902 }
1903
1904 file << theme_content;
1905 file.close();
1906
1907 if (file.fail()) {
1908 return absl::InternalError(
1909 absl::StrFormat("Failed to write theme file: %s", filepath));
1910 }
1911
1912 return absl::OkStatus();
1913}
1914
1916 // Apply the original ColorsYaze() function directly
1917 ColorsYaze();
1918 current_theme_name_ = "Classic YAZE";
1919
1920 // Create a complete Classic theme object that matches what ColorsYaze() sets
1921 Theme classic_theme;
1922 classic_theme.name = "Classic YAZE";
1923 classic_theme.description =
1924 "Original YAZE theme (direct ColorsYaze() function)";
1925 classic_theme.author = "YAZE Team";
1926
1927 // Extract ALL the colors that ColorsYaze() sets (copy from
1928 // CreateFallbackYazeClassic)
1929 classic_theme.primary = RGBA(92, 115, 92); // allttpLightGreen
1930 classic_theme.secondary = RGBA(71, 92, 71); // alttpMidGreen
1931 classic_theme.accent = RGBA(89, 119, 89); // TabActive
1932 classic_theme.background =
1933 RGBA(8, 8, 8); // Very dark gray for better grid visibility
1934
1935 classic_theme.text_primary = RGBA(230, 230, 230); // 0.90f, 0.90f, 0.90f
1936 classic_theme.text_disabled = RGBA(153, 153, 153); // 0.60f, 0.60f, 0.60f
1937 classic_theme.window_bg =
1938 RGBA(8, 8, 8, 217); // Very dark gray with same alpha
1939 classic_theme.child_bg = RGBA(0, 0, 0, 0); // Transparent
1940 classic_theme.popup_bg = RGBA(28, 28, 36, 235); // 0.11f, 0.11f, 0.14f, 0.92f
1941
1942 classic_theme.button = RGBA(71, 92, 71); // alttpMidGreen
1943 classic_theme.button_hovered = RGBA(125, 146, 125); // allttpLightestGreen
1944 classic_theme.button_active = RGBA(92, 115, 92); // allttpLightGreen
1945
1946 classic_theme.header = RGBA(46, 66, 46); // alttpDarkGreen
1947 classic_theme.header_hovered = RGBA(92, 115, 92); // allttpLightGreen
1948 classic_theme.header_active = RGBA(71, 92, 71); // alttpMidGreen
1949
1950 classic_theme.menu_bar_bg = RGBA(46, 66, 46); // alttpDarkGreen
1951 classic_theme.tab = RGBA(46, 66, 46); // alttpDarkGreen
1952 classic_theme.tab_hovered = RGBA(71, 92, 71); // alttpMidGreen
1953 classic_theme.tab_active = RGBA(89, 119, 89); // TabActive
1954 classic_theme.tab_unfocused = RGBA(37, 52, 37); // Darker version of tab
1955 classic_theme.tab_unfocused_active =
1956 RGBA(62, 83, 62); // Darker version of tab_active
1957
1958 // Complete all remaining ImGui colors from original ColorsYaze() function
1959 classic_theme.title_bg = RGBA(71, 92, 71); // alttpMidGreen
1960 classic_theme.title_bg_active = RGBA(46, 66, 46); // alttpDarkGreen
1961 classic_theme.title_bg_collapsed = RGBA(71, 92, 71); // alttpMidGreen
1962
1963 // Initialize missing fields that were added to the struct
1964 classic_theme.surface = classic_theme.background;
1965 classic_theme.error = RGBA(220, 50, 50);
1966 classic_theme.warning = RGBA(255, 200, 50);
1967 classic_theme.success = classic_theme.primary;
1968 classic_theme.info = RGBA(70, 170, 255);
1969 classic_theme.text_secondary = RGBA(200, 200, 200);
1970 classic_theme.modal_bg = classic_theme.popup_bg;
1971
1972 // Borders and separators
1973 classic_theme.border = RGBA(92, 115, 92); // allttpLightGreen
1974 classic_theme.border_shadow = RGBA(0, 0, 0, 0); // Transparent
1975 classic_theme.separator =
1976 RGBA(128, 128, 128, 153); // 0.50f, 0.50f, 0.50f, 0.60f
1977 classic_theme.separator_hovered = RGBA(153, 153, 178); // 0.60f, 0.60f, 0.70f
1978 classic_theme.separator_active = RGBA(178, 178, 230); // 0.70f, 0.70f, 0.90f
1979
1980 // Scrollbars
1981 classic_theme.scrollbar_bg =
1982 RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
1983 classic_theme.scrollbar_grab =
1984 RGBA(92, 115, 92, 76); // 0.36f, 0.45f, 0.36f, 0.30f
1985 classic_theme.scrollbar_grab_hovered =
1986 RGBA(92, 115, 92, 102); // 0.36f, 0.45f, 0.36f, 0.40f
1987 classic_theme.scrollbar_grab_active =
1988 RGBA(92, 115, 92, 153); // 0.36f, 0.45f, 0.36f, 0.60f
1989
1990 // ENHANCED: Frame colors for inputs/widgets
1991 classic_theme.frame_bg =
1992 RGBA(46, 66, 46, 140); // Darker green with some transparency
1993 classic_theme.frame_bg_hovered =
1994 RGBA(71, 92, 71, 170); // Mid green when hovered
1995 classic_theme.frame_bg_active =
1996 RGBA(92, 115, 92, 200); // Light green when active
1997
1998 // FIXED: Resize grips with better visibility
1999 classic_theme.resize_grip = RGBA(92, 115, 92, 80); // Theme green, subtle
2000 classic_theme.resize_grip_hovered =
2001 RGBA(125, 146, 125, 180); // Brighter when hovered
2002 classic_theme.resize_grip_active =
2003 RGBA(125, 146, 125, 255); // Solid when active
2004
2005 // FIXED: Checkmark - bright green for high visibility!
2006 classic_theme.check_mark =
2007 RGBA(125, 255, 125, 255); // Bright green (clearly visible)
2008
2009 // FIXED: Sliders with theme colors
2010 classic_theme.slider_grab = RGBA(92, 115, 92, 255); // Theme green (solid)
2011 classic_theme.slider_grab_active =
2012 RGBA(125, 146, 125, 255); // Lighter when grabbed
2013
2014 // FIXED: Input cursor - white for maximum visibility
2015 classic_theme.input_text_cursor =
2016 RGBA(255, 255, 255, 255); // White cursor (always visible)
2017
2018 // FIXED: Navigation with theme colors
2019 classic_theme.nav_cursor =
2020 RGBA(125, 146, 125, 255); // Light green navigation
2021 classic_theme.nav_windowing_highlight =
2022 RGBA(92, 115, 92, 200); // Theme green highlight
2023 classic_theme.nav_windowing_dim_bg = RGBA(0, 0, 0, 150); // Darker overlay
2024
2025 // FIXED: Modals with better dimming
2026 classic_theme.modal_window_dim_bg = RGBA(0, 0, 0, 128); // 50% alpha
2027
2028 // FIXED: Text selection - visible and theme-appropriate!
2029 classic_theme.text_selected_bg =
2030 RGBA(92, 115, 92, 128); // Theme green with 50% alpha (visible!)
2031
2032 // FIXED: Drag/drop target with high visibility
2033 classic_theme.drag_drop_target = RGBA(125, 146, 125, 200); // Bright green
2034 classic_theme.table_header_bg = RGBA(46, 66, 46);
2035 classic_theme.table_border_strong = RGBA(71, 92, 71);
2036 classic_theme.table_border_light = RGBA(66, 66, 71);
2037 classic_theme.table_row_bg = RGBA(0, 0, 0, 0);
2038 classic_theme.table_row_bg_alt = RGBA(255, 255, 255, 18);
2039 classic_theme.text_link = classic_theme.accent;
2040 classic_theme.plot_lines = RGBA(255, 255, 255);
2041 classic_theme.plot_lines_hovered = RGBA(230, 178, 0);
2042 classic_theme.plot_histogram = RGBA(230, 178, 0);
2043 classic_theme.plot_histogram_hovered = RGBA(255, 153, 0);
2044 classic_theme.docking_preview = RGBA(92, 115, 92, 180);
2045 classic_theme.docking_empty_bg = RGBA(46, 66, 46, 255);
2046 classic_theme.tree_lines =
2047 classic_theme.separator; // Use separator color for tree lines
2048
2049 // Tab dimmed colors (for unfocused tabs)
2050 classic_theme.tab_dimmed = RGBA(37, 52, 37); // Darker version of tab
2051 classic_theme.tab_dimmed_selected =
2052 RGBA(62, 83, 62); // Darker version of tab_active
2053 classic_theme.tab_dimmed_selected_overline = classic_theme.accent;
2054 classic_theme.tab_selected_overline = classic_theme.accent;
2055
2056 // Enhanced semantic colors for better theming
2057 classic_theme.text_highlight =
2058 RGBA(255, 255, 150); // Light yellow for highlights
2059 classic_theme.link_hover =
2060 RGBA(140, 220, 255); // Brighter blue for link hover
2061 classic_theme.code_background =
2062 RGBA(40, 60, 40); // Slightly darker green for code
2063 classic_theme.success_light = RGBA(140, 195, 140); // Light green
2064 classic_theme.warning_light = RGBA(255, 220, 100); // Light yellow
2065 classic_theme.error_light = RGBA(255, 150, 150); // Light red
2066 classic_theme.info_light = RGBA(150, 200, 255); // Light blue
2067
2068 // UI state colors
2069 classic_theme.active_selection =
2070 classic_theme.accent; // Use accent color for active selection
2071 classic_theme.hover_highlight =
2072 RGBA(92, 115, 92, 100); // Semi-transparent green
2073 classic_theme.focus_border = classic_theme.primary; // Use primary for focus
2074 classic_theme.disabled_overlay = RGBA(50, 50, 50, 128); // Gray overlay
2075
2076 // Editor-specific colors
2077 classic_theme.editor_background = RGBA(30, 45, 30); // Dark green background
2078 classic_theme.editor_grid = RGBA(80, 100, 80, 100); // Subtle grid lines
2079 classic_theme.editor_cursor = RGBA(255, 255, 255); // White cursor
2080 classic_theme.editor_selection =
2081 RGBA(110, 145, 110, 100); // Semi-transparent selection
2082
2083 // Apply original style settings
2084 classic_theme.window_rounding = 0.0f;
2085 classic_theme.frame_rounding = 5.0f;
2086 classic_theme.scrollbar_rounding = 5.0f;
2087 classic_theme.tab_rounding = 0.0f;
2088 classic_theme.enable_glow_effects = false;
2089
2090 // DON'T add Classic theme to themes map - keep it as a special case
2091 // themes_["Classic YAZE"] = classic_theme; // REMOVED to prevent off-by-one
2092 current_theme_ = classic_theme;
2093}
2094
2095void ThemeManager::StartPreview(const std::string& theme_name) {
2096 // Don't start a new preview if already previewing
2097 if (preview_active_) {
2098 // If previewing a different theme, just switch to the new one
2099 if (themes_.contains(theme_name)) {
2100 ApplyTheme(themes_.at(theme_name));
2101 }
2102 return;
2103 }
2104
2105 // Store the original theme state before preview
2108 preview_active_ = true;
2109
2110 // Apply the preview theme
2111 if (themes_.contains(theme_name)) {
2112 ApplyTheme(themes_.at(theme_name));
2113 }
2114}
2115
2117 if (!preview_active_) {
2118 return;
2119 }
2120
2121 // Restore the original theme
2124
2125 // Re-apply the original theme's colors to ImGui
2127
2128 preview_active_ = false;
2129}
2130
2132 return preview_active_;
2133}
2134
2136 if (!p_open || !*p_open)
2137 return;
2138
2139 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
2140
2141 if (ImGui::Begin(absl::StrFormat("%s Theme Editor", ICON_MD_PALETTE).c_str(),
2142 p_open, ImGuiWindowFlags_MenuBar)) {
2143 // Add gentle particle effects to theme editor background
2144 static float editor_animation_time = 0.0f;
2145 editor_animation_time += ImGui::GetIO().DeltaTime;
2146
2147 ImDrawList* draw_list = ImGui::GetWindowDrawList();
2148 ImVec2 window_pos = ImGui::GetWindowPos();
2149 ImVec2 window_size = ImGui::GetWindowSize();
2150
2151 // Floating color orbs representing different color categories
2152 auto current_theme = GetCurrentTheme();
2153 std::vector<gui::Color> theme_colors = {
2154 current_theme.primary, current_theme.secondary, current_theme.accent,
2155 current_theme.success, current_theme.warning, current_theme.error};
2156
2157 for (size_t i = 0; i < theme_colors.size(); ++i) {
2158 float time_offset = i * 1.0f;
2159 float orbit_radius = 60.0f + i * 8.0f;
2160 float x = window_pos.x + window_size.x * 0.8f +
2161 cosf(editor_animation_time * 0.3f + time_offset) * orbit_radius;
2162 float y = window_pos.y + window_size.y * 0.3f +
2163 sinf(editor_animation_time * 0.3f + time_offset) * orbit_radius;
2164
2165 float alpha =
2166 0.15f + 0.1f * sinf(editor_animation_time * 1.5f + time_offset);
2167 ImU32 orb_color = ImGui::ColorConvertFloat4ToU32(
2168 ImVec4(theme_colors[i].red, theme_colors[i].green,
2169 theme_colors[i].blue, alpha));
2170
2171 float radius =
2172 4.0f + sinf(editor_animation_time * 2.0f + time_offset) * 1.0f;
2173 draw_list->AddCircleFilled(ImVec2(x, y), radius, orb_color);
2174 }
2175
2176 // Menu bar for theme operations
2177 if (ImGui::BeginMenuBar()) {
2178 if (ImGui::BeginMenu("File")) {
2179 if (ImGui::MenuItem(
2180 absl::StrFormat("%s New Theme", ICON_MD_ADD).c_str())) {
2181 // Reset to default theme
2183 }
2184 if (ImGui::MenuItem(
2185 absl::StrFormat("%s Load Theme", ICON_MD_FOLDER_OPEN)
2186 .c_str())) {
2188 if (!file_path.empty()) {
2189 (void)LoadThemeFromFile(file_path);
2190 }
2191 }
2192 ImGui::Separator();
2193 if (ImGui::MenuItem(
2194 absl::StrFormat("%s Save Theme", ICON_MD_SAVE).c_str())) {
2195 // Save current theme to its existing file
2196 std::string current_file_path = GetCurrentThemeFilePath();
2197 if (!current_file_path.empty()) {
2198 auto status = SaveThemeToFile(current_theme_, current_file_path);
2199 if (!status.ok()) {
2200 LOG_ERROR("Theme Manager", "Failed to save theme");
2201 }
2202 } else {
2203 // No existing file, prompt for new location
2205 current_theme_.name, "theme");
2206 if (!file_path.empty()) {
2207 auto status = SaveThemeToFile(current_theme_, file_path);
2208 if (!status.ok()) {
2209 LOG_ERROR("Theme Manager", "Failed to save theme");
2210 }
2211 }
2212 }
2213 }
2214 if (ImGui::MenuItem(
2215 absl::StrFormat("%s Save As...", ICON_MD_SAVE_AS).c_str())) {
2216 // Save theme to new file
2218 current_theme_.name, "theme");
2219 if (!file_path.empty()) {
2220 auto status = SaveThemeToFile(current_theme_, file_path);
2221 if (!status.ok()) {
2222 LOG_ERROR("Theme Manager", "Failed to save theme");
2223 }
2224 }
2225 }
2226 ImGui::Separator();
2227 if (ImGui::MenuItem(
2228 absl::StrFormat("%s Export to User Themes", ICON_MD_FOLDER_OPEN)
2229 .c_str())) {
2230 // Export current theme to ~/.yaze/themes/ for cross-platform sharing
2231 std::string user_themes_dir = GetUserThemesDirectory();
2232 std::string safe_name = current_theme_.name.empty()
2233 ? "custom_theme"
2235 // Sanitize filename: replace invalid characters with underscores
2236 for (char& c : safe_name) {
2237 if (c == ' ' || c == '/' || c == '\\' || c == ':' || c == '*' ||
2238 c == '?' || c == '"' || c == '<' || c == '>' || c == '|') {
2239 c = '_';
2240 }
2241 }
2242 std::string file_path = user_themes_dir + safe_name + ".theme";
2243
2244 auto status = SaveThemeToFile(current_theme_, file_path);
2245 if (status.ok()) {
2246 LOG_INFO("Theme Manager", "Exported theme to: %s",
2247 file_path.c_str());
2248 } else {
2249 LOG_ERROR("Theme Manager", "Failed to export theme to user themes");
2250 }
2251 }
2252 ImGui::EndMenu();
2253 }
2254
2255 if (ImGui::BeginMenu("Presets")) {
2256 if (ImGui::MenuItem("YAZE Classic")) {
2258 }
2259
2260 auto available_themes = GetAvailableThemes();
2261 if (!available_themes.empty()) {
2262 ImGui::Separator();
2263 for (const auto& theme_name : available_themes) {
2264 if (ImGui::MenuItem(theme_name.c_str())) {
2265 (void)LoadTheme(theme_name);
2266 }
2267 }
2268 }
2269 ImGui::EndMenu();
2270 }
2271
2272 ImGui::EndMenuBar();
2273 }
2274
2275 static Theme edit_theme = current_theme_;
2276 static char theme_name[128];
2277 static char theme_description[256];
2278 static char theme_author[128];
2279 static bool live_preview = true;
2280 static Theme original_theme; // Store original theme for restoration
2281 static bool theme_backup_made = false;
2282
2283 // Helper lambda for live preview application
2284 auto apply_live_preview = [&]() {
2285 if (live_preview) {
2286 if (!theme_backup_made) {
2287 original_theme = current_theme_;
2288 theme_backup_made = true;
2289 }
2290 // Apply the edit theme directly to ImGui without changing theme manager
2291 // state
2292 edit_theme.ApplyToImGui();
2293 }
2294 };
2295
2296 // Live preview toggle
2297 ImGui::Checkbox("Live Preview", &live_preview);
2298 ImGui::SameLine();
2299 ImGui::Text("| Changes apply immediately when enabled");
2300
2301 // If live preview was just disabled, restore original theme
2302 static bool prev_live_preview = live_preview;
2303 if (prev_live_preview && !live_preview && theme_backup_made) {
2304 ApplyTheme(original_theme);
2305 theme_backup_made = false;
2306 }
2307 prev_live_preview = live_preview;
2308
2309 ImGui::Separator();
2310
2311 // Theme metadata in a table for better layout
2312 if (ImGui::BeginTable("ThemeMetadata", 2,
2313 ImGuiTableFlags_SizingStretchProp)) {
2314 ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed,
2315 100.0f);
2316 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
2317
2318 ImGui::TableNextRow();
2319 ImGui::TableNextColumn();
2320 ImGui::AlignTextToFramePadding();
2321 ImGui::Text("Name:");
2322 ImGui::TableNextColumn();
2323 if (ImGui::InputText("##theme_name", theme_name, sizeof(theme_name))) {
2324 edit_theme.name = std::string(theme_name);
2325 }
2326
2327 ImGui::TableNextRow();
2328 ImGui::TableNextColumn();
2329 ImGui::AlignTextToFramePadding();
2330 ImGui::Text("Description:");
2331 ImGui::TableNextColumn();
2332 if (ImGui::InputText("##theme_description", theme_description,
2333 sizeof(theme_description))) {
2334 edit_theme.description = std::string(theme_description);
2335 }
2336
2337 ImGui::TableNextRow();
2338 ImGui::TableNextColumn();
2339 ImGui::AlignTextToFramePadding();
2340 ImGui::Text("Author:");
2341 ImGui::TableNextColumn();
2342 if (ImGui::InputText("##theme_author", theme_author,
2343 sizeof(theme_author))) {
2344 edit_theme.author = std::string(theme_author);
2345 }
2346
2347 ImGui::EndTable();
2348 }
2349
2350 ImGui::Separator();
2351
2352 // Enhanced theme editing with tabs for better organization
2353 if (ImGui::BeginTabBar("ThemeEditorTabs", ImGuiTabBarFlags_None)) {
2354 // Apply live preview on first frame if enabled
2355 static bool first_frame = true;
2356 if (first_frame && live_preview) {
2357 apply_live_preview();
2358 first_frame = false;
2359 }
2360
2361 // Primary Colors Tab
2362 if (ImGui::BeginTabItem(
2363 absl::StrFormat("%s Primary", ICON_MD_COLOR_LENS).c_str())) {
2364
2365 // Accent Color Generator Section
2366 ImGui::PushStyleColor(ImGuiCol_ChildBg,
2367 ImVec4(0.1f, 0.1f, 0.12f, 0.5f));
2368 ImGui::BeginChild("AccentGenerator", ImVec2(0, 120), true);
2369
2370 ImGui::TextColored(ImVec4(0.7f, 0.8f, 1.0f, 1.0f),
2371 "%s Generate Theme from Accent Color",
2373 ImGui::Separator();
2374
2375 static Color accent_picker_color = {0.4f, 0.6f, 0.9f, 1.0f};
2376 static bool dark_mode_generate = true;
2377
2378 ImGui::Text("Accent Color:");
2379 ImGui::SameLine();
2380 ImVec4 accent_vec = ConvertColorToImVec4(accent_picker_color);
2381 if (ImGui::ColorEdit3("##AccentPicker", &accent_vec.x,
2382 ImGuiColorEditFlags_NoInputs)) {
2383 accent_picker_color = {accent_vec.x, accent_vec.y, accent_vec.z,
2384 1.0f};
2385 }
2386
2387 ImGui::SameLine();
2388 ImGui::Checkbox("Dark Mode", &dark_mode_generate);
2389
2390 ImGui::SameLine();
2391 if (ImGui::Button(absl::StrFormat("%s Generate", ICON_MD_BOLT).c_str(),
2392 ImVec2(120, 0))) {
2393 // Generate and apply theme from accent color
2394 Theme generated =
2395 GenerateThemeFromAccent(accent_picker_color, dark_mode_generate);
2396 edit_theme = generated;
2397 apply_live_preview();
2398 }
2399
2400 // Show color harmony preview
2401 ImGui::Text("Preview: ");
2402 ImGui::SameLine();
2403
2404 // Show primary, secondary, background as color swatches
2405 Theme preview_theme =
2406 GenerateThemeFromAccent(accent_picker_color, dark_mode_generate);
2407 ImVec4 prev_primary = ConvertColorToImVec4(preview_theme.primary);
2408 ImVec4 prev_secondary = ConvertColorToImVec4(preview_theme.secondary);
2409 ImVec4 prev_bg = ConvertColorToImVec4(preview_theme.background);
2410 ImVec4 prev_surface = ConvertColorToImVec4(preview_theme.surface);
2411
2412 ImGui::ColorButton("##prev_primary", prev_primary, 0, ImVec2(24, 24));
2413 ImGui::SameLine(0, 2);
2414 ImGui::ColorButton("##prev_secondary", prev_secondary, 0,
2415 ImVec2(24, 24));
2416 ImGui::SameLine(0, 2);
2417 ImGui::ColorButton("##prev_bg", prev_bg, 0, ImVec2(24, 24));
2418 ImGui::SameLine(0, 2);
2419 ImGui::ColorButton("##prev_surface", prev_surface, 0, ImVec2(24, 24));
2420
2421 ImGui::EndChild();
2422 ImGui::PopStyleColor();
2423
2424 ImGui::Spacing();
2425
2426 if (ImGui::BeginTable("PrimaryColorsTable", 3,
2427 ImGuiTableFlags_SizingStretchProp)) {
2428 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed,
2429 120.0f);
2430 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch,
2431 0.6f);
2432 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch,
2433 0.4f);
2434 ImGui::TableHeadersRow();
2435
2436 // Primary color
2437 ImGui::TableNextRow();
2438 ImGui::TableNextColumn();
2439 ImGui::AlignTextToFramePadding();
2440 ImGui::Text("Primary:");
2441 ImGui::TableNextColumn();
2442 ImVec4 primary = ConvertColorToImVec4(edit_theme.primary);
2443 if (ImGui::ColorEdit3("##primary", &primary.x)) {
2444 edit_theme.primary = {primary.x, primary.y, primary.z, primary.w};
2445 apply_live_preview();
2446 }
2447 ImGui::TableNextColumn();
2448 ImGui::Button("Primary Preview", ImVec2(-1, 30));
2449
2450 // Secondary color
2451 ImGui::TableNextRow();
2452 ImGui::TableNextColumn();
2453 ImGui::AlignTextToFramePadding();
2454 ImGui::Text("Secondary:");
2455 ImGui::TableNextColumn();
2456 ImVec4 secondary = ConvertColorToImVec4(edit_theme.secondary);
2457 if (ImGui::ColorEdit3("##secondary", &secondary.x)) {
2458 edit_theme.secondary = {secondary.x, secondary.y, secondary.z,
2459 secondary.w};
2460 apply_live_preview();
2461 }
2462 ImGui::TableNextColumn();
2463 ImGui::PushStyleColor(ImGuiCol_Button, secondary);
2464 ImGui::Button("Secondary Preview", ImVec2(-1, 30));
2465 ImGui::PopStyleColor();
2466
2467 // Accent color
2468 ImGui::TableNextRow();
2469 ImGui::TableNextColumn();
2470 ImGui::AlignTextToFramePadding();
2471 ImGui::Text("Accent:");
2472 ImGui::TableNextColumn();
2473 ImVec4 accent = ConvertColorToImVec4(edit_theme.accent);
2474 if (ImGui::ColorEdit3("##accent", &accent.x)) {
2475 edit_theme.accent = {accent.x, accent.y, accent.z, accent.w};
2476 apply_live_preview();
2477 }
2478 ImGui::TableNextColumn();
2479 ImGui::PushStyleColor(ImGuiCol_Button, accent);
2480 ImGui::Button("Accent Preview", ImVec2(-1, 30));
2481 ImGui::PopStyleColor();
2482
2483 // Background color
2484 ImGui::TableNextRow();
2485 ImGui::TableNextColumn();
2486 ImGui::AlignTextToFramePadding();
2487 ImGui::Text("Background:");
2488 ImGui::TableNextColumn();
2489 ImVec4 background = ConvertColorToImVec4(edit_theme.background);
2490 if (ImGui::ColorEdit4("##background", &background.x)) {
2491 edit_theme.background = {background.x, background.y, background.z,
2492 background.w};
2493 apply_live_preview();
2494 }
2495 ImGui::TableNextColumn();
2496 ImGui::Text("Background preview shown in window");
2497
2498 ImGui::EndTable();
2499 }
2500 ImGui::EndTabItem();
2501 }
2502
2503 // Text Colors Tab
2504 if (ImGui::BeginTabItem(
2505 absl::StrFormat("%s Text", ICON_MD_TEXT_FIELDS).c_str())) {
2506 if (ImGui::BeginTable("TextColorsTable", 3,
2507 ImGuiTableFlags_SizingStretchProp)) {
2508 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed,
2509 120.0f);
2510 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch,
2511 0.6f);
2512 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch,
2513 0.4f);
2514 ImGui::TableHeadersRow();
2515
2516 // Text colors with live preview
2517 auto text_colors = std::vector<std::pair<const char*, Color*>>{
2518 {"Primary Text", &edit_theme.text_primary},
2519 {"Secondary Text", &edit_theme.text_secondary},
2520 {"Disabled Text", &edit_theme.text_disabled},
2521 {"Link Text", &edit_theme.text_link},
2522 {"Text Highlight", &edit_theme.text_highlight},
2523 {"Link Hover", &edit_theme.link_hover},
2524 {"Text Selected BG", &edit_theme.text_selected_bg},
2525 {"Input Text Cursor", &edit_theme.input_text_cursor}};
2526
2527 for (auto& [label, color_ptr] : text_colors) {
2528 ImGui::TableNextRow();
2529 ImGui::TableNextColumn();
2530 ImGui::AlignTextToFramePadding();
2531 ImGui::Text("%s:", label);
2532
2533 ImGui::TableNextColumn();
2534 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2535 std::string id = absl::StrFormat("##%s", label);
2536 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
2537 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
2538 apply_live_preview();
2539 }
2540
2541 ImGui::TableNextColumn();
2542 ImGui::PushStyleColor(ImGuiCol_Text, color_vec);
2543 ImGui::Text("Sample %s", label);
2544 ImGui::PopStyleColor();
2545 }
2546
2547 ImGui::EndTable();
2548 }
2549 ImGui::EndTabItem();
2550 }
2551
2552 // Interactive Elements Tab
2553 if (ImGui::BeginTabItem(
2554 absl::StrFormat("%s Interactive", ICON_MD_TOUCH_APP).c_str())) {
2555 if (ImGui::BeginTable("InteractiveColorsTable", 3,
2556 ImGuiTableFlags_SizingStretchProp)) {
2557 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed,
2558 120.0f);
2559 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch,
2560 0.6f);
2561 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch,
2562 0.4f);
2563 ImGui::TableHeadersRow();
2564
2565 // Interactive element colors
2566 auto interactive_colors =
2567 std::vector<std::tuple<const char*, Color*, ImGuiCol>>{
2568 {"Button", &edit_theme.button, ImGuiCol_Button},
2569 {"Button Hovered", &edit_theme.button_hovered,
2570 ImGuiCol_ButtonHovered},
2571 {"Button Active", &edit_theme.button_active,
2572 ImGuiCol_ButtonActive},
2573 {"Frame Background", &edit_theme.frame_bg, ImGuiCol_FrameBg},
2574 {"Frame BG Hovered", &edit_theme.frame_bg_hovered,
2575 ImGuiCol_FrameBgHovered},
2576 {"Frame BG Active", &edit_theme.frame_bg_active,
2577 ImGuiCol_FrameBgActive},
2578 {"Check Mark", &edit_theme.check_mark, ImGuiCol_CheckMark},
2579 {"Slider Grab", &edit_theme.slider_grab, ImGuiCol_SliderGrab},
2580 {"Slider Grab Active", &edit_theme.slider_grab_active,
2581 ImGuiCol_SliderGrabActive}};
2582
2583 for (auto& [label, color_ptr, imgui_col] : interactive_colors) {
2584 ImGui::TableNextRow();
2585 ImGui::TableNextColumn();
2586 ImGui::AlignTextToFramePadding();
2587 ImGui::Text("%s:", label);
2588
2589 ImGui::TableNextColumn();
2590 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2591 std::string id = absl::StrFormat("##%s", label);
2592 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
2593 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
2594 apply_live_preview();
2595 }
2596
2597 ImGui::TableNextColumn();
2598 ImGui::PushStyleColor(imgui_col, color_vec);
2599 ImGui::Button(absl::StrFormat("Preview %s", label).c_str(),
2600 ImVec2(-1, 30));
2601 ImGui::PopStyleColor();
2602 }
2603
2604 ImGui::EndTable();
2605 }
2606 ImGui::EndTabItem();
2607 }
2608
2609 // Style Parameters Tab
2610 if (ImGui::BeginTabItem(
2611 absl::StrFormat("%s Style", ICON_MD_TUNE).c_str())) {
2612 ImGui::Text("Rounding and Border Settings:");
2613
2614 if (ImGui::SliderFloat("Window Rounding", &edit_theme.window_rounding,
2615 0.0f, 20.0f)) {
2616 if (live_preview)
2617 ApplyTheme(edit_theme);
2618 }
2619 if (ImGui::SliderFloat("Frame Rounding", &edit_theme.frame_rounding,
2620 0.0f, 20.0f)) {
2621 if (live_preview)
2622 ApplyTheme(edit_theme);
2623 }
2624 if (ImGui::SliderFloat("Scrollbar Rounding",
2625 &edit_theme.scrollbar_rounding, 0.0f, 20.0f)) {
2626 if (live_preview)
2627 ApplyTheme(edit_theme);
2628 }
2629 if (ImGui::SliderFloat("Tab Rounding", &edit_theme.tab_rounding, 0.0f,
2630 20.0f)) {
2631 if (live_preview)
2632 ApplyTheme(edit_theme);
2633 }
2634 if (ImGui::SliderFloat("Grab Rounding", &edit_theme.grab_rounding, 0.0f,
2635 20.0f)) {
2636 if (live_preview)
2637 ApplyTheme(edit_theme);
2638 }
2639
2640 ImGui::Separator();
2641 ImGui::Text("Border Sizes:");
2642
2643 if (ImGui::SliderFloat("Window Border Size",
2644 &edit_theme.window_border_size, 0.0f, 3.0f)) {
2645 if (live_preview)
2646 ApplyTheme(edit_theme);
2647 }
2648 if (ImGui::SliderFloat("Frame Border Size",
2649 &edit_theme.frame_border_size, 0.0f, 3.0f)) {
2650 if (live_preview)
2651 ApplyTheme(edit_theme);
2652 }
2653
2654 ImGui::Separator();
2655 ImGui::Text("Animation & Effects:");
2656
2657 if (ImGui::Checkbox("Enable Animations",
2658 &edit_theme.enable_animations)) {
2659 if (live_preview)
2660 ApplyTheme(edit_theme);
2661 }
2662 if (edit_theme.enable_animations) {
2663 if (ImGui::SliderFloat("Animation Speed", &edit_theme.animation_speed,
2664 0.1f, 3.0f)) {
2665 apply_live_preview();
2666 }
2667 }
2668 if (ImGui::Checkbox("Enable Glow Effects",
2669 &edit_theme.enable_glow_effects)) {
2670 if (live_preview)
2671 ApplyTheme(edit_theme);
2672 }
2673
2674 ImGui::EndTabItem();
2675 }
2676
2677 // Navigation & Windows Tab
2678 if (ImGui::BeginTabItem(
2679 absl::StrFormat("%s Navigation", ICON_MD_NAVIGATION).c_str())) {
2680 if (ImGui::BeginTable("NavigationTable", 3,
2681 ImGuiTableFlags_SizingStretchProp)) {
2682 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed,
2683 120.0f);
2684 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch,
2685 0.6f);
2686 ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch,
2687 0.4f);
2688 ImGui::TableHeadersRow();
2689
2690 // Window colors
2691 auto window_colors =
2692 std::vector<std::tuple<const char*, Color*, const char*>>{
2693 {"Window Background", &edit_theme.window_bg,
2694 "Main window background"},
2695 {"Child Background", &edit_theme.child_bg,
2696 "Child window background"},
2697 {"Popup Background", &edit_theme.popup_bg,
2698 "Popup window background"},
2699 {"Modal Background", &edit_theme.modal_bg,
2700 "Modal window background"},
2701 {"Menu Bar BG", &edit_theme.menu_bar_bg,
2702 "Menu bar background"}};
2703
2704 for (auto& [label, color_ptr, description] : window_colors) {
2705 ImGui::TableNextRow();
2706 ImGui::TableNextColumn();
2707 ImGui::AlignTextToFramePadding();
2708 ImGui::Text("%s:", label);
2709
2710 ImGui::TableNextColumn();
2711 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2712 std::string id = absl::StrFormat("##window_%s", label);
2713 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
2714 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
2715 apply_live_preview();
2716 }
2717
2718 ImGui::TableNextColumn();
2719 ImGui::TextWrapped("%s", description);
2720 }
2721
2722 ImGui::EndTable();
2723 }
2724
2725 ImGui::Separator();
2726
2727 // Header and Tab colors
2728 if (ImGui::CollapsingHeader("Headers & Tabs",
2729 ImGuiTreeNodeFlags_DefaultOpen)) {
2730 if (ImGui::BeginTable("HeaderTabTable", 3,
2731 ImGuiTableFlags_SizingStretchProp)) {
2732 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed,
2733 120.0f);
2734 ImGui::TableSetupColumn("Picker",
2735 ImGuiTableColumnFlags_WidthStretch, 0.6f);
2736 ImGui::TableSetupColumn("Preview",
2737 ImGuiTableColumnFlags_WidthStretch, 0.4f);
2738 ImGui::TableHeadersRow();
2739
2740 auto header_tab_colors =
2741 std::vector<std::pair<const char*, Color*>>{
2742 {"Header", &edit_theme.header},
2743 {"Header Hovered", &edit_theme.header_hovered},
2744 {"Header Active", &edit_theme.header_active},
2745 {"Tab", &edit_theme.tab},
2746 {"Tab Hovered", &edit_theme.tab_hovered},
2747 {"Tab Active", &edit_theme.tab_active},
2748 {"Tab Unfocused", &edit_theme.tab_unfocused},
2749 {"Tab Unfocused Active", &edit_theme.tab_unfocused_active},
2750 {"Tab Dimmed", &edit_theme.tab_dimmed},
2751 {"Tab Dimmed Selected", &edit_theme.tab_dimmed_selected},
2752 {"Title Background", &edit_theme.title_bg},
2753 {"Title BG Active", &edit_theme.title_bg_active},
2754 {"Title BG Collapsed", &edit_theme.title_bg_collapsed}};
2755
2756 for (auto& [label, color_ptr] : header_tab_colors) {
2757 ImGui::TableNextRow();
2758 ImGui::TableNextColumn();
2759 ImGui::AlignTextToFramePadding();
2760 ImGui::Text("%s:", label);
2761
2762 ImGui::TableNextColumn();
2763 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2764 std::string id = absl::StrFormat("##header_%s", label);
2765 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
2766 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
2767 color_vec.w};
2768 apply_live_preview();
2769 }
2770
2771 ImGui::TableNextColumn();
2772 ImGui::PushStyleColor(ImGuiCol_Button, color_vec);
2773 ImGui::Button(absl::StrFormat("Preview %s", label).c_str(),
2774 ImVec2(-1, 25));
2775 ImGui::PopStyleColor();
2776 }
2777
2778 ImGui::EndTable();
2779 }
2780 }
2781
2782 // Navigation and Special Elements
2783 if (ImGui::CollapsingHeader("Navigation & Special")) {
2784 if (ImGui::BeginTable("NavSpecialTable", 3,
2785 ImGuiTableFlags_SizingStretchProp)) {
2786 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed,
2787 120.0f);
2788 ImGui::TableSetupColumn("Picker",
2789 ImGuiTableColumnFlags_WidthStretch, 0.6f);
2790 ImGui::TableSetupColumn("Description",
2791 ImGuiTableColumnFlags_WidthStretch, 0.4f);
2792 ImGui::TableHeadersRow();
2793
2794 auto nav_special_colors =
2795 std::vector<std::tuple<const char*, Color*, const char*>>{
2796 {"Nav Cursor", &edit_theme.nav_cursor,
2797 "Navigation cursor color"},
2798 {"Nav Win Highlight", &edit_theme.nav_windowing_highlight,
2799 "Window selection highlight"},
2800 {"Nav Win Dim BG", &edit_theme.nav_windowing_dim_bg,
2801 "Background dimming for navigation"},
2802 {"Modal Win Dim BG", &edit_theme.modal_window_dim_bg,
2803 "Background dimming for modals"},
2804 {"Drag Drop Target", &edit_theme.drag_drop_target,
2805 "Drag and drop target highlight"},
2806 {"Docking Preview", &edit_theme.docking_preview,
2807 "Docking area preview"},
2808 {"Docking Empty BG", &edit_theme.docking_empty_bg,
2809 "Empty docking space background"}};
2810
2811 for (auto& [label, color_ptr, description] : nav_special_colors) {
2812 ImGui::TableNextRow();
2813 ImGui::TableNextColumn();
2814 ImGui::AlignTextToFramePadding();
2815 ImGui::Text("%s:", label);
2816
2817 ImGui::TableNextColumn();
2818 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2819 std::string id = absl::StrFormat("##nav_%s", label);
2820 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
2821 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
2822 color_vec.w};
2823 apply_live_preview();
2824 }
2825
2826 ImGui::TableNextColumn();
2827 ImGui::TextWrapped("%s", description);
2828 }
2829
2830 ImGui::EndTable();
2831 }
2832 }
2833
2834 ImGui::EndTabItem();
2835 }
2836
2837 // Tables & Data Tab
2838 if (ImGui::BeginTabItem(
2839 absl::StrFormat("%s Tables", ICON_MD_TABLE_CHART).c_str())) {
2840 if (ImGui::BeginTable("TablesDataTable", 3,
2841 ImGuiTableFlags_SizingStretchProp)) {
2842 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed,
2843 120.0f);
2844 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch,
2845 0.6f);
2846 ImGui::TableSetupColumn("Description",
2847 ImGuiTableColumnFlags_WidthStretch, 0.4f);
2848 ImGui::TableHeadersRow();
2849
2850 auto table_colors =
2851 std::vector<std::tuple<const char*, Color*, const char*>>{
2852 {"Table Header BG", &edit_theme.table_header_bg,
2853 "Table column headers"},
2854 {"Table Border Strong", &edit_theme.table_border_strong,
2855 "Outer table borders"},
2856 {"Table Border Light", &edit_theme.table_border_light,
2857 "Inner table borders"},
2858 {"Table Row BG", &edit_theme.table_row_bg,
2859 "Normal table rows"},
2860 {"Table Row BG Alt", &edit_theme.table_row_bg_alt,
2861 "Alternating table rows"},
2862 {"Tree Lines", &edit_theme.tree_lines,
2863 "Tree view connection lines"}};
2864
2865 for (auto& [label, color_ptr, description] : table_colors) {
2866 ImGui::TableNextRow();
2867 ImGui::TableNextColumn();
2868 ImGui::AlignTextToFramePadding();
2869 ImGui::Text("%s:", label);
2870
2871 ImGui::TableNextColumn();
2872 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2873 std::string id = absl::StrFormat("##table_%s", label);
2874 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
2875 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
2876 apply_live_preview();
2877 }
2878
2879 ImGui::TableNextColumn();
2880 ImGui::TextWrapped("%s", description);
2881 }
2882
2883 ImGui::EndTable();
2884 }
2885
2886 ImGui::Separator();
2887
2888 // Plots and Graphs
2889 if (ImGui::CollapsingHeader("Plots & Graphs")) {
2890 if (ImGui::BeginTable("PlotsTable", 3,
2891 ImGuiTableFlags_SizingStretchProp)) {
2892 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed,
2893 120.0f);
2894 ImGui::TableSetupColumn("Picker",
2895 ImGuiTableColumnFlags_WidthStretch, 0.6f);
2896 ImGui::TableSetupColumn("Description",
2897 ImGuiTableColumnFlags_WidthStretch, 0.4f);
2898 ImGui::TableHeadersRow();
2899
2900 auto plot_colors =
2901 std::vector<std::tuple<const char*, Color*, const char*>>{
2902 {"Plot Lines", &edit_theme.plot_lines, "Line plot color"},
2903 {"Plot Lines Hovered", &edit_theme.plot_lines_hovered,
2904 "Line plot hover color"},
2905 {"Plot Histogram", &edit_theme.plot_histogram,
2906 "Histogram fill color"},
2907 {"Plot Histogram Hovered",
2908 &edit_theme.plot_histogram_hovered,
2909 "Histogram hover color"}};
2910
2911 for (auto& [label, color_ptr, description] : plot_colors) {
2912 ImGui::TableNextRow();
2913 ImGui::TableNextColumn();
2914 ImGui::AlignTextToFramePadding();
2915 ImGui::Text("%s:", label);
2916
2917 ImGui::TableNextColumn();
2918 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2919 std::string id = absl::StrFormat("##plot_%s", label);
2920 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
2921 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
2922 color_vec.w};
2923 apply_live_preview();
2924 }
2925
2926 ImGui::TableNextColumn();
2927 ImGui::TextWrapped("%s", description);
2928 }
2929
2930 ImGui::EndTable();
2931 }
2932 }
2933
2934 ImGui::EndTabItem();
2935 }
2936
2937 // Borders & Controls Tab
2938 if (ImGui::BeginTabItem(
2939 absl::StrFormat("%s Borders", ICON_MD_BORDER_ALL).c_str())) {
2940 if (ImGui::BeginTable("BordersControlsTable", 3,
2941 ImGuiTableFlags_SizingStretchProp)) {
2942 ImGui::TableSetupColumn("Element", ImGuiTableColumnFlags_WidthFixed,
2943 120.0f);
2944 ImGui::TableSetupColumn("Picker", ImGuiTableColumnFlags_WidthStretch,
2945 0.6f);
2946 ImGui::TableSetupColumn("Description",
2947 ImGuiTableColumnFlags_WidthStretch, 0.4f);
2948 ImGui::TableHeadersRow();
2949
2950 auto border_control_colors =
2951 std::vector<std::tuple<const char*, Color*, const char*>>{
2952 {"Border", &edit_theme.border, "General border color"},
2953 {"Border Shadow", &edit_theme.border_shadow,
2954 "Border shadow/depth"},
2955 {"Separator", &edit_theme.separator,
2956 "Horizontal/vertical separators"},
2957 {"Separator Hovered", &edit_theme.separator_hovered,
2958 "Separator hover state"},
2959 {"Separator Active", &edit_theme.separator_active,
2960 "Separator active/dragged state"},
2961 {"Scrollbar BG", &edit_theme.scrollbar_bg,
2962 "Scrollbar track background"},
2963 {"Scrollbar Grab", &edit_theme.scrollbar_grab,
2964 "Scrollbar handle"},
2965 {"Scrollbar Grab Hovered", &edit_theme.scrollbar_grab_hovered,
2966 "Scrollbar handle hover"},
2967 {"Scrollbar Grab Active", &edit_theme.scrollbar_grab_active,
2968 "Scrollbar handle active"},
2969 {"Resize Grip", &edit_theme.resize_grip,
2970 "Window resize grip"},
2971 {"Resize Grip Hovered", &edit_theme.resize_grip_hovered,
2972 "Resize grip hover"},
2973 {"Resize Grip Active", &edit_theme.resize_grip_active,
2974 "Resize grip active"}};
2975
2976 for (auto& [label, color_ptr, description] : border_control_colors) {
2977 ImGui::TableNextRow();
2978 ImGui::TableNextColumn();
2979 ImGui::AlignTextToFramePadding();
2980 ImGui::Text("%s:", label);
2981
2982 ImGui::TableNextColumn();
2983 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
2984 std::string id = absl::StrFormat("##border_%s", label);
2985 if (ImGui::ColorEdit4(id.c_str(), &color_vec.x)) {
2986 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
2987 apply_live_preview();
2988 }
2989
2990 ImGui::TableNextColumn();
2991 ImGui::TextWrapped("%s", description);
2992 }
2993
2994 ImGui::EndTable();
2995 }
2996
2997 ImGui::EndTabItem();
2998 }
2999
3000 // Enhanced Colors Tab
3001 if (ImGui::BeginTabItem(
3002 absl::StrFormat("%s Enhanced", ICON_MD_AUTO_AWESOME).c_str())) {
3003 ImGui::Text(
3004 "Enhanced semantic colors and editor-specific customization");
3005 ImGui::Separator();
3006
3007 // Enhanced semantic colors section
3008 if (ImGui::CollapsingHeader("Enhanced Semantic Colors",
3009 ImGuiTreeNodeFlags_DefaultOpen)) {
3010 if (ImGui::BeginTable("EnhancedSemanticTable", 3,
3011 ImGuiTableFlags_SizingStretchProp)) {
3012 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed,
3013 120.0f);
3014 ImGui::TableSetupColumn("Picker",
3015 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3016 ImGui::TableSetupColumn("Description",
3017 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3018 ImGui::TableHeadersRow();
3019
3020 auto enhanced_colors =
3021 std::vector<std::tuple<const char*, Color*, const char*>>{
3022 {"Code Background", &edit_theme.code_background,
3023 "Code blocks background"},
3024 {"Success Light", &edit_theme.success_light,
3025 "Light success variant"},
3026 {"Warning Light", &edit_theme.warning_light,
3027 "Light warning variant"},
3028 {"Error Light", &edit_theme.error_light,
3029 "Light error variant"},
3030 {"Info Light", &edit_theme.info_light,
3031 "Light info variant"}};
3032
3033 for (auto& [label, color_ptr, description] : enhanced_colors) {
3034 ImGui::TableNextRow();
3035 ImGui::TableNextColumn();
3036 ImGui::AlignTextToFramePadding();
3037 ImGui::Text("%s:", label);
3038
3039 ImGui::TableNextColumn();
3040 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
3041 std::string id = absl::StrFormat("##enhanced_%s", label);
3042 if (ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
3043 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
3044 color_vec.w};
3045 apply_live_preview();
3046 }
3047
3048 ImGui::TableNextColumn();
3049 ImGui::TextWrapped("%s", description);
3050 }
3051
3052 ImGui::EndTable();
3053 }
3054 }
3055
3056 // UI State colors section
3057 if (ImGui::CollapsingHeader("UI State Colors")) {
3058 if (ImGui::BeginTable("UIStateTable", 3,
3059 ImGuiTableFlags_SizingStretchProp)) {
3060 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed,
3061 120.0f);
3062 ImGui::TableSetupColumn("Picker",
3063 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3064 ImGui::TableSetupColumn("Description",
3065 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3066 ImGui::TableHeadersRow();
3067
3068 // UI state colors with alpha support where needed
3069 ImGui::TableNextRow();
3070 ImGui::TableNextColumn();
3071 ImGui::AlignTextToFramePadding();
3072 ImGui::Text("Active Selection:");
3073 ImGui::TableNextColumn();
3074 ImVec4 active_selection =
3076 if (ImGui::ColorEdit4("##active_selection", &active_selection.x)) {
3077 edit_theme.active_selection = {
3078 active_selection.x, active_selection.y, active_selection.z,
3079 active_selection.w};
3080 apply_live_preview();
3081 }
3082 ImGui::TableNextColumn();
3083 ImGui::TextWrapped("Active/selected UI elements");
3084
3085 ImGui::TableNextRow();
3086 ImGui::TableNextColumn();
3087 ImGui::AlignTextToFramePadding();
3088 ImGui::Text("Hover Highlight:");
3089 ImGui::TableNextColumn();
3090 ImVec4 hover_highlight =
3092 if (ImGui::ColorEdit4("##hover_highlight", &hover_highlight.x)) {
3093 edit_theme.hover_highlight = {
3094 hover_highlight.x, hover_highlight.y, hover_highlight.z,
3095 hover_highlight.w};
3096 apply_live_preview();
3097 }
3098 ImGui::TableNextColumn();
3099 ImGui::TextWrapped("General hover state highlighting");
3100
3101 ImGui::TableNextRow();
3102 ImGui::TableNextColumn();
3103 ImGui::AlignTextToFramePadding();
3104 ImGui::Text("Focus Border:");
3105 ImGui::TableNextColumn();
3106 ImVec4 focus_border = ConvertColorToImVec4(edit_theme.focus_border);
3107 if (ImGui::ColorEdit3("##focus_border", &focus_border.x)) {
3108 edit_theme.focus_border = {focus_border.x, focus_border.y,
3109 focus_border.z, focus_border.w};
3110 apply_live_preview();
3111 }
3112 ImGui::TableNextColumn();
3113 ImGui::TextWrapped("Border for focused input elements");
3114
3115 ImGui::TableNextRow();
3116 ImGui::TableNextColumn();
3117 ImGui::AlignTextToFramePadding();
3118 ImGui::Text("Disabled Overlay:");
3119 ImGui::TableNextColumn();
3120 ImVec4 disabled_overlay =
3122 if (ImGui::ColorEdit4("##disabled_overlay", &disabled_overlay.x)) {
3123 edit_theme.disabled_overlay = {
3124 disabled_overlay.x, disabled_overlay.y, disabled_overlay.z,
3125 disabled_overlay.w};
3126 apply_live_preview();
3127 }
3128 ImGui::TableNextColumn();
3129 ImGui::TextWrapped(
3130 "Semi-transparent overlay for disabled elements");
3131
3132 ImGui::EndTable();
3133 }
3134 }
3135
3136 // Editor-specific colors section
3137 if (ImGui::CollapsingHeader("Editor-Specific Colors")) {
3138 if (ImGui::BeginTable("EditorColorsTable", 3,
3139 ImGuiTableFlags_SizingStretchProp)) {
3140 ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed,
3141 120.0f);
3142 ImGui::TableSetupColumn("Picker",
3143 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3144 ImGui::TableSetupColumn("Description",
3145 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3146 ImGui::TableHeadersRow();
3147
3148 auto editor_colors =
3149 std::vector<std::tuple<const char*, Color*, const char*, bool>>{
3150 {"Editor Background", &edit_theme.editor_background,
3151 "Main editor canvas background", false},
3152 {"Editor Grid", &edit_theme.editor_grid,
3153 "Grid lines in map/graphics editors", true},
3154 {"Editor Cursor", &edit_theme.editor_cursor,
3155 "Cursor color in editors", false},
3156 {"Editor Selection", &edit_theme.editor_selection,
3157 "Selection highlight in editors", true}};
3158
3159 for (auto& [label, color_ptr, description, use_alpha] :
3160 editor_colors) {
3161 ImGui::TableNextRow();
3162 ImGui::TableNextColumn();
3163 ImGui::AlignTextToFramePadding();
3164 ImGui::Text("%s:", label);
3165
3166 ImGui::TableNextColumn();
3167 ImVec4 color_vec = ConvertColorToImVec4(*color_ptr);
3168 std::string id = absl::StrFormat("##editor_%s", label);
3169 if (use_alpha ? ImGui::ColorEdit4(id.c_str(), &color_vec.x)
3170 : ImGui::ColorEdit3(id.c_str(), &color_vec.x)) {
3171 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
3172 color_vec.w};
3173 apply_live_preview();
3174 }
3175
3176 ImGui::TableNextColumn();
3177 ImGui::TextWrapped("%s", description);
3178 }
3179
3180 ImGui::EndTable();
3181 }
3182 }
3183
3184 ImGui::EndTabItem();
3185 }
3186
3187 // Density & Layout Tab
3188 if (ImGui::BeginTabItem(
3189 absl::StrFormat("%s Density", ICON_MD_DENSITY_SMALL).c_str())) {
3190 ImGui::Text("Control UI density, spacing, and typography scaling");
3191 ImGui::Separator();
3192
3193 // Density Preset Selector
3194 ImGui::TextColored(ImVec4(0.7f, 0.8f, 1.0f, 1.0f), "%s Quick Presets",
3195 ICON_MD_TUNE);
3196 ImGui::Spacing();
3197
3198 // Get current preset
3199 int current_preset = static_cast<int>(edit_theme.density_preset);
3200
3201 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(20, 12));
3202 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 0));
3203
3204 // Compact button
3205 bool is_compact = (current_preset == 0);
3206 if (is_compact) {
3207 ImGui::PushStyleColor(
3208 ImGuiCol_Button,
3209 ImVec4(edit_theme.accent.red, edit_theme.accent.green,
3210 edit_theme.accent.blue, 0.8f));
3211 }
3212 if (ImGui::Button(
3213 absl::StrFormat("%s Compact", ICON_MD_COMPRESS).c_str(),
3214 ImVec2(130, 50))) {
3216 apply_live_preview();
3217 }
3218 if (is_compact) {
3219 ImGui::PopStyleColor();
3220 }
3221 if (ImGui::IsItemHovered()) {
3222 ImGui::SetTooltip(
3223 "Dense UI with smaller widgets and tighter spacing\n"
3224 "Best for: Information-dense workflows");
3225 }
3226
3227 ImGui::SameLine();
3228
3229 // Normal button
3230 bool is_normal = (current_preset == 1);
3231 if (is_normal) {
3232 ImGui::PushStyleColor(
3233 ImGuiCol_Button,
3234 ImVec4(edit_theme.accent.red, edit_theme.accent.green,
3235 edit_theme.accent.blue, 0.8f));
3236 }
3237 if (ImGui::Button(absl::StrFormat("%s Normal", ICON_MD_CHECK).c_str(),
3238 ImVec2(130, 50))) {
3240 apply_live_preview();
3241 }
3242 if (is_normal) {
3243 ImGui::PopStyleColor();
3244 }
3245 if (ImGui::IsItemHovered()) {
3246 ImGui::SetTooltip(
3247 "Balanced spacing and widget sizes\n"
3248 "Best for: General use");
3249 }
3250
3251 ImGui::SameLine();
3252
3253 // Comfortable button
3254 bool is_comfortable = (current_preset == 2);
3255 if (is_comfortable) {
3256 ImGui::PushStyleColor(
3257 ImGuiCol_Button,
3258 ImVec4(edit_theme.accent.red, edit_theme.accent.green,
3259 edit_theme.accent.blue, 0.8f));
3260 }
3261 if (ImGui::Button(
3262 absl::StrFormat("%s Comfortable", ICON_MD_EXPAND).c_str(),
3263 ImVec2(130, 50))) {
3265 apply_live_preview();
3266 }
3267 if (is_comfortable) {
3268 ImGui::PopStyleColor();
3269 }
3270 if (ImGui::IsItemHovered()) {
3271 ImGui::SetTooltip(
3272 "Spacious layout with larger click targets\n"
3273 "Best for: Touch screens, accessibility");
3274 }
3275
3276 ImGui::PopStyleVar(2);
3277
3278 ImGui::Spacing();
3279 ImGui::Separator();
3280 ImGui::Spacing();
3281
3282 // Advanced Controls
3283 if (ImGui::CollapsingHeader("Advanced Density Controls")) {
3284 ImGui::Indent();
3285
3286 ImGui::Text("Fine-tune individual spacing multipliers:");
3287 ImGui::Spacing();
3288
3289 // Compact factor slider
3290 if (ImGui::SliderFloat("Compact Factor", &edit_theme.compact_factor,
3291 0.5f, 1.5f, "%.2f")) {
3292 apply_live_preview();
3293 }
3294 if (ImGui::IsItemHovered()) {
3295 ImGui::SetTooltip(
3296 "Global density multiplier (0.5 = very compact, "
3297 "1.5 = very spacious)");
3298 }
3299
3300 ImGui::Spacing();
3301
3302 // Widget height
3303 if (ImGui::SliderFloat("Widget Height",
3304 &edit_theme.widget_height_multiplier, 0.6f,
3305 1.5f, "%.2f")) {
3306 apply_live_preview();
3307 }
3308
3309 // Spacing
3310 if (ImGui::SliderFloat("Item Spacing", &edit_theme.spacing_multiplier,
3311 0.5f, 1.5f, "%.2f")) {
3312 apply_live_preview();
3313 }
3314
3315 // Toolbar height
3316 if (ImGui::SliderFloat("Toolbar Height",
3317 &edit_theme.toolbar_height_multiplier, 0.5f,
3318 1.2f, "%.2f")) {
3319 apply_live_preview();
3320 }
3321
3322 // Panel padding
3323 if (ImGui::SliderFloat("Panel Padding",
3324 &edit_theme.panel_padding_multiplier, 0.5f,
3325 1.5f, "%.2f")) {
3326 apply_live_preview();
3327 }
3328
3329 // Button padding
3330 if (ImGui::SliderFloat("Button Padding",
3331 &edit_theme.button_padding_multiplier, 0.5f,
3332 1.5f, "%.2f")) {
3333 apply_live_preview();
3334 }
3335
3336 // Table row height
3337 if (ImGui::SliderFloat("Table Row Height",
3338 &edit_theme.table_row_height_multiplier, 0.7f,
3339 1.5f, "%.2f")) {
3340 apply_live_preview();
3341 }
3342
3343 ImGui::Unindent();
3344 }
3345
3346 // Style Rounding Controls
3347 if (ImGui::CollapsingHeader("Corner Rounding")) {
3348 ImGui::Indent();
3349
3350 if (ImGui::SliderFloat("Window Rounding", &edit_theme.window_rounding,
3351 0.0f, 20.0f, "%.1f")) {
3352 apply_live_preview();
3353 }
3354
3355 if (ImGui::SliderFloat("Frame Rounding", &edit_theme.frame_rounding,
3356 0.0f, 12.0f, "%.1f")) {
3357 apply_live_preview();
3358 }
3359
3360 if (ImGui::SliderFloat("Tab Rounding", &edit_theme.tab_rounding, 0.0f,
3361 12.0f, "%.1f")) {
3362 apply_live_preview();
3363 }
3364
3365 if (ImGui::SliderFloat("Scrollbar Rounding",
3366 &edit_theme.scrollbar_rounding, 0.0f, 12.0f,
3367 "%.1f")) {
3368 apply_live_preview();
3369 }
3370
3371 if (ImGui::SliderFloat("Grab Rounding", &edit_theme.grab_rounding,
3372 0.0f, 12.0f, "%.1f")) {
3373 apply_live_preview();
3374 }
3375
3376 ImGui::Unindent();
3377 }
3378
3379 // Animation Settings
3380 if (ImGui::CollapsingHeader("Animation Settings")) {
3381 ImGui::Indent();
3382
3383 if (ImGui::Checkbox("Enable Animations",
3384 &edit_theme.enable_animations)) {
3385 apply_live_preview();
3386 }
3387
3388 if (edit_theme.enable_animations) {
3389 if (ImGui::SliderFloat("Animation Speed",
3390 &edit_theme.animation_speed, 0.5f, 2.0f,
3391 "%.2f")) {
3392 apply_live_preview();
3393 }
3394 }
3395
3396 if (ImGui::Checkbox("Enable Glow Effects",
3397 &edit_theme.enable_glow_effects)) {
3398 apply_live_preview();
3399 }
3400
3401 ImGui::Unindent();
3402 }
3403
3404 ImGui::EndTabItem();
3405 }
3406
3407 ImGui::EndTabBar();
3408 }
3409
3410 ImGui::Separator();
3411
3412 if (ImGui::Button("Preview Theme")) {
3413 ApplyTheme(edit_theme);
3414 }
3415
3416 ImGui::SameLine();
3417 if (ImGui::Button("Reset to Current")) {
3418 edit_theme = current_theme_;
3419 // Safe string copy with bounds checking
3420 size_t name_len =
3421 std::min(current_theme_.name.length(), sizeof(theme_name) - 1);
3422 std::memcpy(theme_name, current_theme_.name.c_str(), name_len);
3423 theme_name[name_len] = '\0';
3424
3425 size_t desc_len = std::min(current_theme_.description.length(),
3426 sizeof(theme_description) - 1);
3427 std::memcpy(theme_description, current_theme_.description.c_str(),
3428 desc_len);
3429 theme_description[desc_len] = '\0';
3430
3431 size_t author_len =
3432 std::min(current_theme_.author.length(), sizeof(theme_author) - 1);
3433 std::memcpy(theme_author, current_theme_.author.c_str(), author_len);
3434 theme_author[author_len] = '\0';
3435
3436 // Reset backup state since we're back to current theme
3437 if (theme_backup_made) {
3438 theme_backup_made = false;
3439 current_theme_.ApplyToImGui(); // Apply current theme to clear any
3440 // preview changes
3441 }
3442 }
3443
3444 ImGui::SameLine();
3445 if (ImGui::Button("Save Theme")) {
3446 edit_theme.name = std::string(theme_name);
3447 edit_theme.description = std::string(theme_description);
3448 edit_theme.author = std::string(theme_author);
3449
3450 // Add to themes map and apply
3451 themes_[edit_theme.name] = edit_theme;
3452 ApplyTheme(edit_theme);
3453
3454 // Reset backup state since theme is now applied
3455 theme_backup_made = false;
3456 }
3457
3458 ImGui::SameLine();
3459
3460 // Save Over Current button - overwrites the current theme file
3461 std::string current_file_path = GetCurrentThemeFilePath();
3462 bool can_save_over = !current_file_path.empty();
3463
3464 if (!can_save_over) {
3465 ImGui::BeginDisabled();
3466 }
3467
3468 if (ImGui::Button("Save Over Current")) {
3469 edit_theme.name = std::string(theme_name);
3470 edit_theme.description = std::string(theme_description);
3471 edit_theme.author = std::string(theme_author);
3472
3473 auto status = SaveThemeToFile(edit_theme, current_file_path);
3474 if (status.ok()) {
3475 // Update themes map and apply
3476 themes_[edit_theme.name] = edit_theme;
3477 ApplyTheme(edit_theme);
3478 theme_backup_made =
3479 false; // Reset backup state since theme is now applied
3480 } else {
3481 LOG_ERROR("Theme Manager", "Failed to save over current theme");
3482 }
3483 }
3484
3485 if (!can_save_over) {
3486 ImGui::EndDisabled();
3487 }
3488
3489 if (ImGui::IsItemHovered() && can_save_over) {
3490 ImGui::BeginTooltip();
3491 ImGui::Text("Save over current theme file:");
3492 ImGui::Text("%s", current_file_path.c_str());
3493 ImGui::EndTooltip();
3494 } else if (ImGui::IsItemHovered()) {
3495 ImGui::BeginTooltip();
3496 ImGui::Text("No current theme file to overwrite");
3497 ImGui::Text("Use 'Save to File...' to create a new theme file");
3498 ImGui::EndTooltip();
3499 }
3500
3501 ImGui::SameLine();
3502 if (ImGui::Button("Save to File...")) {
3503 edit_theme.name = std::string(theme_name);
3504 edit_theme.description = std::string(theme_description);
3505 edit_theme.author = std::string(theme_author);
3506
3507 // Use save file dialog with proper defaults
3508 std::string safe_name =
3509 edit_theme.name.empty() ? "custom_theme" : edit_theme.name;
3510 auto file_path =
3512
3513 if (!file_path.empty()) {
3514 // Ensure .theme extension
3515 if (file_path.find(".theme") == std::string::npos) {
3516 file_path += ".theme";
3517 }
3518
3519 auto status = SaveThemeToFile(edit_theme, file_path);
3520 if (status.ok()) {
3521 // Also add to themes map for immediate use
3522 themes_[edit_theme.name] = edit_theme;
3523 ApplyTheme(edit_theme);
3524 } else {
3525 LOG_ERROR("Theme Manager", "Failed to save theme");
3526 }
3527 }
3528 }
3529
3530 if (ImGui::IsItemHovered()) {
3531 ImGui::BeginTooltip();
3532 ImGui::Text("Save theme to a .theme file");
3533 ImGui::Text("Saved themes can be shared and loaded later");
3534 ImGui::EndTooltip();
3535 }
3536 }
3537 ImGui::End();
3538}
3539
3540std::vector<std::string> ThemeManager::GetThemeSearchPaths() const {
3541 std::vector<std::string> search_paths;
3542
3543 // Priority 1: User themes directory (~/.yaze/themes/)
3544 // This is the primary location for user-installed and custom themes
3545 auto config_dir = util::PlatformPaths::GetConfigDirectory();
3546 if (config_dir.ok()) {
3547 auto user_themes = *config_dir / "themes";
3548 // Ensure the directory exists for user themes
3550 search_paths.push_back(user_themes.string() + "/");
3551 }
3552
3553 // Priority 2: Application bundle/install themes
3554#ifdef __APPLE__
3555 // macOS bundle resource path
3556 std::string bundle_themes = util::GetResourcePath("assets/themes/");
3557 if (!bundle_themes.empty()) {
3558 search_paths.push_back(bundle_themes);
3559 }
3560
3561 // Alternative bundle locations
3562 std::string bundle_root = util::GetBundleResourcePath();
3563 if (!bundle_root.empty()) {
3564 search_paths.push_back(bundle_root + "Contents/Resources/themes/");
3565 search_paths.push_back(bundle_root + "Contents/Resources/assets/themes/");
3566 }
3567#endif
3568
3569 // Priority 3: System-wide themes (Unix only)
3570#ifndef _WIN32
3571 search_paths.push_back("/usr/local/share/yaze/themes/");
3572 search_paths.push_back("/usr/share/yaze/themes/");
3573#endif
3574
3575 // Priority 4: Development paths (relative to working directory)
3576 search_paths.push_back("assets/themes/");
3577 search_paths.push_back("../assets/themes/");
3578#ifdef _WIN32
3579 search_paths.push_back("./assets/themes/");
3580 search_paths.push_back("./themes/");
3581#endif
3582
3583 return search_paths;
3584}
3585
3587 auto search_paths = GetThemeSearchPaths();
3588
3589 // Try each search path and return the first one that exists
3590 for (const auto& path : search_paths) {
3591 std::ifstream test_file(
3592 path + "."); // Test if directory exists by trying to access it
3593 if (test_file.good()) {
3594 return path;
3595 }
3596
3597 // Also try with platform-specific directory separators
3598 std::string normalized_path = path;
3599 if (!normalized_path.empty() && normalized_path.back() != '/' &&
3600 normalized_path.back() != '\\') {
3601 normalized_path += "/";
3602 }
3603
3604 std::ifstream test_file2(normalized_path + ".");
3605 if (test_file2.good()) {
3606 return normalized_path;
3607 }
3608 }
3609
3610 return search_paths.empty() ? "assets/themes/" : search_paths[0];
3611}
3612
3614 // Always return the user themes directory (~/.yaze/themes/)
3615 // This is the canonical location for user-created and exported themes
3616 auto config_dir = util::PlatformPaths::GetConfigDirectory();
3617 if (config_dir.ok()) {
3618 auto user_themes_path = *config_dir / "themes";
3619 // Ensure the directory exists
3620 (void)util::PlatformPaths::EnsureDirectoryExists(user_themes_path);
3621 return user_themes_path.string() + "/";
3622 }
3623
3624 // Fallback to development path if config directory unavailable
3625 return "assets/themes/";
3626}
3627
3628std::vector<std::string> ThemeManager::DiscoverAvailableThemeFiles() const {
3629 std::vector<std::string> theme_files;
3630 auto search_paths = GetThemeSearchPaths();
3631
3632 for (const auto& search_path : search_paths) {
3633 try {
3634 std::filesystem::path dir_path(search_path);
3635
3636 // Skip if directory doesn't exist
3637 std::error_code ec;
3638 if (!std::filesystem::exists(dir_path, ec) || ec) {
3639 continue;
3640 }
3641
3642 if (!std::filesystem::is_directory(dir_path, ec) || ec) {
3643 continue;
3644 }
3645
3646 // Iterate directory entries using std::filesystem (cross-platform)
3647 for (const auto& entry :
3648 std::filesystem::directory_iterator(dir_path, ec)) {
3649 if (ec) {
3650 LOG_WARN("Theme Manager", "Error iterating directory: %s",
3651 ec.message().c_str());
3652 break;
3653 }
3654
3655 if (!entry.is_regular_file(ec) || ec) {
3656 continue;
3657 }
3658
3659 std::string filename = entry.path().filename().string();
3660 std::string extension = entry.path().extension().string();
3661
3662 // Accept both .theme and .theme.json extensions
3663 if (extension == ".theme" ||
3664 (filename.length() > 11 &&
3665 filename.substr(filename.length() - 11) == ".theme.json")) {
3666 theme_files.push_back(entry.path().string());
3667 }
3668 }
3669 } catch (const std::exception& e) {
3670 LOG_WARN("Theme Manager", "Error scanning directory %s: %s",
3671 search_path.c_str(), e.what());
3672 }
3673 }
3674
3675 // Remove duplicates while preserving order (user themes take priority)
3676 std::vector<std::string> unique_files;
3677 std::set<std::string> seen_basenames;
3678
3679 for (const auto& file : theme_files) {
3680 std::string basename = util::GetFileName(file);
3681 // Normalize basename by removing both .theme and .theme.json extensions
3682 if (basename.length() > 11 &&
3683 basename.substr(basename.length() - 11) == ".theme.json") {
3684 basename = basename.substr(0, basename.length() - 11);
3685 } else if (basename.length() > 6 &&
3686 basename.substr(basename.length() - 6) == ".theme") {
3687 basename = basename.substr(0, basename.length() - 6);
3688 }
3689
3690 if (seen_basenames.find(basename) == seen_basenames.end()) {
3691 unique_files.push_back(file);
3692 seen_basenames.insert(basename);
3693 }
3694 }
3695
3696 LOG_INFO("Theme Manager", "Discovered %zu theme files", unique_files.size());
3697 return unique_files;
3698}
3699
3701 auto theme_files = DiscoverAvailableThemeFiles();
3702
3703 int successful_loads = 0;
3704 int failed_loads = 0;
3705
3706 for (const auto& theme_file : theme_files) {
3707 auto status = LoadThemeFromFile(theme_file);
3708 if (status.ok()) {
3709 successful_loads++;
3710 } else {
3711 failed_loads++;
3712 }
3713 }
3714
3715 if (successful_loads == 0 && failed_loads > 0) {
3716 return absl::InternalError(absl::StrFormat(
3717 "Failed to load any themes (%d failures)", failed_loads));
3718 }
3719
3720 return absl::OkStatus();
3721}
3722
3726
3728 if (current_theme_name_ == "Classic YAZE") {
3729 return ""; // Classic theme doesn't have a file
3730 }
3731
3732 // Try to find the current theme file in the search paths
3733 auto search_paths = GetThemeSearchPaths();
3734 std::string theme_filename = current_theme_name_ + ".theme";
3735
3736 // Convert theme name to safe filename (replace spaces and special chars)
3737 for (char& c : theme_filename) {
3738 if (!std::isalnum(c) && c != '.' && c != '_') {
3739 c = '_';
3740 }
3741 }
3742
3743 for (const auto& search_path : search_paths) {
3744 std::string full_path = search_path + theme_filename;
3745 std::ifstream test_file(full_path);
3746 if (test_file.good()) {
3747 return full_path;
3748 }
3749 }
3750
3751 // If not found, return path in the first search directory (for new saves)
3752 return search_paths.empty() ? theme_filename
3753 : search_paths[0] + theme_filename;
3754}
3755
3756} // namespace gui
3757} // namespace yaze
Manages themes, loading, saving, and switching.
Color ParseColorFromString(const std::string &color_str) const
std::string preview_original_name_
Color GetWelcomeScreenBackground() const
void StartPreview(const std::string &theme_name)
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_
ImVec4 transition_to_[ImGuiCol_COUNT]
Color GetWelcomeScreenAccent() const
void ApplyAccentColor(const Color &accent, bool dark_mode=true)
std::vector< std::string > DiscoverAvailableThemeFiles() const
std::string GetUserThemesDirectory() 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)
Theme GenerateThemeFromAccent(const Color &accent, bool dark_mode=true)
const Theme * GetTheme(const std::string &name) const
std::string SerializeTheme(const Theme &theme) const
absl::Status RefreshAvailableThemes()
std::string ExportCurrentThemeJson() const
void ApplySmartDefaults(Theme &theme)
Color GetWelcomeScreenBorder() const
void ShowSimpleThemeEditor(bool *p_open)
ImVec4 transition_from_[ImGuiCol_COUNT]
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 absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
static absl::Status EnsureDirectoryExists(const std::filesystem::path &path)
Ensure a directory exists, creating it if necessary.
#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_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_DENSITY_SMALL
Definition icons.h:537
#define ICON_MD_BOLT
Definition icons.h:282
#define ICON_MD_BORDER_ALL
Definition icons.h:292
#define ICON_MD_PREVIEW
Definition icons.h:1512
#define ICON_MD_COMPRESS
Definition icons.h:451
#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_EXPAND
Definition icons.h:700
#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
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
Color RGBA(int r, int g, int b, int a=255)
DensityPreset
Typography and spacing density presets.
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.
HSL ToHSL() const
Definition color.h:31
Color Darker(float amount) const
Definition color.h:107
Color WithAlpha(float new_alpha) const
Definition color.h:129
static Color FromHSL(float h, float s, float l, float a=1.0f)
Definition color.h:60
Color Lighter(float amount) const
Definition color.h:102
Color Desaturate(float amount) const
Definition color.h:117
Color ShiftHue(float degrees) const
Definition color.h:122
float alpha
Definition color.h:20
float green
Definition color.h:18
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
float panel_padding_multiplier
float widget_height_multiplier
struct yaze::gui::Theme::DungeonColors dungeon
struct yaze::gui::Theme::AgentTheme agent
void ApplyToImGui() const
float button_padding_multiplier
float table_row_height_multiplier
float toolbar_height_multiplier
DensityPreset density_preset
void ApplyDensityPreset(DensityPreset preset)
std::string author
float canvas_toolbar_multiplier