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 <cstdio>
7#include <fstream>
8
9#include "absl/strings/str_format.h"
14#include "app/gui/core/icons.h"
15#include "imgui/imgui.h"
16#include "imgui/imgui_internal.h" // For ImGuiWindow and FindWindowByName
17#include "util/json.h"
18#include "util/log.h"
19#include "util/platform_paths.h"
20
21namespace yaze {
22namespace editor {
23
24// ============================================================================
25// Category Icon Mapping
26// ============================================================================
27
28std::string PanelManager::GetCategoryIcon(const std::string& category) {
29 if (category == "Dungeon")
30 return ICON_MD_CASTLE;
31 if (category == "Overworld")
32 return ICON_MD_MAP;
33 if (category == "Graphics")
34 return ICON_MD_IMAGE;
35 if (category == "Palette")
36 return ICON_MD_PALETTE;
37 if (category == "Sprite")
38 return ICON_MD_PERSON;
39 if (category == "Music")
40 return ICON_MD_MUSIC_NOTE;
41 if (category == "Message")
42 return ICON_MD_MESSAGE;
43 if (category == "Screen")
44 return ICON_MD_TV;
45 if (category == "Emulator")
47 if (category == "Assembly")
48 return ICON_MD_CODE;
49 if (category == "Settings")
50 return ICON_MD_SETTINGS;
51 if (category == "Memory")
52 return ICON_MD_MEMORY;
53 if (category == "Agent")
54 return ICON_MD_SMART_TOY;
55 return ICON_MD_FOLDER; // Default for unknown categories
56}
57
58// ============================================================================
59// Category Theme Colors (Expressive Icon Theming)
60// ============================================================================
61
63 const std::string& category) {
64 // Expressive colors for each category - vibrant when active
65 // Format: {icon_r, icon_g, icon_b, icon_a, glow_r, glow_g, glow_b}
66
67 if (category == "Dungeon") {
68 // Castle gold - warm, regal
69 return {0.95f, 0.75f, 0.20f, 1.0f, 0.95f, 0.75f, 0.20f};
70 }
71 if (category == "Overworld") {
72 // Forest green - natural, expansive
73 return {0.30f, 0.85f, 0.45f, 1.0f, 0.30f, 0.85f, 0.45f};
74 }
75 if (category == "Graphics") {
76 // Image blue - creative, visual
77 return {0.40f, 0.70f, 0.95f, 1.0f, 0.40f, 0.70f, 0.95f};
78 }
79 if (category == "Palette") {
80 // Rainbow pink/magenta - colorful, artistic
81 return {0.90f, 0.40f, 0.70f, 1.0f, 0.90f, 0.40f, 0.70f};
82 }
83 if (category == "Sprite") {
84 // Character cyan - lively, animated
85 return {0.30f, 0.85f, 0.85f, 1.0f, 0.30f, 0.85f, 0.85f};
86 }
87 if (category == "Music") {
88 // Note purple - creative, rhythmic
89 return {0.70f, 0.40f, 0.90f, 1.0f, 0.70f, 0.40f, 0.90f};
90 }
91 if (category == "Message") {
92 // Text yellow - communicative, bright
93 return {0.95f, 0.90f, 0.40f, 1.0f, 0.95f, 0.90f, 0.40f};
94 }
95 if (category == "Screen") {
96 // TV white/silver - display, clean
97 return {0.90f, 0.92f, 0.95f, 1.0f, 0.90f, 0.92f, 0.95f};
98 }
99 if (category == "Emulator") {
100 // Game red - playful, active
101 return {0.90f, 0.35f, 0.40f, 1.0f, 0.90f, 0.35f, 0.40f};
102 }
103 if (category == "Assembly") {
104 // Code green - technical, precise
105 return {0.40f, 0.90f, 0.50f, 1.0f, 0.40f, 0.90f, 0.50f};
106 }
107 if (category == "Settings") {
108 // Gear gray/blue - utility, system
109 return {0.60f, 0.70f, 0.80f, 1.0f, 0.60f, 0.70f, 0.80f};
110 }
111 if (category == "Memory") {
112 // Memory orange - data, technical
113 return {0.95f, 0.60f, 0.25f, 1.0f, 0.95f, 0.60f, 0.25f};
114 }
115 if (category == "Agent") {
116 // AI purple/violet - intelligent, futuristic
117 return {0.60f, 0.40f, 0.95f, 1.0f, 0.60f, 0.40f, 0.95f};
118 }
119
120 // Default - neutral blue
121 return {0.50f, 0.60f, 0.80f, 1.0f, 0.50f, 0.60f, 0.80f};
122}
123
124// ============================================================================
125// Session Lifecycle Management
126// ============================================================================
127
128void PanelManager::RegisterSession(size_t session_id) {
129 if (session_cards_.find(session_id) == session_cards_.end()) {
130 session_cards_[session_id] = std::vector<std::string>();
131 session_card_mapping_[session_id] =
132 std::unordered_map<std::string, std::string>();
134 LOG_INFO("PanelManager", "Registered session %zu (total: %zu)", session_id,
136 }
137}
138
139void PanelManager::UnregisterSession(size_t session_id) {
140 auto it = session_cards_.find(session_id);
141 if (it != session_cards_.end()) {
142 UnregisterSessionPanels(session_id);
143 session_cards_.erase(it);
144 session_card_mapping_.erase(session_id);
146
147 // Reset active session if it was the one being removed
148 if (active_session_ == session_id) {
149 active_session_ = 0;
150 if (!session_cards_.empty()) {
151 active_session_ = session_cards_.begin()->first;
152 }
153 }
154
155 LOG_INFO("PanelManager", "Unregistered session %zu (total: %zu)",
156 session_id, session_count_);
157 }
158}
159
160void PanelManager::SetActiveSession(size_t session_id) {
161 if (session_cards_.find(session_id) != session_cards_.end()) {
162 active_session_ = session_id;
163 }
164}
165
166// ============================================================================
167// Panel Registration
168// ============================================================================
169
170void PanelManager::RegisterPanel(size_t session_id,
171 const PanelDescriptor& base_info) {
172 RegisterSession(session_id); // Ensure session exists
173
174 std::string prefixed_id = MakePanelId(session_id, base_info.card_id);
175
176 // Check if already registered to avoid duplicates
177 if (cards_.find(prefixed_id) != cards_.end()) {
178 LOG_WARN("PanelManager",
179 "Panel '%s' already registered, skipping duplicate",
180 prefixed_id.c_str());
181 return;
182 }
183
184 // Create new PanelDescriptor with prefixed ID
185 PanelDescriptor prefixed_info = base_info;
186 prefixed_info.card_id = prefixed_id;
187
188 // If no visibility_flag provided, create centralized one
189 if (!prefixed_info.visibility_flag) {
190 centralized_visibility_[prefixed_id] = false; // Hidden by default
191 prefixed_info.visibility_flag = &centralized_visibility_[prefixed_id];
192 }
193
194 // Register the card
195 cards_[prefixed_id] = prefixed_info;
196
197 // Track in our session mapping
198 session_cards_[session_id].push_back(prefixed_id);
199 session_card_mapping_[session_id][base_info.card_id] = prefixed_id;
200
201 LOG_INFO("PanelManager", "Registered card %s -> %s for session %zu",
202 base_info.card_id.c_str(), prefixed_id.c_str(), session_id);
203}
204
205void PanelManager::RegisterPanel(size_t session_id, const std::string& card_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) {
213 PanelDescriptor info;
214 info.card_id = card_id;
215 info.display_name = display_name;
216 info.icon = icon;
217 info.category = category;
218 info.shortcut_hint = shortcut_hint;
219 info.priority = priority;
220 info.visibility_flag = nullptr; // Will be created in RegisterPanel
221 info.on_show = on_show;
222 info.on_hide = on_hide;
223
224 RegisterPanel(session_id, info);
225
226 // Set initial visibility if requested
227 if (visible_by_default) {
228 ShowPanel(session_id, card_id);
229 }
230}
231
232void PanelManager::UnregisterPanel(size_t session_id,
233 const std::string& base_card_id) {
234 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
235 if (prefixed_id.empty()) {
236 return;
237 }
238
239 auto it = cards_.find(prefixed_id);
240 if (it != cards_.end()) {
241 LOG_INFO("PanelManager", "Unregistered card: %s", prefixed_id.c_str());
242 cards_.erase(it);
243 centralized_visibility_.erase(prefixed_id);
244 pinned_panels_.erase(prefixed_id);
245
246 // Remove from session tracking
247 auto& session_card_list = session_cards_[session_id];
248 session_card_list.erase(std::remove(session_card_list.begin(),
249 session_card_list.end(), prefixed_id),
250 session_card_list.end());
251
252 session_card_mapping_[session_id].erase(base_card_id);
253 }
254}
255
256void PanelManager::UnregisterPanelsWithPrefix(const std::string& prefix) {
257 std::vector<std::string> to_remove;
258
259 // Find all cards with the given prefix
260 for (const auto& [card_id, card_info] : cards_) {
261 if (card_id.find(prefix) == 0) { // Starts with prefix
262 to_remove.push_back(card_id);
263 }
264 }
265
266 // Remove them
267 for (const auto& card_id : to_remove) {
268 cards_.erase(card_id);
269 centralized_visibility_.erase(card_id);
270 pinned_panels_.erase(card_id);
271 LOG_INFO("PanelManager", "Unregistered card with prefix '%s': %s",
272 prefix.c_str(), card_id.c_str());
273 }
274
275 // Also clean up session tracking
276 for (auto& [session_id, card_list] : session_cards_) {
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;
280 }),
281 card_list.end());
282 }
283}
284
286 cards_.clear();
288 pinned_panels_.clear();
289 session_cards_.clear();
290 session_card_mapping_.clear();
291 panel_instances_.clear();
292 session_count_ = 0;
293 LOG_INFO("PanelManager", "Cleared all cards");
294}
295
296// ============================================================================
297// EditorPanel Instance Management (Phase 4)
298// ============================================================================
299
300void PanelManager::RegisterEditorPanel(std::unique_ptr<EditorPanel> panel) {
301 if (!panel) {
302 LOG_ERROR("PanelManager", "Attempted to register null EditorPanel");
303 return;
304 }
305
306 // Phase 6: Resource Panel Limits
307 auto* resource_panel = dynamic_cast<ResourcePanel*>(panel.get());
308 if (resource_panel) {
309 EnforceResourceLimits(resource_panel->GetResourceType());
310 }
311
312 std::string panel_id = panel->GetId();
313
314 // Check if already registered
315 if (panel_instances_.find(panel_id) != panel_instances_.end()) {
316 LOG_WARN("PanelManager", "EditorPanel '%s' already registered, skipping",
317 panel_id.c_str());
318 return;
319 }
320
321 // Auto-register PanelDescriptor for sidebar/menu visibility
322 PanelDescriptor descriptor;
323 descriptor.card_id = panel_id;
324 descriptor.display_name = panel->GetDisplayName();
325 descriptor.icon = panel->GetIcon();
326 descriptor.category = panel->GetEditorCategory();
327 descriptor.priority = panel->GetPriority();
328 descriptor.shortcut_hint = panel->GetShortcutHint();
329 descriptor.visibility_flag = nullptr; // Will be created by RegisterPanel
330 descriptor.window_title = panel->GetIcon() + " " + panel->GetDisplayName();
331
332 // Check if panel should be visible by default
333 bool visible_by_default = panel->IsVisibleByDefault();
334
335 // Register the descriptor (creates visibility flag)
336 RegisterPanel(active_session_, descriptor);
337
338 // Set initial visibility if panel should be visible by default
339 if (visible_by_default) {
340 ShowPanel(active_session_, panel_id);
341 }
342
343 // Store the EditorPanel instance
344 panel_instances_[panel_id] = std::move(panel);
345
346 // Phase 6: Track resource panel usage
347 if (resource_panel) {
348 std::string type = resource_panel->GetResourceType();
349 resource_panels_[type].push_back(panel_id);
350 panel_resource_types_[panel_id] = type;
351 }
352
353 LOG_INFO("PanelManager", "Registered EditorPanel: %s (%s)", panel_id.c_str(),
354 descriptor.display_name.c_str());
355}
356
357// ============================================================================
358// Resource Management (Phase 6)
359// ============================================================================
360
361void PanelManager::EnforceResourceLimits(const std::string& resource_type) {
362 auto it = resource_panels_.find(resource_type);
363 if (it == resource_panels_.end())
364 return;
365
366 auto& panel_list = it->second;
367 size_t limit =
369
370 // Determine limit based on type
371 if (resource_type == "room")
373 else if (resource_type == "song")
375 else if (resource_type == "sheet")
377 else if (resource_type == "map")
379
380 // Evict panels until we have room for one more (current count < limit)
381 // Prioritize evicting non-pinned panels first, then oldest pinned ones
382 while (panel_list.size() >= limit) {
383 // First pass: find oldest non-pinned panel
384 std::string panel_to_evict;
385 for (const auto& panel_id : panel_list) {
386 if (!IsPanelPinned(panel_id)) {
387 panel_to_evict = panel_id;
388 break;
389 }
390 }
391
392 // If all are pinned, evict the oldest (front of list) anyway
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());
397 } else {
398 LOG_INFO("PanelManager",
399 "Evicting non-pinned resource panel: %s (type: %s)",
400 panel_to_evict.c_str(), resource_type.c_str());
401 }
402
403 // Remove from LRU list first to avoid iterator issues
404 panel_list.remove(panel_to_evict);
405
406 UnregisterEditorPanel(panel_to_evict);
407 }
408}
409
410void PanelManager::MarkPanelUsed(const std::string& panel_id) {
411 auto type_it = panel_resource_types_.find(panel_id);
412 if (type_it == panel_resource_types_.end())
413 return;
414
415 std::string type = type_it->second;
416 auto& list = resource_panels_[type];
417
418 // Move to back (MRU)
419 // std::list::remove is slow (linear), but list size is small (<10)
420 list.remove(panel_id);
421 list.push_back(panel_id);
422}
423
424void PanelManager::UnregisterEditorPanel(const std::string& panel_id) {
425 auto it = panel_instances_.find(panel_id);
426 if (it != panel_instances_.end()) {
427 // Call OnClose before removing
428 it->second->OnClose();
429 panel_instances_.erase(it);
430 LOG_INFO("PanelManager", "Unregistered EditorPanel: %s", panel_id.c_str());
431 }
432
433 // Also unregister the descriptor
435}
436
437EditorPanel* PanelManager::GetEditorPanel(const std::string& panel_id) {
438 auto it = panel_instances_.find(panel_id);
439 if (it != panel_instances_.end()) {
440 return it->second.get();
441 }
442 return nullptr;
443}
444
446 // Suppress panel drawing when dashboard is active (no editor selected yet)
447 // This ensures panels don't appear until user selects an editor
449 return;
450 }
451
452 for (auto& [panel_id, panel] : panel_instances_) {
453 // Check visibility via PanelDescriptor
454 if (!IsPanelVisible(panel_id)) {
455 continue;
456 }
457
458 // Category filtering: only draw if matches active category, pinned, or persistent
459 bool should_draw = false;
460 if (panel->GetEditorCategory() == active_category_) {
461 should_draw = true;
462 } else if (IsPanelPinned(panel_id)) {
463 should_draw = true;
464 } else if (panel->GetPanelCategory() == PanelCategory::Persistent) {
465 should_draw = true;
466 }
467
468 if (!should_draw) {
469 continue;
470 }
471
472 // Get visibility flag for the panel window
473 bool* visibility_flag = GetVisibilityFlag(panel_id);
474
475 // Get display name without icon - PanelWindow will add the icon
476 // This fixes the double-icon issue where both descriptor and PanelWindow added icons
477 std::string display_name = panel->GetDisplayName();
478
479 // Create PanelWindow and draw content
480 gui::PanelWindow window(display_name.c_str(), panel->GetIcon().c_str(),
481 visibility_flag);
482
483 // Use preferred width from EditorPanel if specified
484 float preferred_width = panel->GetPreferredWidth();
485 if (preferred_width > 0.0f) {
486 window.SetDefaultSize(preferred_width, 0); // 0 height = auto
487 }
488
489 // Enable pin functionality for cross-editor persistence
490 window.SetPinnable(true);
491 window.SetPinned(IsPanelPinned(panel_id));
492
493 // Wire up pin state change callback to persist to PanelManager
495 [this, panel_id](bool pinned) { SetPanelPinned(panel_id, pinned); });
496
497 if (window.Begin(visibility_flag)) {
498 panel->Draw(visibility_flag);
499 }
500 window.End();
501
502 // Handle visibility change (window closed via X button)
503 if (visibility_flag && !*visibility_flag) {
504 panel->OnClose();
505 }
506 }
507}
508
509void PanelManager::OnEditorSwitch(const std::string& from_category,
510 const std::string& to_category) {
511 if (from_category == to_category) {
512 return; // No switch needed
513 }
514
515 LOG_INFO("PanelManager", "Switching from category '%s' to '%s'",
516 from_category.c_str(), to_category.c_str());
517
518 // Hide non-pinned, non-persistent panels from previous category
519 for (const auto& [panel_id, panel] : panel_instances_) {
520 if (panel->GetEditorCategory() == from_category &&
521 !IsPanelPinned(panel_id) &&
522 panel->GetPanelCategory() != PanelCategory::Persistent) {
523 HidePanel(panel_id);
524 }
525 }
526
527 // Show default panels for new category
528 EditorType editor_type =
530 auto defaults = LayoutPresets::GetDefaultPanels(editor_type);
531 for (const auto& panel_id : defaults) {
532 ShowPanel(panel_id);
533 }
534
535 // Update active category
536 SetActiveCategory(to_category);
537}
538
539// ============================================================================
540// Panel Control (Programmatic, No GUI)
541// ============================================================================
542
543bool PanelManager::ShowPanel(size_t session_id,
544 const std::string& base_card_id) {
545 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
546 if (prefixed_id.empty()) {
547 return false;
548 }
549
550 auto it = cards_.find(prefixed_id);
551 if (it != cards_.end()) {
552 if (it->second.visibility_flag) {
553 *it->second.visibility_flag = true;
554 }
555 if (it->second.on_show) {
556 it->second.on_show();
557 }
558 return true;
559 }
560 return false;
561}
562
563bool PanelManager::HidePanel(size_t session_id,
564 const std::string& base_card_id) {
565 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
566 if (prefixed_id.empty()) {
567 return false;
568 }
569
570 auto it = cards_.find(prefixed_id);
571 if (it != cards_.end()) {
572 if (it->second.visibility_flag) {
573 *it->second.visibility_flag = false;
574 }
575 if (it->second.on_hide) {
576 it->second.on_hide();
577 }
578 return true;
579 }
580 return false;
581}
582
583bool PanelManager::TogglePanel(size_t session_id,
584 const std::string& base_card_id) {
585 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
586 if (prefixed_id.empty()) {
587 return false;
588 }
589
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;
594
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();
599 }
600 return true;
601 }
602 return false;
603}
604
605bool PanelManager::IsPanelVisible(size_t session_id,
606 const std::string& base_card_id) const {
607 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
608 if (prefixed_id.empty()) {
609 return false;
610 }
611
612 auto it = cards_.find(prefixed_id);
613 if (it != cards_.end() && it->second.visibility_flag) {
614 return *it->second.visibility_flag;
615 }
616 return false;
617}
618
619bool* PanelManager::GetVisibilityFlag(size_t session_id,
620 const std::string& base_card_id) {
621 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
622 if (prefixed_id.empty()) {
623 return nullptr;
624 }
625
626 auto it = cards_.find(prefixed_id);
627 if (it != cards_.end()) {
628 return it->second.visibility_flag;
629 }
630 return nullptr;
631}
632
633// ============================================================================
634// Batch Operations
635// ============================================================================
636
638 auto it = session_cards_.find(session_id);
639 if (it != session_cards_.end()) {
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();
646 }
647 }
648 }
649 }
650}
651
653 auto it = session_cards_.find(session_id);
654 if (it != session_cards_.end()) {
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();
661 }
662 }
663 }
664 }
665}
666
668 const std::string& category) {
669 auto it = session_cards_.find(session_id);
670 if (it != session_cards_.end()) {
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;
676 }
677 if (card_it->second.on_show) {
678 card_it->second.on_show();
679 }
680 }
681 }
682 }
683}
684
686 const std::string& category) {
687 auto it = session_cards_.find(session_id);
688 if (it != session_cards_.end()) {
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;
694 }
695 if (card_it->second.on_hide) {
696 card_it->second.on_hide();
697 }
698 }
699 }
700 }
701}
702
703void PanelManager::ShowOnlyPanel(size_t session_id,
704 const std::string& base_card_id) {
705 // First get the category of the target card
706 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
707 if (prefixed_id.empty()) {
708 return;
709 }
710
711 auto target_it = cards_.find(prefixed_id);
712 if (target_it == cards_.end()) {
713 return;
714 }
715
716 std::string category = target_it->second.category;
717
718 // Hide all cards in the same category
719 HideAllPanelsInCategory(session_id, category);
720
721 // Show the target card
722 ShowPanel(session_id, base_card_id);
723}
724
725// ============================================================================
726// Query Methods
727// ============================================================================
728
729std::vector<std::string> PanelManager::GetPanelsInSession(
730 size_t session_id) const {
731 auto it = session_cards_.find(session_id);
732 if (it != session_cards_.end()) {
733 return it->second;
734 }
735 return {};
736}
737
738std::vector<PanelDescriptor> PanelManager::GetPanelsInCategory(
739 size_t session_id, const std::string& category) const {
740 std::vector<PanelDescriptor> result;
741
742 auto it = session_cards_.find(session_id);
743 if (it != session_cards_.end()) {
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);
748 }
749 }
750 }
751
752 // Sort by priority
753 std::sort(result.begin(), result.end(),
754 [](const PanelDescriptor& a, const PanelDescriptor& b) {
755 return a.priority < b.priority;
756 });
757
758 return result;
759}
760
761std::vector<std::string> PanelManager::GetAllCategories(
762 size_t session_id) const {
763 std::vector<std::string> categories;
764
765 auto it = session_cards_.find(session_id);
766 if (it != session_cards_.end()) {
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);
773 }
774 }
775 }
776 }
777 return categories;
778}
779
781 size_t session_id, const std::string& base_card_id) const {
782 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
783 if (prefixed_id.empty()) {
784 return nullptr;
785 }
786
787 auto it = cards_.find(prefixed_id);
788 if (it != cards_.end()) {
789 return &it->second;
790 }
791 return nullptr;
792}
793
794std::vector<std::string> PanelManager::GetAllCategories() const {
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) ==
798 categories.end()) {
799 categories.push_back(card_info.category);
800 }
801 }
802 return categories;
803}
804
805// ============================================================================
806// Sidebar Keyboard Navigation
807// ============================================================================
808
810 size_t session_id, const std::vector<PanelDescriptor>& cards) {
811 // Click to focus - only focus if sidebar window is hovered and mouse clicked
812 if (!sidebar_has_focus_ && ImGui::IsWindowHovered(ImGuiHoveredFlags_None) &&
813 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
814 sidebar_has_focus_ = true;
815 focused_card_index_ = cards.empty() ? -1 : 0;
816 }
817
818 // No navigation if not focused or no cards
819 if (!sidebar_has_focus_ || cards.empty()) {
820 return;
821 }
822
823 // Escape to unfocus
824 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
825 sidebar_has_focus_ = false;
827 return;
828 }
829
830 int card_count = static_cast<int>(cards.size());
831
832 // Arrow keys / vim keys navigation
833 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ||
834 ImGui::IsKeyPressed(ImGuiKey_J)) {
835 focused_card_index_ = std::min(focused_card_index_ + 1, card_count - 1);
836 }
837 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) ||
838 ImGui::IsKeyPressed(ImGuiKey_K)) {
839 focused_card_index_ = std::max(focused_card_index_ - 1, 0);
840 }
841
842 // Home/End for quick navigation
843 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
845 }
846 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
847 focused_card_index_ = card_count - 1;
848 }
849
850 // Enter/Space to toggle card visibility
851 if (focused_card_index_ >= 0 && focused_card_index_ < card_count) {
852 if (ImGui::IsKeyPressed(ImGuiKey_Enter) ||
853 ImGui::IsKeyPressed(ImGuiKey_Space)) {
854 const auto& card = cards[focused_card_index_];
855 TogglePanel(session_id, card.card_id);
856 }
857 }
858}
859
860// ============================================================================
861// Favorites and Recent
862// ============================================================================
863
864void PanelManager::ToggleFavorite(const std::string& card_id) {
865 if (favorite_cards_.find(card_id) != favorite_cards_.end()) {
866 favorite_cards_.erase(card_id);
867 } else {
868 favorite_cards_.insert(card_id);
869 }
870 // TODO: Persist favorites to user settings
871}
872
873bool PanelManager::IsFavorite(const std::string& card_id) const {
874 return favorite_cards_.find(card_id) != favorite_cards_.end();
875}
876
877void PanelManager::AddToRecent(const std::string& card_id) {
878 // Remove if already exists (to move to front)
879 auto it = std::find(recent_cards_.begin(), recent_cards_.end(), card_id);
880 if (it != recent_cards_.end()) {
881 recent_cards_.erase(it);
882 }
883
884 // Add to front
885 recent_cards_.insert(recent_cards_.begin(), card_id);
886
887 // Trim if needed
888 if (recent_cards_.size() > kMaxRecentPanels) {
890 }
891}
892
893// ============================================================================
894// Workspace Presets
895// ============================================================================
896
897void PanelManager::SavePreset(const std::string& name,
898 const std::string& description) {
899 WorkspacePreset preset;
900 preset.name = name;
901 preset.description = description;
902
903 // Collect all visible cards across all sessions
904 for (const auto& [card_id, card_info] : cards_) {
905 if (card_info.visibility_flag && *card_info.visibility_flag) {
906 preset.visible_cards.push_back(card_id);
907 }
908 }
909
910 presets_[name] = preset;
912 LOG_INFO("PanelManager", "Saved preset: %s (%zu cards)", name.c_str(),
913 preset.visible_cards.size());
914}
915
916bool PanelManager::LoadPreset(const std::string& name) {
917 auto it = presets_.find(name);
918 if (it == presets_.end()) {
919 return false;
920 }
921
922 // First hide all cards
923 for (auto& [card_id, card_info] : cards_) {
924 if (card_info.visibility_flag) {
925 *card_info.visibility_flag = false;
926 }
927 }
928
929 // Then show preset cards
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();
936 }
937 }
938 }
939
940 LOG_INFO("PanelManager", "Loaded preset: %s", name.c_str());
941 return true;
942}
943
944void PanelManager::DeletePreset(const std::string& name) {
945 presets_.erase(name);
947}
948
949std::vector<PanelManager::WorkspacePreset> PanelManager::GetPresets() const {
950 std::vector<WorkspacePreset> result;
951 for (const auto& [name, preset] : presets_) {
952 result.push_back(preset);
953 }
954 return result;
955}
956
957// ============================================================================
958// Quick Actions
959// ============================================================================
960
961void PanelManager::ShowAll(size_t session_id) {
962 ShowAllPanelsInSession(session_id);
963}
964
965void PanelManager::HideAll(size_t session_id) {
966 HideAllPanelsInSession(session_id);
967}
968
969void PanelManager::ResetToDefaults(size_t session_id) {
970 // Hide all cards first
971 HideAllPanelsInSession(session_id);
972
973 // TODO: Load default visibility from config file or hardcoded defaults
974 LOG_INFO("PanelManager", "Reset to defaults for session %zu", session_id);
975}
976
977void PanelManager::ResetToDefaults(size_t session_id, EditorType editor_type) {
978 // Get category for this editor
979 std::string category = EditorRegistry::GetEditorCategory(editor_type);
980 if (category.empty()) {
981 LOG_WARN("PanelManager",
982 "No category found for editor type %d, skipping reset",
983 static_cast<int>(editor_type));
984 return;
985 }
986
987 // Hide all cards in this category first
988 HideAllPanelsInCategory(session_id, category);
989
990 // Get default cards from LayoutPresets
991 auto default_panels = LayoutPresets::GetDefaultPanels(editor_type);
992
993 // Show each default card
994 for (const auto& card_id : default_panels) {
995 if (ShowPanel(session_id, card_id)) {
996 LOG_INFO("PanelManager", "Showing default card: %s", card_id.c_str());
997 }
998 }
999
1000 LOG_INFO("PanelManager", "Reset %s editor to defaults (%zu cards visible)",
1001 category.c_str(), default_panels.size());
1002}
1003
1004// ============================================================================
1005// Statistics
1006// ============================================================================
1007
1008size_t PanelManager::GetVisiblePanelCount(size_t session_id) const {
1009 size_t count = 0;
1010 auto it = session_cards_.find(session_id);
1011 if (it != session_cards_.end()) {
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) {
1016 count++;
1017 }
1018 }
1019 }
1020 }
1021 return count;
1022}
1023
1024// ============================================================================
1025// Session Prefixing Utilities
1026// ============================================================================
1027
1028std::string PanelManager::MakePanelId(size_t session_id,
1029 const std::string& base_id) const {
1030 if (ShouldPrefixPanels()) {
1031 return absl::StrFormat("s%zu.%s", session_id, base_id);
1032 }
1033 return base_id;
1034}
1035
1036// ============================================================================
1037// Helper Methods (Private)
1038// ============================================================================
1039
1043
1044std::string PanelManager::GetPrefixedPanelId(size_t session_id,
1045 const std::string& base_id) const {
1046 auto session_it = session_card_mapping_.find(session_id);
1047 if (session_it != session_card_mapping_.end()) {
1048 auto card_it = session_it->second.find(base_id);
1049 if (card_it != session_it->second.end()) {
1050 return card_it->second;
1051 }
1052 }
1053
1054 // Fallback: try unprefixed ID (for single session or direct access)
1055 if (cards_.find(base_id) != cards_.end()) {
1056 return base_id;
1057 }
1058
1059 return ""; // Panel not found
1060}
1061
1063 auto it = session_cards_.find(session_id);
1064 if (it != session_cards_.end()) {
1065 for (const auto& prefixed_card_id : it->second) {
1066 cards_.erase(prefixed_card_id);
1067 centralized_visibility_.erase(prefixed_card_id);
1068 pinned_panels_.erase(prefixed_card_id);
1069 }
1070 }
1071}
1072
1074 auto config_dir_result = util::PlatformPaths::GetConfigDirectory();
1075 if (!config_dir_result.ok()) {
1076 LOG_ERROR("PanelManager", "Failed to get config directory: %s",
1077 config_dir_result.status().ToString().c_str());
1078 return;
1079 }
1080
1081 std::filesystem::path presets_file =
1082 *config_dir_result / "layout_presets.json";
1083
1084 try {
1085 yaze::Json j;
1086 j["version"] = 1;
1087 j["presets"] = yaze::Json::object();
1088
1089 for (const auto& [name, preset] : presets_) {
1090 yaze::Json preset_json;
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;
1095 }
1096
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());
1101 return;
1102 }
1103
1104 file << j.dump(2);
1105 file.close();
1106
1107 LOG_INFO("PanelManager", "Saved %zu presets to %s", presets_.size(),
1108 presets_file.string().c_str());
1109 } catch (const std::exception& e) {
1110 LOG_ERROR("PanelManager", "Error saving presets: %s", e.what());
1111 }
1112}
1113
1115 auto config_dir_result = util::PlatformPaths::GetConfigDirectory();
1116 if (!config_dir_result.ok()) {
1117 LOG_WARN("PanelManager", "Failed to get config directory: %s",
1118 config_dir_result.status().ToString().c_str());
1119 return;
1120 }
1121
1122 std::filesystem::path presets_file =
1123 *config_dir_result / "layout_presets.json";
1124
1125 if (!util::PlatformPaths::Exists(presets_file)) {
1126 LOG_INFO("PanelManager", "No presets file found at %s",
1127 presets_file.string().c_str());
1128 return;
1129 }
1130
1131 try {
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());
1136 return;
1137 }
1138
1139 yaze::Json j;
1140 file >> j;
1141 file.close();
1142
1143 if (!j.contains("presets")) {
1144 LOG_WARN("PanelManager", "Invalid presets file format");
1145 return;
1146 }
1147
1148 size_t loaded_count = 0;
1149 // Note: iterating over yaze::Json or nlohmann::json requires standard loop if using alias
1150 // However, yaze::Json alias is just nlohmann::json when enabled.
1151 // When disabled, the loop will just not execute or stub loop.
1152 // But wait, nlohmann::json iterators return key/value pair or special iterator.
1153 // Let's check how the loop was written: for (auto& [name, preset_json] : j["presets"].items())
1154 // My stub has items(), but nlohmann::json uses items() too.
1155 for (auto& [name, preset_json] : j["presets"].items()) {
1156 WorkspacePreset preset;
1157 preset.name = preset_json.value("name", name);
1158 preset.description = preset_json.value("description", "");
1159
1160 if (preset_json.contains("visible_cards")) {
1161 yaze::Json visible_cards = preset_json["visible_cards"];
1162 if (visible_cards.is_array()) {
1163 for (const auto& card : visible_cards) {
1164 if (card.is_string()) {
1165 preset.visible_cards.push_back(card.get<std::string>());
1166 }
1167 }
1168 }
1169 }
1170
1171 presets_[name] = preset;
1172 loaded_count++;
1173 }
1174
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());
1179 }
1180}
1181
1182// =============================================================================
1183// File Browser Integration
1184// =============================================================================
1185
1186FileBrowser* PanelManager::GetFileBrowser(const std::string& category) {
1187 auto it = category_file_browsers_.find(category);
1188 if (it != category_file_browsers_.end()) {
1189 return it->second.get();
1190 }
1191 return nullptr;
1192}
1193
1194void PanelManager::EnableFileBrowser(const std::string& category,
1195 const std::string& root_path) {
1196 if (category_file_browsers_.find(category) == category_file_browsers_.end()) {
1197 auto browser = std::make_unique<FileBrowser>();
1198
1199 // Set callback to forward file clicks
1200 browser->SetFileClickedCallback([this, category](const std::string& path) {
1201 if (on_file_clicked_) {
1202 on_file_clicked_(category, path);
1203 }
1204 // Also activate the editor for this category
1205 if (on_card_clicked_) {
1206 on_card_clicked_(category);
1207 }
1208 });
1209
1210 if (!root_path.empty()) {
1211 browser->SetRootPath(root_path);
1212 }
1213
1214 // Set defaults for Assembly file browser
1215 if (category == "Assembly") {
1216 browser->SetFileFilter({".asm", ".s", ".65c816", ".inc", ".h"});
1217 }
1218
1219 category_file_browsers_[category] = std::move(browser);
1220 LOG_INFO("PanelManager", "Enabled file browser for category: %s",
1221 category.c_str());
1222 }
1223}
1224
1225void PanelManager::DisableFileBrowser(const std::string& category) {
1226 category_file_browsers_.erase(category);
1227}
1228
1229bool PanelManager::HasFileBrowser(const std::string& category) const {
1230 return category_file_browsers_.find(category) !=
1232}
1233
1234void PanelManager::SetFileBrowserPath(const std::string& category,
1235 const std::string& path) {
1236 auto it = category_file_browsers_.find(category);
1237 if (it != category_file_browsers_.end()) {
1238 it->second->SetRootPath(path);
1239 }
1240}
1241
1242// ============================================================================
1243// Pinning (Phase 3 scaffold)
1244// ============================================================================
1245
1246void PanelManager::SetPanelPinned(size_t session_id,
1247 const std::string& base_card_id,
1248 bool pinned) {
1249 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
1250 if (prefixed_id.empty()) {
1251 prefixed_id = MakePanelId(session_id, base_card_id);
1252 }
1253 pinned_panels_[prefixed_id] = pinned;
1254}
1255
1256bool PanelManager::IsPanelPinned(size_t session_id,
1257 const std::string& base_card_id) const {
1258 std::string prefixed_id = GetPrefixedPanelId(session_id, base_card_id);
1259 if (prefixed_id.empty()) {
1260 prefixed_id = MakePanelId(session_id, base_card_id);
1261 }
1262 auto it = pinned_panels_.find(prefixed_id);
1263 return it != pinned_panels_.end() && it->second;
1264}
1265
1266std::vector<std::string> PanelManager::GetPinnedPanels(
1267 size_t session_id) const {
1268 std::vector<std::string> result;
1269 const std::string prefix =
1270 ShouldPrefixPanels() ? absl::StrFormat("s%zu.", session_id) : "";
1271
1272 for (const auto& [panel_id, pinned] : pinned_panels_) {
1273 if (!pinned)
1274 continue;
1275 if (prefix.empty() || panel_id.rfind(prefix, 0) == 0) {
1276 result.push_back(panel_id);
1277 }
1278 }
1279 return result;
1280}
1281
1282void PanelManager::SetPanelPinned(const std::string& base_card_id,
1283 bool pinned) {
1284 SetPanelPinned(active_session_, base_card_id, pinned);
1285}
1286
1287bool PanelManager::IsPanelPinned(const std::string& base_card_id) const {
1288 return IsPanelPinned(active_session_, base_card_id);
1289}
1290
1291std::vector<std::string> PanelManager::GetPinnedPanels() const {
1293}
1294
1295// =============================================================================
1296// Panel Validation
1297// =============================================================================
1298
1300 const std::string& card_id) const {
1301 PanelValidationResult result;
1302 result.card_id = card_id;
1303
1304 auto it = cards_.find(card_id);
1305 if (it == cards_.end()) {
1306 result.expected_title = "";
1307 result.found_in_imgui = false;
1308 result.message = "Panel not registered";
1309 return result;
1310 }
1311
1312 const PanelDescriptor& info = it->second;
1313 result.expected_title = info.GetWindowTitle();
1314
1315 if (result.expected_title.empty()) {
1316 result.found_in_imgui = false;
1317 result.message = "FAIL - Missing window title";
1318 return result;
1319 }
1320
1321 // Check if ImGui has a window with this title
1322 ImGuiWindow* window = ImGui::FindWindowByName(result.expected_title.c_str());
1323 result.found_in_imgui = (window != nullptr);
1324
1325 if (result.found_in_imgui) {
1326 result.message = "OK - Window found";
1327 } else {
1328 result.message = "FAIL - No window with title: " + result.expected_title;
1329 }
1330
1331 return result;
1332}
1333
1334std::vector<PanelManager::PanelValidationResult> PanelManager::ValidatePanels()
1335 const {
1336 std::vector<PanelValidationResult> results;
1337 results.reserve(cards_.size());
1338
1339 for (const auto& [card_id, info] : cards_) {
1340 results.push_back(ValidatePanel(card_id));
1341 }
1342
1343 return results;
1344}
1345
1346} // namespace editor
1347} // 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.
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)
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)
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)
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_
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)
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
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::function< void()> on_hide
Get the expressive theme color for a category.