Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 51 additions & 19 deletions src/Menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,29 @@ Menu::~Menu()

void Menu::Load(json& o_json)
{
// Store current Theme state before loading config
auto currentTheme = settings.Theme;

settings = o_json;
bool hasThemeObject = o_json.contains("Theme") && o_json["Theme"].is_object();
bool hasFontRoles = hasThemeObject && o_json["Theme"].contains("FontRoles");
MenuFonts::NormalizeFontRoles(settings.Theme, hasFontRoles);
auto& bodyRole = settings.Theme.FontRoles[static_cast<size_t>(FontRole::Body)];
if (!Util::ValidateFont(bodyRole.File)) {
const auto& defaults = Menu::GetDefaultFontRole(FontRole::Body);
logger::warn("Font '{}' not found while loading settings, falling back to default font '{}'",
bodyRole.File, defaults.File);
settings.Theme.FontRoles[static_cast<size_t>(FontRole::Body)] = defaults;
settings.Theme.FontName = defaults.File;

// Restore Theme - don't load it from config, only from theme preset files
settings.Theme = currentTheme;

// Legacy support: If old config has Theme data and no SelectedThemePreset, load it
if (o_json.contains("Theme") && o_json["Theme"].is_object() && settings.SelectedThemePreset.empty()) {
bool hasFontRoles = o_json["Theme"].contains("FontRoles");
settings.Theme = o_json["Theme"];
MenuFonts::NormalizeFontRoles(settings.Theme, hasFontRoles);

auto& bodyRole = settings.Theme.FontRoles[static_cast<size_t>(FontRole::Body)];
if (!Util::ValidateFont(bodyRole.File)) {
const auto& defaults = Menu::GetDefaultFontRole(FontRole::Body);
logger::warn("Font '{}' not found while loading settings, falling back to default font '{}'",
bodyRole.File, defaults.File);
settings.Theme.FontRoles[static_cast<size_t>(FontRole::Body)] = defaults;
settings.Theme.FontName = defaults.File;
}
logger::info("Loaded legacy Theme data from config (no SelectedThemePreset)");
}

// Apply Default Dark theme on first launch if no theme is selected
Expand Down Expand Up @@ -256,7 +268,13 @@ void Menu::Load(json& o_json)
void Menu::Save(json& o_json)
{
settings.Theme.FontName = settings.Theme.FontRoles[static_cast<size_t>(FontRole::Body)].File;

// Save all settings except Theme values
// Theme values should only be saved in theme preset files, not in the main config
o_json = settings;

// Remove Theme object from config, only keep SelectedThemePreset
o_json.erase("Theme");
}

void Menu::LoadTheme(json& o_json)
Expand Down Expand Up @@ -316,13 +334,12 @@ bool Menu::LoadThemePreset(const std::string& themeName)
json themeSettings;

if (themeManager->LoadTheme(themeName, themeSettings)) {
try {
// Create a backup of current theme in case loading fails
ThemeSettings backupTheme = settings.Theme;
ThemeSettings defaultTheme; // For fallback values

bool hasFontRoles = themeSettings.contains("FontRoles");
// Create a backup of current theme in case loading fails
ThemeSettings backupTheme = settings.Theme;
ThemeSettings defaultTheme; // For fallback values
bool hasFontRoles = themeSettings.contains("FontRoles");

try {
// Attempt to load theme with protection against malformed data
try {
settings.Theme = themeSettings;
Expand Down Expand Up @@ -467,10 +484,12 @@ bool Menu::LoadThemePreset(const std::string& themeName)
// Apply background blur enabled state from theme
BackgroundBlur::SetEnabled(settings.Theme.BackgroundBlurEnabled);

logger::info("Loaded theme preset: {}", themeName);
logger::info("Applied theme preset: {}", themeName);
return true;
} catch (const std::exception& e) {
logger::error("Fatal error loading theme '{}': {}.", themeName, e.what());
logger::warn("Error loading theme '{}': {}", themeName, e.what());
// Restore backup to maintain UI consistency
settings.Theme = backupTheme;
return false;
}
} else {
Expand All @@ -481,7 +500,6 @@ bool Menu::LoadThemePreset(const std::string& themeName)

void Menu::CreateDefaultThemes()
{
// Use ThemeManager to create default theme files
auto themeManager = ThemeManager::GetSingleton();
themeManager->CreateDefaultThemeFiles();
}
Expand All @@ -508,6 +526,20 @@ void Menu::Init()
ThemeManager::ForceApplyDefaultTheme();
}

// Re-apply user-selected preset after defaults are applied (covers Default and custom)
if (!settings.SelectedThemePreset.empty()) {
auto themeManagerSingleton = ThemeManager::GetSingleton();
if (themeManagerSingleton && !themeManagerSingleton->IsDiscovered()) {
themeManagerSingleton->DiscoverThemes();
}

if (!LoadThemePreset(settings.SelectedThemePreset)) {
logger::warn("Failed to re-apply preset '{}' during Menu::Init. Keeping Default.", settings.SelectedThemePreset);
} else {
logger::info("Re-applied preset '{}' during Menu::Init", settings.SelectedThemePreset);
}
}

auto& imgui_io = ImGui::GetIO();
imgui_io.ConfigFlags = ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_DockingEnable;
imgui_io.BackendFlags = ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_HasGamepad;
Expand Down
145 changes: 121 additions & 24 deletions src/Menu/SettingsTabRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

#include <algorithm>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <format>
#include <imgui.h>
#include <imgui_internal.h>
#include <set>
#include <string>
#include <windows.h>

Expand Down Expand Up @@ -334,6 +330,18 @@ void SettingsTabRenderer::RenderThemesTab()
static char newThemeName[128] = "";
static char newThemeDisplayName[128] = "";
static char newThemeDescription[256] = "";
static bool showValidationError = false;

// Update feedback tracking
static bool showUpdateFeedback = false;
struct ChangedSetting
{
std::string path;
std::string oldValue;
std::string newValue;
};
static std::vector<ChangedSetting> changedSettings;
static bool updateSuccess = false;

// Theme Preset Selection
SeparatorTextWithFont("Theme Preset", Menu::FontRole::Subheading);
Expand Down Expand Up @@ -456,21 +464,60 @@ void SettingsTabRenderer::RenderThemesTab()
// Update existing theme
const auto* currentThemeInfo = themeManager->GetThemeInfo(currentThemePreset);
if (currentThemeInfo) {
// Use the existing SaveTheme method to serialize the theme settings
// Get current settings
json currentThemeJson;
globals::menu->SaveTheme(currentThemeJson);

// Get saved theme settings for comparison
json savedThemeJson = currentThemeInfo->themeData["Theme"];

// Compare and collect changed settings (with old/new values)
changedSettings.clear();
std::function<void(const std::string&, const json&, const json&)> diffWalker;
diffWalker = [&](const std::string& path, const json& oldVal, const json& newVal) {
// Handle objects by recursing through union of keys
if (oldVal.is_object() && newVal.is_object()) {
std::set<std::string> keys;
for (auto& [k, _] : oldVal.items()) keys.insert(k);
for (auto& [k, _] : newVal.items()) keys.insert(k);
for (const auto& k : keys) {
auto nextPath = path.empty() ? k : path + "." + k;
const json& oldChild = oldVal.contains(k) ? oldVal[k] : json();
const json& newChild = newVal.contains(k) ? newVal[k] : json();
diffWalker(nextPath, oldChild, newChild);
}
return;
}

// For arrays or primitives, record if different
if (oldVal != newVal) {
changedSettings.push_back({ path.empty() ? "<root>" : path,
oldVal.is_null() ? "null" : oldVal.dump(),
newVal.is_null() ? "null" : newVal.dump() });
}
};

diffWalker("", savedThemeJson, currentThemeJson["Theme"]);

logger::info("Attempting to update theme: '{}'", currentThemePreset);

// Overwrite the current theme with updated settings
if (themeManager->SaveTheme(currentThemePreset, currentThemeJson["Theme"],
currentThemeInfo->displayName, currentThemeInfo->description)) {
logger::info("Theme '{}' updated successfully", currentThemePreset);
updateSuccess = true;
showUpdateFeedback = true;
} else {
logger::error("Failed to update theme: '{}'", currentThemePreset);
updateSuccess = false;
showUpdateFeedback = true;
changedSettings.clear();
}
} else {
logger::warn("Cannot update theme '{}' - theme info not found", currentThemePreset);
updateSuccess = false;
showUpdateFeedback = true;
changedSettings.clear();
}
}
}
Expand All @@ -483,6 +530,29 @@ void SettingsTabRenderer::RenderThemesTab()
}
}

