1#define IMGUI_DEFINE_MATH_OPERATORS
9#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
16#include "imgui/imgui_internal.h"
29 if (category ==
"Dungeon")
31 if (category ==
"Overworld")
33 if (category ==
"Graphics")
35 if (category ==
"Palette")
37 if (category ==
"Sprite")
39 if (category ==
"Music")
41 if (category ==
"Message")
43 if (category ==
"Screen")
45 if (category ==
"Emulator")
47 if (category ==
"Assembly")
49 if (category ==
"Settings")
51 if (category ==
"Memory")
53 if (category ==
"Agent")
63 const std::string& category) {
67 if (category ==
"Dungeon") {
69 return {0.95f, 0.75f, 0.20f, 1.0f, 0.95f, 0.75f, 0.20f};
71 if (category ==
"Overworld") {
73 return {0.30f, 0.85f, 0.45f, 1.0f, 0.30f, 0.85f, 0.45f};
75 if (category ==
"Graphics") {
77 return {0.40f, 0.70f, 0.95f, 1.0f, 0.40f, 0.70f, 0.95f};
79 if (category ==
"Palette") {
81 return {0.90f, 0.40f, 0.70f, 1.0f, 0.90f, 0.40f, 0.70f};
83 if (category ==
"Sprite") {
85 return {0.30f, 0.85f, 0.85f, 1.0f, 0.30f, 0.85f, 0.85f};
87 if (category ==
"Music") {
89 return {0.70f, 0.40f, 0.90f, 1.0f, 0.70f, 0.40f, 0.90f};
91 if (category ==
"Message") {
93 return {0.95f, 0.90f, 0.40f, 1.0f, 0.95f, 0.90f, 0.40f};
95 if (category ==
"Screen") {
97 return {0.90f, 0.92f, 0.95f, 1.0f, 0.90f, 0.92f, 0.95f};
99 if (category ==
"Emulator") {
101 return {0.90f, 0.35f, 0.40f, 1.0f, 0.90f, 0.35f, 0.40f};
103 if (category ==
"Assembly") {
105 return {0.40f, 0.90f, 0.50f, 1.0f, 0.40f, 0.90f, 0.50f};
107 if (category ==
"Settings") {
109 return {0.60f, 0.70f, 0.80f, 1.0f, 0.60f, 0.70f, 0.80f};
111 if (category ==
"Memory") {
113 return {0.95f, 0.60f, 0.25f, 1.0f, 0.95f, 0.60f, 0.25f};
115 if (category ==
"Agent") {
117 return {0.60f, 0.40f, 0.95f, 1.0f, 0.60f, 0.40f, 0.95f};
121 return {0.50f, 0.60f, 0.80f, 1.0f, 0.50f, 0.60f, 0.80f};
132 std::unordered_map<std::string, std::string>();
134 LOG_INFO(
"PanelManager",
"Registered session %zu (total: %zu)", session_id,
155 LOG_INFO(
"PanelManager",
"Unregistered session %zu (total: %zu)",
179 "Panel '%s' already registered, skipping duplicate",
180 prefixed_id.c_str());
186 prefixed_info.
card_id = prefixed_id;
195 cards_[prefixed_id] = prefixed_info;
201 LOG_INFO(
"PanelManager",
"Registered card %s -> %s for session %zu",
202 base_info.
card_id.c_str(), prefixed_id.c_str(), session_id);
206 const std::string& display_name,
207 const std::string& icon,
208 const std::string& category,
209 const std::string& shortcut_hint,
int priority,
210 std::function<
void()> on_show,
211 std::function<
void()> on_hide,
212 bool visible_by_default) {
227 if (visible_by_default) {
233 const std::string& base_card_id) {
235 if (prefixed_id.empty()) {
239 auto it =
cards_.find(prefixed_id);
241 LOG_INFO(
"PanelManager",
"Unregistered card: %s", prefixed_id.c_str());
248 session_card_list.erase(std::remove(session_card_list.begin(),
249 session_card_list.end(), prefixed_id),
250 session_card_list.end());
257 std::vector<std::string> to_remove;
260 for (
const auto& [card_id, card_info] :
cards_) {
261 if (card_id.find(prefix) == 0) {
262 to_remove.push_back(card_id);
267 for (
const auto& card_id : to_remove) {
271 LOG_INFO(
"PanelManager",
"Unregistered card with prefix '%s': %s",
272 prefix.c_str(), card_id.c_str());
277 card_list.erase(std::remove_if(card_list.begin(), card_list.end(),
278 [&prefix](
const std::string&
id) {
279 return id.find(prefix) == 0;
293 LOG_INFO(
"PanelManager",
"Cleared all cards");
302 LOG_ERROR(
"PanelManager",
"Attempted to register null EditorPanel");
307 auto* resource_panel =
dynamic_cast<ResourcePanel*
>(panel.get());
308 if (resource_panel) {
312 std::string panel_id = panel->GetId();
316 LOG_WARN(
"PanelManager",
"EditorPanel '%s' already registered, skipping",
325 descriptor.
icon = panel->GetIcon();
326 descriptor.
category = panel->GetEditorCategory();
327 descriptor.
priority = panel->GetPriority();
330 descriptor.
window_title = panel->GetIcon() +
" " + panel->GetDisplayName();
333 bool visible_by_default = panel->IsVisibleByDefault();
339 if (visible_by_default) {
347 if (resource_panel) {
348 std::string type = resource_panel->GetResourceType();
353 LOG_INFO(
"PanelManager",
"Registered EditorPanel: %s (%s)", panel_id.c_str(),
366 auto& panel_list = it->second;
371 if (resource_type ==
"room")
373 else if (resource_type ==
"song")
375 else if (resource_type ==
"sheet")
377 else if (resource_type ==
"map")
382 while (panel_list.size() >= limit) {
384 std::string panel_to_evict;
385 for (
const auto& panel_id : panel_list) {
387 panel_to_evict = panel_id;
393 if (panel_to_evict.empty()) {
394 panel_to_evict = panel_list.front();
395 LOG_INFO(
"PanelManager",
"All %s panels pinned, evicting oldest: %s",
396 resource_type.c_str(), panel_to_evict.c_str());
399 "Evicting non-pinned resource panel: %s (type: %s)",
400 panel_to_evict.c_str(), resource_type.c_str());
404 panel_list.remove(panel_to_evict);
415 std::string type = type_it->second;
420 list.remove(panel_id);
421 list.push_back(panel_id);
428 it->second->OnClose();
430 LOG_INFO(
"PanelManager",
"Unregistered EditorPanel: %s", panel_id.c_str());
440 return it->second.get();
459 bool should_draw =
false;
477 std::string display_name = panel->GetDisplayName();
484 float preferred_width = panel->GetPreferredWidth();
485 if (preferred_width > 0.0f) {
495 [
this, panel_id](
bool pinned) {
SetPanelPinned(panel_id, pinned); });
497 if (window.
Begin(visibility_flag)) {
498 panel->Draw(visibility_flag);
503 if (visibility_flag && !*visibility_flag) {
510 const std::string& to_category) {
511 if (from_category == to_category) {
515 LOG_INFO(
"PanelManager",
"Switching from category '%s' to '%s'",
516 from_category.c_str(), to_category.c_str());
520 if (panel->GetEditorCategory() == from_category &&
531 for (
const auto& panel_id : defaults) {
544 const std::string& base_card_id) {
546 if (prefixed_id.empty()) {
550 auto it =
cards_.find(prefixed_id);
552 if (it->second.visibility_flag) {
553 *it->second.visibility_flag =
true;
555 if (it->second.on_show) {
556 it->second.on_show();
564 const std::string& base_card_id) {
566 if (prefixed_id.empty()) {
570 auto it =
cards_.find(prefixed_id);
572 if (it->second.visibility_flag) {
573 *it->second.visibility_flag =
false;
575 if (it->second.on_hide) {
576 it->second.on_hide();
584 const std::string& base_card_id) {
586 if (prefixed_id.empty()) {
590 auto it =
cards_.find(prefixed_id);
591 if (it !=
cards_.end() && it->second.visibility_flag) {
592 bool new_state = !(*it->second.visibility_flag);
593 *it->second.visibility_flag = new_state;
595 if (new_state && it->second.on_show) {
596 it->second.on_show();
597 }
else if (!new_state && it->second.on_hide) {
598 it->second.on_hide();
606 const std::string& base_card_id)
const {
608 if (prefixed_id.empty()) {
612 auto it =
cards_.find(prefixed_id);
613 if (it !=
cards_.end() && it->second.visibility_flag) {
614 return *it->second.visibility_flag;
620 const std::string& base_card_id) {
622 if (prefixed_id.empty()) {
626 auto it =
cards_.find(prefixed_id);
628 return it->second.visibility_flag;
640 for (
const auto& prefixed_card_id : it->second) {
641 auto card_it =
cards_.find(prefixed_card_id);
642 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
643 *card_it->second.visibility_flag =
true;
644 if (card_it->second.on_show) {
645 card_it->second.on_show();
655 for (
const auto& prefixed_card_id : it->second) {
656 auto card_it =
cards_.find(prefixed_card_id);
657 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
658 *card_it->second.visibility_flag =
false;
659 if (card_it->second.on_hide) {
660 card_it->second.on_hide();
668 const std::string& category) {
671 for (
const auto& prefixed_card_id : it->second) {
672 auto card_it =
cards_.find(prefixed_card_id);
673 if (card_it !=
cards_.end() && card_it->second.category == category) {
674 if (card_it->second.visibility_flag) {
675 *card_it->second.visibility_flag =
true;
677 if (card_it->second.on_show) {
678 card_it->second.on_show();
686 const std::string& category) {
689 for (
const auto& prefixed_card_id : it->second) {
690 auto card_it =
cards_.find(prefixed_card_id);
691 if (card_it !=
cards_.end() && card_it->second.category == category) {
692 if (card_it->second.visibility_flag) {
693 *card_it->second.visibility_flag =
false;
695 if (card_it->second.on_hide) {
696 card_it->second.on_hide();
704 const std::string& base_card_id) {
707 if (prefixed_id.empty()) {
711 auto target_it =
cards_.find(prefixed_id);
712 if (target_it ==
cards_.end()) {
716 std::string category = target_it->second.category;
730 size_t session_id)
const {
739 size_t session_id,
const std::string& category)
const {
740 std::vector<PanelDescriptor> result;
744 for (
const auto& prefixed_card_id : it->second) {
745 auto card_it =
cards_.find(prefixed_card_id);
746 if (card_it !=
cards_.end() && card_it->second.category == category) {
747 result.push_back(card_it->second);
753 std::sort(result.begin(), result.end(),
755 return a.priority < b.priority;
762 size_t session_id)
const {
763 std::vector<std::string> categories;
767 for (
const auto& prefixed_card_id : it->second) {
768 auto card_it =
cards_.find(prefixed_card_id);
769 if (card_it !=
cards_.end()) {
770 if (std::find(categories.begin(), categories.end(),
771 card_it->second.category) == categories.end()) {
772 categories.push_back(card_it->second.category);
781 size_t session_id,
const std::string& base_card_id)
const {
783 if (prefixed_id.empty()) {
787 auto it =
cards_.find(prefixed_id);
795 std::vector<std::string> categories;
796 for (
const auto& [card_id, card_info] :
cards_) {
797 if (std::find(categories.begin(), categories.end(), card_info.category) ==
799 categories.push_back(card_info.category);
810 size_t session_id,
const std::vector<PanelDescriptor>& cards) {
813 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
824 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
830 int card_count =
static_cast<int>(cards.size());
833 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
834 ImGui::IsKeyPressed(ImGuiKey_J)) {
837 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
838 ImGui::IsKeyPressed(ImGuiKey_K)) {
843 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
846 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
852 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
853 ImGui::IsKeyPressed(ImGuiKey_Space)) {
898 const std::string& description) {
904 for (
const auto& [card_id, card_info] :
cards_) {
905 if (card_info.visibility_flag && *card_info.visibility_flag) {
912 LOG_INFO(
"PanelManager",
"Saved preset: %s (%zu cards)", name.c_str(),
923 for (
auto& [card_id, card_info] :
cards_) {
924 if (card_info.visibility_flag) {
925 *card_info.visibility_flag =
false;
930 for (
const auto& card_id : it->second.visible_cards) {
931 auto card_it =
cards_.find(card_id);
932 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
933 *card_it->second.visibility_flag =
true;
934 if (card_it->second.on_show) {
935 card_it->second.on_show();
940 LOG_INFO(
"PanelManager",
"Loaded preset: %s", name.c_str());
950 std::vector<WorkspacePreset> result;
951 for (
const auto& [name, preset] :
presets_) {
952 result.push_back(preset);
974 LOG_INFO(
"PanelManager",
"Reset to defaults for session %zu", session_id);
980 if (category.empty()) {
982 "No category found for editor type %d, skipping reset",
983 static_cast<int>(editor_type));
994 for (
const auto& card_id : default_panels) {
996 LOG_INFO(
"PanelManager",
"Showing default card: %s", card_id.c_str());
1000 LOG_INFO(
"PanelManager",
"Reset %s editor to defaults (%zu cards visible)",
1001 category.c_str(), default_panels.size());
1012 for (
const auto& prefixed_card_id : it->second) {
1013 auto card_it =
cards_.find(prefixed_card_id);
1014 if (card_it !=
cards_.end() && card_it->second.visibility_flag) {
1015 if (*card_it->second.visibility_flag) {
1029 const std::string& base_id)
const {
1031 return absl::StrFormat(
"s%zu.%s", session_id, base_id);
1045 const std::string& base_id)
const {
1048 auto card_it = session_it->second.find(base_id);
1049 if (card_it != session_it->second.end()) {
1050 return card_it->second;
1065 for (
const auto& prefixed_card_id : it->second) {
1066 cards_.erase(prefixed_card_id);
1075 if (!config_dir_result.ok()) {
1076 LOG_ERROR(
"PanelManager",
"Failed to get config directory: %s",
1077 config_dir_result.status().ToString().c_str());
1081 std::filesystem::path presets_file =
1082 *config_dir_result /
"layout_presets.json";
1089 for (
const auto& [name, preset] :
presets_) {
1091 preset_json[
"name"] = preset.name;
1092 preset_json[
"description"] = preset.description;
1093 preset_json[
"visible_cards"] = preset.visible_cards;
1094 j[
"presets"][name] = preset_json;
1097 std::ofstream file(presets_file);
1098 if (!file.is_open()) {
1099 LOG_ERROR(
"PanelManager",
"Failed to open file for writing: %s",
1100 presets_file.string().c_str());
1108 presets_file.string().c_str());
1109 }
catch (
const std::exception& e) {
1110 LOG_ERROR(
"PanelManager",
"Error saving presets: %s", e.what());
1116 if (!config_dir_result.ok()) {
1117 LOG_WARN(
"PanelManager",
"Failed to get config directory: %s",
1118 config_dir_result.status().ToString().c_str());
1122 std::filesystem::path presets_file =
1123 *config_dir_result /
"layout_presets.json";
1126 LOG_INFO(
"PanelManager",
"No presets file found at %s",
1127 presets_file.string().c_str());
1132 std::ifstream file(presets_file);
1133 if (!file.is_open()) {
1134 LOG_WARN(
"PanelManager",
"Failed to open presets file: %s",
1135 presets_file.string().c_str());
1144 LOG_WARN(
"PanelManager",
"Invalid presets file format");
1148 size_t loaded_count = 0;
1155 for (
auto& [name, preset_json] : j[
"presets"].
items()) {
1157 preset.
name = preset_json.value(
"name", name);
1158 preset.
description = preset_json.value(
"description",
"");
1160 if (preset_json.contains(
"visible_cards")) {
1161 yaze::Json visible_cards = preset_json[
"visible_cards"];
1163 for (
const auto& card : visible_cards) {
1164 if (card.is_string()) {
1175 LOG_INFO(
"PanelManager",
"Loaded %zu presets from %s", loaded_count,
1176 presets_file.string().c_str());
1177 }
catch (
const std::exception& e) {
1178 LOG_ERROR(
"PanelManager",
"Error loading presets: %s", e.what());
1189 return it->second.get();
1195 const std::string& root_path) {
1197 auto browser = std::make_unique<FileBrowser>();
1200 browser->SetFileClickedCallback([
this, category](
const std::string& path) {
1210 if (!root_path.empty()) {
1211 browser->SetRootPath(root_path);
1215 if (category ==
"Assembly") {
1216 browser->SetFileFilter({
".asm",
".s",
".65c816",
".inc",
".h"});
1220 LOG_INFO(
"PanelManager",
"Enabled file browser for category: %s",
1235 const std::string& path) {
1238 it->second->SetRootPath(path);
1247 const std::string& base_card_id,
1250 if (prefixed_id.empty()) {
1251 prefixed_id =
MakePanelId(session_id, base_card_id);
1257 const std::string& base_card_id)
const {
1259 if (prefixed_id.empty()) {
1260 prefixed_id =
MakePanelId(session_id, base_card_id);
1267 size_t session_id)
const {
1268 std::vector<std::string> result;
1269 const std::string prefix =
1275 if (prefix.empty() || panel_id.rfind(prefix, 0) == 0) {
1276 result.push_back(panel_id);
1300 const std::string& card_id)
const {
1304 auto it =
cards_.find(card_id);
1305 if (it ==
cards_.end()) {
1308 result.
message =
"Panel not registered";
1317 result.
message =
"FAIL - Missing window title";
1322 ImGuiWindow* window = ImGui::FindWindowByName(result.
expected_title.c_str());
1326 result.
message =
"OK - Window found";
1336 std::vector<PanelValidationResult> results;
1337 results.reserve(
cards_.size());
1339 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