12#include "absl/strings/ascii.h"
13#include "absl/strings/match.h"
14#include "absl/strings/str_cat.h"
15#include "absl/strings/str_format.h"
36#include "imgui/imgui.h"
37#include "imgui/misc/cpp/imgui_stdlib.h"
38#include "nlohmann/json.hpp"
61std::string FormatHexList(
const std::vector<uint16_t>& values) {
63 result.reserve(values.size() * 6);
64 for (
size_t i = 0; i < values.size(); ++i) {
65 const uint16_t value = values[i];
66 std::string token = value <= 0xFF ? absl::StrFormat(
"0x%02X", value)
67 : absl::StrFormat(
"0x%04X", value);
68 if (!result.empty()) {
77 std::vector<uint16_t> values;
78 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
79 values.push_back(tile);
85 return {0xB7, 0xB8, 0xB9, 0xBA};
89 return {0xD0, 0xD1, 0xD2, 0xD3};
100bool IsLocalEndpoint(
const std::string& base_url) {
101 if (base_url.empty()) {
104 std::string lower = absl::AsciiStrToLower(base_url);
105 return absl::StrContains(lower,
"localhost") ||
106 absl::StrContains(lower,
"127.0.0.1") ||
107 absl::StrContains(lower,
"::1") ||
108 absl::StrContains(lower,
"0.0.0.0") ||
109 absl::StrContains(lower,
"192.168.") || absl::StartsWith(lower,
"10.");
112bool IsTailscaleEndpoint(
const std::string& base_url) {
113 if (base_url.empty()) {
116 std::string lower = absl::AsciiStrToLower(base_url);
117 return absl::StrContains(lower,
".ts.net") ||
118 absl::StrContains(lower,
"100.64.");
122 std::vector<std::string> tags;
123 if (IsLocalEndpoint(host.
base_url)) {
124 tags.push_back(
"local");
126 if (IsTailscaleEndpoint(host.
base_url)) {
127 tags.push_back(
"tailscale");
129 if (absl::StartsWith(absl::AsciiStrToLower(host.
base_url),
"https://")) {
130 tags.push_back(
"https");
131 }
else if (absl::StartsWith(absl::AsciiStrToLower(host.
base_url),
134 !IsTailscaleEndpoint(host.
base_url)) {
135 tags.push_back(
"http");
138 tags.push_back(
"vision");
141 tags.push_back(
"tools");
144 tags.push_back(
"stream");
149 std::string result =
"[";
150 for (
size_t i = 0; i < tags.size(); ++i) {
152 if (i + 1 < tags.size()) {
163 if (path.empty() || path.front() !=
'~') {
167 if (home_dir.empty() || home_dir ==
".") {
170 if (path.size() == 1) {
171 return home_dir.string();
173 if (path[1] ==
'/' || path[1] ==
'\\') {
174 return (home_dir / path.substr(2)).string();
176 return (home_dir / path.substr(1)).string();
179bool AddUniquePath(std::vector<std::string>* paths,
const std::string& path) {
180 if (!paths || path.empty()) {
184 auto it = std::find(paths->begin(), paths->end(), expanded);
185 if (it != paths->end()) {
188 paths->push_back(expanded);
192constexpr std::array<SettingsPanel::CategoryDescriptor, 10>
196 "theme font scale density motion colors"},
199 "autosave backup tips editor defaults"},
202 "vsync fps cache undo performance"},
205 "folders roots filesystem icloud sync"},
208 "agent provider model host ollama gemini openai"},
213 "layout dock windows named workspace"},
216 "project rom identity paths build manifest"},
219 "experimental system overworld dungeon resources save flags"},
227 return "Application";
233 return "Application";
239 Scope scope,
const std::string& query) {
240 const std::string needle = absl::AsciiStrToLower(query);
241 std::vector<CategoryDescriptor> result;
243 if (category.scope != scope) {
246 if (!needle.empty()) {
247 const std::string haystack = absl::AsciiStrToLower(absl::StrFormat(
248 "%s %s %s", category.id, category.label, category.keywords));
249 if (haystack.find(needle) == std::string::npos) {
253 result.push_back(category);
260 const auto summarize = [](
const std::vector<uint16_t>& configured,
261 const std::vector<uint16_t>& standard) {
262 const bool uses_standard_values =
263 configured.empty() || configured == standard;
264 return std::pair<std::string, bool>(
265 FormatHexList(configured.empty() ? standard : configured),
266 uses_standard_values);
269 return {summarize(overlay.
track_tiles, DefaultTrackTiles()),
289 return absl::OkStatus();
297 ImGui::TextDisabled(tr(
"Settings not available"));
302 ImGui::SetNextItemWidth(-1.0f);
303 ImGui::InputTextWithHint(
"##SettingsSearch",
307 "input", ImGui::GetItemID());
309 const auto categories =
312 if (categories.empty()) {
314 ImGui::TextDisabled(tr(
"No %s settings match \"%s\"."),
320 const bool wide_layout =
322 if (wide_layout && ImGui::BeginTable(
"##SettingsLayout", 2,
323 ImGuiTableFlags_Resizable |
324 ImGuiTableFlags_BordersInnerV |
325 ImGuiTableFlags_SizingStretchProp)) {
326 ImGui::TableSetupColumn(
"Categories", ImGuiTableColumnFlags_WidthFixed,
327 std::clamp(ImGui::GetContentRegionAvail().x * 0.30f,
330 ImGui::TableSetupColumn(
"Settings", ImGuiTableColumnFlags_WidthStretch);
331 ImGui::TableNextRow();
332 ImGui::TableNextColumn();
334 ImGui::TableNextColumn();
345 if (!ImGui::BeginTabBar(
"##SettingsScopes")) {
348 for (
const Scope scope :
351 const bool tab_open = ImGui::BeginTabItem(ScopeLabel(scope));
353 std::string(
"Settings/tab:") + ScopeLabel(scope),
"tab",
368 const std::vector<CategoryDescriptor>& categories) {
369 if (categories.empty()) {
373 const auto active = std::find_if(categories.begin(), categories.end(),
375 return active_category_ == category.id;
377 if (
active == categories.end()) {
383 const std::vector<CategoryDescriptor>& categories,
bool compact) {
392 const std::string preview =
394 : std::string(
"Select category");
395 ImGui::SetNextItemWidth(-1.0f);
396 if (ImGui::BeginCombo(
"##SettingsCategory", preview.c_str())) {
399 const std::string label =
400 absl::StrFormat(
"%s %s", category.icon, category.label);
401 if (ImGui::Selectable(label.c_str(), selected)) {
405 ImGui::SetItemDefaultFocus();
413 const bool nav_open =
414 ImGui::BeginChild(
"##SettingsCategories", ImVec2(0.0f, 0.0f),
false);
418 const std::string label =
419 absl::StrFormat(
"%s %s", category.icon, category.label);
420 if (ImGui::Selectable(label.c_str(), selected)) {
429 const auto descriptor =
430 std::find_if(kSettingsCategories.begin(), kSettingsCategories.end(),
432 return category_id == category.id;
434 if (descriptor == kSettingsCategories.end()) {
438 const std::string heading =
439 absl::StrFormat(
"%s %s", descriptor->icon, descriptor->label);
440 ImGui::SeparatorText(heading.c_str());
441 if (category_id ==
"appearance") {
443 }
else if (category_id ==
"behavior") {
445 }
else if (category_id ==
"performance") {
447 }
else if (category_id ==
"files") {
449 }
else if (category_id ==
"ai") {
451 }
else if (category_id ==
"shortcuts") {
453 }
else if (category_id ==
"layout") {
455 }
else if (category_id ==
"project") {
457 }
else if (category_id ==
"features") {
459 }
else if (category_id ==
"patches") {
468 ImGui::TextDisabled(tr(
"Feature Flags configuration"));
472 if (ImGui::Checkbox(tr(
"Acknowledge Experimental Editors"),
473 &experiments_enabled)) {
477 if (ImGui::IsItemHovered()) {
479 tr(
"Acknowledges Screen, Music, and Agent editor maturity. Turning "
480 "this off keeps the editors visible but warns and may force a "
481 "backup. Feature-specific write guards remain authoritative."));
483 if (!experiments_enabled) {
486 tr(
"Experimental editors remain available with warnings and defensive "
487 "save safeguards."));
496 if (ImGui::TreeNode(
ICON_MD_MAP " Overworld Flags")) {
519 ImGui::TextDisabled(tr(
"No active project."));
533 const char* roles[] = {
"base",
"dev",
"patched",
"release"};
535 if (ImGui::Combo(tr(
"Role"), &role_index, roles, IM_ARRAYSIZE(roles))) {
540 const char* policies[] = {
"allow",
"warn",
"block",
"read_only"};
542 if (ImGui::Combo(tr(
"Write Policy"), &policy_index, policies,
543 IM_ARRAYSIZE(policies))) {
550 if (ImGui::InputText(tr(
"Expected Hash"), &expected_hash)) {
558 const auto current_hash =
561 ImGui::Text(tr(
"Current ROM Hash: %s"),
562 current_hash.empty() ?
"(unknown)" : current_hash.c_str());
563 const bool use_hash = ImGui::Button(tr(
"Use Current ROM Hash"));
565 "Settings/button:Use Current ROM Hash",
"button", ImGui::GetItemID());
566 if (use_hash && !current_hash.empty()) {
571 ImGui::TextDisabled(tr(
"Current ROM Hash: (no ROM loaded)"));
580 if (ImGui::InputText(tr(
"Output Folder"), &output_folder)) {
587 if (ImGui::InputText(tr(
"Git Repository"), &git_repo)) {
598 if (ImGui::InputText(tr(
"Build Target (ROM)"), &build_target)) {
605 if (ImGui::InputText(tr(
"Symbols File"), &symbols_file)) {
611 ImGui::Text(tr(
"%s ASM / Hack Manifest"),
ICON_MD_CODE);
613 ImGui::TextWrapped(tr(
614 "Optional: load a hack manifest JSON (generated by an ASM project) to "
615 "annotate room tags, show feature flags, and surface which ROM regions "
616 "are owned by ASM vs safe to edit in yaze."));
619 if (ImGui::InputText(tr(
"Hack Manifest File"), &manifest_file)) {
627 ImGui::TextDisabled(manifest_loaded ?
"(loaded)" :
"(not loaded)");
628 if (ImGui::Button(tr(
"Reload Manifest"))) {
632 if (manifest_loaded) {
635 ImGui::Text(tr(
"Manifest Version: %d"),
640 if (!pipeline.dev_rom.empty()) {
641 ImGui::Text(tr(
"Dev ROM: %s"), pipeline.dev_rom.c_str());
643 if (!pipeline.patched_rom.empty()) {
644 ImGui::Text(tr(
"Patched ROM: %s"), pipeline.patched_rom.c_str());
646 if (!pipeline.build_script.empty()) {
647 ImGui::Text(tr(
"Build Script: %s"), pipeline.build_script.c_str());
651 if (msg_layout.first_expanded_id != 0 || msg_layout.last_expanded_id != 0) {
652 ImGui::Text(tr(
"Expanded Messages: 0x%03X-0x%03X (%d)"),
653 msg_layout.first_expanded_id, msg_layout.last_expanded_id,
654 msg_layout.expanded_count);
657 if (ImGui::TreeNode(
ICON_MD_FLAG " Hack Feature Flags")) {
659 ImGui::BulletText(
"%s = %d (%s)", flag.name.c_str(), flag.value,
660 flag.enabled ?
"enabled" :
"disabled");
661 if (!flag.source.empty()) {
663 ImGui::TextDisabled(
"%s", flag.source.c_str());
669 if (ImGui::TreeNode(
ICON_MD_LABEL " Room Tags (Dispatch)")) {
671 ImGui::BulletText(tr(
"0x%02X: %s"), tag.tag_id, tag.name.c_str());
672 if (!tag.enabled && !tag.feature_flag.empty()) {
674 ImGui::TextDisabled(tr(
"(disabled by %s)"), tag.feature_flag.c_str());
676 if (!tag.purpose.empty() && ImGui::IsItemHovered()) {
677 ImGui::SetTooltip(
"%s", tag.purpose.c_str());
689 if (ImGui::InputText(tr(
"Backup Folder"), &backup_folder)) {
707 {.min = 0, .max = 10000})) {
720 {.min = 1, .max = 3650})) {
729 tr(
"Read-only here. Edit collision and object IDs in Minecart Tracks > "
732 constexpr std::array<const char*, 5> kLabels = {
733 "Track Tiles",
"Stop Tiles",
"Switch Tiles",
"Track Object IDs",
734 "Minecart Sprite IDs"};
737 if (ImGui::BeginTable(
"DungeonOverlaySummary", 2,
738 ImGuiTableFlags_SizingStretchProp)) {
739 ImGui::TableSetupColumn(
"Field", ImGuiTableColumnFlags_WidthFixed, 132.0f);
740 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
741 for (
size_t i = 0; i < summary.size(); ++i) {
742 ImGui::TableNextRow();
743 ImGui::TableSetColumnIndex(0);
744 ImGui::TextDisabled(
"%s", tr(kLabels[i]));
745 ImGui::TableSetColumnIndex(1);
746 ImGui::TextWrapped(
"%s", summary[i].first.c_str());
747 ImGui::TextDisabled(
"%s", summary[i].second
748 ? tr(
"Standard values")
749 : tr(
"Custom project values"));
755 const bool custom_objects_enabled =
757 const bool can_open = custom_objects_enabled &&
759 ImGui::BeginDisabled(!can_open);
763 ImGui::EndDisabled();
765 if (!custom_objects_enabled) {
767 tr(
"Enable Custom Dungeon Objects in General Settings first."));
769 ImGui::TextDisabled(tr(
"Minecart Tracks navigation is unavailable."));
785 static int selected_root_index = -1;
786 static std::string new_root_path;
792 ImGui::TextDisabled(tr(
"No project roots configured."));
795 const float roots_height = std::clamp(
796 ImGui::GetContentRegionAvail().y * 0.28f,
gui::ScaledSize(0.0f, 100.0f).y,
798 if (ImGui::BeginChild(
"ProjectRootsList", ImVec2(0, roots_height),
true)) {
799 for (
size_t i = 0; i < roots.size(); ++i) {
800 const bool is_default = roots[i] == prefs.default_project_root;
804 label +=
" (default)";
806 if (ImGui::Selectable(label.c_str(),
807 selected_root_index ==
static_cast<int>(i))) {
808 selected_root_index =
static_cast<int>(i);
814 const bool has_selection =
815 selected_root_index >= 0 &&
816 selected_root_index < static_cast<int>(roots.size());
818 if (ImGui::Button(tr(
"Set Default"))) {
819 prefs.default_project_root = roots[selected_root_index];
824 const std::string removed = roots[selected_root_index];
825 roots.erase(roots.begin() + selected_root_index);
826 if (prefs.default_project_root == removed) {
827 prefs.default_project_root = roots.empty() ?
"" : roots.front();
829 selected_root_index = roots.empty()
831 : std::min(selected_root_index,
832 static_cast<int>(roots.size() - 1));
841 ImGui::InputTextWithHint(
"##project_root_add",
"Add folder path...",
844 const std::string trimmed =
845 std::string(absl::StripAsciiWhitespace(new_root_path));
846 if (!trimmed.empty()) {
847 if (AddUniquePath(&roots, trimmed) &&
848 prefs.default_project_root.empty()) {
849 prefs.default_project_root = trimmed;
857 if (!folder.empty()) {
858 if (AddUniquePath(&roots, folder) && prefs.default_project_root.empty()) {
859 prefs.default_project_root = folder;
872 if (AddUniquePath(&roots, docs_dir->string()) &&
873 prefs.default_project_root.empty()) {
874 prefs.default_project_root = docs_dir->string();
883 if (icloud_dir.ok()) {
884 if (AddUniquePath(&roots, icloud_dir->string())) {
885 prefs.default_project_root = icloud_dir->string();
891 tr(
"iCloud projects live in Documents/Yaze/iCloud on this Mac."));
897 bool use_icloud_sync = prefs.use_icloud_sync;
898 if (ImGui::Checkbox(tr(
"Use iCloud sync (Documents)"), &use_icloud_sync)) {
899 prefs.use_icloud_sync = use_icloud_sync;
900 if (use_icloud_sync) {
903 if (icloud_dir.ok()) {
904 AddUniquePath(&roots, icloud_dir->string());
905 prefs.default_project_root = icloud_dir->string();
911 bool use_files_app = prefs.use_files_app;
912 if (ImGui::Checkbox(tr(
"Prefer Files app on iOS"), &use_files_app)) {
913 prefs.use_files_app = use_files_app;
920 auto theme = theme_manager.GetCurrentTheme();
921 theme.ApplyDensityPreset(preset);
924 theme_manager.ReapplyTheme(theme);
933 const std::string current = theme_manager.GetCurrentThemeName();
934 const auto& current_theme = theme_manager.GetCurrentTheme();
936 ImGui::SetNextItemWidth(-1.0f);
937 if (ImGui::BeginCombo(
"##AppearanceTheme", current.c_str())) {
938 for (
const auto& theme_name : theme_manager.GetAvailableThemes()) {
939 const bool is_current = theme_name == current;
940 if (ImGui::Selectable(theme_name.c_str(), is_current)) {
941 if (theme_manager.IsPreviewActive()) {
942 theme_manager.EndPreview();
944 theme_manager.ApplyTheme(theme_name);
947 ImGui::SetItemDefaultFocus();
953 const ImGuiColorEditFlags swatch_flags = ImGuiColorEditFlags_NoTooltip |
954 ImGuiColorEditFlags_NoDragDrop |
955 ImGuiColorEditFlags_NoPicker;
956 const float swatch_size = std::max(12.0f, ImGui::GetFrameHeight() * 0.62f);
957 ImGui::TextDisabled(
"%s", tr(
"Palette"));
958 ImGui::SameLine(0.0f, 6.0f);
959 ImGui::PushID(
"CurrentThemeSwatches");
960 ImGui::ColorButton(
"Primary",
962 swatch_flags, ImVec2(swatch_size, swatch_size));
963 ImGui::SameLine(0.0f, 3.0f);
964 ImGui::ColorButton(
"Surface",
966 swatch_flags, ImVec2(swatch_size, swatch_size));
967 ImGui::SameLine(0.0f, 3.0f);
969 swatch_flags, ImVec2(swatch_size, swatch_size));
971 if (!current_theme.description.empty()) {
972 ImGui::PushTextWrapPos(0.0f);
973 ImGui::TextDisabled(
"%s", current_theme.description.c_str());
974 ImGui::PopTextWrapPos();
978 theme_manager.RefreshAvailableThemes();
980 if (ImGui::IsItemHovered()) {
981 ImGui::SetTooltip(tr(
"Re-scan theme folders for new or changed themes"));
985 ImGui::SeparatorText(tr(
"Density"));
988 auto preset = theme_manager.GetCurrentTheme().density_preset;
989 int density =
static_cast<int>(preset);
990 const char* density_labels[] = {tr(
"Compact"), tr(
"Normal"),
992 ImGui::SetNextItemWidth(-1.0f);
993 if (ImGui::Combo(
"##DisplayDensity", &density, density_labels,
994 IM_ARRAYSIZE(density_labels))) {
998 "%s", density == 0 ? tr(
"Tighter controls and more visible content")
999 : density == 2 ? tr(
"Larger controls with more breathing room")
1000 : tr(
"Balanced spacing for everyday editing"));
1004 ImGui::SeparatorText(tr(
"Motion"));
1008 if (ImGui::Checkbox(tr(
"Reduced Motion"), &reduced_motion)) {
1009 prefs.reduced_motion = reduced_motion;
1011 prefs.reduced_motion,
1015 if (ImGui::IsItemHovered()) {
1017 tr(
"Disable panel/editor transition animations for a calmer editing "
1021 int switch_profile = std::clamp(prefs.switch_motion_profile, 0, 2);
1022 const char* switch_profile_labels[] = {
"Snappy",
"Standard",
"Relaxed"};
1023 if (ImGui::Combo(tr(
"Switch Motion Profile"), &switch_profile,
1024 switch_profile_labels,
1025 IM_ARRAYSIZE(switch_profile_labels))) {
1026 prefs.switch_motion_profile = switch_profile;
1028 prefs.reduced_motion,
1032 if (ImGui::IsItemHovered()) {
1034 tr(
"Controls editor/workspace switch timing and easing for panel fades "
1035 "and sidebar slides."));
1050 ImGui::Text(tr(
"Global Font Scale"));
1052 if (ImGui::SliderFloat(
"##global_font_scale", &scale, 0.5f, 2.0f,
"%.2f")) {
1054 ImGui::GetIO().FontGlobalScale = scale;
1064 if (ImGui::Checkbox(tr(
"Show Status Bar"), &show_status_bar)) {
1072 if (ImGui::IsItemHovered()) {
1074 tr(
"Display ROM, session, cursor, and zoom info at bottom of window"));
1083 if (ImGui::Checkbox(tr(
"Keep Emulator Running in Background"),
1088 if (ImGui::IsItemHovered()) {
1089 ImGui::SetTooltip(tr(
1090 "When off (default), hiding emulator panels pauses the SNES tick and "
1091 "audio. Music playback still drives its own frames."));
1097 ImGui::TextDisabled(tr(
"UserSettings unavailable."));
1106 if (prefs.named_layouts.empty()) {
1107 ImGui::TextDisabled(
1108 tr(
"No saved layouts yet. Open the Layout Designer to capture one."));
1112 std::vector<std::string> names;
1113 names.reserve(prefs.named_layouts.size());
1114 for (
const auto& entry : prefs.named_layouts) {
1115 names.push_back(entry.first);
1117 std::sort(names.begin(), names.end());
1119 const std::string&
active = prefs.last_applied_layout_name;
1120 const char* preview =
1121 active.empty() ?
"(select a layout…)" :
active.c_str();
1123 if (ImGui::BeginCombo(tr(
"Active Layout"), preview)) {
1124 for (
const auto& name : names) {
1125 const bool is_selected = (name ==
active);
1126 if (ImGui::Selectable(name.c_str(), is_selected)) {
1135 ImGui::SetItemDefaultFocus();
1142 ImGui::BeginDisabled(
active.empty() ||
1143 prefs.named_layouts.count(
active) == 0);
1144 if (ImGui::SmallButton(tr(
"Re-apply"))) {
1147 ImGui::EndDisabled();
1148 if (ImGui::IsItemHovered()) {
1150 tr(
"Re-apply the active layout (useful after closing or rearranging "
1151 "panels manually)."));
1167 "Open failed: \"layout.designer\" panel is not registered.";
1175 if (ImGui::IsItemHovered()) {
1177 tr(
"Open the Layout Designer panel to author or capture a workspace "
1202 if (it == prefs.named_layouts.end()) {
1204 absl::StrCat(
"Apply failed: \"", name,
"\" not found.");
1209 nlohmann::json parsed;
1211 parsed = nlohmann::json::parse(it->second);
1212 }
catch (
const nlohmann::json::parse_error& e) {
1214 absl::StrCat(
"Apply failed: invalid JSON (", e.what(),
").");
1220 if (!tree_or.ok()) {
1222 absl::StrCat(
"Apply failed: ", tree_or.status().message());
1227 std::string validation_error;
1228 if (!tree_or->Validate(&validation_error)) {
1230 absl::StrCat(
"Apply failed: invalid layout (", validation_error,
").");
1236 if (manager ==
nullptr) {
1242 if (dockspace_id == 0) {
1248 const absl::Status apply_status =
1250 if (!apply_status.ok()) {
1252 absl::StrCat(
"Apply failed: ", apply_status.message());
1257 prefs.last_applied_layout_name = name;
1270 ImGui::TextDisabled(
1271 "%s", tr(
"These apply to the open project and save with it."));
1274 if (ImGui::Checkbox(tr(
"Enable Auto-Save"),
1282 if (ImGui::SliderInt(tr(
"Interval (sec)"), &interval, 60, 600)) {
1290 if (ImGui::Checkbox(tr(
"Backup Before Save"),
1309 const char* editors[] = {
"None",
"Overworld",
"Dungeon",
"Graphics",
1312 editors, IM_ARRAYSIZE(editors))) {
1315 ImGui::TextDisabled(
1316 "%s", tr(
"Opens after a ROM or project loads. None shows the picker."));
1321 if (ImGui::Checkbox(tr(
"Use HMagic sprite names (expanded)"),
1334 if (ImGui::IsItemHovered()) {
1336 tr(
"From the next launch: no welcome screen, no editor picker, and "
1337 "the local test server starts on port 50052 (builds with gRPC)."));
1343 if (ImGui::Checkbox(tr(
"Show feature tips (ZScream migration, etc.)"),
1347 ImGui::TextDisabled(tr(
"%zu tip(s) already dismissed"),
1349 if (ImGui::Button(tr(
"Reset dismissed tips"))) {
1375 if (ImGui::SliderInt(tr(
"Cache Size (MB)"),
1380 if (ImGui::SliderInt(tr(
"Undo History"),
1387 ImGui::Text(tr(
"Current FPS: %.1f"), ImGui::GetIO().Framerate);
1388 ImGui::Text(tr(
"Frame Time: %.3f ms"), 1000.0f / ImGui::GetIO().Framerate);
1397 static int selected_host_index = -1;
1399 auto draw_key_row = [&](
const char* label, std::string* key,
1400 const char* env_var,
const char* id) {
1402 ImGui::Text(
"%s", label);
1403 const ImVec2 button_size = ImGui::CalcTextSize(
ICON_MD_SYNC " Env");
1404 float env_button_width =
1405 button_size.x + ImGui::GetStyle().FramePadding.x * 2.0f;
1406 float input_width = ImGui::GetContentRegionAvail().x - env_button_width -
1407 ImGui::GetStyle().ItemSpacing.x;
1408 bool stack = input_width < 160.0f;
1409 ImGui::SetNextItemWidth(stack ? -1.0f : input_width);
1410 if (ImGui::InputTextWithHint(
"##key",
"API key...", key,
1411 ImGuiInputTextFlags_Password)) {
1418 const char* env_key = std::getenv(env_var);
1430 draw_key_row(
"OpenAI", &prefs.openai_api_key,
"OPENAI_API_KEY",
"openai_key");
1431 draw_key_row(
"Anthropic", &prefs.anthropic_api_key,
"ANTHROPIC_API_KEY",
1433 draw_key_row(
"Google (Gemini)", &prefs.gemini_api_key,
"GEMINI_API_KEY",
1438 ImGui::Text(tr(
"%s Provider Defaults (legacy)"),
ICON_MD_CLOUD);
1441 const char* providers[] = {
"Ollama (Local)",
"Gemini (Cloud)",
1443 if (ImGui::Combo(
"##Provider", &prefs.ai_provider, providers,
1444 IM_ARRAYSIZE(providers))) {
1452 const char* active_preview =
"None";
1453 const char* remote_preview =
"None";
1454 for (
const auto& host : hosts) {
1455 if (!prefs.active_ai_host_id.empty() &&
1456 host.id == prefs.active_ai_host_id) {
1457 active_preview = host.label.c_str();
1459 if (!prefs.remote_build_host_id.empty() &&
1460 host.id == prefs.remote_build_host_id) {
1461 remote_preview = host.label.c_str();
1465 if (ImGui::BeginCombo(tr(
"Active Host"), active_preview)) {
1466 for (
size_t i = 0; i < hosts.size(); ++i) {
1467 const bool is_selected = (!prefs.active_ai_host_id.empty() &&
1468 hosts[i].id == prefs.active_ai_host_id);
1469 if (ImGui::Selectable(hosts[i].label.c_str(), is_selected)) {
1470 prefs.active_ai_host_id = hosts[i].id;
1471 if (prefs.remote_build_host_id.empty()) {
1472 prefs.remote_build_host_id = hosts[i].id;
1477 ImGui::SetItemDefaultFocus();
1483 if (ImGui::BeginCombo(tr(
"Remote Build Host"), remote_preview)) {
1484 for (
size_t i = 0; i < hosts.size(); ++i) {
1485 const bool is_selected = (!prefs.remote_build_host_id.empty() &&
1486 hosts[i].id == prefs.remote_build_host_id);
1487 if (ImGui::Selectable(hosts[i].label.c_str(), is_selected)) {
1488 prefs.remote_build_host_id = hosts[i].id;
1492 ImGui::SetItemDefaultFocus();
1502 if (selected_host_index >=
static_cast<int>(hosts.size())) {
1503 selected_host_index = hosts.empty() ? -1 : 0;
1505 if (selected_host_index < 0 && !hosts.empty()) {
1506 for (
size_t i = 0; i < hosts.size(); ++i) {
1507 if (!prefs.active_ai_host_id.empty() &&
1508 hosts[i].id == prefs.active_ai_host_id) {
1509 selected_host_index =
static_cast<int>(i);
1513 if (selected_host_index < 0) {
1514 selected_host_index = 0;
1518 const float hosts_height = std::clamp(
1519 ImGui::GetContentRegionAvail().y * 0.32f,
gui::ScaledSize(0.0f, 120.0f).y,
1521 ImGui::BeginChild(
"##ai_host_list", ImVec2(0, hosts_height),
true);
1522 for (
size_t i = 0; i < hosts.size(); ++i) {
1523 const bool is_selected =
static_cast<int>(i) == selected_host_index;
1524 std::string label = hosts[i].label;
1525 if (hosts[i].
id == prefs.active_ai_host_id) {
1526 label +=
" (active)";
1528 if (hosts[i].
id == prefs.remote_build_host_id) {
1529 label +=
" (build)";
1531 if (ImGui::Selectable(label.c_str(), is_selected)) {
1532 selected_host_index =
static_cast<int>(i);
1534 std::string tags = BuildHostTagString(hosts[i]);
1535 if (!tags.empty()) {
1537 ImGui::TextDisabled(
"%s", tags.c_str());
1543 if (host.id.empty()) {
1544 host.id = absl::StrFormat(
"host-%zu", hosts.size() + 1);
1546 hosts.push_back(host);
1547 selected_host_index =
static_cast<int>(hosts.size() - 1);
1548 if (prefs.active_ai_host_id.empty()) {
1549 prefs.active_ai_host_id = host.id;
1551 if (prefs.remote_build_host_id.empty()) {
1552 prefs.remote_build_host_id = host.id;
1559 host.
label =
"New Host";
1560 host.
base_url =
"http://localhost:1234";
1565 if (ImGui::Button(
ICON_MD_DELETE " Remove") && selected_host_index >= 0 &&
1566 selected_host_index <
static_cast<int>(hosts.size())) {
1567 const std::string removed_id = hosts[selected_host_index].id;
1568 hosts.erase(hosts.begin() + selected_host_index);
1569 if (prefs.active_ai_host_id == removed_id) {
1570 prefs.active_ai_host_id = hosts.empty() ?
"" : hosts.front().id;
1572 if (prefs.remote_build_host_id == removed_id) {
1573 prefs.remote_build_host_id = prefs.active_ai_host_id;
1575 selected_host_index =
1578 : std::min(selected_host_index,
static_cast<int>(hosts.size() - 1));
1583 if (ImGui::Button(tr(
"Add LM Studio"))) {
1585 host.
label =
"LM Studio (local)";
1586 host.
base_url =
"http://localhost:1234";
1593 if (ImGui::Button(tr(
"Add AFS Bridge"))) {
1595 host.
label =
"halext AFS Bridge";
1596 host.
base_url =
"https://halext.org";
1603 if (ImGui::Button(tr(
"Add Ollama"))) {
1605 host.
label =
"Ollama (local)";
1606 host.
base_url =
"http://localhost:11434";
1613 static std::string tailscale_host;
1614 ImGui::InputTextWithHint(
"##tailscale_host",
"host.ts.net:1234",
1617 if (ImGui::Button(tr(
"Add Tailscale Host"))) {
1618 std::string trimmed =
1619 std::string(absl::StripAsciiWhitespace(tailscale_host));
1620 if (!trimmed.empty()) {
1622 host.
label =
"Tailscale Host";
1623 if (absl::StrContains(trimmed,
"://")) {
1626 host.
base_url =
"http://" + trimmed;
1633 tailscale_host.clear();
1637 if (selected_host_index >= 0 &&
1638 selected_host_index <
static_cast<int>(hosts.size())) {
1639 auto& host = hosts[
static_cast<size_t>(selected_host_index)];
1641 ImGui::Text(tr(
"Host Details"));
1643 if (ImGui::InputText(tr(
"Label"), &host.label)) {
1646 if (ImGui::InputText(tr(
"Base URL"), &host.base_url)) {
1650 const char* api_types[] = {
"openai",
"ollama",
"gemini",
1651 "anthropic",
"lmstudio",
"grpc"};
1653 for (
int i = 0; i < IM_ARRAYSIZE(api_types); ++i) {
1654 if (host.api_type == api_types[i]) {
1659 if (ImGui::Combo(tr(
"API Type"), &api_index, api_types,
1660 IM_ARRAYSIZE(api_types))) {
1661 host.api_type = api_types[api_index];
1665 if (ImGui::InputText(tr(
"API Key"), &host.api_key,
1666 ImGuiInputTextFlags_Password)) {
1669 if (ImGui::InputText(tr(
"Keychain ID"), &host.credential_id)) {
1673 if (ImGui::SmallButton(tr(
"Use Host ID"))) {
1674 host.credential_id = host.id;
1677 if (!host.credential_id.empty() && host.api_key.empty()) {
1678 ImGui::TextDisabled(tr(
"Keychain lookup enabled (leave API key empty)."));
1681 if (ImGui::Checkbox(tr(
"Supports Vision"), &host.supports_vision)) {
1685 if (ImGui::Checkbox(tr(
"Supports Tools"), &host.supports_tools)) {
1689 if (ImGui::Checkbox(tr(
"Supports Streaming"), &host.supports_streaming)) {
1692 if (ImGui::Checkbox(tr(
"Allow Insecure HTTP"), &host.allow_insecure)) {
1701 auto& model_paths = prefs.ai_model_paths;
1702 static int selected_model_path = -1;
1703 static std::string new_model_path;
1705 if (model_paths.empty()) {
1706 ImGui::TextDisabled(tr(
"No model paths configured."));
1709 const float model_paths_height = std::clamp(
1710 ImGui::GetContentRegionAvail().y * 0.24f,
gui::ScaledSize(0.0f, 96.0f).y,
1712 if (ImGui::BeginChild(
"ModelPathsList", ImVec2(0, model_paths_height),
1714 for (
size_t i = 0; i < model_paths.size(); ++i) {
1717 if (ImGui::Selectable(label.c_str(),
1718 selected_model_path ==
static_cast<int>(i))) {
1719 selected_model_path =
static_cast<int>(i);
1725 const bool has_model_selection =
1726 selected_model_path >= 0 &&
1727 selected_model_path < static_cast<int>(model_paths.size());
1728 if (has_model_selection) {
1730 model_paths.erase(model_paths.begin() + selected_model_path);
1731 selected_model_path =
1734 : std::min(selected_model_path,
1735 static_cast<int>(model_paths.size() - 1));
1741 ImGui::InputTextWithHint(
"##model_path_add",
"Add folder path...",
1744 const std::string trimmed =
1745 std::string(absl::StripAsciiWhitespace(new_model_path));
1746 if (!trimmed.empty() && AddUniquePath(&model_paths, trimmed)) {
1748 new_model_path.clear();
1754 if (!folder.empty() && AddUniquePath(&model_paths, folder)) {
1764 if (!home_dir.empty() && home_dir !=
".") {
1765 if (AddUniquePath(&model_paths, (home_dir /
"models").
string())) {
1771 if (ImGui::Button(tr(
"Add ~/.lmstudio/models"))) {
1772 if (!home_dir.empty() && home_dir !=
".") {
1773 if (AddUniquePath(&model_paths,
1774 (home_dir /
".lmstudio" /
"models").
string())) {
1780 if (ImGui::Button(tr(
"Add ~/.ollama/models"))) {
1781 if (!home_dir.empty() && home_dir !=
".") {
1782 if (AddUniquePath(&model_paths,
1783 (home_dir /
".ollama" /
"models").
string())) {
1793 if (ImGui::SliderFloat(tr(
"Temperature"),
1797 ImGui::TextDisabled(tr(
"Higher = more creative"));
1808 if (ImGui::Checkbox(tr(
"Proactive Suggestions"),
1813 if (ImGui::Checkbox(tr(
"Auto-Learn Preferences"),
1818 if (ImGui::Checkbox(tr(
"Enable Vision"),
1827 const char* log_levels[] = {
"Debug",
"Info",
"Warning",
"Error",
"Fatal"};
1829 log_levels, IM_ARRAYSIZE(log_levels))) {
1837 ImGuiTreeNodeFlags_DefaultOpen)) {
1838 ImGui::InputTextWithHint(
"##shortcut_filter",
"Filter shortcuts...",
1840 if (ImGui::IsItemHovered()) {
1841 ImGui::SetTooltip(tr(
"Filter by action name or key combo"));
1845 if (ImGui::TreeNode(
"Global Shortcuts")) {
1849 if (ImGui::TreeNode(
"Editor Shortcuts")) {
1853 if (ImGui::TreeNode(
"Panel Shortcuts")) {
1857 ImGui::TextDisabled(
1858 tr(
"Tip: Use Cmd/Opt labels on macOS or Ctrl/Alt on Windows/Linux. "
1859 "Function keys and symbols (/, -) are supported."));
1868 std::string haystack = absl::AsciiStrToLower(text);
1870 return absl::StrContains(haystack, needle);
1882 std::unordered_map<std::string, std::string>& overrides) {
1883 ImGui::PushID(sc.
name.c_str());
1884 ImGui::Text(
"%s", sc.
name.c_str());
1886 ImGui::SetNextItemWidth(std::clamp(ImGui::GetContentRegionAvail().x * 0.45f,
1889 if (ImGui::InputText(
"##binding", &value,
1890 ImGuiInputTextFlags_EnterReturnsTrue |
1891 ImGuiInputTextFlags_AutoSelectAll)) {
1893 if (!parsed.empty() || value.empty()) {
1896 overrides[sc.
name] = value;
1898 user_settings->
Save();
1905 if (!conflicts.empty()) {
1908 if (ImGui::IsItemHovered()) {
1909 std::string tip =
"Also bound to:";
1910 for (
const auto& other : conflicts) {
1911 tip +=
"\n " + other;
1913 ImGui::SetTooltip(
"%s", tip.c_str());
1918 if (ImGui::SmallButton(tr(
"Reset"))) {
1920 overrides.erase(sc.
name);
1922 user_settings->
Save();
1924 if (ImGui::IsItemHovered()) {
1925 ImGui::SetTooltip(
"Default: %s",
1939 ImGui::TextDisabled(tr(
"Not available"));
1945 if (shortcuts.empty()) {
1946 ImGui::TextDisabled(tr(
"No global shortcuts registered."));
1950 shortcuts.begin(), shortcuts.end(),
1953 static std::unordered_map<std::string, std::string> editing;
1955 bool has_match =
false;
1956 for (
const auto& sc : shortcuts) {
1957 if (sc.keys.empty() && sc.default_keys.empty()) {
1965 auto it = editing.find(sc.name);
1966 if (it == editing.end()) {
1967 it = editing.emplace(sc.name, keys).first;
1973 ImGui::TextDisabled(tr(
"No shortcuts match the current filter."));
1979 ImGui::TextDisabled(tr(
"Not available"));
1985 std::map<std::string, std::vector<Shortcut>> grouped;
1986 static std::unordered_map<std::string, std::string> editing;
1988 for (
const auto& sc : shortcuts) {
1989 if (sc.keys.empty() && sc.default_keys.empty()) {
1992 auto pos = sc.name.find(
".");
1994 pos != std::string::npos ? sc.name.substr(0, pos) :
"general";
1995 grouped[group].push_back(sc);
1997 bool has_match =
false;
1998 for (
auto& [group, list] : grouped) {
2000 list.begin(), list.end(),
2002 std::vector<Shortcut> filtered;
2003 filtered.reserve(list.size());
2004 for (
const auto& sc : list) {
2007 filtered.push_back(sc);
2010 if (filtered.empty()) {
2014 if (ImGui::TreeNode(group.c_str())) {
2015 for (
const auto& sc : filtered) {
2016 auto it = editing.find(sc.name);
2017 if (it == editing.end()) {
2018 it = editing.emplace(sc.name,
PrintShortcut(sc.keys)).first;
2028 ImGui::TextDisabled(tr(
"No shortcuts match the current filter."));
2034 ImGui::TextDisabled(tr(
"Registry not available"));
2041 bool has_match =
false;
2042 for (
const auto& category : categories) {
2044 std::vector<
decltype(cards)::value_type> filtered_cards;
2045 filtered_cards.reserve(cards.size());
2046 for (
const auto& card : cards) {
2049 filtered_cards.push_back(card);
2052 if (filtered_cards.empty()) {
2056 if (ImGui::TreeNode(category.c_str())) {
2058 for (
const auto& card : filtered_cards) {
2059 ImGui::PushID(card.card_id.c_str());
2061 ImGui::Text(
"%s %s", card.icon.c_str(), card.display_name.c_str());
2063 std::string current_shortcut;
2066 current_shortcut = it->second;
2067 }
else if (!card.shortcut_hint.empty()) {
2068 current_shortcut = card.shortcut_hint;
2070 current_shortcut =
"None";
2074 std::string display_shortcut = current_shortcut;
2076 if (!parsed.empty()) {
2081 ImGui::SetNextItemWidth(
2082 std::clamp(ImGui::GetContentRegionAvail().x * 0.40f,
2085 ImGui::SetKeyboardFocusHere();
2088 ImGuiInputTextFlags_EnterReturnsTrue)) {
2105 if (ImGui::Button(display_shortcut.c_str(), ImVec2(120, 0))) {
2111 if (ImGui::IsItemHovered()) {
2112 ImGui::SetTooltip(tr(
"Click to edit shortcut"));
2123 ImGui::TextDisabled(tr(
"No shortcuts match the current filter."));
2132 if (patches_dir_status.ok()) {
2147 ImGui::TextDisabled(tr(
"No patches loaded"));
2148 ImGui::TextDisabled(tr(
"Place .asm patches in assets/patches/"));
2150 if (ImGui::Button(tr(
"Browse for Patches Folder..."))) {
2159 ImGui::Text(tr(
"Loaded: %d patches (%d enabled)"), total_count,
2166 ImGuiTabBarFlags_FittingPolicyScroll)) {
2168 if (ImGui::BeginTabItem(folder.c_str())) {
2171 ImGui::EndTabItem();
2184 ImGui::TextDisabled(tr(
"Select a patch to view details"));
2193#ifdef YAZE_WITH_Z3DK
2211 for (
const auto& range :
z3dk.prohibited_memory_ranges) {
2213 {.start = range.start, .end = range.end, .reason = range.reason});
2221 if (!
z3dk.rom_path.empty()) {
2232 LOG_ERROR(
"Settings",
"Failed to apply patches: %s", status.message());
2234 LOG_INFO(
"Settings",
"Applied %d patches successfully", enabled_count);
2237 LOG_WARN(
"Settings",
"No ROM loaded");
2240 if (ImGui::IsItemHovered()) {
2241 ImGui::SetTooltip(tr(
"Apply all enabled patches to the loaded ROM"));
2248 LOG_ERROR(
"Settings",
"Failed to save patches: %s", status.message());
2261 if (patches.empty()) {
2262 ImGui::TextDisabled(tr(
"No patches in this folder"));
2267 float available_height = std::min(200.0f, patches.size() * 25.0f + 10.0f);
2268 if (ImGui::BeginChild(
"##PatchList", ImVec2(0, available_height),
true)) {
2269 for (
auto* patch : patches) {
2270 ImGui::PushID(patch->filename().c_str());
2272 bool enabled = patch->enabled();
2273 if (ImGui::Checkbox(
"##Enabled", &enabled)) {
2274 patch->set_enabled(enabled);
2281 if (ImGui::Selectable(patch->name().c_str(), is_selected)) {
2314 if (!params.empty()) {
2319 for (
auto& param : params) {
2331 switch (param->
type) {
2335 int value = param->
value;
2336 const char* format = param->
use_decimal ?
"%d" :
"$%X";
2339 ImGui::SetNextItemWidth(std::clamp(
2340 ImGui::GetContentRegionAvail().x * 0.50f,
2342 if (ImGui::InputInt(
"##Value", &value, 1, 16)) {
2356 if (ImGui::Checkbox(param->
display_name.c_str(), &checked)) {
2364 for (
size_t i = 0; i < param->
choices.size(); ++i) {
2365 bool selected = (param->
value ==
static_cast<int>(i));
2366 if (ImGui::RadioButton(param->
choices[i].c_str(), selected)) {
2367 param->
value =
static_cast<int>(i);
2375 for (
size_t i = 0; i < param->
choices.size(); ++i) {
2376 if (param->
choices[i].empty() || param->
choices[i] ==
"_EMPTY") {
2379 bool bit_set = (param->
value & (1 << i)) != 0;
2380 if (ImGui::Checkbox(param->
choices[i].c_str(), &bit_set)) {
2382 param->
value |= (1 << i);
2384 param->
value &= ~(1 << i);
2394 ImGui::SetNextItemWidth(-1.0f);
2395 if (ImGui::InputInt(tr(
"Item ID"), ¶m->
value)) {
2396 param->
value = std::clamp(param->
value, 0, 255);
const std::string & version() const
std::vector< PatchParameter > & mutable_parameters()
const std::string & author() const
const std::string & description() const
const std::string & name() const
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.
int manifest_version() const
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)
Manages ImGui DockBuilder layouts for each editor type.
absl::Status ApplyDockTree(const layout_designer::DockTree &tree, ImGuiID dockspace_id)
Apply a DockTree to the given dockspace.
ImGuiID GetMainDockspaceId() const
Get the cached main dockspace ID.
void DrawPerformanceSettings()
std::string editing_card_id_
void SetStatusBar(StatusBar *bar)
std::function< project::YazeProject *()> project_provider_
void DrawProjectSettings()
void DrawPatchList(const std::string &folder)
void DrawFilesystemSettings()
void SetWindowManager(WorkspaceWindowManager *registry)
std::array< char, 128 > settings_search_
void DrawAppearanceSettings()
std::string active_category_
void DrawCategoryNavigation(const std::vector< CategoryDescriptor > &categories, bool compact)
char shortcut_edit_buffer_[64]
ShortcutManager * shortcut_manager_
void EnsureActiveCategory(const std::vector< CategoryDescriptor > &categories)
std::function< std::string()> rom_hash_provider_
void SetDependencies(const EditorDependencies &deps) override
void DrawEditorShortcuts()
void DrawGlobalShortcuts()
void ApplyNamedLayoutToDockspace(const std::string &name)
bool MatchesShortcutFilter(const std::string &text) const
void DrawParameterWidget(core::PatchParameter *param)
void DrawEditorBehavior()
static DungeonOverlaySummary BuildDungeonOverlaySummary(const project::DungeonOverlaySettings &overlay)
core::AsmPatch * selected_patch_
void DrawGeneralSettings()
void DrawWorkspaceSettings()
core::PatchManager patch_manager_
OpenMinecartTracksCallback open_minecart_tracks_callback_
project::YazeProject * project_
void SetShortcutManager(ShortcutManager *manager)
void DrawCategoryContent(const std::string &category_id)
void SetUserSettings(UserSettings *settings)
std::string selected_folder_
UserSettings * user_settings_
void DrawKeyboardShortcuts()
bool is_editing_shortcut_
std::array< std::pair< std::string, bool >, 5 > DungeonOverlaySummary
std::string workspace_status_message_
std::string shortcut_filter_
static std::vector< CategoryDescriptor > FilterCategories(Scope scope, const std::string &query)
void SetProject(project::YazeProject *project)
void ApplyDisplayDensity(gui::DensityPreset preset)
absl::Status RequestOpenMinecartTracks()
WorkspaceWindowManager * window_manager_
bool workspace_status_is_error_
void DrawPanelShortcuts()
void DrawAIAgentSettings()
std::string project_status_message_
const Shortcut * FindShortcut(const std::string &name) const
std::vector< Shortcut > GetShortcutsByScope(Shortcut::Scope scope) const
bool ResetShortcutKeys(const std::string &name)
Restore the keys the shortcut was registered with.
std::vector< std::string > FindConflicts(const std::string &name) const
Names of other shortcuts bound to the same chord that can be active at the same time (same editor own...
bool UpdateShortcutKeys(const std::string &name, const std::vector< ImGuiKey > &keys)
void SetEnabled(bool enabled)
Enable or disable the status bar.
Manages user preferences and settings persistence.
bool HasWorkspaceOverrides() const
std::vector< WindowDescriptor > GetWindowsInCategory(size_t session_id, const std::string &category) const
void SetWindowPinned(size_t session_id, const std::string &base_window_id, bool pinned)
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
std::vector< std::string > GetAllCategories(size_t session_id) const
static MotionProfile ClampMotionProfile(int raw_profile)
void SetMotionPreferences(bool reduced_motion, MotionProfile profile)
static ThemeManager & Get()
static std::string ShowOpenFolderDialog()
ShowOpenFolderDialog opens a file dialog and returns the selected folder path. Uses global feature fl...
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_FOLDER_SPECIAL
#define ICON_MD_LIGHTBULB
#define ICON_MD_TEXT_FIELDS
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_EXTENSION
#define ICON_MD_DASHBOARD_CUSTOMIZE
#define ICON_MD_PSYCHOLOGY
#define ICON_MD_HORIZONTAL_RULE
#define ICON_MD_DASHBOARD
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
constexpr char kProviderOpenAi[]
constexpr char kProviderOllama[]
constexpr char kProviderLmStudio[]
LayoutManager * layout_manager()
Get the shared LayoutManager instance.
std::vector< uint16_t > DefaultTrackTiles()
std::string ExpandLeadingTilde(const std::string &path)
bool AddUniquePath(std::vector< std::string > *paths, const std::string &path)
std::vector< uint16_t > DefaultSwitchTiles()
std::vector< uint16_t > DefaultStopTiles()
void DrawShortcutBindingRow(const Shortcut &sc, std::string &value, ShortcutManager *shortcut_manager, UserSettings *user_settings, std::unordered_map< std::string, std::string > &overrides)
constexpr std::array< SettingsPanel::CategoryDescriptor, 10 > kSettingsCategories
std::string BuildHostTagString(const UserSettings::Preferences::AiHost &host)
const char * ScopeLabel(SettingsPanel::Scope scope)
std::vector< uint16_t > DefaultMinecartSpriteIds()
std::vector< uint16_t > DefaultTrackObjectIds()
absl::StatusOr< DockTree > DockTreeFromJson(const nlohmann::json &j)
std::vector< ImGuiKey > ParseShortcut(const std::string &shortcut)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
bool DrawProperty(const char *label, bool *value, const PropertyOptions &opts)
ImVec4 ConvertColorToImVec4(const Color &color)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
ImVec2 ScaledSize(float base_width, float base_height)
Returns a DPI-scaled version of a fixed pixel size.
DensityPreset
Typography and spacing density presets.
bool FontPicker(const char *label, int *index)
std::string ComputeRomHash(const uint8_t *data, size_t size)
void SetPreferHmagicSpriteNames(bool prefer)
void SetActiveFontIndex(int index)
bool kEnableCustomObjects
Represents a configurable parameter within an ASM patch.
std::vector< std::string > choices
bool warn_branch_outside_bank
std::vector< Z3dkMemoryRange > prohibited_memory_ranges
bool capture_nocash_symbols
std::vector< std::string > include_paths
std::vector< std::pair< std::string, std::string > > defines
std::string std_defines_path
bool warn_unauthorized_hook
std::string std_includes_path
std::string hooks_rom_path
Unified dependency container for all editor types.
project::YazeProject * project
ShortcutManager * shortcut_manager
UserSettings * user_settings
WorkspaceWindowManager * window_manager
std::vector< ImGuiKey > keys
std::vector< ImGuiKey > default_keys
std::vector< std::string > project_root_paths
std::unordered_map< std::string, std::string > panel_shortcuts
bool show_experimental_editors
bool feature_hints_enabled
std::unordered_map< std::string, std::string > named_layouts
std::unordered_map< std::string, std::string > editor_shortcuts
std::vector< AiHost > ai_hosts
std::unordered_map< std::string, std::string > global_shortcuts
bool emulator_keep_running_in_background
std::unordered_set< std::string > dismissed_feature_hints
bool prefer_hmagic_sprite_names
Dungeon overlay configuration (per-project).
std::vector< uint16_t > track_object_ids
std::vector< uint16_t > minecart_sprite_ids
std::vector< uint16_t > track_stop_tiles
std::vector< uint16_t > track_tiles
std::vector< uint16_t > track_switch_tiles
int backup_keep_daily_days
int backup_retention_count
std::string rom_backup_folder
std::string git_repository
core::HackManifest hack_manifest
void ReloadHackManifest()
std::string hack_manifest_file
WorkspaceSettings workspace_settings
std::string output_folder
DungeonOverlaySettings dungeon_overlay
std::string symbols_filename
Z3dkSettings z3dk_settings