1#define IMGUI_DEFINE_MATH_OPERATORS
9#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
16#include "imgui/imgui_internal.h"
50 const std::string& category) {
54 if (category ==
"Dungeon") {
56 return {0.95f, 0.75f, 0.20f, 1.0f, 0.95f, 0.75f, 0.20f};
58 if (category ==
"Overworld") {
60 return {0.30f, 0.85f, 0.45f, 1.0f, 0.30f, 0.85f, 0.45f};
62 if (category ==
"Graphics") {
64 return {0.40f, 0.70f, 0.95f, 1.0f, 0.40f, 0.70f, 0.95f};
66 if (category ==
"Palette") {
68 return {0.90f, 0.40f, 0.70f, 1.0f, 0.90f, 0.40f, 0.70f};
70 if (category ==
"Sprite") {
72 return {0.30f, 0.85f, 0.85f, 1.0f, 0.30f, 0.85f, 0.85f};
74 if (category ==
"Music") {
76 return {0.70f, 0.40f, 0.90f, 1.0f, 0.70f, 0.40f, 0.90f};
78 if (category ==
"Message") {
80 return {0.95f, 0.90f, 0.40f, 1.0f, 0.95f, 0.90f, 0.40f};
82 if (category ==
"Screen") {
84 return {0.90f, 0.92f, 0.95f, 1.0f, 0.90f, 0.92f, 0.95f};
86 if (category ==
"Emulator") {
88 return {0.90f, 0.35f, 0.40f, 1.0f, 0.90f, 0.35f, 0.40f};
90 if (category ==
"Assembly") {
92 return {0.40f, 0.90f, 0.50f, 1.0f, 0.40f, 0.90f, 0.50f};
94 if (category ==
"Settings") {
96 return {0.60f, 0.70f, 0.80f, 1.0f, 0.60f, 0.70f, 0.80f};
98 if (category ==
"Memory") {
100 return {0.95f, 0.60f, 0.25f, 1.0f, 0.95f, 0.60f, 0.25f};
102 if (category ==
"Agent") {
104 return {0.60f, 0.40f, 0.95f, 1.0f, 0.60f, 0.40f, 0.95f};
108 return {0.50f, 0.60f, 0.80f, 1.0f, 0.50f, 0.60f, 0.80f};
119 std::unordered_map<std::string, std::string>();
121 LOG_INFO(
"PanelManager",
"Registered session %zu (total: %zu)",
142 LOG_INFO(
"PanelManager",
"Unregistered session %zu (total: %zu)",
166 "Panel '%s' already registered, skipping duplicate",
167 prefixed_id.c_str());
173 prefixed_info.
card_id = prefixed_id;
182 cards_[prefixed_id] = prefixed_info;
188 LOG_INFO(
"PanelManager",
"Registered card %s -> %s for session %zu",
189 base_info.
card_id.c_str(), prefixed_id.c_str(), session_id);
193 size_t session_id,
const std::string& card_id,
194 const std::string& display_name,
const std::string& icon,
195 const std::string& category,
const std::string& shortcut_hint,
int priority,
196 std::function<
void()> on_show, std::function<
void()> on_hide,
197 bool visible_by_default) {
212 if (visible_by_default) {
218 const std::string& base_card_id) {
220 if (prefixed_id.empty()) {
224 auto it =
cards_.find(prefixed_id);
226 LOG_INFO(
"PanelManager",
"Unregistered card: %s",
227 prefixed_id.c_str());
234 session_card_list.erase(std::remove(session_card_list.begin(),
235 session_card_list.end(), prefixed_id),
236 session_card_list.end());
243 std::vector<std::string> to_remove;
246 for (
const auto& [card_id, card_info] :
cards_) {
247 if (card_id.find(prefix) == 0) {
248 to_remove.push_back(card_id);
253 for (
const auto& card_id : to_remove) {
257 LOG_INFO(
"PanelManager",
"Unregistered card with prefix '%s': %s",
258 prefix.c_str(), card_id.c_str());
263 card_list.erase(std::remove_if(card_list.begin(), card_list.end(),
264 [&prefix](
const std::string&
id) {
265 return id.find(prefix) == 0;
279 LOG_INFO(
"PanelManager",
"Cleared all cards");
288 LOG_ERROR(
"PanelManager",
"Attempted to register null EditorPanel");
293 auto* resource_panel =
dynamic_cast<ResourcePanel*
>(panel.get());
294 if (resource_panel) {
298 std::string panel_id = panel->GetId();
302 LOG_WARN(
"PanelManager",
"EditorPanel '%s' already registered, skipping",
311 descriptor.
icon = panel->GetIcon();
312 descriptor.
category = panel->GetEditorCategory();
313 descriptor.
priority = panel->GetPriority();
316 descriptor.
window_title = panel->GetIcon() +
" " + panel->GetDisplayName();
319 bool visible_by_default = panel->IsVisibleByDefault();
325 if (visible_by_default) {
333 if (resource_panel) {
334 std::string type = resource_panel->GetResourceType();
339 LOG_INFO(
"PanelManager",
"Registered EditorPanel: %s (%s)",
351 auto& panel_list = it->second;
362 while (panel_list.size() >= limit) {
364 std::string panel_to_evict;
365 for (
const auto& panel_id : panel_list) {
367 panel_to_evict = panel_id;
373 if (panel_to_evict.empty()) {
374 panel_to_evict = panel_list.front();
375 LOG_INFO(
"PanelManager",
"All %s panels pinned, evicting oldest: %s",
376 resource_type.c_str(), panel_to_evict.c_str());
378 LOG_INFO(
"PanelManager",
"Evicting non-pinned resource panel: %s (type: %s)",
379 panel_to_evict.c_str(), resource_type.c_str());
383 panel_list.remove(panel_to_evict);
393 std::string type = type_it->second;
398 list.remove(panel_id);
399 list.push_back(panel_id);
406 it->second->OnClose();
408 LOG_INFO(
"PanelManager",
"Unregistered EditorPanel: %s", panel_id.c_str());
418 return it->second.get();
437 bool should_draw =
false;
455 std::string display_name = panel->GetDisplayName();
462 float preferred_width = panel->GetPreferredWidth();
463 if (preferred_width > 0.0f) {
476 if (window.
Begin(visibility_flag)) {
477 panel->Draw(visibility_flag);
482 if (visibility_flag && !*visibility_flag) {
489 const std::string& to_category) {
490 if (from_category == to_category) {
494 LOG_INFO(
"PanelManager",
"Switching from category '%s' to '%s'",
495 from_category.c_str(), to_category.c_str());
499 if (panel->GetEditorCategory() == from_category &&
509 for (
const auto& panel_id : defaults) {
522 const std::string& base_card_id) {
524 if (prefixed_id.empty()) {
528 auto it =
cards_.find(prefixed_id);
530 if (it->second.visibility_flag) {
531 *it->second.visibility_flag =
true;
533 if (it->second.on_show) {
534 it->second.on_show();
542 const std::string& base_card_id) {
544 if (prefixed_id.empty()) {
548 auto it =
cards_.find(prefixed_id);
550 if (it->second.visibility_flag) {
551 *it->second.visibility_flag =
false;
553 if (it->second.on_hide) {
554 it->second.on_hide();
562 const std::string& base_card_id) {
564 if (prefixed_id.empty()) {
568 auto it =
cards_.find(prefixed_id);
569 if (it !=
cards_.end() && it->second.visibility_flag) {
570 bool new_state = !(*it->second.visibility_flag);
571 *it->second.visibility_flag = new_state;
573 if (new_state && it->second.on_show) {
574 it->second.on_show();
575 }
else if (!new_state && it->second.on_hide) {
576 it->second.on_hide();
584 const std::string& base_card_id)
const {
586 if (prefixed_id.empty()) {
590 auto it =
cards_.find(prefixed_id);
591 if (it !=
cards_.end() && it->second.visibility_flag) {
592 return *it->second.visibility_flag;
598 const std::string& base_card_id) {
600 if (prefixed_id.empty()) {
604 auto it =
cards_.find(prefixed_id);
606 return it->second.visibility_flag;
618 for (
const auto& prefixed_card_id : it->second) {
619 auto card_it =
cards_.find(prefixed_card_id);
620 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
621 *card_it->second.visibility_flag =
true;
622 if (card_it->second.on_show) {
623 card_it->second.on_show();
633 for (
const auto& prefixed_card_id : it->second) {
634 auto card_it =
cards_.find(prefixed_card_id);
635 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
636 *card_it->second.visibility_flag =
false;
637 if (card_it->second.on_hide) {
638 card_it->second.on_hide();
646 const std::string& category) {
649 for (
const auto& prefixed_card_id : it->second) {
650 auto card_it =
cards_.find(prefixed_card_id);
651 if (card_it !=
cards_.end() && card_it->second.category == category) {
652 if (card_it->second.visibility_flag) {
653 *card_it->second.visibility_flag =
true;
655 if (card_it->second.on_show) {
656 card_it->second.on_show();
664 const std::string& category) {
667 for (
const auto& prefixed_card_id : it->second) {
668 auto card_it =
cards_.find(prefixed_card_id);
669 if (card_it !=
cards_.end() && card_it->second.category == category) {
670 if (card_it->second.visibility_flag) {
671 *card_it->second.visibility_flag =
false;
673 if (card_it->second.on_hide) {
674 card_it->second.on_hide();
682 const std::string& base_card_id) {
685 if (prefixed_id.empty()) {
689 auto target_it =
cards_.find(prefixed_id);
690 if (target_it ==
cards_.end()) {
694 std::string category = target_it->second.category;
708 size_t session_id)
const {
717 size_t session_id,
const std::string& category)
const {
718 std::vector<PanelDescriptor> result;
722 for (
const auto& prefixed_card_id : it->second) {
723 auto card_it =
cards_.find(prefixed_card_id);
724 if (card_it !=
cards_.end() && card_it->second.category == category) {
725 result.push_back(card_it->second);
731 std::sort(result.begin(), result.end(),
733 return a.priority < b.priority;
740 size_t session_id)
const {
741 std::vector<std::string> categories;
745 for (
const auto& prefixed_card_id : it->second) {
746 auto card_it =
cards_.find(prefixed_card_id);
747 if (card_it !=
cards_.end()) {
748 if (std::find(categories.begin(), categories.end(),
749 card_it->second.category) == categories.end()) {
750 categories.push_back(card_it->second.category);
759 size_t session_id,
const std::string& base_card_id)
const {
761 if (prefixed_id.empty()) {
765 auto it =
cards_.find(prefixed_id);
773 std::vector<std::string> categories;
774 for (
const auto& [card_id, card_info] :
cards_) {
775 if (std::find(categories.begin(), categories.end(), card_info.category) ==
777 categories.push_back(card_info.category);
788 size_t session_id,
const std::vector<PanelDescriptor>& cards) {
791 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
802 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
808 int card_count =
static_cast<int>(cards.size());
811 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
812 ImGui::IsKeyPressed(ImGuiKey_J)) {
815 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
816 ImGui::IsKeyPressed(ImGuiKey_K)) {
821 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
824 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
830 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
831 ImGui::IsKeyPressed(ImGuiKey_Space)) {
876 const std::string& description) {
882 for (
const auto& [card_id, card_info] :
cards_) {
883 if (card_info.visibility_flag && *card_info.visibility_flag) {
890 LOG_INFO(
"PanelManager",
"Saved preset: %s (%zu cards)", name.c_str(),
901 for (
auto& [card_id, card_info] :
cards_) {
902 if (card_info.visibility_flag) {
903 *card_info.visibility_flag =
false;
908 for (
const auto& card_id : it->second.visible_cards) {
909 auto card_it =
cards_.find(card_id);
910 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
911 *card_it->second.visibility_flag =
true;
912 if (card_it->second.on_show) {
913 card_it->second.on_show();
918 LOG_INFO(
"PanelManager",
"Loaded preset: %s", name.c_str());
927std::vector<PanelManager::WorkspacePreset>
929 std::vector<WorkspacePreset> result;
930 for (
const auto& [name, preset] :
presets_) {
931 result.push_back(preset);
953 LOG_INFO(
"PanelManager",
"Reset to defaults for session %zu",
961 if (category.empty()) {
963 "No category found for editor type %d, skipping reset",
964 static_cast<int>(editor_type));
975 for (
const auto& card_id : default_panels) {
977 LOG_INFO(
"PanelManager",
"Showing default card: %s",
983 "Reset %s editor to defaults (%zu cards visible)", category.c_str(),
984 default_panels.size());
995 for (
const auto& prefixed_card_id : it->second) {
996 auto card_it =
cards_.find(prefixed_card_id);
997 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
998 if (*card_it->second.visibility_flag) {
1012 const std::string& base_id)
const {
1014 return absl::StrFormat(
"s%zu.%s", session_id, base_id);
1028 size_t session_id,
const std::string& base_id)
const {
1031 auto card_it = session_it->second.find(base_id);
1032 if (card_it != session_it->second.end()) {
1033 return card_it->second;
1048 for (
const auto& prefixed_card_id : it->second) {
1049 cards_.erase(prefixed_card_id);
1058 if (!config_dir_result.ok()) {
1059 LOG_ERROR(
"PanelManager",
"Failed to get config directory: %s",
1060 config_dir_result.status().ToString().c_str());
1064 std::filesystem::path presets_file = *config_dir_result /
"layout_presets.json";
1071 for (
const auto& [name, preset] :
presets_) {
1073 preset_json[
"name"] = preset.name;
1074 preset_json[
"description"] = preset.description;
1075 preset_json[
"visible_cards"] = preset.visible_cards;
1076 j[
"presets"][name] = preset_json;
1079 std::ofstream file(presets_file);
1080 if (!file.is_open()) {
1081 LOG_ERROR(
"PanelManager",
"Failed to open file for writing: %s",
1082 presets_file.string().c_str());
1090 presets_file.string().c_str());
1091 }
catch (
const std::exception& e) {
1092 LOG_ERROR(
"PanelManager",
"Error saving presets: %s", e.what());
1098 if (!config_dir_result.ok()) {
1099 LOG_WARN(
"PanelManager",
"Failed to get config directory: %s",
1100 config_dir_result.status().ToString().c_str());
1104 std::filesystem::path presets_file = *config_dir_result /
"layout_presets.json";
1107 LOG_INFO(
"PanelManager",
"No presets file found at %s",
1108 presets_file.string().c_str());
1113 std::ifstream file(presets_file);
1114 if (!file.is_open()) {
1115 LOG_WARN(
"PanelManager",
"Failed to open presets file: %s",
1116 presets_file.string().c_str());
1125 LOG_WARN(
"PanelManager",
"Invalid presets file format");
1129 size_t loaded_count = 0;
1136 for (
auto& [name, preset_json] : j[
"presets"].
items()) {
1138 preset.
name = preset_json.value(
"name", name);
1139 preset.
description = preset_json.value(
"description",
"");
1141 if (preset_json.contains(
"visible_cards")) {
1142 yaze::Json visible_cards = preset_json[
"visible_cards"];
1144 for (
const auto& card : visible_cards) {
1145 if (card.is_string()) {
1156 LOG_INFO(
"PanelManager",
"Loaded %zu presets from %s", loaded_count,
1157 presets_file.string().c_str());
1158 }
catch (
const std::exception& e) {
1159 LOG_ERROR(
"PanelManager",
"Error loading presets: %s", e.what());
1170 return it->second.get();
1176 const std::string& root_path) {
1178 auto browser = std::make_unique<FileBrowser>();
1181 browser->SetFileClickedCallback(
1182 [
this, category](
const std::string& path) {
1192 if (!root_path.empty()) {
1193 browser->SetRootPath(root_path);
1197 if (category ==
"Assembly") {
1198 browser->SetFileFilter({
".asm",
".s",
".65c816",
".inc",
".h"});
1202 LOG_INFO(
"PanelManager",
"Enabled file browser for category: %s",
1217 const std::string& path) {
1220 it->second->SetRootPath(path);
1229 const std::string& base_card_id,
1232 if (prefixed_id.empty()) {
1233 prefixed_id =
MakePanelId(session_id, base_card_id);
1239 const std::string& base_card_id)
const {
1241 if (prefixed_id.empty()) {
1242 prefixed_id =
MakePanelId(session_id, base_card_id);
1249 size_t session_id)
const {
1250 std::vector<std::string> result;
1251 const std::string prefix =
1255 if (!pinned)
continue;
1256 if (prefix.empty() || panel_id.rfind(prefix, 0) == 0) {
1257 result.push_back(panel_id);
1283 const std::string& card_id)
const {
1287 auto it =
cards_.find(card_id);
1288 if (it ==
cards_.end()) {
1291 result.
message =
"Panel not registered";
1300 result.
message =
"FAIL - Missing window title";
1305 ImGuiWindow* window = ImGui::FindWindowByName(result.
expected_title.c_str());
1309 result.
message =
"OK - Window found";
1318std::vector<PanelManager::PanelValidationResult>
1320 std::vector<PanelValidationResult> results;
1321 results.reserve(
cards_.size());
1323 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.
static EditorType GetEditorTypeFromCategory(const std::string &category)
static std::string GetEditorCategory(EditorType type)
File system browser for the sidebar.
static std::vector< std::string > GetDefaultPanels(EditorType type)
Get default visible panels for an editor.
std::vector< std::string > GetPinnedPanels() const
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_
void AddToRecent(const std::string &card_id)
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_
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)
std::vector< std::string > GetAllCategories() const
static constexpr size_t kMaxRecentPanels
bool * GetVisibilityFlag(size_t session_id, const std::string &base_card_id)
void UnregisterSession(size_t session_id)
void HideAllPanelsInSession(size_t session_id)
bool HasFileBrowser(const std::string &category) const
void UnregisterPanelsWithPrefix(const std::string &prefix)
std::function< void(const std::string &, const std::string &) on_file_clicked_)
std::vector< std::string > recent_cards_
std::vector< PanelDescriptor > GetPanelsInCategory(size_t session_id, const std::string &category) const
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::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)
void DisableFileBrowser(const std::string &category)
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 UnregisterPanel(size_t session_id, const std::string &base_card_id)
void DrawAllVisiblePanels()
Draw all visible EditorPanel instances (central drawing)
void ResetToDefaults(size_t session_id)
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 EnforceResourceLimits(const std::string &resource_type)
Enforce limits on resource panels (LRU eviction)
static std::string GetCategoryIcon(const std::string &category)
void UpdateSessionCount()
static constexpr const char * kDashboardCategory
bool IsPanelPinned(size_t session_id, const std::string &base_card_id) const
FileBrowser * GetFileBrowser(const std::string &category)
void ToggleFavorite(const std::string &card_id)
void SetActiveCategory(const std::string &category)
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)
void LoadPresetsFromFile()
std::vector< WorkspacePreset > GetPresets() const
std::unordered_set< std::string > favorite_cards_
void SetPanelPinned(size_t session_id, const std::string &base_card_id, bool pinned)
bool IsFavorite(const std::string &card_id) const
void HideAllPanelsInCategory(size_t session_id, const std::string &category)
std::unordered_map< std::string, WorkspacePreset > presets_
std::string active_category_
void ShowAllPanelsInSession(size_t session_id)
Base class for panels that edit specific ROM resources.
Draggable, dockable panel for editor sub-windows.
void SetPinChangedCallback(std::function< void(bool)> callback)
void SetPinned(bool pinned)
void SetPinnable(bool pinnable)
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,...)
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)
@ Persistent
Always visible once shown.
Metadata for an editor panel (formerly PanelInfo)
std::function< void()> on_show
std::string GetWindowTitle() const
Get the effective window title for DockBuilder.
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