// Display update feedback below the buttons
if (showUpdateFeedback) {
ImGui::Spacing();
ImGui::Separator();

if (updateSuccess) {
if (changedSettings.empty()) {
ImGui::TextColored(themeSettings.StatusPalette.SuccessColor, "Theme updated successfully - no changes detected");
} else {
ImGui::TextColored(themeSettings.StatusPalette.SuccessColor, "Theme updated successfully! Changed settings:");
ImGui::Indent();
for (const auto& change : changedSettings) {
ImGui::BulletText("%s: %s -> %s", change.path.c_str(), change.oldValue.c_str(), change.newValue.c_str());
}
ImGui::Unindent();
}
} else {
ImGui::TextColored(themeSettings.StatusPalette.Error, "Failed to update theme");
}

ImGui::Separator();
}

// Create Theme Popup
if (showCreateThemePopup) {
ImGui::OpenPopup("Create New Theme");
Expand All @@ -493,7 +563,26 @@ void SettingsTabRenderer::RenderThemesTab()
ImGui::Text("Create a new theme with your current settings:");
ImGui::Separator();

bool isThemeNameEmpty = strlen(newThemeName) == 0;

// Highlight the input field if invalid and validation error is shown
if (isThemeNameEmpty && showValidationError) {
ImGui::PushStyleColor(ImGuiCol_Border, themeSettings.StatusPalette.Error);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
}

ImGui::InputText("Theme Name", newThemeName, sizeof(newThemeName));

if (isThemeNameEmpty && showValidationError) {
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}

// Show inline error message
if (isThemeNameEmpty && showValidationError) {
ImGui::TextColored(themeSettings.StatusPalette.Error, "Theme name is required");
}

if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("File name for the theme (without .json extension)");
}
Expand All @@ -511,25 +600,33 @@ void SettingsTabRenderer::RenderThemesTab()
ImGui::Separator();

