yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
right_panel_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <chrono>
6#include <cmath>
7#include <filesystem>
8
9#include "absl/strings/str_format.h"
18#include "app/gui/core/icons.h"
19#include "app/gui/core/input.h"
22#include "app/gui/core/style.h"
28#include "imgui/imgui.h"
29#include "util/log.h"
30#include "util/platform_paths.h"
31
32namespace yaze {
33namespace editor {
34
35namespace {
36
37std::string ResolveAgentChatHistoryPath() {
38 auto agent_dir = util::PlatformPaths::GetAppDataSubdirectory("agent");
39 if (agent_dir.ok()) {
40 return (*agent_dir / "agent_chat_history.json").string();
41 }
43 if (temp_dir.ok()) {
44 return (*temp_dir / "agent_chat_history.json").string();
45 }
46 return (std::filesystem::current_path() / "agent_chat_history.json").string();
47}
48
49std::string BuildSelectionContextSummary(const SelectionContext& selection) {
50 if (selection.type == SelectionType::kNone) {
51 return "";
52 }
53 std::string context =
54 absl::StrFormat("Selection: %s", GetSelectionTypeName(selection.type));
55 if (!selection.display_name.empty()) {
56 context += absl::StrFormat("\nName: %s", selection.display_name);
57 }
58 if (selection.id >= 0) {
59 context += absl::StrFormat("\nID: 0x%X", selection.id);
60 }
61 if (selection.secondary_id >= 0) {
62 context += absl::StrFormat("\nSecondary: 0x%X", selection.secondary_id);
63 }
64 if (selection.read_only) {
65 context += "\nRead Only: true";
66 }
67 return context;
68}
69
79
81 for (size_t i = 0; i < kRightPanelSwitchOrder.size(); ++i) {
82 if (kRightPanelSwitchOrder[i] == type) {
83 return static_cast<int>(i);
84 }
85 }
86 return -1;
87}
88
90 RightPanelManager::PanelType current, int direction) {
91 if (kRightPanelSwitchOrder.empty()) {
93 }
94 int index = FindRightPanelIndex(current);
95 if (index < 0) {
96 index = 0;
97 }
98 const int size = static_cast<int>(kRightPanelSwitchOrder.size());
99 const int next = (index + direction + size) % size;
100 return kRightPanelSwitchOrder[static_cast<size_t>(next)];
101}
102
104 switch (type) {
106 return "View: Toggle Project Panel";
108 return "View: Toggle AI Agent Panel";
110 return "View: Toggle Proposals Panel";
112 return "View: Toggle Settings Panel";
114 return "View: Toggle Help Panel";
116 return "View: Toggle Notifications Panel";
118 return "View: Toggle Properties Panel";
120 default:
121 return "";
122 }
123}
124
125} // namespace
126
128 switch (type) {
130 return "None";
132 return "AI Agent";
134 return "Proposals";
136 return "Settings";
138 return "Help";
140 return "Notifications";
142 return "Properties";
144 return "Project";
145 default:
146 return "Unknown";
147 }
148}
149
172
174 switch (type) {
176 return "agent_chat";
178 return "proposals";
180 return "settings";
181 case PanelType::kHelp:
182 return "help";
184 return "notifications";
186 return "properties";
188 return "project";
189 case PanelType::kNone:
190 default:
191 return "none";
192 }
193}
194
196 if (active_panel_ == type) {
197 ClosePanel();
198 } else {
199 // Opens the requested panel (also handles re-opening during close animation)
200 OpenPanel(type);
201 }
202}
203
207
209 // If we were closing, cancel the close animation
210 closing_ = false;
212
213 active_panel_ = type;
214 animating_ = true;
215 animation_target_ = 1.0f;
216
217 // Check if animations are enabled
218 if (!gui::GetAnimator().IsEnabled()) {
219 panel_animation_ = 1.0f;
220 animating_ = false;
221 }
222 // Otherwise keep current panel_animation_ for smooth transition
223}
224
226 if (!gui::GetAnimator().IsEnabled()) {
227 // Instant close
229 closing_ = false;
231 panel_animation_ = 0.0f;
232 animating_ = false;
233 return;
234 }
235
236 // Start close animation — keep the panel type so we can still draw it
237 closing_ = true;
240 animating_ = true;
241 animation_target_ = 0.0f;
242}
243
244void RightPanelManager::CyclePanel(int direction) {
245 if (direction == 0) {
246 return;
247 }
248
249 const PanelType current_panel =
251 if (current_panel == PanelType::kNone) {
252 return;
253 }
254
255 const int step = direction > 0 ? 1 : -1;
256 OpenPanel(StepRightPanel(current_panel, step));
257}
258
260 // Snap transition state to a stable endpoint. This avoids stale intermediate
261 // frames being composited when the OS moves the app across spaces.
262 (void)visible;
263 closing_ = false;
265 animating_ = false;
266
269}
270
272 // Determine which panel to measure: active panel, or the one being closed
273 PanelType effective_panel = active_panel_;
274 if (effective_panel == PanelType::kNone && closing_) {
275 effective_panel = closing_panel_;
276 }
277 if (effective_panel == PanelType::kNone) {
278 return 0.0f;
279 }
280
281 ImGuiContext* context = ImGui::GetCurrentContext();
282 if (!context) {
283 return GetConfiguredPanelWidth(effective_panel) * panel_animation_;
284 }
285
286 const ImGuiViewport* viewport = ImGui::GetMainViewport();
287 if (!viewport) {
288 return GetConfiguredPanelWidth(effective_panel) * panel_animation_;
289 }
290
291 const float vp_width = viewport->WorkSize.x;
292 const float width = GetClampedPanelWidth(effective_panel, vp_width);
293
294 // Scale by animation progress for smooth docking space adjustment
295 return width * panel_animation_;
296}
297
299 if (type == PanelType::kNone) {
300 return;
301 }
302 float viewport_width = 0.0f;
303 if (const ImGuiViewport* viewport = ImGui::GetMainViewport()) {
304 viewport_width = viewport->WorkSize.x;
305 }
306 if (viewport_width <= 0.0f && ImGui::GetCurrentContext()) {
307 viewport_width = ImGui::GetIO().DisplaySize.x;
308 }
309 const auto limits = GetPanelSizeLimits(type);
310 float clamped = std::max(limits.min_width, width);
311 if (viewport_width > 0.0f) {
312 const float ratio = viewport_width < 768.0f
313 ? std::max(0.88f, limits.max_width_ratio)
314 : limits.max_width_ratio;
315 const float max_width = std::max(limits.min_width, viewport_width * ratio);
316 clamped = std::clamp(clamped, limits.min_width, max_width);
317 }
318
319 float* target = nullptr;
320 switch (type) {
322 target = &agent_chat_width_;
323 break;
325 target = &proposals_width_;
326 break;
328 target = &settings_width_;
329 break;
330 case PanelType::kHelp:
331 target = &help_width_;
332 break;
334 target = &notifications_width_;
335 break;
337 target = &properties_width_;
338 break;
340 target = &project_width_;
341 break;
342 default:
343 break;
344 }
345 if (!target) {
346 return;
347 }
348 if (std::abs(*target - clamped) < 0.5f) {
349 return;
350 }
351 *target = clamped;
352#if !defined(NDEBUG)
353 LOG_INFO("RightPanelManager",
354 "SetPanelWidth type=%d requested=%.1f clamped=%.1f",
355 static_cast<int>(type), width, clamped);
356#endif
357 NotifyPanelWidthChanged(type, *target);
358}
359
381
383 EditorType editor) {
384 switch (type) {
386 return std::max(gui::UIConfig::kPanelWidthAgentChat, 480.0f);
388 return std::max(gui::UIConfig::kPanelWidthProposals, 440.0f);
390 return std::max(gui::UIConfig::kPanelWidthSettings, 380.0f);
391 case PanelType::kHelp:
392 return std::max(gui::UIConfig::kPanelWidthHelp, 380.0f);
394 return std::max(gui::UIConfig::kPanelWidthNotifications, 380.0f);
396 // Property panel can be wider in certain editors.
397 if (editor == EditorType::kDungeon) {
398 return 440.0f;
399 }
400 return std::max(gui::UIConfig::kPanelWidthProperties, 400.0f);
402 return std::max(gui::UIConfig::kPanelWidthProject, 420.0f);
403 default:
404 return std::max(gui::UIConfig::kPanelWidthMedium, 380.0f);
405 }
406}
407
409 const PanelSizeLimits& limits) {
410 if (type == PanelType::kNone) {
411 return;
412 }
413 PanelSizeLimits normalized = limits;
414 normalized.min_width =
416 normalized.max_width_ratio =
417 std::clamp(normalized.max_width_ratio, 0.25f, 0.95f);
418 panel_size_limits_[PanelTypeKey(type)] = normalized;
419}
420
422 PanelType type) const {
423 auto it = panel_size_limits_.find(PanelTypeKey(type));
424 if (it != panel_size_limits_.end()) {
425 return it->second;
426 }
427
428 PanelSizeLimits defaults;
429 switch (type) {
432 defaults.max_width_ratio = 0.90f;
433 break;
436 defaults.max_width_ratio = 0.86f;
437 break;
440 defaults.max_width_ratio = 0.80f;
441 break;
442 case PanelType::kHelp:
444 defaults.max_width_ratio = 0.80f;
445 break;
448 defaults.max_width_ratio = 0.82f;
449 break;
452 defaults.max_width_ratio = 0.90f;
453 break;
456 defaults.max_width_ratio = 0.86f;
457 break;
458 case PanelType::kNone:
459 default:
460 break;
461 }
462 return defaults;
463}
464
466 switch (type) {
468 return agent_chat_width_;
470 return proposals_width_;
472 return settings_width_;
473 case PanelType::kHelp:
474 return help_width_;
478 return properties_width_;
480 return project_width_;
481 case PanelType::kNone:
482 default:
483 return 0.0f;
484 }
485}
486
488 float viewport_width) const {
489 float width = GetConfiguredPanelWidth(type);
490 if (width <= 0.0f) {
491 return width;
492 }
493 const auto limits = GetPanelSizeLimits(type);
494 const float ratio = viewport_width < 768.0f
495 ? std::max(0.88f, limits.max_width_ratio)
496 : limits.max_width_ratio;
497 const float max_width = std::max(limits.min_width, viewport_width * ratio);
498 return std::clamp(width, limits.min_width, max_width);
499}
500
503 on_panel_width_changed_(type, width);
504 }
505}
506
519
521 const std::unordered_map<std::string, float>& widths) {
522#if !defined(NDEBUG)
523 LOG_INFO("RightPanelManager", "RestorePanelWidths: %zu entries from settings",
524 widths.size());
525#endif
526 auto apply = [&](PanelType type) {
527 auto it = widths.find(PanelTypeKey(type));
528 if (it != widths.end()) {
529 SetPanelWidth(type, it->second);
530 }
531 };
535 apply(PanelType::kHelp);
538 apply(PanelType::kProject);
539}
540
542 // Nothing to draw if no panel is active and no close animation running
544 return;
545 }
546
547 // Handle Escape key to close panel
549 ImGui::IsKeyPressed(ImGuiKey_Escape)) {
550 ClosePanel();
551 // Don't return — we need to start drawing the close animation this frame
552 if (!closing_)
553 return;
554 }
555
556 const bool animations_enabled = gui::GetAnimator().IsEnabled();
557 if (!animations_enabled && animating_) {
559 animating_ = false;
560 if (closing_ && animation_target_ == 0.0f) {
561 closing_ = false;
563 return;
564 }
565 }
566
567 // Advance animation
568 if (animating_ && animations_enabled) {
569 // Clamp dt to avoid giant interpolation jumps after focus/space changes.
570 float delta_time = std::clamp(ImGui::GetIO().DeltaTime, 0.0f, 1.0f / 20.0f);
571 float speed = gui::UIConfig::kAnimationSpeed;
572 switch (gui::GetAnimator().motion_profile()) {
574 speed *= 1.20f;
575 break;
577 speed *= 0.75f;
578 break;
580 default:
581 break;
582 }
583 float diff = animation_target_ - panel_animation_;
584 panel_animation_ += diff * std::min(1.0f, delta_time * speed);
585
586 // Snap to target when close enough
587 if (std::abs(animation_target_ - panel_animation_) <
590 animating_ = false;
591
592 // Close animation finished — fully clean up
593 if (closing_ && animation_target_ == 0.0f) {
594 closing_ = false;
596 return;
597 }
598 }
599 }
600
601 // Determine which panel type to draw content for
602 PanelType draw_panel = active_panel_;
603 if (draw_panel == PanelType::kNone && closing_) {
604 draw_panel = closing_panel_;
605 }
606
607 const ImGuiViewport* viewport = ImGui::GetMainViewport();
608 const float viewport_width = viewport->WorkSize.x;
609 const float top_inset = gui::LayoutHelpers::GetTopInset();
610 const float bottom_safe = gui::LayoutHelpers::GetSafeAreaInsets().bottom;
611 const float viewport_height =
612 std::max(0.0f, viewport->WorkSize.y - top_inset - bottom_safe);
613
614 // Keep full-width state explicit so drag-resize and animation remain stable.
615 const float full_width =
616 (draw_panel == PanelType::kNone)
617 ? 0.0f
618 : GetClampedPanelWidth(draw_panel, viewport_width);
619 const float animated_width = full_width * panel_animation_;
620
621 // Use SurfaceContainer for slightly elevated panel background
622 ImVec4 panel_bg = gui::GetSurfaceContainerVec4();
623 ImVec4 panel_border = gui::GetOutlineVec4();
624
625 ImGuiWindowFlags panel_flags =
626 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
627 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking |
628 ImGuiWindowFlags_NoNavFocus;
629
630 // Position panel: slides from right edge. At animation=1.0, fully visible.
631 // At animation=0.0, fully off-screen to the right.
632 float panel_x = viewport->WorkPos.x + viewport_width - animated_width;
633 ImGui::SetNextWindowPos(ImVec2(panel_x, viewport->WorkPos.y + top_inset));
634 ImGui::SetNextWindowSize(ImVec2(full_width, viewport_height));
635
636 gui::StyledWindow panel("##RightPanel",
637 {.bg = panel_bg,
638 .border = panel_border,
639 .padding = ImVec2(0.0f, 0.0f),
640 .border_size = 1.0f},
641 nullptr, panel_flags);
642 if (panel) {
643 // Draw enhanced panel header
644 DrawPanelHeader(GetPanelTypeName(draw_panel), GetPanelTypeIcon(draw_panel));
645
646 // Content area with padding and minimum height so content never collapses
647 gui::StyleVarGuard content_padding(
648 ImGuiStyleVar_WindowPadding,
651 const bool panel_content_open = gui::LayoutHelpers::BeginContentChild(
652 "##PanelContent", ImVec2(0.0f, gui::UIConfig::kContentMinHeightList),
653 false, ImGuiWindowFlags_AlwaysUseWindowPadding);
654 if (panel_content_open) {
655 switch (draw_panel) {
658 break;
661 break;
664 break;
665 case PanelType::kHelp:
667 break;
670 break;
673 break;
676 break;
677 default:
678 break;
679 }
680 }
682
683 // VSCode-style splitter: drag from the left edge to resize.
685 const float handle_width = gui::UIConfig::kSplitterWidth;
686 const ImVec2 win_pos = ImGui::GetWindowPos();
687 const float win_height = ImGui::GetWindowHeight();
688 ImGui::SetCursorScreenPos(
689 ImVec2(win_pos.x - handle_width * 0.5f, win_pos.y));
690 ImGui::InvisibleButton("##RightPanelResizeHandle",
691 ImVec2(handle_width, win_height));
692 const bool handle_hovered = ImGui::IsItemHovered();
693 const bool handle_active = ImGui::IsItemActive();
694 if (handle_hovered || handle_active) {
695 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
696 }
697 if (handle_hovered &&
698 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
701 }
702 if (handle_active) {
703 const float new_width = GetConfiguredPanelWidth(active_panel_) -
704 ImGui::GetIO().MouseDelta.x;
705 SetPanelWidth(active_panel_, new_width);
706 ImGui::SetTooltip("Width: %.0f px",
708 }
709
710 ImVec4 handle_color = gui::GetOutlineVec4();
711 handle_color.w = handle_active ? 0.95f : (handle_hovered ? 0.72f : 0.35f);
712 ImGui::GetWindowDrawList()->AddLine(
713 ImVec2(win_pos.x, win_pos.y),
714 ImVec2(win_pos.x, win_pos.y + win_height),
715 ImGui::GetColorU32(handle_color), handle_active ? 2.0f : 1.0f);
716 }
717 }
718}
719
720void RightPanelManager::DrawPanelHeader(const char* title, const char* icon) {
721 const float header_height = gui::UIConfig::kPanelHeaderHeight;
722 const float padding = gui::UIConfig::kPanelPaddingLarge;
723
724 // Header background - slightly elevated surface
725 ImVec2 header_min = ImGui::GetCursorScreenPos();
726 ImVec2 header_max = ImVec2(header_min.x + ImGui::GetWindowWidth(),
727 header_min.y + header_height);
728
729 ImDrawList* draw_list = ImGui::GetWindowDrawList();
730 draw_list->AddRectFilled(
731 header_min, header_max,
732 ImGui::GetColorU32(gui::GetSurfaceContainerHighVec4()));
733
734 // Draw subtle bottom border
735 draw_list->AddLine(ImVec2(header_min.x, header_max.y),
736 ImVec2(header_max.x, header_max.y),
737 ImGui::GetColorU32(gui::GetOutlineVec4()), 1.0f);
738
739 // Position content within header
740 ImGui::SetCursorPosX(padding);
741 ImGui::SetCursorPosY(ImGui::GetCursorPosY() +
742 (header_height - ImGui::GetTextLineHeight()) * 0.5f);
743
744 // Panel icon with primary color
746
747 ImGui::SameLine();
748
749 // Panel title (use current style text color)
750 gui::ColoredText(title, ImGui::GetStyleColorVec4(ImGuiCol_Text));
751
752 const PanelType current_panel =
754 const std::string previous_shortcut =
755 GetShortcutLabel("View: Previous Right Panel", "");
756 const std::string next_shortcut =
757 GetShortcutLabel("View: Next Right Panel", "");
758 const std::string previous_tooltip =
759 previous_shortcut.empty() ? "Previous right panel"
760 : absl::StrFormat("Previous right panel (%s)",
761 previous_shortcut.c_str());
762 const std::string next_tooltip =
763 next_shortcut.empty()
764 ? "Next right panel"
765 : absl::StrFormat("Next right panel (%s)", next_shortcut.c_str());
766
767 ImGui::SameLine(0.0f, gui::UIConfig::kHeaderButtonSpacing);
769 previous_tooltip.c_str(), false,
770 gui::GetTextSecondaryVec4(), "right_sidebar",
771 "switch_panel_prev")) {
773 }
774
775 ImGui::SameLine(0.0f, gui::UIConfig::kHeaderButtonGap);
777 ICON_MD_SWAP_HORIZ, gui::IconSize::Small(), "Panel switcher", false,
778 gui::GetTextSecondaryVec4(), "right_sidebar", "switch_panel_menu")) {
779 ImGui::OpenPopup("##RightPanelSwitcher");
780 }
781
782 ImGui::SameLine(0.0f, gui::UIConfig::kHeaderButtonGap);
784 next_tooltip.c_str(), false,
785 gui::GetTextSecondaryVec4(), "right_sidebar",
786 "switch_panel_next")) {
788 }
789
790 if (ImGui::BeginPopup("##RightPanelSwitcher")) {
791 for (PanelType panel_type : kRightPanelSwitchOrder) {
792 std::string label = absl::StrFormat("%s %s", GetPanelTypeIcon(panel_type),
793 GetPanelTypeName(panel_type));
794 const char* shortcut_action = GetPanelShortcutAction(panel_type);
795 std::string shortcut;
796 if (shortcut_action[0] != '\0') {
797 shortcut = GetShortcutLabel(shortcut_action, "");
798 if (shortcut == "Unassigned") {
799 shortcut.clear();
800 }
801 }
802 if (ImGui::MenuItem(label.c_str(),
803 shortcut.empty() ? nullptr : shortcut.c_str(),
804 current_panel == panel_type)) {
805 OpenPanel(panel_type);
806 }
807 }
808 ImGui::EndPopup();
809 }
810
811 // Right-aligned buttons
812 const ImVec2 chrome_button_size = gui::IconSize::Toolbar();
813 const float button_size = chrome_button_size.x;
814 const float button_y =
815 header_min.y + (header_height - chrome_button_size.y) * 0.5f;
816 float current_x = ImGui::GetWindowWidth() - button_size - padding;
817
818 // Close button
819 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, button_y));
820 if (gui::TransparentIconButton(ICON_MD_CANCEL, chrome_button_size,
821 "Close Panel (Esc)", false, ImVec4(0, 0, 0, 0),
822 "right_sidebar", "close_panel")) {
823 ClosePanel();
824 }
825
826 // Lock Toggle (Only for Properties Panel)
828 current_x -= (button_size + 4.0f);
829 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, button_y));
830
833 chrome_button_size,
834 properties_locked_ ? "Unlock Selection" : "Lock Selection",
835 properties_locked_, ImVec4(0, 0, 0, 0), "right_sidebar",
836 "lock_selection")) {
838 }
839 }
840
841 // Move cursor past the header
842 ImGui::SetCursorPosY(header_height + 8.0f);
843}
844
845// =============================================================================
846// Panel Styling Helpers
847// =============================================================================
848
849bool RightPanelManager::BeginPanelSection(const char* label, const char* icon,
850 bool default_open) {
851 gui::StyleColorGuard section_colors({
852 {ImGuiCol_Header, gui::GetSurfaceContainerHighVec4()},
853 {ImGuiCol_HeaderHovered, gui::GetSurfaceContainerHighestVec4()},
854 {ImGuiCol_HeaderActive, gui::GetSurfaceContainerHighestVec4()},
855 });
856 gui::StyleVarGuard section_vars({
857 {ImGuiStyleVar_FramePadding, ImVec2(8.0f, 6.0f)},
858 {ImGuiStyleVar_FrameRounding, 4.0f},
859 });
860
861 // Build header text with icon if provided
862 std::string header_text;
863 if (icon) {
864 header_text = std::string(icon) + " " + label;
865 } else {
866 header_text = label;
867 }
868
869 ImGuiTreeNodeFlags flags =
870 ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth |
871 ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_FramePadding;
872 if (default_open) {
873 flags |= ImGuiTreeNodeFlags_DefaultOpen;
874 }
875
876 bool is_open = ImGui::TreeNodeEx(header_text.c_str(), flags);
877
878 if (is_open) {
879 ImGui::Spacing();
880 ImGui::Indent(4.0f);
881 }
882
883 return is_open;
884}
885
887 ImGui::Unindent(4.0f);
888 ImGui::TreePop();
889 ImGui::Spacing();
890}
891
893 ImGui::Spacing();
894 {
895 gui::StyleColorGuard sep_color(ImGuiCol_Separator, gui::GetOutlineVec4());
896 ImGui::Separator();
897 }
898 ImGui::Spacing();
899}
900
904
905void RightPanelManager::DrawPanelValue(const char* label, const char* value) {
907 ImGui::SameLine();
908 ImGui::TextUnformatted(value);
909}
910
912 gui::StyleColorGuard desc_color(ImGuiCol_Text, gui::GetTextDisabledVec4());
913 ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x);
914 ImGui::TextWrapped("%s", text);
915 ImGui::PopTextWrapPos();
916}
917
919 const std::string& action, const std::string& fallback) const {
920 if (!shortcut_manager_) {
921 return fallback;
922 }
923
924 const Shortcut* shortcut = shortcut_manager_->FindShortcut(action);
925 if (!shortcut) {
926 return fallback;
927 }
928 if (shortcut->keys.empty()) {
929 return "Unassigned";
930 }
931
932 return PrintShortcut(shortcut->keys);
933}
934
935void RightPanelManager::DrawShortcutRow(const std::string& action,
936 const char* description,
937 const std::string& fallback) {
938 std::string label = GetShortcutLabel(action, fallback);
939 DrawPanelValue(label.c_str(), description);
940}
941
942// =============================================================================
943// Panel Content Drawing
944// =============================================================================
945
947#ifdef YAZE_BUILD_AGENT_UI
948 if (!agent_chat_) {
949 gui::ColoredText(ICON_MD_SMART_TOY " AI Agent Not Available",
951 ImGui::Spacing();
953 "The AI Agent is not initialized. "
954 "Open the AI Agent from View menu or use Ctrl+Shift+A.");
955 return;
956 }
957
958 agent_chat_->set_active(true);
959
960 const float action_bar_height = ImGui::GetFrameHeightWithSpacing() + 8.0f;
961 const float content_height =
963 ImGui::GetContentRegionAvail().y - action_bar_height);
964
965 if (ImGui::BeginChild("AgentChatBody", ImVec2(0, content_height), true)) {
966 agent_chat_->Draw(0.0f);
967 }
968 ImGui::EndChild();
969
970 gui::StyleVarGuard action_spacing(ImGuiStyleVar_ItemSpacing, ImVec2(6, 6));
971 const ImVec2 action_size = gui::IconSize::Toolbar();
972 const ImVec4 transparent_bg(0, 0, 0, 0);
973
974 if (proposal_drawer_) {
976 "Open Proposals", false, transparent_bg,
977 "agent_sidebar", "open_proposals")) {
979 }
980 ImGui::SameLine();
981 }
982
984 "Clear Chat History", false, transparent_bg,
985 "agent_sidebar", "clear_history")) {
987 }
988 ImGui::SameLine();
989
991 "Save Chat History", false, transparent_bg,
992 "agent_sidebar", "save_history")) {
993 agent_chat_->SaveHistory(ResolveAgentChatHistoryPath());
994 }
995#else
996 gui::ColoredText(ICON_MD_SMART_TOY " AI Agent Not Available",
998
999 ImGui::Spacing();
1001 "The AI Agent requires agent UI support. "
1002 "Build with YAZE_BUILD_AGENT_UI=ON to enable.");
1003#endif
1004}
1005
1007#ifdef YAZE_BUILD_AGENT_UI
1008 if (!agent_chat_) {
1009 return false;
1010 }
1011 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
1012 const ImVec4 accent = gui::GetPrimaryVec4();
1013
1014 std::string selection_context;
1016 selection_context =
1017 BuildSelectionContextSummary(properties_panel_->GetSelection());
1018 }
1019
1020 struct QuickAction {
1021 const char* label;
1022 std::string prompt;
1023 };
1024
1025 std::vector<QuickAction> actions;
1026 if (!selection_context.empty()) {
1027 actions.push_back({"Explain selection",
1028 "Explain this selection and how to edit it safely.\n\n" +
1029 selection_context});
1030 actions.push_back(
1031 {"Suggest fixes",
1032 "Suggest improvements or checks for this selection.\n\n" +
1033 selection_context});
1034 }
1035
1036 switch (active_editor_type_) {
1038 actions.push_back({"Summarize map",
1039 "Summarize the current overworld map and its key "
1040 "features. Use overworld tools if available."});
1041 actions.push_back({"List sprites/items",
1042 "List notable sprites or items on the current "
1043 "overworld map."});
1044 break;
1046 actions.push_back({"Audit room",
1047 "Summarize the current dungeon room layout, doors, "
1048 "and object density."});
1049 actions.push_back({"List sprites",
1050 "List sprites in the current dungeon room and any "
1051 "potential conflicts."});
1052 break;
1054 actions.push_back({"Review tiles",
1055 "Review the current tileset usage and point out any "
1056 "obvious issues."});
1057 actions.push_back({"Palette check",
1058 "Check palette usage for contrast/readability "
1059 "problems."});
1060 break;
1062 actions.push_back({"Palette audit",
1063 "Audit the active palette for hue/contrast balance "
1064 "and note risks."});
1065 actions.push_back({"Theme ideas",
1066 "Suggest a palette variation that fits the current "
1067 "scene style."});
1068 break;
1070 actions.push_back({"Sprite review",
1071 "Review the selected sprite properties and suggest "
1072 "tuning."});
1073 break;
1075 actions.push_back({"Copy edit",
1076 "Review the current message text for clarity and "
1077 "style improvements."});
1078 break;
1080 actions.push_back({"ASM review",
1081 "Review the current ASM changes for risks and style "
1082 "issues."});
1083 break;
1084 case EditorType::kHex:
1085 actions.push_back({"Hex context",
1086 "Explain what the current hex selection likely "
1087 "represents."});
1088 break;
1090 actions.push_back({"Test suggestion",
1091 "Propose a short emulator test to validate the "
1092 "current feature."});
1093 break;
1094 case EditorType::kAgent:
1095 actions.push_back({"Agent config review",
1096 "Review current agent configuration for practical "
1097 "improvements."});
1098 break;
1099 default:
1100 actions.push_back({"Agent overview",
1101 "Suggest the next best agent-assisted action for the "
1102 "current editor context."});
1103 break;
1104 }
1105
1106 if (actions.empty()) {
1107 return false;
1108 }
1109
1110 ImGui::TextColored(accent, "%s Editor Actions", ICON_MD_BOLT);
1111 gui::ColoredText("Send a context-aware prompt to the agent.",
1113
1114 int columns = ImGui::GetContentRegionAvail().x > 420.0f ? 2 : 1;
1115 if (ImGui::BeginTable("AgentQuickActionsTable", columns,
1116 ImGuiTableFlags_SizingStretchSame)) {
1117 for (const auto& action : actions) {
1118 ImGui::TableNextColumn();
1119 if (ImGui::Button(action.label, ImVec2(-1, 0))) {
1120 agent_chat_->SendMessage(action.prompt);
1121 }
1122 }
1123 ImGui::EndTable();
1124 }
1125 return true;
1126#else
1127 return false;
1128#endif
1129}
1130
1132 if (proposal_drawer_) {
1133 // Set ROM and draw content inside the panel (not a separate window)
1134 if (rom_) {
1136 }
1138 } else {
1139 gui::ColoredText(ICON_MD_DESCRIPTION " Proposals Not Available",
1141
1142 ImGui::Spacing();
1144 "The proposal system is not initialized. "
1145 "Proposals will appear here when the AI Agent creates them.");
1146 }
1147}
1148
1150 if (settings_panel_) {
1151 // Draw settings inline (no card windows)
1153 } else {
1154 gui::ColoredText(ICON_MD_SETTINGS " Settings Not Available",
1156
1157 ImGui::Spacing();
1159 "Settings will be available once initialized. "
1160 "This panel provides quick access to application settings.");
1161 }
1162}
1163
1165 // Context-aware editor header
1167
1168 // Keyboard Shortcuts section (default open)
1169 if (BeginPanelSection("Keyboard Shortcuts", ICON_MD_KEYBOARD, true)) {
1173 }
1174
1175 // Editor-specific help (default open)
1176 if (BeginPanelSection("Editor Guide", ICON_MD_HELP, true)) {
1179 }
1180
1181 // Quick Actions (collapsed by default)
1182 if (BeginPanelSection("Quick Actions", ICON_MD_BOLT, false)) {
1185 }
1186
1187 // About section (collapsed by default)
1188 if (BeginPanelSection("About", ICON_MD_INFO, false)) {
1191 }
1192}
1193
1195 const char* editor_name = "No Editor Selected";
1196 const char* editor_icon = ICON_MD_HELP;
1197
1198 switch (active_editor_type_) {
1200 editor_name = "Overworld Editor";
1201 editor_icon = ICON_MD_LANDSCAPE;
1202 break;
1204 editor_name = "Dungeon Editor";
1205 editor_icon = ICON_MD_CASTLE;
1206 break;
1208 editor_name = "Graphics Editor";
1209 editor_icon = ICON_MD_IMAGE;
1210 break;
1212 editor_name = "Palette Editor";
1213 editor_icon = ICON_MD_PALETTE;
1214 break;
1215 case EditorType::kMusic:
1216 editor_name = "Music Editor";
1217 editor_icon = ICON_MD_MUSIC_NOTE;
1218 break;
1220 editor_name = "Screen Editor";
1221 editor_icon = ICON_MD_TV;
1222 break;
1224 editor_name = "Sprite Editor";
1225 editor_icon = ICON_MD_SMART_TOY;
1226 break;
1228 editor_name = "Message Editor";
1229 editor_icon = ICON_MD_CHAT;
1230 break;
1232 editor_name = "Emulator";
1233 editor_icon = ICON_MD_VIDEOGAME_ASSET;
1234 break;
1235 default:
1236 break;
1237 }
1238
1239 // Draw context header with editor info
1240 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s %s Help", editor_icon,
1241 editor_name);
1242
1244}
1245
1247 const char* ctrl = gui::GetCtrlDisplayName();
1248 DrawPanelLabel("Global");
1249 ImGui::Indent(8.0f);
1250 DrawShortcutRow("Open", "Open ROM", absl::StrFormat("%s+O", ctrl));
1251 DrawShortcutRow("Save", "Save ROM", absl::StrFormat("%s+S", ctrl));
1252 DrawShortcutRow("Save As", "Save ROM As",
1253 absl::StrFormat("%s+Shift+S", ctrl));
1254 DrawShortcutRow("Undo", "Undo", absl::StrFormat("%s+Z", ctrl));
1255 DrawShortcutRow("Redo", "Redo", absl::StrFormat("%s+Shift+Z", ctrl));
1256 DrawShortcutRow("Command Palette", "Command Palette",
1257 absl::StrFormat("%s+Shift+P", ctrl));
1258 DrawShortcutRow("Global Search", "Global Search",
1259 absl::StrFormat("%s+Shift+K", ctrl));
1260 DrawShortcutRow("view.toggle_activity_bar", "Toggle Sidebar",
1261 absl::StrFormat("%s+B", ctrl));
1262 DrawShortcutRow("Show About", "About / Help", "F1");
1263 DrawPanelValue("Esc", "Close Panel");
1264 ImGui::Unindent(8.0f);
1265 ImGui::Spacing();
1266}
1267
1269 const char* ctrl = gui::GetCtrlDisplayName();
1270 switch (active_editor_type_) {
1272 DrawPanelLabel("Overworld");
1273 ImGui::Indent(8.0f);
1274 DrawPanelValue("1-3", "Switch World (LW/DW/SP)");
1275 DrawPanelValue("Arrow Keys", "Navigate Maps");
1276 DrawPanelValue("E", "Entity Mode");
1277 DrawPanelValue("T", "Tile Mode");
1278 DrawShortcutRow("overworld.brush_toggle", "Toggle brush", "B");
1279 DrawShortcutRow("overworld.fill", "Fill tool", "F");
1280 DrawShortcutRow("overworld.next_tile", "Next tile", "]");
1281 DrawShortcutRow("overworld.prev_tile", "Previous tile", "[");
1282 DrawPanelValue("Right Click", "Pick Tile");
1283 ImGui::Unindent(8.0f);
1284 break;
1285
1287 DrawPanelLabel("Dungeon");
1288 ImGui::Indent(8.0f);
1289 DrawShortcutRow("dungeon.object.select_tool", "Select tool", "S");
1290 DrawShortcutRow("dungeon.object.place_tool", "Place tool", "P");
1291 DrawShortcutRow("dungeon.object.delete_tool", "Delete tool", "D");
1292 DrawShortcutRow("dungeon.object.copy", "Copy selection",
1293 absl::StrFormat("%s+C", ctrl));
1294 DrawShortcutRow("dungeon.object.paste", "Paste selection",
1295 absl::StrFormat("%s+V", ctrl));
1296 DrawShortcutRow("dungeon.object.delete", "Delete selection", "Delete");
1297 DrawPanelValue("Arrow Keys", "Move Object");
1298 DrawPanelValue("G", "Toggle Grid");
1299 DrawPanelValue("L", "Cycle Layers");
1300 ImGui::Unindent(8.0f);
1301 break;
1302
1304 DrawPanelLabel("Graphics");
1305 ImGui::Indent(8.0f);
1306 DrawShortcutRow("graphics.prev_sheet", "Previous sheet", "PageUp");
1307 DrawShortcutRow("graphics.next_sheet", "Next sheet", "PageDown");
1308 DrawShortcutRow("graphics.tool.pencil", "Pencil tool", "B");
1309 DrawShortcutRow("graphics.tool.fill", "Fill tool", "G");
1310 DrawShortcutRow("graphics.zoom_in", "Zoom in", "+");
1311 DrawShortcutRow("graphics.zoom_out", "Zoom out", "-");
1312 DrawShortcutRow("graphics.toggle_grid", "Toggle grid",
1313 absl::StrFormat("%s+G", ctrl));
1314 ImGui::Unindent(8.0f);
1315 break;
1316
1318 DrawPanelLabel("Palette");
1319 ImGui::Indent(8.0f);
1320 DrawPanelValue("Click", "Select Color");
1321 DrawPanelValue("Double Click", "Edit Color");
1322 DrawPanelValue("Drag", "Copy Color");
1323 ImGui::Unindent(8.0f);
1324 break;
1325
1326 case EditorType::kMusic:
1327 DrawPanelLabel("Music");
1328 ImGui::Indent(8.0f);
1329 DrawShortcutRow("music.play_pause", "Play/Pause", "Space");
1330 DrawShortcutRow("music.stop", "Stop", "Esc");
1331 DrawShortcutRow("music.speed_up", "Speed up", "+");
1332 DrawShortcutRow("music.speed_down", "Slow down", "-");
1333 DrawPanelValue("Left/Right", "Seek");
1334 ImGui::Unindent(8.0f);
1335 break;
1336
1338 DrawPanelLabel("Message");
1339 ImGui::Indent(8.0f);
1340 DrawPanelValue(absl::StrFormat("%s+Enter", ctrl).c_str(),
1341 "Insert Line Break");
1342 DrawPanelValue("Up/Down", "Navigate Messages");
1343 ImGui::Unindent(8.0f);
1344 break;
1345
1346 default:
1347 DrawPanelLabel("Editor Shortcuts");
1348 ImGui::Indent(8.0f);
1349 {
1350 gui::StyleColorGuard text_color(ImGuiCol_Text,
1352 ImGui::TextWrapped("Select an editor to see specific shortcuts.");
1353 }
1354 ImGui::Unindent(8.0f);
1355 break;
1356 }
1357}
1358
1360 switch (active_editor_type_) {
1362 gui::StyleColorGuard text_color(ImGuiCol_Text,
1363 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1364 ImGui::Bullet();
1365 ImGui::TextWrapped("Paint tiles by selecting from Tile16 Selector");
1366 ImGui::Bullet();
1367 ImGui::TextWrapped(
1368 "Switch between Light World, Dark World, and Special Areas");
1369 ImGui::Bullet();
1370 ImGui::TextWrapped(
1371 "Use Entity Mode to place entrances, exits, items, and sprites");
1372 ImGui::Bullet();
1373 ImGui::TextWrapped("Right-click on the map to pick a tile for painting");
1374 } break;
1375
1376 case EditorType::kDungeon: {
1377 gui::StyleColorGuard text_color(ImGuiCol_Text,
1378 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1379 ImGui::Bullet();
1380 ImGui::TextWrapped("Select rooms from the Room Selector or Room Matrix");
1381 ImGui::Bullet();
1382 ImGui::TextWrapped("Place objects using the Object Editor panel");
1383 ImGui::Bullet();
1384 ImGui::TextWrapped(
1385 "Edit room headers for palette, GFX, and floor settings");
1386 ImGui::Bullet();
1387 ImGui::TextWrapped("Multiple rooms can be opened in separate tabs");
1388 } break;
1389
1390 case EditorType::kGraphics: {
1391 gui::StyleColorGuard text_color(ImGuiCol_Text,
1392 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1393 ImGui::Bullet();
1394 ImGui::TextWrapped("Browse graphics sheets using the Sheet Browser");
1395 ImGui::Bullet();
1396 ImGui::TextWrapped("Edit pixels directly with the Pixel Editor");
1397 ImGui::Bullet();
1398 ImGui::TextWrapped("Choose palettes from Palette Controls");
1399 ImGui::Bullet();
1400 ImGui::TextWrapped("View 3D objects like rupees and crystals");
1401 } break;
1402
1403 case EditorType::kPalette: {
1404 gui::StyleColorGuard text_color(ImGuiCol_Text,
1405 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1406 ImGui::Bullet();
1407 ImGui::TextWrapped("Edit overworld, dungeon, and sprite palettes");
1408 ImGui::Bullet();
1409 ImGui::TextWrapped("Use Quick Access for color harmony tools");
1410 ImGui::Bullet();
1411 ImGui::TextWrapped("Changes update in real-time across all editors");
1412 } break;
1413
1414 case EditorType::kMusic: {
1415 gui::StyleColorGuard text_color(ImGuiCol_Text,
1416 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1417 ImGui::Bullet();
1418 ImGui::TextWrapped("Browse songs in the Song Browser");
1419 ImGui::Bullet();
1420 ImGui::TextWrapped("Use the tracker for playback control");
1421 ImGui::Bullet();
1422 ImGui::TextWrapped("Edit instruments and BRR samples");
1423 } break;
1424
1425 case EditorType::kMessage: {
1426 gui::StyleColorGuard text_color(ImGuiCol_Text,
1427 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1428 ImGui::Bullet();
1429 ImGui::TextWrapped("Edit all in-game dialog messages");
1430 ImGui::Bullet();
1431 ImGui::TextWrapped("Preview text rendering with the font atlas");
1432 ImGui::Bullet();
1433 ImGui::TextWrapped("Manage the compression dictionary");
1434 } break;
1435
1436 default:
1437 ImGui::Bullet();
1438 ImGui::TextWrapped("Open a ROM file via File > Open ROM");
1439 ImGui::Bullet();
1440 ImGui::TextWrapped("Select an editor from the sidebar");
1441 ImGui::Bullet();
1442 ImGui::TextWrapped("Use panels to access tools and settings");
1443 ImGui::Bullet();
1444 ImGui::TextWrapped("Save your work via File > Save ROM");
1445 break;
1446 }
1447}
1448
1450 const float button_width = ImGui::GetContentRegionAvail().x;
1451
1452 gui::StyleVarGuard button_vars({
1453 {ImGuiStyleVar_FramePadding, ImVec2(8.0f, 6.0f)},
1454 {ImGuiStyleVar_FrameRounding, 4.0f},
1455 });
1456
1457 // Documentation button
1458 {
1459 gui::StyleColorGuard btn_colors({
1460 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1461 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1462 });
1463 if (ImGui::Button(ICON_MD_DESCRIPTION " Open Documentation",
1464 ImVec2(button_width, 0))) {
1465 gui::OpenUrl("https://github.com/scawful/yaze/wiki");
1466 }
1467 }
1468
1469 ImGui::Spacing();
1470
1471 // GitHub Issues button
1472 {
1473 gui::StyleColorGuard btn_colors({
1474 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1475 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1476 });
1477 if (ImGui::Button(ICON_MD_BUG_REPORT " Report Issue",
1478 ImVec2(button_width, 0))) {
1479 gui::OpenUrl("https://github.com/scawful/yaze/issues/new");
1480 }
1481 }
1482
1483 ImGui::Spacing();
1484
1485 // Discord button
1486 {
1487 gui::StyleColorGuard btn_colors({
1488 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1489 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1490 });
1491 if (ImGui::Button(ICON_MD_FORUM " Join Discord", ImVec2(button_width, 0))) {
1492 gui::OpenUrl("https://discord.gg/zU5qDm8MZg");
1493 }
1494 }
1495}
1496
1498 gui::ColoredText("YAZE - Yet Another Zelda3 Editor", gui::GetPrimaryVec4());
1499
1500 ImGui::Spacing();
1502 "A comprehensive editor for The Legend of Zelda: "
1503 "A Link to the Past ROM files.");
1504
1506
1507 DrawPanelLabel("Credits");
1508 ImGui::Spacing();
1509 ImGui::Text("Written by: scawful");
1510 ImGui::Text("Special Thanks: Zarby89, JaredBrian");
1511
1513
1514 DrawPanelLabel("Links");
1515 ImGui::Spacing();
1516 gui::ColoredText(ICON_MD_LINK " github.com/scawful/yaze",
1518}
1519
1521 if (!toast_manager_) {
1522 gui::ColoredText(ICON_MD_NOTIFICATIONS_OFF " Notifications Unavailable",
1524 return;
1525 }
1526
1527 // Header actions
1528 float avail = ImGui::GetContentRegionAvail().x;
1529
1530 // Mark all read / Clear all buttons
1531 {
1532 gui::StyleColorGuard btn_colors({
1533 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1534 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1535 });
1536
1537 if (ImGui::Button(ICON_MD_DONE_ALL " Mark All Read",
1538 ImVec2(avail * 0.5f - 4.0f, 0))) {
1540 }
1541 ImGui::SameLine();
1542 if (ImGui::Button(ICON_MD_DELETE_SWEEP " Clear All",
1543 ImVec2(avail * 0.5f - 4.0f, 0))) {
1545 }
1546 }
1547
1549
1550 // Notification history
1551 const auto& history = toast_manager_->GetHistory();
1552
1553 if (history.empty()) {
1554 ImGui::Spacing();
1555 gui::ColoredText(ICON_MD_INBOX " No notifications",
1557 ImGui::Spacing();
1559 "Notifications will appear here when actions complete.");
1560 return;
1561 }
1562
1563 // Stats
1564 size_t unread_count = toast_manager_->GetUnreadCount();
1565 if (unread_count > 0) {
1566 gui::ColoredTextF(gui::GetPrimaryVec4(), "%zu unread", unread_count);
1567 } else {
1568 gui::ColoredText("All caught up", gui::GetTextSecondaryVec4());
1569 }
1570
1571 ImGui::Spacing();
1572
1573 // Scrollable notification list (minimum height so list never collapses)
1574 const bool notification_list_open = gui::LayoutHelpers::BeginContentChild(
1575 "##NotificationList", ImVec2(0.0f, gui::UIConfig::kContentMinHeightList),
1576 false, ImGuiWindowFlags_AlwaysVerticalScrollbar);
1577 if (notification_list_open) {
1578 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
1579 auto now = std::chrono::system_clock::now();
1580
1581 // Group by time (Today, Yesterday, Older)
1582 bool shown_today = false;
1583 bool shown_yesterday = false;
1584 bool shown_older = false;
1585
1586 for (const auto& entry : history) {
1587 auto diff =
1588 std::chrono::duration_cast<std::chrono::hours>(now - entry.timestamp)
1589 .count();
1590
1591 // Time grouping headers
1592 if (diff < 24 && !shown_today) {
1593 DrawPanelLabel("Today");
1594 shown_today = true;
1595 } else if (diff >= 24 && diff < 48 && !shown_yesterday) {
1596 ImGui::Spacing();
1597 DrawPanelLabel("Yesterday");
1598 shown_yesterday = true;
1599 } else if (diff >= 48 && !shown_older) {
1600 ImGui::Spacing();
1601 DrawPanelLabel("Older");
1602 shown_older = true;
1603 }
1604
1605 // Notification item
1606 ImGui::PushID(&entry);
1607
1608 // Icon and color based on type
1609 const char* icon;
1610 ImVec4 color;
1611 switch (entry.type) {
1613 icon = ICON_MD_CHECK_CIRCLE;
1614 color = gui::ConvertColorToImVec4(theme.success);
1615 break;
1617 icon = ICON_MD_WARNING;
1618 color = gui::ConvertColorToImVec4(theme.warning);
1619 break;
1620 case ToastType::kError:
1621 icon = ICON_MD_ERROR;
1622 color = gui::ConvertColorToImVec4(theme.error);
1623 break;
1624 default:
1625 icon = ICON_MD_INFO;
1626 color = gui::ConvertColorToImVec4(theme.info);
1627 break;
1628 }
1629
1630 // Unread indicator
1631 if (!entry.read) {
1633 ImGui::SameLine();
1634 }
1635
1636 // Icon
1637 gui::ColoredTextF(color, "%s", icon);
1638 ImGui::SameLine();
1639
1640 // Message
1641 ImGui::TextWrapped("%s", entry.message.c_str());
1642
1643 // Timestamp
1644 auto diff_sec = std::chrono::duration_cast<std::chrono::seconds>(
1645 now - entry.timestamp)
1646 .count();
1647 std::string time_str;
1648 if (diff_sec < 60) {
1649 time_str = "just now";
1650 } else if (diff_sec < 3600) {
1651 time_str = absl::StrFormat("%dm ago", diff_sec / 60);
1652 } else if (diff_sec < 86400) {
1653 time_str = absl::StrFormat("%dh ago", diff_sec / 3600);
1654 } else {
1655 time_str = absl::StrFormat("%dd ago", diff_sec / 86400);
1656 }
1657
1658 gui::ColoredTextF(gui::GetTextDisabledVec4(), " %s", time_str.c_str());
1659
1660 ImGui::PopID();
1661 ImGui::Spacing();
1662 }
1663 }
1665}
1666
1668 if (properties_panel_) {
1670 } else {
1671 // Placeholder when no properties panel is set
1672 gui::ColoredText(ICON_MD_SELECT_ALL " No Selection",
1674
1675 ImGui::Spacing();
1677 "Select an item in the editor to view and edit its properties here.");
1678
1680
1681 // Show placeholder sections for what properties would look like
1682 if (BeginPanelSection("Position & Size", ICON_MD_STRAIGHTEN, true)) {
1683 DrawPanelValue("X", "--");
1684 DrawPanelValue("Y", "--");
1685 DrawPanelValue("Width", "--");
1686 DrawPanelValue("Height", "--");
1688 }
1689
1690 if (BeginPanelSection("Appearance", ICON_MD_PALETTE, false)) {
1691 DrawPanelValue("Tile ID", "--");
1692 DrawPanelValue("Palette", "--");
1693 DrawPanelValue("Layer", "--");
1695 }
1696
1697 if (BeginPanelSection("Behavior", ICON_MD_SETTINGS, false)) {
1698 DrawPanelValue("Type", "--");
1699 DrawPanelValue("Subtype", "--");
1700 DrawPanelValue("Properties", "--");
1702 }
1703 }
1704}
1705
1707 if (project_panel_) {
1709 } else {
1710 gui::ColoredText(ICON_MD_FOLDER_SPECIAL " No Project Loaded",
1712
1713 ImGui::Spacing();
1715 "Open a .yaze project file to access project management features "
1716 "including ROM versioning, snapshots, and configuration.");
1717
1719
1720 // Placeholder for project features
1721 if (BeginPanelSection("Quick Start", ICON_MD_ROCKET_LAUNCH, true)) {
1722 ImGui::Bullet();
1723 ImGui::TextWrapped("Create a new project via File > New Project");
1724 ImGui::Bullet();
1725 ImGui::TextWrapped("Open existing .yaze project files");
1726 ImGui::Bullet();
1727 ImGui::TextWrapped("Projects track ROM versions and settings");
1729 }
1730
1731 if (BeginPanelSection("Features", ICON_MD_CHECKLIST, false)) {
1732 ImGui::Bullet();
1733 ImGui::TextWrapped("Version snapshots with Git integration");
1734 ImGui::Bullet();
1735 ImGui::TextWrapped("ROM backup and restore");
1736 ImGui::Bullet();
1737 ImGui::TextWrapped("Project-specific settings");
1738 ImGui::Bullet();
1739 ImGui::TextWrapped("Assembly code folder integration");
1741 }
1742 }
1743}
1744
1746 bool clicked = false;
1747
1748 // Keep menu-bar controls on SmallButton metrics so baseline/spacing stays
1749 // consistent with the session + notification controls.
1750 auto DrawPanelButton = [&](const char* icon, const char* base_tooltip,
1751 const char* shortcut_action, PanelType type) {
1752 const bool is_active = IsPanelActive(type);
1753 gui::StyleColorGuard button_colors({
1754 {ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
1755 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
1756 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
1757 {ImGuiCol_Text,
1759 });
1760
1761 if (ImGui::SmallButton(icon)) {
1762 TogglePanel(type);
1763 clicked = true;
1764 }
1765
1766 if (ImGui::IsItemHovered()) {
1767 const std::string shortcut = GetShortcutLabel(shortcut_action, "");
1768 if (shortcut.empty() || shortcut == "Unassigned") {
1769 ImGui::SetTooltip("%s", base_tooltip);
1770 } else {
1771 ImGui::SetTooltip("%s (%s)", base_tooltip, shortcut.c_str());
1772 }
1773 }
1774 };
1775
1776 DrawPanelButton(ICON_MD_FOLDER_SPECIAL, "Project Panel",
1777 "View: Toggle Project Panel", PanelType::kProject);
1778 ImGui::SameLine();
1779
1780 DrawPanelButton(ICON_MD_SMART_TOY, "AI Agent Panel",
1781 "View: Toggle AI Agent Panel", PanelType::kAgentChat);
1782 ImGui::SameLine();
1783
1784 DrawPanelButton(ICON_MD_HELP_OUTLINE, "Help Panel", "View: Toggle Help Panel",
1786 ImGui::SameLine();
1787
1788 DrawPanelButton(ICON_MD_SETTINGS, "Settings Panel",
1789 "View: Toggle Settings Panel", PanelType::kSettings);
1790 ImGui::SameLine();
1791
1792 DrawPanelButton(ICON_MD_LIST_ALT, "Properties Panel",
1793 "View: Toggle Properties Panel", PanelType::kProperties);
1794
1795 return clicked;
1796}
1797
1798} // namespace editor
1799} // namespace yaze
void SendMessage(const std::string &message)
void Draw(float available_height=0.0f)
void set_active(bool active)
Definition agent_chat.h:71
absl::Status SaveHistory(const std::string &filepath)
std::string GetShortcutLabel(const std::string &action, const std::string &fallback) const
void Draw()
Draw the panel and its contents.
std::unordered_map< std::string, float > SerializePanelWidths() const
Persist/restore per-panel widths for user settings.
float GetClampedPanelWidth(PanelType type, float viewport_width) const
float GetPanelWidth() const
Get the width of the panel when expanded.
void ClosePanel()
Close the currently active panel.
void CyclePanel(int direction)
Cycle to the next/previous right panel in header order.
void DrawPanelValue(const char *label, const char *value)
void NotifyPanelWidthChanged(PanelType type, float width)
void SetPanelWidth(PanelType type, float width)
Set panel width for a specific panel type.
void OnHostVisibilityChanged(bool visible)
Snap transient animations when host visibility changes.
void TogglePanel(PanelType type)
Toggle a specific panel on/off.
bool BeginPanelSection(const char *label, const char *icon=nullptr, bool default_open=true)
static std::string PanelTypeKey(PanelType type)
std::unordered_map< std::string, PanelSizeLimits > panel_size_limits_
float GetConfiguredPanelWidth(PanelType type) const
static float GetDefaultPanelWidth(PanelType type, EditorType editor=EditorType::kUnknown)
Get the default width for a specific panel type.
void SetPanelSizeLimits(PanelType type, const PanelSizeLimits &limits)
Set sizing constraints for an individual right panel.
bool IsPanelExpanded() const
Check if any panel is currently expanded (or animating closed)
bool DrawPanelToggleButtons()
Draw toggle buttons for the status cluster.
bool IsPanelActive(PanelType type) const
Check if a specific panel is active.
void ResetPanelWidths()
Reset all panel widths to their defaults.
void OpenPanel(PanelType type)
Open a specific panel.
void DrawPanelLabel(const char *label)
SelectionPropertiesPanel * properties_panel_
void DrawPanelDescription(const char *text)
PanelSizeLimits GetPanelSizeLimits(PanelType type) const
void DrawShortcutRow(const std::string &action, const char *description, const std::string &fallback)
std::function< void(PanelType, float)> on_panel_width_changed_
void DrawPanelHeader(const char *title, const char *icon)
ProjectManagementPanel * project_panel_
void RestorePanelWidths(const std::unordered_map< std::string, float > &widths)
const SelectionContext & GetSelection() const
Get the current selection context.
bool HasSelection() const
Check if there's an active selection.
void Draw()
Draw the properties panel content.
const Shortcut * FindShortcut(const std::string &name) const
const std::deque< NotificationEntry > & GetHistory() const
bool IsEnabled() const
Definition animator.cc:247
static bool BeginContentChild(const char *id, const ImVec2 &min_size, bool border=false, ImGuiWindowFlags flags=0)
static void EndContentChild()
static SafeAreaInsets GetSafeAreaInsets()
RAII guard for ImGui style colors.
Definition style_guard.h:27
RAII guard for ImGui style vars.
Definition style_guard.h:68
RAII compound guard for window-level style setup.
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
static absl::StatusOr< std::filesystem::path > GetTempDirectory()
Get a temporary directory for the application.
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
#define ICON_MD_ROCKET_LAUNCH
Definition icons.h:1612
#define ICON_MD_NOTIFICATIONS
Definition icons.h:1335
#define ICON_MD_SETTINGS
Definition icons.h:1699
#define ICON_MD_LINK
Definition icons.h:1090
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CHAT
Definition icons.h:394
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_LANDSCAPE
Definition icons.h:1059
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_LOCK_OPEN
Definition icons.h:1142
#define ICON_MD_DONE_ALL
Definition icons.h:608
#define ICON_MD_FOLDER_SPECIAL
Definition icons.h:815
#define ICON_MD_LOCK
Definition icons.h:1140
#define ICON_MD_CHECKLIST
Definition icons.h:402
#define ICON_MD_FORUM
Definition icons.h:851
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:744
#define ICON_MD_SWAP_HORIZ
Definition icons.h:1896
#define ICON_MD_LIST_ALT
Definition icons.h:1095
#define ICON_MD_VIDEOGAME_ASSET
Definition icons.h:2076
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1264
#define ICON_MD_INBOX
Definition icons.h:990
#define ICON_MD_KEYBOARD
Definition icons.h:1028
#define ICON_MD_BOLT
Definition icons.h:282
#define ICON_MD_CHEVRON_LEFT
Definition icons.h:405
#define ICON_MD_IMAGE
Definition icons.h:982
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_HELP_OUTLINE
Definition icons.h:935
#define ICON_MD_STRAIGHTEN
Definition icons.h:1871
#define ICON_MD_NOTIFICATIONS_OFF
Definition icons.h:1338
#define ICON_MD_SELECT_ALL
Definition icons.h:1680
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_TV
Definition icons.h:2032
#define ICON_MD_DELETE_FOREVER
Definition icons.h:531
#define ICON_MD_HELP
Definition icons.h:933
#define ICON_MD_CHEVRON_RIGHT
Definition icons.h:406
#define ICON_MD_FIBER_MANUAL_RECORD
Definition icons.h:739
#define ICON_MD_SMART_TOY
Definition icons.h:1781
#define ICON_MD_DELETE_SWEEP
Definition icons.h:533
#define LOG_INFO(category, format,...)
Definition log.h:105
std::string BuildSelectionContextSummary(const SelectionContext &selection)
const char * GetPanelShortcutAction(RightPanelManager::PanelType type)
RightPanelManager::PanelType StepRightPanel(RightPanelManager::PanelType current, int direction)
const std::array< RightPanelManager::PanelType, 7 > kRightPanelSwitchOrder
int FindRightPanelIndex(RightPanelManager::PanelType type)
const char * GetSelectionTypeName(SelectionType type)
Get a human-readable name for a selection type.
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
const char * GetPanelTypeName(RightPanelManager::PanelType type)
Get the name of a panel type.
const char * GetPanelTypeIcon(RightPanelManager::PanelType type)
Get the icon for a panel type.
bool TransparentIconButton(const char *icon, const ImVec2 &size, const char *tooltip, bool is_active, const ImVec4 &active_color, const char *panel_id, const char *anim_id)
Draw a transparent icon button (hover effect only).
const char * GetCtrlDisplayName()
Get the display name for the primary modifier key.
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
void ColoredText(const char *text, const ImVec4 &color)
ImVec4 GetSurfaceContainerHighestVec4()
ImVec4 GetPrimaryVec4()
bool OpenUrl(const std::string &url)
Definition input.cc:667
ImVec4 GetTextDisabledVec4()
ImVec4 GetTextSecondaryVec4()
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
Animator & GetAnimator()
Definition animator.cc:301
ImVec4 GetSurfaceContainerHighVec4()
ImVec4 GetOutlineVec4()
ImVec4 GetSurfaceContainerVec4()
Holds information about the current selection.
std::vector< ImGuiKey > keys
static constexpr float kPanelWidthSettings
Definition ui_config.h:31
static constexpr float kPanelWidthHelp
Definition ui_config.h:32
static constexpr float kPanelMinWidthProject
Definition ui_config.h:50
static constexpr float kHeaderButtonSpacing
Definition ui_config.h:77
static constexpr float kPanelMinWidthHelp
Definition ui_config.h:47
static constexpr float kPanelWidthNotifications
Definition ui_config.h:33
static constexpr float kPanelWidthMedium
Definition ui_config.h:25
static constexpr float kAnimationSnapThreshold
Definition ui_config.h:82
static constexpr float kPanelMinWidthNotifications
Definition ui_config.h:48
static constexpr float kPanelMinWidthAgentChat
Definition ui_config.h:44
static constexpr float kContentMinHeightChat
Definition ui_config.h:53
static constexpr float kPanelPaddingLarge
Definition ui_config.h:73
static constexpr float kHeaderButtonGap
Definition ui_config.h:78
static constexpr float kAnimationSpeed
Definition ui_config.h:81
static constexpr float kPanelMinWidthSettings
Definition ui_config.h:46
static constexpr float kPanelPaddingMedium
Definition ui_config.h:72
static constexpr float kPanelWidthProject
Definition ui_config.h:35
static constexpr float kSplitterWidth
Definition ui_config.h:76
static constexpr float kPanelMinWidthProposals
Definition ui_config.h:45
static constexpr float kPanelHeaderHeight
Definition ui_config.h:38
static constexpr float kPanelMinWidthAbsolute
Definition ui_config.h:43
static constexpr float kContentMinHeightList
Definition ui_config.h:54
static constexpr float kPanelMinWidthProperties
Definition ui_config.h:49
static constexpr float kPanelWidthProposals
Definition ui_config.h:30
static constexpr float kPanelWidthProperties
Definition ui_config.h:34
static constexpr float kPanelWidthAgentChat
Definition ui_config.h:29