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