// Buttons
if (Util::ButtonWithFlash("Create Theme") && strlen(newThemeName) > 0) {
// Use the existing SaveTheme method to serialize the theme settings
json currentThemeJson;
globals::menu->SaveTheme(currentThemeJson);

std::string displayName = strlen(newThemeDisplayName) > 0 ? std::string(newThemeDisplayName) : std::string(newThemeName);
std::string description = strlen(newThemeDescription) > 0 ? std::string(newThemeDescription) : "";

logger::info("Attempting to save new theme: '{}' with display name: '{}'", newThemeName, displayName);

if (themeManager->SaveTheme(std::string(newThemeName), currentThemeJson["Theme"], displayName, description)) {
logger::info("Theme saved successfully. Loading theme preset: '{}'", newThemeName);
// Theme created successfully, load it and exit create mode
globals::menu->LoadThemePreset(std::string(newThemeName));
isCreatingNewTheme = false;
showCreateThemePopup = false;
logger::info("Theme creation complete. Total themes: {}", themeManager->GetThemes().size());
if (Util::ButtonWithFlash("Create Theme")) {
if (strlen(newThemeName) > 0) {
// Valid theme name, reset error state and proceed
showValidationError = false;

// Use the existing SaveTheme method to serialize the theme settings
json currentThemeJson;
globals::menu->SaveTheme(currentThemeJson);

std::string displayName = strlen(newThemeDisplayName) > 0 ? std::string(newThemeDisplayName) : std::string(newThemeName);
std::string description = strlen(newThemeDescription) > 0 ? std::string(newThemeDescription) : "";

logger::info("Attempting to save new theme: '{}' with display name: '{}'", newThemeName, displayName);

if (themeManager->SaveTheme(std::string(newThemeName), currentThemeJson["Theme"], displayName, description)) {
logger::info("Theme saved successfully. Loading theme preset: '{}'", newThemeName);
// Theme created successfully, load it and exit create mode
globals::menu->LoadThemePreset(std::string(newThemeName));
isCreatingNewTheme = false;
showCreateThemePopup = false;
logger::info("Theme creation complete. Total themes: {}", themeManager->GetThemes().size());
} else {
logger::error("Failed to save theme: '{}'", newThemeName);
}
} else {
logger::error("Failed to save theme: '{}'", newThemeName);
// Empty theme name, show validation error
showValidationError = true;
}
}

Expand Down
Loading