13#include "absl/strings/str_format.h"
14#include "absl/types/span.h"
36#include "imgui/imgui.h"
37#include "imgui/imgui_internal.h"
47std::string ResolveAgentChatHistoryPath() {
50 return (*agent_dir /
"agent_chat_history.json").string();
54 return (*temp_dir /
"agent_chat_history.json").string();
56 return (std::filesystem::current_path() /
"agent_chat_history.json").string();
61 return value.
get<std::string>();
64 return value.
get<
bool>() ?
"true" :
"false";
66 if (value.is_number_integer()) {
67 return std::to_string(value.
get<
long long>());
69 if (value.is_number_unsigned()) {
70 return std::to_string(value.
get<
unsigned long long>());
72 if (value.is_number_float()) {
73 return absl::StrFormat(
"%.3f", value.
get<
double>());
82 std::string sanitized;
83 sanitized.reserve(value.size());
84 for (
unsigned char ch : value) {
85 if (std::isalnum(ch)) {
86 sanitized.push_back(
static_cast<char>(ch));
88 sanitized.push_back(
'_');
91 return sanitized.empty() ? std::string(
"entry") : sanitized;
95 if (!out || text.
empty()) {
107 if (value.is_number_unsigned()) {
108 return static_cast<uint32_t
>(value.
get<uint64_t>());
110 if (value.is_number_integer()) {
111 return static_cast<uint32_t
>(value.
get<int64_t>());
117 std::string token = value.
get<std::string>();
121 if (token[0] ==
'$') {
122 token = token.substr(1);
123 }
else if (token.size() > 2 && token[0] ==
'0' &&
124 (token[1] ==
'x' || token[1] ==
'X')) {
125 token = token.substr(2);
128 return static_cast<uint32_t
>(std::stoul(token,
nullptr, 16));
135 if (!
object.is_object()) {
138 for (
const char* key : {
"address",
"entry_address"}) {
139 if (
object.contains(key)) {
141 if (parsed.has_value()) {
150 if (!
object.is_object()) {
153 if (
object.contains(
"source") &&
object[
"source"].is_string()) {
154 return object[
"source"].get<std::string>();
156 if (
object.contains(
"file") &&
object[
"file"].is_string()) {
157 std::string reference =
object[
"file"].get<std::string>();
158 if (
object.contains(
"line") &&
object[
"line"].is_number_integer()) {
159 reference = absl::StrCat(reference,
":",
object[
"line"].get<int>());
167 if (!
object.is_object()) {
171 const std::string address =
object.contains(
"address")
174 const std::string bank =
176 const std::string name =
180 if (!source.empty() && !address.empty()) {
181 return absl::StrFormat(
"%s (%s)", source.c_str(), address.c_str());
183 if (!name.empty() && !address.empty()) {
184 return absl::StrFormat(
"%s %s", name.c_str(), address.c_str());
186 if (!name.empty() && !bank.empty()) {
187 return absl::StrFormat(
"%s %s", name.c_str(), bank.c_str());
189 if (!source.empty()) {
192 if (!address.empty()) {
205 const char* action_key,
206 const Json&
object) {
208 if (address.has_value()) {
209 return absl::StrFormat(
"%s##tool_output_%s_%06X", visible_label, action_key,
214 if (!reference.empty()) {
215 return absl::StrFormat(
"%s##tool_output_%s_%s", visible_label, action_key,
219 return absl::StrFormat(
"%s##tool_output_%s_entry", visible_label, action_key);
224 if (!
object.is_object()) {
230 bool drew_any =
false;
233 const std::string label =
235 if (ImGui::SmallButton(label.c_str())) {
244 const std::string label =
246 if (ImGui::SmallButton(label.c_str())) {
255 const std::string label =
257 if (ImGui::SmallButton(label.c_str())) {
264 for (
auto it =
object.begin(); it !=
object.end(); ++it) {
265 if (it.value().is_array() || it.value().is_object()) {
268 ImGui::BulletText(
"%s: %s", it.key().c_str(),
274 const char* label,
const Json& array,
279 if (!ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen)) {
282 ImGui::PushID(label);
283 for (
size_t i = 0; i < array.
size(); ++i) {
284 const auto& entry = array[i];
285 ImGui::PushID(
static_cast<int>(i));
286 if (entry.is_object()) {
288 if (!title.empty()) {
292 if (!title.empty() || !entry.empty()) {
299 if (i + 1 < array.
size()) {
311 std::string context =
314 context += absl::StrFormat(
"\nName: %s", selection.
display_name);
316 if (selection.
id >= 0) {
317 context += absl::StrFormat(
"\nID: 0x%X", selection.
id);
320 context += absl::StrFormat(
"\nSecondary: 0x%X", selection.
secondary_id);
323 context +=
"\nRead Only: true";
329 const char* title,
const char* fallback_icon,
331 const std::function<
void()>& cancel_callback = {}) {
338 ImGui::TextWrapped(
"%s", status.
summary.empty() ? status.
label.c_str()
339 : status.summary.c_str());
340 if (!status.
detail.empty()) {
341 ImGui::TextWrapped(
"%s", status.
detail.c_str());
344 ImGui::TextWrapped(
"%s", status.
output_tail.c_str());
362 ImGui::TextDisabled(
"%s",
371 entry, callbacks, {.show_open_output =
true, .show_copy_log =
true});
379 "View: Toggle Project Panel"},
381 "View: Toggle Properties Panel"},
383 "View: Toggle AI Agent Panel"},
389 "View: Toggle Help Panel"},
391 "View: Toggle Settings Panel"},
399 return static_cast<int>(i);
415 const int next = (index + direction + size) % size;
428 return "View: Toggle Project Panel";
430 return "View: Toggle AI Agent Panel";
432 return "View: Toggle Proposals Panel";
434 return "View: Toggle Settings Panel";
436 return "View: Toggle Help Panel";
438 return "View: Toggle Notifications Panel";
440 return "View: Toggle Properties Panel";
460 return "Notifications";
466 return "Tool Output";
508 return "notifications";
514 return "tool_output";
580 if (direction == 0) {
590 const int step = direction > 0 ? 1 : -1;
591 OpenDrawer(StepRightPanel(current_panel, step));
616 ImGuiContext* context = ImGui::GetCurrentContext();
621 const ImGuiViewport* viewport = ImGui::GetMainViewport();
626 const float vp_width = viewport->WorkSize.x;
637 float viewport_width = 0.0f;
638 if (
const ImGuiViewport* viewport = ImGui::GetMainViewport()) {
639 viewport_width = viewport->WorkSize.x;
641 if (viewport_width <= 0.0f && ImGui::GetCurrentContext()) {
642 viewport_width = ImGui::GetIO().DisplaySize.x;
645 float clamped = std::max(limits.min_width, width);
646 if (viewport_width > 0.0f) {
647 const float ratio = viewport_width < 768.0f
648 ? std::max(0.88f, limits.max_width_ratio)
649 : limits.max_width_ratio;
650 const float max_width = std::max(limits.min_width, viewport_width * ratio);
651 clamped = std::clamp(clamped, limits.min_width, max_width);
654 float* target =
nullptr;
686 if (std::abs(*target - clamped) < 0.5f) {
692 "SetDrawerWidth type=%d requested=%.1f clamped=%.1f",
693 static_cast<int>(type), width, clamped);
838 float viewport_width)
const {
844 const float ratio = viewport_width < 768.0f
845 ? std::max(0.88f, limits.max_width_ratio)
846 : limits.max_width_ratio;
847 const float max_width = std::max(limits.min_width, viewport_width * ratio);
848 return std::clamp(width, limits.min_width, max_width);
857std::unordered_map<std::string, float>
872 const std::unordered_map<std::string, float>& widths) {
875 "RestoreDrawerWidths: %zu entries from settings", widths.size());
879 if (it != widths.end()) {
901 ImGui::IsKeyPressed(ImGuiKey_Escape)) {
922 float delta_time = std::clamp(ImGui::GetIO().DeltaTime, 0.0f, 1.0f / 20.0f);
959 const ImGuiViewport* viewport = ImGui::GetMainViewport();
960 const float viewport_width = viewport->WorkSize.x;
963 const float viewport_height =
964 std::max(0.0f, viewport->WorkSize.y - top_inset - bottom_safe);
967 const float full_width =
977 ImGuiWindowFlags panel_flags =
978 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
979 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking |
980 ImGuiWindowFlags_NoNavFocus;
984 float panel_x = viewport->WorkPos.x + viewport_width - animated_width;
985 ImGui::SetNextWindowPos(ImVec2(panel_x, viewport->WorkPos.y + top_inset));
986 ImGui::SetNextWindowSize(ImVec2(full_width, viewport_height));
990 .border = panel_border,
991 .padding = ImVec2(0.0f, 0.0f),
992 .border_size = 1.0f},
993 nullptr, panel_flags);
1006 ImGuiStyleVar_WindowPadding,
1011 ImGuiChildFlags_AlwaysUseWindowPadding);
1012 if (panel_content_open) {
1013 switch (draw_panel) {
1047 const ImVec2 win_pos = ImGui::GetWindowPos();
1048 const float win_height = ImGui::GetWindowHeight();
1049 ImGui::SetCursorScreenPos(
1050 ImVec2(win_pos.x - handle_width * 0.5f, win_pos.y));
1051 ImGui::InvisibleButton(
"##RightPanelResizeHandle",
1052 ImVec2(handle_width, win_height));
1053 const bool handle_hovered = ImGui::IsItemHovered();
1054 const bool handle_active = ImGui::IsItemActive();
1055 if (handle_hovered || handle_active) {
1056 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
1058 if (handle_hovered &&
1059 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
1063 if (handle_active) {
1065 ImGui::GetIO().MouseDelta.x;
1067 ImGui::SetTooltip(tr(
"Width: %.0f px"),
1072 handle_color.w = handle_active ? 0.95f : (handle_hovered ? 0.72f : 0.35f);
1073 ImGui::GetWindowDrawList()->AddLine(
1074 ImVec2(win_pos.x, win_pos.y),
1075 ImVec2(win_pos.x, win_pos.y + win_height),
1076 ImGui::GetColorU32(handle_color), handle_active ? 2.0f : 1.0f);
1084#ifdef YAZE_BUILD_AGENT_UI
1087 if (ImGui::IsItemHovered()) {
1088 ImGui::SetTooltip(
"%s", tr(
"Agent Ready"));
1098 const std::string badge = absl::StrFormat(
"%zu", unread);
1099 const ImVec2 badge_size = ImGui::CalcTextSize(badge.c_str());
1100 const float pad_x = 5.0f;
1101 const float badge_w = badge_size.x + pad_x * 2.0f;
1102 const float badge_h = ImGui::GetTextLineHeight() + 2.0f;
1103 const ImVec2 p = ImGui::GetCursorScreenPos();
1104 ImDrawList* dl = ImGui::GetWindowDrawList();
1105 dl->AddRectFilled(p, ImVec2(p.x + badge_w, p.y + badge_h),
1107 dl->AddText(ImVec2(p.x + pad_x, p.y + 1.0f),
1108 ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f)),
1110 ImGui::Dummy(ImVec2(badge_w, badge_h));
1111 if (ImGui::IsItemHovered()) {
1112 ImGui::SetTooltip(tr(
"%zu unread notifications"), unread);
1121 if (ImGui::IsItemHovered()) {
1122 ImGui::SetTooltip(
"%s", tr(
"Selection Locked"));
1128 const char* editor_name =
nullptr;
1131 editor_name =
"Overworld";
1134 editor_name =
"Dungeon";
1137 editor_name =
"Palette";
1140 editor_name =
"Graphics";
1143 editor_name =
"Assembly";
1146 editor_name =
"Music";
1149 editor_name =
"Messages";
1156 const ImVec2 text_sz = ImGui::CalcTextSize(editor_name);
1157 const float pad = 5.0f;
1158 const ImVec2 p = ImGui::GetCursorScreenPos();
1159 ImDrawList* dl = ImGui::GetWindowDrawList();
1161 p, ImVec2(p.x + text_sz.x + pad * 2.0f, p.y + text_sz.y + 2.0f),
1162 ImGui::GetColorU32(tag_bg), 4.0f);
1163 dl->AddText(ImVec2(p.x + pad, p.y + 1.0f),
1165 ImGui::Dummy(ImVec2(text_sz.x + pad * 2.0f, text_sz.y + 2.0f));
1184 ImVec2 header_min = ImGui::GetCursorScreenPos();
1185 ImVec2 header_max = ImVec2(header_min.x + ImGui::GetWindowWidth(),
1186 header_min.y + header_height);
1188 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1189 draw_list->AddRectFilled(
1190 header_min, header_max,
1192 draw_list->AddLine(ImVec2(header_min.x, header_max.y),
1193 ImVec2(header_max.x, header_max.y),
1197 const float icon_chip_size = 24.0f;
1198 const float chip_y = header_min.y + (header_height - icon_chip_size) * 0.5f;
1199 ImVec2 chip_min(header_min.x + padding, chip_y);
1200 ImVec2 chip_max(chip_min.x + icon_chip_size, chip_min.y + icon_chip_size);
1203 draw_list->AddRectFilled(chip_min, chip_max, ImGui::GetColorU32(chip_bg),
1205 const ImVec2 icon_sz = ImGui::CalcTextSize(icon);
1206 draw_list->AddText(ImVec2(chip_min.x + (icon_chip_size - icon_sz.x) * 0.5f,
1207 chip_min.y + (icon_chip_size - icon_sz.y) * 0.5f),
1213 const ImVec2 chrome_btn_size(24.0f, 24.0f);
1214 const float chrome_gap = 4.0f;
1215 int chrome_button_count = 2;
1217 chrome_button_count += 1;
1219#ifdef YAZE_BUILD_AGENT_UI
1222 chrome_button_count += 2;
1224 chrome_button_count += 1;
1230 chrome_button_count += 2;
1234 chrome_button_count += 1;
1237 chrome_button_count += 1;
1239 const float chrome_reserve =
1240 chrome_button_count * chrome_btn_size.x +
1241 std::max(0, chrome_button_count - 1) * chrome_gap + padding;
1245 const float title_x = padding + icon_chip_size + 6.0f;
1246 const float title_y = ImGui::GetCursorPosY() +
1247 (header_height - ImGui::GetTextLineHeight()) * 0.5f;
1248 ImGui::SetCursorPos(ImVec2(title_x, title_y));
1249 const float title_max_x =
1250 std::max(title_x, ImGui::GetWindowWidth() - chrome_reserve - 8.0f);
1251 const float title_avail = std::max(0.0f, title_max_x - title_x);
1252 ImVec2 title_size = ImGui::CalcTextSize(title);
1253 const ImVec2 text_min = ImGui::GetCursorScreenPos();
1254 const ImVec2 text_max =
1255 ImVec2(text_min.x + title_avail, text_min.y + ImGui::GetTextLineHeight());
1256 ImGui::RenderTextEllipsis(draw_list, text_min, text_max, text_max.x, title,
1257 nullptr, &title_size);
1258 const float drawn_title_w = std::min(title_size.x, title_avail);
1259 ImGui::Dummy(ImVec2(drawn_title_w, ImGui::GetTextLineHeight()));
1260 if (title_size.x > title_avail && ImGui::IsItemHovered()) {
1261 ImGui::SetTooltip(
"%s", title);
1265 if (ImGui::GetCursorPosX() + 20.0f < title_max_x) {
1266 ImGui::SameLine(0.0f, 6.0f);
1271 const float btn_y = header_min.y + (header_height - chrome_btn_size.y) * 0.5f;
1272 float current_x = ImGui::GetWindowWidth() - chrome_btn_size.x - padding;
1275 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1278 ImVec4(0, 0, 0, 0),
"right_sidebar",
"close_panel")) {
1283 current_x -= (chrome_btn_size.x + 4.0f);
1284 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1288 ImGui::OpenPopup(
"##RightPanelSwitcher");
1290 if (ImGui::BeginPopup(
"##RightPanelSwitcher")) {
1292 std::string label = absl::StrFormat(
"%s %s", entry.icon, entry.name);
1293 std::string shortcut;
1294 if (entry.shortcut_action && entry.shortcut_action[0] !=
'\0') {
1296 if (shortcut ==
"Unassigned") {
1300 if (ImGui::MenuItem(label.c_str(),
1301 shortcut.empty() ?
nullptr : shortcut.c_str(),
1302 type == entry.type)) {
1311 current_x -= (chrome_btn_size.x + 4.0f);
1312 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1318 "lock_selection")) {
1322#ifdef YAZE_BUILD_AGENT_UI
1324 current_x -= (chrome_btn_size.x + 4.0f);
1325 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1328 false, ImVec4(0, 0, 0, 0),
"right_sidebar",
"agent_clear_chat")) {
1331 current_x -= (chrome_btn_size.x + 4.0f);
1332 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1335 ImVec4(0, 0, 0, 0),
"right_sidebar",
"agent_save_chat")) {
1339 current_x -= (chrome_btn_size.x + 4.0f);
1340 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1343 ImVec4(0, 0, 0, 0),
"right_sidebar",
"agent_open_proposals")) {
1351 current_x -= (chrome_btn_size.x + 4.0f);
1352 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1355 false, ImVec4(0, 0, 0, 0),
"right_sidebar",
"notif_clear_all")) {
1358 current_x -= (chrome_btn_size.x + 4.0f);
1359 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1361 "Mark All Read",
false, ImVec4(0, 0, 0, 0),
1362 "right_sidebar",
"notif_mark_read")) {
1368 current_x -= (chrome_btn_size.x + 4.0f);
1369 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1371 "Copy Output",
false, ImVec4(0, 0, 0, 0),
1372 "right_sidebar",
"tool_copy_out")) {
1377 current_x -= (chrome_btn_size.x + 4.0f);
1378 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1381 false, ImVec4(0, 0, 0, 0),
"right_sidebar",
"help_open_docs")) {
1386 ImGui::SetCursorPosY(header_height);
1390 const float nav_height = 32.0f;
1391 const float padding = 6.0f;
1392 const float gap = 3.0f;
1394 const ImVec2 nav_min = ImGui::GetCursorScreenPos();
1395 const ImVec2 nav_max =
1396 ImVec2(nav_min.x + ImGui::GetWindowWidth(), nav_min.y + nav_height);
1398 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1399 draw_list->AddRectFilled(nav_min, nav_max,
1401 draw_list->AddLine(ImVec2(nav_min.x, nav_max.y), ImVec2(nav_max.x, nav_max.y),
1405 const size_t count = catalog.size();
1407 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + nav_height + 4.0f);
1411 const float avail_w = ImGui::GetWindowWidth() - padding * 2.0f;
1413 std::max(24.0f, std::floor((avail_w - (count - 1) * gap) / count));
1414 const float tab_h = 24.0f;
1415 const float tab_y = nav_min.y + (nav_height - tab_h) * 0.5f;
1417 for (
size_t i = 0; i < count; ++i) {
1419 const bool is_active = (current_panel == entry.
type);
1420 const float tab_x = nav_min.x + padding + i * (tab_w + gap);
1422 const ImVec2 tab_rect_min(tab_x, tab_y);
1423 const ImVec2 tab_rect_max(tab_x + tab_w, tab_y + tab_h);
1425 ImGui::SetCursorScreenPos(tab_rect_min);
1426 const std::string tab_id = absl::StrFormat(
"##drawer_nav_%zu_%s", i,
1428 if (ImGui::InvisibleButton(tab_id.c_str(), ImVec2(tab_w, tab_h))) {
1435 const bool hovered = ImGui::IsItemHovered();
1438 draw_list->AddRectFilled(
1439 tab_rect_min, tab_rect_max,
1441 draw_list->AddLine(ImVec2(tab_rect_min.x + 3.0f, tab_rect_max.y - 1.0f),
1442 ImVec2(tab_rect_max.x - 3.0f, tab_rect_max.y - 1.0f),
1444 }
else if (hovered) {
1445 draw_list->AddRectFilled(
1446 tab_rect_min, tab_rect_max,
1450 const ImVec2 icon_sz = ImGui::CalcTextSize(entry.
icon);
1451 const ImVec2 icon_pos(tab_rect_min.x + (tab_w - icon_sz.x) * 0.5f,
1452 tab_rect_min.y + (tab_h - icon_sz.y) * 0.5f);
1456 draw_list->AddText(icon_pos, ImGui::GetColorU32(icon_col), entry.
icon);
1461 draw_list->AddCircleFilled(
1462 ImVec2(tab_rect_max.x - 4.0f, tab_rect_min.y + 4.0f), 3.0f,
1467 std::string tip = entry.
name ? entry.
name :
"";
1470 if (!sc.empty() && sc !=
"Unassigned") {
1471 tip = absl::StrFormat(
"%s (%s)", entry.
name, sc.c_str());
1474 ImGui::SetTooltip(
"%s", tip.c_str());
1487 bool default_open) {
1494 {ImGuiStyleVar_FramePadding, ImVec2(8.0f, 6.0f)},
1495 {ImGuiStyleVar_FrameRounding, 4.0f},
1499 std::string header_text;
1501 header_text = std::string(icon) +
" " + label;
1503 header_text = label;
1506 ImGuiTreeNodeFlags flags =
1507 ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth |
1508 ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_FramePadding;
1510 flags |= ImGuiTreeNodeFlags_DefaultOpen;
1513 bool is_open = ImGui::TreeNodeEx(header_text.c_str(), flags);
1517 ImGui::Indent(4.0f);
1524 ImGui::Unindent(4.0f);
1545 ImGui::TextUnformatted(value);
1550 ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x);
1551 ImGui::TextWrapped(
"%s", text);
1552 ImGui::PopTextWrapPos();
1556 const std::string& action,
const std::string& fallback)
const {
1565 if (shortcut->
keys.empty()) {
1566 return "Unassigned";
1573 const char* description,
1574 const std::string& fallback) {
1584#ifdef YAZE_BUILD_AGENT_UI
1590 "The AI Agent is not initialized. "
1591 "Open the AI Agent from View menu or use Ctrl+Shift+A.");
1598 if (ImGui::BeginChild(
"AgentChatBody", ImVec2(0, 0),
false)) {
1608 "The AI Agent requires agent UI support. "
1609 "Build with YAZE_BUILD_AGENT_UI=ON to enable.");
1614#ifdef YAZE_BUILD_AGENT_UI
1621 std::string selection_context;
1627 struct QuickAction {
1632 std::vector<QuickAction> actions;
1633 if (!selection_context.empty()) {
1634 actions.push_back({
"Explain selection",
1635 "Explain this selection and how to edit it safely.\n\n" +
1636 selection_context});
1639 "Suggest improvements or checks for this selection.\n\n" +
1640 selection_context});
1645 actions.push_back({
"Summarize map",
1646 "Summarize the current overworld map and its key "
1647 "features. Use overworld tools if available."});
1648 actions.push_back({
"List sprites/items",
1649 "List notable sprites or items on the current "
1653 actions.push_back({
"Audit room",
1654 "Summarize the current dungeon room layout, doors, "
1655 "and object density."});
1656 actions.push_back({
"List sprites",
1657 "List sprites in the current dungeon room and any "
1658 "potential conflicts."});
1661 actions.push_back({
"Review tiles",
1662 "Review the current tileset usage and point out any "
1663 "obvious issues."});
1664 actions.push_back({
"Palette check",
1665 "Check palette usage for contrast/readability "
1669 actions.push_back({
"Palette audit",
1670 "Audit the active palette for hue/contrast balance "
1671 "and note risks."});
1672 actions.push_back({
"Theme ideas",
1673 "Suggest a palette variation that fits the current "
1677 actions.push_back({
"Sprite review",
1678 "Review the selected sprite properties and suggest "
1682 actions.push_back({
"Copy edit",
1683 "Review the current message text for clarity and "
1684 "style improvements."});
1687 actions.push_back({
"ASM review",
1688 "Review the current ASM changes for risks and style "
1692 actions.push_back({
"Hex context",
1693 "Explain what the current hex selection likely "
1697 actions.push_back({
"Test suggestion",
1698 "Propose a short emulator test to validate the "
1699 "current feature."});
1702 actions.push_back({
"Agent config review",
1703 "Review current agent configuration for practical "
1707 actions.push_back({
"Agent overview",
1708 "Suggest the next best agent-assisted action for the "
1709 "current editor context."});
1713 if (actions.empty()) {
1717 ImGui::TextColored(accent, tr(
"%s Editor Actions"),
ICON_MD_BOLT);
1721 int columns = ImGui::GetContentRegionAvail().x > 420.0f ? 2 : 1;
1722 if (ImGui::BeginTable(
"AgentQuickActionsTable", columns,
1723 ImGuiTableFlags_SizingStretchSame)) {
1724 for (
const auto& action : actions) {
1725 ImGui::TableNextColumn();
1726 if (ImGui::Button(action.label, ImVec2(-1, 0))) {
1751 "The proposal system is not initialized. "
1752 "Proposals will appear here when the AI Agent creates them.");
1766 "Settings will be available once initialized. "
1767 "This panel provides quick access to application settings.");
1802 const char* editor_name =
"No Editor Selected";
1807 editor_name =
"Overworld Editor";
1811 editor_name =
"Dungeon Editor";
1815 editor_name =
"Graphics Editor";
1819 editor_name =
"Palette Editor";
1823 editor_name =
"Music Editor";
1827 editor_name =
"Screen Editor";
1831 editor_name =
"Sprite Editor";
1835 editor_name =
"Message Editor";
1839 editor_name =
"Emulator";
1856 ImGui::Indent(8.0f);
1860 absl::StrFormat(
"%s+Shift+S", ctrl));
1864 absl::StrFormat(
"%s+Shift+P", ctrl));
1866 absl::StrFormat(
"%s+Shift+K", ctrl));
1868 absl::StrFormat(
"%s+B", ctrl));
1871 ImGui::Unindent(8.0f);
1880 ImGui::Indent(8.0f);
1890 ImGui::Unindent(8.0f);
1895 ImGui::Indent(8.0f);
1900 absl::StrFormat(
"%s+C", ctrl));
1902 absl::StrFormat(
"%s+V", ctrl));
1903 DrawShortcutRow(
"dungeon.object.delete",
"Delete selection",
"Delete");
1907 ImGui::Unindent(8.0f);
1912 ImGui::Indent(8.0f);
1920 absl::StrFormat(
"%s+G", ctrl));
1921 ImGui::Unindent(8.0f);
1926 ImGui::Indent(8.0f);
1930 ImGui::Unindent(8.0f);
1935 ImGui::Indent(8.0f);
1941 ImGui::Unindent(8.0f);
1946 ImGui::Indent(8.0f);
1948 "Insert Line Break");
1950 ImGui::Unindent(8.0f);
1955 ImGui::Indent(8.0f);
1959 ImGui::TextWrapped(tr(
"Select an editor to see specific shortcuts."));
1961 ImGui::Unindent(8.0f);
1970 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1972 ImGui::TextWrapped(tr(
"Paint tiles by selecting from Tile16 Selector"));
1975 tr(
"Switch between Light World, Dark World, and Special Areas"));
1978 tr(
"Use Entity Mode to place entrances, exits, items, and sprites"));
1981 tr(
"Right-click on the map to pick a tile for painting"));
1986 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1989 tr(
"Select rooms from the Room Selector or Room Matrix"));
1991 ImGui::TextWrapped(tr(
"Place objects using the Object Editor panel"));
1994 tr(
"Edit room headers for palette, GFX, and floor settings"));
1996 ImGui::TextWrapped(tr(
"Multiple rooms can be opened in separate tabs"));
2001 ImGui::GetStyleColorVec4(ImGuiCol_Text));
2003 ImGui::TextWrapped(tr(
"Browse graphics sheets using the Sheet Browser"));
2005 ImGui::TextWrapped(tr(
"Edit pixels directly with the Pixel Editor"));
2007 ImGui::TextWrapped(tr(
"Choose palettes from Palette Controls"));
2009 ImGui::TextWrapped(tr(
"View 3D objects like rupees and crystals"));
2014 ImGui::GetStyleColorVec4(ImGuiCol_Text));
2016 ImGui::TextWrapped(tr(
"Edit overworld, dungeon, and sprite palettes"));
2018 ImGui::TextWrapped(tr(
"Use Quick Access for color harmony tools"));
2020 ImGui::TextWrapped(tr(
"Changes update in real-time across all editors"));
2025 ImGui::GetStyleColorVec4(ImGuiCol_Text));
2027 ImGui::TextWrapped(tr(
"Browse songs in the Song Browser"));
2029 ImGui::TextWrapped(tr(
"Use the tracker for playback control"));
2031 ImGui::TextWrapped(tr(
"Edit instruments and BRR samples"));
2036 ImGui::GetStyleColorVec4(ImGuiCol_Text));
2038 ImGui::TextWrapped(tr(
"Edit all in-game dialog messages"));
2040 ImGui::TextWrapped(tr(
"Preview text rendering with the font atlas"));
2042 ImGui::TextWrapped(tr(
"Manage the compression dictionary"));
2047 ImGui::TextWrapped(tr(
"Open a ROM file via File > Open ROM"));
2049 ImGui::TextWrapped(tr(
"Select an editor from the sidebar"));
2051 ImGui::TextWrapped(tr(
"Use panels to access tools and settings"));
2053 ImGui::TextWrapped(tr(
"Save your work via File > Save ROM"));
2059 const float button_width = ImGui::GetContentRegionAvail().x;
2062 {ImGuiStyleVar_FramePadding, ImVec2(10.0f, 7.0f)},
2063 {ImGuiStyleVar_FrameRounding, 6.0f},
2064 {ImGuiStyleVar_FrameBorderSize, 1.0f},
2067 auto draw_link_button = [&](
const char* icon,
const char* label,
2074 const std::string text =
2076 if (ImGui::Button(text.c_str(), ImVec2(button_width, 0))) {
2082 "https://github.com/scawful/yaze/wiki");
2085 "https://github.com/scawful/yaze/issues/new");
2088 "https://discord.gg/zU5qDm8MZg");
2096 "A comprehensive editor for The Legend of Zelda: "
2097 "A Link to the Past ROM files.");
2103 ImGui::Text(tr(
"Written by: scawful"));
2104 ImGui::Text(tr(
"Special Thanks: Zarby89, JaredBrian"));
2122 float avail = ImGui::GetContentRegionAvail().x;
2132 ImVec2(avail * 0.5f - 4.0f, 0))) {
2137 ImVec2(avail * 0.5f - 4.0f, 0))) {
2154 const auto cancel_build =
2157 if (build_status.visible || run_status.visible || !workflow_history.empty()) {
2159 if (build_status.visible) {
2160 DrawWorkflowSummaryCard(
"Build",
ICON_MD_BUILD, build_status,
2164 if (run_status.visible) {
2168 if (!workflow_history.empty()) {
2170 const auto preview_entries =
2172 for (
size_t i = 0; i < preview_entries.size(); ++i) {
2173 ImGui::PushID(
static_cast<int>(i));
2174 DrawWorkflowPreviewEntry(preview_entries[i], workflow_callbacks);
2176 if (i + 1 < preview_entries.size()) {
2180 if (workflow_history.size() > preview_entries.size()) {
2182 ImGui::TextDisabled(
2183 tr(
"+%zu more entries available in Workflow Output"),
2184 workflow_history.size() - preview_entries.size());
2187 " View Full History##workflow_view_full")) {
2199 if (history.empty()) {
2205 "Notifications will appear here when actions complete.");
2211 if (unread_count > 0) {
2222 ImGuiChildFlags_None, ImGuiWindowFlags_AlwaysVerticalScrollbar);
2223 if (notification_list_open) {
2225 auto now = std::chrono::system_clock::now();
2228 bool shown_today =
false;
2229 bool shown_yesterday =
false;
2230 bool shown_older =
false;
2232 for (
const auto& entry : history) {
2234 std::chrono::duration_cast<std::chrono::hours>(now - entry.timestamp)
2238 if (diff < 24 && !shown_today) {
2241 }
else if (diff >= 24 && diff < 48 && !shown_yesterday) {
2244 shown_yesterday =
true;
2245 }
else if (diff >= 48 && !shown_older) {
2252 ImGui::PushID(&entry);
2257 switch (entry.type) {
2287 ImGui::TextWrapped(
"%s", entry.message.c_str());
2290 auto diff_sec = std::chrono::duration_cast<std::chrono::seconds>(
2291 now - entry.timestamp)
2293 std::string time_str;
2294 if (diff_sec < 60) {
2295 time_str =
"just now";
2296 }
else if (diff_sec < 3600) {
2297 time_str = absl::StrFormat(
"%dm ago", diff_sec / 60);
2298 }
else if (diff_sec < 86400) {
2299 time_str = absl::StrFormat(
"%dh ago", diff_sec / 3600);
2301 time_str = absl::StrFormat(
"%dd ago", diff_sec / 86400);
2343 opts.
title =
"No tool output";
2345 "Run a project-graph query from the editor to inspect its output here.";
2361 ImGui::PushID(
"summary");
2363 if (!parsed.
empty()) {
2366 DrawJsonObjectFields(parsed);
2372 ImGui::PushID(
"resolved_source");
2374 if (!parsed[
"source"].empty()) {
2377 DrawJsonObjectFields(parsed[
"source"]);
2381 DrawJsonObjectArraySection(
"Matching Symbols", parsed[
"matching_symbols"],
2383 DrawJsonObjectArraySection(
"Sources", parsed[
"sources"],
2386 DrawJsonObjectArraySection(
"Writes", parsed[
"writes"],
2389 DrawJsonObjectArraySection(
"Symbols", parsed[
"symbols"],
2393 if (ImGui::CollapsingHeader(tr(
"Raw Output"),
2394 has_json ? 0 : ImGuiTreeNodeFlags_DefaultOpen)) {
2395 if (ImGui::BeginChild(
"##tool_output_result", ImVec2(0.0f, 220.0f),
true)) {
2403 bool interacted =
false;
2408 {ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
2416 ImGui::OpenPopup(
"##DrawersOverflowMenu");
2419 if (ImGui::IsItemHovered()) {
2420 ImGui::SetTooltip(
"%s", tr(
"Drawers"));
2423 if (ImGui::BeginPopup(
"##DrawersOverflowMenu")) {
2425 std::string label = absl::StrFormat(
"%s %s", entry.icon, entry.name);
2426 std::string shortcut;
2427 if (entry.shortcut_action && entry.shortcut_action[0] !=
'\0') {
2429 if (shortcut ==
"Unassigned") {
2433 if (ImGui::MenuItem(label.c_str(),
2434 shortcut.empty() ?
nullptr : shortcut.c_str(),
2447 const float frame_padding = ImGui::GetStyle().FramePadding.x;
2450 return icon_width + frame_padding * 2.0f;
static Json parse(const std::string &)
std::string dump(int=-1, char=' ', bool=false, int=0) const
bool contains(const std::string &) const
void SendMessage(const std::string &message)
void Draw(float available_height=0.0f)
void set_active(bool active)
absl::Status SaveHistory(const std::string &filepath)
void CycleDrawer(int direction)
Cycle to the next/previous right drawer in header order.
bool DrawDrawerToggleButtons()
Draw the single Drawers overflow control for the status cluster.
void DrawPanelDescription(const char *text)
void DrawEditorContextHeader()
static std::string PanelTypeKey(PanelType type)
void NotifyPanelWidthChanged(PanelType type, float width)
EditorType active_editor_type_
void DrawEditorSpecificShortcuts()
void DrawPanelHeader(PanelType type, const char *title, const char *icon)
void DrawShortcutRow(const std::string &action, const char *description, const std::string &fallback)
std::string GetShortcutLabel(const std::string &action, const std::string &fallback) const
float GetClampedPanelWidth(PanelType type, float viewport_width) const
void OpenDrawer(DrawerType type)
Open a specific drawer.
void SetDrawerWidth(DrawerType type, float width)
Set drawer width for a specific drawer type.
void SetToolOutput(std::string title, std::string query, std::string content, ToolOutputActions actions={})
ToolOutputActions tool_output_actions_
void DrawPanelValue(const char *label, const char *value)
void Draw()
Draw the drawer and its contents.
std::function< void(PanelType, float)> on_panel_width_changed_
void DrawGlobalShortcuts()
void ResetDrawerWidths()
Reset all drawer widths to their defaults.
void DrawEditorSpecificHelp()
static float GetDrawerToggleClusterWidth()
Menu-bar width reserved for the Drawers overflow control.
SelectionPropertiesPanel * properties_panel_
bool BeginPanelSection(const char *label, const char *icon=nullptr, bool default_open=true)
void ToggleDrawer(DrawerType type)
Toggle a specific drawer on/off.
float notifications_width_
void DrawProposalsPanel()
static float GetDefaultDrawerWidth(DrawerType type, EditorType editor=EditorType::kUnknown)
Get the default width for a specific drawer type.
ShortcutManager * shortcut_manager_
void DrawHeaderActions(PanelType type)
std::string tool_output_title_
float GetConfiguredPanelWidth(PanelType type) const
void DrawQuickActionButtons()
std::unordered_map< std::string, PanelSizeLimits > panel_size_limits_
void DrawPropertiesPanel()
void DrawAgentChatPanel()
SettingsPanel * settings_panel_
void DrawNotificationsPanel()
ToastManager * toast_manager_
bool IsDrawerActive(DrawerType type) const
Check if a specific drawer is active.
void DrawHeaderContextBadge(PanelType type)
bool IsDrawerExpanded() const
Check if any drawer is currently expanded (or animating closed)
float GetDrawerWidth() const
Get the width of the drawer when expanded.
std::string tool_output_content_
void OnHostVisibilityChanged(bool visible)
Snap transient animations when host visibility changes.
void DrawPanelLabel(const char *label)
bool DrawAgentQuickActions()
void CloseDrawer()
Close the currently active drawer.
void RestoreDrawerWidths(const std::unordered_map< std::string, float > &widths)
std::unordered_map< std::string, float > SerializeDrawerWidths() const
Persist/restore per-drawer widths for user settings.
ProposalDrawer * proposal_drawer_
std::string tool_output_query_
ProjectManagementPanel * project_panel_
void DrawToolOutputPanel()
void DrawDrawerNavStrip(PanelType current_panel)
PanelSizeLimits GetPanelSizeLimits(PanelType type) const
void SetPanelSizeLimits(PanelType type, const PanelSizeLimits &limits)
Set sizing constraints for an individual right panel.
const SelectionContext & GetSelection() const
Get the current selection context.
bool HasSelection() const
Check if there's an active selection.
void Draw()
Draw the properties panel content.
const Shortcut * FindShortcut(const std::string &name) const
size_t GetUnreadCount() const
const std::deque< NotificationEntry > & GetHistory() const
static bool BeginContentChild(const char *id, const ImVec2 &min_size, bool border=false, ImGuiWindowFlags flags=0)
static void EndContentChild()
static SafeAreaInsets GetSafeAreaInsets()
static float GetTopInset()
RAII guard for ImGui style colors.
RAII guard for ImGui style vars.
RAII compound guard for window-level style setup.
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
#define ICON_MD_NOTIFICATIONS
#define ICON_MD_ACCOUNT_TREE
#define ICON_MD_LANDSCAPE
#define ICON_MD_LOCK_OPEN
#define ICON_MD_FOLDER_SPECIAL
#define ICON_MD_PLAY_ARROW
#define ICON_MD_SWAP_HORIZ
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_DESCRIPTION
#define ICON_MD_NOTIFICATIONS_OFF
#define ICON_MD_OPEN_IN_NEW
#define ICON_MD_CONTENT_COPY
#define ICON_MD_VERTICAL_SPLIT
#define ICON_MD_FIBER_MANUAL_RECORD
#define ICON_MD_SMART_TOY
#define ICON_MD_DELETE_SWEEP
#define LOG_INFO(category, format,...)
std::vector< ProjectWorkflowHistoryEntry > workflow_history()
std::function< void()> cancel_build_workflow_callback()
ProjectWorkflowStatus build_workflow_status()
std::function< void()> run_project_workflow_callback()
std::function< void()> show_workflow_output_callback()
std::function< void()> start_build_workflow_callback()
ProjectWorkflowStatus run_workflow_status()
std::string JsonValueToDisplayString(const Json &value)
void DrawWorkflowPreviewEntry(const ProjectWorkflowHistoryEntry &entry, const workflow::WorkflowActionCallbacks &callbacks)
void DrawWorkflowSummaryCard(const char *title, const char *fallback_icon, const ProjectWorkflowStatus &status, const std::function< void()> &cancel_callback={})
int FindRightPanelIndex(RightDrawerManager::PanelType type)
std::string ExtractToolOutputReference(const Json &object)
bool TryParseToolOutputJson(const std::string &text, Json *out)
std::string BuildSelectionContextSummary(const SelectionContext &selection)
std::string BuildToolOutputEntryTitle(const Json &object)
std::string SanitizeToolOutputIdFragment(const std::string &value)
void DrawJsonObjectArraySection(const char *label, const Json &array, const RightDrawerManager::ToolOutputActions &actions)
void DrawJsonObjectFields(const Json &object)
RightDrawerManager::PanelType StepRightPanel(RightDrawerManager::PanelType current, int direction)
std::optional< uint32_t > ParseToolOutputAddress(const Json &value)
std::optional< uint32_t > ExtractToolOutputAddress(const Json &object)
void DrawToolOutputEntryActions(const Json &object, const RightDrawerManager::ToolOutputActions &actions)
std::string BuildToolOutputActionLabel(const char *visible_label, const char *action_key, const Json &object)
std::string FormatHistoryTime(std::chrono::system_clock::time_point timestamp)
const char * WorkflowIcon(const ProjectWorkflowStatus &status, const char *fallback_icon)
WorkflowActionRowResult DrawHistoryActionRow(const ProjectWorkflowHistoryEntry &entry, const WorkflowActionCallbacks &callbacks, const WorkflowActionRowOptions &options)
std::vector< ProjectWorkflowHistoryEntry > SelectWorkflowPreviewEntries(const std::vector< ProjectWorkflowHistoryEntry > &history, size_t max_entries)
ImVec4 WorkflowColor(ProjectWorkflowState state)
const std::array< DrawerCatalogEntry, 7 > kDrawerCatalog
absl::Span< const DrawerCatalogEntry > GetDrawerCatalog()
Switchable drawers in header-cycle order (excludes Tool Output).
const char * GetPanelTypeName(RightDrawerManager::PanelType type)
Get the name of a panel type.
const char * GetPanelShortcutAction(RightDrawerManager::PanelType type)
Shortcut action id used by ShortcutManager for a drawer toggle.
const char * GetSelectionTypeName(SelectionType type)
Get a human-readable name for a selection type.
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
const char * GetPanelTypeIcon(RightDrawerManager::PanelType type)
Get the icon for a panel type.
bool TransparentIconButton(const char *icon, const ImVec2 &size, const char *tooltip, bool is_active, const ImVec4 &active_color, const char *panel_id, const char *anim_id)
Draw a transparent icon button (hover effect only).
const char * GetCtrlDisplayName()
Get the display name for the primary modifier key.
ImVec4 ConvertColorToImVec4(const Color &color)
void ColoredText(const char *text, const ImVec4 &color)
ImVec4 GetSurfaceContainerHighestVec4()
ImVec4 GetTextPrimaryVec4()
bool OpenUrl(const std::string &url)
ImVec4 GetTextDisabledVec4()
EmptyStateOptions EmptyNoProject(bool compact)
ImVec4 GetTextSecondaryVec4()
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
bool DrawEmptyState(const EmptyStateOptions &options)
Draw a centered empty-state block.
ImVec4 GetSurfaceContainerHighVec4()
EmptyStateOptions EmptyNoSelection(bool compact)
ImVec4 GetOnSurfaceVec4()
ImVec4 GetSurfaceContainerVec4()
One entry in the shared right-drawer catalog (header / overflow / View).
RightDrawerManager::DrawerType type
const char * shortcut_action
ProjectWorkflowStatus status
std::chrono::system_clock::time_point timestamp
ProjectWorkflowState state
Holds information about the current selection.
std::vector< ImGuiKey > keys
std::function< void()> run_project
std::function< void()> show_output
std::function< void()> start_build
Shared empty / disabled panel presentation.
bool compact
Tighter vertical rhythm for drawers / inspectors.
static constexpr float kPanelWidthSettings
static constexpr float kPanelWidthHelp
static constexpr float kPanelMinWidthProject
static constexpr float kPanelMinWidthHelp
static constexpr float kPanelWidthNotifications
static constexpr float kPanelWidthMedium
static constexpr float kAnimationSnapThreshold
static constexpr float kPanelMinWidthNotifications
static constexpr float kPanelMinWidthAgentChat
static constexpr float kPanelPaddingLarge
static constexpr float kAnimationSpeed
static constexpr float kPanelMinWidthSettings
static constexpr float kPanelPaddingMedium
static constexpr float kPanelWidthProject
static constexpr float kSplitterWidth
static constexpr float kPanelMinWidthProposals
static constexpr float kPanelHeaderHeight
static constexpr float kPanelMinWidthAbsolute
static constexpr float kContentMinHeightList
static constexpr float kPanelMinWidthProperties
static constexpr float kPanelWidthProposals
static constexpr float kPanelWidthProperties
static constexpr float kPanelWidthAgentChat