1308 Theme& theme,
const std::set<std::string>& declared_keys) {
1320 auto declared = [&declared_keys](
const char* key) {
1321 return key !=
nullptr && declared_keys.count(key) != 0;
1323 auto looks_default = [](
const Color& color) {
1324 return color.red == 0.0f && color.green == 0.0f && color.blue == 0.0f &&
1325 (color.alpha == 0.0f || color.alpha == 1.0f);
1327 auto is_unset = [&](
const Color& color,
const char* key =
nullptr) {
1328 return !declared(key) && looks_default(color);
1333 auto needs_semantic_default = [&](
const Color& color,
1334 const char* key =
nullptr) {
1335 return is_unset(color, key);
1349 if (is_unset(theme.
accent,
"accent")) {
1351 ?
Color{0.35f, 0.70f, 0.95f, 1.0f}
1354 if (is_unset(theme.
error,
"error")) {
1355 theme.
error =
Color{0.90f, 0.30f, 0.30f, 1.0f};
1357 if (is_unset(theme.
warning,
"warning")) {
1360 if (is_unset(theme.
success,
"success")) {
1363 if (is_unset(theme.
info,
"info")) {
1364 theme.
info =
Color{0.35f, 0.70f, 0.95f, 1.0f};
1368 auto with_alpha = [](
const Color& color,
float alpha) {
1373 auto lighten = [](
const Color& color,
float amount) {
1374 return Color{std::min(1.0f, color.
red + amount),
1375 std::min(1.0f, color.
green + amount),
1376 std::min(1.0f, color.
blue + amount), color.
alpha};
1380 auto darken = [](
const Color& color,
float amount) {
1381 return Color{std::max(0.0f, color.
red - amount),
1382 std::max(0.0f, color.
green - amount),
1383 std::max(0.0f, color.
blue - amount), color.
alpha};
1385 auto darken_with_floor = [](
const Color& color,
float amount,
1386 float floor = 0.03f) {
1387 return Color{std::max(floor, color.
red - amount),
1388 std::max(floor, color.
green - amount),
1389 std::max(floor, color.
blue - amount), color.
alpha};
1391 auto is_effectively_transparent = [](
const Color& color) {
1392 return color.
alpha <= 0.01f;
1396 if (is_unset(theme.
border,
"border")) {
1402 if (is_unset(theme.
separator,
"separator")) {
1438 if (is_unset(theme.
check_mark,
"check_mark")) {
1466 if (is_unset(theme.
text_link,
"text_link")) {
1472 if (is_unset(theme.
plot_lines,
"plot_lines")) {
1489 if (is_unset(theme.
nav_cursor,
"nav_cursor")) {
1517 if (is_unset(theme.
tree_lines,
"tree_lines")) {
1522 if (is_unset(theme.
tab_dimmed,
"tab_dimmed")) {
1529 "tab_dimmed_selected_overline")) {
1537 if (is_unset(theme.
surface,
"surface")) {
1545 if (needs_semantic_default(theme.
text_highlight,
"text_highlight")) {
1548 if (needs_semantic_default(theme.
link_hover,
"link_hover")) {
1551 if (needs_semantic_default(theme.
code_background,
"code_background")) {
1554 if (needs_semantic_default(theme.
success_light,
"success_light")) {
1557 if (needs_semantic_default(theme.
warning_light,
"warning_light")) {
1560 if (needs_semantic_default(theme.
error_light,
"error_light")) {
1563 if (needs_semantic_default(theme.
info_light,
"info_light")) {
1579 if (needs_semantic_default(theme.
hover_highlight,
"hover_highlight")) {
1582 if (needs_semantic_default(theme.
focus_border,
"focus_border")) {
1594 ?
RGBA(8, 8, 10, 255)
1597 if (needs_semantic_default(theme.
editor_grid,
"editor_grid")) {
1600 if (needs_semantic_default(theme.
editor_cursor,
"editor_cursor")) {
1629 if (needs_semantic_default(theme.
entrance_color,
"entrance_color")) {
1632 if (needs_semantic_default(theme.
hole_color,
"hole_color")) {
1635 if (needs_semantic_default(theme.
exit_color,
"exit_color")) {
1638 if (needs_semantic_default(theme.
item_color,
"item_color")) {
1641 if (needs_semantic_default(theme.
sprite_color,
"sprite_color")) {
1644 if (needs_semantic_default(theme.
transport_color,
"transport_color")) {
1757 ? with_alpha(theme.
surface, 0.95f)
2426 if (!p_open || !*p_open)
2429 ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
2431 if (ImGui::Begin(absl::StrFormat(
"%s Theme Editor",
ICON_MD_PALETTE).c_str(),
2432 p_open, ImGuiWindowFlags_MenuBar)) {
2434 static float editor_animation_time = 0.0f;
2435 editor_animation_time += ImGui::GetIO().DeltaTime;
2437 ImDrawList* draw_list = ImGui::GetWindowDrawList();
2438 ImVec2 window_pos = ImGui::GetWindowPos();
2439 ImVec2 window_size = ImGui::GetWindowSize();
2443 std::vector<gui::Color> theme_colors = {
2444 current_theme.primary, current_theme.secondary, current_theme.accent,
2445 current_theme.success, current_theme.warning, current_theme.error};
2447 for (
size_t i = 0; i < theme_colors.size(); ++i) {
2448 float time_offset = i * 1.0f;
2449 float orbit_radius = 60.0f + i * 8.0f;
2450 float x = window_pos.x + window_size.x * 0.8f +
2451 cosf(editor_animation_time * 0.3f + time_offset) * orbit_radius;
2452 float y = window_pos.y + window_size.y * 0.3f +
2453 sinf(editor_animation_time * 0.3f + time_offset) * orbit_radius;
2456 0.15f + 0.1f * sinf(editor_animation_time * 1.5f + time_offset);
2457 ImU32 orb_color = ImGui::ColorConvertFloat4ToU32(
2458 ImVec4(theme_colors[i].red, theme_colors[i].green,
2459 theme_colors[i].blue, alpha));
2462 4.0f + sinf(editor_animation_time * 2.0f + time_offset) * 1.0f;
2463 draw_list->AddCircleFilled(ImVec2(x, y), radius, orb_color);
2467 if (ImGui::BeginMenuBar()) {
2468 if (ImGui::BeginMenu(tr(
"File"))) {
2469 if (ImGui::MenuItem(
2470 absl::StrFormat(
"%s New Theme",
ICON_MD_ADD).c_str())) {
2474 if (ImGui::MenuItem(
2478 if (!file_path.empty()) {
2483 if (ImGui::MenuItem(
2484 absl::StrFormat(
"%s Save Theme",
ICON_MD_SAVE).c_str())) {
2487 if (!current_file_path.empty()) {
2490 LOG_ERROR(
"Theme Manager",
"Failed to save theme");
2496 if (!file_path.empty()) {
2499 LOG_ERROR(
"Theme Manager",
"Failed to save theme");
2504 if (ImGui::MenuItem(
2509 if (!file_path.empty()) {
2512 LOG_ERROR(
"Theme Manager",
"Failed to save theme");
2517 if (ImGui::MenuItem(
2526 for (
char& c : safe_name) {
2527 if (c ==
' ' || c ==
'/' || c ==
'\\' || c ==
':' || c ==
'*' ||
2528 c ==
'?' || c ==
'"' || c ==
'<' || c ==
'>' || c ==
'|') {
2532 std::string file_path = user_themes_dir + safe_name +
".theme";
2536 LOG_INFO(
"Theme Manager",
"Exported theme to: %s",
2539 LOG_ERROR(
"Theme Manager",
"Failed to export theme to user themes");
2545 if (ImGui::BeginMenu(tr(
"Presets"))) {
2546 if (ImGui::MenuItem(tr(
"YAZE Classic"))) {
2551 if (!available_themes.empty()) {
2553 for (
const auto& theme_name : available_themes) {
2554 if (theme_name ==
"Classic YAZE") {
2557 if (ImGui::MenuItem(theme_name.c_str())) {
2565 ImGui::EndMenuBar();
2569 static char theme_name[128];
2570 static char theme_description[256];
2571 static char theme_author[128];
2572 static bool live_preview =
true;
2573 static Theme original_theme;
2574 static bool theme_backup_made =
false;
2577 auto apply_live_preview = [&]() {
2579 if (!theme_backup_made) {
2581 theme_backup_made =
true;
2590 ImGui::Checkbox(tr(
"Live Preview"), &live_preview);
2592 ImGui::Text(tr(
"| Changes apply immediately when enabled"));
2595 static bool prev_live_preview = live_preview;
2596 if (prev_live_preview && !live_preview && theme_backup_made) {
2598 theme_backup_made =
false;
2600 prev_live_preview = live_preview;
2605 if (ImGui::BeginTable(
"ThemeMetadata", 2,
2606 ImGuiTableFlags_SizingStretchProp)) {
2607 ImGui::TableSetupColumn(
"Field", ImGuiTableColumnFlags_WidthFixed,
2609 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
2611 ImGui::TableNextRow();
2612 ImGui::TableNextColumn();
2613 ImGui::AlignTextToFramePadding();
2614 ImGui::Text(tr(
"Name:"));
2615 ImGui::TableNextColumn();
2616 if (ImGui::InputText(
"##theme_name", theme_name,
sizeof(theme_name))) {
2617 edit_theme.
name = std::string(theme_name);
2620 ImGui::TableNextRow();
2621 ImGui::TableNextColumn();
2622 ImGui::AlignTextToFramePadding();
2623 ImGui::Text(tr(
"Description:"));
2624 ImGui::TableNextColumn();
2625 if (ImGui::InputText(
"##theme_description", theme_description,
2626 sizeof(theme_description))) {
2627 edit_theme.
description = std::string(theme_description);
2630 ImGui::TableNextRow();
2631 ImGui::TableNextColumn();
2632 ImGui::AlignTextToFramePadding();
2633 ImGui::Text(tr(
"Author:"));
2634 ImGui::TableNextColumn();
2635 if (ImGui::InputText(
"##theme_author", theme_author,
2636 sizeof(theme_author))) {
2637 edit_theme.
author = std::string(theme_author);
2646 if (ImGui::BeginTabBar(
"ThemeEditorTabs", ImGuiTabBarFlags_None)) {
2648 static bool first_frame =
true;
2649 if (first_frame && live_preview) {
2650 apply_live_preview();
2651 first_frame =
false;
2655 if (ImGui::BeginTabItem(
2659 ImGui::PushStyleColor(ImGuiCol_ChildBg,
2660 ImVec4(0.1f, 0.1f, 0.12f, 0.5f));
2661 ImGui::BeginChild(
"AccentGenerator", ImVec2(0, 120),
true);
2663 ImGui::TextColored(ImVec4(0.7f, 0.8f, 1.0f, 1.0f),
2664 tr(
"%s Generate Theme from Accent Color"),
2668 static Color accent_picker_color = {0.4f, 0.6f, 0.9f, 1.0f};
2669 static bool dark_mode_generate =
true;
2671 ImGui::Text(tr(
"Accent Color:"));
2674 if (ImGui::ColorEdit3(
"##AccentPicker", &accent_vec.x,
2675 ImGuiColorEditFlags_NoInputs)) {
2676 accent_picker_color = {accent_vec.x, accent_vec.y, accent_vec.z,
2681 ImGui::Checkbox(tr(
"Dark Mode"), &dark_mode_generate);
2684 if (ImGui::Button(absl::StrFormat(
"%s Generate",
ICON_MD_BOLT).c_str(),
2689 edit_theme = generated;
2690 apply_live_preview();
2694 ImGui::Text(tr(
"Preview: "));
2698 Theme preview_theme =
2705 ImGui::ColorButton(
"##prev_primary", prev_primary, 0, ImVec2(24, 24));
2706 ImGui::SameLine(0, 2);
2707 ImGui::ColorButton(
"##prev_secondary", prev_secondary, 0,
2709 ImGui::SameLine(0, 2);
2710 ImGui::ColorButton(
"##prev_bg", prev_bg, 0, ImVec2(24, 24));
2711 ImGui::SameLine(0, 2);
2712 ImGui::ColorButton(
"##prev_surface", prev_surface, 0, ImVec2(24, 24));
2715 ImGui::PopStyleColor();
2719 if (ImGui::BeginTable(
"PrimaryColorsTable", 3,
2720 ImGuiTableFlags_SizingStretchProp)) {
2721 ImGui::TableSetupColumn(
"Color", ImGuiTableColumnFlags_WidthFixed,
2723 ImGui::TableSetupColumn(
"Picker", ImGuiTableColumnFlags_WidthStretch,
2725 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch,
2727 ImGui::TableHeadersRow();
2730 ImGui::TableNextRow();
2731 ImGui::TableNextColumn();
2732 ImGui::AlignTextToFramePadding();
2733 ImGui::Text(tr(
"Primary:"));
2734 ImGui::TableNextColumn();
2736 if (ImGui::ColorEdit3(
"##primary", &primary.x)) {
2737 edit_theme.
primary = {primary.x, primary.y, primary.z, primary.w};
2738 apply_live_preview();
2740 ImGui::TableNextColumn();
2741 ImGui::Button(tr(
"Primary Preview"), ImVec2(-1, 30));
2744 ImGui::TableNextRow();
2745 ImGui::TableNextColumn();
2746 ImGui::AlignTextToFramePadding();
2747 ImGui::Text(tr(
"Secondary:"));
2748 ImGui::TableNextColumn();
2750 if (ImGui::ColorEdit3(
"##secondary", &secondary.x)) {
2751 edit_theme.
secondary = {secondary.x, secondary.y, secondary.z,
2753 apply_live_preview();
2755 ImGui::TableNextColumn();
2756 ImGui::PushStyleColor(ImGuiCol_Button, secondary);
2757 ImGui::Button(tr(
"Secondary Preview"), ImVec2(-1, 30));
2758 ImGui::PopStyleColor();
2761 ImGui::TableNextRow();
2762 ImGui::TableNextColumn();
2763 ImGui::AlignTextToFramePadding();
2764 ImGui::Text(tr(
"Accent:"));
2765 ImGui::TableNextColumn();
2767 if (ImGui::ColorEdit3(
"##accent", &accent.x)) {
2768 edit_theme.
accent = {accent.x, accent.y, accent.z, accent.w};
2769 apply_live_preview();
2771 ImGui::TableNextColumn();
2772 ImGui::PushStyleColor(ImGuiCol_Button, accent);
2773 ImGui::Button(tr(
"Accent Preview"), ImVec2(-1, 30));
2774 ImGui::PopStyleColor();
2777 ImGui::TableNextRow();
2778 ImGui::TableNextColumn();
2779 ImGui::AlignTextToFramePadding();
2780 ImGui::Text(tr(
"Background:"));
2781 ImGui::TableNextColumn();
2783 if (ImGui::ColorEdit4(
"##background", &background.x)) {
2784 edit_theme.
background = {background.x, background.y, background.z,
2786 apply_live_preview();
2788 ImGui::TableNextColumn();
2789 ImGui::Text(tr(
"Background preview shown in window"));
2793 ImGui::EndTabItem();
2797 if (ImGui::BeginTabItem(
2799 if (ImGui::BeginTable(
"TextColorsTable", 3,
2800 ImGuiTableFlags_SizingStretchProp)) {
2801 ImGui::TableSetupColumn(
"Color", ImGuiTableColumnFlags_WidthFixed,
2803 ImGui::TableSetupColumn(
"Picker", ImGuiTableColumnFlags_WidthStretch,
2805 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch,
2807 ImGui::TableHeadersRow();
2810 auto text_colors = std::vector<std::pair<const char*, Color*>>{
2820 for (
auto& [label, color_ptr] : text_colors) {
2821 ImGui::TableNextRow();
2822 ImGui::TableNextColumn();
2823 ImGui::AlignTextToFramePadding();
2824 ImGui::Text(
"%s:", label);
2826 ImGui::TableNextColumn();
2828 std::string
id = absl::StrFormat(
"##%s", label);
2829 if (ImGui::ColorEdit3(
id.c_str(), &color_vec.x)) {
2830 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
2831 apply_live_preview();
2834 ImGui::TableNextColumn();
2835 ImGui::PushStyleColor(ImGuiCol_Text, color_vec);
2836 ImGui::Text(tr(
"Sample %s"), label);
2837 ImGui::PopStyleColor();
2842 ImGui::EndTabItem();
2846 if (ImGui::BeginTabItem(
2848 if (ImGui::BeginTable(
"InteractiveColorsTable", 3,
2849 ImGuiTableFlags_SizingStretchProp)) {
2850 ImGui::TableSetupColumn(
"Element", ImGuiTableColumnFlags_WidthFixed,
2852 ImGui::TableSetupColumn(
"Picker", ImGuiTableColumnFlags_WidthStretch,
2854 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch,
2856 ImGui::TableHeadersRow();
2859 auto interactive_colors =
2860 std::vector<std::tuple<const char*, Color*, ImGuiCol>>{
2861 {
"Button", &edit_theme.
button, ImGuiCol_Button},
2863 ImGuiCol_ButtonHovered},
2865 ImGuiCol_ButtonActive},
2866 {
"Frame Background", &edit_theme.
frame_bg, ImGuiCol_FrameBg},
2868 ImGuiCol_FrameBgHovered},
2870 ImGuiCol_FrameBgActive},
2871 {
"Check Mark", &edit_theme.
check_mark, ImGuiCol_CheckMark},
2872 {
"Slider Grab", &edit_theme.
slider_grab, ImGuiCol_SliderGrab},
2874 ImGuiCol_SliderGrabActive}};
2876 for (
auto& [label, color_ptr, imgui_col] : interactive_colors) {
2877 ImGui::TableNextRow();
2878 ImGui::TableNextColumn();
2879 ImGui::AlignTextToFramePadding();
2880 ImGui::Text(
"%s:", label);
2882 ImGui::TableNextColumn();
2884 std::string
id = absl::StrFormat(
"##%s", label);
2885 if (ImGui::ColorEdit3(
id.c_str(), &color_vec.x)) {
2886 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
2887 apply_live_preview();
2890 ImGui::TableNextColumn();
2891 ImGui::PushStyleColor(imgui_col, color_vec);
2892 ImGui::Button(absl::StrFormat(
"Preview %s", label).c_str(),
2894 ImGui::PopStyleColor();
2899 ImGui::EndTabItem();
2903 if (ImGui::BeginTabItem(
2905 ImGui::Text(tr(
"Rounding and Border Settings:"));
2907 if (ImGui::SliderFloat(tr(
"Window Rounding"),
2912 if (ImGui::SliderFloat(tr(
"Frame Rounding"), &edit_theme.
frame_rounding,
2917 if (ImGui::SliderFloat(tr(
"Scrollbar Rounding"),
2922 if (ImGui::SliderFloat(tr(
"Tab Rounding"), &edit_theme.
tab_rounding,
2927 if (ImGui::SliderFloat(tr(
"Grab Rounding"), &edit_theme.
grab_rounding,
2934 ImGui::Text(tr(
"Border Sizes:"));
2936 if (ImGui::SliderFloat(tr(
"Window Border Size"),
2941 if (ImGui::SliderFloat(tr(
"Frame Border Size"),
2948 ImGui::Text(tr(
"Animation & Effects:"));
2950 if (ImGui::Checkbox(tr(
"Enable Animations"),
2956 if (ImGui::SliderFloat(tr(
"Animation Speed"),
2958 apply_live_preview();
2961 if (ImGui::Checkbox(tr(
"Enable Glow Effects"),
2967 ImGui::EndTabItem();
2971 if (ImGui::BeginTabItem(
2973 if (ImGui::BeginTable(
"NavigationTable", 3,
2974 ImGuiTableFlags_SizingStretchProp)) {
2975 ImGui::TableSetupColumn(
"Element", ImGuiTableColumnFlags_WidthFixed,
2977 ImGui::TableSetupColumn(
"Picker", ImGuiTableColumnFlags_WidthStretch,
2979 ImGui::TableSetupColumn(
"Preview", ImGuiTableColumnFlags_WidthStretch,
2981 ImGui::TableHeadersRow();
2984 auto window_colors =
2985 std::vector<std::tuple<const char*, Color*, const char*>>{
2986 {
"Window Background", &edit_theme.
window_bg,
2987 "Main window background"},
2988 {
"Child Background", &edit_theme.
child_bg,
2989 "Child window background"},
2990 {
"Popup Background", &edit_theme.
popup_bg,
2991 "Popup window background"},
2992 {
"Modal Background", &edit_theme.
modal_bg,
2993 "Modal window background"},
2995 "Menu bar background"}};
2997 for (
auto& [label, color_ptr, description] : window_colors) {
2998 ImGui::TableNextRow();
2999 ImGui::TableNextColumn();
3000 ImGui::AlignTextToFramePadding();
3001 ImGui::Text(
"%s:", label);
3003 ImGui::TableNextColumn();
3005 std::string
id = absl::StrFormat(
"##window_%s", label);
3006 if (ImGui::ColorEdit4(
id.c_str(), &color_vec.x)) {
3007 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
3008 apply_live_preview();
3011 ImGui::TableNextColumn();
3012 ImGui::TextWrapped(
"%s", description);
3021 if (ImGui::CollapsingHeader(tr(
"Headers & Tabs"),
3022 ImGuiTreeNodeFlags_DefaultOpen)) {
3023 if (ImGui::BeginTable(
"HeaderTabTable", 3,
3024 ImGuiTableFlags_SizingStretchProp)) {
3025 ImGui::TableSetupColumn(
"Element", ImGuiTableColumnFlags_WidthFixed,
3027 ImGui::TableSetupColumn(
"Picker",
3028 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3029 ImGui::TableSetupColumn(
"Preview",
3030 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3031 ImGui::TableHeadersRow();
3033 auto header_tab_colors =
3034 std::vector<std::pair<const char*, Color*>>{
3035 {
"Header", &edit_theme.
header},
3038 {
"Tab", &edit_theme.
tab},
3045 {
"Title Background", &edit_theme.
title_bg},
3049 for (
auto& [label, color_ptr] : header_tab_colors) {
3050 ImGui::TableNextRow();
3051 ImGui::TableNextColumn();
3052 ImGui::AlignTextToFramePadding();
3053 ImGui::Text(
"%s:", label);
3055 ImGui::TableNextColumn();
3057 std::string
id = absl::StrFormat(
"##header_%s", label);
3058 if (ImGui::ColorEdit3(
id.c_str(), &color_vec.x)) {
3059 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
3061 apply_live_preview();
3064 ImGui::TableNextColumn();
3065 ImGui::PushStyleColor(ImGuiCol_Button, color_vec);
3066 ImGui::Button(absl::StrFormat(
"Preview %s", label).c_str(),
3068 ImGui::PopStyleColor();
3076 if (ImGui::CollapsingHeader(tr(
"Navigation & Special"))) {
3077 if (ImGui::BeginTable(
"NavSpecialTable", 3,
3078 ImGuiTableFlags_SizingStretchProp)) {
3079 ImGui::TableSetupColumn(
"Element", ImGuiTableColumnFlags_WidthFixed,
3081 ImGui::TableSetupColumn(
"Picker",
3082 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3083 ImGui::TableSetupColumn(
"Description",
3084 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3085 ImGui::TableHeadersRow();
3087 auto nav_special_colors =
3088 std::vector<std::tuple<const char*, Color*, const char*>>{
3090 "Navigation cursor color"},
3092 "Window selection highlight"},
3094 "Background dimming for navigation"},
3096 "Background dimming for modals"},
3098 "Drag and drop target highlight"},
3100 "Docking area preview"},
3102 "Empty docking space background"}};
3104 for (
auto& [label, color_ptr, description] : nav_special_colors) {
3105 ImGui::TableNextRow();
3106 ImGui::TableNextColumn();
3107 ImGui::AlignTextToFramePadding();
3108 ImGui::Text(
"%s:", label);
3110 ImGui::TableNextColumn();
3112 std::string
id = absl::StrFormat(
"##nav_%s", label);
3113 if (ImGui::ColorEdit4(
id.c_str(), &color_vec.x)) {
3114 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
3116 apply_live_preview();
3119 ImGui::TableNextColumn();
3120 ImGui::TextWrapped(
"%s", description);
3127 ImGui::EndTabItem();
3131 if (ImGui::BeginTabItem(
3133 if (ImGui::BeginTable(
"TablesDataTable", 3,
3134 ImGuiTableFlags_SizingStretchProp)) {
3135 ImGui::TableSetupColumn(
"Element", ImGuiTableColumnFlags_WidthFixed,
3137 ImGui::TableSetupColumn(
"Picker", ImGuiTableColumnFlags_WidthStretch,
3139 ImGui::TableSetupColumn(
"Description",
3140 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3141 ImGui::TableHeadersRow();
3144 std::vector<std::tuple<const char*, Color*, const char*>>{
3146 "Table column headers"},
3148 "Outer table borders"},
3150 "Inner table borders"},
3152 "Normal table rows"},
3154 "Alternating table rows"},
3156 "Tree view connection lines"}};
3158 for (
auto& [label, color_ptr, description] : table_colors) {
3159 ImGui::TableNextRow();
3160 ImGui::TableNextColumn();
3161 ImGui::AlignTextToFramePadding();
3162 ImGui::Text(
"%s:", label);
3164 ImGui::TableNextColumn();
3166 std::string
id = absl::StrFormat(
"##table_%s", label);
3167 if (ImGui::ColorEdit4(
id.c_str(), &color_vec.x)) {
3168 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
3169 apply_live_preview();
3172 ImGui::TableNextColumn();
3173 ImGui::TextWrapped(
"%s", description);
3182 if (ImGui::CollapsingHeader(tr(
"Plots & Graphs"))) {
3183 if (ImGui::BeginTable(
"PlotsTable", 3,
3184 ImGuiTableFlags_SizingStretchProp)) {
3185 ImGui::TableSetupColumn(
"Element", ImGuiTableColumnFlags_WidthFixed,
3187 ImGui::TableSetupColumn(
"Picker",
3188 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3189 ImGui::TableSetupColumn(
"Description",
3190 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3191 ImGui::TableHeadersRow();
3194 std::vector<std::tuple<const char*, Color*, const char*>>{
3195 {
"Plot Lines", &edit_theme.
plot_lines,
"Line plot color"},
3197 "Line plot hover color"},
3199 "Histogram fill color"},
3200 {
"Plot Histogram Hovered",
3202 "Histogram hover color"}};
3204 for (
auto& [label, color_ptr, description] : plot_colors) {
3205 ImGui::TableNextRow();
3206 ImGui::TableNextColumn();
3207 ImGui::AlignTextToFramePadding();
3208 ImGui::Text(
"%s:", label);
3210 ImGui::TableNextColumn();
3212 std::string
id = absl::StrFormat(
"##plot_%s", label);
3213 if (ImGui::ColorEdit3(
id.c_str(), &color_vec.x)) {
3214 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
3216 apply_live_preview();
3219 ImGui::TableNextColumn();
3220 ImGui::TextWrapped(
"%s", description);
3227 ImGui::EndTabItem();
3231 if (ImGui::BeginTabItem(
3233 if (ImGui::BeginTable(
"BordersControlsTable", 3,
3234 ImGuiTableFlags_SizingStretchProp)) {
3235 ImGui::TableSetupColumn(
"Element", ImGuiTableColumnFlags_WidthFixed,
3237 ImGui::TableSetupColumn(
"Picker", ImGuiTableColumnFlags_WidthStretch,
3239 ImGui::TableSetupColumn(
"Description",
3240 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3241 ImGui::TableHeadersRow();
3243 auto border_control_colors =
3244 std::vector<std::tuple<const char*, Color*, const char*>>{
3245 {
"Border", &edit_theme.
border,
"General border color"},
3247 "Border shadow/depth"},
3249 "Horizontal/vertical separators"},
3251 "Separator hover state"},
3253 "Separator active/dragged state"},
3255 "Scrollbar track background"},
3257 "Scrollbar handle"},
3259 "Scrollbar handle hover"},
3261 "Scrollbar handle active"},
3263 "Window resize grip"},
3265 "Resize grip hover"},
3267 "Resize grip active"}};
3269 for (
auto& [label, color_ptr, description] : border_control_colors) {
3270 ImGui::TableNextRow();
3271 ImGui::TableNextColumn();
3272 ImGui::AlignTextToFramePadding();
3273 ImGui::Text(
"%s:", label);
3275 ImGui::TableNextColumn();
3277 std::string
id = absl::StrFormat(
"##border_%s", label);
3278 if (ImGui::ColorEdit4(
id.c_str(), &color_vec.x)) {
3279 *color_ptr = {color_vec.x, color_vec.y, color_vec.z, color_vec.w};
3280 apply_live_preview();
3283 ImGui::TableNextColumn();
3284 ImGui::TextWrapped(
"%s", description);
3290 ImGui::EndTabItem();
3294 if (ImGui::BeginTabItem(
3297 tr(
"Enhanced semantic colors and editor-specific customization"));
3301 if (ImGui::CollapsingHeader(tr(
"Enhanced Semantic Colors"),
3302 ImGuiTreeNodeFlags_DefaultOpen)) {
3303 if (ImGui::BeginTable(
"EnhancedSemanticTable", 3,
3304 ImGuiTableFlags_SizingStretchProp)) {
3305 ImGui::TableSetupColumn(
"Color", ImGuiTableColumnFlags_WidthFixed,
3307 ImGui::TableSetupColumn(
"Picker",
3308 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3309 ImGui::TableSetupColumn(
"Description",
3310 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3311 ImGui::TableHeadersRow();
3313 auto enhanced_colors =
3314 std::vector<std::tuple<const char*, Color*, const char*>>{
3316 "Code blocks background"},
3318 "Light success variant"},
3320 "Light warning variant"},
3322 "Light error variant"},
3324 "Light info variant"}};
3326 for (
auto& [label, color_ptr, description] : enhanced_colors) {
3327 ImGui::TableNextRow();
3328 ImGui::TableNextColumn();
3329 ImGui::AlignTextToFramePadding();
3330 ImGui::Text(
"%s:", label);
3332 ImGui::TableNextColumn();
3334 std::string
id = absl::StrFormat(
"##enhanced_%s", label);
3335 if (ImGui::ColorEdit3(
id.c_str(), &color_vec.x)) {
3336 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
3338 apply_live_preview();
3341 ImGui::TableNextColumn();
3342 ImGui::TextWrapped(
"%s", description);
3350 if (ImGui::CollapsingHeader(tr(
"UI State Colors"))) {
3351 if (ImGui::BeginTable(
"UIStateTable", 3,
3352 ImGuiTableFlags_SizingStretchProp)) {
3353 ImGui::TableSetupColumn(
"Color", ImGuiTableColumnFlags_WidthFixed,
3355 ImGui::TableSetupColumn(
"Picker",
3356 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3357 ImGui::TableSetupColumn(
"Description",
3358 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3359 ImGui::TableHeadersRow();
3362 ImGui::TableNextRow();
3363 ImGui::TableNextColumn();
3364 ImGui::AlignTextToFramePadding();
3365 ImGui::Text(tr(
"Active Selection:"));
3366 ImGui::TableNextColumn();
3367 ImVec4 active_selection =
3369 if (ImGui::ColorEdit4(
"##active_selection", &active_selection.x)) {
3371 active_selection.x, active_selection.y, active_selection.z,
3372 active_selection.w};
3373 apply_live_preview();
3375 ImGui::TableNextColumn();
3376 ImGui::TextWrapped(tr(
"Active/selected UI elements"));
3378 ImGui::TableNextRow();
3379 ImGui::TableNextColumn();
3380 ImGui::AlignTextToFramePadding();
3381 ImGui::Text(tr(
"Hover Highlight:"));
3382 ImGui::TableNextColumn();
3383 ImVec4 hover_highlight =
3385 if (ImGui::ColorEdit4(
"##hover_highlight", &hover_highlight.x)) {
3387 hover_highlight.x, hover_highlight.y, hover_highlight.z,
3389 apply_live_preview();
3391 ImGui::TableNextColumn();
3392 ImGui::TextWrapped(tr(
"General hover state highlighting"));
3394 ImGui::TableNextRow();
3395 ImGui::TableNextColumn();
3396 ImGui::AlignTextToFramePadding();
3397 ImGui::Text(tr(
"Focus Border:"));
3398 ImGui::TableNextColumn();
3400 if (ImGui::ColorEdit3(
"##focus_border", &focus_border.x)) {
3401 edit_theme.
focus_border = {focus_border.x, focus_border.y,
3402 focus_border.z, focus_border.w};
3403 apply_live_preview();
3405 ImGui::TableNextColumn();
3406 ImGui::TextWrapped(tr(
"Border for focused input elements"));
3408 ImGui::TableNextRow();
3409 ImGui::TableNextColumn();
3410 ImGui::AlignTextToFramePadding();
3411 ImGui::Text(tr(
"Disabled Overlay:"));
3412 ImGui::TableNextColumn();
3413 ImVec4 disabled_overlay =
3415 if (ImGui::ColorEdit4(
"##disabled_overlay", &disabled_overlay.x)) {
3417 disabled_overlay.x, disabled_overlay.y, disabled_overlay.z,
3418 disabled_overlay.w};
3419 apply_live_preview();
3421 ImGui::TableNextColumn();
3423 tr(
"Semi-transparent overlay for disabled elements"));
3430 if (ImGui::CollapsingHeader(tr(
"Editor-Specific Colors"))) {
3431 if (ImGui::BeginTable(
"EditorColorsTable", 3,
3432 ImGuiTableFlags_SizingStretchProp)) {
3433 ImGui::TableSetupColumn(
"Color", ImGuiTableColumnFlags_WidthFixed,
3435 ImGui::TableSetupColumn(
"Picker",
3436 ImGuiTableColumnFlags_WidthStretch, 0.6f);
3437 ImGui::TableSetupColumn(
"Description",
3438 ImGuiTableColumnFlags_WidthStretch, 0.4f);
3439 ImGui::TableHeadersRow();
3441 auto editor_colors =
3442 std::vector<std::tuple<const char*, Color*, const char*, bool>>{
3444 "Main editor canvas background",
false},
3446 "Grid lines in map/graphics editors",
true},
3448 "Cursor color in editors",
false},
3450 "Selection highlight in editors",
true},
3452 "Overworld entrance boxes",
true},
3454 "Overworld hole boxes",
true},
3456 "Overworld exit boxes",
true},
3458 "Overworld item boxes",
true},
3460 "Overworld sprite boxes",
true},
3462 "Transport and connection markers",
true},
3464 "Music zone markers",
true}};
3466 for (
auto& [label, color_ptr, description, use_alpha] :
3468 ImGui::TableNextRow();
3469 ImGui::TableNextColumn();
3470 ImGui::AlignTextToFramePadding();
3471 ImGui::Text(
"%s:", label);
3473 ImGui::TableNextColumn();
3475 std::string
id = absl::StrFormat(
"##editor_%s", label);
3476 if (use_alpha ? ImGui::ColorEdit4(
id.c_str(), &color_vec.x)
3477 : ImGui::ColorEdit3(
id.c_str(), &color_vec.x)) {
3478 *color_ptr = {color_vec.x, color_vec.y, color_vec.z,
3480 apply_live_preview();
3483 ImGui::TableNextColumn();
3484 ImGui::TextWrapped(
"%s", description);
3491 ImGui::EndTabItem();
3495 if (ImGui::BeginTabItem(
3497 ImGui::Text(tr(
"Control UI density, spacing, and typography scaling"));
3501 ImGui::TextColored(ImVec4(0.7f, 0.8f, 1.0f, 1.0f),
3506 int current_preset =
static_cast<int>(edit_theme.
density_preset);
3508 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(20, 12));
3509 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 0));
3512 bool is_compact = (current_preset == 0);
3514 ImGui::PushStyleColor(
3523 apply_live_preview();
3526 ImGui::PopStyleColor();
3528 if (ImGui::IsItemHovered()) {
3530 tr(
"Dense UI with smaller widgets and tighter spacing\n"
3531 "Best for: Information-dense workflows"));
3537 bool is_normal = (current_preset == 1);
3539 ImGui::PushStyleColor(
3544 if (ImGui::Button(absl::StrFormat(
"%s Normal",
ICON_MD_CHECK).c_str(),
3547 apply_live_preview();
3550 ImGui::PopStyleColor();
3552 if (ImGui::IsItemHovered()) {
3554 tr(
"Balanced spacing and widget sizes\n"
3555 "Best for: General use"));
3561 bool is_comfortable = (current_preset == 2);
3562 if (is_comfortable) {
3563 ImGui::PushStyleColor(
3572 apply_live_preview();
3574 if (is_comfortable) {
3575 ImGui::PopStyleColor();
3577 if (ImGui::IsItemHovered()) {
3579 tr(
"Spacious layout with larger click targets\n"
3580 "Best for: Touch screens, accessibility"));
3583 ImGui::PopStyleVar(2);
3590 if (ImGui::CollapsingHeader(tr(
"Advanced Density Controls"))) {
3593 ImGui::Text(tr(
"Fine-tune individual spacing multipliers:"));
3597 if (ImGui::SliderFloat(tr(
"Compact Factor"),
3600 apply_live_preview();
3602 if (ImGui::IsItemHovered()) {
3604 tr(
"Global density multiplier (0.5 = very compact, "
3605 "1.5 = very spacious)"));
3611 if (ImGui::SliderFloat(tr(
"Widget Height"),
3614 apply_live_preview();
3618 if (ImGui::SliderFloat(tr(
"Item Spacing"),
3621 apply_live_preview();
3625 if (ImGui::SliderFloat(tr(
"Toolbar Height"),
3628 apply_live_preview();
3632 if (ImGui::SliderFloat(tr(
"Panel Padding"),
3635 apply_live_preview();
3639 if (ImGui::SliderFloat(tr(
"Button Padding"),
3642 apply_live_preview();
3646 if (ImGui::SliderFloat(tr(
"Table Row Height"),
3649 apply_live_preview();
3656 if (ImGui::CollapsingHeader(tr(
"Corner Rounding"))) {
3659 if (ImGui::SliderFloat(tr(
"Window Rounding"),
3662 apply_live_preview();
3665 if (ImGui::SliderFloat(tr(
"Frame Rounding"),
3668 apply_live_preview();
3671 if (ImGui::SliderFloat(tr(
"Tab Rounding"), &edit_theme.
tab_rounding,
3672 0.0f, 12.0f,
"%.1f")) {
3673 apply_live_preview();
3676 if (ImGui::SliderFloat(tr(
"Scrollbar Rounding"),
3679 apply_live_preview();
3682 if (ImGui::SliderFloat(tr(
"Grab Rounding"), &edit_theme.
grab_rounding,
3683 0.0f, 12.0f,
"%.1f")) {
3684 apply_live_preview();
3691 if (ImGui::CollapsingHeader(tr(
"Animation Settings"))) {
3694 if (ImGui::Checkbox(tr(
"Enable Animations"),
3696 apply_live_preview();
3700 if (ImGui::SliderFloat(tr(
"Animation Speed"),
3703 apply_live_preview();
3707 if (ImGui::Checkbox(tr(
"Enable Glow Effects"),
3709 apply_live_preview();
3715 ImGui::EndTabItem();
3723 if (ImGui::Button(tr(
"Preview Theme"))) {
3728 if (ImGui::Button(tr(
"Reset to Current"))) {
3734 theme_name[name_len] =
'\0';
3737 sizeof(theme_description) - 1);
3740 theme_description[desc_len] =
'\0';
3745 theme_author[author_len] =
'\0';
3748 if (theme_backup_made) {
3749 theme_backup_made =
false;
3759 if (ImGui::Button(tr(
"Save Theme"))) {
3760 edit_theme.
name = std::string(theme_name);
3761 edit_theme.
description = std::string(theme_description);
3762 edit_theme.
author = std::string(theme_author);
3769 theme_backup_made =
false;
3776 bool can_save_over = !current_file_path.empty();
3778 if (!can_save_over) {
3779 ImGui::BeginDisabled();
3782 if (ImGui::Button(tr(
"Save Over Current"))) {
3783 edit_theme.
name = std::string(theme_name);
3784 edit_theme.
description = std::string(theme_description);
3785 edit_theme.
author = std::string(theme_author);
3795 LOG_ERROR(
"Theme Manager",
"Failed to save over current theme");
3799 if (!can_save_over) {
3800 ImGui::EndDisabled();
3803 if (ImGui::IsItemHovered() && can_save_over) {
3804 ImGui::BeginTooltip();
3805 ImGui::Text(tr(
"Save over current theme file:"));
3806 ImGui::Text(
"%s", current_file_path.c_str());
3807 ImGui::EndTooltip();
3808 }
else if (ImGui::IsItemHovered()) {
3809 ImGui::BeginTooltip();
3810 ImGui::Text(tr(
"No current theme file to overwrite"));
3811 ImGui::Text(tr(
"Use 'Save to File...' to create a new theme file"));
3812 ImGui::EndTooltip();
3816 if (ImGui::Button(tr(
"Save to File..."))) {
3817 edit_theme.
name = std::string(theme_name);
3818 edit_theme.
description = std::string(theme_description);
3819 edit_theme.
author = std::string(theme_author);
3822 std::string safe_name =
3823 edit_theme.
name.empty() ?
"custom_theme" : edit_theme.
name;
3827 if (!file_path.empty()) {
3829 if (file_path.find(
".theme") == std::string::npos) {
3830 file_path +=
".theme";
3839 LOG_ERROR(
"Theme Manager",
"Failed to save theme");
3844 if (ImGui::IsItemHovered()) {
3845 ImGui::BeginTooltip();
3846 ImGui::Text(tr(
"Save theme to a .theme file"));
3847 ImGui::Text(tr(
"Saved themes can be shared and loaded later"));
3848 ImGui::EndTooltip();