yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
panel_manager.cc
Go to the documentation of this file.
1#define IMGUI_DEFINE_MATH_OPERATORS
2
4
5#include <algorithm>
6#include <cmath>
7#include <cstdio>
8#include <fstream>
9
10#include "absl/strings/str_format.h"
18#include "app/gui/core/icons.h"
21#include "imgui/imgui.h"
22#include "imgui/imgui_internal.h" // For ImGuiWindow and FindWindowByName
23#include "util/json.h"
24#include "util/log.h"
25#include "util/platform_paths.h"
26
27namespace yaze {
28namespace editor {
29
30namespace {
31
33 PanelDescriptor descriptor;
34 descriptor.card_id = panel.GetId();
35 descriptor.display_name = panel.GetDisplayName();
36 descriptor.icon = panel.GetIcon();
37 descriptor.category = panel.GetEditorCategory();
38 descriptor.priority = panel.GetPriority();
39 descriptor.shortcut_hint = panel.GetShortcutHint();
40 descriptor.scope = panel.GetScope();
41 descriptor.panel_category = panel.GetPanelCategory();
42 descriptor.context_scope = panel.GetContextScope();
43 descriptor.visibility_flag = nullptr; // Created by RegisterPanel
44 descriptor.window_title = panel.GetIcon() + " " + panel.GetDisplayName();
45 return descriptor;
46}
47
48} // namespace
49
50// ============================================================================
51// Category Icon Mapping
52// ============================================================================
53
54std::string PanelManager::GetCategoryIcon(const std::string& category) {
55 if (category == "Dungeon")
56 return ICON_MD_CASTLE;
57 if (category == "Overworld")
58 return ICON_MD_MAP;
59 if (category == "Graphics")
60 return ICON_MD_IMAGE;
61 if (category == "Palette")
62 return ICON_MD_PALETTE;
63 if (category == "Sprite")
64 return ICON_MD_PERSON;
65 if (category == "Music")
66 return ICON_MD_MUSIC_NOTE;
67 if (category == "Message")
68 return ICON_MD_MESSAGE;
69 if (category == "Screen")
70 return ICON_MD_TV;
71 if (category == "Emulator")
73 if (category == "Assembly")
74 return ICON_MD_CODE;
75 if (category == "Settings")
76 return ICON_MD_SETTINGS;
77 if (category == "Memory")
78 return ICON_MD_MEMORY;
79 if (category == "Agent")
80 return ICON_MD_SMART_TOY;
81 return ICON_MD_FOLDER; // Default for unknown categories
82}
83
84// ============================================================================
85// Category Theme Colors (Expressive Icon Theming)
86// ============================================================================
87
89 const std::string& category) {
90 // Expressive colors for each category - vibrant when active
91 // Format: {icon_r, icon_g, icon_b, icon_a, glow_r, glow_g, glow_b}
92
93 if (category == "Dungeon") {
94 // Castle gold - warm, regal
95 return {0.95f, 0.75f, 0.20f, 1.0f, 0.95f, 0.75f, 0.20f};
96 }
97 if (category == "Overworld") {
98 // Forest green - natural, expansive
99 return {0.30f, 0.85f, 0.45f, 1.0f, 0.30f, 0.85f, 0.45f};
100 }
101 if (category == "Graphics") {
102 // Image blue - creative, visual
103 return {0.40f, 0.70f, 0.95f, 1.0f, 0.40f, 0.70f, 0.95f};
104 }
105 if (category == "Palette") {
106 // Rainbow pink/magenta - colorful, artistic
107 return {0.90f, 0.40f, 0.70f, 1.0f, 0.90f, 0.40f, 0.70f};
108 }
109 if (category == "Sprite") {
110 // Character cyan - lively, animated
111 return {0.30f, 0.85f, 0.85f, 1.0f, 0.30f, 0.85f, 0.85f};
112 }
113 if (category == "Music") {
114 // Note purple - creative, rhythmic
115 return {0.70f, 0.40f, 0.90f, 1.0f, 0.70f, 0.40f, 0.90f};
116 }
117 if (category == "Message") {
118 // Text yellow - communicative, bright
119 return {0.95f, 0.90f, 0.40f, 1.0f, 0.95f, 0.90f, 0.40f};
120 }
121 if (category == "Screen") {
122 // TV white/silver - display, clean
123 return {0.90f, 0.92f, 0.95f, 1.0f, 0.90f, 0.92f, 0.95f};
124 }
125 if (category == "Emulator") {
126 // Game red - playful, active
127 return {0.90f, 0.35f, 0.40f, 1.0f, 0.90f, 0.35f, 0.40f};
128 }
129 if (category == "Assembly") {
130 // Code green - technical, precise
131 return {0.40f, 0.90f, 0.50f, 1.0f, 0.40f, 0.90f, 0.50f};
132 }
133 if (category == "Settings") {
134 // Gear gray/blue - utility, system
135 return {0.60f, 0.70f, 0.80f, 1.0f, 0.60f, 0.70f, 0.80f};
136 }
137 if (category == "Memory") {
138 // Memory orange - data, technical
139 return {0.95f, 0.60f, 0.25f, 1.0f, 0.95f, 0.60f, 0.25f};
140 }
141 if (category == "Agent") {
142 // AI purple/violet - intelligent, futuristic
143 return {0.60f, 0.40f, 0.95f, 1.0f, 0.60f, 0.40f, 0.95f};
144 }
145
146 // Default - neutral blue
147 return {0.50f, 0.60f, 0.80f, 1.0f, 0.50f, 0.60f, 0.80f};
148}
149
151 float viewport_width) {
152 const float fallback = GetSidePanelWidthForViewport(viewport_width);
153 if (viewport_width <= 0.0f) {
154 SidePanelWidthBounds bounds{};
155 bounds.min_width = 220.0f;
156 bounds.max_width = std::max(520.0f, fallback);
157 return bounds;
158 }
159
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);
162 SidePanelWidthBounds bounds{};
163 bounds.min_width = min_width;
164 bounds.max_width = max_width;
165 return bounds;
166}
167
168float PanelManager::GetActiveSidePanelWidth(float viewport_width) const {
169 const float default_width = GetSidePanelWidthForViewport(viewport_width);
170 if (side_panel_width_ <= 0.0f) {
171 return default_width;
172 }
173 const auto bounds = GetSidePanelWidthBounds(viewport_width);
174 return std::clamp(side_panel_width_, bounds.min_width, bounds.max_width);
175}
176
177void PanelManager::SetActiveSidePanelWidth(float width, float viewport_width,
178 bool notify) {
179 if (width <= 0.0f) {
180 side_panel_width_ = 0.0f;
181 if (notify && on_side_panel_width_changed_) {
183 }
184 return;
185 }
186
187 const auto bounds = GetSidePanelWidthBounds(viewport_width);
188 const float clamped = std::clamp(width, bounds.min_width, bounds.max_width);
189 if (std::abs(side_panel_width_ - clamped) < 0.5f) {
190 return;
191 }
192 side_panel_width_ = clamped;
193 if (notify && on_side_panel_width_changed_) {
195 }
196}
197
199 if (side_panel_width_ <= 0.0f) {
200 return;
201 }
202 side_panel_width_ = 0.0f;
203 if (notify && on_side_panel_width_changed_) {
205 }
206}
207
208void PanelManager::SetPanelBrowserCategoryWidth(float width, bool notify) {
209 const float fallback = GetDefaultPanelBrowserCategoryWidth();
210 const float clamped =
211 std::max(180.0f, width > 0.0f ? width : fallback);
212 if (std::abs(panel_browser_category_width_ - clamped) < 0.5f) {
213 return;
214 }
218 }
219}
220
221// ============================================================================
222// Session Lifecycle Management
223// ============================================================================
224
225void PanelManager::RegisterSession(size_t session_id) {
226 if (session_cards_.find(session_id) == session_cards_.end()) {
227 session_cards_[session_id] = std::vector<std::string>();
228 session_card_mapping_[session_id] =
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,
235 }
236}
237
238void PanelManager::UnregisterSession(size_t session_id) {
239 auto it = session_cards_.find(session_id);
240 if (it != session_cards_.end()) {
241 UnregisterSessionPanels(session_id);
242 session_cards_.erase(it);
243 session_card_mapping_.erase(session_id);
244 session_reverse_card_mapping_.erase(session_id);
245 session_context_keys_.erase(session_id);
247
248 // Reset active session if it was the one being removed
249 if (active_session_ == session_id) {
250 active_session_ = 0;
251 if (!session_cards_.empty()) {
252 active_session_ = session_cards_.begin()->first;
253 }
254 }
255
256 LOG_INFO("PanelManager", "Unregistered session %zu (total: %zu)",
257 session_id, session_count_);
258 }
259}
260
261void PanelManager::SetActiveSession(size_t session_id) {
262 active_session_ = session_id;
263}
264
265// ============================================================================
266// Context Keys (Optional Policy Engine)
267// ============================================================================
268
269void PanelManager::SetContextKey(size_t session_id, PanelContextScope scope,
270 std::string key) {
271 RegisterSession(session_id);
272 auto& session_map = session_context_keys_[session_id];
273 const std::string old_key =
274 (session_map.find(scope) != session_map.end()) ? session_map[scope] : "";
275 if (old_key == key) {
276 return;
277 }
278 session_map[scope] = std::move(key);
279 ApplyContextPolicy(session_id, scope, old_key, session_map[scope]);
280}
281
282std::string PanelManager::GetContextKey(size_t session_id,
283 PanelContextScope scope) const {
284 auto sit = session_context_keys_.find(session_id);
285 if (sit == session_context_keys_.end()) {
286 return "";
287 }
288 const auto& session_map = sit->second;
289 auto it = session_map.find(scope);
290 if (it == session_map.end()) {
291 return "";
292 }
293 return it->second;
294}
295
296void PanelManager::RegisterPanelAlias(const std::string& legacy_base_id,
297 const std::string& canonical_base_id) {
298 if (legacy_base_id.empty() || canonical_base_id.empty() ||
299 legacy_base_id == canonical_base_id) {
300 return;
301 }
302 panel_id_aliases_[legacy_base_id] = canonical_base_id;
303}
304
305std::string PanelManager::ResolvePanelAlias(const std::string& panel_id) const {
306 return ResolveBasePanelId(panel_id);
307}
308
309std::string PanelManager::ResolveBasePanelId(const std::string& panel_id) const {
310 if (panel_id.empty()) {
311 return "";
312 }
313
314 std::string resolved = panel_id;
315 std::unordered_set<std::string> visited;
316 visited.insert(resolved);
317
318 for (int depth = 0; depth < 16; ++depth) {
319 auto alias_it = panel_id_aliases_.find(resolved);
320 if (alias_it == panel_id_aliases_.end() || alias_it->second.empty()) {
321 return resolved;
322 }
323
324 const std::string& next = alias_it->second;
325 if (next == resolved || visited.count(next) > 0) {
326 return resolved;
327 }
328
329 resolved = next;
330 visited.insert(resolved);
331 }
332
333 return resolved;
334}
335
337 const std::string& old_key,
338 const std::string& new_key) {
339 (void)old_key;
340 // Conservative default: if a context is cleared, auto-hide panels that were
341 // explicitly bound to it (unless pinned). This reduces stale "mystery panels"
342 // without being aggressive when context changes (selection A -> selection B).
343 if (!new_key.empty()) {
344 return;
345 }
346
347 auto sit = session_cards_.find(session_id);
348 if (sit == session_cards_.end()) {
349 return;
350 }
351
352 for (const auto& prefixed_id : sit->second) {
353 auto dit = cards_.find(prefixed_id);
354 if (dit == cards_.end()) {
355 continue;
356 }
357 const PanelDescriptor& desc = dit->second;
358 if (desc.context_scope != scope) {
359 continue;
360 }
361 if (!desc.visibility_flag || !*desc.visibility_flag) {
362 continue;
363 }
364 if (IsPanelPinned(prefixed_id)) {
365 continue;
366 }
367
368 const std::string base_id = GetBaseIdForPrefixedId(session_id, prefixed_id);
369 if (!base_id.empty()) {
370 (void)HidePanel(session_id, base_id);
371 }
372 }
373}
374
376 size_t session_id, const std::string& prefixed_id) const {
377 auto sit = session_reverse_card_mapping_.find(session_id);
378 if (sit == session_reverse_card_mapping_.end()) {
379 return "";
380 }
381 const auto& reverse = sit->second;
382 auto it = reverse.find(prefixed_id);
383 if (it == reverse.end()) {
384 return "";
385 }
386 return it->second;
387}
388
389// ============================================================================
390// Panel Registration
391// ============================================================================
392
393void PanelManager::RegisterPanel(size_t session_id,
394 const PanelDescriptor& base_info) {
395 RegisterSession(session_id); // Ensure session exists
396
397 PanelDescriptor canonical_info = base_info;
398 canonical_info.card_id = ResolveBasePanelId(base_info.card_id);
399
400 std::string panel_id =
401 MakePanelId(session_id, canonical_info.card_id, canonical_info.scope);
402
403 bool already_registered = (cards_.find(panel_id) != cards_.end());
404 if (already_registered && canonical_info.scope != PanelScope::kGlobal) {
405 LOG_WARN("PanelManager",
406 "Panel '%s' already registered, skipping duplicate",
407 panel_id.c_str());
408 }
409
410 if (!already_registered) {
411 // Create new PanelDescriptor with final ID
412 PanelDescriptor panel_info = canonical_info;
413 panel_info.card_id = panel_id;
414
415 // If no visibility_flag provided, create centralized one
416 if (!panel_info.visibility_flag) {
417 centralized_visibility_[panel_id] = false; // Hidden by default
418 panel_info.visibility_flag = &centralized_visibility_[panel_id];
419 }
420
421 // Register the card
422 cards_[panel_id] = panel_info;
423
424 LOG_INFO("PanelManager", "Registered card %s -> %s for session %zu",
425 canonical_info.card_id.c_str(), panel_id.c_str(), session_id);
426 }
427
428 if (canonical_info.scope == PanelScope::kGlobal) {
429 global_panel_ids_.insert(panel_id);
430 }
431
432 TrackPanelForSession(session_id, canonical_info.card_id, panel_id);
433}
434
435void PanelManager::RegisterPanel(size_t session_id, const std::string& card_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) {
443 PanelDescriptor info;
444 info.card_id = ResolveBasePanelId(card_id);
445 info.display_name = display_name;
446 info.icon = icon;
447 info.category = category;
448 info.shortcut_hint = shortcut_hint;
449 info.priority = priority;
450 info.visibility_flag = nullptr; // Will be created in RegisterPanel
451 info.on_show = on_show;
452 info.on_hide = on_hide;
453
454 RegisterPanel(session_id, info);
455
456 // Set initial visibility if requested
457 if (visible_by_default) {
458 ShowPanel(session_id, info.card_id);
459 }
460}
461
462void PanelManager::UnregisterPanel(size_t session_id,
463 const std::string& base_card_id) {
464 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
465 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
466 if (prefixed_id.empty()) {
467 return;
468 }
469
470 auto it = cards_.find(prefixed_id);
471 if (it != cards_.end()) {
472 LOG_INFO("PanelManager", "Unregistered card: %s", prefixed_id.c_str());
473 cards_.erase(it);
474 centralized_visibility_.erase(prefixed_id);
475 pinned_panels_.erase(prefixed_id);
476 if (global_panel_ids_.find(prefixed_id) != global_panel_ids_.end()) {
477 global_panel_ids_.erase(prefixed_id);
478 for (auto& [mapped_session, card_list] : session_cards_) {
479 card_list.erase(std::remove(card_list.begin(), card_list.end(),
480 prefixed_id),
481 card_list.end());
482 }
483 for (auto& [mapped_session, mapping] : session_card_mapping_) {
484 mapping.erase(canonical_base_id);
485 }
486 return;
487 }
488
489 // Remove from session tracking
490 auto& session_card_list = session_cards_[session_id];
491 session_card_list.erase(std::remove(session_card_list.begin(),
492 session_card_list.end(), prefixed_id),
493 session_card_list.end());
494
495 session_card_mapping_[session_id].erase(canonical_base_id);
496 }
497}
498
499void PanelManager::UnregisterPanelsWithPrefix(const std::string& prefix) {
500 std::vector<std::string> to_remove;
501
502 // Find all cards with the given prefix
503 for (const auto& [card_id, card_info] : cards_) {
504 if (card_id.find(prefix) == 0) { // Starts with prefix
505 to_remove.push_back(card_id);
506 }
507 }
508
509 // Remove them
510 for (const auto& card_id : to_remove) {
511 cards_.erase(card_id);
512 centralized_visibility_.erase(card_id);
513 pinned_panels_.erase(card_id);
514 LOG_INFO("PanelManager", "Unregistered card with prefix '%s': %s",
515 prefix.c_str(), card_id.c_str());
516 }
517
518 // Also clean up session tracking
519 for (auto& [session_id, card_list] : session_cards_) {
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;
523 }),
524 card_list.end());
525 }
526}
527
529 cards_.clear();
531 pinned_panels_.clear();
532 session_cards_.clear();
533 session_card_mapping_.clear();
535 session_context_keys_.clear();
536 panel_instances_.clear();
537 registry_panel_ids_.clear();
538 global_panel_ids_.clear();
539 session_count_ = 0;
540 LOG_INFO("PanelManager", "Cleared all cards");
541}
542
543// ============================================================================
544// EditorPanel Instance Management (Phase 4)
545// ============================================================================
546
547void PanelManager::RegisterRegistryPanel(std::unique_ptr<EditorPanel> panel) {
548 if (!panel) {
549 LOG_ERROR("PanelManager", "Attempted to register null EditorPanel");
550 return;
551 }
552
553 // Phase 6: Resource Panel Limits
554 auto* resource_panel = dynamic_cast<ResourcePanel*>(panel.get());
555 if (resource_panel) {
556 EnforceResourceLimits(resource_panel->GetResourceType());
557 }
558
559 std::string panel_id = panel->GetId();
560
561 // Check if already registered
562 if (panel_instances_.find(panel_id) != panel_instances_.end()) {
563 LOG_WARN("PanelManager",
564 "EditorPanel '%s' already registered, skipping registry add",
565 panel_id.c_str());
566 return;
567 }
568
569 if (panel->GetScope() == PanelScope::kGlobal) {
570 global_panel_ids_.insert(panel_id);
571 }
572 registry_panel_ids_.insert(panel_id);
573
574 // Store the EditorPanel instance
575 panel_instances_[panel_id] = std::move(panel);
576
577 // Phase 6: Track resource panel usage
578 if (resource_panel) {
579 std::string type = resource_panel->GetResourceType();
580 resource_panels_[type].push_back(panel_id);
581 panel_resource_types_[panel_id] = type;
582 }
583
584 LOG_INFO("PanelManager", "Registered registry EditorPanel: %s",
585 panel_id.c_str());
586}
587
589 RegisterSession(session_id);
590 for (const auto& panel_id : registry_panel_ids_) {
591 auto it = panel_instances_.find(panel_id);
592 if (it == panel_instances_.end()) {
593 continue;
594 }
595 RegisterPanelDescriptorForSession(session_id, *it->second);
596 }
597}
598
599void PanelManager::RegisterEditorPanel(std::unique_ptr<EditorPanel> panel) {
600 if (!panel) {
601 LOG_ERROR("PanelManager", "Attempted to register null EditorPanel");
602 return;
603 }
604
605 // Phase 6: Resource Panel Limits
606 auto* resource_panel = dynamic_cast<ResourcePanel*>(panel.get());
607 if (resource_panel) {
608 EnforceResourceLimits(resource_panel->GetResourceType());
609 }
610
611 std::string panel_id = panel->GetId();
612
613 // Check if already registered
614 if (panel_instances_.find(panel_id) != panel_instances_.end()) {
615 LOG_WARN("PanelManager", "EditorPanel '%s' already registered, skipping",
616 panel_id.c_str());
617 return;
618 }
619
620 // Auto-register PanelDescriptor for sidebar/menu visibility
621 PanelDescriptor descriptor = BuildDescriptorFromPanel(*panel);
622
623 // Check if panel should be visible by default
624 bool visible_by_default = panel->IsVisibleByDefault();
625
626 // Register the descriptor (creates visibility flag)
627 RegisterPanel(active_session_, descriptor);
628
629 // Set initial visibility if panel should be visible by default
630 if (visible_by_default) {
631 ShowPanel(active_session_, panel_id);
632 }
633
634 // Store the EditorPanel instance
635 panel_instances_[panel_id] = std::move(panel);
636
637 // Phase 6: Track resource panel usage
638 if (resource_panel) {
639 std::string type = resource_panel->GetResourceType();
640 resource_panels_[type].push_back(panel_id);
641 panel_resource_types_[panel_id] = type;
642 }
643
644 LOG_INFO("PanelManager", "Registered EditorPanel: %s (%s)", panel_id.c_str(),
645 descriptor.display_name.c_str());
646}
647
648// ============================================================================
649// Resource Management (Phase 6)
650// ============================================================================
651
652void PanelManager::EnforceResourceLimits(const std::string& resource_type) {
653 auto it = resource_panels_.find(resource_type);
654 if (it == resource_panels_.end())
655 return;
656
657 auto& panel_list = it->second;
658 size_t limit =
660
661 // Determine limit based on type
662 if (resource_type == "room")
664 else if (resource_type == "song")
666 else if (resource_type == "sheet")
668 else if (resource_type == "map")
670
671 // Evict panels until we have room for one more (current count < limit)
672 // Prioritize evicting non-pinned panels first, then oldest pinned ones
673 while (panel_list.size() >= limit) {
674 // First pass: find oldest non-pinned panel
675 std::string panel_to_evict;
676 for (const auto& panel_id : panel_list) {
677 if (!IsPanelPinned(panel_id)) {
678 panel_to_evict = panel_id;
679 break;
680 }
681 }
682
683 // If all are pinned, evict the oldest (front of list) anyway
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());
688 } else {
689 LOG_INFO("PanelManager",
690 "Evicting non-pinned resource panel: %s (type: %s)",
691 panel_to_evict.c_str(), resource_type.c_str());
692 }
693
694 // Remove from LRU list first to avoid iterator issues
695 panel_list.remove(panel_to_evict);
696
697 UnregisterEditorPanel(panel_to_evict);
698 }
699}
700
701void PanelManager::MarkPanelUsed(const std::string& panel_id) {
702 auto type_it = panel_resource_types_.find(panel_id);
703 if (type_it == panel_resource_types_.end())
704 return;
705
706 std::string type = type_it->second;
707 auto& list = resource_panels_[type];
708
709 // Move to back (MRU)
710 // std::list::remove is slow (linear), but list size is small (<10)
711 list.remove(panel_id);
712 list.push_back(panel_id);
713}
714
715void PanelManager::UnregisterEditorPanel(const std::string& panel_id) {
716 auto it = panel_instances_.find(panel_id);
717 if (it != panel_instances_.end()) {
718 // Call OnClose before removing
719 it->second->OnClose();
721 panel_instances_.erase(it);
722 registry_panel_ids_.erase(panel_id);
723 global_panel_ids_.erase(panel_id);
724 LOG_INFO("PanelManager", "Unregistered EditorPanel: %s", panel_id.c_str());
725 }
726
727 // Also unregister the descriptor
729}
730
731EditorPanel* PanelManager::GetEditorPanel(const std::string& panel_id) {
732 auto it = panel_instances_.find(panel_id);
733 if (it != panel_instances_.end()) {
734 return it->second.get();
735 }
736 return nullptr;
737}
738
740 const std::string& prefixed_panel_id, const std::string& base_panel_id) {
741 auto prefixed_it = panel_instances_.find(prefixed_panel_id);
742 if (prefixed_it != panel_instances_.end()) {
743 return prefixed_it->second.get();
744 }
745 auto base_it = panel_instances_.find(base_panel_id);
746 if (base_it != panel_instances_.end()) {
747 return base_it->second.get();
748 }
749 return nullptr;
750}
751
753 const std::string& prefixed_panel_id,
754 const std::string& base_panel_id) const {
755 auto prefixed_it = panel_instances_.find(prefixed_panel_id);
756 if (prefixed_it != panel_instances_.end()) {
757 return prefixed_it->second.get();
758 }
759 auto base_it = panel_instances_.find(base_panel_id);
760 if (base_it != panel_instances_.end()) {
761 return base_it->second.get();
762 }
763 return nullptr;
764}
765
767 // Suppress panel drawing when dashboard is active (no editor selected yet)
768 // This ensures panels don't appear until user selects an editor
770 return;
771 }
772
773 auto session_it = session_cards_.find(active_session_);
774 if (session_it == session_cards_.end()) {
775 return;
776 }
777
778 auto& animator = gui::GetAnimator();
779 bool animations_enabled = animator.IsEnabled();
780 const bool touch_device = gui::LayoutHelpers::IsTouchDevice();
781
782 const auto resolve_switch_speed = [&](float snappy, float standard,
783 float relaxed) {
784 switch (animator.motion_profile()) {
786 return snappy;
788 return relaxed;
790 default:
791 return standard;
792 }
793 };
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);
797
798 // Animate global category transition alpha
799 float global_alpha = 1.0f;
800 if (animations_enabled) {
801 global_alpha = animator.Animate("global", "category_transition", 1.0f,
802 category_transition_speed);
803 }
804
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()) {
808 continue;
809 }
810 PanelDescriptor& descriptor = descriptor_it->second;
811
812 std::string base_panel_id =
813 GetBaseIdForPrefixedId(active_session_, prefixed_panel_id);
814 if (base_panel_id.empty()) {
815 base_panel_id = prefixed_panel_id;
816 }
817
818 EditorPanel* panel = FindPanelInstance(prefixed_panel_id, base_panel_id);
819 if (!panel) {
820 continue;
821 }
822
823 bool is_visible = descriptor.visibility_flag && *descriptor.visibility_flag;
824
825 // Category filtering: only draw if matches active category, pinned, or persistent
826 bool should_draw = false;
827 if (is_visible) {
828 if (panel->GetEditorCategory() == active_category_ ||
829 descriptor.category == active_category_) {
830 should_draw = true;
831 } else if (IsPanelPinned(active_session_, base_panel_id)) {
832 should_draw = true;
833 } else if (panel->GetPanelCategory() == PanelCategory::Persistent ||
835 should_draw = true;
836 }
837 }
838
839 // Compute target alpha: 1.0 if should draw, 0.0 if should hide
840 float target_alpha = should_draw ? 1.0f : 0.0f;
841
842 // Animate alpha towards target (or snap if animations disabled)
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; // Apply global category fade
848 } else {
849 current_alpha = target_alpha;
850 }
851
852 // Skip drawing if alpha is effectively zero
853 if (current_alpha < 0.01f) {
854 continue;
855 }
856
857 // Get visibility flag for the panel window
858 bool* visibility_flag = descriptor.visibility_flag;
859
860 // Get display name without icon - PanelWindow will add the icon
861 // This fixes the double-icon issue where both descriptor and PanelWindow added icons
862 std::string display_name = descriptor.display_name.empty()
863 ? panel->GetDisplayName()
864 : descriptor.display_name;
865 std::string icon = descriptor.icon.empty() ? panel->GetIcon()
866 : descriptor.icon;
867
868 if (editor_resolver_) {
869 if (Editor* editor = editor_resolver_(panel->GetEditorCategory())) {
871 }
872 }
873
874 // Create PanelWindow and draw content
875 gui::PanelWindow window(display_name.c_str(), icon.c_str(), visibility_flag);
876 window.SetStableId(prefixed_panel_id);
877 if (touch_device) {
879 }
880
881 // Use preferred width from EditorPanel if specified
882 float preferred_width = panel->GetPreferredWidth();
883 if (preferred_width > 0.0f) {
884 window.SetDefaultSize(preferred_width, 0); // 0 height = auto
885 }
886
887 // Enable pin functionality for cross-editor persistence
888 window.SetPinnable(true);
889 window.SetPinned(IsPanelPinned(active_session_, base_panel_id));
890
891 // Wire up pin state change callback to persist to PanelManager
892 window.SetPinChangedCallback([this, base_panel_id](bool pinned) {
893 SetPanelPinned(base_panel_id, pinned);
894 });
895
896 // Apply fade alpha for smooth transitions
897 std::optional<gui::StyleVarGuard> alpha_guard;
898 if (current_alpha < 1.0f) {
899 alpha_guard.emplace(ImGuiStyleVar_Alpha, current_alpha);
900 }
901
902 if (window.Begin(visibility_flag)) {
903 panel->DrawWithLazyInit(visibility_flag);
904 }
905 window.End();
906
907 alpha_guard.reset();
908
909 // Handle visibility change (window closed via X button)
910 if (visibility_flag && !*visibility_flag) {
911 panel->OnClose();
912 }
913 }
914}
915
916void PanelManager::OnEditorSwitch(const std::string& from_category,
917 const std::string& to_category) {
918 if (from_category == to_category) {
919 return; // No switch needed
920 }
921
922 LOG_INFO("PanelManager", "Switching from category '%s' to '%s'",
923 from_category.c_str(), to_category.c_str());
924
925 // IMPORTANT:
926 // Editor switching must *not* be treated as a user-initiated hide/show.
927 //
928 // Historically this function toggled visibility flags (HidePanel/ShowPanel),
929 // which publishes PanelVisibilityChangedEvent and permanently overwrote user
930 // prefs. Worse, dynamic "resource windows" (rooms/songs) interpret
931 // IsPanelVisible()==false as "user closed", unregistering themselves.
932 //
933 // Reset global category transition to trigger fade-in
934 if (gui::GetAnimator().IsEnabled()) {
936 }
937
938 SetActiveCategory(to_category);
939}
940
941// ============================================================================
942// Panel Control (Programmatic, No GUI)
943// ============================================================================
944
945bool PanelManager::ShowPanel(size_t session_id,
946 const std::string& base_card_id) {
947 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
948 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
949 if (prefixed_id.empty()) {
950 return false;
951 }
952
953 auto it = cards_.find(prefixed_id);
954 if (it != cards_.end()) {
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;
959 }
960 if (it->second.on_show) {
961 it->second.on_show();
962 }
963 if (!was_visible) {
964 if (EditorPanel* panel =
965 FindPanelInstance(prefixed_id, canonical_base_id)) {
966 panel->OnOpen();
967 }
968 }
969
970 // Publish visibility changed event
971 if (auto* bus = ContentRegistry::Context::event_bus()) {
972 bus->Publish(PanelVisibilityChangedEvent::Create(prefixed_id,
973 canonical_base_id,
974 it->second.category,
975 true, session_id));
976 }
977 return true;
978 }
979 return false;
980}
981
982bool PanelManager::HidePanel(size_t session_id,
983 const std::string& base_card_id) {
984 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
985 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
986 if (prefixed_id.empty()) {
987 return false;
988 }
989
990 auto it = cards_.find(prefixed_id);
991 if (it != cards_.end()) {
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;
996 }
997 if (it->second.on_hide) {
998 it->second.on_hide();
999 }
1000 if (was_visible) {
1001 if (EditorPanel* panel =
1002 FindPanelInstance(prefixed_id, canonical_base_id)) {
1003 panel->OnClose();
1004 }
1005 }
1006
1007 // Publish visibility changed event
1008 if (auto* bus = ContentRegistry::Context::event_bus()) {
1009 bus->Publish(PanelVisibilityChangedEvent::Create(prefixed_id,
1010 canonical_base_id,
1011 it->second.category,
1012 false, session_id));
1013 }
1014 return true;
1015 }
1016 return false;
1017}
1018
1019bool PanelManager::TogglePanel(size_t session_id,
1020 const std::string& base_card_id) {
1021 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
1022 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
1023 if (prefixed_id.empty()) {
1024 return false;
1025 }
1026
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;
1031
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();
1036 }
1037 if (EditorPanel* panel = FindPanelInstance(prefixed_id, canonical_base_id)) {
1038 if (new_state) {
1039 panel->OnOpen();
1040 } else {
1041 panel->OnClose();
1042 }
1043 }
1044
1045 // Publish visibility changed event
1046 if (auto* bus = ContentRegistry::Context::event_bus()) {
1047 bus->Publish(PanelVisibilityChangedEvent::Create(prefixed_id,
1048 canonical_base_id,
1049 it->second.category,
1050 new_state, session_id));
1051 }
1052 return true;
1053 }
1054 return false;
1055}
1056
1057bool PanelManager::IsPanelVisible(size_t session_id,
1058 const std::string& base_card_id) const {
1059 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
1060 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
1061 if (prefixed_id.empty()) {
1062 return false;
1063 }
1064
1065 auto it = cards_.find(prefixed_id);
1066 if (it != cards_.end() && it->second.visibility_flag) {
1067 return *it->second.visibility_flag;
1068 }
1069 return false;
1070}
1071
1072bool* PanelManager::GetVisibilityFlag(size_t session_id,
1073 const std::string& base_card_id) {
1074 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
1075 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
1076 if (prefixed_id.empty()) {
1077 return nullptr;
1078 }
1079
1080 auto it = cards_.find(prefixed_id);
1081 if (it != cards_.end()) {
1082 return it->second.visibility_flag;
1083 }
1084 return nullptr;
1085}
1086
1087// ============================================================================
1088// Batch Operations
1089// ============================================================================
1090
1092 auto it = session_cards_.find(session_id);
1093 if (it != session_cards_.end()) {
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();
1100 }
1101 }
1102 }
1103 }
1104}
1105
1107 auto it = session_cards_.find(session_id);
1108 if (it != session_cards_.end()) {
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();
1115 }
1116 }
1117 }
1118 }
1119}
1120
1122 const std::string& category) {
1123 auto it = session_cards_.find(session_id);
1124 if (it != session_cards_.end()) {
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;
1130 }
1131 if (card_it->second.on_show) {
1132 card_it->second.on_show();
1133 }
1134 }
1135 }
1136 }
1137}
1138
1140 const std::string& category) {
1141 auto it = session_cards_.find(session_id);
1142 if (it != session_cards_.end()) {
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;
1148 }
1149 if (card_it->second.on_hide) {
1150 card_it->second.on_hide();
1151 }
1152 }
1153 }
1154 }
1155}
1156
1157void PanelManager::ShowOnlyPanel(size_t session_id,
1158 const std::string& base_card_id) {
1159 // First get the category of the target card
1160 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
1161 if (prefixed_id.empty()) {
1162 return;
1163 }
1164
1165 auto target_it = cards_.find(prefixed_id);
1166 if (target_it == cards_.end()) {
1167 return;
1168 }
1169
1170 std::string category = target_it->second.category;
1171
1172 // Hide all cards in the same category
1173 HideAllPanelsInCategory(session_id, category);
1174
1175 // Show the target card
1176 ShowPanel(session_id, base_card_id);
1177}
1178
1179// ============================================================================
1180// Query Methods
1181// ============================================================================
1182
1183std::vector<std::string> PanelManager::GetPanelsInSession(
1184 size_t session_id) const {
1185 auto it = session_cards_.find(session_id);
1186 if (it != session_cards_.end()) {
1187 return it->second;
1188 }
1189 return {};
1190}
1191
1192std::vector<PanelDescriptor> PanelManager::GetPanelsInCategory(
1193 size_t session_id, const std::string& category) const {
1194 std::vector<PanelDescriptor> result;
1195
1196 auto it = session_cards_.find(session_id);
1197 if (it != session_cards_.end()) {
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);
1202 }
1203 }
1204 }
1205
1206 // Sort by priority
1207 std::sort(result.begin(), result.end(),
1208 [](const PanelDescriptor& a, const PanelDescriptor& b) {
1209 return a.priority < b.priority;
1210 });
1211
1212 return result;
1213}
1214
1215std::vector<std::string> PanelManager::GetAllCategories(
1216 size_t session_id) const {
1217 std::vector<std::string> categories;
1218
1219 auto it = session_cards_.find(session_id);
1220 if (it != session_cards_.end()) {
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);
1227 }
1228 }
1229 }
1230 }
1231 return categories;
1232}
1233
1235 size_t session_id, const std::string& base_card_id) const {
1236 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
1237 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
1238 if (prefixed_id.empty()) {
1239 return nullptr;
1240 }
1241
1242 auto it = cards_.find(prefixed_id);
1243 if (it != cards_.end()) {
1244 return &it->second;
1245 }
1246 return nullptr;
1247}
1248
1249std::vector<std::string> PanelManager::GetAllCategories() const {
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) ==
1253 categories.end()) {
1254 categories.push_back(card_info.category);
1255 }
1256 }
1257 return categories;
1258}
1259
1260// ============================================================================
1261// State Persistence
1262// ============================================================================
1263
1264std::vector<std::string> PanelManager::GetVisiblePanelIds(
1265 size_t session_id) const {
1266 std::vector<std::string> visible_panels;
1267
1268 auto session_it = session_card_mapping_.find(session_id);
1269 if (session_it == session_card_mapping_.end()) {
1270 return visible_panels;
1271 }
1272
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);
1278 }
1279 }
1280
1281 return visible_panels;
1282}
1283
1285 size_t session_id, const std::vector<std::string>& panel_ids) {
1286 auto session_it = session_card_mapping_.find(session_id);
1287 if (session_it == session_card_mapping_.end()) {
1288 return;
1289 }
1290
1291 // Convert panel_ids to canonical IDs for O(1) lookup
1292 std::unordered_set<std::string> visible_set;
1293 visible_set.reserve(panel_ids.size());
1294 for (const auto& panel_id : panel_ids) {
1295 visible_set.insert(ResolveBasePanelId(panel_id));
1296 }
1297
1298 // Update visibility for all panels in this session
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;
1304 }
1305 }
1306
1307 LOG_INFO("PanelManager", "Set %zu panels visible for session %zu",
1308 panel_ids.size(), session_id);
1309}
1310
1311std::unordered_map<std::string, bool> PanelManager::SerializeVisibilityState(
1312 size_t session_id) const {
1313 std::unordered_map<std::string, bool> state;
1314
1315 auto session_it = session_card_mapping_.find(session_id);
1316 if (session_it == session_card_mapping_.end()) {
1317 return state;
1318 }
1319
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;
1324 }
1325 }
1326
1327 return state;
1328}
1329
1331 size_t session_id, const std::unordered_map<std::string, bool>& state,
1332 bool publish_events) {
1333 auto session_it = session_card_mapping_.find(session_id);
1334 if (session_it == session_card_mapping_.end()) {
1335 LOG_WARN("PanelManager",
1336 "Cannot restore visibility: session %zu not found", session_id);
1337 return;
1338 }
1339
1340 size_t restored = 0;
1341 for (const auto& [base_id, visible] : state) {
1342 const std::string canonical_base_id = ResolveBasePanelId(base_id);
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) {
1349 if (auto* bus = ContentRegistry::Context::event_bus()) {
1351 mapping_it->second, canonical_base_id, card_it->second.category,
1352 visible, session_id));
1353 }
1354 }
1355 restored++;
1356 }
1357 }
1358 }
1359
1360 LOG_INFO("PanelManager",
1361 "Restored visibility for %zu/%zu panels in session %zu", restored,
1362 state.size(), session_id);
1363}
1364
1365std::unordered_map<std::string, bool> PanelManager::SerializePinnedState()
1366 const {
1367 std::unordered_map<std::string, bool> state;
1368
1369 for (const auto& [prefixed_id, pinned] : pinned_panels_) {
1370 std::string base_id = prefixed_id;
1371 // Session-prefixed IDs use format: "s{session}.{base_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);
1376 }
1377 }
1378 state[base_id] = pinned;
1379 }
1380
1381 return state;
1382}
1383
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) {
1389 canonical_state[ResolveBasePanelId(base_id)] = pinned;
1390 }
1391
1392 // Apply pinned state to all sessions
1393 for (const auto& [session_id, card_mapping] : session_card_mapping_) {
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()) {
1397 pinned_panels_[prefixed_id] = state_it->second;
1398 }
1399 }
1400 }
1401
1402 LOG_INFO("PanelManager", "Restored pinned state for %zu panels",
1403 canonical_state.size());
1404}
1405
1407 size_t session_id, const std::string& base_card_id) const {
1408 const PanelDescriptor* descriptor = GetPanelDescriptor(session_id, base_card_id);
1409 if (!descriptor) {
1410 return "";
1411 }
1412 return GetPanelWindowName(*descriptor);
1413}
1414
1416 const PanelDescriptor& descriptor) const {
1417 return descriptor.GetImGuiWindowName();
1418}
1419
1420// ============================================================================
1421// Sidebar Keyboard Navigation
1422// ============================================================================
1423
1425 size_t session_id, const std::vector<PanelDescriptor>& cards) {
1426 // Click to focus - only focus if sidebar window is hovered and mouse clicked
1427 if (!sidebar_has_focus_ && ImGui::IsWindowHovered(ImGuiHoveredFlags_None) &&
1428 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
1429 sidebar_has_focus_ = true;
1430 focused_card_index_ = cards.empty() ? -1 : 0;
1431 }
1432
1433 // No navigation if not focused or no cards
1434 if (!sidebar_has_focus_ || cards.empty()) {
1435 return;
1436 }
1437
1438 // Escape to unfocus
1439 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
1440 sidebar_has_focus_ = false;
1442 return;
1443 }
1444
1445 int card_count = static_cast<int>(cards.size());
1446
1447 // Arrow keys / vim keys navigation
1448 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
1449 ImGui::IsKeyPressed(ImGuiKey_J)) {
1450 focused_card_index_ = std::min(focused_card_index_ + 1, card_count - 1);
1451 }
1452 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
1453 ImGui::IsKeyPressed(ImGuiKey_K)) {
1454 focused_card_index_ = std::max(focused_card_index_ - 1, 0);
1455 }
1456
1457 // Home/End for quick navigation
1458 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
1460 }
1461 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
1462 focused_card_index_ = card_count - 1;
1463 }
1464
1465 // Enter/Space to toggle card visibility
1466 if (focused_card_index_ >= 0 && focused_card_index_ < card_count) {
1467 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
1468 ImGui::IsKeyPressed(ImGuiKey_Space)) {
1469 const auto& card = cards[focused_card_index_];
1470 TogglePanel(session_id, card.card_id);
1471 }
1472 }
1473}
1474
1475// ============================================================================
1476// MRU Tracking
1477// ============================================================================
1478
1479void PanelManager::MarkPanelRecentlyUsed(const std::string& card_id) {
1480 last_used_at_[card_id] = ++mru_counter_;
1481}
1482
1483std::vector<PanelDescriptor> PanelManager::GetPanelsSortedByMRU(
1484 size_t session_id, const std::string& category) const {
1485 auto panels = GetPanelsInCategory(session_id, category);
1486
1487 // Sort: pinned first, then by MRU (higher counter = more recent = first)
1488 std::sort(panels.begin(), panels.end(),
1489 [this, session_id](const PanelDescriptor& a,
1490 const PanelDescriptor& b) {
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;
1494
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;
1500
1501 // Fall back to priority for panels never used
1502 return a.priority < b.priority;
1503 });
1504
1505 return panels;
1506}
1507
1508// ============================================================================
1509// Workspace Presets
1510// ============================================================================
1511
1512void PanelManager::SavePreset(const std::string& name,
1513 const std::string& description) {
1514 WorkspacePreset preset;
1515 preset.name = name;
1516 preset.description = description;
1517
1518 // Collect all visible cards across all sessions
1519 for (const auto& [card_id, card_info] : cards_) {
1520 if (card_info.visibility_flag && *card_info.visibility_flag) {
1521 preset.visible_cards.push_back(card_id);
1522 }
1523 }
1524
1525 presets_[name] = preset;
1527 LOG_INFO("PanelManager", "Saved preset: %s (%zu cards)", name.c_str(),
1528 preset.visible_cards.size());
1529}
1530
1531bool PanelManager::LoadPreset(const std::string& name) {
1532 auto it = presets_.find(name);
1533 if (it == presets_.end()) {
1534 return false;
1535 }
1536
1537 // First hide all cards
1538 for (auto& [card_id, card_info] : cards_) {
1539 if (card_info.visibility_flag) {
1540 *card_info.visibility_flag = false;
1541 }
1542 }
1543
1544 // Then show preset cards
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();
1551 }
1552 }
1553 }
1554
1555 LOG_INFO("PanelManager", "Loaded preset: %s", name.c_str());
1556 return true;
1557}
1558
1559void PanelManager::DeletePreset(const std::string& name) {
1560 presets_.erase(name);
1562}
1563
1564std::vector<PanelManager::WorkspacePreset> PanelManager::GetPresets() const {
1565 std::vector<WorkspacePreset> result;
1566 for (const auto& [name, preset] : presets_) {
1567 result.push_back(preset);
1568 }
1569 return result;
1570}
1571
1572// ============================================================================
1573// Quick Actions
1574// ============================================================================
1575
1576void PanelManager::ShowAll(size_t session_id) {
1577 ShowAllPanelsInSession(session_id);
1578}
1579
1580void PanelManager::HideAll(size_t session_id) {
1581 HideAllPanelsInSession(session_id);
1582}
1583
1584void PanelManager::ResetToDefaults(size_t session_id) {
1585 // Hide all cards first
1586 HideAllPanelsInSession(session_id);
1587
1588 // TODO: Load default visibility from config file or hardcoded defaults
1589 LOG_INFO("PanelManager", "Reset to defaults for session %zu", session_id);
1590}
1591
1592void PanelManager::ResetToDefaults(size_t session_id, EditorType editor_type) {
1593 // Get category for this editor
1594 std::string category = EditorRegistry::GetEditorCategory(editor_type);
1595 if (category.empty()) {
1596 LOG_WARN("PanelManager",
1597 "No category found for editor type %d, skipping reset",
1598 static_cast<int>(editor_type));
1599 return;
1600 }
1601
1602 // Hide all cards in this category first
1603 HideAllPanelsInCategory(session_id, category);
1604
1605 // Get default cards from LayoutPresets
1606 auto default_panels = LayoutPresets::GetDefaultPanels(editor_type);
1607
1608 // Show each default card
1609 for (const auto& card_id : default_panels) {
1610 if (ShowPanel(session_id, card_id)) {
1611 LOG_INFO("PanelManager", "Showing default card: %s", card_id.c_str());
1612 }
1613 }
1614
1615 LOG_INFO("PanelManager", "Reset %s editor to defaults (%zu cards visible)",
1616 category.c_str(), default_panels.size());
1617}
1618
1619// ============================================================================
1620// Statistics
1621// ============================================================================
1622
1623size_t PanelManager::GetVisiblePanelCount(size_t session_id) const {
1624 size_t count = 0;
1625 auto it = session_cards_.find(session_id);
1626 if (it != session_cards_.end()) {
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) {
1631 count++;
1632 }
1633 }
1634 }
1635 }
1636 return count;
1637}
1638
1639// ============================================================================
1640// Session Prefixing Utilities
1641// ============================================================================
1642
1643std::string PanelManager::MakePanelId(size_t session_id,
1644 const std::string& base_id) const {
1645 return MakePanelId(session_id, base_id, PanelScope::kSession);
1646}
1647
1648std::string PanelManager::MakePanelId(size_t session_id,
1649 const std::string& base_id,
1650 PanelScope scope) const {
1651 const std::string canonical_base_id = ResolveBasePanelId(base_id);
1652 if (scope == PanelScope::kGlobal) {
1653 return canonical_base_id;
1654 }
1655 if (ShouldPrefixPanels()) {
1656 return absl::StrFormat("s%zu.%s", session_id, canonical_base_id);
1657 }
1658 return canonical_base_id;
1659}
1660
1661// ============================================================================
1662// Helper Methods (Private)
1663// ============================================================================
1664
1668
1669std::string PanelManager::GetPrefixedPanelId(size_t session_id,
1670 const std::string& base_id) const {
1671 const std::string resolved_base_id = ResolveBasePanelId(base_id);
1672
1673 auto session_it = session_card_mapping_.find(session_id);
1674 if (session_it != session_card_mapping_.end()) {
1675 auto card_it = session_it->second.find(resolved_base_id);
1676 if (card_it != session_it->second.end()) {
1677 return card_it->second;
1678 }
1679 }
1680
1681 // Fallback: try unprefixed ID (for single session or direct access)
1682 if (cards_.find(resolved_base_id) != cards_.end()) {
1683 return resolved_base_id;
1684 }
1685
1686 return ""; // Panel not found
1687}
1688
1690 size_t session_id, const EditorPanel& panel) {
1691 RegisterSession(session_id);
1692 std::string panel_id =
1693 MakePanelId(session_id, panel.GetId(), panel.GetScope());
1694 bool already_registered = (cards_.find(panel_id) != cards_.end());
1695 PanelDescriptor descriptor = BuildDescriptorFromPanel(panel);
1696 RegisterPanel(session_id, descriptor);
1697 if (!already_registered && panel.IsVisibleByDefault()) {
1698 ShowPanel(session_id, panel.GetId());
1699 }
1700}
1701
1703 const std::string& base_id,
1704 const std::string& panel_id) {
1705 const std::string canonical_base_id = ResolveBasePanelId(base_id);
1706
1707 auto& card_list = session_cards_[session_id];
1708 if (std::find(card_list.begin(), card_list.end(), panel_id) ==
1709 card_list.end()) {
1710 card_list.push_back(panel_id);
1711 }
1712 session_card_mapping_[session_id][canonical_base_id] = panel_id;
1713 session_reverse_card_mapping_[session_id][panel_id] = canonical_base_id;
1714}
1715
1717 auto it = session_cards_.find(session_id);
1718 if (it != session_cards_.end()) {
1719 for (const auto& prefixed_card_id : it->second) {
1720 if (global_panel_ids_.find(prefixed_card_id) != global_panel_ids_.end()) {
1721 continue;
1722 }
1723 cards_.erase(prefixed_card_id);
1724 centralized_visibility_.erase(prefixed_card_id);
1725 pinned_panels_.erase(prefixed_card_id);
1726 }
1727 }
1728}
1729
1731 auto config_dir_result = util::PlatformPaths::GetConfigDirectory();
1732 if (!config_dir_result.ok()) {
1733 LOG_ERROR("PanelManager", "Failed to get config directory: %s",
1734 config_dir_result.status().ToString().c_str());
1735 return;
1736 }
1737
1738 std::filesystem::path presets_file =
1739 *config_dir_result / "layout_presets.json";
1740
1741 try {
1742 yaze::Json j;
1743 j["version"] = 1;
1744 j["presets"] = yaze::Json::object();
1745
1746 for (const auto& [name, preset] : presets_) {
1747 yaze::Json preset_json;
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;
1752 }
1753
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());
1758 return;
1759 }
1760
1761 file << j.dump(2);
1762 file.close();
1763
1764 LOG_INFO("PanelManager", "Saved %zu presets to %s", presets_.size(),
1765 presets_file.string().c_str());
1766 } catch (const std::exception& e) {
1767 LOG_ERROR("PanelManager", "Error saving presets: %s", e.what());
1768 }
1769}
1770
1772 auto config_dir_result = util::PlatformPaths::GetConfigDirectory();
1773 if (!config_dir_result.ok()) {
1774 LOG_WARN("PanelManager", "Failed to get config directory: %s",
1775 config_dir_result.status().ToString().c_str());
1776 return;
1777 }
1778
1779 std::filesystem::path presets_file =
1780 *config_dir_result / "layout_presets.json";
1781
1782 if (!util::PlatformPaths::Exists(presets_file)) {
1783 LOG_INFO("PanelManager", "No presets file found at %s",
1784 presets_file.string().c_str());
1785 return;
1786 }
1787
1788 try {
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());
1793 return;
1794 }
1795
1796 yaze::Json j;
1797 file >> j;
1798 file.close();
1799
1800 if (!j.contains("presets")) {
1801 LOG_WARN("PanelManager", "Invalid presets file format");
1802 return;
1803 }
1804
1805 size_t loaded_count = 0;
1806 // Note: iterating over yaze::Json or nlohmann::json requires standard loop if using alias
1807 // However, yaze::Json alias is just nlohmann::json when enabled.
1808 // When disabled, the loop will just not execute or stub loop.
1809 // But wait, nlohmann::json iterators return key/value pair or special iterator.
1810 // Let's check how the loop was written: for (auto& [name, preset_json] : j["presets"].items())
1811 // My stub has items(), but nlohmann::json uses items() too.
1812 for (auto& [name, preset_json] : j["presets"].items()) {
1813 WorkspacePreset preset;
1814 preset.name = preset_json.value("name", name);
1815 preset.description = preset_json.value("description", "");
1816
1817 if (preset_json.contains("visible_cards")) {
1818 yaze::Json visible_cards = preset_json["visible_cards"];
1819 if (visible_cards.is_array()) {
1820 for (const auto& card : visible_cards) {
1821 if (card.is_string()) {
1822 preset.visible_cards.push_back(card.get<std::string>());
1823 }
1824 }
1825 }
1826 }
1827
1828 presets_[name] = preset;
1829 loaded_count++;
1830 }
1831
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());
1836 }
1837}
1838
1839// =============================================================================
1840// File Browser Integration
1841// =============================================================================
1842
1843FileBrowser* PanelManager::GetFileBrowser(const std::string& category) {
1844 auto it = category_file_browsers_.find(category);
1845 if (it != category_file_browsers_.end()) {
1846 return it->second.get();
1847 }
1848 return nullptr;
1849}
1850
1851void PanelManager::EnableFileBrowser(const std::string& category,
1852 const std::string& root_path) {
1853 if (category_file_browsers_.find(category) == category_file_browsers_.end()) {
1854 auto browser = std::make_unique<FileBrowser>();
1855
1856 // Set callback to forward file clicks
1857 browser->SetFileClickedCallback([this, category](const std::string& path) {
1858 if (on_file_clicked_) {
1859 on_file_clicked_(category, path);
1860 }
1861 // Also activate the editor for this category
1862 if (on_card_clicked_) {
1863 on_card_clicked_(category);
1864 }
1865 });
1866
1867 if (!root_path.empty()) {
1868 browser->SetRootPath(root_path);
1869 }
1870
1871 // Set defaults for Assembly file browser
1872 if (category == "Assembly") {
1873 browser->SetFileFilter({".asm", ".s", ".65c816", ".inc", ".h"});
1874 }
1875
1876 category_file_browsers_[category] = std::move(browser);
1877 LOG_INFO("PanelManager", "Enabled file browser for category: %s",
1878 category.c_str());
1879 }
1880}
1881
1882void PanelManager::DisableFileBrowser(const std::string& category) {
1883 category_file_browsers_.erase(category);
1884}
1885
1886bool PanelManager::HasFileBrowser(const std::string& category) const {
1887 return category_file_browsers_.find(category) !=
1889}
1890
1891void PanelManager::SetFileBrowserPath(const std::string& category,
1892 const std::string& path) {
1893 auto it = category_file_browsers_.find(category);
1894 if (it != category_file_browsers_.end()) {
1895 it->second->SetRootPath(path);
1896 }
1897}
1898
1899// ============================================================================
1900// Pinning (Phase 3 scaffold)
1901// ============================================================================
1902
1903void PanelManager::SetPanelPinned(size_t session_id,
1904 const std::string& base_card_id,
1905 bool pinned) {
1906 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
1907 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
1908 if (prefixed_id.empty()) {
1909 prefixed_id = MakePanelId(session_id, canonical_base_id);
1910 }
1911 pinned_panels_[prefixed_id] = pinned;
1912}
1913
1914bool PanelManager::IsPanelPinned(size_t session_id,
1915 const std::string& base_card_id) const {
1916 const std::string canonical_base_id = ResolveBasePanelId(base_card_id);
1917 std::string prefixed_id = GetPrefixedPanelId(session_id, canonical_base_id);
1918 if (prefixed_id.empty()) {
1919 prefixed_id = MakePanelId(session_id, canonical_base_id);
1920 }
1921 auto it = pinned_panels_.find(prefixed_id);
1922 return it != pinned_panels_.end() && it->second;
1923}
1924
1925std::vector<std::string> PanelManager::GetPinnedPanels(
1926 size_t session_id) const {
1927 std::vector<std::string> result;
1928 auto session_it = session_cards_.find(session_id);
1929 if (session_it == session_cards_.end()) {
1930 return result;
1931 }
1932 const auto& session_panels = session_it->second;
1933 for (const auto& [panel_id, pinned] : pinned_panels_) {
1934 if (!pinned) {
1935 continue;
1936 }
1937 if (std::find(session_panels.begin(), session_panels.end(), panel_id) !=
1938 session_panels.end()) {
1939 result.push_back(panel_id);
1940 }
1941 }
1942 return result;
1943}
1944
1945void PanelManager::SetPanelPinned(const std::string& base_card_id,
1946 bool pinned) {
1947 SetPanelPinned(active_session_, base_card_id, pinned);
1948}
1949
1950bool PanelManager::IsPanelPinned(const std::string& base_card_id) const {
1951 return IsPanelPinned(active_session_, base_card_id);
1952}
1953
1954std::vector<std::string> PanelManager::GetPinnedPanels() const {
1956}
1957
1958// =============================================================================
1959// Panel Validation
1960// =============================================================================
1961
1963 const std::string& card_id) const {
1964 PanelValidationResult result;
1965 result.card_id = card_id;
1966
1967 auto it = cards_.find(card_id);
1968 if (it == cards_.end()) {
1969 result.expected_title = "";
1970 result.found_in_imgui = false;
1971 result.message = "Panel not registered";
1972 return result;
1973 }
1974
1975 const PanelDescriptor& info = it->second;
1976 result.expected_title = GetPanelWindowName(info);
1977
1978 if (result.expected_title.empty()) {
1979 result.found_in_imgui = false;
1980 result.message = "FAIL - Missing window name";
1981 return result;
1982 }
1983
1984 // Check if ImGui has a window with this title
1985 ImGuiWindow* window = ImGui::FindWindowByName(result.expected_title.c_str());
1986 result.found_in_imgui = (window != nullptr);
1987
1988 if (result.found_in_imgui) {
1989 result.message = "OK - Window found";
1990 } else {
1991 result.message = "FAIL - No window with name: " + result.expected_title;
1992 }
1993
1994 return result;
1995}
1996
1997std::vector<PanelManager::PanelValidationResult> PanelManager::ValidatePanels()
1998 const {
1999 std::vector<PanelValidationResult> results;
2000 results.reserve(cards_.size());
2001
2002 for (const auto& [card_id, info] : cards_) {
2003 results.push_back(ValidatePanel(card_id));
2004 }
2005
2006 return results;
2007}
2008
2009} // namespace editor
2010} // namespace yaze
bool is_array() const
Definition json.h:58
static Json object()
Definition json.h:34
items_view items()
Definition json.h:88
std::string dump(int=-1, char=' ', bool=false, int=0) const
Definition json.h:91
bool contains(const std::string &) const
Definition json.h:53
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.
Definition editor.h:236
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)
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)
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)
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_
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_
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)
Definition animator.cc:83
void BeginPanelTransition(const std::string &panel_id, TransitionType type)
Definition animator.cc:111
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)
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
static bool Exists(const std::filesystem::path &path)
Check if a file or directory exists.
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_MEMORY
Definition icons.h:1195
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_CODE
Definition icons.h:434
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_MESSAGE
Definition icons.h:1201
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1264
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_PERSON
Definition icons.h:1415
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_TV
Definition icons.h:2032
#define ICON_MD_SMART_TOY
Definition icons.h:1781
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
::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.
Animator & GetAnimator()
Definition animator.cc:301
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
PanelContextScope context_scope
std::function< void()> on_hide
Get the expressive theme color for a category.
static PanelVisibilityChangedEvent Create(const std::string &id, const std::string &base_id, const std::string &cat, bool vis, size_t session=0)