yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project_management_panel.cc
Go to the documentation of this file.
2
3#include "absl/strings/str_format.h"
9#include "imgui/imgui.h"
10#include "rom/rom.h"
11#include "util/platform_paths.h"
12#include "yaze_config.h"
13
14namespace yaze {
15namespace editor {
16
17namespace {
18
20 switch (state) {
22 return ImVec4(0.45f, 0.70f, 0.95f, 1.0f);
24 return ImVec4(0.35f, 0.80f, 0.45f, 1.0f);
26 return ImVec4(0.90f, 0.35f, 0.35f, 1.0f);
28 default:
30 }
31}
32
33const char* WorkflowIcon(ProjectWorkflowState state, const char* fallback) {
34 switch (state) {
36 return ICON_MD_SYNC;
40 return ICON_MD_ERROR;
42 default:
43 return fallback;
44 }
45}
46
47void DrawWorkflowCard(const ProjectWorkflowStatus& status,
48 const char* fallback_icon) {
49 if (!status.visible) {
50 return;
51 }
52
54 WorkflowColor(status.state), "%s %s",
55 WorkflowIcon(status.state, fallback_icon),
56 status.summary.empty() ? status.label.c_str() : status.summary.c_str());
57 if (!status.detail.empty()) {
58 ImGui::TextWrapped("%s", status.detail.c_str());
59 }
60 if (!status.output_tail.empty()) {
61 ImGui::TextWrapped("%s", status.output_tail.c_str());
62 }
63}
64
65} // namespace
66
68 if (!project_) {
69 ImGui::TextDisabled("No project loaded");
70 ImGui::Spacing();
71 ImGui::TextWrapped(
72 "Open a .yaze project file or create a new project to access "
73 "project management features.");
74 return;
75 }
76
78 ImGui::Separator();
80 ImGui::Separator();
82 ImGui::Separator();
84 ImGui::Separator();
86}
87
89 // Section header
92 ImGui::Spacing();
93
94 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Project Format:");
95 ImGui::SameLine();
97 ? ".yaze"
98 : ".zsproj");
99
100 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Project YAZE Version:");
101 ImGui::SameLine();
102 const std::string& project_version = project_->metadata.yaze_version;
103 if (project_version.empty()) {
104 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
105 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning), "Unknown");
106 } else if (project_version != YAZE_VERSION_STRING) {
107 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
108 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning), "%s",
109 project_version.c_str());
110 if (ImGui::IsItemHovered()) {
111 ImGui::SetTooltip("Project saved with v%s; running v%s",
112 project_version.c_str(), YAZE_VERSION_STRING);
113 }
114 } else {
115 ImGui::Text("%s", project_version.c_str());
116 }
117
118 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Running YAZE:");
119 ImGui::SameLine();
120 ImGui::Text("%s", YAZE_VERSION_STRING);
121
122 // Project file path (read-only, click to copy)
123 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Path:");
124 ImGui::SameLine();
125 if (ImGui::Selectable(project_->filepath.c_str(), false,
126 ImGuiSelectableFlags_None,
127 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
128 ImGui::SetClipboardText(project_->filepath.c_str());
129 if (toast_manager_) {
130 toast_manager_->Show("Path copied to clipboard", ToastType::kInfo);
131 }
132 }
133 if (ImGui::IsItemHovered()) {
134 ImGui::SetTooltip("Click to copy path");
135 }
136
137 ImGui::Spacing();
138
139 // Editable Project Name
140 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Project Name:");
141 static char name_buffer[256] = {};
142 if (name_buffer[0] == '\0' && !project_->name.empty()) {
143 strncpy(name_buffer, project_->name.c_str(), sizeof(name_buffer) - 1);
144 }
145 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
146 if (ImGui::InputText("##project_name", name_buffer, sizeof(name_buffer))) {
147 project_->name = name_buffer;
148 project_dirty_ = true;
149 }
150
151 // Editable Author
152 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Author:");
153 static char author_buffer[256] = {};
154 if (author_buffer[0] == '\0' && !project_->metadata.author.empty()) {
155 strncpy(author_buffer, project_->metadata.author.c_str(),
156 sizeof(author_buffer) - 1);
157 }
158 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
159 if (ImGui::InputText("##author", author_buffer, sizeof(author_buffer))) {
160 project_->metadata.author = author_buffer;
161 project_dirty_ = true;
162 }
163
164 // Editable Description
165 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Description:");
166 static char desc_buffer[1024] = {};
167 if (desc_buffer[0] == '\0' && !project_->metadata.description.empty()) {
168 strncpy(desc_buffer, project_->metadata.description.c_str(),
169 sizeof(desc_buffer) - 1);
170 }
171 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
172 if (ImGui::InputTextMultiline("##description", desc_buffer,
173 sizeof(desc_buffer), ImVec2(0, 60))) {
174 project_->metadata.description = desc_buffer;
175 project_dirty_ = true;
176 }
177
178 ImGui::Spacing();
179}
180
183 ImGui::Spacing();
184
185 ImGui::TextWrapped(
186 "Primary data lives under the .yaze root. Click any path to copy it.");
187 ImGui::Spacing();
188
190 if (!app_root.ok()) {
191 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
192 ImGui::TextColored(gui::ConvertColorToImVec4(theme.error),
193 "Storage unavailable: %s",
194 std::string(app_root.status().message()).c_str());
195 return;
196 }
197
198 std::vector<std::pair<const char*, std::filesystem::path>> locations = {
199 {"Root", *app_root},
200 {"Projects", *app_root / "projects"},
201 {"Layouts", *app_root / "layouts"},
202 {"Workspaces", *app_root / "workspaces"},
203 {"Logs", *app_root / "logs"},
204 {"Agent", *app_root / "agent"}};
205
206 auto temp_root = util::PlatformPaths::GetTempDirectory();
207 if (temp_root.ok()) {
208 locations.emplace_back("Temp", *temp_root);
209 }
210
211 if (ImGui::BeginTable("##storage_locations", 2,
212 ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV |
213 ImGuiTableFlags_SizingStretchProp)) {
214 ImGui::TableSetupColumn("Location", ImGuiTableColumnFlags_WidthFixed,
215 110.0f);
216 ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthStretch);
217 for (const auto& entry : locations) {
218 ImGui::TableNextRow();
219 ImGui::TableNextColumn();
220 ImGui::TextColored(gui::GetTextSecondaryVec4(), "%s", entry.first);
221 ImGui::TableNextColumn();
222 const std::string display_path =
224 ImGui::PushID(entry.first);
225 if (ImGui::Selectable(display_path.c_str(), false,
226 ImGuiSelectableFlags_SpanAllColumns)) {
227 ImGui::SetClipboardText(display_path.c_str());
228 if (toast_manager_) {
229 toast_manager_->Show("Path copied to clipboard", ToastType::kInfo);
230 }
231 }
232 if (ImGui::IsItemHovered()) {
233 ImGui::SetTooltip("Click to copy");
234 }
235 ImGui::PopID();
236 }
237 ImGui::EndTable();
238 }
239
240 ImGui::Spacing();
241}
242
245 ImGui::Spacing();
246
247 // Current ROM
248 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Current ROM:");
249 if (project_->rom_filename.empty()) {
250 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
251 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning),
252 "Not configured");
253 } else {
254 // Show just the filename, full path on hover
255 std::string filename = project_->rom_filename;
256 size_t pos = filename.find_last_of("/\\");
257 if (pos != std::string::npos) {
258 filename = filename.substr(pos + 1);
259 }
260 ImGui::Text("%s", filename.c_str());
261 if (ImGui::IsItemHovered()) {
262 ImGui::SetTooltip("%s", project_->rom_filename.c_str());
263 }
264 }
265
266 // ROM status
267 if (rom_ && rom_->is_loaded()) {
268 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Title:");
269 ImGui::SameLine();
270 ImGui::Text("%s", rom_->title().c_str());
271
272 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Size:");
273 ImGui::SameLine();
274 ImGui::Text("%.2f MB", static_cast<float>(rom_->size()) / (1024 * 1024));
275
276 if (rom_->dirty()) {
277 const auto& theme2 = gui::ThemeManager::Get().GetCurrentTheme();
278 ImGui::TextColored(gui::ConvertColorToImVec4(theme2.warning),
279 "%s Unsaved changes", ICON_MD_WARNING);
280 }
281 }
282
283 ImGui::Spacing();
284
285 // Action buttons
286 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
287
288 if (ImGui::Button(ICON_MD_SWAP_HORIZ " Swap ROM", ImVec2(button_width, 0))) {
289 if (swap_rom_callback_) {
291 }
292 }
293 if (ImGui::IsItemHovered()) {
294 ImGui::SetTooltip("Replace the ROM file for this project");
295 }
296
297 ImGui::SameLine();
298
299 if (ImGui::Button(ICON_MD_REFRESH " Reload", ImVec2(button_width, 0))) {
302 }
303 }
304 if (ImGui::IsItemHovered()) {
305 ImGui::SetTooltip("Reload ROM from disk");
306 }
307
308 ImGui::Spacing();
309}
310
312 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s Version Control",
314 ImGui::Spacing();
315
316 if (!version_manager_) {
317 ImGui::TextDisabled("Version manager not available");
318 return;
319 }
320
321 bool git_initialized = version_manager_->IsGitInitialized();
322
323 if (!git_initialized) {
324 ImGui::TextWrapped(
325 "Git is not initialized for this project. Initialize Git to enable "
326 "version control and snapshots.");
327 ImGui::Spacing();
328
329 if (ImGui::Button(ICON_MD_ADD " Initialize Git",
330 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
331 auto status = version_manager_->InitializeGit();
332 if (status.ok()) {
333 if (toast_manager_) {
334 toast_manager_->Show("Git repository initialized",
336 }
337 } else {
338 if (toast_manager_) {
340 absl::StrFormat("Failed to initialize Git: %s", status.message()),
342 }
343 }
344 }
345 return;
346 }
347
348 // Show current commit
349 std::string current_hash = version_manager_->GetCurrentHash();
350 if (!current_hash.empty()) {
351 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Current:");
352 ImGui::SameLine();
353 ImGui::Text("%s", current_hash.substr(0, 7).c_str());
354 }
355
356 ImGui::Spacing();
357
358 // Create snapshot section
359 ImGui::Text("Create Snapshot:");
360 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
361 ImGui::InputTextWithHint("##snapshot_msg", "Snapshot message...",
363
364 if (ImGui::Button(ICON_MD_CAMERA_ALT " Create Snapshot",
365 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
366 std::string msg =
367 snapshot_message_[0] ? snapshot_message_ : "Manual snapshot";
368 auto result = version_manager_->CreateSnapshot(msg);
369 if (result.ok()) {
370 if (toast_manager_) {
372 absl::StrFormat("Snapshot created: %s", result->commit_hash),
374 }
375 snapshot_message_[0] = '\0';
376 history_dirty_ = true;
377 } else {
378 if (toast_manager_) {
380 absl::StrFormat("Snapshot failed: %s", result.status().message()),
382 }
383 }
384 }
385 if (ImGui::IsItemHovered()) {
386 ImGui::SetTooltip(
387 "Create a snapshot of your project (Git commit + ROM backup)");
388 }
389
390 // Show recent history
392}
393
396 return;
397 }
398
399 ImGui::Spacing();
400 if (ImGui::CollapsingHeader(ICON_MD_LIST " Recent Snapshots",
401 ImGuiTreeNodeFlags_DefaultOpen)) {
402 // Refresh history if needed
403 if (history_dirty_) {
405 history_dirty_ = false;
406 }
407
408 if (history_cache_.empty()) {
409 ImGui::TextDisabled("No snapshots yet");
410 } else {
411 for (const auto& entry : history_cache_) {
412 // Format: "hash message"
413 size_t space_pos = entry.find(' ');
414 std::string hash =
415 space_pos != std::string::npos ? entry.substr(0, 7) : entry;
416 std::string message =
417 space_pos != std::string::npos ? entry.substr(space_pos + 1) : "";
418
420 ImGui::SameLine();
421 ImGui::TextWrapped("%s", message.c_str());
422 }
423 }
424 }
425}
426
428 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s Quick Actions", ICON_MD_BOLT);
429 ImGui::Spacing();
430
431 float button_width = ImGui::GetContentRegionAvail().x;
432
433 // Show unsaved indicator
434 if (project_dirty_) {
435 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
436 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning),
437 "%s Project has unsaved changes", ICON_MD_EDIT);
438 ImGui::Spacing();
439 }
440
441 if (ImGui::Button(ICON_MD_SAVE " Save Project", ImVec2(button_width, 0))) {
444 project_dirty_ = false;
445 }
446 }
447
448 if (ImGui::Button(ICON_MD_BUILD " Build Project", ImVec2(button_width, 0))) {
451 }
452 }
453
456 if (ImGui::Button(ICON_MD_CANCEL " Cancel Build",
457 ImVec2(button_width, 0))) {
459 }
460 }
461
462 if (ImGui::Button(ICON_MD_PLAY_ARROW " Run Project Output",
463 ImVec2(button_width, 0))) {
466 }
467 }
468
470 ImGui::Spacing();
474 }
475 if (!build_log_output_.empty()) {
476 ImGui::Spacing();
477 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Build Output:");
478 ImGui::BeginChild("##build_output_log", ImVec2(0, 140), true,
479 ImGuiWindowFlags_HorizontalScrollbar);
480 ImGui::TextUnformatted(build_log_output_.c_str());
481 if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
482 ImGui::SetScrollHereY(1.0f);
483 }
484 ImGui::EndChild();
485 }
486 if (run_status_.visible) {
488 }
489 ImGui::Spacing();
490 }
491
492 ImGui::Spacing();
493
494 // Editable Code folder
495 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Code Folder:");
496 static char code_buffer[512] = {};
497 if (code_buffer[0] == '\0' && !project_->code_folder.empty()) {
498 strncpy(code_buffer, project_->code_folder.c_str(),
499 sizeof(code_buffer) - 1);
500 }
501 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 32);
502 if (ImGui::InputText("##code_folder", code_buffer, sizeof(code_buffer))) {
503 project_->code_folder = code_buffer;
504 project_dirty_ = true;
505 }
506 ImGui::SameLine();
507 if (ImGui::Button(ICON_MD_FOLDER_OPEN "##browse_code")) {
510 }
511 }
512 if (ImGui::IsItemHovered()) {
513 ImGui::SetTooltip("Browse for code folder");
514 }
515
516 // Editable Assets folder
517 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Assets Folder:");
518 static char assets_buffer[512] = {};
519 if (assets_buffer[0] == '\0' && !project_->assets_folder.empty()) {
520 strncpy(assets_buffer, project_->assets_folder.c_str(),
521 sizeof(assets_buffer) - 1);
522 }
523 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 32);
524 if (ImGui::InputText("##assets_folder", assets_buffer,
525 sizeof(assets_buffer))) {
526 project_->assets_folder = assets_buffer;
527 project_dirty_ = true;
528 }
529 ImGui::SameLine();
530 if (ImGui::Button(ICON_MD_FOLDER_OPEN "##browse_assets")) {
532 browse_folder_callback_("assets");
533 }
534 }
535 if (ImGui::IsItemHovered()) {
536 ImGui::SetTooltip("Browse for assets folder");
537 }
538
539 // Editable Build target
540 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Build Target:");
541 static char build_buffer[256] = {};
542 if (build_buffer[0] == '\0' && !project_->build_target.empty()) {
543 strncpy(build_buffer, project_->build_target.c_str(),
544 sizeof(build_buffer) - 1);
545 }
546 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
547 if (ImGui::InputText("##build_target", build_buffer, sizeof(build_buffer))) {
548 project_->build_target = build_buffer;
549 project_dirty_ = true;
550 }
551
552 // Build script
553 ImGui::TextColored(gui::GetTextSecondaryVec4(), "Build Script:");
554 static char script_buffer[512] = {};
555 if (script_buffer[0] == '\0' && !project_->build_script.empty()) {
556 strncpy(script_buffer, project_->build_script.c_str(),
557 sizeof(script_buffer) - 1);
558 }
559 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
560 if (ImGui::InputText("##build_script", script_buffer,
561 sizeof(script_buffer))) {
562 project_->build_script = script_buffer;
563 project_dirty_ = true;
564 }
565}
566
567} // namespace editor
568} // namespace yaze
auto size() const
Definition rom.h:138
bool dirty() const
Definition rom.h:133
bool is_loaded() const
Definition rom.h:132
auto title() const
Definition rom.h:137
std::vector< std::string > GetHistory(int limit=10) const
std::string GetCurrentHash() const
absl::StatusOr< SnapshotResult > CreateSnapshot(const std::string &message)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
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 > GetAppDataDirectory()
Get the user-specific application data directory for YAZE.
static std::string NormalizePathForDisplay(const std::filesystem::path &path)
Normalize path separators for display.
#define YAZE_VERSION_STRING
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_MEMORY
Definition icons.h:1195
#define ICON_MD_STORAGE
Definition icons.h:1865
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_FOLDER_SPECIAL
Definition icons.h:815
#define ICON_MD_CAMERA_ALT
Definition icons.h:355
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_SWAP_HORIZ
Definition icons.h:1896
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_ROUTE
Definition icons.h:1627
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_BOLT
Definition icons.h:282
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_BUILD
Definition icons.h:328
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_SYNC
Definition icons.h:1919
#define ICON_MD_HISTORY
Definition icons.h:946
void DrawWorkflowCard(const ProjectWorkflowStatus &status, const char *fallback_icon)
const char * WorkflowIcon(const ProjectWorkflowStatus &status, const char *fallback_icon)
ImVec4 WorkflowColor(ProjectWorkflowState state)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
void ColoredText(const char *text, const ImVec4 &color)
ImVec4 GetPrimaryVec4()
ImVec4 GetTextSecondaryVec4()
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
ProjectMetadata metadata
Definition project.h:166
std::string assets_folder
Definition project.h:179