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 if (ImGui::Selectable(project_->filepath.c_str(), false,
177 ImGuiSelectableFlags_None,
178 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
179 ImGui::SetClipboardText(project_->filepath.c_str());
180 if (toast_manager_) {
181 toast_manager_->Show("Path copied to clipboard", ToastType::kInfo);
182 }
183 }
184 if (ImGui::IsItemHovered()) {
185 ImGui::SetTooltip(tr("Click to copy path"));
186 }
187
188 ImGui::Spacing();
189
190 // Editable Project Name
191 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Project Name:"));
192 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
193 if (ImGui::InputText("##project_name", name_buffer_.data(),
194 name_buffer_.size())) {
195 project_->name = name_buffer_.data();
196 project_dirty_ = true;
197 }
198
199 // Editable Author
200 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Author:"));
201 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
202 if (ImGui::InputText("##author", author_buffer_.data(),
203 author_buffer_.size())) {
205 project_dirty_ = true;
206 }
207
208 // Editable Description
209 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Description:"));
210 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
211 if (ImGui::InputTextMultiline("##description", description_buffer_.data(),
212 description_buffer_.size(), ImVec2(0, 60))) {
214 project_dirty_ = true;
215 }
216
217 ImGui::Spacing();
218}
219
222 ImGui::Spacing();
223
224 ImGui::TextWrapped(tr(
225 "Primary data lives under the .yaze root. Click any path to copy it."));
226 ImGui::Spacing();
227
229 if (!app_root.ok()) {
230 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
231 ImGui::TextColored(gui::ConvertColorToImVec4(theme.error),
232 tr("Storage unavailable: %s"),
233 std::string(app_root.status().message()).c_str());
234 return;
235 }
236
237 std::vector<std::pair<const char*, std::filesystem::path>> locations = {
238 {"Root", *app_root},
239 {"Projects", *app_root / "projects"},
240 {"Layouts", *app_root / "layouts"},
241 {"Workspaces", *app_root / "workspaces"},
242 {"Logs", *app_root / "logs"},
243 {"Agent", *app_root / "agent"}};
244
245 auto temp_root = util::PlatformPaths::GetTempDirectory();
246 if (temp_root.ok()) {
247 locations.emplace_back("Temp", *temp_root);
248 }
249
250 if (ImGui::BeginTable("##storage_locations", 2,
251 ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV |
252 ImGuiTableFlags_SizingStretchProp)) {
253 ImGui::TableSetupColumn("Location", ImGuiTableColumnFlags_WidthFixed,
254 110.0f);
255 ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthStretch);
256 for (const auto& entry : locations) {
257 ImGui::TableNextRow();
258 ImGui::TableNextColumn();
259 ImGui::TextColored(gui::GetTextSecondaryVec4(), "%s", entry.first);
260 ImGui::TableNextColumn();
261 const std::string display_path =
263 ImGui::PushID(entry.first);
264 if (ImGui::Selectable(display_path.c_str(), false,
265 ImGuiSelectableFlags_SpanAllColumns)) {
266 ImGui::SetClipboardText(display_path.c_str());
267 if (toast_manager_) {
268 toast_manager_->Show("Path copied to clipboard", ToastType::kInfo);
269 }
270 }
271 if (ImGui::IsItemHovered()) {
272 ImGui::SetTooltip(tr("Click to copy"));
273 }
274 ImGui::PopID();
275 }
276 ImGui::EndTable();
277 }
278
279 ImGui::Spacing();
280}
281
284 ImGui::Spacing();
285
286 // Current ROM
287 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Current ROM:"));
288 if (project_->rom_filename.empty()) {
289 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
290 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning),
291 tr("Not configured"));
292 } else {
293 // Show just the filename, full path on hover
294 std::string filename = project_->rom_filename;
295 size_t pos = filename.find_last_of("/\\");
296 if (pos != std::string::npos) {
297 filename = filename.substr(pos + 1);
298 }
299 ImGui::Text("%s", filename.c_str());
300 if (ImGui::IsItemHovered()) {
301 ImGui::SetTooltip("%s", project_->rom_filename.c_str());
302 }
303 }
304
305 // ROM status
306 if (rom_ && rom_->is_loaded()) {
307 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Title:"));
308 ImGui::SameLine();
309 ImGui::Text("%s", rom_->title().c_str());
310
311 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Size:"));
312 ImGui::SameLine();
313 ImGui::Text(tr("%.2f MB"),
314 static_cast<float>(rom_->size()) / (1024 * 1024));
315
316 if (rom_->dirty()) {
317 const auto& theme2 = gui::ThemeManager::Get().GetCurrentTheme();
318 ImGui::TextColored(gui::ConvertColorToImVec4(theme2.warning),
319 tr("%s Unsaved changes"), ICON_MD_WARNING);
320 }
321 }
322
323 ImGui::Spacing();
324
325 // Action buttons
326 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
327
328 if (ImGui::Button(ICON_MD_SWAP_HORIZ " Swap ROM", ImVec2(button_width, 0))) {
329 if (swap_rom_callback_) {
331 }
332 }
333 if (ImGui::IsItemHovered()) {
334 ImGui::SetTooltip(tr("Replace the ROM file for this project"));
335 }
336
337 ImGui::SameLine();
338
339 if (ImGui::Button(ICON_MD_REFRESH " Reload", ImVec2(button_width, 0))) {
342 }
343 }
344 if (ImGui::IsItemHovered()) {
345 ImGui::SetTooltip(tr("Reload ROM from disk"));
346 }
347
348 ImGui::Spacing();
349}
350
352 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s Version Control",
354 ImGui::Spacing();
355
356 if (!version_manager_) {
357 ImGui::TextDisabled(tr("Version manager not available"));
358 return;
359 }
360
361 bool git_initialized = version_manager_->IsGitInitialized();
362
363 if (!git_initialized) {
364 ImGui::TextWrapped(
365 tr("Git is not initialized for this project. Initialize Git to enable "
366 "version control and snapshots."));
367 ImGui::Spacing();
368
369 if (ImGui::Button(ICON_MD_ADD " Initialize Git",
370 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
371 auto status = version_manager_->InitializeGit();
372 if (status.ok()) {
373 if (toast_manager_) {
374 toast_manager_->Show("Git repository initialized",
376 }
377 } else {
378 if (toast_manager_) {
380 absl::StrFormat("Failed to initialize Git: %s", status.message()),
382 }
383 }
384 }
385 return;
386 }
387
388 // Show current commit
389 std::string current_hash = version_manager_->GetCurrentHash();
390 if (!current_hash.empty()) {
391 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Current:"));
392 ImGui::SameLine();
393 ImGui::Text("%s", current_hash.substr(0, 7).c_str());
394 }
395
396 ImGui::Spacing();
397
398 // Create snapshot section
399 ImGui::Text(tr("Create Snapshot:"));
400 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
401 ImGui::InputTextWithHint("##snapshot_msg", "Snapshot message...",
403
404 if (ImGui::Button(ICON_MD_CAMERA_ALT " Create Snapshot",
405 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
406 std::string msg =
407 snapshot_message_[0] ? snapshot_message_ : "Manual snapshot";
408 auto result = version_manager_->CreateSnapshot(msg);
409 if (result.ok()) {
410 if (toast_manager_) {
412 absl::StrFormat("Snapshot created: %s", result->commit_hash),
414 }
415 snapshot_message_[0] = '\0';
416 history_dirty_ = true;
417 } else {
418 if (toast_manager_) {
420 absl::StrFormat("Snapshot failed: %s", result.status().message()),
422 }
423 }
424 }
425 if (ImGui::IsItemHovered()) {
426 ImGui::SetTooltip(
427 tr("Create a snapshot of your project (Git commit + ROM backup)"));
428 }
429
430 // Show recent history
432}
433
436 return;
437 }
438
439 ImGui::Spacing();
440 if (ImGui::CollapsingHeader(ICON_MD_LIST " Recent Snapshots",
441 ImGuiTreeNodeFlags_DefaultOpen)) {
442 // Refresh history if needed
443 if (history_dirty_) {
445 history_dirty_ = false;
446 }
447
448 if (history_cache_.empty()) {
449 ImGui::TextDisabled(tr("No snapshots yet"));
450 } else {
451 for (const auto& entry : history_cache_) {
452 // Format: "hash message"
453 size_t space_pos = entry.find(' ');
454 std::string hash =
455 space_pos != std::string::npos ? entry.substr(0, 7) : entry;
456 std::string message =
457 space_pos != std::string::npos ? entry.substr(space_pos + 1) : "";
458
460 ImGui::SameLine();
461 ImGui::TextWrapped("%s", message.c_str());
462 }
463 }
464 }
465}
466
468 gui::ColoredTextF(gui::GetPrimaryVec4(), "%s Quick Actions", ICON_MD_BOLT);
469 ImGui::Spacing();
470
471 float button_width = ImGui::GetContentRegionAvail().x;
472
473 // Show unsaved indicator
474 if (project_dirty_) {
475 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
476 ImGui::TextColored(gui::ConvertColorToImVec4(theme.warning),
477 tr("%s Project has unsaved changes"), ICON_MD_EDIT);
478 ImGui::Spacing();
479 }
480
481 if (ImGui::Button(ICON_MD_SAVE " Save Project", ImVec2(button_width, 0))) {
482 (void)SaveProjectEdits();
483 }
484
485 if (ImGui::Button(ICON_MD_BUILD " Build Project", ImVec2(button_width, 0))) {
488 }
489 }
490
493 if (ImGui::Button(ICON_MD_CANCEL " Cancel Build",
494 ImVec2(button_width, 0))) {
496 }
497 }
498
499 if (ImGui::Button(ICON_MD_PLAY_ARROW " Run Project Output",
500 ImVec2(button_width, 0))) {
503 }
504 }
505
507 ImGui::Spacing();
510 DrawWorkflowCard(build_status_, ICON_MD_BUILD);
511 }
512 if (!build_log_output_.empty()) {
513 ImGui::Spacing();
514 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Build Output:"));
515 ImGui::BeginChild("##build_output_log", ImVec2(0, 140), true,
516 ImGuiWindowFlags_HorizontalScrollbar);
517 ImGui::TextUnformatted(build_log_output_.c_str());
518 if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
519 ImGui::SetScrollHereY(1.0f);
520 }
521 ImGui::EndChild();
522 }
523 if (run_status_.visible) {
524 DrawWorkflowCard(run_status_, ICON_MD_PLAY_ARROW);
525 }
526 ImGui::Spacing();
527 }
528
529 ImGui::Spacing();
530
531 // Editable Code folder
532 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Code Folder:"));
533 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 32);
534 if (ImGui::InputText("##code_folder", code_buffer_.data(),
535 code_buffer_.size())) {
537 project_dirty_ = true;
538 }
539 ImGui::SameLine();
540 if (ImGui::Button(ICON_MD_FOLDER_OPEN "##browse_code")) {
543 }
544 }
545 if (ImGui::IsItemHovered()) {
546 ImGui::SetTooltip(tr("Browse for code folder"));
547 }
548
549 // Editable Assets folder
550 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Assets Folder:"));
551 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 32);
552 if (ImGui::InputText("##assets_folder", assets_buffer_.data(),
553 assets_buffer_.size())) {
555 project_dirty_ = true;
556 }
557 ImGui::SameLine();
558 if (ImGui::Button(ICON_MD_FOLDER_OPEN "##browse_assets")) {
560 browse_folder_callback_("assets");
561 }
562 }
563 if (ImGui::IsItemHovered()) {
564 ImGui::SetTooltip(tr("Browse for assets folder"));
565 }
566
567 // Editable Build target
568 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Build Target:"));
569 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
570 if (ImGui::InputText("##build_target", build_buffer_.data(),
571 build_buffer_.size())) {
573 project_dirty_ = true;
574 }
575
576 // Build script
577 ImGui::TextColored(gui::GetTextSecondaryVec4(), tr("Build Script:"));
578 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
579 if (ImGui::InputText("##build_script", script_buffer_.data(),
580 script_buffer_.size())) {
582 project_dirty_ = true;
583 }
584}
585
586} // namespace editor
587} // namespace yaze
auto size() const
Definition rom.h:150
bool dirty() const
Definition rom.h:145
bool is_loaded() const
Definition rom.h:144
auto title() const
Definition rom.h:149
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)
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:134
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:172
ProjectMetadata metadata
Definition project.h:174
std::string assets_folder
Definition project.h:187