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