23#include <nlohmann/json.hpp>
27#include <shared_mutex>
39using json = nlohmann::json;
53 std::filesystem::path configPath;
54 size_t maxRecentFiles = 10;
55 mutable std::shared_mutex mutex;
58 static constexpr int DEFAULT_WINDOW_WIDTH = 1200;
59 static constexpr int DEFAULT_WINDOW_HEIGHT = 800;
60 static constexpr const char* DEFAULT_THEME =
"Modern";
65 void ResetUnlocked() {
66 config = json::object();
67 config[
"window"][
"width"] = DEFAULT_WINDOW_WIDTH;
68 config[
"window"][
"height"] = DEFAULT_WINDOW_HEIGHT;
69 config[
"window"][
"maximized"] =
false;
70 config[
"theme"] = DEFAULT_THEME;
71 config[
"recentFiles"] = json::array();
72 config[
"settings"] = json::object();
79 m_impl->ResetUnlocked();
86 const std::unique_lock lock(m_impl->mutex);
88 if (!std::filesystem::exists(m_impl->configPath)) {
89 LOG_INFO(
"Config file not found, using defaults");
93 std::ifstream file(m_impl->configPath);
94 if (!file.is_open()) {
95 LOG_ERROR(
"Failed to open config file: {}", m_impl->configPath.string());
99 m_impl->config = json::parse(file);
100 LOG_INFO(
"Configuration loaded from: {}", m_impl->configPath.string());
102 }
catch (
const json::exception& e) {
103 LOG_ERROR(
"Failed to parse config file: {}", e.what());
104 m_impl->ResetUnlocked();
106 }
catch (
const std::exception& e) {
107 LOG_ERROR(
"Failed to load config: {}", e.what());
115 std::filesystem::path path;
117 const std::shared_lock lock(m_impl->mutex);
118 snapshot = m_impl->config;
119 path = m_impl->configPath;
123 if (!EnsureConfigDirectoryExists()) {
124 LOG_ERROR(
"Failed to create config directory");
128 std::ofstream file(path);
129 if (!file.is_open()) {
130 LOG_ERROR(
"Failed to open config file for writing: {}", path.string());
134 file << snapshot.dump(2);
135 LOG_INFO(
"Configuration saved to: {}", path.string());
137 }
catch (
const std::exception& e) {
138 LOG_ERROR(
"Failed to save config: {}", e.what());
144 const std::unique_lock lock(m_impl->mutex);
145 m_impl->ResetUnlocked();
149 const std::shared_lock lock(m_impl->mutex);
150 return std::filesystem::exists(m_impl->configPath);
154 return GetConfigDirectory() /
"config.json";
160 const std::unique_lock lock(m_impl->mutex);
161 m_impl->config[
"window"][
"x"] = x;
162 m_impl->config[
"window"][
"y"] = y;
166 const std::unique_lock lock(m_impl->mutex);
167 m_impl->config[
"window"][
"width"] = width;
168 m_impl->config[
"window"][
"height"] = height;
172 const std::shared_lock lock(m_impl->mutex);
174 if (m_impl->config.contains(
"window") && m_impl->config[
"window"].contains(
"x") &&
175 m_impl->config[
"window"].contains(
"y")) {
176 return std::make_pair(m_impl->config[
"window"][
"x"].get<
int>(), m_impl->config[
"window"][
"y"].get<
int>());
178 }
catch (
const json::exception& e) {
179 LOG_WARNING(
"Failed to get window position from config: {}", e.what());
185 const std::shared_lock lock(m_impl->mutex);
187 if (m_impl->config.contains(
"window") && m_impl->config[
"window"].contains(
"width") &&
188 m_impl->config[
"window"].contains(
"height")) {
189 return std::make_pair(m_impl->config[
"window"][
"width"].get<
int>(),
190 m_impl->config[
"window"][
"height"].get<
int>());
192 }
catch (
const json::exception& e) {
193 LOG_WARNING(
"Failed to get window size from config: {}", e.what());
199 const std::unique_lock lock(m_impl->mutex);
200 m_impl->config[
"window"][
"maximized"] = maximized;
204 const std::shared_lock lock(m_impl->mutex);
206 if (m_impl->config.contains(
"window") && m_impl->config[
"window"].contains(
"maximized")) {
207 return m_impl->config[
"window"][
"maximized"].get<
bool>();
209 }
catch (
const json::exception& e) {
210 LOG_WARNING(
"Failed to get window maximized state from config: {}", e.what());
218 const std::unique_lock lock(m_impl->mutex);
219 m_impl->config[
"theme"] = theme;
223 const std::shared_lock lock(m_impl->mutex);
225 if (m_impl->config.contains(
"theme")) {
226 return m_impl->config[
"theme"].get<std::string>();
228 }
catch (
const json::exception& e) {
229 LOG_WARNING(
"Failed to get theme from config: {}", e.what());
231 return Impl::DEFAULT_THEME;
237 const std::unique_lock lock(m_impl->mutex);
238 if (!m_impl->config.contains(
"recentFiles")) {
239 m_impl->config[
"recentFiles"] = json::array();
242 auto& recentFiles = m_impl->config[
"recentFiles"];
245 for (
auto it = recentFiles.begin(); it != recentFiles.end(); ++it) {
246 if (*it == filepath) {
247 recentFiles.erase(it);
253 recentFiles.insert(recentFiles.begin(), filepath);
256 while (recentFiles.size() > m_impl->maxRecentFiles) {
257 recentFiles.erase(recentFiles.end() - 1);
262 const std::shared_lock lock(m_impl->mutex);
263 std::vector<std::string> result;
265 if (m_impl->config.contains(
"recentFiles")) {
266 for (
const auto& file : m_impl->config[
"recentFiles"]) {
267 result.push_back(file.get<std::string>());
270 }
catch (
const json::exception& e) {
271 LOG_WARNING(
"Failed to get recent files from config: {}", e.what());
277 const std::unique_lock lock(m_impl->mutex);
278 m_impl->config[
"recentFiles"] = json::array();
282 const std::unique_lock lock(m_impl->mutex);
283 m_impl->maxRecentFiles = max;
289 const std::unique_lock lock(m_impl->mutex);
290 if (!m_impl->config.contains(
"settings")) {
291 m_impl->config[
"settings"] = json::object();
293 m_impl->config[
"settings"][key] = value;
297 const std::shared_lock lock(m_impl->mutex);
299 if (m_impl->config.contains(
"settings") && m_impl->config[
"settings"].contains(key)) {
300 return m_impl->config[
"settings"][key].get<std::string>();
302 }
catch (
const json::exception& e) {
303 LOG_WARNING(
"Failed to get string '{}' from config: {}", key, e.what());
309 const std::unique_lock lock(m_impl->mutex);
310 if (!m_impl->config.contains(
"settings")) {
311 m_impl->config[
"settings"] = json::object();
313 m_impl->config[
"settings"][key] = value;
317 const std::shared_lock lock(m_impl->mutex);
319 if (m_impl->config.contains(
"settings") && m_impl->config[
"settings"].contains(key)) {
320 return m_impl->config[
"settings"][key].get<
int>();
322 }
catch (
const json::exception& e) {
323 LOG_WARNING(
"Failed to get int '{}' from config: {}", key, e.what());
329 const std::unique_lock lock(m_impl->mutex);
330 if (!m_impl->config.contains(
"settings")) {
331 m_impl->config[
"settings"] = json::object();
333 m_impl->config[
"settings"][key] = value;
337 const std::shared_lock lock(m_impl->mutex);
339 if (m_impl->config.contains(
"settings") && m_impl->config[
"settings"].contains(key)) {
340 return m_impl->config[
"settings"][key].get<
bool>();
342 }
catch (
const json::exception& e) {
343 LOG_WARNING(
"Failed to get bool '{}' from config: {}", key, e.what());
349 const std::unique_lock lock(m_impl->mutex);
350 if (!m_impl->config.contains(
"settings")) {
351 m_impl->config[
"settings"] = json::object();
353 m_impl->config[
"settings"][key] = value;
357 const std::shared_lock lock(m_impl->mutex);
359 if (m_impl->config.contains(
"settings") && m_impl->config[
"settings"].contains(key)) {
360 return m_impl->config[
"settings"][key].get<
float>();
362 }
catch (
const json::exception& e) {
363 LOG_WARNING(
"Failed to get float '{}' from config: {}", key, e.what());
369 const std::shared_lock lock(m_impl->mutex);
370 return m_impl->config.contains(
"settings") && m_impl->config[
"settings"].contains(key);
374 const std::unique_lock lock(m_impl->mutex);
375 if (m_impl->config.contains(
"settings")) {
376 m_impl->config[
"settings"].erase(key);
381 const std::shared_lock lock(m_impl->mutex);
382 std::vector<std::string> keys;
384 if (m_impl->config.contains(
"settings")) {
385 for (
auto it = m_impl->config[
"settings"].begin(); it != m_impl->config[
"settings"].end(); ++it) {
386 keys.push_back(it.key());
389 }
catch (
const json::exception& e) {
390 LOG_WARNING(
"Failed to get all keys from config: {}", e.what());
397std::filesystem::path ConfigManager::GetConfigDirectory() {
401 PWSTR path =
nullptr;
402 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &path))) {
403 std::filesystem::path result(path);
405 return result /
"MetaImGUI";
407 return std::filesystem::path(
"./config");
408#elif defined(__APPLE__)
411 const char* home = getenv(
"HOME");
413 return std::filesystem::path(home) /
"Library" /
"Application Support" /
"MetaImGUI";
415 return std::filesystem::path(
"./config");
419 const char* xdgConfig = getenv(
"XDG_CONFIG_HOME");
420 if (xdgConfig !=
nullptr) {
421 return std::filesystem::path(xdgConfig) /
"MetaImGUI";
425 const char* home = getenv(
"HOME");
426 if (home !=
nullptr) {
427 return std::filesystem::path(home) /
".config" /
"MetaImGUI";
434bool ConfigManager::EnsureConfigDirectoryExists() {
436 const std::filesystem::path dir = GetConfigDirectory();
437 if (!std::filesystem::exists(dir)) {
438 return std::filesystem::create_directories(dir);
441 }
catch (
const std::filesystem::filesystem_error& e) {
442 LOG_ERROR(
"Failed to create config directory: {}", e.what());