Enhance theme management with dynamic discovery and macOS bundle support
- Updated CMake configuration to ensure theme files are included in macOS bundles and added explicit file handling for theme resources. - Refactored ThemeManager to replace hardcoded theme lists with dynamic discovery of available themes, improving flexibility across development and deployment scenarios. - Introduced methods for refreshing and loading themes at runtime, enhancing user experience and customization options. - Improved logging for theme loading processes, providing better feedback on successes and failures.
This commit is contained in:
@@ -122,11 +122,14 @@ endif()
|
|||||||
# ImGui (after minimal build flags are set)
|
# ImGui (after minimal build flags are set)
|
||||||
include(cmake/imgui.cmake)
|
include(cmake/imgui.cmake)
|
||||||
|
|
||||||
# Project Files
|
# Project Files
|
||||||
# Copy theme files to build directory
|
# Copy theme files to build directory (for development)
|
||||||
file(GLOB THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme")
|
file(GLOB THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme")
|
||||||
file(COPY ${THEME_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/themes/")
|
file(COPY ${THEME_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/themes/")
|
||||||
|
|
||||||
|
# IMPORTANT: Also ensure themes are included in macOS bundles
|
||||||
|
# This is handled in src/CMakeLists.txt via YAZE_RESOURCE_FILES
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ set(YAZE_RESOURCE_FILES
|
|||||||
${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf
|
${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add theme files for macOS bundle (replacing the glob pattern with explicit files)
|
||||||
|
file(GLOB YAZE_THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme")
|
||||||
|
list(APPEND YAZE_RESOURCE_FILES ${YAZE_THEME_FILES})
|
||||||
|
|
||||||
foreach (FILE ${YAZE_RESOURCE_FILES})
|
foreach (FILE ${YAZE_RESOURCE_FILES})
|
||||||
file(RELATIVE_PATH NEW_FILE "${CMAKE_SOURCE_DIR}/assets" ${FILE})
|
file(RELATIVE_PATH NEW_FILE "${CMAKE_SOURCE_DIR}/assets" ${FILE})
|
||||||
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
|
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
@@ -109,20 +110,10 @@ void ThemeManager::InitializeBuiltInThemes() {
|
|||||||
// Always create fallback theme first
|
// Always create fallback theme first
|
||||||
CreateFallbackYazeClassic();
|
CreateFallbackYazeClassic();
|
||||||
|
|
||||||
// Try to load themes from files (will override fallback if successful)
|
// Load all available theme files dynamically
|
||||||
std::vector<std::string> theme_files = {
|
auto status = LoadAllAvailableThemes();
|
||||||
"yaze_tre.theme",
|
if (!status.ok()) {
|
||||||
"cyberpunk.theme",
|
util::logf("Warning: Failed to load some theme files: %s", status.message().data());
|
||||||
"sunset.theme",
|
|
||||||
"forest.theme",
|
|
||||||
"midnight.theme"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& theme_file : theme_files) {
|
|
||||||
auto status = LoadThemeFromFile(theme_file);
|
|
||||||
if (!status.ok()) {
|
|
||||||
util::logf("Failed to load theme file %s: %s", theme_file.c_str(), status.message().data());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we have a valid current theme (prefer file-based theme)
|
// Ensure we have a valid current theme (prefer file-based theme)
|
||||||
@@ -424,6 +415,14 @@ void ThemeManager::ShowThemeSelector(bool* p_open) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
if (ImGui::Button(absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) {
|
||||||
|
auto status = RefreshAvailableThemes();
|
||||||
|
if (!status.ok()) {
|
||||||
|
util::logf("Failed to refresh themes: %s", status.message().data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(absl::StrFormat("%s Load Custom Theme", ICON_MD_FOLDER_OPEN).c_str())) {
|
if (ImGui::Button(absl::StrFormat("%s Load Custom Theme", ICON_MD_FOLDER_OPEN).c_str())) {
|
||||||
auto file_path = core::FileDialogWrapper::ShowOpenFileDialog();
|
auto file_path = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
if (!file_path.empty()) {
|
if (!file_path.empty()) {
|
||||||
@@ -875,5 +874,163 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
|
|||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ThemeManager::GetThemeSearchPaths() const {
|
||||||
|
std::vector<std::string> search_paths;
|
||||||
|
|
||||||
|
// Development path (relative to build directory)
|
||||||
|
search_paths.push_back("assets/themes/");
|
||||||
|
search_paths.push_back("../assets/themes/");
|
||||||
|
|
||||||
|
// Platform-specific resource paths
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// macOS bundle resource path (this should be the primary path for bundled apps)
|
||||||
|
std::string bundle_themes = core::GetResourcePath("assets/themes/");
|
||||||
|
util::logf("🔍 Bundle themes path from GetResourcePath: '%s'", bundle_themes.c_str());
|
||||||
|
if (!bundle_themes.empty()) {
|
||||||
|
search_paths.push_back(bundle_themes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternative bundle locations
|
||||||
|
std::string bundle_root = core::GetBundleResourcePath();
|
||||||
|
util::logf("🔍 Bundle root path: '%s'", bundle_root.c_str());
|
||||||
|
|
||||||
|
search_paths.push_back(bundle_root + "Contents/Resources/themes/");
|
||||||
|
search_paths.push_back(bundle_root + "Contents/Resources/assets/themes/");
|
||||||
|
search_paths.push_back(bundle_root + "assets/themes/");
|
||||||
|
search_paths.push_back(bundle_root + "themes/");
|
||||||
|
#else
|
||||||
|
// Linux/Windows relative paths
|
||||||
|
search_paths.push_back("./assets/themes/");
|
||||||
|
search_paths.push_back("./themes/");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// User config directory
|
||||||
|
std::string config_themes = core::GetConfigDirectory() + "/themes/";
|
||||||
|
search_paths.push_back(config_themes);
|
||||||
|
|
||||||
|
// Debug: Print all search paths
|
||||||
|
util::logf("🔍 Theme search paths (%zu total):", search_paths.size());
|
||||||
|
for (size_t i = 0; i < search_paths.size(); ++i) {
|
||||||
|
util::logf(" [%zu]: '%s'", i, search_paths[i].c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return search_paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ThemeManager::GetThemesDirectory() const {
|
||||||
|
auto search_paths = GetThemeSearchPaths();
|
||||||
|
|
||||||
|
// Try each search path and return the first one that exists
|
||||||
|
for (const auto& path : search_paths) {
|
||||||
|
std::ifstream test_file(path + "."); // Test if directory exists by trying to access it
|
||||||
|
if (test_file.good()) {
|
||||||
|
util::logf("Found themes directory: %s", path.c_str());
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also try with platform-specific directory separators
|
||||||
|
std::string normalized_path = path;
|
||||||
|
if (!normalized_path.empty() && normalized_path.back() != '/' && normalized_path.back() != '\\') {
|
||||||
|
normalized_path += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream test_file2(normalized_path + ".");
|
||||||
|
if (test_file2.good()) {
|
||||||
|
util::logf("Found themes directory: %s", normalized_path.c_str());
|
||||||
|
return normalized_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util::logf("No themes directory found in search paths");
|
||||||
|
return search_paths.empty() ? "assets/themes/" : search_paths[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ThemeManager::DiscoverAvailableThemeFiles() const {
|
||||||
|
std::vector<std::string> theme_files;
|
||||||
|
auto search_paths = GetThemeSearchPaths();
|
||||||
|
|
||||||
|
for (const auto& search_path : search_paths) {
|
||||||
|
util::logf("Searching for theme files in: %s", search_path.c_str());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use platform-specific file discovery instead of glob
|
||||||
|
#ifdef __APPLE__
|
||||||
|
auto files_in_folder = core::FileDialogWrapper::GetFilesInFolder(search_path);
|
||||||
|
for (const auto& file : files_in_folder) {
|
||||||
|
if (file.length() > 6 && file.substr(file.length() - 6) == ".theme") {
|
||||||
|
std::string full_path = search_path + file;
|
||||||
|
util::logf("Found theme file: %s", full_path.c_str());
|
||||||
|
theme_files.push_back(full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// For Linux/Windows, use filesystem directory iteration
|
||||||
|
// (could be extended with platform-specific implementations if needed)
|
||||||
|
std::vector<std::string> known_themes = {
|
||||||
|
"yaze_tre.theme", "cyberpunk.theme", "sunset.theme",
|
||||||
|
"forest.theme", "midnight.theme"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& theme_name : known_themes) {
|
||||||
|
std::string full_path = search_path + theme_name;
|
||||||
|
std::ifstream test_file(full_path);
|
||||||
|
if (test_file.good()) {
|
||||||
|
util::logf("Found theme file: %s", full_path.c_str());
|
||||||
|
theme_files.push_back(full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
util::logf("Error scanning directory %s: %s", search_path.c_str(), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicates while preserving order
|
||||||
|
std::vector<std::string> unique_files;
|
||||||
|
std::set<std::string> seen_basenames;
|
||||||
|
|
||||||
|
for (const auto& file : theme_files) {
|
||||||
|
std::string basename = core::GetFileName(file);
|
||||||
|
if (seen_basenames.find(basename) == seen_basenames.end()) {
|
||||||
|
unique_files.push_back(file);
|
||||||
|
seen_basenames.insert(basename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util::logf("Discovered %zu unique theme files", unique_files.size());
|
||||||
|
return unique_files;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ThemeManager::LoadAllAvailableThemes() {
|
||||||
|
auto theme_files = DiscoverAvailableThemeFiles();
|
||||||
|
|
||||||
|
int successful_loads = 0;
|
||||||
|
int failed_loads = 0;
|
||||||
|
|
||||||
|
for (const auto& theme_file : theme_files) {
|
||||||
|
auto status = LoadThemeFromFile(theme_file);
|
||||||
|
if (status.ok()) {
|
||||||
|
successful_loads++;
|
||||||
|
util::logf("✅ Successfully loaded theme: %s", theme_file.c_str());
|
||||||
|
} else {
|
||||||
|
failed_loads++;
|
||||||
|
util::logf("❌ Failed to load theme %s: %s", theme_file.c_str(), status.message().data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util::logf("Theme loading complete: %d successful, %d failed", successful_loads, failed_loads);
|
||||||
|
|
||||||
|
if (successful_loads == 0 && failed_loads > 0) {
|
||||||
|
return absl::InternalError(absl::StrFormat("Failed to load any themes (%d failures)", failed_loads));
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ThemeManager::RefreshAvailableThemes() {
|
||||||
|
util::logf("Refreshing available themes...");
|
||||||
|
return LoadAllAvailableThemes();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace gui
|
} // namespace gui
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -138,6 +138,12 @@ public:
|
|||||||
absl::Status LoadThemeFromFile(const std::string& filepath);
|
absl::Status LoadThemeFromFile(const std::string& filepath);
|
||||||
absl::Status SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const;
|
absl::Status SaveThemeToFile(const EnhancedTheme& theme, const std::string& filepath) const;
|
||||||
|
|
||||||
|
// Dynamic theme discovery - replaces hardcoded theme lists with automatic discovery
|
||||||
|
// This works across development builds, macOS app bundles, and other deployment scenarios
|
||||||
|
std::vector<std::string> DiscoverAvailableThemeFiles() const;
|
||||||
|
absl::Status LoadAllAvailableThemes();
|
||||||
|
absl::Status RefreshAvailableThemes(); // Public method to refresh at runtime
|
||||||
|
|
||||||
// Built-in themes
|
// Built-in themes
|
||||||
void InitializeBuiltInThemes();
|
void InitializeBuiltInThemes();
|
||||||
std::vector<std::string> GetAvailableThemes() const;
|
std::vector<std::string> GetAvailableThemes() const;
|
||||||
@@ -171,6 +177,10 @@ private:
|
|||||||
absl::Status ParseThemeFile(const std::string& content, EnhancedTheme& theme);
|
absl::Status ParseThemeFile(const std::string& content, EnhancedTheme& theme);
|
||||||
Color ParseColorFromString(const std::string& color_str) const;
|
Color ParseColorFromString(const std::string& color_str) const;
|
||||||
std::string SerializeTheme(const EnhancedTheme& theme) const;
|
std::string SerializeTheme(const EnhancedTheme& theme) const;
|
||||||
|
|
||||||
|
// Helper methods for path resolution
|
||||||
|
std::vector<std::string> GetThemeSearchPaths() const;
|
||||||
|
std::string GetThemesDirectory() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gui
|
} // namespace gui
|
||||||
|
|||||||
Reference in New Issue
Block a user