1#define IMGUI_DEFINE_MATH_OPERATORS
10#include "absl/strings/str_format.h"
21#include "imgui/imgui.h"
22#include "imgui/imgui_internal.h"
55 if (category ==
"Dungeon")
57 if (category ==
"Overworld")
59 if (category ==
"Graphics")
61 if (category ==
"Palette")
63 if (category ==
"Sprite")
65 if (category ==
"Music")
67 if (category ==
"Message")
69 if (category ==
"Screen")
71 if (category ==
"Emulator")
73 if (category ==
"Assembly")
75 if (category ==
"Settings")
77 if (category ==
"Memory")
79 if (category ==
"Agent")
89 const std::string& category) {
93 if (category ==
"Dungeon") {
95 return {0.95f, 0.75f, 0.20f, 1.0f, 0.95f, 0.75f, 0.20f};
97 if (category ==
"Overworld") {
99 return {0.30f, 0.85f, 0.45f, 1.0f, 0.30f, 0.85f, 0.45f};
101 if (category ==
"Graphics") {
103 return {0.40f, 0.70f, 0.95f, 1.0f, 0.40f, 0.70f, 0.95f};
105 if (category ==
"Palette") {
107 return {0.90f, 0.40f, 0.70f, 1.0f, 0.90f, 0.40f, 0.70f};
109 if (category ==
"Sprite") {
111 return {0.30f, 0.85f, 0.85f, 1.0f, 0.30f, 0.85f, 0.85f};
113 if (category ==
"Music") {
115 return {0.70f, 0.40f, 0.90f, 1.0f, 0.70f, 0.40f, 0.90f};
117 if (category ==
"Message") {
119 return {0.95f, 0.90f, 0.40f, 1.0f, 0.95f, 0.90f, 0.40f};
121 if (category ==
"Screen") {
123 return {0.90f, 0.92f, 0.95f, 1.0f, 0.90f, 0.92f, 0.95f};
125 if (category ==
"Emulator") {
127 return {0.90f, 0.35f, 0.40f, 1.0f, 0.90f, 0.35f, 0.40f};
129 if (category ==
"Assembly") {
131 return {0.40f, 0.90f, 0.50f, 1.0f, 0.40f, 0.90f, 0.50f};
133 if (category ==
"Settings") {
135 return {0.60f, 0.70f, 0.80f, 1.0f, 0.60f, 0.70f, 0.80f};
137 if (category ==
"Memory") {
139 return {0.95f, 0.60f, 0.25f, 1.0f, 0.95f, 0.60f, 0.25f};
141 if (category ==
"Agent") {
143 return {0.60f, 0.40f, 0.95f, 1.0f, 0.60f, 0.40f, 0.95f};
147 return {0.50f, 0.60f, 0.80f, 1.0f, 0.50f, 0.60f, 0.80f};
151 float viewport_width) {
153 if (viewport_width <= 0.0f) {
156 bounds.max_width = std::max(520.0f, fallback);
160 const float min_width = std::max(220.0f, viewport_width * 0.18f);
161 const float max_width = std::max(min_width + 20.0f, viewport_width * 0.62f);
164 bounds.max_width = max_width;
171 return default_width;
188 const float clamped = std::clamp(width, bounds.min_width, bounds.max_width);
210 const float clamped =
211 std::max(180.0f, width > 0.0f ? width : fallback);
229 std::unordered_map<std::string, std::string>();
231 std::unordered_map<std::string, std::string>();
233 LOG_INFO(
"PanelManager",
"Registered session %zu (total: %zu)", session_id,
256 LOG_INFO(
"PanelManager",
"Unregistered session %zu (total: %zu)",
273 const std::string old_key =
274 (session_map.find(scope) != session_map.end()) ? session_map[scope] :
"";
275 if (old_key == key) {
278 session_map[scope] = std::move(key);
288 const auto& session_map = sit->second;
289 auto it = session_map.find(scope);
290 if (it == session_map.end()) {
297 const std::string& canonical_base_id) {
298 if (legacy_base_id.empty() || canonical_base_id.empty() ||
299 legacy_base_id == canonical_base_id) {
310 if (panel_id.empty()) {
314 std::string resolved = panel_id;
315 std::unordered_set<std::string> visited;
316 visited.insert(resolved);
318 for (
int depth = 0; depth < 16; ++depth) {
324 const std::string& next = alias_it->second;
325 if (next == resolved || visited.count(next) > 0) {
330 visited.insert(resolved);
337 const std::string& old_key,
338 const std::string& new_key) {
343 if (!new_key.empty()) {
352 for (
const auto& prefixed_id : sit->second) {
353 auto dit =
cards_.find(prefixed_id);
354 if (dit ==
cards_.end()) {
369 if (!base_id.empty()) {
376 size_t session_id,
const std::string& prefixed_id)
const {
381 const auto& reverse = sit->second;
382 auto it = reverse.find(prefixed_id);
383 if (it == reverse.end()) {
400 std::string panel_id =
403 bool already_registered = (
cards_.find(panel_id) !=
cards_.end());
406 "Panel '%s' already registered, skipping duplicate",
410 if (!already_registered) {
422 cards_[panel_id] = panel_info;
424 LOG_INFO(
"PanelManager",
"Registered card %s -> %s for session %zu",
425 canonical_info.
card_id.c_str(), panel_id.c_str(), session_id);
436 const std::string& display_name,
437 const std::string& icon,
438 const std::string& category,
439 const std::string& shortcut_hint,
int priority,
440 std::function<
void()> on_show,
441 std::function<
void()> on_hide,
442 bool visible_by_default) {
457 if (visible_by_default) {
463 const std::string& base_card_id) {
466 if (prefixed_id.empty()) {
470 auto it =
cards_.find(prefixed_id);
472 LOG_INFO(
"PanelManager",
"Unregistered card: %s", prefixed_id.c_str());
479 card_list.erase(std::remove(card_list.begin(), card_list.end(),
484 mapping.erase(canonical_base_id);
491 session_card_list.erase(std::remove(session_card_list.begin(),
492 session_card_list.end(), prefixed_id),
493 session_card_list.end());
500 std::vector<std::string> to_remove;
503 for (
const auto& [card_id, card_info] :
cards_) {
504 if (card_id.find(prefix) == 0) {
505 to_remove.push_back(card_id);
510 for (
const auto& card_id : to_remove) {
514 LOG_INFO(
"PanelManager",
"Unregistered card with prefix '%s': %s",
515 prefix.c_str(), card_id.c_str());
520 card_list.erase(std::remove_if(card_list.begin(), card_list.end(),
521 [&prefix](
const std::string&
id) {
522 return id.find(prefix) == 0;
540 LOG_INFO(
"PanelManager",
"Cleared all cards");
549 LOG_ERROR(
"PanelManager",
"Attempted to register null EditorPanel");
554 auto* resource_panel =
dynamic_cast<ResourcePanel*
>(panel.get());
555 if (resource_panel) {
559 std::string panel_id = panel->GetId();
564 "EditorPanel '%s' already registered, skipping registry add",
578 if (resource_panel) {
579 std::string type = resource_panel->GetResourceType();
584 LOG_INFO(
"PanelManager",
"Registered registry EditorPanel: %s",
601 LOG_ERROR(
"PanelManager",
"Attempted to register null EditorPanel");
606 auto* resource_panel =
dynamic_cast<ResourcePanel*
>(panel.get());
607 if (resource_panel) {
611 std::string panel_id = panel->GetId();
615 LOG_WARN(
"PanelManager",
"EditorPanel '%s' already registered, skipping",
624 bool visible_by_default = panel->IsVisibleByDefault();
630 if (visible_by_default) {
638 if (resource_panel) {
639 std::string type = resource_panel->GetResourceType();
644 LOG_INFO(
"PanelManager",
"Registered EditorPanel: %s (%s)", panel_id.c_str(),
657 auto& panel_list = it->second;
662 if (resource_type ==
"room")
664 else if (resource_type ==
"song")
666 else if (resource_type ==
"sheet")
668 else if (resource_type ==
"map")
673 while (panel_list.size() >= limit) {
675 std::string panel_to_evict;
676 for (
const auto& panel_id : panel_list) {
678 panel_to_evict = panel_id;
684 if (panel_to_evict.empty()) {
685 panel_to_evict = panel_list.front();
686 LOG_INFO(
"PanelManager",
"All %s panels pinned, evicting oldest: %s",
687 resource_type.c_str(), panel_to_evict.c_str());
690 "Evicting non-pinned resource panel: %s (type: %s)",
691 panel_to_evict.c_str(), resource_type.c_str());
695 panel_list.remove(panel_to_evict);
706 std::string type = type_it->second;
711 list.remove(panel_id);
712 list.push_back(panel_id);
719 it->second->OnClose();
724 LOG_INFO(
"PanelManager",
"Unregistered EditorPanel: %s", panel_id.c_str());
734 return it->second.get();
740 const std::string& prefixed_panel_id,
const std::string& base_panel_id) {
743 return prefixed_it->second.get();
747 return base_it->second.get();
753 const std::string& prefixed_panel_id,
754 const std::string& base_panel_id)
const {
757 return prefixed_it->second.get();
761 return base_it->second.get();
779 bool animations_enabled = animator.IsEnabled();
782 const auto resolve_switch_speed = [&](
float snappy,
float standard,
784 switch (animator.motion_profile()) {
794 const float category_transition_speed =
795 resolve_switch_speed(8.5f, 6.0f, 4.5f);
796 const float panel_fade_speed = resolve_switch_speed(11.0f, 8.0f, 6.0f);
799 float global_alpha = 1.0f;
800 if (animations_enabled) {
801 global_alpha = animator.Animate(
"global",
"category_transition", 1.0f,
802 category_transition_speed);
805 for (
const auto& prefixed_panel_id : session_it->second) {
806 auto descriptor_it =
cards_.find(prefixed_panel_id);
807 if (descriptor_it ==
cards_.end()) {
812 std::string base_panel_id =
814 if (base_panel_id.empty()) {
815 base_panel_id = prefixed_panel_id;
826 bool should_draw =
false;
840 float target_alpha = should_draw ? 1.0f : 0.0f;
843 float current_alpha = 1.0f;
844 if (animations_enabled) {
845 current_alpha = animator.Animate(prefixed_panel_id,
"panel_alpha",
846 target_alpha, panel_fade_speed);
847 current_alpha *= global_alpha;
849 current_alpha = target_alpha;
853 if (current_alpha < 0.01f) {
862 std::string display_name = descriptor.
display_name.empty()
865 std::string icon = descriptor.
icon.empty() ? panel->
GetIcon()
875 gui::PanelWindow window(display_name.c_str(), icon.c_str(), visibility_flag);
883 if (preferred_width > 0.0f) {
897 std::optional<gui::StyleVarGuard> alpha_guard;
898 if (current_alpha < 1.0f) {
899 alpha_guard.emplace(ImGuiStyleVar_Alpha, current_alpha);
902 if (window.
Begin(visibility_flag)) {
910 if (visibility_flag && !*visibility_flag) {
917 const std::string& to_category) {
918 if (from_category == to_category) {
922 LOG_INFO(
"PanelManager",
"Switching from category '%s' to '%s'",
923 from_category.c_str(), to_category.c_str());
946 const std::string& base_card_id) {
949 if (prefixed_id.empty()) {
953 auto it =
cards_.find(prefixed_id);
955 const bool was_visible =
956 (it->second.visibility_flag && *it->second.visibility_flag);
957 if (it->second.visibility_flag) {
958 *it->second.visibility_flag =
true;
960 if (it->second.on_show) {
961 it->second.on_show();
983 const std::string& base_card_id) {
986 if (prefixed_id.empty()) {
990 auto it =
cards_.find(prefixed_id);
992 const bool was_visible =
993 (it->second.visibility_flag && *it->second.visibility_flag);
994 if (it->second.visibility_flag) {
995 *it->second.visibility_flag =
false;
997 if (it->second.on_hide) {
998 it->second.on_hide();
1011 it->second.category,
1012 false, session_id));
1020 const std::string& base_card_id) {
1023 if (prefixed_id.empty()) {
1027 auto it =
cards_.find(prefixed_id);
1028 if (it !=
cards_.end() && it->second.visibility_flag) {
1029 bool new_state = !(*it->second.visibility_flag);
1030 *it->second.visibility_flag = new_state;
1032 if (new_state && it->second.on_show) {
1033 it->second.on_show();
1034 }
else if (!new_state && it->second.on_hide) {
1035 it->second.on_hide();
1049 it->second.category,
1050 new_state, session_id));
1058 const std::string& base_card_id)
const {
1061 if (prefixed_id.empty()) {
1065 auto it =
cards_.find(prefixed_id);
1066 if (it !=
cards_.end() && it->second.visibility_flag) {
1067 return *it->second.visibility_flag;
1073 const std::string& base_card_id) {
1076 if (prefixed_id.empty()) {
1080 auto it =
cards_.find(prefixed_id);
1081 if (it !=
cards_.end()) {
1082 return it->second.visibility_flag;
1094 for (
const auto& prefixed_card_id : it->second) {
1095 auto card_it =
cards_.find(prefixed_card_id);
1096 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1097 *card_it->second.visibility_flag =
true;
1098 if (card_it->second.on_show) {
1099 card_it->second.on_show();
1109 for (
const auto& prefixed_card_id : it->second) {
1110 auto card_it =
cards_.find(prefixed_card_id);
1111 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1112 *card_it->second.visibility_flag =
false;
1113 if (card_it->second.on_hide) {
1114 card_it->second.on_hide();
1122 const std::string& category) {
1125 for (
const auto& prefixed_card_id : it->second) {
1126 auto card_it =
cards_.find(prefixed_card_id);
1127 if (card_it !=
cards_.end() && card_it->second.category == category) {
1128 if (card_it->second.visibility_flag) {
1129 *card_it->second.visibility_flag =
true;
1131 if (card_it->second.on_show) {
1132 card_it->second.on_show();
1140 const std::string& category) {
1143 for (
const auto& prefixed_card_id : it->second) {
1144 auto card_it =
cards_.find(prefixed_card_id);
1145 if (card_it !=
cards_.end() && card_it->second.category == category) {
1146 if (card_it->second.visibility_flag) {
1147 *card_it->second.visibility_flag =
false;
1149 if (card_it->second.on_hide) {
1150 card_it->second.on_hide();
1158 const std::string& base_card_id) {
1161 if (prefixed_id.empty()) {
1165 auto target_it =
cards_.find(prefixed_id);
1166 if (target_it ==
cards_.end()) {
1170 std::string category = target_it->second.category;
1184 size_t session_id)
const {
1193 size_t session_id,
const std::string& category)
const {
1194 std::vector<PanelDescriptor> result;
1198 for (
const auto& prefixed_card_id : it->second) {
1199 auto card_it =
cards_.find(prefixed_card_id);
1200 if (card_it !=
cards_.end() && card_it->second.category == category) {
1201 result.push_back(card_it->second);
1207 std::sort(result.begin(), result.end(),
1209 return a.priority < b.priority;
1216 size_t session_id)
const {
1217 std::vector<std::string> categories;
1221 for (
const auto& prefixed_card_id : it->second) {
1222 auto card_it =
cards_.find(prefixed_card_id);
1223 if (card_it !=
cards_.end()) {
1224 if (std::find(categories.begin(), categories.end(),
1225 card_it->second.category) == categories.end()) {
1226 categories.push_back(card_it->second.category);
1235 size_t session_id,
const std::string& base_card_id)
const {
1238 if (prefixed_id.empty()) {
1242 auto it =
cards_.find(prefixed_id);
1243 if (it !=
cards_.end()) {
1250 std::vector<std::string> categories;
1251 for (
const auto& [card_id, card_info] :
cards_) {
1252 if (std::find(categories.begin(), categories.end(), card_info.category) ==
1254 categories.push_back(card_info.category);
1265 size_t session_id)
const {
1266 std::vector<std::string> visible_panels;
1270 return visible_panels;
1273 for (
const auto& [base_id, prefixed_id] : session_it->second) {
1274 auto card_it =
cards_.find(prefixed_id);
1275 if (card_it !=
cards_.end() && card_it->second.visibility_flag &&
1276 *card_it->second.visibility_flag) {
1277 visible_panels.push_back(base_id);
1281 return visible_panels;
1285 size_t session_id,
const std::vector<std::string>& panel_ids) {
1292 std::unordered_set<std::string> visible_set;
1293 visible_set.reserve(panel_ids.size());
1294 for (
const auto& panel_id : panel_ids) {
1299 for (
const auto& [base_id, prefixed_id] : session_it->second) {
1300 auto card_it =
cards_.find(prefixed_id);
1301 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1302 bool should_be_visible = visible_set.count(base_id) > 0;
1303 *card_it->second.visibility_flag = should_be_visible;
1307 LOG_INFO(
"PanelManager",
"Set %zu panels visible for session %zu",
1308 panel_ids.size(), session_id);
1312 size_t session_id)
const {
1313 std::unordered_map<std::string, bool> state;
1320 for (
const auto& [base_id, prefixed_id] : session_it->second) {
1321 auto card_it =
cards_.find(prefixed_id);
1322 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1323 state[base_id] = *card_it->second.visibility_flag;
1331 size_t session_id,
const std::unordered_map<std::string, bool>& state,
1332 bool publish_events) {
1336 "Cannot restore visibility: session %zu not found", session_id);
1340 size_t restored = 0;
1341 for (
const auto& [base_id, visible] : state) {
1343 auto mapping_it = session_it->second.find(canonical_base_id);
1344 if (mapping_it != session_it->second.end()) {
1345 auto card_it =
cards_.find(mapping_it->second);
1346 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1347 *card_it->second.visibility_flag = visible;
1348 if (publish_events) {
1351 mapping_it->second, canonical_base_id, card_it->second.category,
1352 visible, session_id));
1361 "Restored visibility for %zu/%zu panels in session %zu", restored,
1362 state.size(), session_id);
1367 std::unordered_map<std::string, bool> state;
1370 std::string base_id = prefixed_id;
1372 if (prefixed_id.size() > 2 && prefixed_id[0] ==
's') {
1373 size_t dot_pos = prefixed_id.find(
'.');
1374 if (dot_pos != std::string::npos && dot_pos + 1 < prefixed_id.size()) {
1375 base_id = prefixed_id.substr(dot_pos + 1);
1378 state[base_id] = pinned;
1385 const std::unordered_map<std::string, bool>& state) {
1386 std::unordered_map<std::string, bool> canonical_state;
1387 canonical_state.reserve(state.size());
1388 for (
const auto& [base_id, pinned] : state) {
1394 for (
const auto& [base_id, prefixed_id] : card_mapping) {
1395 auto state_it = canonical_state.find(base_id);
1396 if (state_it != canonical_state.end()) {
1402 LOG_INFO(
"PanelManager",
"Restored pinned state for %zu panels",
1403 canonical_state.size());
1407 size_t session_id,
const std::string& base_card_id)
const {
1425 size_t session_id,
const std::vector<PanelDescriptor>& cards) {
1428 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
1439 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
1445 int card_count =
static_cast<int>(cards.size());
1448 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
1449 ImGui::IsKeyPressed(ImGuiKey_J)) {
1452 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
1453 ImGui::IsKeyPressed(ImGuiKey_K)) {
1458 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
1461 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
1467 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
1468 ImGui::IsKeyPressed(ImGuiKey_Space)) {
1484 size_t session_id,
const std::string& category)
const {
1488 std::sort(panels.begin(), panels.end(),
1491 bool a_pinned = IsPanelPinned(session_id, a.card_id);
1492 bool b_pinned = IsPanelPinned(session_id, b.card_id);
1493 if (a_pinned != b_pinned) return a_pinned > b_pinned;
1495 auto a_it = last_used_at_.find(a.card_id);
1496 auto b_it = last_used_at_.find(b.card_id);
1497 uint64_t a_time = (a_it != last_used_at_.end()) ? a_it->second : 0;
1498 uint64_t b_time = (b_it != last_used_at_.end()) ? b_it->second : 0;
1499 if (a_time != b_time) return a_time > b_time;
1502 return a.priority < b.priority;
1513 const std::string& description) {
1519 for (
const auto& [card_id, card_info] :
cards_) {
1520 if (card_info.visibility_flag && *card_info.visibility_flag) {
1527 LOG_INFO(
"PanelManager",
"Saved preset: %s (%zu cards)", name.c_str(),
1538 for (
auto& [card_id, card_info] :
cards_) {
1539 if (card_info.visibility_flag) {
1540 *card_info.visibility_flag =
false;
1545 for (
const auto& card_id : it->second.visible_cards) {
1546 auto card_it =
cards_.find(card_id);
1547 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1548 *card_it->second.visibility_flag =
true;
1549 if (card_it->second.on_show) {
1550 card_it->second.on_show();
1555 LOG_INFO(
"PanelManager",
"Loaded preset: %s", name.c_str());
1565 std::vector<WorkspacePreset> result;
1566 for (
const auto& [name, preset] :
presets_) {
1567 result.push_back(preset);
1589 LOG_INFO(
"PanelManager",
"Reset to defaults for session %zu", session_id);
1595 if (category.empty()) {
1597 "No category found for editor type %d, skipping reset",
1598 static_cast<int>(editor_type));
1609 for (
const auto& card_id : default_panels) {
1611 LOG_INFO(
"PanelManager",
"Showing default card: %s", card_id.c_str());
1615 LOG_INFO(
"PanelManager",
"Reset %s editor to defaults (%zu cards visible)",
1616 category.c_str(), default_panels.size());
1627 for (
const auto& prefixed_card_id : it->second) {
1628 auto card_it =
cards_.find(prefixed_card_id);
1629 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1630 if (*card_it->second.visibility_flag) {
1644 const std::string& base_id)
const {
1649 const std::string& base_id,
1653 return canonical_base_id;
1656 return absl::StrFormat(
"s%zu.%s", session_id, canonical_base_id);
1658 return canonical_base_id;
1670 const std::string& base_id)
const {
1675 auto card_it = session_it->second.find(resolved_base_id);
1676 if (card_it != session_it->second.end()) {
1677 return card_it->second;
1683 return resolved_base_id;
1692 std::string panel_id =
1694 bool already_registered = (
cards_.find(panel_id) !=
cards_.end());
1703 const std::string& base_id,
1704 const std::string& panel_id) {
1708 if (std::find(card_list.begin(), card_list.end(), panel_id) ==
1710 card_list.push_back(panel_id);
1719 for (
const auto& prefixed_card_id : it->second) {
1723 cards_.erase(prefixed_card_id);
1732 if (!config_dir_result.ok()) {
1733 LOG_ERROR(
"PanelManager",
"Failed to get config directory: %s",
1734 config_dir_result.status().ToString().c_str());
1738 std::filesystem::path presets_file =
1739 *config_dir_result /
"layout_presets.json";
1746 for (
const auto& [name, preset] :
presets_) {
1748 preset_json[
"name"] = preset.name;
1749 preset_json[
"description"] = preset.description;
1750 preset_json[
"visible_cards"] = preset.visible_cards;
1751 j[
"presets"][name] = preset_json;
1754 std::ofstream file(presets_file);
1755 if (!file.is_open()) {
1756 LOG_ERROR(
"PanelManager",
"Failed to open file for writing: %s",
1757 presets_file.string().c_str());
1765 presets_file.string().c_str());
1766 }
catch (
const std::exception& e) {
1767 LOG_ERROR(
"PanelManager",
"Error saving presets: %s", e.what());
1773 if (!config_dir_result.ok()) {
1774 LOG_WARN(
"PanelManager",
"Failed to get config directory: %s",
1775 config_dir_result.status().ToString().c_str());
1779 std::filesystem::path presets_file =
1780 *config_dir_result /
"layout_presets.json";
1783 LOG_INFO(
"PanelManager",
"No presets file found at %s",
1784 presets_file.string().c_str());
1789 std::ifstream file(presets_file);
1790 if (!file.is_open()) {
1791 LOG_WARN(
"PanelManager",
"Failed to open presets file: %s",
1792 presets_file.string().c_str());
1801 LOG_WARN(
"PanelManager",
"Invalid presets file format");
1805 size_t loaded_count = 0;
1812 for (
auto& [name, preset_json] : j[
"presets"].
items()) {
1814 preset.
name = preset_json.value(
"name", name);
1815 preset.
description = preset_json.value(
"description",
"");
1817 if (preset_json.contains(
"visible_cards")) {
1818 yaze::Json visible_cards = preset_json[
"visible_cards"];
1820 for (
const auto& card : visible_cards) {
1821 if (card.is_string()) {
1832 LOG_INFO(
"PanelManager",
"Loaded %zu presets from %s", loaded_count,
1833 presets_file.string().c_str());
1834 }
catch (
const std::exception& e) {
1835 LOG_ERROR(
"PanelManager",
"Error loading presets: %s", e.what());
1846 return it->second.get();
1852 const std::string& root_path) {
1854 auto browser = std::make_unique<FileBrowser>();
1857 browser->SetFileClickedCallback([
this, category](
const std::string& path) {
1867 if (!root_path.empty()) {
1868 browser->SetRootPath(root_path);
1872 if (category ==
"Assembly") {
1873 browser->SetFileFilter({
".asm",
".s",
".65c816",
".inc",
".h"});
1877 LOG_INFO(
"PanelManager",
"Enabled file browser for category: %s",
1892 const std::string& path) {
1895 it->second->SetRootPath(path);
1904 const std::string& base_card_id,
1908 if (prefixed_id.empty()) {
1909 prefixed_id =
MakePanelId(session_id, canonical_base_id);
1915 const std::string& base_card_id)
const {
1918 if (prefixed_id.empty()) {
1919 prefixed_id =
MakePanelId(session_id, canonical_base_id);
1926 size_t session_id)
const {
1927 std::vector<std::string> result;
1932 const auto& session_panels = session_it->second;
1937 if (std::find(session_panels.begin(), session_panels.end(), panel_id) !=
1938 session_panels.end()) {
1939 result.push_back(panel_id);
1963 const std::string& card_id)
const {
1967 auto it =
cards_.find(card_id);
1968 if (it ==
cards_.end()) {
1971 result.
message =
"Panel not registered";
1980 result.
message =
"FAIL - Missing window name";
1985 ImGuiWindow* window = ImGui::FindWindowByName(result.
expected_title.c_str());
1989 result.
message =
"OK - Window found";
1999 std::vector<PanelValidationResult> results;
2000 results.reserve(
cards_.size());
2002 for (
const auto& [card_id, info] :
cards_) {
std::string dump(int=-1, char=' ', bool=false, int=0) const
bool contains(const std::string &) const
Base interface for all logical panel components.
virtual void OnClose()
Called when panel is hidden.
virtual std::string GetId() const =0
Unique identifier for this panel.
virtual bool IsVisibleByDefault() const
Whether this panel should be visible by default.
virtual std::string GetIcon() const =0
Material Design icon for this panel.
void DrawWithLazyInit(bool *p_open)
Execute lazy initialization if needed, then call Draw()
virtual float GetPreferredWidth() const
Get preferred width for this panel (optional)
virtual PanelScope GetScope() const
Get the registration scope for this panel.
virtual int GetPriority() const
Get display priority for menu ordering.
virtual std::string GetShortcutHint() const
Get keyboard shortcut hint for display.
virtual std::string GetDisplayName() const =0
Human-readable name shown in menus and title bars.
virtual PanelContextScope GetContextScope() const
Optional context binding for this panel (room/selection/etc)
virtual PanelCategory GetPanelCategory() const
Get the lifecycle category for this panel.
virtual std::string GetEditorCategory() const =0
Editor category this panel belongs to.
static std::string GetEditorCategory(EditorType type)
Interface for editor classes.
File system browser for the sidebar.
static std::vector< std::string > GetDefaultPanels(EditorType type)
Get default visible panels for an editor.
std::unordered_map< std::string, bool > SerializeVisibilityState(size_t session_id) const
Serialize panel visibility state for persistence.
std::vector< std::string > GetPinnedPanels() const
void ApplyContextPolicy(size_t session_id, PanelContextScope scope, const std::string &old_key, const std::string &new_key)
size_t GetVisiblePanelCount(size_t session_id) const
void ShowOnlyPanel(size_t session_id, const std::string &base_card_id)
std::unordered_map< std::string, bool > centralized_visibility_
std::unordered_map< std::string, std::unique_ptr< EditorPanel > > panel_instances_
void RegisterSession(size_t session_id)
void SetFileBrowserPath(const std::string &category, const std::string &path)
bool ShouldPrefixPanels() const
EditorPanel * GetEditorPanel(const std::string &panel_id)
Get an EditorPanel instance by ID.
void OnEditorSwitch(const std::string &from_category, const std::string &to_category)
Handle editor/category switching for panel visibility.
void UnregisterSessionPanels(size_t session_id)
void SetActiveSession(size_t session_id)
bool TogglePanel(size_t session_id, const std::string &base_card_id)
const PanelDescriptor * GetPanelDescriptor(size_t session_id, const std::string &base_card_id) const
std::unordered_map< std::string, std::string > panel_resource_types_
std::unordered_map< size_t, std::vector< std::string > > session_cards_
void SetActiveSidePanelWidth(float width, float viewport_width=0.0f, bool notify=true)
void ResetSidePanelWidth(bool notify=true)
std::vector< std::string > GetPanelsInSession(size_t session_id) const
void HandleSidebarKeyboardNav(size_t session_id, const std::vector< PanelDescriptor > &cards)
Handle keyboard navigation in sidebar (click-to-focus modal)
void RestorePinnedState(const std::unordered_map< std::string, bool > &state)
Restore pinned panel state from persistence.
void RestoreVisibilityState(size_t session_id, const std::unordered_map< std::string, bool > &state, bool publish_events=false)
Restore panel visibility state from persistence.
std::unordered_map< size_t, std::unordered_map< std::string, std::string > > session_reverse_card_mapping_
std::unordered_map< std::string, bool > SerializePinnedState() const
Serialize pinned panel state for persistence.
void SetActiveCategory(const std::string &category, bool notify=true)
std::vector< std::string > GetAllCategories() const
std::string GetContextKey(size_t session_id, PanelContextScope scope) const
bool * GetVisibilityFlag(size_t session_id, const std::string &base_card_id)
void UnregisterSession(size_t session_id)
void HideAllPanelsInSession(size_t session_id)
std::string GetBaseIdForPrefixedId(size_t session_id, const std::string &prefixed_id) const
bool HasFileBrowser(const std::string &category) const
void RegisterPanelAlias(const std::string &legacy_base_id, const std::string &canonical_base_id)
Register a legacy panel ID alias that resolves to a canonical ID.
static float GetSidePanelWidthForViewport(float viewport_width)
std::vector< PanelDescriptor > GetPanelsSortedByMRU(size_t session_id, const std::string &category) const
Get panels in category sorted by: pinned first, then MRU.
void UnregisterPanelsWithPrefix(const std::string &prefix)
std::function< void(const std::string &, const std::string &) on_file_clicked_)
std::vector< PanelDescriptor > GetPanelsInCategory(size_t session_id, const std::string &category) const
std::unordered_map< size_t, std::unordered_map< PanelContextScope, std::string, PanelContextScopeHash > > session_context_keys_
bool ShowPanel(size_t session_id, const std::string &base_card_id)
std::string MakePanelId(size_t session_id, const std::string &base_id) const
std::function< void(float width)> on_side_panel_width_changed_
std::unordered_map< std::string, PanelDescriptor > cards_
std::unordered_map< std::string, bool > pinned_panels_
static CategoryTheme GetCategoryTheme(const std::string &category)
void RegisterPanel(size_t session_id, const PanelDescriptor &base_info)
bool IsPanelVisible(size_t session_id, const std::string &base_card_id) const
bool LoadPreset(const std::string &name)
void EnableFileBrowser(const std::string &category, const std::string &root_path="")
PanelValidationResult ValidatePanel(const std::string &card_id) const
std::unordered_map< size_t, std::unordered_map< std::string, std::string > > session_card_mapping_
void DeletePreset(const std::string &name)
std::string ResolveBasePanelId(const std::string &panel_id) const
void DisableFileBrowser(const std::string &category)
void RegisterRegistryPanelsForSession(size_t session_id)
Register descriptors for all registry panels in a session.
std::function< void(const std::string &) on_card_clicked_)
std::unordered_map< std::string, std::unique_ptr< FileBrowser > > category_file_browsers_
std::string GetPrefixedPanelId(size_t session_id, const std::string &base_id) const
void SetPanelBrowserCategoryWidth(float width, bool notify=true)
std::string GetPanelWindowName(size_t session_id, const std::string &base_card_id) const
Resolve the exact ImGui window name for a panel by base ID.
void UnregisterPanel(size_t session_id, const std::string &base_card_id)
void DrawAllVisiblePanels()
Draw all visible EditorPanel instances (central drawing)
float panel_browser_category_width_
void ResetToDefaults(size_t session_id)
std::unordered_set< std::string > global_panel_ids_
void UnregisterEditorPanel(const std::string &panel_id)
Unregister and destroy an EditorPanel instance.
void ShowAllPanelsInCategory(size_t session_id, const std::string &category)
std::vector< PanelValidationResult > ValidatePanels() const
void MarkPanelUsed(const std::string &panel_id)
Mark a panel as recently used (for LRU)
std::unordered_map< std::string, std::list< std::string > > resource_panels_
void MarkPanelRecentlyUsed(const std::string &card_id)
Record that a panel was used (for MRU ordering in sidebar)
void SetContextKey(size_t session_id, PanelContextScope scope, std::string key)
Set a string key for a given context scope (room/selection/etc)
void EnforceResourceLimits(const std::string &resource_type)
Enforce limits on resource panels (LRU eviction)
static std::string GetCategoryIcon(const std::string &category)
void UpdateSessionCount()
EditorPanel * FindPanelInstance(const std::string &prefixed_panel_id, const std::string &base_panel_id)
static constexpr const char * kDashboardCategory
static SidePanelWidthBounds GetSidePanelWidthBounds(float viewport_width)
void SetVisiblePanels(size_t session_id, const std::vector< std::string > &panel_ids)
Set which panels should be visible for a session.
void TrackPanelForSession(size_t session_id, const std::string &base_id, const std::string &panel_id)
void RegisterRegistryPanel(std::unique_ptr< EditorPanel > panel)
Register a ContentRegistry-managed EditorPanel instance.
std::unordered_map< std::string, std::string > panel_id_aliases_
float GetActiveSidePanelWidth(float viewport_width) const
std::function< void(float width)> on_panel_browser_category_width_changed_
static constexpr float GetDefaultPanelBrowserCategoryWidth()
bool IsPanelPinned(size_t session_id, const std::string &base_card_id) const
FileBrowser * GetFileBrowser(const std::string &category)
std::unordered_map< std::string, uint64_t > last_used_at_
void RegisterEditorPanel(std::unique_ptr< EditorPanel > panel)
Register an EditorPanel instance for central drawing.
void SavePreset(const std::string &name, const std::string &description="")
bool HidePanel(size_t session_id, const std::string &base_card_id)
std::unordered_set< std::string > registry_panel_ids_
void LoadPresetsFromFile()
std::vector< WorkspacePreset > GetPresets() const
std::function< Editor *(const std::string &) editor_resolver_)
void SetPanelPinned(size_t session_id, const std::string &base_card_id, bool pinned)
void HideAllPanelsInCategory(size_t session_id, const std::string &category)
std::unordered_map< std::string, WorkspacePreset > presets_
std::string active_category_
void RegisterPanelDescriptorForSession(size_t session_id, const EditorPanel &panel)
std::vector< std::string > GetVisiblePanelIds(size_t session_id) const
Get list of currently visible panel IDs for a session.
void ShowAllPanelsInSession(size_t session_id)
std::string ResolvePanelAlias(const std::string &panel_id) const
Resolve a panel ID through the alias table.
Base class for panels that edit specific ROM resources.
void ClearAnimationsForPanel(const std::string &panel_id)
void BeginPanelTransition(const std::string &panel_id, TransitionType type)
static bool IsTouchDevice()
Draggable, dockable panel for editor sub-windows.
void SetPinChangedCallback(std::function< void(bool)> callback)
void SetPinned(bool pinned)
void SetStableId(const std::string &stable_id)
void SetPinnable(bool pinnable)
void SetPosition(Position pos)
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
::yaze::EventBus * event_bus()
Get the current EventBus instance.
void SetCurrentEditor(Editor *editor)
Set the currently active editor.
constexpr size_t kMaxTotalResourcePanels
Maximum total resource panels across all types.
constexpr size_t kMaxSongPanels
Maximum open song panels (music editor)
constexpr size_t kMaxSheetPanels
Maximum open graphics sheet panels.
constexpr size_t kMaxRoomPanels
Maximum open room panels (dungeon editor)
constexpr size_t kMaxMapPanels
Maximum open map panels (overworld editor)
PanelDescriptor BuildDescriptorFromPanel(const EditorPanel &panel)
@ Persistent
Always visible once shown.
PanelScope
Defines whether a panel is session-scoped or global.
PanelContextScope
Optional context binding for a panel's behavior within an editor.
Metadata for an editor panel (formerly PanelInfo)
std::string GetImGuiWindowName() const
Build the exact ImGui window name used by PanelWindow::Begin.
std::function< void()> on_show
PanelCategory panel_category
PanelContextScope context_scope
std::string shortcut_hint
std::function< void()> on_hide
Get the expressive theme color for a category.
std::string expected_title
std::vector< std::string > visible_cards
static PanelVisibilityChangedEvent Create(const std::string &id, const std::string &base_id, const std::string &cat, bool vis, size_t session=0)