yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
settings_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdlib>
5#include <cstring>
6#include <filesystem>
7#include <set>
8#include <sstream>
9#include <unordered_set>
10#include <vector>
11
12#include "absl/strings/ascii.h"
13#include "absl/strings/match.h"
14#include "absl/strings/str_format.h"
19#include "app/gui/core/icons.h"
20#include "app/gui/core/style.h"
25#include "imgui/imgui.h"
26#include "imgui/misc/cpp/imgui_stdlib.h"
27#include "rom/rom.h"
28#include "util/file_util.h"
29#include "util/log.h"
30#include "util/platform_paths.h"
31#include "util/rom_hash.h"
33
34namespace yaze {
35namespace editor {
36
46
47namespace {
48
50 std::string text;
51 std::string error;
52};
53
54bool ParseHexToken(const std::string& token, uint16_t* out) {
55 if (!out) {
56 return false;
57 }
58 if (token.empty()) {
59 return false;
60 }
61 std::string trimmed = token;
62 if (absl::StartsWithIgnoreCase(trimmed, "0x")) {
63 trimmed = trimmed.substr(2);
64 }
65 if (trimmed.empty()) {
66 return false;
67 }
68 char* end = nullptr;
69 unsigned long value = std::strtoul(trimmed.c_str(), &end, 16);
70 if (end == nullptr || *end != '\0') {
71 return false;
72 }
73 if (value > 0xFFFFu) {
74 return false;
75 }
76 *out = static_cast<uint16_t>(value);
77 return true;
78}
79
80bool ParseHexList(const std::string& input, std::vector<uint16_t>* out,
81 std::string* error) {
82 if (!out) {
83 return false;
84 }
85 out->clear();
86 if (error) {
87 error->clear();
88 }
89 if (input.empty()) {
90 return true;
91 }
92
93 std::string normalized = input;
94 for (char& c : normalized) {
95 if (c == ',' || c == ';') {
96 c = ' ';
97 }
98 }
99
100 std::stringstream ss(normalized);
101 std::string token;
102 std::unordered_set<uint16_t> seen;
103 while (ss >> token) {
104 auto dash = token.find('-');
105 if (dash != std::string::npos) {
106 std::string left = token.substr(0, dash);
107 std::string right = token.substr(dash + 1);
108 uint16_t start = 0;
109 uint16_t end = 0;
110 if (!ParseHexToken(left, &start) || !ParseHexToken(right, &end)) {
111 if (error) {
112 *error = absl::StrFormat("Invalid range: %s", token);
113 }
114 return false;
115 }
116 if (end < start) {
117 if (error) {
118 *error = absl::StrFormat("Range end before start: %s", token);
119 }
120 return false;
121 }
122 for (uint16_t value = start; value <= end; ++value) {
123 if (seen.insert(value).second) {
124 out->push_back(value);
125 }
126 if (value == 0xFFFF) {
127 break;
128 }
129 }
130 } else {
131 uint16_t value = 0;
132 if (!ParseHexToken(token, &value)) {
133 if (error) {
134 *error = absl::StrFormat("Invalid hex value: %s", token);
135 }
136 return false;
137 }
138 if (seen.insert(value).second) {
139 out->push_back(value);
140 }
141 }
142 }
143 return true;
144}
145
146std::string FormatHexList(const std::vector<uint16_t>& values) {
147 std::string result;
148 result.reserve(values.size() * 6);
149 for (size_t i = 0; i < values.size(); ++i) {
150 const uint16_t value = values[i];
151 std::string token = value <= 0xFF ? absl::StrFormat("0x%02X", value)
152 : absl::StrFormat("0x%04X", value);
153 if (!result.empty()) {
154 result.append(", ");
155 }
156 result.append(token);
157 }
158 return result;
159}
160
161std::vector<uint16_t> DefaultTrackTiles() {
162 std::vector<uint16_t> values;
163 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
164 values.push_back(tile);
165 }
166 return values;
167}
168
169std::vector<uint16_t> DefaultStopTiles() {
170 return {0xB7, 0xB8, 0xB9, 0xBA};
171}
172
173std::vector<uint16_t> DefaultSwitchTiles() {
174 return {0xD0, 0xD1, 0xD2, 0xD3};
175}
176
177std::vector<uint16_t> DefaultTrackObjectIds() {
178 return {0x31};
179}
180
181std::vector<uint16_t> DefaultMinecartSpriteIds() {
182 return {0xA3};
183}
184
185bool IsLocalEndpoint(const std::string& base_url) {
186 if (base_url.empty()) {
187 return false;
188 }
189 std::string lower = absl::AsciiStrToLower(base_url);
190 return absl::StrContains(lower, "localhost") ||
191 absl::StrContains(lower, "127.0.0.1") ||
192 absl::StrContains(lower, "::1") ||
193 absl::StrContains(lower, "0.0.0.0") ||
194 absl::StrContains(lower, "192.168.") || absl::StartsWith(lower, "10.");
195}
196
197bool IsTailscaleEndpoint(const std::string& base_url) {
198 if (base_url.empty()) {
199 return false;
200 }
201 std::string lower = absl::AsciiStrToLower(base_url);
202 return absl::StrContains(lower, ".ts.net") ||
203 absl::StrContains(lower, "100.64.");
204}
205
207 std::vector<std::string> tags;
208 if (IsLocalEndpoint(host.base_url)) {
209 tags.push_back("local");
210 }
211 if (IsTailscaleEndpoint(host.base_url)) {
212 tags.push_back("tailscale");
213 }
214 if (absl::StartsWith(absl::AsciiStrToLower(host.base_url), "https://")) {
215 tags.push_back("https");
216 } else if (absl::StartsWith(absl::AsciiStrToLower(host.base_url),
217 "http://") &&
218 !IsLocalEndpoint(host.base_url) &&
219 !IsTailscaleEndpoint(host.base_url)) {
220 tags.push_back("http");
221 }
222 if (host.supports_vision) {
223 tags.push_back("vision");
224 }
225 if (host.supports_tools) {
226 tags.push_back("tools");
227 }
228 if (host.supports_streaming) {
229 tags.push_back("stream");
230 }
231 if (tags.empty()) {
232 return "";
233 }
234 std::string result = "[";
235 for (size_t i = 0; i < tags.size(); ++i) {
236 result += tags[i];
237 if (i + 1 < tags.size()) {
238 result += ", ";
239 }
240 }
241 result += "]";
242 return result;
243}
244
245bool AddUniquePath(std::vector<std::string>* paths, const std::string& path) {
246 if (!paths || path.empty()) {
247 return false;
248 }
249 auto it = std::find(paths->begin(), paths->end(), path);
250 if (it != paths->end()) {
251 return false;
252 }
253 paths->push_back(path);
254 return true;
255}
256
257} // namespace
258
260 if (!user_settings_) {
261 ImGui::TextDisabled("Settings not available");
262 return;
263 }
264
265 // Use collapsing headers for sections
266 // Default open the General Settings
267 if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " General Settings",
268 ImGuiTreeNodeFlags_DefaultOpen)) {
269 ImGui::Indent();
271 ImGui::Unindent();
272 ImGui::Spacing();
273 }
274
275 // Add Project Settings section
276 if (ImGui::CollapsingHeader(ICON_MD_FOLDER " Project Configuration")) {
277 ImGui::Indent();
279 ImGui::Unindent();
280 ImGui::Spacing();
281 }
282
283 if (ImGui::CollapsingHeader(ICON_MD_STORAGE " Files & Sync")) {
284 ImGui::Indent();
286 ImGui::Unindent();
287 ImGui::Spacing();
288 }
289
290 if (ImGui::CollapsingHeader(ICON_MD_PALETTE " Appearance")) {
291 ImGui::Indent();
293 ImGui::Unindent();
294 ImGui::Spacing();
295 }
296
297 if (ImGui::CollapsingHeader(ICON_MD_TUNE " Editor Behavior")) {
298 ImGui::Indent();
300 ImGui::Unindent();
301 ImGui::Spacing();
302 }
303
304 if (ImGui::CollapsingHeader(ICON_MD_SPEED " Performance")) {
305 ImGui::Indent();
307 ImGui::Unindent();
308 ImGui::Spacing();
309 }
310
311 if (ImGui::CollapsingHeader(ICON_MD_SMART_TOY " AI Agent")) {
312 ImGui::Indent();
314 ImGui::Unindent();
315 ImGui::Spacing();
316 }
317
318 if (ImGui::CollapsingHeader(ICON_MD_KEYBOARD " Keyboard Shortcuts")) {
319 ImGui::Indent();
321 ImGui::Unindent();
322 ImGui::Spacing();
323 }
324
325 if (ImGui::CollapsingHeader(ICON_MD_EXTENSION " ASM Patches")) {
326 ImGui::Indent();
328 ImGui::Unindent();
329 }
330}
331
333 // Refactored from table to vertical list for sidebar
334 static gui::FlagsMenu flags;
335
336 ImGui::TextDisabled("Feature Flags configuration");
337 ImGui::Spacing();
338
339 if (ImGui::TreeNode(ICON_MD_FLAG " System Flags")) {
340 flags.DrawSystemFlags();
341 ImGui::TreePop();
342 }
343
344 if (ImGui::TreeNode(ICON_MD_MAP " Overworld Flags")) {
345 flags.DrawOverworldFlags();
346 ImGui::TreePop();
347 }
348
349 if (ImGui::TreeNode(ICON_MD_EXTENSION " ZSCustomOverworld Enable Flags")) {
351 ImGui::TreePop();
352 }
353
354 if (ImGui::TreeNode(ICON_MD_CASTLE " Dungeon Flags")) {
355 flags.DrawDungeonFlags();
356 ImGui::TreePop();
357 }
358
359 if (ImGui::TreeNode(ICON_MD_FOLDER_SPECIAL " Resource Flags")) {
360 flags.DrawResourceFlags();
361 ImGui::TreePop();
362 }
363}
364
366 if (!project_) {
367 ImGui::TextDisabled("No active project.");
368 return;
369 }
370
371 ImGui::Text("%s Project Info", ICON_MD_INFO);
372 ImGui::Separator();
373
374 ImGui::Text("Name: %s", project_->name.c_str());
375 ImGui::Text("Path: %s", project_->filepath.c_str());
376
377 ImGui::Spacing();
378 ImGui::Text("%s ROM Identity", ICON_MD_VIDEOGAME_ASSET);
379 ImGui::Separator();
380
381 const char* roles[] = {"base", "dev", "patched", "release"};
382 int role_index = static_cast<int>(project_->rom_metadata.role);
383 if (ImGui::Combo("Role", &role_index, roles, IM_ARRAYSIZE(roles))) {
384 project_->rom_metadata.role = static_cast<project::RomRole>(role_index);
385 project_->Save();
386 }
387
388 const char* policies[] = {"allow", "warn", "block"};
389 int policy_index = static_cast<int>(project_->rom_metadata.write_policy);
390 if (ImGui::Combo("Write Policy", &policy_index, policies,
391 IM_ARRAYSIZE(policies))) {
393 static_cast<project::RomWritePolicy>(policy_index);
394 project_->Save();
395 }
396
397 std::string expected_hash = project_->rom_metadata.expected_hash;
398 if (ImGui::InputText("Expected Hash", &expected_hash)) {
399 project_->rom_metadata.expected_hash = expected_hash;
400 project_->Save();
401 }
402
403 static std::string cached_rom_hash;
404 static std::string cached_rom_path;
405 if (rom_ && rom_->is_loaded()) {
406 if (cached_rom_path != rom_->filename()) {
407 cached_rom_path = rom_->filename();
408 cached_rom_hash = util::ComputeRomHash(rom_->data(), rom_->size());
409 }
410 ImGui::Text("Current ROM Hash: %s", cached_rom_hash.empty()
411 ? "(unknown)"
412 : cached_rom_hash.c_str());
413 if (ImGui::Button("Use Current ROM Hash")) {
414 project_->rom_metadata.expected_hash = cached_rom_hash;
415 project_->Save();
416 }
417 } else {
418 ImGui::TextDisabled("Current ROM Hash: (no ROM loaded)");
419 }
420
421 ImGui::Spacing();
422 ImGui::Text("%s Paths", ICON_MD_FOLDER_OPEN);
423 ImGui::Separator();
424
425 // Output Folder
426 std::string output_folder = project_->output_folder;
427 if (ImGui::InputText("Output Folder", &output_folder)) {
428 project_->output_folder = output_folder;
429 project_->Save();
430 }
431
432 // Git Repository
433 std::string git_repo = project_->git_repository;
434 if (ImGui::InputText("Git Repository", &git_repo)) {
435 project_->git_repository = git_repo;
436 project_->Save();
437 }
438
439 ImGui::Spacing();
440 ImGui::Text("%s Build", ICON_MD_BUILD);
441 ImGui::Separator();
442
443 // Build Target
444 std::string build_target = project_->build_target;
445 if (ImGui::InputText("Build Target (ROM)", &build_target)) {
446 project_->build_target = build_target;
447 project_->Save();
448 }
449
450 // Symbols File
451 std::string symbols_file = project_->symbols_filename;
452 if (ImGui::InputText("Symbols File", &symbols_file)) {
453 project_->symbols_filename = symbols_file;
454 project_->Save();
455 }
456
457 ImGui::Spacing();
458 ImGui::Text("%s ASM / Hack Manifest", ICON_MD_CODE);
459 ImGui::Separator();
460 ImGui::TextWrapped(
461 "Optional: load a hack manifest JSON (generated by an ASM project) to "
462 "annotate room tags, show feature flags, and surface which ROM regions "
463 "are owned by ASM vs safe to edit in yaze.");
464
465 std::string manifest_file = project_->hack_manifest_file;
466 if (ImGui::InputText("Hack Manifest File", &manifest_file)) {
467 project_->hack_manifest_file = manifest_file;
469 project_->Save();
470 }
471
472 const bool manifest_loaded = project_->hack_manifest.loaded();
473 ImGui::SameLine();
474 ImGui::TextDisabled(manifest_loaded ? "(loaded)" : "(not loaded)");
475 if (ImGui::Button("Reload Manifest")) {
477 }
478
479 if (manifest_loaded) {
480 ImGui::Spacing();
481 ImGui::Text("Hack: %s", project_->hack_manifest.hack_name().c_str());
482 ImGui::Text("Manifest Version: %d",
484 ImGui::Text("Hooks Tracked: %d", project_->hack_manifest.total_hooks());
485
486 const auto& pipeline = project_->hack_manifest.build_pipeline();
487 if (!pipeline.dev_rom.empty()) {
488 ImGui::Text("Dev ROM: %s", pipeline.dev_rom.c_str());
489 }
490 if (!pipeline.patched_rom.empty()) {
491 ImGui::Text("Patched ROM: %s", pipeline.patched_rom.c_str());
492 }
493 if (!pipeline.build_script.empty()) {
494 ImGui::Text("Build Script: %s", pipeline.build_script.c_str());
495 }
496
497 const auto& msg_layout = project_->hack_manifest.message_layout();
498 if (msg_layout.first_expanded_id != 0 || msg_layout.last_expanded_id != 0) {
499 ImGui::Text("Expanded Messages: 0x%03X-0x%03X (%d)",
500 msg_layout.first_expanded_id, msg_layout.last_expanded_id,
501 msg_layout.expanded_count);
502 }
503
504 if (ImGui::TreeNode(ICON_MD_FLAG " Hack Feature Flags")) {
505 for (const auto& flag : project_->hack_manifest.feature_flags()) {
506 ImGui::BulletText("%s = %d (%s)", flag.name.c_str(), flag.value,
507 flag.enabled ? "enabled" : "disabled");
508 if (!flag.source.empty()) {
509 ImGui::SameLine();
510 ImGui::TextDisabled("%s", flag.source.c_str());
511 }
512 }
513 ImGui::TreePop();
514 }
515
516 if (ImGui::TreeNode(ICON_MD_LABEL " Room Tags (Dispatch)")) {
517 for (const auto& tag : project_->hack_manifest.room_tags()) {
518 ImGui::BulletText("0x%02X: %s", tag.tag_id, tag.name.c_str());
519 if (!tag.enabled && !tag.feature_flag.empty()) {
520 ImGui::SameLine();
521 ImGui::TextDisabled("(disabled by %s)", tag.feature_flag.c_str());
522 }
523 if (!tag.purpose.empty() && ImGui::IsItemHovered()) {
524 ImGui::SetTooltip("%s", tag.purpose.c_str());
525 }
526 }
527 ImGui::TreePop();
528 }
529 }
530
531 ImGui::Spacing();
532 ImGui::Text("%s Backup Settings", ICON_MD_BACKUP);
533 ImGui::Separator();
534
535 std::string backup_folder = project_->rom_backup_folder;
536 if (ImGui::InputText("Backup Folder", &backup_folder)) {
537 project_->rom_backup_folder = backup_folder;
538 project_->Save();
539 }
540
541 bool backup_on_save = project_->workspace_settings.backup_on_save;
542 if (ImGui::Checkbox("Backup Before Save", &backup_on_save)) {
544 project_->Save();
545 }
546
548 if (ImGui::InputInt("Retention Count", &retention)) {
550 std::max(0, retention);
551 project_->Save();
552 }
553
555 if (ImGui::Checkbox("Keep Daily Snapshots", &keep_daily)) {
557 project_->Save();
558 }
559
561 if (ImGui::InputInt("Keep Daily Days", &keep_days)) {
563 std::max(1, keep_days);
564 project_->Save();
565 }
566
567 ImGui::Spacing();
568 ImGui::Text("%s Dungeon Overlay", ICON_MD_TRAIN);
569 ImGui::Separator();
570 ImGui::TextWrapped(
571 "Configure collision/object IDs used by minecart overlays and audits. "
572 "Hex values, ranges allowed (e.g. B0-BE).");
573
574 static std::string overlay_project_path;
575 static HexListEditorState track_tiles_state;
576 static HexListEditorState stop_tiles_state;
577 static HexListEditorState switch_tiles_state;
578 static HexListEditorState track_object_state;
579 static HexListEditorState minecart_sprite_state;
580
581 if (overlay_project_path != project_->filepath) {
582 overlay_project_path = project_->filepath;
583 track_tiles_state.text =
584 FormatHexList(project_->dungeon_overlay.track_tiles);
585 stop_tiles_state.text =
587 switch_tiles_state.text =
589 track_object_state.text =
591 minecart_sprite_state.text =
593 track_tiles_state.error.clear();
594 stop_tiles_state.error.clear();
595 switch_tiles_state.error.clear();
596 track_object_state.error.clear();
597 minecart_sprite_state.error.clear();
598 }
599
600 auto draw_hex_list = [&](const char* label, const char* hint,
601 HexListEditorState& state,
602 const std::vector<uint16_t>& defaults,
603 std::vector<uint16_t>* target) {
604 if (!target) {
605 return;
606 }
607
608 bool apply = false;
609 ImGui::PushItemWidth(-180.0f);
610 if (ImGui::InputTextWithHint(label, hint, &state.text)) {
611 state.error.clear();
612 }
613 ImGui::PopItemWidth();
614 if (ImGui::IsItemDeactivatedAfterEdit()) {
615 apply = true;
616 }
617
618 ImGui::SameLine();
619 if (ImGui::SmallButton(absl::StrFormat("Apply##%s", label).c_str())) {
620 apply = true;
621 }
622 ImGui::SameLine();
623 if (ImGui::SmallButton(absl::StrFormat("Defaults##%s", label).c_str())) {
624 state.text = FormatHexList(defaults);
625 apply = true;
626 }
627 if (ImGui::IsItemHovered()) {
628 ImGui::SetTooltip("Reset to defaults");
629 }
630 ImGui::SameLine();
631 if (ImGui::SmallButton(absl::StrFormat("Clear##%s", label).c_str())) {
632 state.text.clear();
633 apply = true;
634 }
635 if (ImGui::IsItemHovered()) {
636 ImGui::SetTooltip("Clear list (empty uses defaults)");
637 }
638
639 const bool uses_defaults = target->empty();
640 const std::vector<uint16_t>& effective_values =
641 uses_defaults ? defaults : *target;
642 ImGui::SameLine();
643 ImGui::TextDisabled(ICON_MD_INFO);
644 if (ImGui::IsItemHovered()) {
645 ImGui::BeginTooltip();
646 ImGui::Text("Effective: %s", FormatHexList(effective_values).c_str());
647 if (uses_defaults) {
648 ImGui::TextDisabled("Using defaults (list is empty)");
649 }
650 ImGui::EndTooltip();
651 }
652
653 if (apply) {
654 std::vector<uint16_t> parsed;
655 std::string error;
656 if (ParseHexList(state.text, &parsed, &error)) {
657 *target = parsed;
658 project_->Save();
659 state.error.clear();
660 state.text = FormatHexList(parsed);
661 } else {
662 state.error = error;
663 }
664 }
665
666 if (!state.error.empty()) {
667 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.4f, 1.0f), "%s",
668 state.error.c_str());
669 }
670 };
671
672 draw_hex_list("Track Tiles", "0xB0-0xBE", track_tiles_state,
673 DefaultTrackTiles(), &project_->dungeon_overlay.track_tiles);
674 draw_hex_list("Stop Tiles", "0xB7, 0xB8, 0xB9, 0xBA", stop_tiles_state,
675 DefaultStopTiles(),
677 draw_hex_list("Switch Tiles", "0xD0-0xD3", switch_tiles_state,
678 DefaultSwitchTiles(),
680 draw_hex_list("Track Object IDs", "0x31", track_object_state,
681 DefaultTrackObjectIds(),
683 draw_hex_list("Minecart Sprite IDs", "0xA3", minecart_sprite_state,
684 DefaultMinecartSpriteIds(),
686}
687
689 if (!user_settings_) {
690 return;
691 }
692
693 auto& prefs = user_settings_->prefs();
694 auto& roots = prefs.project_root_paths;
695 static int selected_root_index = -1;
696 static std::string new_root_path;
697
698 ImGui::Text("%s Project Roots", ICON_MD_FOLDER_OPEN);
699 ImGui::Separator();
700
701 if (roots.empty()) {
702 ImGui::TextDisabled("No project roots configured.");
703 }
704
705 if (ImGui::BeginChild("ProjectRootsList", ImVec2(0, 140), true)) {
706 for (size_t i = 0; i < roots.size(); ++i) {
707 const bool is_default = roots[i] == prefs.default_project_root;
708 std::string label =
710 if (is_default) {
711 label += " (default)";
712 }
713 if (ImGui::Selectable(label.c_str(),
714 selected_root_index == static_cast<int>(i))) {
715 selected_root_index = static_cast<int>(i);
716 }
717 }
718 }
719 ImGui::EndChild();
720
721 const bool has_selection =
722 selected_root_index >= 0 &&
723 selected_root_index < static_cast<int>(roots.size());
724 if (has_selection) {
725 if (ImGui::Button("Set Default")) {
726 prefs.default_project_root = roots[selected_root_index];
728 }
729 ImGui::SameLine();
730 if (ImGui::Button(ICON_MD_DELETE " Remove")) {
731 const std::string removed = roots[selected_root_index];
732 roots.erase(roots.begin() + selected_root_index);
733 if (prefs.default_project_root == removed) {
734 prefs.default_project_root = roots.empty() ? "" : roots.front();
735 }
736 selected_root_index = roots.empty()
737 ? -1
738 : std::min(selected_root_index,
739 static_cast<int>(roots.size() - 1));
741 }
742 }
743
744 ImGui::Spacing();
745 ImGui::Text("%s Add Root", ICON_MD_ADD);
746 ImGui::Separator();
747
748 ImGui::InputTextWithHint("##project_root_add", "Add folder path...",
749 &new_root_path);
750 if (ImGui::Button(ICON_MD_ADD " Add Path")) {
751 const std::string trimmed =
752 std::string(absl::StripAsciiWhitespace(new_root_path));
753 if (!trimmed.empty()) {
754 if (AddUniquePath(&roots, trimmed) &&
755 prefs.default_project_root.empty()) {
756 prefs.default_project_root = trimmed;
757 }
759 }
760 }
761 ImGui::SameLine();
762 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Browse")) {
763 const std::string folder = util::FileDialogWrapper::ShowOpenFolderDialog();
764 if (!folder.empty()) {
765 if (AddUniquePath(&roots, folder) && prefs.default_project_root.empty()) {
766 prefs.default_project_root = folder;
767 }
769 }
770 }
771
772 ImGui::Spacing();
773 ImGui::Text("%s Quick Add", ICON_MD_BOLT);
774 ImGui::Separator();
775
776 if (ImGui::Button(ICON_MD_HOME " Add Documents")) {
778 if (docs_dir.ok()) {
779 if (AddUniquePath(&roots, docs_dir->string()) &&
780 prefs.default_project_root.empty()) {
781 prefs.default_project_root = docs_dir->string();
782 }
784 }
785 }
786 ImGui::SameLine();
787 if (ImGui::Button(ICON_MD_CLOUD " Add iCloud Projects")) {
788 auto icloud_dir =
790 if (icloud_dir.ok()) {
791 if (AddUniquePath(&roots, icloud_dir->string())) {
792 prefs.default_project_root = icloud_dir->string();
793 }
795 }
796 }
797 ImGui::TextDisabled(
798 "iCloud projects live in Documents/Yaze/iCloud on this Mac.");
799
800 ImGui::Spacing();
801 ImGui::Text("%s Sync Options", ICON_MD_SYNC);
802 ImGui::Separator();
803
804 bool use_icloud_sync = prefs.use_icloud_sync;
805 if (ImGui::Checkbox("Use iCloud sync (Documents)", &use_icloud_sync)) {
806 prefs.use_icloud_sync = use_icloud_sync;
807 if (use_icloud_sync) {
808 auto icloud_dir =
810 if (icloud_dir.ok()) {
811 AddUniquePath(&roots, icloud_dir->string());
812 prefs.default_project_root = icloud_dir->string();
813 }
814 }
816 }
817
818 bool use_files_app = prefs.use_files_app;
819 if (ImGui::Checkbox("Prefer Files app on iOS", &use_files_app)) {
820 prefs.use_files_app = use_files_app;
822 }
823}
824
826 auto& theme_manager = gui::ThemeManager::Get();
827
828 ImGui::Text("%s Theme Management", ICON_MD_PALETTE);
829 ImGui::Separator();
830
831 // Current theme with color swatches
832 const auto& current = theme_manager.GetCurrentThemeName();
833 const auto& current_theme = theme_manager.GetCurrentTheme();
834
835 ImGui::Text("Current Theme:");
836 ImGui::SameLine();
837
838 // Draw 3 color swatches inline: primary, surface, accent
839 {
840 ImDrawList* draw_list = ImGui::GetWindowDrawList();
841 ImVec2 cursor = ImGui::GetCursorScreenPos();
842 const float swatch_size = 12.0f;
843 const float spacing = 2.0f;
844
845 auto draw_swatch = [&](const gui::Color& color, float offset_x) {
846 ImVec2 p_min(cursor.x + offset_x, cursor.y);
847 ImVec2 p_max(p_min.x + swatch_size, p_min.y + swatch_size);
848 ImU32 col =
849 ImGui::ColorConvertFloat4ToU32(gui::ConvertColorToImVec4(color));
850 draw_list->AddRectFilled(p_min, p_max, col);
851 draw_list->AddRect(
852 p_min, p_max,
853 ImGui::ColorConvertFloat4ToU32(ImVec4(0.5f, 0.5f, 0.5f, 0.6f)));
854 };
855
856 draw_swatch(current_theme.primary, 0.0f);
857 draw_swatch(current_theme.surface, swatch_size + spacing);
858 draw_swatch(current_theme.accent, 2.0f * (swatch_size + spacing));
859
860 // Advance cursor past the swatches
861 ImGui::Dummy(
862 ImVec2(3.0f * swatch_size + 2.0f * spacing + 4.0f, swatch_size));
863 }
864
865 ImGui::SameLine();
866 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s", current.c_str());
867
868 ImGui::Spacing();
869
870 // Available themes list with hover preview and color swatches
871 ImGui::Text("Available Themes:");
872
873 bool any_theme_hovered = false;
874 if (ImGui::BeginChild("ThemeList", ImVec2(0, 200), true)) {
875 for (const auto& theme_name : theme_manager.GetAvailableThemes()) {
876 ImGui::PushID(theme_name.c_str());
877 bool is_current = (theme_name == current);
878
879 // Draw color swatches before the theme name
880 const gui::Theme* theme_data = theme_manager.GetTheme(theme_name);
881 if (theme_data) {
882 ImDrawList* draw_list = ImGui::GetWindowDrawList();
883 ImVec2 cursor = ImGui::GetCursorScreenPos();
884 const float swatch_size = 10.0f;
885 const float swatch_spacing = 2.0f;
886 const float total_swatch_width =
887 3.0f * swatch_size + 2.0f * swatch_spacing + 6.0f;
888
889 auto draw_small_swatch = [&](const gui::Color& color, float offset_x) {
890 ImVec2 p_min(cursor.x + offset_x, cursor.y + 2.0f);
891 ImVec2 p_max(p_min.x + swatch_size, p_min.y + swatch_size);
892 ImU32 col =
893 ImGui::ColorConvertFloat4ToU32(gui::ConvertColorToImVec4(color));
894 draw_list->AddRectFilled(p_min, p_max, col);
895 draw_list->AddRect(
896 p_min, p_max,
897 ImGui::ColorConvertFloat4ToU32(ImVec4(0.4f, 0.4f, 0.4f, 0.5f)));
898 };
899
900 draw_small_swatch(theme_data->primary, 0.0f);
901 draw_small_swatch(theme_data->surface, swatch_size + swatch_spacing);
902 draw_small_swatch(theme_data->accent,
903 2.0f * (swatch_size + swatch_spacing));
904
905 // Reserve space for swatches then draw the selectable
906 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + total_swatch_width);
907 }
908
909 // Checkmark prefix for the active theme
910 std::string label = is_current
911 ? std::string(ICON_MD_CHECK " ") + theme_name
912 : std::string(" ") + theme_name;
913
914 if (ImGui::Selectable(label.c_str(), is_current)) {
915 // If we're previewing, end preview first so the selected theme becomes
916 // the new baseline (otherwise EndPreview would restore the pre-preview
917 // theme when the cursor leaves the list).
918 if (theme_manager.IsPreviewActive()) {
919 theme_manager.EndPreview();
920 }
921 theme_manager.LoadTheme(theme_name);
922 }
923
924 // Hover triggers live preview
925 if (ImGui::IsItemHovered()) {
926 any_theme_hovered = true;
927 theme_manager.StartPreview(theme_name);
928 }
929
930 ImGui::PopID();
931 }
932 }
933 ImGui::EndChild();
934
935 // Restore original theme when nothing is hovered
936 if (!any_theme_hovered && theme_manager.IsPreviewActive()) {
937 theme_manager.EndPreview();
938 }
939
940 // Refresh button
941 if (ImGui::Button(ICON_MD_REFRESH " Refresh Themes")) {
942 theme_manager.RefreshAvailableThemes();
943 }
944 if (ImGui::IsItemHovered()) {
945 ImGui::SetTooltip("Re-scan theme directories for new or changed themes");
946 }
947
948 ImGui::Spacing();
949 ImGui::SeparatorText("Display Density");
950
951 {
952 auto preset = theme_manager.GetCurrentTheme().density_preset;
953 int density = static_cast<int>(preset);
954 bool changed = false;
955 changed |= ImGui::RadioButton("Compact (0.75x)", &density, 0);
956 ImGui::SameLine();
957 changed |= ImGui::RadioButton("Normal (1.0x)", &density, 1);
958 ImGui::SameLine();
959 changed |= ImGui::RadioButton("Comfortable (1.25x)", &density, 2);
960
961 if (changed) {
962 auto new_preset = static_cast<gui::DensityPreset>(density);
963 auto theme = theme_manager.GetCurrentTheme();
964 theme.ApplyDensityPreset(new_preset);
965 theme_manager.ApplyTheme(theme);
966 }
967 }
968
969 ImGui::Spacing();
970 ImGui::SeparatorText("Editor/Workspace Motion");
971
972 auto& prefs = user_settings_->prefs();
973 bool reduced_motion = prefs.reduced_motion;
974 if (ImGui::Checkbox("Reduced Motion", &reduced_motion)) {
975 prefs.reduced_motion = reduced_motion;
977 prefs.reduced_motion,
978 gui::Animator::ClampMotionProfile(prefs.switch_motion_profile));
980 }
981 if (ImGui::IsItemHovered()) {
982 ImGui::SetTooltip(
983 "Disable panel/editor transition animations for a calmer editing "
984 "experience.");
985 }
986
987 int switch_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
988 const char* switch_profile_labels[] = {"Snappy", "Standard", "Relaxed"};
989 if (ImGui::Combo("Switch Motion Profile", &switch_profile,
990 switch_profile_labels,
991 IM_ARRAYSIZE(switch_profile_labels))) {
992 prefs.switch_motion_profile = switch_profile;
994 prefs.reduced_motion,
995 gui::Animator::ClampMotionProfile(prefs.switch_motion_profile));
997 }
998 if (ImGui::IsItemHovered()) {
999 ImGui::SetTooltip(
1000 "Controls editor/workspace switch timing and easing for panel fades "
1001 "and sidebar slides.");
1002 }
1003
1004 ImGui::Spacing();
1006
1007 // Global font scale with persistence
1008 if (user_settings_) {
1009 ImGui::Separator();
1010 ImGui::Text("Global Font Scale");
1011 float scale = user_settings_->prefs().font_global_scale;
1012 if (ImGui::SliderFloat("##global_font_scale", &scale, 0.5f, 2.0f, "%.2f")) {
1014 ImGui::GetIO().FontGlobalScale = scale;
1016 }
1017 }
1018
1019 ImGui::Spacing();
1020 ImGui::Text("%s Status Bar", ICON_MD_HORIZONTAL_RULE);
1021 ImGui::Separator();
1022
1023 bool show_status_bar = user_settings_->prefs().show_status_bar;
1024 if (ImGui::Checkbox("Show Status Bar", &show_status_bar)) {
1025 user_settings_->prefs().show_status_bar = show_status_bar;
1027 // Immediately apply to status bar if status_bar_ is available
1028 if (status_bar_) {
1029 status_bar_->SetEnabled(show_status_bar);
1030 }
1031 }
1032 if (ImGui::IsItemHovered()) {
1033 ImGui::SetTooltip(
1034 "Display ROM, session, cursor, and zoom info at bottom of window");
1035 }
1036}
1037
1039 if (!user_settings_)
1040 return;
1041
1042 ImGui::Text("%s Auto-Save", ICON_MD_SAVE);
1043 ImGui::Separator();
1044
1045 if (ImGui::Checkbox("Enable Auto-Save",
1048 }
1049
1051 ImGui::Indent();
1052 int interval = static_cast<int>(user_settings_->prefs().autosave_interval);
1053 if (ImGui::SliderInt("Interval (sec)", &interval, 60, 600)) {
1054 user_settings_->prefs().autosave_interval = static_cast<float>(interval);
1056 }
1057
1058 if (ImGui::Checkbox("Backup Before Save",
1061 }
1062 ImGui::Unindent();
1063 }
1064
1065 ImGui::Spacing();
1066 ImGui::Text("%s Recent Files", ICON_MD_HISTORY);
1067 ImGui::Separator();
1068
1069 if (ImGui::SliderInt("Limit", &user_settings_->prefs().recent_files_limit, 5,
1070 50)) {
1072 }
1073
1074 ImGui::Spacing();
1075 ImGui::Text("%s Default Editor", ICON_MD_EDIT);
1076 ImGui::Separator();
1077
1078 const char* editors[] = {"None", "Overworld", "Dungeon", "Graphics"};
1079 if (ImGui::Combo("##DefaultEditor", &user_settings_->prefs().default_editor,
1080 editors, IM_ARRAYSIZE(editors))) {
1082 }
1083
1084 ImGui::Spacing();
1085 ImGui::Text("%s Sprite Names", ICON_MD_LABEL);
1086 ImGui::Separator();
1087 if (ImGui::Checkbox("Use HMagic sprite names (expanded)",
1092 }
1093}
1094
1096 if (!user_settings_)
1097 return;
1098
1099 ImGui::Text("%s Graphics", ICON_MD_IMAGE);
1100 ImGui::Separator();
1101
1102 if (ImGui::Checkbox("V-Sync", &user_settings_->prefs().vsync)) {
1104 }
1105
1106 if (ImGui::SliderInt("Target FPS", &user_settings_->prefs().target_fps, 30,
1107 144)) {
1109 }
1110
1111 ImGui::Spacing();
1112 ImGui::Text("%s Memory", ICON_MD_MEMORY);
1113 ImGui::Separator();
1114
1115 if (ImGui::SliderInt("Cache Size (MB)",
1116 &user_settings_->prefs().cache_size_mb, 128, 2048)) {
1118 }
1119
1120 if (ImGui::SliderInt("Undo History",
1121 &user_settings_->prefs().undo_history_size, 10, 200)) {
1123 }
1124
1125 ImGui::Spacing();
1126 ImGui::Separator();
1127 ImGui::Text("Current FPS: %.1f", ImGui::GetIO().Framerate);
1128 ImGui::Text("Frame Time: %.3f ms", 1000.0f / ImGui::GetIO().Framerate);
1129}
1130
1132 if (!user_settings_)
1133 return;
1134
1135 auto& prefs = user_settings_->prefs();
1136 auto& hosts = prefs.ai_hosts;
1137 static int selected_host_index = -1;
1138
1139 auto draw_key_row = [&](const char* label, std::string* key,
1140 const char* env_var, const char* id) {
1141 ImGui::PushID(id);
1142 ImGui::Text("%s", label);
1143 const ImVec2 button_size = ImGui::CalcTextSize(ICON_MD_SYNC " Env");
1144 float env_button_width =
1145 button_size.x + ImGui::GetStyle().FramePadding.x * 2.0f;
1146 float input_width = ImGui::GetContentRegionAvail().x - env_button_width -
1147 ImGui::GetStyle().ItemSpacing.x;
1148 bool stack = input_width < 160.0f;
1149 ImGui::SetNextItemWidth(stack ? -1.0f : input_width);
1150 if (ImGui::InputTextWithHint("##key", "API key...", key,
1151 ImGuiInputTextFlags_Password)) {
1153 }
1154 if (!stack) {
1155 ImGui::SameLine();
1156 }
1157 if (ImGui::SmallButton(ICON_MD_SYNC " Env")) {
1158 const char* env_key = std::getenv(env_var);
1159 if (env_key) {
1160 *key = env_key;
1162 }
1163 }
1164 ImGui::Spacing();
1165 ImGui::PopID();
1166 };
1167
1168 ImGui::Text("%s Provider Keys", ICON_MD_VPN_KEY);
1169 ImGui::Separator();
1170 draw_key_row("OpenAI", &prefs.openai_api_key, "OPENAI_API_KEY", "openai_key");
1171 draw_key_row("Anthropic", &prefs.anthropic_api_key, "ANTHROPIC_API_KEY",
1172 "anthropic_key");
1173 draw_key_row("Google (Gemini)", &prefs.gemini_api_key, "GEMINI_API_KEY",
1174 "gemini_key");
1175 ImGui::Spacing();
1176
1177 // Provider selection
1178 ImGui::Text("%s Provider Defaults (legacy)", ICON_MD_CLOUD);
1179 ImGui::Separator();
1180
1181 const char* providers[] = {"Ollama (Local)", "Gemini (Cloud)",
1182 "Mock (Testing)"};
1183 if (ImGui::Combo("##Provider", &prefs.ai_provider, providers,
1184 IM_ARRAYSIZE(providers))) {
1186 }
1187
1188 ImGui::Spacing();
1189 ImGui::Text("%s Host Routing", ICON_MD_STORAGE);
1190 ImGui::Separator();
1191
1192 const char* active_preview = "None";
1193 const char* remote_preview = "None";
1194 for (const auto& host : hosts) {
1195 if (!prefs.active_ai_host_id.empty() &&
1196 host.id == prefs.active_ai_host_id) {
1197 active_preview = host.label.c_str();
1198 }
1199 if (!prefs.remote_build_host_id.empty() &&
1200 host.id == prefs.remote_build_host_id) {
1201 remote_preview = host.label.c_str();
1202 }
1203 }
1204
1205 if (ImGui::BeginCombo("Active Host", active_preview)) {
1206 for (size_t i = 0; i < hosts.size(); ++i) {
1207 const bool is_selected = (!prefs.active_ai_host_id.empty() &&
1208 hosts[i].id == prefs.active_ai_host_id);
1209 if (ImGui::Selectable(hosts[i].label.c_str(), is_selected)) {
1210 prefs.active_ai_host_id = hosts[i].id;
1211 if (prefs.remote_build_host_id.empty()) {
1212 prefs.remote_build_host_id = hosts[i].id;
1213 }
1215 }
1216 if (is_selected) {
1217 ImGui::SetItemDefaultFocus();
1218 }
1219 }
1220 ImGui::EndCombo();
1221 }
1222
1223 if (ImGui::BeginCombo("Remote Build Host", remote_preview)) {
1224 for (size_t i = 0; i < hosts.size(); ++i) {
1225 const bool is_selected = (!prefs.remote_build_host_id.empty() &&
1226 hosts[i].id == prefs.remote_build_host_id);
1227 if (ImGui::Selectable(hosts[i].label.c_str(), is_selected)) {
1228 prefs.remote_build_host_id = hosts[i].id;
1230 }
1231 if (is_selected) {
1232 ImGui::SetItemDefaultFocus();
1233 }
1234 }
1235 ImGui::EndCombo();
1236 }
1237
1238 ImGui::Spacing();
1239 ImGui::Text("%s AI Hosts", ICON_MD_STORAGE);
1240 ImGui::Separator();
1241
1242 if (selected_host_index >= static_cast<int>(hosts.size())) {
1243 selected_host_index = hosts.empty() ? -1 : 0;
1244 }
1245 if (selected_host_index < 0 && !hosts.empty()) {
1246 for (size_t i = 0; i < hosts.size(); ++i) {
1247 if (!prefs.active_ai_host_id.empty() &&
1248 hosts[i].id == prefs.active_ai_host_id) {
1249 selected_host_index = static_cast<int>(i);
1250 break;
1251 }
1252 }
1253 if (selected_host_index < 0) {
1254 selected_host_index = 0;
1255 }
1256 }
1257
1258 ImGui::BeginChild("##ai_host_list", ImVec2(0, 150), true);
1259 for (size_t i = 0; i < hosts.size(); ++i) {
1260 const bool is_selected = static_cast<int>(i) == selected_host_index;
1261 std::string label = hosts[i].label;
1262 if (hosts[i].id == prefs.active_ai_host_id) {
1263 label += " (active)";
1264 }
1265 if (hosts[i].id == prefs.remote_build_host_id) {
1266 label += " (build)";
1267 }
1268 if (ImGui::Selectable(label.c_str(), is_selected)) {
1269 selected_host_index = static_cast<int>(i);
1270 }
1271 std::string tags = BuildHostTagString(hosts[i]);
1272 if (!tags.empty()) {
1273 ImGui::SameLine();
1274 ImGui::TextDisabled("%s", tags.c_str());
1275 }
1276 }
1277 ImGui::EndChild();
1278
1279 auto add_host = [&](UserSettings::Preferences::AiHost host) {
1280 if (host.id.empty()) {
1281 host.id = absl::StrFormat("host-%zu", hosts.size() + 1);
1282 }
1283 hosts.push_back(host);
1284 selected_host_index = static_cast<int>(hosts.size() - 1);
1285 if (prefs.active_ai_host_id.empty()) {
1286 prefs.active_ai_host_id = host.id;
1287 }
1288 if (prefs.remote_build_host_id.empty()) {
1289 prefs.remote_build_host_id = host.id;
1290 }
1292 };
1293
1294 if (ImGui::Button(ICON_MD_ADD " Add Host")) {
1296 host.label = "New Host";
1297 host.base_url = "http://localhost:1234";
1298 host.api_type = "openai";
1299 add_host(host);
1300 }
1301 ImGui::SameLine();
1302 if (ImGui::Button(ICON_MD_DELETE " Remove") && selected_host_index >= 0 &&
1303 selected_host_index < static_cast<int>(hosts.size())) {
1304 const std::string removed_id = hosts[selected_host_index].id;
1305 hosts.erase(hosts.begin() + selected_host_index);
1306 if (prefs.active_ai_host_id == removed_id) {
1307 prefs.active_ai_host_id = hosts.empty() ? "" : hosts.front().id;
1308 }
1309 if (prefs.remote_build_host_id == removed_id) {
1310 prefs.remote_build_host_id = prefs.active_ai_host_id;
1311 }
1312 selected_host_index =
1313 hosts.empty()
1314 ? -1
1315 : std::min(selected_host_index, static_cast<int>(hosts.size() - 1));
1317 }
1318
1319 ImGui::SameLine();
1320 if (ImGui::Button("Add LM Studio")) {
1322 host.label = "LM Studio (local)";
1323 host.base_url = "http://localhost:1234";
1324 host.api_type = "lmstudio";
1325 host.supports_tools = true;
1326 host.supports_streaming = true;
1327 add_host(host);
1328 }
1329 ImGui::SameLine();
1330 if (ImGui::Button("Add Ollama")) {
1332 host.label = "Ollama (local)";
1333 host.base_url = "http://localhost:11434";
1334 host.api_type = "ollama";
1335 host.supports_tools = true;
1336 host.supports_streaming = true;
1337 add_host(host);
1338 }
1339
1340 static std::string tailscale_host;
1341 ImGui::InputTextWithHint("##tailscale_host", "host.ts.net:1234",
1342 &tailscale_host);
1343 ImGui::SameLine();
1344 if (ImGui::Button("Add Tailscale Host")) {
1345 std::string trimmed =
1346 std::string(absl::StripAsciiWhitespace(tailscale_host));
1347 if (!trimmed.empty()) {
1349 host.label = "Tailscale Host";
1350 if (absl::StrContains(trimmed, "://")) {
1351 host.base_url = trimmed;
1352 } else {
1353 host.base_url = "http://" + trimmed;
1354 }
1355 host.api_type = "openai";
1356 host.supports_tools = true;
1357 host.supports_streaming = true;
1358 host.allow_insecure = true;
1359 add_host(host);
1360 tailscale_host.clear();
1361 }
1362 }
1363
1364 if (selected_host_index >= 0 &&
1365 selected_host_index < static_cast<int>(hosts.size())) {
1366 auto& host = hosts[static_cast<size_t>(selected_host_index)];
1367 ImGui::Spacing();
1368 ImGui::Text("Host Details");
1369 ImGui::Separator();
1370 if (ImGui::InputText("Label", &host.label)) {
1372 }
1373 if (ImGui::InputText("Base URL", &host.base_url)) {
1375 }
1376
1377 const char* api_types[] = {"openai", "ollama", "gemini",
1378 "anthropic", "lmstudio", "grpc"};
1379 int api_index = 0;
1380 for (int i = 0; i < IM_ARRAYSIZE(api_types); ++i) {
1381 if (host.api_type == api_types[i]) {
1382 api_index = i;
1383 break;
1384 }
1385 }
1386 if (ImGui::Combo("API Type", &api_index, api_types,
1387 IM_ARRAYSIZE(api_types))) {
1388 host.api_type = api_types[api_index];
1390 }
1391
1392 if (ImGui::InputText("API Key", &host.api_key,
1393 ImGuiInputTextFlags_Password)) {
1395 }
1396 if (ImGui::InputText("Keychain ID", &host.credential_id)) {
1398 }
1399 ImGui::SameLine();
1400 if (ImGui::SmallButton("Use Host ID")) {
1401 host.credential_id = host.id;
1403 }
1404 if (!host.credential_id.empty() && host.api_key.empty()) {
1405 ImGui::TextDisabled("Keychain lookup enabled (leave API key empty).");
1406 }
1407
1408 if (ImGui::Checkbox("Supports Vision", &host.supports_vision)) {
1410 }
1411 ImGui::SameLine();
1412 if (ImGui::Checkbox("Supports Tools", &host.supports_tools)) {
1414 }
1415 ImGui::SameLine();
1416 if (ImGui::Checkbox("Supports Streaming", &host.supports_streaming)) {
1418 }
1419 if (ImGui::Checkbox("Allow Insecure HTTP", &host.allow_insecure)) {
1421 }
1422 }
1423
1424 ImGui::Spacing();
1425 ImGui::Text("%s Local Model Paths", ICON_MD_FOLDER);
1426 ImGui::Separator();
1427
1428 auto& model_paths = prefs.ai_model_paths;
1429 static int selected_model_path = -1;
1430 static std::string new_model_path;
1431
1432 if (model_paths.empty()) {
1433 ImGui::TextDisabled("No model paths configured.");
1434 }
1435
1436 if (ImGui::BeginChild("ModelPathsList", ImVec2(0, 120), true)) {
1437 for (size_t i = 0; i < model_paths.size(); ++i) {
1438 std::string label =
1440 if (ImGui::Selectable(label.c_str(),
1441 selected_model_path == static_cast<int>(i))) {
1442 selected_model_path = static_cast<int>(i);
1443 }
1444 }
1445 }
1446 ImGui::EndChild();
1447
1448 const bool has_model_selection =
1449 selected_model_path >= 0 &&
1450 selected_model_path < static_cast<int>(model_paths.size());
1451 if (has_model_selection) {
1452 if (ImGui::Button(ICON_MD_DELETE " Remove")) {
1453 model_paths.erase(model_paths.begin() + selected_model_path);
1454 selected_model_path =
1455 model_paths.empty()
1456 ? -1
1457 : std::min(selected_model_path,
1458 static_cast<int>(model_paths.size() - 1));
1460 }
1461 }
1462
1463 ImGui::Spacing();
1464 ImGui::InputTextWithHint("##model_path_add", "Add folder path...",
1465 &new_model_path);
1466 if (ImGui::Button(ICON_MD_ADD " Add Path")) {
1467 const std::string trimmed =
1468 std::string(absl::StripAsciiWhitespace(new_model_path));
1469 if (!trimmed.empty() && AddUniquePath(&model_paths, trimmed)) {
1471 new_model_path.clear();
1472 }
1473 }
1474 ImGui::SameLine();
1475 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Browse")) {
1476 const std::string folder = util::FileDialogWrapper::ShowOpenFolderDialog();
1477 if (!folder.empty() && AddUniquePath(&model_paths, folder)) {
1479 }
1480 }
1481
1482 ImGui::Spacing();
1483 ImGui::Text("%s Quick Add", ICON_MD_BOLT);
1484 ImGui::Separator();
1485 const auto home_dir = util::PlatformPaths::GetHomeDirectory();
1486 if (ImGui::Button(ICON_MD_HOME " Add ~/models")) {
1487 if (!home_dir.empty() && home_dir != ".") {
1488 if (AddUniquePath(&model_paths, (home_dir / "models").string())) {
1490 }
1491 }
1492 }
1493 ImGui::SameLine();
1494 if (ImGui::Button("Add ~/.lmstudio/models")) {
1495 if (!home_dir.empty() && home_dir != ".") {
1496 if (AddUniquePath(&model_paths,
1497 (home_dir / ".lmstudio" / "models").string())) {
1499 }
1500 }
1501 }
1502 ImGui::SameLine();
1503 if (ImGui::Button("Add ~/.ollama/models")) {
1504 if (!home_dir.empty() && home_dir != ".") {
1505 if (AddUniquePath(&model_paths,
1506 (home_dir / ".ollama" / "models").string())) {
1508 }
1509 }
1510 }
1511
1512 ImGui::Spacing();
1513 ImGui::Text("%s Parameters", ICON_MD_TUNE);
1514 ImGui::Separator();
1515
1516 if (ImGui::SliderFloat("Temperature", &user_settings_->prefs().ai_temperature,
1517 0.0f, 2.0f)) {
1519 }
1520 ImGui::TextDisabled("Higher = more creative");
1521
1522 if (ImGui::SliderInt("Max Tokens", &user_settings_->prefs().ai_max_tokens,
1523 256, 8192)) {
1525 }
1526
1527 ImGui::Spacing();
1528 ImGui::Text("%s Behavior", ICON_MD_PSYCHOLOGY);
1529 ImGui::Separator();
1530
1531 if (ImGui::Checkbox("Proactive Suggestions",
1534 }
1535
1536 if (ImGui::Checkbox("Auto-Learn Preferences",
1539 }
1540
1541 if (ImGui::Checkbox("Enable Vision",
1544 }
1545
1546 ImGui::Spacing();
1547 ImGui::Text("%s Logging", ICON_MD_TERMINAL);
1548 ImGui::Separator();
1549
1550 const char* log_levels[] = {"Debug", "Info", "Warning", "Error", "Fatal"};
1551 if (ImGui::Combo("Log Level", &user_settings_->prefs().log_level, log_levels,
1552 IM_ARRAYSIZE(log_levels))) {
1553 // Apply log level logic here if needed
1555 }
1556}
1557
1559 if (ImGui::TreeNodeEx(ICON_MD_KEYBOARD " Shortcuts",
1560 ImGuiTreeNodeFlags_DefaultOpen)) {
1561 ImGui::InputTextWithHint("##shortcut_filter", "Filter shortcuts...",
1563 if (ImGui::IsItemHovered()) {
1564 ImGui::SetTooltip("Filter by action name or key combo");
1565 }
1566 ImGui::Spacing();
1567
1568 if (ImGui::TreeNode("Global Shortcuts")) {
1570 ImGui::TreePop();
1571 }
1572 if (ImGui::TreeNode("Editor Shortcuts")) {
1574 ImGui::TreePop();
1575 }
1576 if (ImGui::TreeNode("Panel Shortcuts")) {
1578 ImGui::TreePop();
1579 }
1580 ImGui::TextDisabled(
1581 "Tip: Use Cmd/Opt labels on macOS or Ctrl/Alt on Windows/Linux. "
1582 "Function keys and symbols (/, -) are supported.");
1583 ImGui::TreePop();
1584 }
1585}
1586
1587bool SettingsPanel::MatchesShortcutFilter(const std::string& text) const {
1588 if (shortcut_filter_.empty()) {
1589 return true;
1590 }
1591 std::string haystack = absl::AsciiStrToLower(text);
1592 std::string needle = absl::AsciiStrToLower(shortcut_filter_);
1593 return absl::StrContains(haystack, needle);
1594}
1595
1598 ImGui::TextDisabled("Not available");
1599 return;
1600 }
1601
1602 auto shortcuts =
1604 if (shortcuts.empty()) {
1605 ImGui::TextDisabled("No global shortcuts registered.");
1606 return;
1607 }
1608
1609 static std::unordered_map<std::string, std::string> editing;
1610
1611 bool has_match = false;
1612 for (const auto& sc : shortcuts) {
1613 std::string label = sc.name;
1614 std::string keys = PrintShortcut(sc.keys);
1615 if (!MatchesShortcutFilter(label) && !MatchesShortcutFilter(keys)) {
1616 continue;
1617 }
1618 has_match = true;
1619 auto it = editing.find(sc.name);
1620 if (it == editing.end()) {
1621 std::string current = PrintShortcut(sc.keys);
1622 // Use user override if present
1623 auto u = user_settings_->prefs().global_shortcuts.find(sc.name);
1624 if (u != user_settings_->prefs().global_shortcuts.end()) {
1625 current = u->second;
1626 }
1627 editing[sc.name] = current;
1628 }
1629
1630 ImGui::PushID(sc.name.c_str());
1631 ImGui::Text("%s", sc.name.c_str());
1632 ImGui::SameLine();
1633 ImGui::SetNextItemWidth(180);
1634 std::string& value = editing[sc.name];
1635 if (ImGui::InputText("##global", &value,
1636 ImGuiInputTextFlags_EnterReturnsTrue |
1637 ImGuiInputTextFlags_AutoSelectAll)) {
1638 auto parsed = ParseShortcut(value);
1639 if (!parsed.empty() || value.empty()) {
1640 // Empty string clears the shortcut
1641 shortcut_manager_->UpdateShortcutKeys(sc.name, parsed);
1642 if (value.empty()) {
1643 user_settings_->prefs().global_shortcuts.erase(sc.name);
1644 } else {
1645 user_settings_->prefs().global_shortcuts[sc.name] = value;
1646 }
1648 }
1649 }
1650 ImGui::PopID();
1651 }
1652 if (!has_match) {
1653 ImGui::TextDisabled("No shortcuts match the current filter.");
1654 }
1655}
1656
1659 ImGui::TextDisabled("Not available");
1660 return;
1661 }
1662
1663 auto shortcuts =
1665 std::map<std::string, std::vector<Shortcut>> grouped;
1666 static std::unordered_map<std::string, std::string> editing;
1667
1668 for (const auto& sc : shortcuts) {
1669 auto pos = sc.name.find(".");
1670 std::string group =
1671 pos != std::string::npos ? sc.name.substr(0, pos) : "general";
1672 grouped[group].push_back(sc);
1673 }
1674 bool has_match = false;
1675 for (const auto& [group, list] : grouped) {
1676 std::vector<Shortcut> filtered;
1677 filtered.reserve(list.size());
1678 for (const auto& sc : list) {
1679 std::string keys = PrintShortcut(sc.keys);
1680 if (MatchesShortcutFilter(sc.name) || MatchesShortcutFilter(keys)) {
1681 filtered.push_back(sc);
1682 }
1683 }
1684 if (filtered.empty()) {
1685 continue;
1686 }
1687 has_match = true;
1688 if (ImGui::TreeNode(group.c_str())) {
1689 for (const auto& sc : filtered) {
1690 ImGui::PushID(sc.name.c_str());
1691 ImGui::Text("%s", sc.name.c_str());
1692 ImGui::SameLine();
1693 ImGui::SetNextItemWidth(180);
1694 std::string& value = editing[sc.name];
1695 if (value.empty()) {
1696 value = PrintShortcut(sc.keys);
1697 // Apply user override if present
1698 auto u = user_settings_->prefs().editor_shortcuts.find(sc.name);
1699 if (u != user_settings_->prefs().editor_shortcuts.end()) {
1700 value = u->second;
1701 }
1702 }
1703 if (ImGui::InputText("##editor", &value,
1704 ImGuiInputTextFlags_EnterReturnsTrue |
1705 ImGuiInputTextFlags_AutoSelectAll)) {
1706 auto parsed = ParseShortcut(value);
1707 if (!parsed.empty() || value.empty()) {
1708 shortcut_manager_->UpdateShortcutKeys(sc.name, parsed);
1709 if (value.empty()) {
1710 user_settings_->prefs().editor_shortcuts.erase(sc.name);
1711 } else {
1712 user_settings_->prefs().editor_shortcuts[sc.name] = value;
1713 }
1715 }
1716 }
1717 ImGui::PopID();
1718 }
1719 ImGui::TreePop();
1720 }
1721 }
1722 if (!has_match) {
1723 ImGui::TextDisabled("No shortcuts match the current filter.");
1724 }
1725}
1726
1728 if (!panel_manager_ || !user_settings_) {
1729 ImGui::TextDisabled("Registry not available");
1730 return;
1731 }
1732
1733 // Simplified shortcut editor for sidebar
1734 auto categories = panel_manager_->GetAllCategories();
1735
1736 bool has_match = false;
1737 for (const auto& category : categories) {
1738 auto cards = panel_manager_->GetPanelsInCategory(0, category);
1739 std::vector<decltype(cards)::value_type> filtered_cards;
1740 filtered_cards.reserve(cards.size());
1741 for (const auto& card : cards) {
1742 if (MatchesShortcutFilter(card.display_name) ||
1743 MatchesShortcutFilter(card.card_id)) {
1744 filtered_cards.push_back(card);
1745 }
1746 }
1747 if (filtered_cards.empty()) {
1748 continue;
1749 }
1750 has_match = true;
1751 if (ImGui::TreeNode(category.c_str())) {
1752
1753 for (const auto& card : filtered_cards) {
1754 ImGui::PushID(card.card_id.c_str());
1755
1756 ImGui::Text("%s %s", card.icon.c_str(), card.display_name.c_str());
1757
1758 std::string current_shortcut;
1759 auto it = user_settings_->prefs().panel_shortcuts.find(card.card_id);
1760 if (it != user_settings_->prefs().panel_shortcuts.end()) {
1761 current_shortcut = it->second;
1762 } else if (!card.shortcut_hint.empty()) {
1763 current_shortcut = card.shortcut_hint;
1764 } else {
1765 current_shortcut = "None";
1766 }
1767
1768 // Display platform-aware label
1769 std::string display_shortcut = current_shortcut;
1770 auto parsed = ParseShortcut(current_shortcut);
1771 if (!parsed.empty()) {
1772 display_shortcut = PrintShortcut(parsed);
1773 }
1774
1775 if (is_editing_shortcut_ && editing_card_id_ == card.card_id) {
1776 ImGui::SetNextItemWidth(120);
1777 ImGui::SetKeyboardFocusHere();
1778 if (ImGui::InputText("##Edit", shortcut_edit_buffer_,
1779 sizeof(shortcut_edit_buffer_),
1780 ImGuiInputTextFlags_EnterReturnsTrue)) {
1781 if (strlen(shortcut_edit_buffer_) > 0) {
1782 user_settings_->prefs().panel_shortcuts[card.card_id] =
1784 } else {
1785 user_settings_->prefs().panel_shortcuts.erase(card.card_id);
1786 }
1788 is_editing_shortcut_ = false;
1789 editing_card_id_.clear();
1790 }
1791 ImGui::SameLine();
1792 if (ImGui::Button(ICON_MD_CLOSE)) {
1793 is_editing_shortcut_ = false;
1794 editing_card_id_.clear();
1795 }
1796 } else {
1797 if (ImGui::Button(display_shortcut.c_str(), ImVec2(120, 0))) {
1798 is_editing_shortcut_ = true;
1799 editing_card_id_ = card.card_id;
1800 strncpy(shortcut_edit_buffer_, current_shortcut.c_str(),
1801 sizeof(shortcut_edit_buffer_) - 1);
1802 }
1803 if (ImGui::IsItemHovered()) {
1804 ImGui::SetTooltip("Click to edit shortcut");
1805 }
1806 }
1807
1808 ImGui::PopID();
1809 }
1810
1811 ImGui::TreePop();
1812 }
1813 }
1814 if (!has_match) {
1815 ImGui::TextDisabled("No shortcuts match the current filter.");
1816 }
1817}
1818
1820 // Load patches on first access
1821 if (!patches_loaded_) {
1822 // Try to load from default patches location
1823 auto patches_dir_status = util::PlatformPaths::FindAsset("patches");
1824 if (patches_dir_status.ok()) {
1825 auto status = patch_manager_.LoadPatches(patches_dir_status->string());
1826 if (status.ok()) {
1827 patches_loaded_ = true;
1828 if (!patch_manager_.folders().empty()) {
1830 }
1831 }
1832 }
1833 }
1834
1835 ImGui::Text("%s ZScream Patch System", ICON_MD_EXTENSION);
1836 ImGui::Separator();
1837
1838 if (!patches_loaded_) {
1839 ImGui::TextDisabled("No patches loaded");
1840 ImGui::TextDisabled("Place .asm patches in assets/patches/");
1841
1842 if (ImGui::Button("Browse for Patches Folder...")) {
1843 // TODO: File browser for patches folder
1844 }
1845 return;
1846 }
1847
1848 // Status line
1849 int enabled_count = patch_manager_.GetEnabledPatchCount();
1850 int total_count = static_cast<int>(patch_manager_.patches().size());
1851 ImGui::Text("Loaded: %d patches (%d enabled)", total_count, enabled_count);
1852
1853 ImGui::Spacing();
1854
1855 // Folder tabs
1856 if (gui::BeginThemedTabBar("##PatchFolders",
1857 ImGuiTabBarFlags_FittingPolicyScroll)) {
1858 for (const auto& folder : patch_manager_.folders()) {
1859 if (ImGui::BeginTabItem(folder.c_str())) {
1860 selected_folder_ = folder;
1861 DrawPatchList(folder);
1862 ImGui::EndTabItem();
1863 }
1864 }
1866 }
1867
1868 ImGui::Spacing();
1869 ImGui::Separator();
1870
1871 // Selected patch details
1872 if (selected_patch_) {
1874 } else {
1875 ImGui::TextDisabled("Select a patch to view details");
1876 }
1877
1878 ImGui::Spacing();
1879 ImGui::Separator();
1880
1881 // Action buttons
1882 if (ImGui::Button(ICON_MD_CHECK " Apply Patches to ROM")) {
1883 if (rom_ && rom_->is_loaded()) {
1885 if (!status.ok()) {
1886 LOG_ERROR("Settings", "Failed to apply patches: %s", status.message());
1887 } else {
1888 LOG_INFO("Settings", "Applied %d patches successfully", enabled_count);
1889 }
1890 } else {
1891 LOG_WARN("Settings", "No ROM loaded");
1892 }
1893 }
1894 if (ImGui::IsItemHovered()) {
1895 ImGui::SetTooltip("Apply all enabled patches to the loaded ROM");
1896 }
1897
1898 ImGui::SameLine();
1899 if (ImGui::Button(ICON_MD_SAVE " Save All")) {
1900 auto status = patch_manager_.SaveAllPatches();
1901 if (!status.ok()) {
1902 LOG_ERROR("Settings", "Failed to save patches: %s", status.message());
1903 }
1904 }
1905
1906 if (ImGui::Button(ICON_MD_REFRESH " Reload Patches")) {
1907 patches_loaded_ = false;
1908 selected_patch_ = nullptr;
1909 }
1910}
1911
1912void SettingsPanel::DrawPatchList(const std::string& folder) {
1913 auto patches = patch_manager_.GetPatchesInFolder(folder);
1914
1915 if (patches.empty()) {
1916 ImGui::TextDisabled("No patches in this folder");
1917 return;
1918 }
1919
1920 // Use a child region for scrolling
1921 float available_height = std::min(200.0f, patches.size() * 25.0f + 10.0f);
1922 if (ImGui::BeginChild("##PatchList", ImVec2(0, available_height), true)) {
1923 for (auto* patch : patches) {
1924 ImGui::PushID(patch->filename().c_str());
1925
1926 bool enabled = patch->enabled();
1927 if (ImGui::Checkbox("##Enabled", &enabled)) {
1928 patch->set_enabled(enabled);
1929 }
1930
1931 ImGui::SameLine();
1932
1933 // Highlight selected patch
1934 bool is_selected = (selected_patch_ == patch);
1935 if (ImGui::Selectable(patch->name().c_str(), is_selected)) {
1936 selected_patch_ = patch;
1937 }
1938
1939 ImGui::PopID();
1940 }
1941 }
1942 ImGui::EndChild();
1943}
1944
1946 if (!selected_patch_)
1947 return;
1948
1949 ImGui::Text("%s %s", ICON_MD_INFO, selected_patch_->name().c_str());
1950
1951 if (!selected_patch_->author().empty()) {
1952 ImGui::TextDisabled("by %s", selected_patch_->author().c_str());
1953 }
1954
1955 if (!selected_patch_->version().empty()) {
1956 ImGui::SameLine();
1957 ImGui::TextDisabled("v%s", selected_patch_->version().c_str());
1958 }
1959
1960 // Description
1961 if (!selected_patch_->description().empty()) {
1962 ImGui::Spacing();
1963 ImGui::TextWrapped("%s", selected_patch_->description().c_str());
1964 }
1965
1966 // Parameters
1967 auto& params = selected_patch_->mutable_parameters();
1968 if (!params.empty()) {
1969 ImGui::Spacing();
1970 ImGui::Text("%s Parameters", ICON_MD_TUNE);
1971 ImGui::Separator();
1972
1973 for (auto& param : params) {
1974 DrawParameterWidget(&param);
1975 }
1976 }
1977}
1978
1980 if (!param)
1981 return;
1982
1983 ImGui::PushID(param->define_name.c_str());
1984
1985 switch (param->type) {
1989 int value = param->value;
1990 const char* format = param->use_decimal ? "%d" : "$%X";
1991
1992 ImGui::Text("%s", param->display_name.c_str());
1993 ImGui::SetNextItemWidth(100);
1994 if (ImGui::InputInt("##Value", &value, 1, 16)) {
1995 param->value = std::clamp(value, param->min_value, param->max_value);
1996 }
1997
1998 // Show range hint
1999 if (param->min_value != 0 || param->max_value != 0xFF) {
2000 ImGui::SameLine();
2001 ImGui::TextDisabled("(%d-%d)", param->min_value, param->max_value);
2002 }
2003 break;
2004 }
2005
2007 bool checked = (param->value == param->checked_value);
2008 if (ImGui::Checkbox(param->display_name.c_str(), &checked)) {
2009 param->value = checked ? param->checked_value : param->unchecked_value;
2010 }
2011 break;
2012 }
2013
2015 ImGui::Text("%s", param->display_name.c_str());
2016 for (size_t i = 0; i < param->choices.size(); ++i) {
2017 bool selected = (param->value == static_cast<int>(i));
2018 if (ImGui::RadioButton(param->choices[i].c_str(), selected)) {
2019 param->value = static_cast<int>(i);
2020 }
2021 }
2022 break;
2023 }
2024
2026 ImGui::Text("%s", param->display_name.c_str());
2027 for (size_t i = 0; i < param->choices.size(); ++i) {
2028 if (param->choices[i].empty() || param->choices[i] == "_EMPTY") {
2029 continue;
2030 }
2031 bool bit_set = (param->value & (1 << i)) != 0;
2032 if (ImGui::Checkbox(param->choices[i].c_str(), &bit_set)) {
2033 if (bit_set) {
2034 param->value |= (1 << i);
2035 } else {
2036 param->value &= ~(1 << i);
2037 }
2038 }
2039 }
2040 break;
2041 }
2042
2044 ImGui::Text("%s", param->display_name.c_str());
2045 // TODO: Implement item dropdown using game item names
2046 ImGui::SetNextItemWidth(150);
2047 if (ImGui::InputInt("Item ID", &param->value)) {
2048 param->value = std::clamp(param->value, 0, 255);
2049 }
2050 break;
2051 }
2052 }
2053
2054 ImGui::PopID();
2055 ImGui::Spacing();
2056}
2057
2058} // namespace editor
2059} // namespace yaze
auto filename() const
Definition rom.h:145
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
const std::string & version() const
Definition asm_patch.h:86
std::vector< PatchParameter > & mutable_parameters()
Definition asm_patch.h:98
const std::string & author() const
Definition asm_patch.h:85
const std::string & description() const
Definition asm_patch.h:87
const std::string & name() const
Definition asm_patch.h:84
const std::vector< FeatureFlag > & feature_flags() const
const MessageLayout & message_layout() const
const std::vector< RoomTagEntry > & room_tags() const
Get all room tags.
const std::string & hack_name() const
bool loaded() const
Check if the manifest has been loaded.
const BuildPipeline & build_pipeline() const
absl::Status ApplyEnabledPatches(Rom *rom)
Apply all enabled patches to a ROM.
absl::Status SaveAllPatches()
Save all patches to their files.
const std::vector< std::string > & folders() const
Get list of patch folder names.
int GetEnabledPatchCount() const
Get count of enabled patches.
std::vector< AsmPatch * > GetPatchesInFolder(const std::string &folder)
Get all patches in a specific folder.
const std::vector< std::unique_ptr< AsmPatch > > & patches() const
Get all loaded patches.
absl::Status LoadPatches(const std::string &patches_dir)
Load all patches from a directory structure.
virtual void SetDependencies(const EditorDependencies &deps)
Definition editor.h:241
std::vector< std::string > GetAllCategories(size_t session_id) const
std::vector< PanelDescriptor > GetPanelsInCategory(size_t session_id, const std::string &category) const
void SetStatusBar(StatusBar *bar)
void DrawPatchList(const std::string &folder)
ShortcutManager * shortcut_manager_
void SetDependencies(const EditorDependencies &deps) override
bool MatchesShortcutFilter(const std::string &text) const
void DrawParameterWidget(core::PatchParameter *param)
core::AsmPatch * selected_patch_
void SetPanelManager(PanelManager *registry)
core::PatchManager patch_manager_
project::YazeProject * project_
void SetShortcutManager(ShortcutManager *manager)
void SetUserSettings(UserSettings *settings)
void SetProject(project::YazeProject *project)
std::vector< Shortcut > GetShortcutsByScope(Shortcut::Scope scope) const
bool UpdateShortcutKeys(const std::string &name, const std::vector< ImGuiKey > &keys)
void SetEnabled(bool enabled)
Enable or disable the status bar.
Definition status_bar.h:53
static MotionProfile ClampMotionProfile(int raw_profile)
Definition animator.cc:95
void SetMotionPreferences(bool reduced_motion, MotionProfile profile)
Definition animator.cc:105
static ThemeManager & Get()
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
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::StatusOr< std::filesystem::path > FindAsset(const std::string &relative_path)
Find an asset file in multiple standard locations.
static std::string NormalizePathForDisplay(const std::filesystem::path &path)
Normalize path separators for display.
static std::filesystem::path GetHomeDirectory()
Get the user's home directory in a cross-platform way.
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_MEMORY
Definition icons.h:1195
#define ICON_MD_STORAGE
Definition icons.h:1865
#define ICON_MD_FOLDER_SPECIAL
Definition icons.h:815
#define ICON_MD_TRAIN
Definition icons.h:2005
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_CODE
Definition icons.h:434
#define ICON_MD_LABEL
Definition icons.h:1053
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_SPEED
Definition icons.h:1817
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_HOME
Definition icons.h:953
#define ICON_MD_EXTENSION
Definition icons.h:715
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_PSYCHOLOGY
Definition icons.h:1523
#define ICON_MD_BOLT
Definition icons.h:282
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_TERMINAL
Definition icons.h:1951
#define ICON_MD_FLAG
Definition icons.h:784
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_HORIZONTAL_RULE
Definition icons.h:960
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_BACKUP
Definition icons.h:231
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_SYNC
Definition icons.h:1919
#define ICON_MD_CLOUD
Definition icons.h:423
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_VPN_KEY
Definition icons.h:2113
#define ICON_MD_SMART_TOY
Definition icons.h:1781
#define ICON_MD_HISTORY
Definition icons.h:946
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
bool ParseHexToken(const std::string &token, uint16_t *out)
bool AddUniquePath(std::vector< std::string > *paths, const std::string &path)
std::string BuildHostTagString(const UserSettings::Preferences::AiHost &host)
std::vector< ImGuiKey > ParseShortcut(const std::string &shortcut)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
void DrawFontManager()
Definition style.cc:1321
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
void EndThemedTabBar()
DensityPreset
Typography and spacing density presets.
Animator & GetAnimator()
Definition animator.cc:301
std::string ComputeRomHash(const uint8_t *data, size_t size)
Definition rom_hash.cc:70
void SetPreferHmagicSpriteNames(bool prefer)
Definition sprite.cc:273
Represents a configurable parameter within an ASM patch.
Definition asm_patch.h:33
PatchParameterType type
Definition asm_patch.h:36
std::vector< std::string > choices
Definition asm_patch.h:43
Unified dependency container for all editor types.
Definition editor.h:163
project::YazeProject * project
Definition editor.h:167
ShortcutManager * shortcut_manager
Definition editor.h:177
std::vector< std::string > project_root_paths
std::unordered_map< std::string, std::string > panel_shortcuts
std::unordered_map< std::string, std::string > editor_shortcuts
std::unordered_map< std::string, std::string > global_shortcuts
void DrawZSCustomOverworldFlags(Rom *rom)
Comprehensive theme structure for YAZE.
std::vector< uint16_t > track_object_ids
Definition project.h:98
std::vector< uint16_t > minecart_sprite_ids
Definition project.h:99
std::vector< uint16_t > track_stop_tiles
Definition project.h:94
std::vector< uint16_t > track_tiles
Definition project.h:93
std::vector< uint16_t > track_switch_tiles
Definition project.h:95
std::string expected_hash
Definition project.h:107
RomWritePolicy write_policy
Definition project.h:108
std::string rom_backup_folder
Definition project.h:129
std::string git_repository
Definition project.h:171
core::HackManifest hack_manifest
Definition project.h:160
std::string hack_manifest_file
Definition project.h:142
WorkspaceSettings workspace_settings
Definition project.h:149
std::string output_folder
Definition project.h:164
DungeonOverlaySettings dungeon_overlay
Definition project.h:150
std::string symbols_filename
Definition project.h:138