yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
user_settings.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <filesystem>
5#include <fstream>
6#include <sstream>
7
8#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
11#include "util/file_util.h"
12#include "util/log.h"
13#include "util/platform_paths.h"
14
15#ifdef YAZE_WITH_JSON
16#include "nlohmann/json.hpp"
17#endif
18
19namespace yaze {
20namespace editor {
21
22#ifdef YAZE_WITH_JSON
23using json = nlohmann::json;
24#endif
25
26namespace {
27
28absl::Status EnsureParentDirectory(const std::filesystem::path& path) {
29 auto parent = path.parent_path();
30 if (parent.empty()) {
31 return absl::OkStatus();
32 }
34}
35
36absl::Status LoadPreferencesFromIni(const std::filesystem::path& path,
38 if (!prefs) {
39 return absl::InvalidArgumentError("prefs is null");
40 }
41
42 auto data = util::LoadFile(path.string());
43 if (data.empty()) {
44 return absl::OkStatus();
45 }
46
47 std::istringstream ss(data);
48 std::string line;
49 while (std::getline(ss, line)) {
50 size_t eq_pos = line.find('=');
51 if (eq_pos == std::string::npos) {
52 continue;
53 }
54
55 std::string key = line.substr(0, eq_pos);
56 std::string val = line.substr(eq_pos + 1);
57
58 // General
59 if (key == "font_global_scale") {
60 prefs->font_global_scale = std::stof(val);
61 } else if (key == "backup_rom") {
62 prefs->backup_rom = (val == "1");
63 } else if (key == "save_new_auto") {
64 prefs->save_new_auto = (val == "1");
65 } else if (key == "autosave_enabled") {
66 prefs->autosave_enabled = (val == "1");
67 } else if (key == "autosave_interval") {
68 prefs->autosave_interval = std::stof(val);
69 } else if (key == "recent_files_limit") {
70 prefs->recent_files_limit = std::stoi(val);
71 } else if (key == "last_rom_path") {
72 prefs->last_rom_path = val;
73 } else if (key == "last_project_path") {
74 prefs->last_project_path = val;
75 } else if (key == "show_welcome_on_startup") {
76 prefs->show_welcome_on_startup = (val == "1");
77 } else if (key == "restore_last_session") {
78 prefs->restore_last_session = (val == "1");
79 } else if (key == "prefer_hmagic_sprite_names") {
80 prefs->prefer_hmagic_sprite_names = (val == "1");
81 } else if (key == "welcome_triforce_alpha") {
82 prefs->welcome_triforce_alpha = std::stof(val);
83 } else if (key == "welcome_triforce_speed") {
84 prefs->welcome_triforce_speed = std::stof(val);
85 } else if (key == "welcome_triforce_size") {
86 prefs->welcome_triforce_size = std::stof(val);
87 } else if (key == "welcome_particles_enabled") {
88 prefs->welcome_particles_enabled = (val == "1");
89 } else if (key == "welcome_mouse_repel_enabled") {
90 prefs->welcome_mouse_repel_enabled = (val == "1");
91 } else if (key == "reduced_motion") {
92 prefs->reduced_motion = (val == "1");
93 } else if (key == "switch_motion_profile") {
94 prefs->switch_motion_profile = std::stoi(val);
95 } else if (key == "last_theme_name") {
96 prefs->last_theme_name = val;
97 }
98 // Editor Behavior
99 else if (key == "backup_before_save") {
100 prefs->backup_before_save = (val == "1");
101 } else if (key == "default_editor") {
102 prefs->default_editor = std::stoi(val);
103 }
104 // Performance
105 else if (key == "vsync") {
106 prefs->vsync = (val == "1");
107 } else if (key == "target_fps") {
108 prefs->target_fps = std::stoi(val);
109 } else if (key == "cache_size_mb") {
110 prefs->cache_size_mb = std::stoi(val);
111 } else if (key == "undo_history_size") {
112 prefs->undo_history_size = std::stoi(val);
113 }
114 // AI Agent
115 else if (key == "ai_provider") {
116 prefs->ai_provider = std::stoi(val);
117 } else if (key == "ai_model") {
118 prefs->ai_model = val;
119 } else if (key == "ollama_url") {
120 prefs->ollama_url = val;
121 } else if (key == "gemini_api_key") {
122 prefs->gemini_api_key = val;
123 } else if (key == "openai_api_key") {
124 prefs->openai_api_key = val;
125 } else if (key == "anthropic_api_key") {
126 prefs->anthropic_api_key = val;
127 } else if (key == "ai_temperature") {
128 prefs->ai_temperature = std::stof(val);
129 } else if (key == "ai_max_tokens") {
130 prefs->ai_max_tokens = std::stoi(val);
131 } else if (key == "ai_proactive") {
132 prefs->ai_proactive = (val == "1");
133 } else if (key == "ai_auto_learn") {
134 prefs->ai_auto_learn = (val == "1");
135 } else if (key == "ai_multimodal") {
136 prefs->ai_multimodal = (val == "1");
137 }
138 // CLI Logging
139 else if (key == "log_level") {
140 prefs->log_level = std::stoi(val);
141 } else if (key == "log_to_file") {
142 prefs->log_to_file = (val == "1");
143 } else if (key == "log_file_path") {
144 prefs->log_file_path = val;
145 } else if (key == "log_ai_requests") {
146 prefs->log_ai_requests = (val == "1");
147 } else if (key == "log_rom_operations") {
148 prefs->log_rom_operations = (val == "1");
149 } else if (key == "log_gui_automation") {
150 prefs->log_gui_automation = (val == "1");
151 } else if (key == "log_proposals") {
152 prefs->log_proposals = (val == "1");
153 }
154 // Panel Shortcuts (format: panel_shortcut.panel_id=shortcut)
155 else if (key.substr(0, 15) == "panel_shortcut.") {
156 std::string panel_id = key.substr(15);
157 prefs->panel_shortcuts[panel_id] = val;
158 }
159 // Backward compatibility for card_shortcut
160 else if (key.substr(0, 14) == "card_shortcut.") {
161 std::string panel_id = key.substr(14);
162 prefs->panel_shortcuts[panel_id] = val;
163 }
164 // Sidebar State
165 else if (key == "sidebar_visible") {
166 prefs->sidebar_visible = (val == "1");
167 } else if (key == "sidebar_panel_expanded") {
168 prefs->sidebar_panel_expanded = (val == "1");
169 } else if (key == "sidebar_panel_width") {
170 prefs->sidebar_panel_width = std::stof(val);
171 } else if (key == "panel_browser_category_width") {
172 prefs->panel_browser_category_width = std::stof(val);
173 } else if (key == "panel_layout_defaults_revision") {
174 prefs->panel_layout_defaults_revision = std::stoi(val);
175 } else if (key == "sidebar_active_category") {
176 prefs->sidebar_active_category = val;
177 }
178 // Status Bar
179 else if (key == "show_status_bar") {
180 prefs->show_status_bar = (val == "1");
181 }
182 // Panel Visibility State (format: panel_visibility.EditorType.panel_id=1)
183 else if (key.substr(0, 17) == "panel_visibility.") {
184 std::string rest = key.substr(17);
185 size_t dot_pos = rest.find('.');
186 if (dot_pos != std::string::npos) {
187 std::string editor_type = rest.substr(0, dot_pos);
188 std::string panel_id = rest.substr(dot_pos + 1);
189 prefs->panel_visibility_state[editor_type][panel_id] = (val == "1");
190 }
191 }
192 // Pinned Panels (format: pinned_panel.panel_id=1)
193 else if (key.substr(0, 13) == "pinned_panel.") {
194 std::string panel_id = key.substr(13);
195 prefs->pinned_panels[panel_id] = (val == "1");
196 }
197 // Right panel widths (format: right_panel_width.panel_key=420.0)
198 else if (key.substr(0, 18) == "right_panel_width.") {
199 std::string panel_key = key.substr(18);
200 prefs->right_panel_widths[panel_key] = std::stof(val);
201 }
202 // Saved Layouts (format: saved_layout.LayoutName.panel_id=1)
203 else if (key.substr(0, 13) == "saved_layout.") {
204 std::string rest = key.substr(13);
205 size_t dot_pos = rest.find('.');
206 if (dot_pos != std::string::npos) {
207 std::string layout_name = rest.substr(0, dot_pos);
208 std::string panel_id = rest.substr(dot_pos + 1);
209 prefs->saved_layouts[layout_name][panel_id] = (val == "1");
210 }
211 }
212 }
213
214 return absl::OkStatus();
215}
216
217absl::Status SavePreferencesToIni(const std::filesystem::path& path,
218 const UserSettings::Preferences& prefs) {
219 auto ensure_status = EnsureParentDirectory(path);
220 if (!ensure_status.ok()) {
221 return ensure_status;
222 }
223
224 std::ostringstream ss;
225 // General
226 ss << "font_global_scale=" << prefs.font_global_scale << "\n";
227 ss << "backup_rom=" << (prefs.backup_rom ? 1 : 0) << "\n";
228 ss << "save_new_auto=" << (prefs.save_new_auto ? 1 : 0) << "\n";
229 ss << "autosave_enabled=" << (prefs.autosave_enabled ? 1 : 0) << "\n";
230 ss << "autosave_interval=" << prefs.autosave_interval << "\n";
231 ss << "recent_files_limit=" << prefs.recent_files_limit << "\n";
232 ss << "last_rom_path=" << prefs.last_rom_path << "\n";
233 ss << "last_project_path=" << prefs.last_project_path << "\n";
234 ss << "show_welcome_on_startup=" << (prefs.show_welcome_on_startup ? 1 : 0)
235 << "\n";
236 ss << "restore_last_session=" << (prefs.restore_last_session ? 1 : 0) << "\n";
237 ss << "prefer_hmagic_sprite_names="
238 << (prefs.prefer_hmagic_sprite_names ? 1 : 0) << "\n";
239 ss << "welcome_triforce_alpha=" << prefs.welcome_triforce_alpha << "\n";
240 ss << "welcome_triforce_speed=" << prefs.welcome_triforce_speed << "\n";
241 ss << "welcome_triforce_size=" << prefs.welcome_triforce_size << "\n";
242 ss << "welcome_particles_enabled="
243 << (prefs.welcome_particles_enabled ? 1 : 0) << "\n";
244 ss << "welcome_mouse_repel_enabled="
245 << (prefs.welcome_mouse_repel_enabled ? 1 : 0) << "\n";
246 ss << "reduced_motion=" << (prefs.reduced_motion ? 1 : 0) << "\n";
247 ss << "switch_motion_profile=" << prefs.switch_motion_profile << "\n";
248 ss << "last_theme_name=" << prefs.last_theme_name << "\n";
249
250 // Editor Behavior
251 ss << "backup_before_save=" << (prefs.backup_before_save ? 1 : 0) << "\n";
252 ss << "default_editor=" << prefs.default_editor << "\n";
253
254 // Performance
255 ss << "vsync=" << (prefs.vsync ? 1 : 0) << "\n";
256 ss << "target_fps=" << prefs.target_fps << "\n";
257 ss << "cache_size_mb=" << prefs.cache_size_mb << "\n";
258 ss << "undo_history_size=" << prefs.undo_history_size << "\n";
259
260 // AI Agent
261 ss << "ai_provider=" << prefs.ai_provider << "\n";
262 ss << "ai_model=" << prefs.ai_model << "\n";
263 ss << "ollama_url=" << prefs.ollama_url << "\n";
264 ss << "gemini_api_key=" << prefs.gemini_api_key << "\n";
265 ss << "openai_api_key=" << prefs.openai_api_key << "\n";
266 ss << "anthropic_api_key=" << prefs.anthropic_api_key << "\n";
267 ss << "ai_temperature=" << prefs.ai_temperature << "\n";
268 ss << "ai_max_tokens=" << prefs.ai_max_tokens << "\n";
269 ss << "ai_proactive=" << (prefs.ai_proactive ? 1 : 0) << "\n";
270 ss << "ai_auto_learn=" << (prefs.ai_auto_learn ? 1 : 0) << "\n";
271 ss << "ai_multimodal=" << (prefs.ai_multimodal ? 1 : 0) << "\n";
272
273 // CLI Logging
274 ss << "log_level=" << prefs.log_level << "\n";
275 ss << "log_to_file=" << (prefs.log_to_file ? 1 : 0) << "\n";
276 ss << "log_file_path=" << prefs.log_file_path << "\n";
277 ss << "log_ai_requests=" << (prefs.log_ai_requests ? 1 : 0) << "\n";
278 ss << "log_rom_operations=" << (prefs.log_rom_operations ? 1 : 0) << "\n";
279 ss << "log_gui_automation=" << (prefs.log_gui_automation ? 1 : 0) << "\n";
280 ss << "log_proposals=" << (prefs.log_proposals ? 1 : 0) << "\n";
281
282 // Panel Shortcuts
283 for (const auto& [panel_id, shortcut] : prefs.panel_shortcuts) {
284 ss << "panel_shortcut." << panel_id << "=" << shortcut << "\n";
285 }
286
287 // Sidebar State
288 ss << "sidebar_visible=" << (prefs.sidebar_visible ? 1 : 0) << "\n";
289 ss << "sidebar_panel_expanded=" << (prefs.sidebar_panel_expanded ? 1 : 0)
290 << "\n";
291 ss << "sidebar_panel_width=" << prefs.sidebar_panel_width << "\n";
292 ss << "panel_browser_category_width=" << prefs.panel_browser_category_width
293 << "\n";
294 ss << "panel_layout_defaults_revision="
295 << prefs.panel_layout_defaults_revision << "\n";
296 ss << "sidebar_active_category=" << prefs.sidebar_active_category << "\n";
297
298 // Status Bar
299 ss << "show_status_bar=" << (prefs.show_status_bar ? 1 : 0) << "\n";
300
301 // Panel Visibility State
302 for (const auto& [editor_type, panel_state] : prefs.panel_visibility_state) {
303 for (const auto& [panel_id, visible] : panel_state) {
304 ss << "panel_visibility." << editor_type << "." << panel_id << "="
305 << (visible ? 1 : 0) << "\n";
306 }
307 }
308
309 // Pinned Panels
310 for (const auto& [panel_id, pinned] : prefs.pinned_panels) {
311 ss << "pinned_panel." << panel_id << "=" << (pinned ? 1 : 0) << "\n";
312 }
313
314 for (const auto& [panel_key, width] : prefs.right_panel_widths) {
315 ss << "right_panel_width." << panel_key << "=" << width << "\n";
316 }
317
318 // Saved Layouts
319 for (const auto& [layout_name, panel_state] : prefs.saved_layouts) {
320 for (const auto& [panel_id, visible] : panel_state) {
321 ss << "saved_layout." << layout_name << "." << panel_id << "="
322 << (visible ? 1 : 0) << "\n";
323 }
324 }
325
326 std::ofstream file(path);
327 if (!file.is_open()) {
328 return absl::InternalError(
329 absl::StrFormat("Failed to open settings file: %s", path.string()));
330 }
331 file << ss.str();
332 return absl::OkStatus();
333}
334
335#ifdef YAZE_WITH_JSON
336void EnsureDefaultAiHosts(UserSettings::Preferences* prefs) {
337 if (!prefs) {
338 return;
339 }
340
341 if (!prefs->ai_hosts.empty()) {
342 if (prefs->active_ai_host_id.empty()) {
343 prefs->active_ai_host_id = prefs->ai_hosts.front().id;
344 }
345 return;
346 }
347
348 if (!prefs->ollama_url.empty()) {
349 UserSettings::Preferences::AiHost host;
350 host.id = "ollama-local";
351 host.label = "Ollama (local)";
352 host.base_url = prefs->ollama_url;
353 host.api_type = "ollama";
354 host.supports_tools = true;
355 host.supports_streaming = true;
356 prefs->ai_hosts.push_back(host);
357 }
358
359 // Provide a local OpenAI-compatible host for LM Studio by default.
360 UserSettings::Preferences::AiHost lmstudio;
361 lmstudio.id = "lmstudio-local";
362 lmstudio.label = "LM Studio (local)";
363 lmstudio.base_url = "http://localhost:1234";
364 lmstudio.api_type = "lmstudio";
365 lmstudio.supports_tools = true;
366 lmstudio.supports_streaming = true;
367 prefs->ai_hosts.push_back(lmstudio);
368
369 if (!prefs->ai_hosts.empty() && prefs->active_ai_host_id.empty()) {
370 prefs->active_ai_host_id = prefs->ai_hosts.front().id;
371 }
372}
373
374void EnsureDefaultAiProfiles(UserSettings::Preferences* prefs) {
375 if (!prefs) {
376 return;
377 }
378 if (!prefs->ai_profiles.empty()) {
379 if (prefs->active_ai_profile.empty()) {
380 prefs->active_ai_profile = prefs->ai_profiles.front().name;
381 }
382 return;
383 }
384 if (!prefs->ai_model.empty()) {
385 UserSettings::Preferences::AiModelProfile profile;
386 profile.name = "default";
387 profile.model = prefs->ai_model;
388 profile.temperature = prefs->ai_temperature;
389 profile.top_p = 0.95f;
390 profile.max_output_tokens = prefs->ai_max_tokens;
391 profile.supports_tools = true;
392 prefs->ai_profiles.push_back(profile);
393 prefs->active_ai_profile = profile.name;
394 }
395}
396
397void EnsureDefaultFilesystemRoots(UserSettings::Preferences* prefs) {
398 if (!prefs) {
399 return;
400 }
401
402 auto add_unique_root = [&](const std::filesystem::path& path) {
403 if (path.empty()) {
404 return;
405 }
406 const std::string path_str = path.string();
407 auto it = std::find(prefs->project_root_paths.begin(),
408 prefs->project_root_paths.end(), path_str);
409 if (it == prefs->project_root_paths.end()) {
410 prefs->project_root_paths.push_back(path_str);
411 }
412 };
413
415 if (docs_dir.ok()) {
416 add_unique_root(*docs_dir);
417 }
418
419 if (prefs->use_icloud_sync) {
420 auto icloud_dir =
422 if (icloud_dir.ok()) {
423 add_unique_root(*icloud_dir);
424 if (prefs->default_project_root.empty()) {
425 prefs->default_project_root = icloud_dir->string();
426 }
427 }
428 }
429
430 if (prefs->default_project_root.empty() &&
431 !prefs->project_root_paths.empty()) {
432 prefs->default_project_root = prefs->project_root_paths.front();
433 }
434}
435
436void EnsureDefaultModelPaths(UserSettings::Preferences* prefs) {
437 if (!prefs) {
438 return;
439 }
440 if (!prefs->ai_model_paths.empty()) {
441 return;
442 }
443
444 auto add_unique_path = [&](const std::filesystem::path& path) {
445 if (path.empty()) {
446 return;
447 }
448 const std::string path_str = path.string();
449 auto it = std::find(prefs->ai_model_paths.begin(),
450 prefs->ai_model_paths.end(), path_str);
451 if (it == prefs->ai_model_paths.end()) {
452 prefs->ai_model_paths.push_back(path_str);
453 }
454 };
455
456 const auto home_dir = util::PlatformPaths::GetHomeDirectory();
457 if (!home_dir.empty() && home_dir != ".") {
458 add_unique_path(home_dir / "models");
459 add_unique_path(home_dir / ".lmstudio" / "models");
460 add_unique_path(home_dir / ".ollama" / "models");
461 }
462}
463
464void LoadStringMap(const json& src,
465 std::unordered_map<std::string, std::string>* target) {
466 if (!target || !src.is_object()) {
467 return;
468 }
469 target->clear();
470 for (const auto& [key, value] : src.items()) {
471 if (value.is_string()) {
472 (*target)[key] = value.get<std::string>();
473 }
474 }
475}
476
477void LoadBoolMap(const json& src,
478 std::unordered_map<std::string, bool>* target) {
479 if (!target || !src.is_object()) {
480 return;
481 }
482 target->clear();
483 for (const auto& [key, value] : src.items()) {
484 if (value.is_boolean()) {
485 (*target)[key] = value.get<bool>();
486 }
487 }
488}
489
490void LoadFloatMap(const json& src,
491 std::unordered_map<std::string, float>* target) {
492 if (!target || !src.is_object()) {
493 return;
494 }
495 target->clear();
496 for (const auto& [key, value] : src.items()) {
497 if (value.is_number()) {
498 (*target)[key] = value.get<float>();
499 }
500 }
501}
502
503void LoadNestedBoolMap(
504 const json& src,
505 std::unordered_map<std::string, std::unordered_map<std::string, bool>>*
506 target) {
507 if (!target || !src.is_object()) {
508 return;
509 }
510 target->clear();
511 for (const auto& [outer_key, outer_val] : src.items()) {
512 if (!outer_val.is_object()) {
513 continue;
514 }
515 auto& inner = (*target)[outer_key];
516 inner.clear();
517 for (const auto& [inner_key, inner_val] : outer_val.items()) {
518 if (inner_val.is_boolean()) {
519 inner[inner_key] = inner_val.get<bool>();
520 }
521 }
522 }
523}
524
525json ToStringMap(const std::unordered_map<std::string, std::string>& map) {
526 json obj = json::object();
527 for (const auto& [key, value] : map) {
528 obj[key] = value;
529 }
530 return obj;
531}
532
533json ToBoolMap(const std::unordered_map<std::string, bool>& map) {
534 json obj = json::object();
535 for (const auto& [key, value] : map) {
536 obj[key] = value;
537 }
538 return obj;
539}
540
541json ToFloatMap(const std::unordered_map<std::string, float>& map) {
542 json obj = json::object();
543 for (const auto& [key, value] : map) {
544 obj[key] = value;
545 }
546 return obj;
547}
548
549json ToNestedBoolMap(const std::unordered_map<
550 std::string, std::unordered_map<std::string, bool>>& map) {
551 json obj = json::object();
552 for (const auto& [outer_key, inner] : map) {
553 obj[outer_key] = ToBoolMap(inner);
554 }
555 return obj;
556}
557
558absl::Status LoadPreferencesFromJson(const std::filesystem::path& path,
559 UserSettings::Preferences* prefs) {
560 if (!prefs) {
561 return absl::InvalidArgumentError("prefs is null");
562 }
563
564 std::ifstream file(path);
565 if (!file.is_open()) {
566 return absl::NotFoundError(
567 absl::StrFormat("Settings file not found: %s", path.string()));
568 }
569
570 json root;
571 try {
572 file >> root;
573 } catch (const std::exception& e) {
574 return absl::InternalError(
575 absl::StrFormat("Failed to parse settings.json: %s", e.what()));
576 }
577
578 if (root.contains("general")) {
579 const auto& g = root["general"];
580 prefs->font_global_scale =
581 g.value("font_global_scale", prefs->font_global_scale);
582 prefs->backup_rom = g.value("backup_rom", prefs->backup_rom);
583 prefs->save_new_auto = g.value("save_new_auto", prefs->save_new_auto);
584 prefs->autosave_enabled =
585 g.value("autosave_enabled", prefs->autosave_enabled);
586 prefs->autosave_interval =
587 g.value("autosave_interval", prefs->autosave_interval);
588 prefs->recent_files_limit =
589 g.value("recent_files_limit", prefs->recent_files_limit);
590 prefs->last_rom_path = g.value("last_rom_path", prefs->last_rom_path);
591 prefs->last_project_path =
592 g.value("last_project_path", prefs->last_project_path);
593 prefs->show_welcome_on_startup =
594 g.value("show_welcome_on_startup", prefs->show_welcome_on_startup);
595 prefs->restore_last_session =
596 g.value("restore_last_session", prefs->restore_last_session);
597 prefs->prefer_hmagic_sprite_names = g.value(
598 "prefer_hmagic_sprite_names", prefs->prefer_hmagic_sprite_names);
599 prefs->welcome_triforce_alpha =
600 g.value("welcome_triforce_alpha", prefs->welcome_triforce_alpha);
601 prefs->welcome_triforce_speed =
602 g.value("welcome_triforce_speed", prefs->welcome_triforce_speed);
603 prefs->welcome_triforce_size =
604 g.value("welcome_triforce_size", prefs->welcome_triforce_size);
605 prefs->welcome_particles_enabled =
606 g.value("welcome_particles_enabled", prefs->welcome_particles_enabled);
607 prefs->welcome_mouse_repel_enabled = g.value(
608 "welcome_mouse_repel_enabled", prefs->welcome_mouse_repel_enabled);
609 }
610
611 if (root.contains("appearance")) {
612 const auto& appearance = root["appearance"];
613 prefs->reduced_motion =
614 appearance.value("reduced_motion", prefs->reduced_motion);
615 prefs->switch_motion_profile =
616 appearance.value("switch_motion_profile", prefs->switch_motion_profile);
617 prefs->last_theme_name =
618 appearance.value("last_theme_name", prefs->last_theme_name);
619 }
620
621 if (root.contains("editor")) {
622 const auto& e = root["editor"];
623 prefs->backup_before_save =
624 e.value("backup_before_save", prefs->backup_before_save);
625 prefs->default_editor = e.value("default_editor", prefs->default_editor);
626 }
627
628 if (root.contains("performance")) {
629 const auto& p = root["performance"];
630 prefs->vsync = p.value("vsync", prefs->vsync);
631 prefs->target_fps = p.value("target_fps", prefs->target_fps);
632 prefs->cache_size_mb = p.value("cache_size_mb", prefs->cache_size_mb);
633 prefs->undo_history_size =
634 p.value("undo_history_size", prefs->undo_history_size);
635 }
636
637 if (root.contains("ai")) {
638 const auto& ai = root["ai"];
639 prefs->ai_provider = ai.value("provider", prefs->ai_provider);
640 prefs->ai_model = ai.value("model", prefs->ai_model);
641 prefs->ollama_url = ai.value("ollama_url", prefs->ollama_url);
642 prefs->gemini_api_key = ai.value("gemini_api_key", prefs->gemini_api_key);
643 prefs->openai_api_key = ai.value("openai_api_key", prefs->openai_api_key);
644 prefs->anthropic_api_key =
645 ai.value("anthropic_api_key", prefs->anthropic_api_key);
646 std::string google_key = ai.value("google_api_key", std::string());
647 if (prefs->gemini_api_key.empty() && !google_key.empty()) {
648 prefs->gemini_api_key = google_key;
649 }
650 prefs->ai_temperature = ai.value("temperature", prefs->ai_temperature);
651 prefs->ai_max_tokens = ai.value("max_tokens", prefs->ai_max_tokens);
652 prefs->ai_proactive = ai.value("proactive", prefs->ai_proactive);
653 prefs->ai_auto_learn = ai.value("auto_learn", prefs->ai_auto_learn);
654 prefs->ai_multimodal = ai.value("multimodal", prefs->ai_multimodal);
655 prefs->active_ai_host_id =
656 ai.value("active_host_id", prefs->active_ai_host_id);
657 prefs->active_ai_profile =
658 ai.value("active_profile", prefs->active_ai_profile);
659 prefs->remote_build_host_id =
660 ai.value("remote_build_host_id", prefs->remote_build_host_id);
661 if (ai.contains("model_paths") && ai["model_paths"].is_array()) {
662 prefs->ai_model_paths.clear();
663 for (const auto& item : ai["model_paths"]) {
664 if (item.is_string()) {
665 prefs->ai_model_paths.push_back(item.get<std::string>());
666 }
667 }
668 }
669
670 if (ai.contains("hosts") && ai["hosts"].is_array()) {
671 prefs->ai_hosts.clear();
672 for (const auto& host : ai["hosts"]) {
673 if (!host.is_object()) {
674 continue;
675 }
676 UserSettings::Preferences::AiHost entry;
677 entry.id = host.value("id", "");
678 entry.label = host.value("label", "");
679 entry.base_url = host.value("base_url", "");
680 entry.api_type = host.value("api_type", "");
681 entry.supports_vision =
682 host.value("supports_vision", entry.supports_vision);
683 entry.supports_tools =
684 host.value("supports_tools", entry.supports_tools);
685 entry.supports_streaming =
686 host.value("supports_streaming", entry.supports_streaming);
687 entry.allow_insecure =
688 host.value("allow_insecure", entry.allow_insecure);
689 entry.api_key = host.value("api_key", "");
690 entry.credential_id = host.value("credential_id", "");
691 prefs->ai_hosts.push_back(entry);
692 }
693 }
694
695 if (ai.contains("profiles") && ai["profiles"].is_array()) {
696 prefs->ai_profiles.clear();
697 for (const auto& profile : ai["profiles"]) {
698 if (!profile.is_object()) {
699 continue;
700 }
701 UserSettings::Preferences::AiModelProfile entry;
702 entry.name = profile.value("name", "");
703 entry.model = profile.value("model", "");
704 entry.temperature = profile.value("temperature", entry.temperature);
705 entry.top_p = profile.value("top_p", entry.top_p);
706 entry.max_output_tokens =
707 profile.value("max_output_tokens", entry.max_output_tokens);
708 entry.supports_vision =
709 profile.value("supports_vision", entry.supports_vision);
710 entry.supports_tools =
711 profile.value("supports_tools", entry.supports_tools);
712 prefs->ai_profiles.push_back(entry);
713 }
714 }
715 }
716
717 if (root.contains("logging")) {
718 const auto& log = root["logging"];
719 prefs->log_level = log.value("level", prefs->log_level);
720 prefs->log_to_file = log.value("to_file", prefs->log_to_file);
721 prefs->log_file_path = log.value("file_path", prefs->log_file_path);
722 prefs->log_ai_requests = log.value("ai_requests", prefs->log_ai_requests);
723 prefs->log_rom_operations =
724 log.value("rom_operations", prefs->log_rom_operations);
725 prefs->log_gui_automation =
726 log.value("gui_automation", prefs->log_gui_automation);
727 prefs->log_proposals = log.value("proposals", prefs->log_proposals);
728 }
729
730 if (root.contains("shortcuts")) {
731 const auto& shortcuts = root["shortcuts"];
732 if (shortcuts.contains("panel")) {
733 LoadStringMap(shortcuts["panel"], &prefs->panel_shortcuts);
734 }
735 if (shortcuts.contains("global")) {
736 LoadStringMap(shortcuts["global"], &prefs->global_shortcuts);
737 }
738 if (shortcuts.contains("editor")) {
739 LoadStringMap(shortcuts["editor"], &prefs->editor_shortcuts);
740 }
741 }
742
743 if (root.contains("sidebar")) {
744 const auto& sidebar = root["sidebar"];
745 prefs->sidebar_visible = sidebar.value("visible", prefs->sidebar_visible);
746 prefs->sidebar_panel_expanded =
747 sidebar.value("panel_expanded", prefs->sidebar_panel_expanded);
748 prefs->sidebar_panel_width =
749 sidebar.value("panel_width", prefs->sidebar_panel_width);
750 prefs->panel_browser_category_width = sidebar.value(
751 "panel_browser_category_width", prefs->panel_browser_category_width);
752 prefs->sidebar_active_category =
753 sidebar.value("active_category", prefs->sidebar_active_category);
754
755 if (sidebar.contains("order") && sidebar["order"].is_array()) {
756 prefs->sidebar_order.clear();
757 for (const auto& item : sidebar["order"]) {
758 if (item.is_string()) {
759 prefs->sidebar_order.push_back(item.get<std::string>());
760 }
761 }
762 }
763 if (sidebar.contains("hidden") && sidebar["hidden"].is_array()) {
764 prefs->sidebar_hidden.clear();
765 for (const auto& item : sidebar["hidden"]) {
766 if (item.is_string()) {
767 prefs->sidebar_hidden.insert(item.get<std::string>());
768 }
769 }
770 }
771 if (sidebar.contains("pinned") && sidebar["pinned"].is_array()) {
772 prefs->sidebar_pinned.clear();
773 for (const auto& item : sidebar["pinned"]) {
774 if (item.is_string()) {
775 prefs->sidebar_pinned.insert(item.get<std::string>());
776 }
777 }
778 }
779 }
780
781 if (root.contains("status_bar")) {
782 const auto& status_bar = root["status_bar"];
783 prefs->show_status_bar =
784 status_bar.value("visible", prefs->show_status_bar);
785 }
786
787 if (root.contains("layouts")) {
788 const auto& layouts = root["layouts"];
789 prefs->panel_layout_defaults_revision = layouts.value(
790 "defaults_revision", prefs->panel_layout_defaults_revision);
791 if (layouts.contains("panel_visibility")) {
792 LoadNestedBoolMap(layouts["panel_visibility"],
793 &prefs->panel_visibility_state);
794 }
795 if (layouts.contains("pinned_panels")) {
796 LoadBoolMap(layouts["pinned_panels"], &prefs->pinned_panels);
797 }
798 if (layouts.contains("right_panel_widths")) {
799 LoadFloatMap(layouts["right_panel_widths"], &prefs->right_panel_widths);
800 }
801 if (layouts.contains("saved_layouts")) {
802 LoadNestedBoolMap(layouts["saved_layouts"], &prefs->saved_layouts);
803 }
804 }
805
806 if (root.contains("filesystem")) {
807 const auto& fs = root["filesystem"];
808 if (fs.contains("project_root_paths") &&
809 fs["project_root_paths"].is_array()) {
810 prefs->project_root_paths.clear();
811 for (const auto& item : fs["project_root_paths"]) {
812 if (item.is_string()) {
813 prefs->project_root_paths.push_back(item.get<std::string>());
814 }
815 }
816 }
817 prefs->default_project_root =
818 fs.value("default_project_root", prefs->default_project_root);
819 prefs->use_files_app = fs.value("use_files_app", prefs->use_files_app);
820 prefs->use_icloud_sync =
821 fs.value("use_icloud_sync", prefs->use_icloud_sync);
822 }
823
824 EnsureDefaultAiHosts(prefs);
825 EnsureDefaultAiProfiles(prefs);
826 EnsureDefaultFilesystemRoots(prefs);
827
828 return absl::OkStatus();
829}
830
831absl::Status SavePreferencesToJson(const std::filesystem::path& path,
832 const UserSettings::Preferences& prefs) {
833 auto ensure_status = EnsureParentDirectory(path);
834 if (!ensure_status.ok()) {
835 return ensure_status;
836 }
837
838 json root;
839 root["version"] = 1;
840 root["general"] = {
841 {"font_global_scale", prefs.font_global_scale},
842 {"backup_rom", prefs.backup_rom},
843 {"save_new_auto", prefs.save_new_auto},
844 {"autosave_enabled", prefs.autosave_enabled},
845 {"autosave_interval", prefs.autosave_interval},
846 {"recent_files_limit", prefs.recent_files_limit},
847 {"last_rom_path", prefs.last_rom_path},
848 {"last_project_path", prefs.last_project_path},
849 {"show_welcome_on_startup", prefs.show_welcome_on_startup},
850 {"restore_last_session", prefs.restore_last_session},
851 {"prefer_hmagic_sprite_names", prefs.prefer_hmagic_sprite_names},
852 {"welcome_triforce_alpha", prefs.welcome_triforce_alpha},
853 {"welcome_triforce_speed", prefs.welcome_triforce_speed},
854 {"welcome_triforce_size", prefs.welcome_triforce_size},
855 {"welcome_particles_enabled", prefs.welcome_particles_enabled},
856 {"welcome_mouse_repel_enabled", prefs.welcome_mouse_repel_enabled},
857 };
858
859 root["appearance"] = {
860 {"reduced_motion", prefs.reduced_motion},
861 {"switch_motion_profile", prefs.switch_motion_profile},
862 {"last_theme_name", prefs.last_theme_name},
863 };
864
865 root["editor"] = {
866 {"backup_before_save", prefs.backup_before_save},
867 {"default_editor", prefs.default_editor},
868 };
869
870 root["performance"] = {
871 {"vsync", prefs.vsync},
872 {"target_fps", prefs.target_fps},
873 {"cache_size_mb", prefs.cache_size_mb},
874 {"undo_history_size", prefs.undo_history_size},
875 };
876
877 json ai_hosts = json::array();
878 for (const auto& host : prefs.ai_hosts) {
879 ai_hosts.push_back({
880 {"id", host.id},
881 {"label", host.label},
882 {"base_url", host.base_url},
883 {"api_type", host.api_type},
884 {"supports_vision", host.supports_vision},
885 {"supports_tools", host.supports_tools},
886 {"supports_streaming", host.supports_streaming},
887 {"allow_insecure", host.allow_insecure},
888 {"api_key", host.api_key},
889 {"credential_id", host.credential_id},
890 });
891 }
892
893 json ai_profiles = json::array();
894 for (const auto& profile : prefs.ai_profiles) {
895 ai_profiles.push_back({
896 {"name", profile.name},
897 {"model", profile.model},
898 {"temperature", profile.temperature},
899 {"top_p", profile.top_p},
900 {"max_output_tokens", profile.max_output_tokens},
901 {"supports_vision", profile.supports_vision},
902 {"supports_tools", profile.supports_tools},
903 });
904 }
905
906 root["ai"] = {
907 {"provider", prefs.ai_provider},
908 {"model", prefs.ai_model},
909 {"ollama_url", prefs.ollama_url},
910 {"gemini_api_key", prefs.gemini_api_key},
911 {"google_api_key", prefs.gemini_api_key},
912 {"openai_api_key", prefs.openai_api_key},
913 {"anthropic_api_key", prefs.anthropic_api_key},
914 {"temperature", prefs.ai_temperature},
915 {"max_tokens", prefs.ai_max_tokens},
916 {"proactive", prefs.ai_proactive},
917 {"auto_learn", prefs.ai_auto_learn},
918 {"multimodal", prefs.ai_multimodal},
919 {"hosts", ai_hosts},
920 {"active_host_id", prefs.active_ai_host_id},
921 {"profiles", ai_profiles},
922 {"active_profile", prefs.active_ai_profile},
923 {"remote_build_host_id", prefs.remote_build_host_id},
924 {"model_paths", prefs.ai_model_paths},
925 };
926
927 root["logging"] = {
928 {"level", prefs.log_level},
929 {"to_file", prefs.log_to_file},
930 {"file_path", prefs.log_file_path},
931 {"ai_requests", prefs.log_ai_requests},
932 {"rom_operations", prefs.log_rom_operations},
933 {"gui_automation", prefs.log_gui_automation},
934 {"proposals", prefs.log_proposals},
935 };
936
937 root["shortcuts"] = {
938 {"panel", ToStringMap(prefs.panel_shortcuts)},
939 {"global", ToStringMap(prefs.global_shortcuts)},
940 {"editor", ToStringMap(prefs.editor_shortcuts)},
941 };
942
943 auto set_to_sorted_vec =
944 [](const std::unordered_set<std::string>& s) -> std::vector<std::string> {
945 std::vector<std::string> v(s.begin(), s.end());
946 std::sort(v.begin(), v.end());
947 return v;
948 };
949
950 root["sidebar"] = {
951 {"visible", prefs.sidebar_visible},
952 {"panel_expanded", prefs.sidebar_panel_expanded},
953 {"panel_width", prefs.sidebar_panel_width},
954 {"panel_browser_category_width", prefs.panel_browser_category_width},
955 {"active_category", prefs.sidebar_active_category},
956 {"order", prefs.sidebar_order},
957 {"hidden", set_to_sorted_vec(prefs.sidebar_hidden)},
958 {"pinned", set_to_sorted_vec(prefs.sidebar_pinned)},
959 };
960
961 root["status_bar"] = {
962 {"visible", prefs.show_status_bar},
963 };
964
965 root["layouts"] = {
966 {"defaults_revision", prefs.panel_layout_defaults_revision},
967 {"panel_visibility", ToNestedBoolMap(prefs.panel_visibility_state)},
968 {"pinned_panels", ToBoolMap(prefs.pinned_panels)},
969 {"right_panel_widths", ToFloatMap(prefs.right_panel_widths)},
970 {"saved_layouts", ToNestedBoolMap(prefs.saved_layouts)},
971 };
972
973 root["filesystem"] = {
974 {"project_root_paths", prefs.project_root_paths},
975 {"default_project_root", prefs.default_project_root},
976 {"use_files_app", prefs.use_files_app},
977 {"use_icloud_sync", prefs.use_icloud_sync},
978 };
979
980 std::ofstream file(path);
981 if (!file.is_open()) {
982 return absl::InternalError(
983 absl::StrFormat("Failed to open settings file: %s", path.string()));
984 }
985
986 file << root.dump(2) << "\n";
987 return absl::OkStatus();
988}
989#endif // YAZE_WITH_JSON
990
991} // namespace
992
994 auto docs_dir_status = util::PlatformPaths::GetUserDocumentsDirectory();
995 auto config_dir_status = util::PlatformPaths::GetConfigDirectory();
996 if (docs_dir_status.ok()) {
997 settings_file_path_ = (*docs_dir_status / "settings.json").string();
998 } else if (config_dir_status.ok()) {
999 settings_file_path_ = (*config_dir_status / "settings.json").string();
1000 } else {
1001 LOG_WARN("UserSettings",
1002 "Could not determine user documents or config directory. Using "
1003 "local settings.json.");
1004 settings_file_path_ = "settings.json";
1005 }
1006
1007 if (config_dir_status.ok()) {
1009 (*config_dir_status / "yaze_settings.ini").string();
1010 } else {
1011 legacy_settings_file_path_ = "yaze_settings.ini";
1012 }
1013}
1014
1015absl::Status UserSettings::Load() {
1016 try {
1017 bool loaded = false;
1018#ifdef YAZE_WITH_JSON
1020 if (json_exists) {
1021 auto status = LoadPreferencesFromJson(settings_file_path_, &prefs_);
1022 if (status.ok()) {
1023 loaded = true;
1024 } else {
1025 LOG_WARN("UserSettings", "Failed to load settings.json: %s",
1026 status.ToString().c_str());
1027 }
1028 }
1029#endif
1030
1032 auto status = LoadPreferencesFromIni(legacy_settings_file_path_, &prefs_);
1033 if (!status.ok()) {
1034 return status;
1035 }
1036 loaded = true;
1037#ifdef YAZE_WITH_JSON
1039 (void)SavePreferencesToJson(settings_file_path_, prefs_);
1040 }
1041#endif
1042 }
1043
1044 if (!loaded) {
1045#if defined(__APPLE__) && \
1046 (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
1047 prefs_.sidebar_visible = false;
1049#endif
1050 LOG_INFO("UserSettings", "Settings not found, creating defaults at: %s",
1051 settings_file_path_.c_str());
1052 return Save();
1053 }
1054
1055#ifdef YAZE_WITH_JSON
1056 EnsureDefaultAiHosts(&prefs_);
1057 EnsureDefaultAiProfiles(&prefs_);
1058 EnsureDefaultFilesystemRoots(&prefs_);
1059 EnsureDefaultModelPaths(&prefs_);
1060#endif
1061
1063 std::clamp(prefs_.switch_motion_profile, 0, 2);
1064
1065 if (ImGui::GetCurrentContext() != nullptr) {
1066 ImGui::GetIO().FontGlobalScale = prefs_.font_global_scale;
1067 } else {
1068 LOG_WARN("UserSettings",
1069 "ImGui context not available; skipping FontGlobalScale update");
1070 }
1071 } catch (const std::exception& e) {
1072 return absl::InternalError(
1073 absl::StrFormat("Failed to load user settings: %s", e.what()));
1074 }
1075 return absl::OkStatus();
1076}
1077
1079 if (target_revision <= 0) {
1080 return false;
1081 }
1082
1083 bool applied = false;
1084
1085 if (prefs_.panel_layout_defaults_revision < 4 && target_revision >= 4) {
1086 prefs_.sidebar_visible = true;
1091
1093 prefs_.pinned_panels.clear();
1094 prefs_.right_panel_widths.clear();
1095 prefs_.saved_layouts.clear();
1096
1098 applied = true;
1099 }
1100
1101 if (prefs_.panel_layout_defaults_revision < 5 && target_revision >= 5) {
1102 auto overworld_it = prefs_.panel_visibility_state.find("Overworld");
1103 if (overworld_it != prefs_.panel_visibility_state.end()) {
1104 overworld_it->second["overworld.tile16_editor"] = false;
1105 }
1107 applied = true;
1108 }
1109
1110 if (prefs_.panel_layout_defaults_revision < 6 && target_revision >= 6) {
1111 auto overworld_it = prefs_.panel_visibility_state.find("Overworld");
1112 if (overworld_it != prefs_.panel_visibility_state.end()) {
1113 auto& overworld_windows = overworld_it->second;
1114 overworld_windows["overworld.canvas"] = true;
1115 overworld_windows["overworld.tile16_selector"] = true;
1116 overworld_windows["overworld.properties"] = true;
1117 overworld_windows["overworld.tile16_editor"] = false;
1118 overworld_windows["overworld.tile8_selector"] = false;
1119 overworld_windows["overworld.area_graphics"] = false;
1120 overworld_windows["overworld.item_list"] = false;
1121 }
1123 applied = true;
1124 }
1125
1126 // Revision 7: WindowLifecycle::Persistent collapsed into CrossEditor.
1127 // Force-pin the two former-Persistent panels on upgrade so always-visible
1128 // behavior carries through. We must overwrite an existing pinned=false
1129 // entry here: under the old Persistent regime that value was a silent no-op
1130 // (the draw loop ignored pin state for Persistent panels), so treating it
1131 // as a "user choice" post-collapse would be a regression, not preservation.
1132 // After the migration runs once, subsequent unpin actions ARE load-bearing
1133 // and persist normally.
1134 if (prefs_.panel_layout_defaults_revision < 7 && target_revision >= 7) {
1135 prefs_.pinned_panels["agent.oracle_ram"] = true;
1136 prefs_.pinned_panels["workflow.output"] = true;
1138 applied = true;
1139 }
1140
1141 if (prefs_.panel_layout_defaults_revision < 8 && target_revision >= 8) {
1142 auto dungeon_it = prefs_.panel_visibility_state.find("Dungeon");
1143 if (dungeon_it != prefs_.panel_visibility_state.end()) {
1144 auto& dungeon_windows = dungeon_it->second;
1145 dungeon_windows["dungeon.workbench"] = true;
1146 dungeon_windows["dungeon.room_selector"] = false;
1147 dungeon_windows["dungeon.room_matrix"] = true;
1148 dungeon_windows["dungeon.object_editor"] = true;
1149 dungeon_windows["dungeon.room_graphics"] = true;
1150 dungeon_windows["dungeon.palette_editor"] = true;
1151 }
1153 applied = true;
1154 }
1155
1156 if (prefs_.panel_layout_defaults_revision < 9 && target_revision >= 9) {
1157 auto dungeon_it = prefs_.panel_visibility_state.find("Dungeon");
1158 if (dungeon_it != prefs_.panel_visibility_state.end()) {
1159 auto& dungeon_windows = dungeon_it->second;
1160 dungeon_windows["dungeon.door_editor"] = true;
1161 }
1163 applied = true;
1164 }
1165
1166 if (prefs_.panel_layout_defaults_revision < 10 && target_revision >= 10) {
1167 auto graphics_it = prefs_.panel_visibility_state.find("Graphics");
1168 if (graphics_it != prefs_.panel_visibility_state.end()) {
1169 auto& graphics_windows = graphics_it->second;
1170 graphics_windows["graphics.prototype_viewer"] = true;
1171 }
1173 applied = true;
1174 }
1175
1176 if (prefs_.panel_layout_defaults_revision < 11 && target_revision >= 11) {
1177 auto dungeon_it = prefs_.panel_visibility_state.find("Dungeon");
1178 if (dungeon_it != prefs_.panel_visibility_state.end()) {
1179 auto& dungeon_windows = dungeon_it->second;
1180 const bool legacy_object_surface =
1181 dungeon_windows.contains("dungeon.object_editor")
1182 ? dungeon_windows["dungeon.object_editor"]
1183 : true;
1184 dungeon_windows["dungeon.object_selector"] = legacy_object_surface;
1185 }
1187 applied = true;
1188 }
1189
1190 if (prefs_.panel_layout_defaults_revision < 12 && target_revision >= 12) {
1191 auto graphics_it = prefs_.panel_visibility_state.find("Graphics");
1192 if (graphics_it != prefs_.panel_visibility_state.end()) {
1193 auto& graphics_windows = graphics_it->second;
1194 graphics_windows["graphics.polyhedral"] = false;
1195 }
1197 applied = true;
1198 }
1199
1200 return applied;
1201}
1202
1203absl::Status UserSettings::Save() {
1204 try {
1205 absl::Status status = absl::OkStatus();
1206#ifdef YAZE_WITH_JSON
1207 status = SavePreferencesToJson(settings_file_path_, prefs_);
1208 if (!status.ok()) {
1209 return status;
1210 }
1211#endif
1212 status = SavePreferencesToIni(legacy_settings_file_path_, prefs_);
1213 if (!status.ok()) {
1214 return status;
1215 }
1216 } catch (const std::exception& e) {
1217 return absl::InternalError(
1218 absl::StrFormat("Failed to save user settings: %s", e.what()));
1219 }
1220 return absl::OkStatus();
1221}
1222
1223} // namespace editor
1224} // namespace yaze
bool ApplyPanelLayoutDefaultsRevision(int target_revision)
std::string legacy_settings_file_path_
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
static absl::StatusOr< std::filesystem::path > GetUserDocumentsSubdirectory(const std::string &subdir)
Get a subdirectory within the user documents folder.
static absl::StatusOr< std::filesystem::path > GetUserDocumentsDirectory()
Get the user's Documents directory.
static absl::Status EnsureDirectoryExists(const std::filesystem::path &path)
Ensure a directory exists, creating it if necessary.
static bool Exists(const std::filesystem::path &path)
Check if a file or directory exists.
static std::filesystem::path GetHomeDirectory()
Get the user's home directory in a cross-platform way.
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
absl::Status SavePreferencesToIni(const std::filesystem::path &path, const UserSettings::Preferences &prefs)
absl::Status LoadPreferencesFromIni(const std::filesystem::path &path, UserSettings::Preferences *prefs)
absl::Status EnsureParentDirectory(const std::filesystem::path &path)
std::string LoadFile(const std::string &filename)
Loads the entire contents of a file into a string.
Definition file_util.cc:23
std::unordered_map< std::string, std::string > panel_shortcuts
std::unordered_map< std::string, std::unordered_map< std::string, bool > > saved_layouts
std::unordered_map< std::string, float > right_panel_widths
std::unordered_map< std::string, std::unordered_map< std::string, bool > > panel_visibility_state
std::unordered_map< std::string, bool > pinned_panels