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#include "util/i18n/tr.h"
3
4#include <algorithm>
5
6#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
13#include "rom/rom.h"
14#include "util/platform_paths.h"
15#include "yaze_config.h"
16
17namespace yaze {
18namespace editor {
19
20namespace {
21
22template <size_t N>
23void CopyToBuffer(std::array<char, N>* buffer, const std::string& value) {
24 buffer->fill('\0');
25 const size_t length = std::min(value.size(), N - 1);
26 std::copy_n(value.data(), length, buffer->data());
27}
28
29ImVec4 WorkflowColor(ProjectWorkflowState state) {
30 switch (state) {
32 return ImVec4(0.45f, 0.70f, 0.95f, 1.0f);
34 return ImVec4(0.35f, 0.80f, 0.45f, 1.0f);
36 return ImVec4(0.90f, 0.35f, 0.35f, 1.0f);
38 default:
40 }
41}
42
43const char* WorkflowIcon(ProjectWorkflowState state, const char* fallback) {
44 switch (state) {
46 return ICON_MD_SYNC;
50 return ICON_MD_ERROR;
52 default:
53 return fallback;
54 }
55}
56
58 const char* fallback_icon) {
59 if (!status.visible) {
60 return;
61 }
62
64 WorkflowColor(status.state), "%s %s",
65 WorkflowIcon(status.state, fallback_icon),
66 status.summary.empty() ? status.label.c_str() : status.summary.c_str());
67 if (!status.detail.empty()) {
68 ImGui::TextWrapped("%s", status.detail.c_str());
69 }
70 if (!status.output_tail.empty()) {
71 ImGui::TextWrapped("%s", status.output_tail.c_str());
72 }
73}
74
75} // namespace
76
78 bool dirty) {
79 project_ = project;
80 project_dirty_ = dirty;
81 history_cache_.clear();
82 history_dirty_ = true;
84}
85
87 if (!project_) {
88 name_buffer_.fill('\0');
89 author_buffer_.fill('\0');
90 description_buffer_.fill('\0');
91 code_buffer_.fill('\0');
92 assets_buffer_.fill('\0');
93 build_buffer_.fill('\0');
94 script_buffer_.fill('\0');
95 return;
96 }
97
98 CopyToBuffer(&name_buffer_, project_->name);
99 CopyToBuffer(&author_buffer_, project_->metadata.author);
101 CopyToBuffer(&code_buffer_, project_->code_folder);
102 CopyToBuffer(&assets_buffer_, project_->assets_folder);
103 CopyToBuffer(&build_buffer_, project_->build_target);
104 CopyToBuffer(&script_buffer_, project_->build_script);
105}
106
109 return absl::FailedPreconditionError("Project save callback unavailable");
110 }
111 auto status = save_project_callback_();
112 if (status.ok()) {
113 project_dirty_ = false;
114 }
115 return status;
116}
117
119 if (!project_) {
120 ImGui::TextDisabled(tr("No project loaded"));
121 ImGui::Spacing();
122 ImGui::TextWrapped(
123 tr("Open a .yaze project file or create a new project to access "
124 "project management features."));
125 return;
126 }
127
129 ImGui::Separator();
131 ImGui::Separator();
133 ImGui::Separator();
135 ImGui::Separator();
137}
138
140 // Section header
143 ImGui::Spacing();
144
145 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Project Format:"));
146 ImGui::SameLine();
148 ? ".yaze"
149 : ".zsproj");
150
151 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Project YAZE Version:"));
152 ImGui::SameLine();
153 const std::string& project_version = project_->metadata.yaze_version;
154 if (project_version.empty()) {
155 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
156 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning), tr("Unknown"));
157 } else if (project_version != YAZE_VERSION_STRING) {
158 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
159 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning), "%s",
160 project_version.c_str());
161 if (ImGui::IsItemHovered()) {
162 ImGui::SetTooltip(tr("Project saved with v%s; running v%s"),
163 project_version.c_str(), YAZE_VERSION_STRING);
164 }
165 } else {
166 ImGui::Text("%s", project_version.c_str());
167 }
168
169 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Running YAZE:"));
170 ImGui::SameLine();
171 ImGui::Text("%s", YAZE_VERSION_STRING);
172
173 // Project file path (read-only, click to copy)
174 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Path:"));
175 ImGui::SameLine();
176 const bool has_project_path = !project_->filepath.empty();
177 ImGui::BeginDisabled(!has_project_path);
178 const std::string project_path_label =
179 has_project_path ? project_->filepath + "##project_path"
180 : std::string("(no project file)##project_path");
181 if (ImGui::Selectable(project_path_label.c_str(), false,
182 ImGuiSelectableFlags_None,
183 ImVec2(ImGui::GetContentRegionAvail().x, 0)) &&
184 has_project_path) {
185 ImGui::SetClipboardText(project_->filepath.c_str());
186 if (toast_manager_) {
187 toast_manager_->Show("Path copied to clipboard", ToastType::kInfo);
188 }
189 }
190 if (has_project_path && ImGui::IsItemHovered()) {
191 ImGui::SetTooltip(tr("Click to copy path"));
192 }
193 ImGui::EndDisabled();
194
195 ImGui::Spacing();
196
197 // Editable Project Name
198 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Project Name:"));
199 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
200 if (ImGui::InputText("##project_name", name_buffer_.data(),
201 name_buffer_.size())) {
202 project_->name = name_buffer_.data();
203 project_dirty_ = true;
204 }
205
206 // Editable Author
207 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Author:"));
208 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
209 if (ImGui::InputText("##author", author_buffer_.data(),
210 author_buffer_.size())) {
212 project_dirty_ = true;
213 }
214
215 // Editable Description
216 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Description:"));
217 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
218 if (ImGui::InputTextMultiline("##description", description_buffer_.data(),
219 description_buffer_.size(), ImVec2(0, 60))) {
221 project_dirty_ = true;
222 }
223
224 ImGui::Spacing();
225}
226
229 ImGui::Spacing();
230
231 ImGui::TextWrapped(tr(
232 "Primary data lives under the .yaze root. Click any path to copy it."));
233 ImGui::Spacing();
234
236 if (!app_root.ok()) {
237 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
238 ImGui::TextColored(gui::ConvertColorToImVec4(theme.error),
239 tr("Storage unavailable: %s"),
240 std::string(app_root.status().message()).c_str());
241 return;
242 }
243
244 std::vector<std::pair<const char*, std::filesystem::path>> locations = {
245 {"Root", *app_root},
246 {"Projects", *app_root / "projects"},
247 {"Layouts", *app_root / "layouts"},
248 {"Workspaces", *app_root / "workspaces"},
249 {"Logs", *app_root / "logs"},
250 {"Agent", *app_root / "agent"}};
251
252 auto temp_root = util::PlatformPaths::GetTempDirectory();
253 if (temp_root.ok()) {
254 locations.emplace_back("Temp", *temp_root);
255 }
256
257 if (ImGui::BeginTable("##storage_locations", 2,
258 ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV |
259 ImGuiTableFlags_SizingStretchProp)) {
260 ImGui::TableSetupColumn("Location", ImGuiTableColumnFlags_WidthFixed,
261 110.0f);
262 ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthStretch);
263 for (const auto& entry : locations) {
264 ImGui::TableNextRow();
265 ImGui::TableNextColumn();
266 ImGui::TextColored(gui::GetTextSecondaryVec4(), "%s", entry.first);
267 ImGui::TableNextColumn();
268 const std::string display_path =
270 ImGui::PushID(entry.first);
271 if (ImGui::Selectable(display_path.c_str(), false,
272 ImGuiSelectableFlags_SpanAllColumns)) {
273 ImGui::SetClipboardText(display_path.c_str());
274 if (toast_manager_) {
275 toast_manager_->Show("Path copied to clipboard", ToastType::kInfo);
276 }
277 }
278 if (ImGui::IsItemHovered()) {
279 ImGui::SetTooltip(tr("Click to copy"));
280 }
281 ImGui::PopID();
282 }
283 ImGui::EndTable();
284 }
285
286 ImGui::Spacing();
287}
288
291 ImGui::Spacing();
292
293 // Current ROM
294 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Current ROM:"));
295 if (project_->rom_filename.empty()) {
296 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
297 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning),
298 tr("Not configured"));
299 } else {
300 // Show just the filename, full path on hover
301 std::string filename = project_->rom_filename;
302 size_t pos = filename.find_last_of("/\\");
303 if (pos != std::string::npos) {
304 filename = filename.substr(pos + 1);
305 }
306 ImGui::Text("%s", filename.c_str());
307 if (ImGui::IsItemHovered()) {
308 ImGui::SetTooltip("%s", project_->rom_filename.c_str());
309 }
310 }
311
312 // ROM status
313 if (rom_ && rom_->is_loaded()) {
314 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Title:"));
315 ImGui::SameLine();
316 ImGui::Text("%s", rom_->title().c_str());
317
318 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Size:"));
319 ImGui::SameLine();
320 ImGui::Text(tr("%.2f MB"),
321 static_cast<float>(rom_->size()) / (1024 * 1024));
322
323 if (rom_->dirty()) {
324 const auto& theme2 = gui::ThemeManager::Get().GetCurrentTheme();
325 ImGui::TextColored(gui::ConvertColorToImVec4(theme2.warning),
326 tr("%s Unsaved changes"), ICON_MD_WARNING);
327 }
328 }
329
330 ImGui::Spacing();
331
332 // Action buttons
333 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
334
335 if (ImGui::Button(ICON_MD_SWAP_HORIZ " Swap ROM", ImVec2(button_width, 0))) {
336 if (swap_rom_callback_) {
338 }
339 }
340 if (ImGui::IsItemHovered()) {
341 ImGui::SetTooltip(tr("Replace the ROM file for this project"));
342 }
343
344 ImGui::SameLine();
345
346 if (ImGui::Button(ICON_MD_REFRESH " Reload", ImVec2(button_width, 0))) {
349 }
350 }
351 if (ImGui::IsItemHovered()) {
352 ImGui::SetTooltip(tr("Reload ROM from disk"));
353 }
354
355 ImGui::Spacing();
356}
357
359 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s Version Control",
361 ImGui::Spacing();
362
363 if (!version_manager_) {
364 ImGui::TextDisabled(tr("Version manager not available"));
365 return;
366 }
367
368 bool git_initialized = version_manager_->IsGitInitialized();
369
370 if (!git_initialized) {
371 ImGui::TextWrapped(
372 tr("Git is not initialized for this project. Initialize Git to enable "
373 "version control and snapshots."));
374 ImGui::Spacing();
375
376 if (ImGui::Button(ICON_MD_ADD " Initialize Git",
377 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
378 auto status = version_manager_->InitializeGit();
379 if (status.ok()) {
380 if (toast_manager_) {
381 toast_manager_->Show("Git repository initialized",
383 }
384 } else {
385 if (toast_manager_) {
387 absl::StrFormat("Failed to initialize Git: %s", status.message()),
389 }
390 }
391 }
392 return;
393 }
394
395 // Show current commit
396 std::string current_hash = version_manager_->GetCurrentHash();
397 if (!current_hash.empty()) {
398 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Current:"));
399 ImGui::SameLine();
400 ImGui::Text("%s", current_hash.substr(0, 7).c_str());
401 }
402
403 ImGui::Spacing();
404
405 // Create snapshot section
406 ImGui::Text(tr("Create Snapshot:"));
407 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
408 ImGui::InputTextWithHint("##snapshot_msg", "Snapshot message...",
410
411 if (ImGui::Button(ICON_MD_CAMERA_ALT " Create Snapshot",
412 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
413 std::string msg =
414 snapshot_message_[0] ? snapshot_message_ : "Manual snapshot";
415 auto result = version_manager_->CreateSnapshot(msg);
416 if (result.ok()) {
417 if (toast_manager_) {
419 absl::StrFormat("Snapshot created: %s", result->commit_hash),
421 }
422 snapshot_message_[0] = '\0';
423 history_dirty_ = true;
424 } else {
425 if (toast_manager_) {
427 absl::StrFormat("Snapshot failed: %s", result.status().message()),
429 }
430 }
431 }
432 if (ImGui::IsItemHovered()) {
433 ImGui::SetTooltip(
434 tr("Create a snapshot of your project (Git commit + ROM backup)"));
435 }
436
437 // Show recent history
439}
440
443 return;
444 }
445
446 ImGui::Spacing();
447 if (ImGui::CollapsingHeader(ICON_MD_LIST " Recent Snapshots",
448 ImGuiTreeNodeFlags_DefaultOpen)) {
449 // Refresh history if needed
450 if (history_dirty_) {
452 history_dirty_ = false;
453 }
454
455 if (history_cache_.empty()) {
456 ImGui::TextDisabled(tr("No snapshots yet"));
457 } else {
458 for (const auto& entry : history_cache_) {
459 // Format: "hash message"
460 size_t space_pos = entry.find(' ');
461 std::string hash =
462 space_pos != std::string::npos ? entry.substr(0, 7) : entry;
463 std::string message =
464 space_pos != std::string::npos ? entry.substr(space_pos + 1) : "";
465
467 ImGui::SameLine();
468 ImGui::TextWrapped("%s", message.c_str());
469 }
470 }
471 }
472}
473
475 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s Quick Actions", ICON_MD_BOLT);
476 ImGui::Spacing();
477
478 float button_width = ImGui::GetContentRegionAvail().x;
479
480 // Show unsaved indicator
481 if (project_dirty_) {
482 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
483 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning),
484 tr("%s Project has unsaved changes"), ICON_MD_EDIT);
485 ImGui::Spacing();
486 }
487
488 if (ImGui::Button(ICON_MD_SAVE " Save Project", ImVec2(button_width, 0))) {
489 (void)SaveProjectEdits();
490 }
491
492 if (ImGui::Button(ICON_MD_BUILD " Build Project", ImVec2(button_width, 0))) {
495 }
496 }
497
500 if (ImGui::Button(ICON_MD_CANCEL " Cancel Build",
501 ImVec2(button_width, 0))) {
503 }
504 }
505
506 if (ImGui::Button(ICON_MD_PLAY_ARROW " Run Project Output",
507 ImVec2(button_width, 0))) {
510 }
511 }
512
514 ImGui::Spacing();
517 DrawWorkflowCard(build_status_, ICON_MD_BUILD);
518 }
519 if (!build_log_output_.empty()) {
520 ImGui::Spacing();
521 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Build Output:"));
522 ImGui::BeginChild("##build_output_log", ImVec2(0, 140), true,
523 ImGuiWindowFlags_HorizontalScrollbar);
524 ImGui::TextUnformatted(build_log_output_.c_str());
525 if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
526 ImGui::SetScrollHereY(1.0f);
527 }
528 ImGui::EndChild();
529 }
530 if (run_status_.visible) {
531 DrawWorkflowCard(run_status_, ICON_MD_PLAY_ARROW);
532 }
533 ImGui::Spacing();
534 }
535
536 ImGui::Spacing();
537
538 // Editable Code folder
539 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Code Folder:"));
540 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 32);
541 if (ImGui::InputText("##code_folder", code_buffer_.data(),
542 code_buffer_.size())) {
544 project_dirty_ = true;
545 }
546 ImGui::SameLine();
547 if (ImGui::Button(ICON_MD_FOLDER_OPEN "##browse_code")) {
550 }
551 }
552 if (ImGui::IsItemHovered()) {
553 ImGui::SetTooltip(tr("Browse for code folder"));
554 }
555
556 // Editable Assets folder
557 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Assets Folder:"));
558 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 32);
559 if (ImGui::InputText("##assets_folder", assets_buffer_.data(),
560 assets_buffer_.size())) {
562 project_dirty_ = true;
563 }
564 ImGui::SameLine();
565 if (ImGui::Button(ICON_MD_FOLDER_OPEN "##browse_assets")) {
567 browse_folder_callback_("assets");
568 }
569 }
570 if (ImGui::IsItemHovered()) {
571 ImGui::SetTooltip(tr("Browse for assets folder"));
572 }
573
574 // Editable Build target
575 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Build Target:"));
576 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
577 if (ImGui::InputText("##build_target", build_buffer_.data(),
578 build_buffer_.size())) {
580 project_dirty_ = true;
581 }
582
583 // Build script
584 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Build Script:"));
585 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
586 if (ImGui::InputText("##build_script", script_buffer_.data(),
587 script_buffer_.size())) {
589 project_dirty_ = true;
590 }
591}
592
593} // namespace editor
594} // namespace yaze
auto size() const
Definition rom.h:200
bool dirty() const
Definition rom.h:188
bool is_loaded() const
Definition rom.h:187
auto title() const
Definition rom.h:199
std::vector< std::string > GetHistory(int limit=10) const
std::string GetCurrentHash() const
absl::StatusOr< SnapshotResult > CreateSnapshot(const std::string &message)
void SetProject(project::YazeProject *project, bool dirty=false)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
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)
void CopyToBuffer(std::array< char, N > *buffer, const std::string &value)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:140
void ColoredText(const char *text, const ImVec4 &color)
ImVec4 GetPrimaryVec4()
ImVec4 GetTextSecondaryVec4()
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
Modern project structure with comprehensive settings consolidation.
Definition project.h:287
ProjectMetadata metadata
Definition project.h:299
std::string assets_folder
Definition project.h:312