14#include "absl/strings/str_format.h"
15#include "absl/types/span.h"
37#include "imgui/imgui.h"
38#include "imgui/imgui_internal.h"
48std::string ResolveAgentChatHistoryPath() {
51 return (*agent_dir /
"agent_chat_history.json").string();
55 return (*temp_dir /
"agent_chat_history.json").string();
57 return (std::filesystem::current_path() /
"agent_chat_history.json").string();
62 return value.
get<std::string>();
65 return value.
get<
bool>() ?
"true" :
"false";
67 if (value.is_number_integer()) {
68 return std::to_string(value.
get<
long long>());
70 if (value.is_number_unsigned()) {
71 return std::to_string(value.
get<
unsigned long long>());
73 if (value.is_number_float()) {
74 return absl::StrFormat(
"%.3f", value.
get<
double>());
83 std::string sanitized;
84 sanitized.reserve(value.size());
85 for (
unsigned char ch : value) {
86 if (std::isalnum(ch)) {
87 sanitized.push_back(
static_cast<char>(ch));
89 sanitized.push_back(
'_');
92 return sanitized.empty() ? std::string(
"entry") : sanitized;
96 if (!out || text.
empty()) {
108 if (value.is_number_unsigned()) {
109 return static_cast<uint32_t
>(value.
get<uint64_t>());
111 if (value.is_number_integer()) {
112 return static_cast<uint32_t
>(value.
get<int64_t>());
118 std::string token = value.
get<std::string>();
122 if (token[0] ==
'$') {
123 token = token.substr(1);
124 }
else if (token.size() > 2 && token[0] ==
'0' &&
125 (token[1] ==
'x' || token[1] ==
'X')) {
126 token = token.substr(2);
129 return static_cast<uint32_t
>(std::stoul(token,
nullptr, 16));
136 if (!
object.is_object()) {
139 for (
const char* key : {
"address",
"entry_address"}) {
140 if (
object.contains(key)) {
142 if (parsed.has_value()) {
151 if (!
object.is_object()) {
154 if (
object.contains(
"source") &&
object[
"source"].is_string()) {
155 return object[
"source"].get<std::string>();
157 if (
object.contains(
"file") &&
object[
"file"].is_string()) {
158 std::string reference =
object[
"file"].get<std::string>();
159 if (
object.contains(
"line") &&
object[
"line"].is_number_integer()) {
160 reference = absl::StrCat(reference,
":",
object[
"line"].get<int>());
168 if (!
object.is_object()) {
172 const std::string address =
object.contains(
"address")
175 const std::string bank =
177 const std::string name =
181 if (!source.empty() && !address.empty()) {
182 return absl::StrFormat(
"%s (%s)", source.c_str(), address.c_str());
184 if (!name.empty() && !address.empty()) {
185 return absl::StrFormat(
"%s %s", name.c_str(), address.c_str());
187 if (!name.empty() && !bank.empty()) {
188 return absl::StrFormat(
"%s %s", name.c_str(), bank.c_str());
190 if (!source.empty()) {
193 if (!address.empty()) {
206 const char* action_key,
207 const Json&
object) {
209 if (address.has_value()) {
210 return absl::StrFormat(
"%s##tool_output_%s_%06X", visible_label, action_key,
215 if (!reference.empty()) {
216 return absl::StrFormat(
"%s##tool_output_%s_%s", visible_label, action_key,
220 return absl::StrFormat(
"%s##tool_output_%s_entry", visible_label, action_key);
225 if (!
object.is_object()) {
231 bool drew_any =
false;
234 const std::string label =
236 if (ImGui::SmallButton(label.c_str())) {
245 const std::string label =
247 if (ImGui::SmallButton(label.c_str())) {
256 const std::string label =
258 if (ImGui::SmallButton(label.c_str())) {
265 for (
auto it =
object.begin(); it !=
object.end(); ++it) {
266 if (it.value().is_array() || it.value().is_object()) {
269 ImGui::BulletText(
"%s: %s", it.key().c_str(),
275 const char* label,
const Json& array,
280 if (!ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen)) {
283 ImGui::PushID(label);
284 for (
size_t i = 0; i < array.
size(); ++i) {
285 const auto& entry = array[i];
286 ImGui::PushID(
static_cast<int>(i));
287 if (entry.is_object()) {
289 if (!title.empty()) {
293 if (!title.empty() || !entry.empty()) {
300 if (i + 1 < array.
size()) {
312 std::string context =
315 context += absl::StrFormat(
"\nName: %s", selection.
display_name);
317 if (selection.
id >= 0) {
318 context += absl::StrFormat(
"\nID: 0x%X", selection.
id);
321 context += absl::StrFormat(
"\nSecondary: 0x%X", selection.
secondary_id);
324 context +=
"\nRead Only: true";
330 const char* title,
const char* fallback_icon,
332 const std::function<
void()>& cancel_callback = {}) {
339 ImGui::TextWrapped(
"%s", status.
summary.empty() ? status.
label.c_str()
340 : status.summary.c_str());
341 if (!status.
detail.empty()) {
342 ImGui::TextWrapped(
"%s", status.
detail.c_str());
345 ImGui::TextWrapped(
"%s", status.
output_tail.c_str());
363 ImGui::TextDisabled(
"%s",
372 entry, callbacks, {.show_open_output =
true, .show_copy_log =
true});
380 "View: Toggle Project Panel"},
382 "View: Toggle Properties Panel"},
384 "View: Toggle AI Agent Panel"},
390 "View: Toggle Help Panel"},
392 "View: Toggle Settings Panel"},
400 return static_cast<int>(i);
416 const int next = (index + direction + size) % size;
429 return "View: Toggle Project Panel";
431 return "View: Toggle AI Agent Panel";
433 return "View: Toggle Proposals Panel";
435 return "View: Toggle Settings Panel";
437 return "View: Toggle Help Panel";
439 return "View: Toggle Notifications Panel";
441 return "View: Toggle Properties Panel";
461 return "Notifications";
467 return "Tool Output";
509 return "notifications";
515 return "tool_output";
564 if (FindRightPanelIndex(type) >= 0) {
598 if (direction == 0) {
608 const int step = direction > 0 ? 1 : -1;
609 OpenDrawer(StepRightPanel(current_panel, step));
638 ImGuiContext* context = ImGui::GetCurrentContext();
643 const ImGuiViewport* viewport = ImGui::GetMainViewport();
655 float viewport_width = 0.0f;
656 if (
const ImGuiViewport* viewport = ImGui::GetMainViewport()) {
657 viewport_width = viewport->WorkSize.x;
659 if (viewport_width <= 0.0f && ImGui::GetCurrentContext()) {
660 viewport_width = ImGui::GetIO().DisplaySize.x;
663 float clamped = std::max(limits.min_width, width);
664 if (viewport_width > 0.0f) {
665 const float ratio = viewport_width < 768.0f
666 ? std::max(0.88f, limits.max_width_ratio)
667 : limits.max_width_ratio;
668 const float max_width = std::max(limits.min_width, viewport_width * ratio);
669 clamped = std::clamp(clamped, limits.min_width, max_width);
678 "SetDrawerWidth type=%d requested=%.1f clamped=%.1f",
679 static_cast<int>(type), width, clamped);
685 float default_width =
688 default_width = std::max(
787 .max_width_ratio = 0.95f,
789 const auto fold = [
this, &shared](
PanelType type) {
791 shared.min_width = std::max(shared.min_width, limits.
min_width);
792 shared.max_width_ratio =
807 float viewport_width)
const {
813 const float ratio = viewport_width < 768.0f
814 ? std::max(0.88f, limits.max_width_ratio)
815 : limits.max_width_ratio;
816 const float max_width = std::max(limits.min_width, viewport_width * ratio);
817 return std::clamp(width, limits.min_width, max_width);
826std::unordered_map<std::string, float>
832 const std::unordered_map<std::string, float>& widths) {
835 "RestoreDrawerWidths: %zu entries from settings", widths.size());
837 if (
const auto shared = widths.find(
"right_sidebar.shared");
838 shared != widths.end()) {
848 migrated = std::max(migrated,
852 if (
const auto saved = widths.find(
PanelTypeKey(entry.type));
853 saved != widths.end()) {
854 migrated = std::max(migrated, saved->second);
857 if (
const auto tool_output =
859 tool_output != widths.end()) {
860 migrated = std::max(migrated, tool_output->second);
873 ImGui::IsKeyPressed(ImGuiKey_Escape)) {
894 float delta_time = std::clamp(ImGui::GetIO().DeltaTime, 0.0f, 1.0f / 20.0f);
931 const ImGuiViewport* viewport = ImGui::GetMainViewport();
932 const float viewport_width = viewport->WorkSize.x;
935 const float viewport_height =
936 std::max(0.0f, viewport->WorkSize.y - top_inset - bottom_safe);
939 const float full_width =
949 ImGuiWindowFlags panel_flags =
950 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
951 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking |
952 ImGuiWindowFlags_NoNavFocus;
956 float panel_x = viewport->WorkPos.x + viewport_width - animated_width;
957 ImGui::SetNextWindowPos(ImVec2(panel_x, viewport->WorkPos.y + top_inset));
958 ImGui::SetNextWindowSize(ImVec2(full_width, viewport_height));
962 .border = panel_border,
963 .padding = ImVec2(0.0f, 0.0f),
964 .border_size = 0.0f},
965 nullptr, panel_flags);
978 ImGuiStyleVar_WindowPadding,
983 ImGuiChildFlags_AlwaysUseWindowPadding);
984 if (panel_content_open) {
985 switch (draw_panel) {
1019 const ImVec2 win_pos = ImGui::GetWindowPos();
1020 const float win_height = ImGui::GetWindowHeight();
1021 ImGui::SetCursorScreenPos(
1022 ImVec2(win_pos.x - handle_width * 0.5f, win_pos.y));
1023 ImGui::InvisibleButton(
"##RightPanelResizeHandle",
1024 ImVec2(handle_width, win_height));
1025 const bool handle_hovered = ImGui::IsItemHovered();
1026 const bool handle_active = ImGui::IsItemActive();
1027 if (handle_hovered || handle_active) {
1028 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
1030 if (handle_hovered &&
1031 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
1034 if (handle_active) {
1036 ImGui::GetIO().MouseDelta.x;
1038 ImGui::SetTooltip(tr(
"Width: %.0f px"),
1043 handle_color.w = handle_active ? 0.95f : (handle_hovered ? 0.72f : 0.35f);
1044 ImGui::GetWindowDrawList()->AddLine(
1045 ImVec2(win_pos.x, win_pos.y),
1046 ImVec2(win_pos.x, win_pos.y + win_height),
1047 ImGui::GetColorU32(handle_color), handle_active ? 2.0f : 1.0f);
1053 float available_width) {
1056#ifdef YAZE_BUILD_AGENT_UI
1060 if (ImGui::IsItemHovered()) {
1061 ImGui::SetTooltip(
"%s", tr(
"Agent Ready"));
1071 const std::string badge = absl::StrFormat(
"%zu", unread);
1072 const ImVec2 badge_size = ImGui::CalcTextSize(badge.c_str());
1073 const float pad_x = 5.0f;
1074 const float badge_w = badge_size.x + pad_x * 2.0f;
1075 if (badge_w > available_width) {
1078 const float badge_h = ImGui::GetTextLineHeight() + 2.0f;
1079 const ImVec2 p = ImGui::GetCursorScreenPos();
1080 ImDrawList* dl = ImGui::GetWindowDrawList();
1081 dl->AddRectFilled(p, ImVec2(p.x + badge_w, p.y + badge_h),
1083 dl->AddText(ImVec2(p.x + pad_x, p.y + 1.0f),
1084 ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f)),
1086 ImGui::Dummy(ImVec2(badge_w, badge_h));
1087 if (ImGui::IsItemHovered()) {
1088 ImGui::SetTooltip(tr(
"%zu unread notifications"), unread);
1096 ImGui::CalcTextSize(
ICON_MD_LOCK).x <= available_width) {
1098 if (ImGui::IsItemHovered()) {
1099 ImGui::SetTooltip(
"%s", tr(
"Selection Locked"));
1105 const char* editor_name =
nullptr;
1108 editor_name =
"Overworld";
1111 editor_name =
"Dungeon";
1114 editor_name =
"Palette";
1117 editor_name =
"Graphics";
1120 editor_name =
"Assembly";
1123 editor_name =
"Music";
1126 editor_name =
"Messages";
1133 const ImVec2 text_sz = ImGui::CalcTextSize(editor_name);
1134 const float pad = 5.0f;
1135 if (text_sz.x + pad * 2.0f > available_width) {
1138 const ImVec2 p = ImGui::GetCursorScreenPos();
1139 ImDrawList* dl = ImGui::GetWindowDrawList();
1141 p, ImVec2(p.x + text_sz.x + pad * 2.0f, p.y + text_sz.y + 2.0f),
1142 ImGui::GetColorU32(tag_bg), 4.0f);
1143 dl->AddText(ImVec2(p.x + pad, p.y + 1.0f),
1145 ImGui::Dummy(ImVec2(text_sz.x + pad * 2.0f, text_sz.y + 2.0f));
1160 const float header_height =
1162 const float padding =
1167 ImVec2 header_min = ImGui::GetCursorScreenPos();
1169 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1172 const float chip_y = header_min.y + (header_height - icon_chip_size) * 0.5f;
1173 ImVec2 chip_min(header_min.x + padding, chip_y);
1174 const ImVec2 icon_sz = ImGui::CalcTextSize(icon);
1175 draw_list->AddText(ImVec2(chip_min.x + (icon_chip_size - icon_sz.x) * 0.5f,
1176 chip_min.y + (icon_chip_size - icon_sz.y) * 0.5f),
1183 int chrome_button_count = 1;
1185 chrome_button_count += 1;
1187#ifdef YAZE_BUILD_AGENT_UI
1190 chrome_button_count += 2;
1192 chrome_button_count += 1;
1198 chrome_button_count += 2;
1202 chrome_button_count += 1;
1205 chrome_button_count += 1;
1207 const float chrome_reserve =
1208 chrome_button_count * chrome_btn_size.x +
1209 std::max(0, chrome_button_count - 1) * chrome_gap + padding;
1213 const float title_x = padding + icon_chip_size + 6.0f;
1214 const float title_y = ImGui::GetCursorPosY() +
1215 (header_height - ImGui::GetTextLineHeight()) * 0.5f;
1216 ImGui::SetCursorPos(ImVec2(title_x, title_y));
1217 const float title_max_x =
1218 std::max(title_x, ImGui::GetWindowWidth() - chrome_reserve - 8.0f);
1219 const float title_avail = std::max(0.0f, title_max_x - title_x);
1220 ImVec2 title_size = ImGui::CalcTextSize(title);
1221 const ImVec2 text_min = ImGui::GetCursorScreenPos();
1222 const ImVec2 text_max =
1223 ImVec2(text_min.x + title_avail, text_min.y + ImGui::GetTextLineHeight());
1224 ImGui::RenderTextEllipsis(draw_list, text_min, text_max, text_max.x, title,
1225 nullptr, &title_size);
1226 const float drawn_title_w = std::min(title_size.x, title_avail);
1227 ImGui::Dummy(ImVec2(drawn_title_w, ImGui::GetTextLineHeight()));
1228 if (title_size.x > title_avail && ImGui::IsItemHovered()) {
1229 ImGui::SetTooltip(
"%s", title);
1234 const float badge_x = title_x + drawn_title_w + 6.0f;
1235 if (badge_x < title_max_x) {
1236 ImGui::SameLine(0.0f, 6.0f);
1241 const float btn_y = header_min.y + (header_height - chrome_btn_size.y) * 0.5f;
1242 float current_x = ImGui::GetWindowWidth() - chrome_btn_size.x - padding;
1245 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1248 ImVec4(0, 0, 0, 0),
"right_sidebar",
"close_panel")) {
1254 current_x -= (chrome_btn_size.x + chrome_gap);
1255 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1261 "lock_selection")) {
1265#ifdef YAZE_BUILD_AGENT_UI
1267 current_x -= (chrome_btn_size.x + chrome_gap);
1268 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1271 false, ImVec4(0, 0, 0, 0),
"right_sidebar",
"agent_clear_chat")) {
1274 current_x -= (chrome_btn_size.x + chrome_gap);
1275 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1278 ImVec4(0, 0, 0, 0),
"right_sidebar",
"agent_save_chat")) {
1282 current_x -= (chrome_btn_size.x + chrome_gap);
1283 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1286 ImVec4(0, 0, 0, 0),
"right_sidebar",
"agent_open_proposals")) {
1294 current_x -= (chrome_btn_size.x + chrome_gap);
1295 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1298 false, ImVec4(0, 0, 0, 0),
"right_sidebar",
"notif_clear_all")) {
1301 current_x -= (chrome_btn_size.x + chrome_gap);
1302 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1304 "Mark All Read",
false, ImVec4(0, 0, 0, 0),
1305 "right_sidebar",
"notif_mark_read")) {
1311 current_x -= (chrome_btn_size.x + chrome_gap);
1312 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1314 "Copy Output",
false, ImVec4(0, 0, 0, 0),
1315 "right_sidebar",
"tool_copy_out")) {
1320 current_x -= (chrome_btn_size.x + chrome_gap);
1321 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, btn_y));
1324 false, ImVec4(0, 0, 0, 0),
"right_sidebar",
"help_open_docs")) {
1329 ImGui::SetCursorPosY(header_height);
1333 const float standard_height =
1335 const float padding =
1337 const float gap = std::max(2.0f, padding * 0.5f);
1338 const float tab_h = standard_height;
1339 const float nav_height = tab_h + padding * 2.0f;
1341 const ImVec2 nav_min = ImGui::GetCursorScreenPos();
1342 const ImVec2 nav_max =
1343 ImVec2(nav_min.x + ImGui::GetWindowWidth(), nav_min.y + nav_height);
1345 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1346 draw_list->AddRectFilled(nav_min, nav_max,
1348 draw_list->AddLine(ImVec2(nav_min.x, nav_max.y), ImVec2(nav_max.x, nav_max.y),
1352 const size_t count = catalog.size();
1354 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + nav_height + 4.0f);
1358 const float avail_w = ImGui::GetWindowWidth() - padding * 2.0f;
1359 const float tab_y = nav_min.y + (nav_height - tab_h) * 0.5f;
1360 std::vector<float> labeled_widths;
1361 labeled_widths.reserve(count);
1362 float labeled_total = gap *
static_cast<float>(count - 1);
1364 const std::string label = absl::StrFormat(
"%s %s", entry.icon, entry.name);
1366 std::max(tab_h, ImGui::CalcTextSize(label.c_str()).x + padding * 2.0f);
1367 labeled_widths.push_back(width);
1368 labeled_total += width;
1370 const bool show_labels = labeled_total <= avail_w;
1371 const float extra_per_tab =
1372 show_labels ? std::max(0.0f, (avail_w - labeled_total) / count) : 0.0f;
1373 const float compact_tab_w =
1374 std::max(tab_h, std::floor((avail_w - (count - 1) * gap) / count));
1375 float tab_x = nav_min.x + padding;
1377 for (
size_t i = 0; i < count; ++i) {
1379 const bool is_active = (current_panel == entry.
type);
1381 show_labels ? labeled_widths[i] + extra_per_tab : compact_tab_w;
1383 const ImVec2 tab_rect_min(tab_x, tab_y);
1384 const ImVec2 tab_rect_max(tab_x + tab_w, tab_y + tab_h);
1386 ImGui::SetCursorScreenPos(tab_rect_min);
1387 const std::string tab_id = absl::StrFormat(
"##drawer_nav_%zu_%s", i,
1389 if (ImGui::InvisibleButton(tab_id.c_str(), ImVec2(tab_w, tab_h))) {
1394 const bool hovered = ImGui::IsItemHovered();
1397 draw_list->AddRectFilled(
1398 tab_rect_min, tab_rect_max,
1402 draw_list->AddLine(ImVec2(tab_rect_min.x + 3.0f, tab_rect_max.y - 1.0f),
1403 ImVec2(tab_rect_max.x - 3.0f, tab_rect_max.y - 1.0f),
1407 const std::string tab_label =
1408 show_labels ? absl::StrFormat(
"%s %s", entry.
icon, entry.
name)
1409 : std::string(entry.
icon);
1410 const ImVec2 label_size = ImGui::CalcTextSize(tab_label.c_str());
1411 const ImVec2 label_pos(tab_rect_min.x + (tab_w - label_size.x) * 0.5f,
1412 tab_rect_min.y + (tab_h - label_size.y) * 0.5f);
1413 const ImVec4 icon_col = (is_active || hovered)
1416 draw_list->AddText(label_pos, ImGui::GetColorU32(icon_col),
1422 draw_list->AddCircleFilled(
1423 ImVec2(tab_rect_max.x - 4.0f, tab_rect_min.y + 4.0f), 3.0f,
1428 std::string tip = entry.
name ? entry.
name :
"";
1431 if (!sc.empty() && sc !=
"Unassigned") {
1432 tip = absl::StrFormat(
"%s (%s)", entry.
name, sc.c_str());
1435 ImGui::SetTooltip(
"%s", tip.c_str());
1437 tab_x += tab_w + gap;
1441 const float header_height =
1443 ImGui::SetCursorPosY(header_height + nav_height +
1452 bool default_open) {
1459 {ImGuiStyleVar_FramePadding, ImVec2(8.0f, 6.0f)},
1460 {ImGuiStyleVar_FrameRounding, 4.0f},
1464 std::string header_text;
1466 header_text = std::string(icon) +
" " + label;
1468 header_text = label;
1471 ImGuiTreeNodeFlags flags =
1472 ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth |
1473 ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_FramePadding;
1475 flags |= ImGuiTreeNodeFlags_DefaultOpen;
1478 bool is_open = ImGui::TreeNodeEx(header_text.c_str(), flags);
1482 ImGui::Indent(4.0f);
1489 ImGui::Unindent(4.0f);
1510 ImGui::TextUnformatted(value);
1515 ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x);
1516 ImGui::TextWrapped(
"%s", text);
1517 ImGui::PopTextWrapPos();
1521 const std::string& action,
const std::string& fallback)
const {
1530 if (shortcut->
keys.empty()) {
1531 return "Unassigned";
1538 const char* description,
1539 const std::string& fallback) {
1549#ifdef YAZE_BUILD_AGENT_UI
1555 "The AI Agent is not initialized. "
1556 "Open the AI Agent from View menu or use Ctrl+Shift+A.");
1563 if (ImGui::BeginChild(
"AgentChatBody", ImVec2(0, 0),
false)) {
1573 "The AI Agent requires agent UI support. "
1574 "Build with YAZE_BUILD_AGENT_UI=ON to enable.");
1579#ifdef YAZE_BUILD_AGENT_UI
1586 std::string selection_context;
1592 struct QuickAction {
1597 std::vector<QuickAction> actions;
1598 if (!selection_context.empty()) {
1599 actions.push_back({
"Explain selection",
1600 "Explain this selection and how to edit it safely.\n\n" +
1601 selection_context});
1604 "Suggest improvements or checks for this selection.\n\n" +
1605 selection_context});
1610 actions.push_back({
"Summarize map",
1611 "Summarize the current overworld map and its key "
1612 "features. Use overworld tools if available."});
1613 actions.push_back({
"List sprites/items",
1614 "List notable sprites or items on the current "
1618 actions.push_back({
"Audit room",
1619 "Summarize the current dungeon room layout, doors, "
1620 "and object density."});
1621 actions.push_back({
"List sprites",
1622 "List sprites in the current dungeon room and any "
1623 "potential conflicts."});
1626 actions.push_back({
"Review tiles",
1627 "Review the current tileset usage and point out any "
1628 "obvious issues."});
1629 actions.push_back({
"Palette check",
1630 "Check palette usage for contrast/readability "
1634 actions.push_back({
"Palette audit",
1635 "Audit the active palette for hue/contrast balance "
1636 "and note risks."});
1637 actions.push_back({
"Theme ideas",
1638 "Suggest a palette variation that fits the current "
1642 actions.push_back({
"Sprite review",
1643 "Review the selected sprite properties and suggest "
1647 actions.push_back({
"Copy edit",
1648 "Review the current message text for clarity and "
1649 "style improvements."});
1652 actions.push_back({
"ASM review",
1653 "Review the current ASM changes for risks and style "
1657 actions.push_back({
"Hex context",
1658 "Explain what the current hex selection likely "
1662 actions.push_back({
"Test suggestion",
1663 "Propose a short emulator test to validate the "
1664 "current feature."});
1667 actions.push_back({
"Agent config review",
1668 "Review current agent configuration for practical "
1672 actions.push_back({
"Agent overview",
1673 "Suggest the next best agent-assisted action for the "
1674 "current editor context."});
1678 if (actions.empty()) {
1682 ImGui::TextColored(accent, tr(
"%s Editor Actions"),
ICON_MD_BOLT);
1686 int columns = ImGui::GetContentRegionAvail().x > 420.0f ? 2 : 1;
1687 if (ImGui::BeginTable(
"AgentQuickActionsTable", columns,
1688 ImGuiTableFlags_SizingStretchSame)) {
1689 for (
const auto& action : actions) {
1690 ImGui::TableNextColumn();
1691 if (ImGui::Button(action.label, ImVec2(-1, 0))) {
1716 "The proposal system is not initialized. "
1717 "Proposals will appear here when the AI Agent creates them.");
1734 "Settings will be available once initialized. "
1735 "This panel provides quick access to application settings.");
1770 const char* editor_name =
"No Editor Selected";
1775 editor_name =
"Overworld Editor";
1779 editor_name =
"Dungeon Editor";
1783 editor_name =
"Graphics Editor";
1787 editor_name =
"Palette Editor";
1791 editor_name =
"Music Editor";
1795 editor_name =
"Screen Editor";
1799 editor_name =
"Sprite Editor";
1803 editor_name =
"Message Editor";
1807 editor_name =
"Emulator";
1824 ImGui::Indent(8.0f);
1828 absl::StrFormat(
"%s+Shift+S", ctrl));
1832 absl::StrFormat(
"%s+Shift+P", ctrl));
1834 absl::StrFormat(
"%s+Shift+K", ctrl));
1836 absl::StrFormat(
"%s+B", ctrl));
1839 ImGui::Unindent(8.0f);
1848 ImGui::Indent(8.0f);
1850 DrawPanelValue(
"Alt+1 / Alt+2 / Alt+3",
"Switch World (LW/DW/SP)");
1859 ImGui::Unindent(8.0f);
1864 ImGui::Indent(8.0f);
1869 absl::StrFormat(
"%s+C", ctrl));
1871 absl::StrFormat(
"%s+V", ctrl));
1872 DrawShortcutRow(
"dungeon.object.delete",
"Delete selection",
"Delete");
1876 ImGui::Unindent(8.0f);
1881 ImGui::Indent(8.0f);
1889 absl::StrFormat(
"%s+G", ctrl));
1890 ImGui::Unindent(8.0f);
1895 ImGui::Indent(8.0f);
1899 ImGui::Unindent(8.0f);
1904 ImGui::Indent(8.0f);
1910 ImGui::Unindent(8.0f);
1915 ImGui::Indent(8.0f);
1917 "Insert Line Break");
1919 ImGui::Unindent(8.0f);
1924 ImGui::Indent(8.0f);
1928 ImGui::TextWrapped(tr(
"Select an editor to see specific shortcuts."));
1930 ImGui::Unindent(8.0f);
1939 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1941 ImGui::TextWrapped(tr(
"Paint tiles by selecting from Tile16 Selector"));
1944 tr(
"Switch between Light World, Dark World, and Special Areas"));
1947 tr(
"Use Entity Mode to place entrances, exits, items, and sprites"));
1950 tr(
"Right-click on the map to pick a tile for painting"));
1955 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1958 tr(
"Select rooms from the Room Selector or Room Matrix"));
1960 ImGui::TextWrapped(tr(
"Place objects using the Object Editor panel"));
1963 tr(
"Edit room headers for palette, GFX, and floor settings"));
1965 ImGui::TextWrapped(tr(
"Multiple rooms can be opened in separate tabs"));
1970 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1972 ImGui::TextWrapped(tr(
"Browse graphics sheets using the Sheet Browser"));
1974 ImGui::TextWrapped(tr(
"Edit pixels directly with the Pixel Editor"));
1976 ImGui::TextWrapped(tr(
"Choose palettes from Palette Controls"));
1978 ImGui::TextWrapped(tr(
"View 3D objects like rupees and crystals"));
1983 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1985 ImGui::TextWrapped(tr(
"Edit overworld, dungeon, and sprite palettes"));
1987 ImGui::TextWrapped(tr(
"Use Quick Access for color harmony tools"));
1989 ImGui::TextWrapped(tr(
"Changes update in real-time across all editors"));
1994 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1996 ImGui::TextWrapped(tr(
"Browse songs in the Song Browser"));
1998 ImGui::TextWrapped(tr(
"Use the tracker for playback control"));
2000 ImGui::TextWrapped(tr(
"Edit instruments and BRR samples"));
2005 ImGui::GetStyleColorVec4(ImGuiCol_Text));
2007 ImGui::TextWrapped(tr(
"Edit all in-game dialog messages"));
2009 ImGui::TextWrapped(tr(
"Preview text rendering with the font atlas"));
2011 ImGui::TextWrapped(tr(
"Manage the compression dictionary"));
2016 ImGui::TextWrapped(tr(
"Open a ROM file via File > Open ROM"));
2018 ImGui::TextWrapped(tr(
"Select an editor from the sidebar"));
2020 ImGui::TextWrapped(tr(
"Use panels to access tools and settings"));
2022 ImGui::TextWrapped(tr(
"Save your work via File > Save ROM"));
2028 const float button_width = ImGui::GetContentRegionAvail().x;
2031 {ImGuiStyleVar_FramePadding, ImVec2(10.0f, 7.0f)},
2032 {ImGuiStyleVar_FrameRounding, 6.0f},
2033 {ImGuiStyleVar_FrameBorderSize, 1.0f},
2036 auto draw_link_button = [&](
const char* icon,
const char* label,
2043 const std::string text =
2045 if (ImGui::Button(text.c_str(), ImVec2(button_width, 0))) {
2051 "https://github.com/scawful/yaze/wiki");
2054 "https://github.com/scawful/yaze/issues/new");
2057 "https://discord.gg/zU5qDm8MZg");
2065 "A comprehensive editor for The Legend of Zelda: "
2066 "A Link to the Past ROM files.");
2072 ImGui::Text(tr(
"Written by: scawful"));
2073 ImGui::Text(tr(
"Special Thanks: Zarby89, JaredBrian"));
2091 float avail = ImGui::GetContentRegionAvail().x;
2101 ImVec2(avail * 0.5f - 4.0f, 0))) {
2106 ImVec2(avail * 0.5f - 4.0f, 0))) {
2123 const auto cancel_build =
2126 if (build_status.visible || run_status.visible || !workflow_history.empty()) {
2128 if (build_status.visible) {
2129 DrawWorkflowSummaryCard(
"Build",
ICON_MD_BUILD, build_status,
2133 if (run_status.visible) {
2137 if (!workflow_history.empty()) {
2139 const auto preview_entries =
2141 for (
size_t i = 0; i < preview_entries.size(); ++i) {
2142 ImGui::PushID(
static_cast<int>(i));
2143 DrawWorkflowPreviewEntry(preview_entries[i], workflow_callbacks);
2145 if (i + 1 < preview_entries.size()) {
2149 if (workflow_history.size() > preview_entries.size()) {
2151 ImGui::TextDisabled(
2152 tr(
"+%zu more entries available in Workflow Output"),
2153 workflow_history.size() - preview_entries.size());
2156 " View Full History##workflow_view_full")) {
2168 if (history.empty()) {
2174 "Notifications will appear here when actions complete.");
2180 if (unread_count > 0) {
2191 ImGuiChildFlags_None, ImGuiWindowFlags_AlwaysVerticalScrollbar);
2192 if (notification_list_open) {
2194 auto now = std::chrono::system_clock::now();
2197 bool shown_today =
false;
2198 bool shown_yesterday =
false;
2199 bool shown_older =
false;
2201 for (
const auto& entry : history) {
2203 std::chrono::duration_cast<std::chrono::hours>(now - entry.timestamp)
2207 if (diff < 24 && !shown_today) {
2210 }
else if (diff >= 24 && diff < 48 && !shown_yesterday) {
2213 shown_yesterday =
true;
2214 }
else if (diff >= 48 && !shown_older) {
2221 ImGui::PushID(&entry);
2226 switch (entry.type) {
2256 ImGui::TextWrapped(
"%s", entry.message.c_str());
2259 auto diff_sec = std::chrono::duration_cast<std::chrono::seconds>(
2260 now - entry.timestamp)
2262 std::string time_str;
2263 if (diff_sec < 60) {
2264 time_str =
"just now";
2265 }
else if (diff_sec < 3600) {
2266 time_str = absl::StrFormat(
"%dm ago", diff_sec / 60);
2267 }
else if (diff_sec < 86400) {
2268 time_str = absl::StrFormat(
"%dh ago", diff_sec / 3600);
2270 time_str = absl::StrFormat(
"%dd ago", diff_sec / 86400);
2312 opts.
title =
"No tool output";
2314 "Run a project-graph query from the editor to inspect its output here.";
2330 ImGui::PushID(
"summary");
2332 if (!parsed.
empty()) {
2335 DrawJsonObjectFields(parsed);
2341 ImGui::PushID(
"resolved_source");
2343 if (!parsed[
"source"].empty()) {
2346 DrawJsonObjectFields(parsed[
"source"]);
2350 DrawJsonObjectArraySection(
"Matching Symbols", parsed[
"matching_symbols"],
2352 DrawJsonObjectArraySection(
"Sources", parsed[
"sources"],
2355 DrawJsonObjectArraySection(
"Writes", parsed[
"writes"],
2358 DrawJsonObjectArraySection(
"Symbols", parsed[
"symbols"],
2362 if (ImGui::CollapsingHeader(tr(
"Raw Output"),
2363 has_json ? 0 : ImGuiTreeNodeFlags_DefaultOpen)) {
2364 if (ImGui::BeginChild(
"##tool_output_result", ImVec2(0.0f, 220.0f),
true)) {
2374 {ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
2381 bool interacted =
false;
2386 if (ImGui::IsItemHovered()) {
2387 const std::string tooltip =
2388 sidebar_open ? tr(
"Close Right Sidebar")
2389 : absl::StrFormat(
"Open Right Sidebar (%s)",
2391 ImGui::SetTooltip(
"%s", tooltip.c_str());
2398 const float frame_padding = ImGui::GetStyle().FramePadding.x;
2400 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.
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.
bool DrawSidebarToggleButton()
void SetToolOutput(std::string title, std::string query, std::string content, ToolOutputActions actions={})
void ToggleLastDrawer()
Toggle the right sidebar using its last selected persistent tab.
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()
float GetExpandedDrawerWidth() const
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.
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
PanelSizeLimits GetSharedPanelSizeLimits() const
void DrawQuickActionButtons()
std::unordered_map< std::string, PanelSizeLimits > panel_size_limits_
void DrawPropertiesPanel()
void DrawAgentChatPanel()
SettingsPanel * settings_panel_
void DrawHeaderContextBadge(PanelType type, float available_width)
void DrawNotificationsPanel()
ToastManager * toast_manager_
bool IsDrawerExpanded() const
Check if any drawer is currently expanded (or animating closed)
float GetDrawerWidth() const
std::string tool_output_content_
void OnHostVisibilityChanged(bool visible)
Snap transient animations when host visibility changes.
void DrawPanelLabel(const char *label)
static float GetSidebarToggleWidth()
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
PanelType last_active_panel_
ProposalDrawer * proposal_drawer_
std::string tool_output_query_
ProjectManagementPanel * project_panel_
void DrawToolOutputPanel()
void DrawDrawerNavStrip(PanelType current_panel)
PanelSizeLimits GetPanelSizeLimits(PanelType type) const
std::function< SettingsPanel *()> settings_panel_provider_
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()
static float GetStandardWidgetHeight()
static float GetStandardSpacing()
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_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_VIEW_SIDEBAR
#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()
ImVec2 ScaledSize(float base_width, float base_height)
Returns a DPI-scaled version of a fixed pixel size.
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