yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
right_drawer_manager.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <array>
6#include <cctype>
7#include <chrono>
8#include <cmath>
9#include <ctime>
10#include <filesystem>
11#include <optional>
12
13#include "absl/strings/str_format.h"
24#include "app/gui/core/icons.h"
25#include "app/gui/core/input.h"
28#include "app/gui/core/style.h"
34#include "imgui/imgui.h"
35#include "util/json.h"
36#include "util/log.h"
37#include "util/platform_paths.h"
38
39namespace yaze {
40namespace editor {
41
42namespace {
43
44std::string ResolveAgentChatHistoryPath() {
45 auto agent_dir = util::PlatformPaths::GetAppDataSubdirectory("agent");
46 if (agent_dir.ok()) {
47 return (*agent_dir / "agent_chat_history.json").string();
48 }
50 if (temp_dir.ok()) {
51 return (*temp_dir / "agent_chat_history.json").string();
52 }
53 return (std::filesystem::current_path() / "agent_chat_history.json").string();
54}
55
56std::string JsonValueToDisplayString(const Json& value) {
57 if (value.is_string()) {
58 return value.get<std::string>();
59 }
60 if (value.is_boolean()) {
61 return value.get<bool>() ? "true" : "false";
62 }
63 if (value.is_number_integer()) {
64 return std::to_string(value.get<long long>());
65 }
66 if (value.is_number_unsigned()) {
67 return std::to_string(value.get<unsigned long long>());
68 }
69 if (value.is_number_float()) {
70 return absl::StrFormat("%.3f", value.get<double>());
71 }
72 if (value.is_null()) {
73 return "null";
74 }
75 return value.dump();
76}
77
78std::string SanitizeToolOutputIdFragment(const std::string& value) {
79 std::string sanitized;
80 sanitized.reserve(value.size());
81 for (unsigned char ch : value) {
82 if (std::isalnum(ch)) {
83 sanitized.push_back(static_cast<char>(ch));
84 } else {
85 sanitized.push_back('_');
86 }
87 }
88 return sanitized.empty() ? std::string("entry") : sanitized;
89}
90
91bool TryParseToolOutputJson(const std::string& text, Json* out) {
92 if (!out || text.empty()) {
93 return false;
94 }
95 try {
96 *out = Json::parse(text);
97 return out->is_object();
98 } catch (...) {
99 return false;
100 }
101}
102
103std::optional<uint32_t> ParseToolOutputAddress(const Json& value) {
104 if (value.is_number_unsigned()) {
105 return static_cast<uint32_t>(value.get<uint64_t>());
106 }
107 if (value.is_number_integer()) {
108 return static_cast<uint32_t>(value.get<int64_t>());
109 }
110 if (!value.is_string()) {
111 return std::nullopt;
112 }
113
114 std::string token = value.get<std::string>();
115 if (token.empty()) {
116 return std::nullopt;
117 }
118 if (token[0] == '$') {
119 token = token.substr(1);
120 } else if (token.size() > 2 && token[0] == '0' &&
121 (token[1] == 'x' || token[1] == 'X')) {
122 token = token.substr(2);
123 }
124 try {
125 return static_cast<uint32_t>(std::stoul(token, nullptr, 16));
126 } catch (...) {
127 return std::nullopt;
128 }
129}
130
131std::optional<uint32_t> ExtractToolOutputAddress(const Json& object) {
132 if (!object.is_object()) {
133 return std::nullopt;
134 }
135 for (const char* key : {"address", "entry_address"}) {
136 if (object.contains(key)) {
137 auto parsed = ParseToolOutputAddress(object[key]);
138 if (parsed.has_value()) {
139 return parsed;
140 }
141 }
142 }
143 return std::nullopt;
144}
145
146std::string ExtractToolOutputReference(const Json& object) {
147 if (!object.is_object()) {
148 return {};
149 }
150 if (object.contains("source") && object["source"].is_string()) {
151 return object["source"].get<std::string>();
152 }
153 if (object.contains("file") && object["file"].is_string()) {
154 std::string reference = object["file"].get<std::string>();
155 if (object.contains("line") && object["line"].is_number_integer()) {
156 reference = absl::StrCat(reference, ":", object["line"].get<int>());
157 }
158 return reference;
159 }
160 return {};
161}
162
163std::string BuildToolOutputEntryTitle(const Json& object) {
164 if (!object.is_object()) {
165 return {};
166 }
167
168 const std::string address = object.contains("address")
169 ? JsonValueToDisplayString(object["address"])
170 : "";
171 const std::string bank =
172 object.contains("bank") ? JsonValueToDisplayString(object["bank"]) : "";
173 const std::string name =
174 object.contains("name") ? JsonValueToDisplayString(object["name"]) : "";
175 const std::string source = ExtractToolOutputReference(object);
176
177 if (!source.empty() && !address.empty()) {
178 return absl::StrFormat("%s (%s)", source.c_str(), address.c_str());
179 }
180 if (!name.empty() && !address.empty()) {
181 return absl::StrFormat("%s %s", name.c_str(), address.c_str());
182 }
183 if (!name.empty() && !bank.empty()) {
184 return absl::StrFormat("%s %s", name.c_str(), bank.c_str());
185 }
186 if (!source.empty()) {
187 return source;
188 }
189 if (!address.empty()) {
190 return address;
191 }
192 if (!bank.empty()) {
193 return bank;
194 }
195 if (!name.empty()) {
196 return name;
197 }
198 return {};
199}
200
201std::string BuildToolOutputActionLabel(const char* visible_label,
202 const char* action_key,
203 const Json& object) {
204 const auto address = ExtractToolOutputAddress(object);
205 if (address.has_value()) {
206 return absl::StrFormat("%s##tool_output_%s_%06X", visible_label, action_key,
207 *address);
208 }
209
210 const std::string reference = ExtractToolOutputReference(object);
211 if (!reference.empty()) {
212 return absl::StrFormat("%s##tool_output_%s_%s", visible_label, action_key,
213 SanitizeToolOutputIdFragment(reference).c_str());
214 }
215
216 return absl::StrFormat("%s##tool_output_%s_entry", visible_label, action_key);
217}
218
220 const Json& object, const RightDrawerManager::ToolOutputActions& actions) {
221 if (!object.is_object()) {
222 return;
223 }
224
225 const std::string reference = ExtractToolOutputReference(object);
226 const auto address = ExtractToolOutputAddress(object);
227 bool drew_any = false;
228
229 if (!reference.empty() && actions.on_open_reference) {
230 const std::string label =
231 BuildToolOutputActionLabel("Open", "open", object);
232 if (ImGui::SmallButton(label.c_str())) {
233 actions.on_open_reference(reference);
234 }
235 drew_any = true;
236 }
237 if (address.has_value() && actions.on_open_address) {
238 if (drew_any) {
239 ImGui::SameLine();
240 }
241 const std::string label =
242 BuildToolOutputActionLabel("Addr", "addr", object);
243 if (ImGui::SmallButton(label.c_str())) {
244 actions.on_open_address(*address);
245 }
246 drew_any = true;
247 }
248 if (address.has_value() && actions.on_open_lookup) {
249 if (drew_any) {
250 ImGui::SameLine();
251 }
252 const std::string label =
253 BuildToolOutputActionLabel("Lookup", "lookup", object);
254 if (ImGui::SmallButton(label.c_str())) {
255 actions.on_open_lookup(*address);
256 }
257 }
258}
259
260void DrawJsonObjectFields(const Json& object) {
261 for (auto it = object.begin(); it != object.end(); ++it) {
262 if (it.value().is_array() || it.value().is_object()) {
263 continue;
264 }
265 ImGui::BulletText("%s: %s", it.key().c_str(),
266 JsonValueToDisplayString(it.value()).c_str());
267 }
268}
269
271 const char* label, const Json& array,
273 if (!array.is_array() || array.empty()) {
274 return;
275 }
276 if (!ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen)) {
277 return;
278 }
279 ImGui::PushID(label);
280 for (size_t i = 0; i < array.size(); ++i) {
281 const auto& entry = array[i];
282 ImGui::PushID(static_cast<int>(i));
283 if (entry.is_object()) {
284 const std::string title = BuildToolOutputEntryTitle(entry);
285 if (!title.empty()) {
286 gui::ColoredText(title.c_str(), gui::GetOnSurfaceVec4());
287 }
288 DrawToolOutputEntryActions(entry, actions);
289 if (!title.empty() || !entry.empty()) {
290 ImGui::Spacing();
291 }
293 } else {
294 ImGui::BulletText("%s", JsonValueToDisplayString(entry).c_str());
295 }
296 if (i + 1 < array.size()) {
297 ImGui::Separator();
298 }
299 ImGui::PopID();
300 }
301 ImGui::PopID();
302}
303
304std::string BuildSelectionContextSummary(const SelectionContext& selection) {
305 if (selection.type == SelectionType::kNone) {
306 return "";
307 }
308 std::string context =
309 absl::StrFormat("Selection: %s", GetSelectionTypeName(selection.type));
310 if (!selection.display_name.empty()) {
311 context += absl::StrFormat("\nName: %s", selection.display_name);
312 }
313 if (selection.id >= 0) {
314 context += absl::StrFormat("\nID: 0x%X", selection.id);
315 }
316 if (selection.secondary_id >= 0) {
317 context += absl::StrFormat("\nSecondary: 0x%X", selection.secondary_id);
318 }
319 if (selection.read_only) {
320 context += "\nRead Only: true";
321 }
322 return context;
323}
324
326 const char* title, const char* fallback_icon,
327 const ProjectWorkflowStatus& status,
328 const std::function<void()>& cancel_callback = {}) {
329 if (!status.visible) {
330 return;
331 }
332
334 workflow::WorkflowIcon(status, fallback_icon), title);
335 ImGui::TextWrapped("%s", status.summary.empty() ? status.label.c_str()
336 : status.summary.c_str());
337 if (!status.detail.empty()) {
338 ImGui::TextWrapped("%s", status.detail.c_str());
339 }
340 if (!status.output_tail.empty()) {
341 ImGui::TextWrapped("%s", status.output_tail.c_str());
342 }
343 if (status.can_cancel && cancel_callback) {
344 if (ImGui::SmallButton(ICON_MD_CANCEL " Cancel Build")) {
345 cancel_callback();
346 }
347 }
348}
349
351 const ProjectWorkflowHistoryEntry& entry,
352 const workflow::WorkflowActionCallbacks& callbacks) {
355 entry.status, entry.kind == "Run" ? ICON_MD_PLAY_ARROW
356 : ICON_MD_BUILD),
357 entry.kind.c_str());
358 ImGui::SameLine();
359 ImGui::TextDisabled("%s",
361 ImGui::TextWrapped("%s", entry.status.summary.empty()
362 ? entry.status.label.c_str()
363 : entry.status.summary.c_str());
364 if (!entry.status.output_tail.empty()) {
365 ImGui::TextWrapped("%s", entry.status.output_tail.c_str());
366 }
368 entry, callbacks, {.show_open_output = true, .show_copy_log = true});
369}
370
380
382 for (size_t i = 0; i < kRightPanelSwitchOrder.size(); ++i) {
383 if (kRightPanelSwitchOrder[i] == type) {
384 return static_cast<int>(i);
385 }
386 }
387 return -1;
388}
389
391 RightDrawerManager::PanelType current, int direction) {
392 if (kRightPanelSwitchOrder.empty()) {
394 }
395 int index = FindRightPanelIndex(current);
396 if (index < 0) {
397 index = 0;
398 }
399 const int size = static_cast<int>(kRightPanelSwitchOrder.size());
400 const int next = (index + direction + size) % size;
401 return kRightPanelSwitchOrder[static_cast<size_t>(next)];
402}
403
405 switch (type) {
407 return "View: Toggle Project Panel";
409 return "View: Toggle AI Agent Panel";
411 return "View: Toggle Proposals Panel";
413 return "View: Toggle Settings Panel";
415 return "View: Toggle Help Panel";
417 return "View: Toggle Notifications Panel";
419 return "View: Toggle Properties Panel";
421 default:
422 return "";
423 }
424}
425
426} // namespace
427
429 switch (type) {
431 return "None";
433 return "AI Agent";
435 return "Proposals";
437 return "Settings";
439 return "Help";
441 return "Notifications";
443 return "Properties";
445 return "Project";
447 return "Tool Output";
448 default:
449 return "Unknown";
450 }
451}
452
477
479 switch (type) {
481 return "agent_chat";
483 return "proposals";
485 return "settings";
486 case PanelType::kHelp:
487 return "help";
489 return "notifications";
491 return "properties";
493 return "project";
495 return "tool_output";
496 case PanelType::kNone:
497 default:
498 return "none";
499 }
500}
501
503 if (active_panel_ == type) {
504 CloseDrawer();
505 } else {
506 // Opens the requested panel (also handles re-opening during close animation)
507 OpenDrawer(type);
508 }
509}
510
511void RightDrawerManager::SetToolOutput(std::string title, std::string query,
512 std::string content,
513 ToolOutputActions actions) {
514 tool_output_title_ = std::move(title);
515 tool_output_query_ = std::move(query);
516 tool_output_content_ = std::move(content);
517 tool_output_actions_ = std::move(actions);
518}
519
523
525 // If we were closing, cancel the close animation
526 closing_ = false;
528
529 active_panel_ = type;
530 animating_ = true;
531 animation_target_ = 1.0f;
532
533 // Check if animations are enabled
534 if (!gui::GetAnimator().IsEnabled()) {
535 panel_animation_ = 1.0f;
536 animating_ = false;
537 }
538 // Otherwise keep current panel_animation_ for smooth transition
539}
540
542 if (!gui::GetAnimator().IsEnabled()) {
543 // Instant close
545 closing_ = false;
547 panel_animation_ = 0.0f;
548 animating_ = false;
549 return;
550 }
551
552 // Start close animation — keep the panel type so we can still draw it
553 closing_ = true;
556 animating_ = true;
557 animation_target_ = 0.0f;
558}
559
561 if (direction == 0) {
562 return;
563 }
564
565 const PanelType current_panel =
567 if (current_panel == PanelType::kNone) {
568 return;
569 }
570
571 const int step = direction > 0 ? 1 : -1;
572 OpenDrawer(StepRightPanel(current_panel, step));
573}
574
576 // Snap transition state to a stable endpoint. This avoids stale intermediate
577 // frames being composited when the OS moves the app across spaces.
578 (void)visible;
579 closing_ = false;
581 animating_ = false;
582
585}
586
588 // Determine which panel to measure: active panel, or the one being closed
589 PanelType effective_panel = active_panel_;
590 if (effective_panel == PanelType::kNone && closing_) {
591 effective_panel = closing_panel_;
592 }
593 if (effective_panel == PanelType::kNone) {
594 return 0.0f;
595 }
596
597 ImGuiContext* context = ImGui::GetCurrentContext();
598 if (!context) {
599 return GetConfiguredPanelWidth(effective_panel) * panel_animation_;
600 }
601
602 const ImGuiViewport* viewport = ImGui::GetMainViewport();
603 if (!viewport) {
604 return GetConfiguredPanelWidth(effective_panel) * panel_animation_;
605 }
606
607 const float vp_width = viewport->WorkSize.x;
608 const float width = GetClampedPanelWidth(effective_panel, vp_width);
609
610 // Scale by animation progress for smooth docking space adjustment
611 return width * panel_animation_;
612}
613
615 if (type == PanelType::kNone) {
616 return;
617 }
618 float viewport_width = 0.0f;
619 if (const ImGuiViewport* viewport = ImGui::GetMainViewport()) {
620 viewport_width = viewport->WorkSize.x;
621 }
622 if (viewport_width <= 0.0f && ImGui::GetCurrentContext()) {
623 viewport_width = ImGui::GetIO().DisplaySize.x;
624 }
625 const auto limits = GetPanelSizeLimits(type);
626 float clamped = std::max(limits.min_width, width);
627 if (viewport_width > 0.0f) {
628 const float ratio = viewport_width < 768.0f
629 ? std::max(0.88f, limits.max_width_ratio)
630 : limits.max_width_ratio;
631 const float max_width = std::max(limits.min_width, viewport_width * ratio);
632 clamped = std::clamp(clamped, limits.min_width, max_width);
633 }
634
635 float* target = nullptr;
636 switch (type) {
638 target = &agent_chat_width_;
639 break;
641 target = &proposals_width_;
642 break;
644 target = &settings_width_;
645 break;
646 case PanelType::kHelp:
647 target = &help_width_;
648 break;
650 target = &notifications_width_;
651 break;
653 target = &properties_width_;
654 break;
656 target = &project_width_;
657 break;
659 target = &tool_output_width_;
660 break;
661 default:
662 break;
663 }
664 if (!target) {
665 return;
666 }
667 if (std::abs(*target - clamped) < 0.5f) {
668 return;
669 }
670 *target = clamped;
671#if !defined(NDEBUG)
672 LOG_INFO("RightDrawerManager",
673 "SetDrawerWidth type=%d requested=%.1f clamped=%.1f",
674 static_cast<int>(type), width, clamped);
675#endif
676 NotifyPanelWidthChanged(type, *target);
677}
678
704
706 EditorType editor) {
707 switch (type) {
709 return std::max(gui::UIConfig::kPanelWidthAgentChat, 480.0f);
711 return std::max(gui::UIConfig::kPanelWidthProposals, 440.0f);
713 return std::max(gui::UIConfig::kPanelWidthSettings, 380.0f);
714 case PanelType::kHelp:
715 return std::max(gui::UIConfig::kPanelWidthHelp, 380.0f);
717 return std::max(gui::UIConfig::kPanelWidthNotifications, 380.0f);
719 // Property panel can be wider in certain editors.
720 if (editor == EditorType::kDungeon) {
721 return 440.0f;
722 }
723 return std::max(gui::UIConfig::kPanelWidthProperties, 400.0f);
725 return std::max(gui::UIConfig::kPanelWidthProject, 420.0f);
727 return 460.0f;
728 default:
729 return std::max(gui::UIConfig::kPanelWidthMedium, 380.0f);
730 }
731}
732
734 const PanelSizeLimits& limits) {
735 if (type == PanelType::kNone) {
736 return;
737 }
738 PanelSizeLimits normalized = limits;
739 normalized.min_width =
741 normalized.max_width_ratio =
742 std::clamp(normalized.max_width_ratio, 0.25f, 0.95f);
743 panel_size_limits_[PanelTypeKey(type)] = normalized;
744}
745
747 PanelType type) const {
748 auto it = panel_size_limits_.find(PanelTypeKey(type));
749 if (it != panel_size_limits_.end()) {
750 return it->second;
751 }
752
753 PanelSizeLimits defaults;
754 switch (type) {
757 defaults.max_width_ratio = 0.90f;
758 break;
761 defaults.max_width_ratio = 0.86f;
762 break;
765 defaults.max_width_ratio = 0.80f;
766 break;
767 case PanelType::kHelp:
769 defaults.max_width_ratio = 0.80f;
770 break;
773 defaults.max_width_ratio = 0.82f;
774 break;
777 defaults.max_width_ratio = 0.90f;
778 break;
781 defaults.max_width_ratio = 0.86f;
782 break;
784 defaults.min_width = 360.0f;
785 defaults.max_width_ratio = 0.88f;
786 break;
787 case PanelType::kNone:
788 default:
789 break;
790 }
791 return defaults;
792}
793
795 switch (type) {
797 return agent_chat_width_;
799 return proposals_width_;
801 return settings_width_;
802 case PanelType::kHelp:
803 return help_width_;
807 return properties_width_;
809 return project_width_;
811 return tool_output_width_;
812 case PanelType::kNone:
813 default:
814 return 0.0f;
815 }
816}
817
819 float viewport_width) const {
820 float width = GetConfiguredPanelWidth(type);
821 if (width <= 0.0f) {
822 return width;
823 }
824 const auto limits = GetPanelSizeLimits(type);
825 const float ratio = viewport_width < 768.0f
826 ? std::max(0.88f, limits.max_width_ratio)
827 : limits.max_width_ratio;
828 const float max_width = std::max(limits.min_width, viewport_width * ratio);
829 return std::clamp(width, limits.min_width, max_width);
830}
831
834 on_panel_width_changed_(type, width);
835 }
836}
837
838std::unordered_map<std::string, float>
851
853 const std::unordered_map<std::string, float>& widths) {
854#if !defined(NDEBUG)
855 LOG_INFO("RightDrawerManager",
856 "RestoreDrawerWidths: %zu entries from settings", widths.size());
857#endif
858 auto apply = [&](PanelType type) {
859 auto it = widths.find(PanelTypeKey(type));
860 if (it != widths.end()) {
861 SetDrawerWidth(type, it->second);
862 }
863 };
867 apply(PanelType::kHelp);
870 apply(PanelType::kProject);
872}
873
875 // Nothing to draw if no panel is active and no close animation running
877 return;
878 }
879
880 // Handle Escape key to close panel
882 ImGui::IsKeyPressed(ImGuiKey_Escape)) {
883 CloseDrawer();
884 // Don't return — we need to start drawing the close animation this frame
885 if (!closing_)
886 return;
887 }
888
889 const bool animations_enabled = gui::GetAnimator().IsEnabled();
890 if (!animations_enabled && animating_) {
892 animating_ = false;
893 if (closing_ && animation_target_ == 0.0f) {
894 closing_ = false;
896 return;
897 }
898 }
899
900 // Advance animation
901 if (animating_ && animations_enabled) {
902 // Clamp dt to avoid giant interpolation jumps after focus/space changes.
903 float delta_time = std::clamp(ImGui::GetIO().DeltaTime, 0.0f, 1.0f / 20.0f);
904 float speed = gui::UIConfig::kAnimationSpeed;
905 switch (gui::GetAnimator().motion_profile()) {
907 speed *= 1.20f;
908 break;
910 speed *= 0.75f;
911 break;
913 default:
914 break;
915 }
916 float diff = animation_target_ - panel_animation_;
917 panel_animation_ += diff * std::min(1.0f, delta_time * speed);
918
919 // Snap to target when close enough
920 if (std::abs(animation_target_ - panel_animation_) <
923 animating_ = false;
924
925 // Close animation finished — fully clean up
926 if (closing_ && animation_target_ == 0.0f) {
927 closing_ = false;
929 return;
930 }
931 }
932 }
933
934 // Determine which panel type to draw content for
935 PanelType draw_panel = active_panel_;
936 if (draw_panel == PanelType::kNone && closing_) {
937 draw_panel = closing_panel_;
938 }
939
940 const ImGuiViewport* viewport = ImGui::GetMainViewport();
941 const float viewport_width = viewport->WorkSize.x;
942 const float top_inset = gui::LayoutHelpers::GetTopInset();
943 const float bottom_safe = gui::LayoutHelpers::GetSafeAreaInsets().bottom;
944 const float viewport_height =
945 std::max(0.0f, viewport->WorkSize.y - top_inset - bottom_safe);
946
947 // Keep full-width state explicit so drag-resize and animation remain stable.
948 const float full_width =
949 (draw_panel == PanelType::kNone)
950 ? 0.0f
951 : GetClampedPanelWidth(draw_panel, viewport_width);
952 const float animated_width = full_width * panel_animation_;
953
954 // Use SurfaceContainer for slightly elevated panel background
955 ImVec4 panel_bg = gui::GetSurfaceContainerVec4();
956 ImVec4 panel_border = gui::GetOutlineVec4();
957
958 ImGuiWindowFlags panel_flags =
959 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
960 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking |
961 ImGuiWindowFlags_NoNavFocus;
962
963 // Position panel: slides from right edge. At animation=1.0, fully visible.
964 // At animation=0.0, fully off-screen to the right.
965 float panel_x = viewport->WorkPos.x + viewport_width - animated_width;
966 ImGui::SetNextWindowPos(ImVec2(panel_x, viewport->WorkPos.y + top_inset));
967 ImGui::SetNextWindowSize(ImVec2(full_width, viewport_height));
968
969 gui::StyledWindow panel("##RightPanel",
970 {.bg = panel_bg,
971 .border = panel_border,
972 .padding = ImVec2(0.0f, 0.0f),
973 .border_size = 1.0f},
974 nullptr, panel_flags);
975 if (panel) {
976 const char* panel_title = GetPanelTypeName(draw_panel);
977 const char* panel_icon = GetPanelTypeIcon(draw_panel);
978 if (draw_panel == PanelType::kToolOutput && !tool_output_title_.empty()) {
979 panel_title = tool_output_title_.c_str();
980 }
981 // Draw enhanced panel header
982 DrawPanelHeader(panel_title, panel_icon);
983
984 // Content area with padding and minimum height so content never collapses
985 gui::StyleVarGuard content_padding(
986 ImGuiStyleVar_WindowPadding,
989 const bool panel_content_open = gui::LayoutHelpers::BeginContentChild(
990 "##PanelContent", ImVec2(0.0f, gui::UIConfig::kContentMinHeightList),
991 ImGuiChildFlags_AlwaysUseWindowPadding);
992 if (panel_content_open) {
993 switch (draw_panel) {
996 break;
999 break;
1002 break;
1003 case PanelType::kHelp:
1004 DrawHelpPanel();
1005 break;
1008 break;
1011 break;
1014 break;
1017 break;
1018 default:
1019 break;
1020 }
1021 }
1023
1024 // VSCode-style splitter: drag from the left edge to resize.
1026 const float handle_width = gui::UIConfig::kSplitterWidth;
1027 const ImVec2 win_pos = ImGui::GetWindowPos();
1028 const float win_height = ImGui::GetWindowHeight();
1029 ImGui::SetCursorScreenPos(
1030 ImVec2(win_pos.x - handle_width * 0.5f, win_pos.y));
1031 ImGui::InvisibleButton("##RightPanelResizeHandle",
1032 ImVec2(handle_width, win_height));
1033 const bool handle_hovered = ImGui::IsItemHovered();
1034 const bool handle_active = ImGui::IsItemActive();
1035 if (handle_hovered || handle_active) {
1036 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
1037 }
1038 if (handle_hovered &&
1039 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
1042 }
1043 if (handle_active) {
1044 const float new_width = GetConfiguredPanelWidth(active_panel_) -
1045 ImGui::GetIO().MouseDelta.x;
1046 SetDrawerWidth(active_panel_, new_width);
1047 ImGui::SetTooltip(tr("Width: %.0f px"),
1049 }
1050
1051 ImVec4 handle_color = gui::GetOutlineVec4();
1052 handle_color.w = handle_active ? 0.95f : (handle_hovered ? 0.72f : 0.35f);
1053 ImGui::GetWindowDrawList()->AddLine(
1054 ImVec2(win_pos.x, win_pos.y),
1055 ImVec2(win_pos.x, win_pos.y + win_height),
1056 ImGui::GetColorU32(handle_color), handle_active ? 2.0f : 1.0f);
1057 }
1058 }
1059}
1060
1061void RightDrawerManager::DrawPanelHeader(const char* title, const char* icon) {
1062 const float header_height = gui::UIConfig::kPanelHeaderHeight;
1063 const float padding = gui::UIConfig::kPanelPaddingLarge;
1064
1065 // Header background - slightly elevated surface
1066 ImVec2 header_min = ImGui::GetCursorScreenPos();
1067 ImVec2 header_max = ImVec2(header_min.x + ImGui::GetWindowWidth(),
1068 header_min.y + header_height);
1069
1070 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1071 draw_list->AddRectFilled(
1072 header_min, header_max,
1073 ImGui::GetColorU32(gui::GetSurfaceContainerHighVec4()));
1074
1075 // Draw subtle bottom border
1076 draw_list->AddLine(ImVec2(header_min.x, header_max.y),
1077 ImVec2(header_max.x, header_max.y),
1078 ImGui::GetColorU32(gui::GetOutlineVec4()), 1.0f);
1079
1080 // Position content within header
1081 ImGui::SetCursorPosX(padding);
1082 ImGui::SetCursorPosY(ImGui::GetCursorPosY() +
1083 (header_height - ImGui::GetTextLineHeight()) * 0.5f);
1084
1085 // Panel icon with primary color
1087
1088 ImGui::SameLine();
1089
1090 // Panel title (use current style text color)
1091 gui::ColoredText(title, ImGui::GetStyleColorVec4(ImGuiCol_Text));
1092
1093 const PanelType current_panel =
1095 const std::string previous_shortcut =
1096 GetShortcutLabel("View: Previous Right Panel", "");
1097 const std::string next_shortcut =
1098 GetShortcutLabel("View: Next Right Panel", "");
1099 const std::string previous_tooltip =
1100 previous_shortcut.empty() ? "Previous right panel"
1101 : absl::StrFormat("Previous right panel (%s)",
1102 previous_shortcut.c_str());
1103 const std::string next_tooltip =
1104 next_shortcut.empty()
1105 ? "Next right panel"
1106 : absl::StrFormat("Next right panel (%s)", next_shortcut.c_str());
1107
1108 ImGui::SameLine(0.0f, gui::UIConfig::kHeaderButtonSpacing);
1110 previous_tooltip.c_str(), false,
1111 gui::GetTextSecondaryVec4(), "right_sidebar",
1112 "switch_panel_prev")) {
1114 }
1115
1116 ImGui::SameLine(0.0f, gui::UIConfig::kHeaderButtonGap);
1118 ICON_MD_SWAP_HORIZ, gui::IconSize::Small(), "Panel switcher", false,
1119 gui::GetTextSecondaryVec4(), "right_sidebar", "switch_panel_menu")) {
1120 ImGui::OpenPopup("##RightPanelSwitcher");
1121 }
1122
1123 ImGui::SameLine(0.0f, gui::UIConfig::kHeaderButtonGap);
1125 next_tooltip.c_str(), false,
1126 gui::GetTextSecondaryVec4(), "right_sidebar",
1127 "switch_panel_next")) {
1129 }
1130
1131 if (ImGui::BeginPopup("##RightPanelSwitcher")) {
1132 for (PanelType panel_type : kRightPanelSwitchOrder) {
1133 std::string label = absl::StrFormat("%s %s", GetPanelTypeIcon(panel_type),
1134 GetPanelTypeName(panel_type));
1135 const char* shortcut_action = GetPanelShortcutAction(panel_type);
1136 std::string shortcut;
1137 if (shortcut_action[0] != '\0') {
1138 shortcut = GetShortcutLabel(shortcut_action, "");
1139 if (shortcut == "Unassigned") {
1140 shortcut.clear();
1141 }
1142 }
1143 if (ImGui::MenuItem(label.c_str(),
1144 shortcut.empty() ? nullptr : shortcut.c_str(),
1145 current_panel == panel_type)) {
1146 OpenDrawer(panel_type);
1147 }
1148 }
1149 ImGui::EndPopup();
1150 }
1151
1152 // Right-aligned buttons
1153 const ImVec2 chrome_button_size = gui::IconSize::Toolbar();
1154 const float button_size = chrome_button_size.x;
1155 const float button_y =
1156 header_min.y + (header_height - chrome_button_size.y) * 0.5f;
1157 float current_x = ImGui::GetWindowWidth() - button_size - padding;
1158
1159 // Close button
1160 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, button_y));
1162 ICON_MD_CANCEL, chrome_button_size, "Close Drawer (Esc)", false,
1163 ImVec4(0, 0, 0, 0), "right_sidebar", "close_panel")) {
1164 CloseDrawer();
1165 }
1166
1167 // Lock Toggle (Only for Properties Panel)
1169 current_x -= (button_size + 4.0f);
1170 ImGui::SetCursorScreenPos(ImVec2(header_min.x + current_x, button_y));
1171
1174 chrome_button_size,
1175 properties_locked_ ? "Unlock Selection" : "Lock Selection",
1176 properties_locked_, ImVec4(0, 0, 0, 0), "right_sidebar",
1177 "lock_selection")) {
1179 }
1180 }
1181
1182 // Move cursor past the header
1183 ImGui::SetCursorPosY(header_height + 8.0f);
1184}
1185
1186// =============================================================================
1187// Panel Styling Helpers
1188// =============================================================================
1189
1190bool RightDrawerManager::BeginPanelSection(const char* label, const char* icon,
1191 bool default_open) {
1192 gui::StyleColorGuard section_colors({
1193 {ImGuiCol_Header, gui::GetSurfaceContainerHighVec4()},
1194 {ImGuiCol_HeaderHovered, gui::GetSurfaceContainerHighestVec4()},
1195 {ImGuiCol_HeaderActive, gui::GetSurfaceContainerHighestVec4()},
1196 });
1197 gui::StyleVarGuard section_vars({
1198 {ImGuiStyleVar_FramePadding, ImVec2(8.0f, 6.0f)},
1199 {ImGuiStyleVar_FrameRounding, 4.0f},
1200 });
1201
1202 // Build header text with icon if provided
1203 std::string header_text;
1204 if (icon) {
1205 header_text = std::string(icon) + " " + label;
1206 } else {
1207 header_text = label;
1208 }
1209
1210 ImGuiTreeNodeFlags flags =
1211 ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth |
1212 ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_FramePadding;
1213 if (default_open) {
1214 flags |= ImGuiTreeNodeFlags_DefaultOpen;
1215 }
1216
1217 bool is_open = ImGui::TreeNodeEx(header_text.c_str(), flags);
1218
1219 if (is_open) {
1220 ImGui::Spacing();
1221 ImGui::Indent(4.0f);
1222 }
1223
1224 return is_open;
1225}
1226
1228 ImGui::Unindent(4.0f);
1229 ImGui::TreePop();
1230 ImGui::Spacing();
1231}
1232
1234 ImGui::Spacing();
1235 {
1236 gui::StyleColorGuard sep_color(ImGuiCol_Separator, gui::GetOutlineVec4());
1237 ImGui::Separator();
1238 }
1239 ImGui::Spacing();
1240}
1241
1245
1246void RightDrawerManager::DrawPanelValue(const char* label, const char* value) {
1248 ImGui::SameLine();
1249 ImGui::TextUnformatted(value);
1250}
1251
1253 gui::StyleColorGuard desc_color(ImGuiCol_Text, gui::GetTextDisabledVec4());
1254 ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x);
1255 ImGui::TextWrapped("%s", text);
1256 ImGui::PopTextWrapPos();
1257}
1258
1260 const std::string& action, const std::string& fallback) const {
1261 if (!shortcut_manager_) {
1262 return fallback;
1263 }
1264
1265 const Shortcut* shortcut = shortcut_manager_->FindShortcut(action);
1266 if (!shortcut) {
1267 return fallback;
1268 }
1269 if (shortcut->keys.empty()) {
1270 return "Unassigned";
1271 }
1272
1273 return PrintShortcut(shortcut->keys);
1274}
1275
1276void RightDrawerManager::DrawShortcutRow(const std::string& action,
1277 const char* description,
1278 const std::string& fallback) {
1279 std::string label = GetShortcutLabel(action, fallback);
1280 DrawPanelValue(label.c_str(), description);
1281}
1282
1283// =============================================================================
1284// Panel Content Drawing
1285// =============================================================================
1286
1288#ifdef YAZE_BUILD_AGENT_UI
1289 if (!agent_chat_) {
1290 gui::ColoredText(ICON_MD_SMART_TOY " AI Agent Not Available",
1292 ImGui::Spacing();
1294 "The AI Agent is not initialized. "
1295 "Open the AI Agent from View menu or use Ctrl+Shift+A.");
1296 return;
1297 }
1298
1299 agent_chat_->set_active(true);
1300
1301 const float action_bar_height = ImGui::GetFrameHeightWithSpacing() + 8.0f;
1302 const float content_height =
1304 ImGui::GetContentRegionAvail().y - action_bar_height);
1305
1306 if (ImGui::BeginChild("AgentChatBody", ImVec2(0, content_height), true)) {
1307 agent_chat_->Draw(0.0f);
1308 }
1309 ImGui::EndChild();
1310
1311 gui::StyleVarGuard action_spacing(ImGuiStyleVar_ItemSpacing, ImVec2(6, 6));
1312 const ImVec2 action_size = gui::IconSize::Toolbar();
1313 const ImVec4 transparent_bg(0, 0, 0, 0);
1314
1315 if (proposal_drawer_) {
1317 "Open Proposals", false, transparent_bg,
1318 "agent_sidebar", "open_proposals")) {
1320 }
1321 ImGui::SameLine();
1322 }
1323
1325 "Clear Chat History", false, transparent_bg,
1326 "agent_sidebar", "clear_history")) {
1328 }
1329 ImGui::SameLine();
1330
1332 "Save Chat History", false, transparent_bg,
1333 "agent_sidebar", "save_history")) {
1334 agent_chat_->SaveHistory(ResolveAgentChatHistoryPath());
1335 }
1336#else
1337 gui::ColoredText(ICON_MD_SMART_TOY " AI Agent Not Available",
1339
1340 ImGui::Spacing();
1342 "The AI Agent requires agent UI support. "
1343 "Build with YAZE_BUILD_AGENT_UI=ON to enable.");
1344#endif
1345}
1346
1348#ifdef YAZE_BUILD_AGENT_UI
1349 if (!agent_chat_) {
1350 return false;
1351 }
1352 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
1353 const ImVec4 accent = gui::GetPrimaryVec4();
1354
1355 std::string selection_context;
1357 selection_context =
1358 BuildSelectionContextSummary(properties_panel_->GetSelection());
1359 }
1360
1361 struct QuickAction {
1362 const char* label;
1363 std::string prompt;
1364 };
1365
1366 std::vector<QuickAction> actions;
1367 if (!selection_context.empty()) {
1368 actions.push_back({"Explain selection",
1369 "Explain this selection and how to edit it safely.\n\n" +
1370 selection_context});
1371 actions.push_back(
1372 {"Suggest fixes",
1373 "Suggest improvements or checks for this selection.\n\n" +
1374 selection_context});
1375 }
1376
1377 switch (active_editor_type_) {
1379 actions.push_back({"Summarize map",
1380 "Summarize the current overworld map and its key "
1381 "features. Use overworld tools if available."});
1382 actions.push_back({"List sprites/items",
1383 "List notable sprites or items on the current "
1384 "overworld map."});
1385 break;
1387 actions.push_back({"Audit room",
1388 "Summarize the current dungeon room layout, doors, "
1389 "and object density."});
1390 actions.push_back({"List sprites",
1391 "List sprites in the current dungeon room and any "
1392 "potential conflicts."});
1393 break;
1395 actions.push_back({"Review tiles",
1396 "Review the current tileset usage and point out any "
1397 "obvious issues."});
1398 actions.push_back({"Palette check",
1399 "Check palette usage for contrast/readability "
1400 "problems."});
1401 break;
1403 actions.push_back({"Palette audit",
1404 "Audit the active palette for hue/contrast balance "
1405 "and note risks."});
1406 actions.push_back({"Theme ideas",
1407 "Suggest a palette variation that fits the current "
1408 "scene style."});
1409 break;
1411 actions.push_back({"Sprite review",
1412 "Review the selected sprite properties and suggest "
1413 "tuning."});
1414 break;
1416 actions.push_back({"Copy edit",
1417 "Review the current message text for clarity and "
1418 "style improvements."});
1419 break;
1421 actions.push_back({"ASM review",
1422 "Review the current ASM changes for risks and style "
1423 "issues."});
1424 break;
1425 case EditorType::kHex:
1426 actions.push_back({"Hex context",
1427 "Explain what the current hex selection likely "
1428 "represents."});
1429 break;
1431 actions.push_back({"Test suggestion",
1432 "Propose a short emulator test to validate the "
1433 "current feature."});
1434 break;
1435 case EditorType::kAgent:
1436 actions.push_back({"Agent config review",
1437 "Review current agent configuration for practical "
1438 "improvements."});
1439 break;
1440 default:
1441 actions.push_back({"Agent overview",
1442 "Suggest the next best agent-assisted action for the "
1443 "current editor context."});
1444 break;
1445 }
1446
1447 if (actions.empty()) {
1448 return false;
1449 }
1450
1451 ImGui::TextColored(accent, tr("%s Editor Actions"), ICON_MD_BOLT);
1452 gui::ColoredText("Send a context-aware prompt to the agent.",
1454
1455 int columns = ImGui::GetContentRegionAvail().x > 420.0f ? 2 : 1;
1456 if (ImGui::BeginTable("AgentQuickActionsTable", columns,
1457 ImGuiTableFlags_SizingStretchSame)) {
1458 for (const auto& action : actions) {
1459 ImGui::TableNextColumn();
1460 if (ImGui::Button(action.label, ImVec2(-1, 0))) {
1461 agent_chat_->SendMessage(action.prompt);
1462 }
1463 }
1464 ImGui::EndTable();
1465 }
1466 return true;
1467#else
1468 return false;
1469#endif
1470}
1471
1473 if (proposal_drawer_) {
1474 // Set ROM and draw content inside the panel (not a separate window)
1475 if (rom_) {
1477 }
1479 } else {
1480 gui::ColoredText(ICON_MD_DESCRIPTION " Proposals Not Available",
1482
1483 ImGui::Spacing();
1485 "The proposal system is not initialized. "
1486 "Proposals will appear here when the AI Agent creates them.");
1487 }
1488}
1489
1491 if (settings_panel_) {
1492 // Draw settings inline (no card windows)
1494 } else {
1495 gui::ColoredText(ICON_MD_SETTINGS " Settings Not Available",
1497
1498 ImGui::Spacing();
1500 "Settings will be available once initialized. "
1501 "This panel provides quick access to application settings.");
1502 }
1503}
1504
1506 // Context-aware editor header
1508
1509 // Keyboard Shortcuts section (default open)
1510 if (BeginPanelSection("Keyboard Shortcuts", ICON_MD_KEYBOARD, true)) {
1514 }
1515
1516 // Editor-specific help (default open)
1517 if (BeginPanelSection("Editor Guide", ICON_MD_HELP, true)) {
1520 }
1521
1522 // Quick Actions (collapsed by default)
1523 if (BeginPanelSection("Quick Actions", ICON_MD_BOLT, false)) {
1526 }
1527
1528 // About section (collapsed by default)
1529 if (BeginPanelSection("About", ICON_MD_INFO, false)) {
1532 }
1533}
1534
1536 const char* editor_name = "No Editor Selected";
1537 const char* editor_icon = ICON_MD_HELP;
1538
1539 switch (active_editor_type_) {
1541 editor_name = "Overworld Editor";
1542 editor_icon = ICON_MD_LANDSCAPE;
1543 break;
1545 editor_name = "Dungeon Editor";
1546 editor_icon = ICON_MD_CASTLE;
1547 break;
1549 editor_name = "Graphics Editor";
1550 editor_icon = ICON_MD_IMAGE;
1551 break;
1553 editor_name = "Palette Editor";
1554 editor_icon = ICON_MD_PALETTE;
1555 break;
1556 case EditorType::kMusic:
1557 editor_name = "Music Editor";
1558 editor_icon = ICON_MD_MUSIC_NOTE;
1559 break;
1561 editor_name = "Screen Editor";
1562 editor_icon = ICON_MD_TV;
1563 break;
1565 editor_name = "Sprite Editor";
1566 editor_icon = ICON_MD_SMART_TOY;
1567 break;
1569 editor_name = "Message Editor";
1570 editor_icon = ICON_MD_CHAT;
1571 break;
1573 editor_name = "Emulator";
1574 editor_icon = ICON_MD_VIDEOGAME_ASSET;
1575 break;
1576 default:
1577 break;
1578 }
1579
1580 // Draw context header with editor info
1581 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s %s Help", editor_icon,
1582 editor_name);
1583
1585}
1586
1588 const char* ctrl = gui::GetCtrlDisplayName();
1589 DrawPanelLabel("Global");
1590 ImGui::Indent(8.0f);
1591 DrawShortcutRow("Open", "Open ROM", absl::StrFormat("%s+O", ctrl));
1592 DrawShortcutRow("Save", "Save ROM", absl::StrFormat("%s+S", ctrl));
1593 DrawShortcutRow("Save As", "Save ROM As",
1594 absl::StrFormat("%s+Shift+S", ctrl));
1595 DrawShortcutRow("Undo", "Undo", absl::StrFormat("%s+Z", ctrl));
1596 DrawShortcutRow("Redo", "Redo", absl::StrFormat("%s+Shift+Z", ctrl));
1597 DrawShortcutRow("Command Palette", "Command Palette",
1598 absl::StrFormat("%s+Shift+P", ctrl));
1599 DrawShortcutRow("Global Search", "Global Search",
1600 absl::StrFormat("%s+Shift+K", ctrl));
1601 DrawShortcutRow("view.toggle_activity_bar", "Toggle Sidebar",
1602 absl::StrFormat("%s+B", ctrl));
1603 DrawShortcutRow("Show About", "About / Help", "F1");
1604 DrawPanelValue("Esc", "Close Drawer");
1605 ImGui::Unindent(8.0f);
1606 ImGui::Spacing();
1607}
1608
1610 const char* ctrl = gui::GetCtrlDisplayName();
1611 switch (active_editor_type_) {
1613 DrawPanelLabel("Overworld");
1614 ImGui::Indent(8.0f);
1615 DrawPanelValue("1-3", "Switch World (LW/DW/SP)");
1616 DrawPanelValue("Arrow Keys", "Navigate Maps");
1617 DrawPanelValue("E", "Entity Mode");
1618 DrawPanelValue("T", "Tile Mode");
1619 DrawShortcutRow("overworld.brush_toggle", "Toggle brush", "B");
1620 DrawShortcutRow("overworld.fill", "Fill tool", "F");
1621 DrawShortcutRow("overworld.next_tile", "Next tile", "]");
1622 DrawShortcutRow("overworld.prev_tile", "Previous tile", "[");
1623 DrawPanelValue("Right Click", "Pick Tile");
1624 ImGui::Unindent(8.0f);
1625 break;
1626
1628 DrawPanelLabel("Dungeon");
1629 ImGui::Indent(8.0f);
1630 DrawShortcutRow("dungeon.object.select_tool", "Select tool", "S");
1631 DrawShortcutRow("dungeon.object.place_tool", "Place tool", "P");
1632 DrawShortcutRow("dungeon.object.delete_tool", "Delete tool", "D");
1633 DrawShortcutRow("dungeon.object.copy", "Copy selection",
1634 absl::StrFormat("%s+C", ctrl));
1635 DrawShortcutRow("dungeon.object.paste", "Paste selection",
1636 absl::StrFormat("%s+V", ctrl));
1637 DrawShortcutRow("dungeon.object.delete", "Delete selection", "Delete");
1638 DrawPanelValue("Arrow Keys", "Move Object");
1639 DrawPanelValue("G", "Toggle Grid");
1640 DrawPanelValue("L", "Cycle Layers");
1641 ImGui::Unindent(8.0f);
1642 break;
1643
1645 DrawPanelLabel("Graphics");
1646 ImGui::Indent(8.0f);
1647 DrawShortcutRow("graphics.prev_sheet", "Previous sheet", "PageUp");
1648 DrawShortcutRow("graphics.next_sheet", "Next sheet", "PageDown");
1649 DrawShortcutRow("graphics.tool.pencil", "Pencil tool", "B");
1650 DrawShortcutRow("graphics.tool.fill", "Fill tool", "G");
1651 DrawShortcutRow("graphics.zoom_in", "Zoom in", "+");
1652 DrawShortcutRow("graphics.zoom_out", "Zoom out", "-");
1653 DrawShortcutRow("graphics.toggle_grid", "Toggle grid",
1654 absl::StrFormat("%s+G", ctrl));
1655 ImGui::Unindent(8.0f);
1656 break;
1657
1659 DrawPanelLabel("Palette");
1660 ImGui::Indent(8.0f);
1661 DrawPanelValue("Click", "Select Color");
1662 DrawPanelValue("Double Click", "Edit Color");
1663 DrawPanelValue("Drag", "Copy Color");
1664 ImGui::Unindent(8.0f);
1665 break;
1666
1667 case EditorType::kMusic:
1668 DrawPanelLabel("Music");
1669 ImGui::Indent(8.0f);
1670 DrawShortcutRow("music.play_pause", "Play/Pause", "Space");
1671 DrawShortcutRow("music.stop", "Stop", "Esc");
1672 DrawShortcutRow("music.speed_up", "Speed up", "+");
1673 DrawShortcutRow("music.speed_down", "Slow down", "-");
1674 DrawPanelValue("Left/Right", "Seek");
1675 ImGui::Unindent(8.0f);
1676 break;
1677
1679 DrawPanelLabel("Message");
1680 ImGui::Indent(8.0f);
1681 DrawPanelValue(absl::StrFormat("%s+Enter", ctrl).c_str(),
1682 "Insert Line Break");
1683 DrawPanelValue("Up/Down", "Navigate Messages");
1684 ImGui::Unindent(8.0f);
1685 break;
1686
1687 default:
1688 DrawPanelLabel("Editor Shortcuts");
1689 ImGui::Indent(8.0f);
1690 {
1691 gui::StyleColorGuard text_color(ImGuiCol_Text,
1693 ImGui::TextWrapped(tr("Select an editor to see specific shortcuts."));
1694 }
1695 ImGui::Unindent(8.0f);
1696 break;
1697 }
1698}
1699
1701 switch (active_editor_type_) {
1703 gui::StyleColorGuard text_color(ImGuiCol_Text,
1704 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1705 ImGui::Bullet();
1706 ImGui::TextWrapped(tr("Paint tiles by selecting from Tile16 Selector"));
1707 ImGui::Bullet();
1708 ImGui::TextWrapped(
1709 tr("Switch between Light World, Dark World, and Special Areas"));
1710 ImGui::Bullet();
1711 ImGui::TextWrapped(
1712 tr("Use Entity Mode to place entrances, exits, items, and sprites"));
1713 ImGui::Bullet();
1714 ImGui::TextWrapped(
1715 tr("Right-click on the map to pick a tile for painting"));
1716 } break;
1717
1718 case EditorType::kDungeon: {
1719 gui::StyleColorGuard text_color(ImGuiCol_Text,
1720 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1721 ImGui::Bullet();
1722 ImGui::TextWrapped(
1723 tr("Select rooms from the Room Selector or Room Matrix"));
1724 ImGui::Bullet();
1725 ImGui::TextWrapped(tr("Place objects using the Object Editor panel"));
1726 ImGui::Bullet();
1727 ImGui::TextWrapped(
1728 tr("Edit room headers for palette, GFX, and floor settings"));
1729 ImGui::Bullet();
1730 ImGui::TextWrapped(tr("Multiple rooms can be opened in separate tabs"));
1731 } break;
1732
1733 case EditorType::kGraphics: {
1734 gui::StyleColorGuard text_color(ImGuiCol_Text,
1735 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1736 ImGui::Bullet();
1737 ImGui::TextWrapped(tr("Browse graphics sheets using the Sheet Browser"));
1738 ImGui::Bullet();
1739 ImGui::TextWrapped(tr("Edit pixels directly with the Pixel Editor"));
1740 ImGui::Bullet();
1741 ImGui::TextWrapped(tr("Choose palettes from Palette Controls"));
1742 ImGui::Bullet();
1743 ImGui::TextWrapped(tr("View 3D objects like rupees and crystals"));
1744 } break;
1745
1746 case EditorType::kPalette: {
1747 gui::StyleColorGuard text_color(ImGuiCol_Text,
1748 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1749 ImGui::Bullet();
1750 ImGui::TextWrapped(tr("Edit overworld, dungeon, and sprite palettes"));
1751 ImGui::Bullet();
1752 ImGui::TextWrapped(tr("Use Quick Access for color harmony tools"));
1753 ImGui::Bullet();
1754 ImGui::TextWrapped(tr("Changes update in real-time across all editors"));
1755 } break;
1756
1757 case EditorType::kMusic: {
1758 gui::StyleColorGuard text_color(ImGuiCol_Text,
1759 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1760 ImGui::Bullet();
1761 ImGui::TextWrapped(tr("Browse songs in the Song Browser"));
1762 ImGui::Bullet();
1763 ImGui::TextWrapped(tr("Use the tracker for playback control"));
1764 ImGui::Bullet();
1765 ImGui::TextWrapped(tr("Edit instruments and BRR samples"));
1766 } break;
1767
1768 case EditorType::kMessage: {
1769 gui::StyleColorGuard text_color(ImGuiCol_Text,
1770 ImGui::GetStyleColorVec4(ImGuiCol_Text));
1771 ImGui::Bullet();
1772 ImGui::TextWrapped(tr("Edit all in-game dialog messages"));
1773 ImGui::Bullet();
1774 ImGui::TextWrapped(tr("Preview text rendering with the font atlas"));
1775 ImGui::Bullet();
1776 ImGui::TextWrapped(tr("Manage the compression dictionary"));
1777 } break;
1778
1779 default:
1780 ImGui::Bullet();
1781 ImGui::TextWrapped(tr("Open a ROM file via File > Open ROM"));
1782 ImGui::Bullet();
1783 ImGui::TextWrapped(tr("Select an editor from the sidebar"));
1784 ImGui::Bullet();
1785 ImGui::TextWrapped(tr("Use panels to access tools and settings"));
1786 ImGui::Bullet();
1787 ImGui::TextWrapped(tr("Save your work via File > Save ROM"));
1788 break;
1789 }
1790}
1791
1793 const float button_width = ImGui::GetContentRegionAvail().x;
1794
1795 gui::StyleVarGuard button_vars({
1796 {ImGuiStyleVar_FramePadding, ImVec2(8.0f, 6.0f)},
1797 {ImGuiStyleVar_FrameRounding, 4.0f},
1798 });
1799
1800 // Documentation button
1801 {
1802 gui::StyleColorGuard btn_colors({
1803 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1804 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1805 });
1806 if (ImGui::Button(ICON_MD_DESCRIPTION " Open Documentation",
1807 ImVec2(button_width, 0))) {
1808 gui::OpenUrl("https://github.com/scawful/yaze/wiki");
1809 }
1810 }
1811
1812 ImGui::Spacing();
1813
1814 // GitHub Issues button
1815 {
1816 gui::StyleColorGuard btn_colors({
1817 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1818 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1819 });
1820 if (ImGui::Button(ICON_MD_BUG_REPORT " Report Issue",
1821 ImVec2(button_width, 0))) {
1822 gui::OpenUrl("https://github.com/scawful/yaze/issues/new");
1823 }
1824 }
1825
1826 ImGui::Spacing();
1827
1828 // Discord button
1829 {
1830 gui::StyleColorGuard btn_colors({
1831 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1832 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1833 });
1834 if (ImGui::Button(ICON_MD_FORUM " Join Discord", ImVec2(button_width, 0))) {
1835 gui::OpenUrl("https://discord.gg/zU5qDm8MZg");
1836 }
1837 }
1838}
1839
1841 gui::ColoredText("YAZE - Yet Another Zelda3 Editor", gui::GetPrimaryVec4());
1842
1843 ImGui::Spacing();
1845 "A comprehensive editor for The Legend of Zelda: "
1846 "A Link to the Past ROM files.");
1847
1849
1850 DrawPanelLabel("Credits");
1851 ImGui::Spacing();
1852 ImGui::Text(tr("Written by: scawful"));
1853 ImGui::Text(tr("Special Thanks: Zarby89, JaredBrian"));
1854
1856
1857 DrawPanelLabel("Links");
1858 ImGui::Spacing();
1859 gui::ColoredText(ICON_MD_LINK " github.com/scawful/yaze",
1861}
1862
1864 if (!toast_manager_) {
1865 gui::ColoredText(ICON_MD_NOTIFICATIONS_OFF " Notifications Unavailable",
1867 return;
1868 }
1869
1870 // Header actions
1871 float avail = ImGui::GetContentRegionAvail().x;
1872
1873 // Mark all read / Clear all buttons
1874 {
1875 gui::StyleColorGuard btn_colors({
1876 {ImGuiCol_Button, gui::GetSurfaceContainerHighVec4()},
1877 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighestVec4()},
1878 });
1879
1880 if (ImGui::Button(ICON_MD_DONE_ALL " Mark All Read",
1881 ImVec2(avail * 0.5f - 4.0f, 0))) {
1883 }
1884 ImGui::SameLine();
1885 if (ImGui::Button(ICON_MD_DELETE_SWEEP " Clear All",
1886 ImVec2(avail * 0.5f - 4.0f, 0))) {
1888 }
1889 }
1890
1892
1893 const auto build_status = ContentRegistry::Context::build_workflow_status();
1894 const auto run_status = ContentRegistry::Context::run_workflow_status();
1895 const auto workflow_history = ContentRegistry::Context::workflow_history();
1896 workflow::WorkflowActionCallbacks workflow_callbacks;
1897 workflow_callbacks.start_build =
1899 workflow_callbacks.run_project =
1901 workflow_callbacks.show_output =
1903 const auto cancel_build =
1905
1906 if (build_status.visible || run_status.visible || !workflow_history.empty()) {
1907 DrawPanelLabel("Workflow Activity");
1908 if (build_status.visible) {
1909 DrawWorkflowSummaryCard("Build", ICON_MD_BUILD, build_status,
1910 cancel_build);
1911 ImGui::Spacing();
1912 }
1913 if (run_status.visible) {
1914 DrawWorkflowSummaryCard("Run", ICON_MD_PLAY_ARROW, run_status);
1915 ImGui::Spacing();
1916 }
1917 if (!workflow_history.empty()) {
1918 DrawPanelLabel("Recent Workflow History");
1919 const auto preview_entries =
1920 workflow::SelectWorkflowPreviewEntries(workflow_history, 3);
1921 for (size_t i = 0; i < preview_entries.size(); ++i) {
1922 ImGui::PushID(static_cast<int>(i));
1923 DrawWorkflowPreviewEntry(preview_entries[i], workflow_callbacks);
1924 ImGui::PopID();
1925 if (i + 1 < preview_entries.size()) {
1926 ImGui::Separator();
1927 }
1928 }
1929 if (workflow_history.size() > preview_entries.size()) {
1930 ImGui::Spacing();
1931 ImGui::TextDisabled(
1932 tr("+%zu more entries available in Workflow Output"),
1933 workflow_history.size() - preview_entries.size());
1934 if (workflow_callbacks.show_output) {
1935 if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW
1936 " View Full History##workflow_view_full")) {
1937 workflow_callbacks.show_output();
1938 }
1939 }
1940 }
1941 }
1943 }
1944
1945 // Notification history
1946 const auto& history = toast_manager_->GetHistory();
1947
1948 if (history.empty()) {
1949 ImGui::Spacing();
1950 gui::ColoredText(ICON_MD_INBOX " No notifications",
1952 ImGui::Spacing();
1954 "Notifications will appear here when actions complete.");
1955 return;
1956 }
1957
1958 // Stats
1959 size_t unread_count = toast_manager_->GetUnreadCount();
1960 if (unread_count > 0) {
1961 gui::ColoredTextF(gui::GetPrimaryVec4(), "%zu unread", unread_count);
1962 } else {
1963 gui::ColoredText("All caught up", gui::GetTextSecondaryVec4());
1964 }
1965
1966 ImGui::Spacing();
1967
1968 // Scrollable notification list (minimum height so list never collapses)
1969 const bool notification_list_open = gui::LayoutHelpers::BeginContentChild(
1970 "##NotificationList", ImVec2(0.0f, gui::UIConfig::kContentMinHeightList),
1971 ImGuiChildFlags_None, ImGuiWindowFlags_AlwaysVerticalScrollbar);
1972 if (notification_list_open) {
1973 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
1974 auto now = std::chrono::system_clock::now();
1975
1976 // Group by time (Today, Yesterday, Older)
1977 bool shown_today = false;
1978 bool shown_yesterday = false;
1979 bool shown_older = false;
1980
1981 for (const auto& entry : history) {
1982 auto diff =
1983 std::chrono::duration_cast<std::chrono::hours>(now - entry.timestamp)
1984 .count();
1985
1986 // Time grouping headers
1987 if (diff < 24 && !shown_today) {
1988 DrawPanelLabel("Today");
1989 shown_today = true;
1990 } else if (diff >= 24 && diff < 48 && !shown_yesterday) {
1991 ImGui::Spacing();
1992 DrawPanelLabel("Yesterday");
1993 shown_yesterday = true;
1994 } else if (diff >= 48 && !shown_older) {
1995 ImGui::Spacing();
1996 DrawPanelLabel("Older");
1997 shown_older = true;
1998 }
1999
2000 // Notification item
2001 ImGui::PushID(&entry);
2002
2003 // Icon and color based on type
2004 const char* icon;
2005 ImVec4 color;
2006 switch (entry.type) {
2008 icon = ICON_MD_CHECK_CIRCLE;
2009 color = gui::ConvertColorToImVec4(theme.success);
2010 break;
2012 icon = ICON_MD_WARNING;
2013 color = gui::ConvertColorToImVec4(theme.warning);
2014 break;
2015 case ToastType::kError:
2016 icon = ICON_MD_ERROR;
2017 color = gui::ConvertColorToImVec4(theme.error);
2018 break;
2019 default:
2020 icon = ICON_MD_INFO;
2021 color = gui::ConvertColorToImVec4(theme.info);
2022 break;
2023 }
2024
2025 // Unread indicator
2026 if (!entry.read) {
2028 ImGui::SameLine();
2029 }
2030
2031 // Icon
2032 gui::ColoredTextF(color, "%s", icon);
2033 ImGui::SameLine();
2034
2035 // Message
2036 ImGui::TextWrapped("%s", entry.message.c_str());
2037
2038 // Timestamp
2039 auto diff_sec = std::chrono::duration_cast<std::chrono::seconds>(
2040 now - entry.timestamp)
2041 .count();
2042 std::string time_str;
2043 if (diff_sec < 60) {
2044 time_str = "just now";
2045 } else if (diff_sec < 3600) {
2046 time_str = absl::StrFormat("%dm ago", diff_sec / 60);
2047 } else if (diff_sec < 86400) {
2048 time_str = absl::StrFormat("%dh ago", diff_sec / 3600);
2049 } else {
2050 time_str = absl::StrFormat("%dd ago", diff_sec / 86400);
2051 }
2052
2053 gui::ColoredTextF(gui::GetTextDisabledVec4(), " %s", time_str.c_str());
2054
2055 ImGui::PopID();
2056 ImGui::Spacing();
2057 }
2058 }
2060}
2061
2063 if (properties_panel_) {
2065 } else {
2066 // Placeholder when no properties panel is set
2067 gui::ColoredText(ICON_MD_SELECT_ALL " No Selection",
2069
2070 ImGui::Spacing();
2072 "Select an item in the editor to view and edit its properties here.");
2073
2075
2076 // Show placeholder sections for what properties would look like
2077 if (BeginPanelSection("Position & Size", ICON_MD_STRAIGHTEN, true)) {
2078 DrawPanelValue("X", "--");
2079 DrawPanelValue("Y", "--");
2080 DrawPanelValue("Width", "--");
2081 DrawPanelValue("Height", "--");
2083 }
2084
2085 if (BeginPanelSection("Appearance", ICON_MD_PALETTE, false)) {
2086 DrawPanelValue("Tile ID", "--");
2087 DrawPanelValue("Palette", "--");
2088 DrawPanelValue("Layer", "--");
2090 }
2091
2092 if (BeginPanelSection("Behavior", ICON_MD_SETTINGS, false)) {
2093 DrawPanelValue("Type", "--");
2094 DrawPanelValue("Subtype", "--");
2095 DrawPanelValue("Properties", "--");
2097 }
2098 }
2099}
2100
2102 if (project_panel_) {
2104 } else {
2105 gui::ColoredText(ICON_MD_FOLDER_SPECIAL " No Project Loaded",
2107
2108 ImGui::Spacing();
2110 "Open a .yaze project file to access project management features "
2111 "including ROM versioning, snapshots, and configuration.");
2112
2114
2115 // Placeholder for project features
2116 if (BeginPanelSection("Quick Start", ICON_MD_ROCKET_LAUNCH, true)) {
2117 ImGui::Bullet();
2118 ImGui::TextWrapped(tr("Create a new project via File > New Project"));
2119 ImGui::Bullet();
2120 ImGui::TextWrapped(tr("Open existing .yaze project files"));
2121 ImGui::Bullet();
2122 ImGui::TextWrapped(tr("Projects track ROM versions and settings"));
2124 }
2125
2126 if (BeginPanelSection("Features", ICON_MD_CHECKLIST, false)) {
2127 ImGui::Bullet();
2128 ImGui::TextWrapped(tr("Version snapshots with Git integration"));
2129 ImGui::Bullet();
2130 ImGui::TextWrapped(tr("ROM backup and restore"));
2131 ImGui::Bullet();
2132 ImGui::TextWrapped(tr("Project-specific settings"));
2133 ImGui::Bullet();
2134 ImGui::TextWrapped(tr("Assembly code folder integration"));
2136 }
2137 }
2138}
2139
2141 if (!tool_output_query_.empty()) {
2143 ImGui::SameLine();
2144 if (ImGui::SmallButton(ICON_MD_CONTENT_COPY " Copy")) {
2145 ImGui::SetClipboardText(tool_output_query_.c_str());
2146 }
2147 ImGui::TextWrapped("%s", tool_output_query_.c_str());
2149 }
2150
2151 if (tool_output_content_.empty()) {
2152 gui::ColoredText(ICON_MD_INFO " No tool output",
2155 "Run a project-graph query from the editor to inspect its output "
2156 "here.");
2157 return;
2158 }
2159
2160 Json parsed;
2161 const bool has_json = TryParseToolOutputJson(tool_output_content_, &parsed);
2162
2164 if (ImGui::SmallButton(ICON_MD_CONTENT_COPY " Copy Result")) {
2165 ImGui::SetClipboardText(tool_output_content_.c_str());
2166 }
2167
2168 if (has_json) {
2169 if (BeginPanelSection("Summary", ICON_MD_INFO, true)) {
2170 ImGui::PushID("summary");
2171 DrawToolOutputEntryActions(parsed, tool_output_actions_);
2172 if (!parsed.empty()) {
2173 ImGui::Spacing();
2174 }
2175 DrawJsonObjectFields(parsed);
2176 ImGui::PopID();
2178 }
2179 if (parsed.contains("source") && parsed["source"].is_object() &&
2180 BeginPanelSection("Resolved Source", ICON_MD_CODE, true)) {
2181 ImGui::PushID("resolved_source");
2182 DrawToolOutputEntryActions(parsed["source"], tool_output_actions_);
2183 if (!parsed["source"].empty()) {
2184 ImGui::Spacing();
2185 }
2186 DrawJsonObjectFields(parsed["source"]);
2187 ImGui::PopID();
2189 }
2190 DrawJsonObjectArraySection("Matching Symbols", parsed["matching_symbols"],
2192 DrawJsonObjectArraySection("Sources", parsed["sources"],
2194 DrawJsonObjectArraySection("Hooks", parsed["hooks"], tool_output_actions_);
2195 DrawJsonObjectArraySection("Writes", parsed["writes"],
2197 DrawJsonObjectArraySection("Banks", parsed["banks"], tool_output_actions_);
2198 DrawJsonObjectArraySection("Symbols", parsed["symbols"],
2200 }
2201
2202 if (ImGui::CollapsingHeader(tr("Raw Output"),
2203 has_json ? 0 : ImGuiTreeNodeFlags_DefaultOpen)) {
2204 if (ImGui::BeginChild("##tool_output_result", ImVec2(0.0f, 220.0f), true)) {
2205 ImGui::TextUnformatted(tool_output_content_.c_str());
2206 }
2207 ImGui::EndChild();
2208 }
2209}
2210
2212 bool clicked = false;
2213
2214 // Keep menu-bar controls on SmallButton metrics so baseline/spacing stays
2215 // consistent with the session + notification controls.
2216 auto DrawPanelButton = [&](const char* icon, const char* base_tooltip,
2217 const char* shortcut_action, PanelType type) {
2218 const bool is_active = IsDrawerActive(type);
2219 gui::StyleColorGuard button_colors({
2220 {ImGuiCol_Button, ImVec4(0, 0, 0, 0)},
2221 {ImGuiCol_ButtonHovered, gui::GetSurfaceContainerHighVec4()},
2222 {ImGuiCol_ButtonActive, gui::GetSurfaceContainerHighestVec4()},
2223 {ImGuiCol_Text,
2225 });
2226
2227 if (ImGui::SmallButton(icon)) {
2228 ToggleDrawer(type);
2229 clicked = true;
2230 }
2231
2232 if (ImGui::IsItemHovered()) {
2233 const std::string shortcut = GetShortcutLabel(shortcut_action, "");
2234 if (shortcut.empty() || shortcut == "Unassigned") {
2235 ImGui::SetTooltip("%s", base_tooltip);
2236 } else {
2237 ImGui::SetTooltip("%s (%s)", base_tooltip, shortcut.c_str());
2238 }
2239 }
2240 };
2241
2242 DrawPanelButton(ICON_MD_FOLDER_SPECIAL, "Project Drawer",
2243 "View: Toggle Project Panel", PanelType::kProject);
2244 ImGui::SameLine();
2245
2246 DrawPanelButton(ICON_MD_SMART_TOY, "AI Agent Drawer",
2247 "View: Toggle AI Agent Panel", PanelType::kAgentChat);
2248 ImGui::SameLine();
2249
2250 DrawPanelButton(ICON_MD_HELP_OUTLINE, "Help Drawer",
2251 "View: Toggle Help Panel", PanelType::kHelp);
2252 ImGui::SameLine();
2253
2254 DrawPanelButton(ICON_MD_SETTINGS, "Settings Drawer",
2255 "View: Toggle Settings Panel", PanelType::kSettings);
2256 ImGui::SameLine();
2257
2258 DrawPanelButton(ICON_MD_LIST_ALT, "Properties Drawer",
2259 "View: Toggle Properties Panel", PanelType::kProperties);
2260
2261 return clicked;
2262}
2263
2264} // namespace editor
2265} // namespace yaze
bool is_object() const
Definition json.h:57
bool is_boolean() const
Definition json.h:55
static Json parse(const std::string &)
Definition json.h:36
bool is_array() const
Definition json.h:58
bool is_null() const
Definition json.h:54
bool is_string() const
Definition json.h:59
size_t size() const
Definition json.h:61
T get() const
Definition json.h:49
bool empty() const
Definition json.h:62
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
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)
void CycleDrawer(int direction)
Cycle to the next/previous right drawer in header order.
bool DrawDrawerToggleButtons()
Draw drawer toggle buttons for the status cluster.
static std::string PanelTypeKey(PanelType type)
void NotifyPanelWidthChanged(PanelType type, float width)
void DrawShortcutRow(const std::string &action, const char *description, const std::string &fallback)
std::string GetShortcutLabel(const std::string &action, const std::string &fallback) const
float GetClampedPanelWidth(PanelType type, float viewport_width) const
void OpenDrawer(DrawerType type)
Open a specific drawer.
void SetDrawerWidth(DrawerType type, float width)
Set drawer width for a specific drawer type.
void SetToolOutput(std::string title, std::string query, std::string content, ToolOutputActions actions={})
void DrawPanelValue(const char *label, const char *value)
void Draw()
Draw the drawer and its contents.
std::function< void(PanelType, float)> on_panel_width_changed_
void ResetDrawerWidths()
Reset all drawer widths to their defaults.
void DrawPanelHeader(const char *title, const char *icon)
SelectionPropertiesPanel * properties_panel_
bool BeginPanelSection(const char *label, const char *icon=nullptr, bool default_open=true)
void ToggleDrawer(DrawerType type)
Toggle a specific drawer on/off.
static float GetDefaultDrawerWidth(DrawerType type, EditorType editor=EditorType::kUnknown)
Get the default width for a specific drawer type.
float GetConfiguredPanelWidth(PanelType type) const
std::unordered_map< std::string, PanelSizeLimits > panel_size_limits_
bool IsDrawerActive(DrawerType type) const
Check if a specific drawer is active.
bool IsDrawerExpanded() const
Check if any drawer is currently expanded (or animating closed)
float GetDrawerWidth() const
Get the width of the drawer when expanded.
void OnHostVisibilityChanged(bool visible)
Snap transient animations when host visibility changes.
void CloseDrawer()
Close the currently active drawer.
void RestoreDrawerWidths(const std::unordered_map< std::string, float > &widths)
std::unordered_map< std::string, float > SerializeDrawerWidths() const
Persist/restore per-drawer widths for user settings.
ProjectManagementPanel * project_panel_
PanelSizeLimits GetPanelSizeLimits(PanelType type) const
void SetPanelSizeLimits(PanelType type, const PanelSizeLimits &limits)
Set sizing constraints for an individual right panel.
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:264
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_ACCOUNT_TREE
Definition icons.h:83
#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_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:744
#define ICON_MD_SWAP_HORIZ
Definition icons.h:1896
#define ICON_MD_CODE
Definition icons.h:434
#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_TERMINAL
Definition icons.h:1951
#define ICON_MD_DESCRIPTION
Definition icons.h:539
#define ICON_MD_BUILD
Definition icons.h:328
#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_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#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::vector< ProjectWorkflowHistoryEntry > workflow_history()
std::function< void()> cancel_build_workflow_callback()
ProjectWorkflowStatus build_workflow_status()
std::function< void()> run_project_workflow_callback()
std::function< void()> show_workflow_output_callback()
std::function< void()> start_build_workflow_callback()
void DrawWorkflowPreviewEntry(const ProjectWorkflowHistoryEntry &entry, const workflow::WorkflowActionCallbacks &callbacks)
void DrawWorkflowSummaryCard(const char *title, const char *fallback_icon, const ProjectWorkflowStatus &status, const std::function< void()> &cancel_callback={})
const std::array< RightDrawerManager::PanelType, 7 > kRightPanelSwitchOrder
bool TryParseToolOutputJson(const std::string &text, Json *out)
const char * GetPanelShortcutAction(RightDrawerManager::PanelType type)
std::string BuildSelectionContextSummary(const SelectionContext &selection)
std::string SanitizeToolOutputIdFragment(const std::string &value)
void DrawJsonObjectArraySection(const char *label, const Json &array, const RightDrawerManager::ToolOutputActions &actions)
RightDrawerManager::PanelType StepRightPanel(RightDrawerManager::PanelType current, int direction)
std::optional< uint32_t > ParseToolOutputAddress(const Json &value)
std::optional< uint32_t > ExtractToolOutputAddress(const Json &object)
void DrawToolOutputEntryActions(const Json &object, const RightDrawerManager::ToolOutputActions &actions)
std::string BuildToolOutputActionLabel(const char *visible_label, const char *action_key, const Json &object)
std::string FormatHistoryTime(std::chrono::system_clock::time_point timestamp)
const char * WorkflowIcon(const ProjectWorkflowStatus &status, const char *fallback_icon)
WorkflowActionRowResult DrawHistoryActionRow(const ProjectWorkflowHistoryEntry &entry, const WorkflowActionCallbacks &callbacks, const WorkflowActionRowOptions &options)
std::vector< ProjectWorkflowHistoryEntry > SelectWorkflowPreviewEntries(const std::vector< ProjectWorkflowHistoryEntry > &history, size_t max_entries)
ImVec4 WorkflowColor(ProjectWorkflowState state)
const char * GetPanelTypeName(RightDrawerManager::PanelType type)
Get the name of a panel 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 * GetPanelTypeIcon(RightDrawerManager::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:664
ImVec4 GetTextDisabledVec4()
ImVec4 GetTextSecondaryVec4()
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
Animator & GetAnimator()
Definition animator.cc:318
ImVec4 GetSurfaceContainerHighVec4()
ImVec4 GetOutlineVec4()
ImVec4 GetOnSurfaceVec4()
ImVec4 GetSurfaceContainerVec4()
std::chrono::system_clock::time_point timestamp
std::function< void(const std::string &) on_open_reference)
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