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