yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
new_project_dialog.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <cstdio>
5#include <cstring>
6#include <filesystem>
7
8#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
13#include "util/file_util.h"
14
15namespace yaze {
16namespace editor {
17
18namespace {
19
21 const char* id;
22 const char* icon;
23 const char* name;
24 const char* use_when;
25 const char* what_changes;
26 int skill_level; // 1 beginner, 2 intermediate, 3 advanced
27};
28
29// Canonical visual catalog for guided project creation. Command-palette
30// actions use the same stable template IDs.
32 {"Vanilla ROM Hack", ICON_MD_COTTAGE, "Vanilla ROM Hack",
33 "Edit rooms, sprites, or graphics without custom code.",
34 "Creates a project on top of the ROM. The ROM itself is only modified "
35 "when you save edits inside the editors.",
36 1},
37 {"ZSCustomOverworld v3", ICON_MD_TERRAIN, "ZSCustomOverworld v3",
38 "Resize overworld areas and add custom map features.",
39 "Configures editor save flags for a ROM that already uses ZSO3. It does "
40 "not install the ZSO3 ASM patch.",
41 2},
42 {"ZSCustomOverworld v2", ICON_MD_MAP, "ZSCustomOverworld v2",
43 "Port an older hack that already uses ZSO v2.",
44 "Configures editor save flags for a ROM that already uses legacy ZSO2. "
45 "It does not install the patch.",
46 2},
47 {"Randomizer Compatible", ICON_MD_SHUFFLE, "Randomizer Compatible",
48 "Build a ROM that has to work with ALTTPR or similar.",
49 "Uses conservative save flags that avoid ASM hooks and overworld "
50 "remapping; validate the finished ROM with your target randomizer.",
51 3},
52};
53constexpr int kTemplateCount = sizeof(kTemplates) / sizeof(kTemplates[0]);
54
55int FindTemplateIndex(const std::string& name) {
56 if (name.empty())
57 return 0;
58 for (int i = 0; i < kTemplateCount; ++i) {
59 if (name == kTemplates[i].id)
60 return i;
61 }
62 return 0;
63}
64
65} // namespace
66
67void NewProjectDialog::Open(const std::string& initial_template) {
68 selected_template_ = FindTemplateIndex(initial_template);
69 // Seed the project name with the template name so users who don't care
70 // about naming get something reasonable by default — they can still edit.
71 std::snprintf(project_name_buffer_, sizeof(project_name_buffer_), "%s",
72 kTemplates[selected_template_].name);
73 rom_path_buffer_[0] = '\0';
74 status_message_.clear();
75 open_requested_ = true;
76 just_opened_ = true;
77}
78
80 open_requested_ = false;
81 just_opened_ = false;
82 status_message_.clear();
83}
84
85void NewProjectDialog::ApplyTemplateSelection(const std::string& name) {
86 const int new_index = FindTemplateIndex(name);
87 selected_template_ = new_index;
88}
89
91 if (!open_requested_)
92 return false;
93
94 constexpr const char* kPopupId = "##new_project_dialog";
95 if (just_opened_) {
96 ImGui::OpenPopup(kPopupId);
97 just_opened_ = false;
98 }
99
100 ImGui::SetNextWindowSize(ImVec2(560, 0), ImGuiCond_Appearing);
101 if (!ImGui::BeginPopupModal(kPopupId, nullptr,
102 ImGuiWindowFlags_AlwaysAutoResize |
103 ImGuiWindowFlags_NoSavedSettings)) {
104 // Popup closed externally (escape key inside ImGui's own machinery, etc.).
105 Reset();
106 return false;
107 }
108
109 const ImVec4 text_secondary = gui::GetTextSecondaryVec4();
110
111 ImGui::TextUnformatted(ICON_MD_ROCKET_LAUNCH " Start a new project");
112 {
113 gui::StyleColorGuard text_guard(ImGuiCol_Text, text_secondary);
114 ImGui::TextWrapped(
115 tr("Pick a template, point at the ROM it should build on, and give the "
116 "project a name. The project file will be saved beside the source "
117 "ROM."));
118 }
119 ImGui::Separator();
120 ImGui::Spacing();
121
122 // --- Template picker ------------------------------------------------------
123 ImGui::TextUnformatted(tr("Template"));
124 const char* combo_preview = kTemplates[selected_template_].name;
125 ImGui::SetNextItemWidth(-1);
126 if (ImGui::BeginCombo("##template_combo", combo_preview)) {
127 for (int i = 0; i < kTemplateCount; ++i) {
128 const bool selected = (i == selected_template_);
129 const std::string label =
130 absl::StrFormat("%s %s", kTemplates[i].icon, kTemplates[i].name);
131 if (ImGui::Selectable(label.c_str(), selected)) {
133 }
134 if (selected)
135 ImGui::SetItemDefaultFocus();
136 }
137 ImGui::EndCombo();
138 }
139
140 // Template description block — gives the user an at-a-glance sense of what
141 // the checkbox actually does, same copy as the welcome screen details.
142 const TemplateDescriptor& tmpl = kTemplates[selected_template_];
143 {
144 gui::StyleColorGuard text_guard(ImGuiCol_Text, text_secondary);
145 ImGui::TextWrapped(tr("%s Use when: %s"), ICON_MD_LIGHTBULB, tmpl.use_when);
146 ImGui::TextWrapped(tr("%s Effect: %s"), ICON_MD_EDIT, tmpl.what_changes);
147 const char* skill_tag = tmpl.skill_level == 1 ? "Beginner"
148 : tmpl.skill_level == 2 ? "Intermediate"
149 : "Advanced";
150 ImGui::TextWrapped(tr("%s Skill: %s"), ICON_MD_INFO, skill_tag);
151 }
152
153 ImGui::Spacing();
154
155 // --- ROM picker -----------------------------------------------------------
156 ImGui::TextUnformatted(tr("Source ROM"));
157 const float browse_width = ImGui::CalcTextSize(" Browse…").x +
158 ImGui::CalcTextSize(ICON_MD_FOLDER_OPEN).x +
159 ImGui::GetStyle().FramePadding.x * 2.0f + 8.0f;
160 ImGui::SetNextItemWidth(-browse_width - ImGui::GetStyle().ItemSpacing.x);
161 ImGui::InputText("##rom_path", rom_path_buffer_, sizeof(rom_path_buffer_));
162 ImGui::SameLine();
163 if (ImGui::Button(
164 absl::StrFormat("%s Browse…", ICON_MD_FOLDER_OPEN).c_str())) {
166 if (!picked.empty()) {
167 std::snprintf(rom_path_buffer_, sizeof(rom_path_buffer_), "%s",
168 picked.c_str());
169 status_message_.clear();
170 }
171 }
172
173 ImGui::Spacing();
174
175 // --- Project name ---------------------------------------------------------
176 ImGui::TextUnformatted(tr("Project name"));
177 ImGui::SetNextItemWidth(-1);
178 ImGui::InputText("##project_name", project_name_buffer_,
179 sizeof(project_name_buffer_));
180 {
181 gui::StyleColorGuard text_guard(ImGuiCol_Text, text_secondary);
182 ImGui::TextWrapped(tr(
183 "Used as the project display name and to derive the project filename. "
184 "Spaces are replaced with underscores."));
185 }
186
187 ImGui::Spacing();
188 ImGui::Separator();
189 ImGui::Spacing();
190
191 // --- Actions --------------------------------------------------------------
192 const std::string rom_path(rom_path_buffer_);
193 const std::string project_name(project_name_buffer_);
194 const bool rom_valid = !rom_path.empty() && std::filesystem::exists(rom_path);
195 const bool name_valid = !project_name.empty();
196 const bool can_create = rom_valid && name_valid;
197
198 if (!can_create)
199 ImGui::BeginDisabled();
200 if (ImGui::Button(ICON_MD_ROCKET_LAUNCH " Create project", ImVec2(180, 0))) {
201 const auto status =
203 rom_path, project_name)
204 : absl::FailedPreconditionError(
205 "Project creation callback is not configured");
206 if (!status.ok()) {
207 status_message_ = absl::StrFormat("Create failed: %s", status.message());
208 } else {
209 ImGui::CloseCurrentPopup();
210 Reset();
211 ImGui::EndPopup();
212 return false;
213 }
214 }
215 if (!can_create)
216 ImGui::EndDisabled();
217
218 ImGui::SameLine();
219 if (ImGui::Button(ICON_MD_CLOSE " Cancel", ImVec2(100, 0)) ||
220 ImGui::IsKeyPressed(ImGuiKey_Escape, /*repeat=*/false)) {
221 ImGui::CloseCurrentPopup();
222 Reset();
223 ImGui::EndPopup();
224 return false;
225 }
226
227 // Inline validation hint — more discoverable than a disabled button alone.
228 if (!can_create) {
229 ImGui::SameLine();
230 const ImVec4 warn = gui::ConvertColorToImVec4(
231 gui::ThemeManager::Get().GetCurrentTheme().warning);
232 gui::StyleColorGuard warn_guard(ImGuiCol_Text, warn);
233 if (!rom_valid && rom_path.empty()) {
234 ImGui::TextUnformatted(ICON_MD_WARNING " Pick a ROM to continue");
235 } else if (!rom_valid) {
236 ImGui::TextUnformatted(ICON_MD_WARNING " ROM path does not exist");
237 } else if (!name_valid) {
238 ImGui::TextUnformatted(ICON_MD_WARNING " Give the project a name");
239 }
240 } else if (!status_message_.empty()) {
241 ImGui::SameLine();
242 ImGui::TextUnformatted(status_message_.c_str());
243 }
244
245 ImGui::EndPopup();
246 return true;
247}
248
249} // namespace editor
250} // namespace yaze
void Open(const std::string &initial_template="")
void ApplyTemplateSelection(const std::string &name)
RAII guard for ImGui style colors.
Definition style_guard.h:27
static ThemeManager & Get()
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_ROCKET_LAUNCH
Definition icons.h:1612
#define ICON_MD_SHUFFLE
Definition icons.h:1738
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_LIGHTBULB
Definition icons.h:1083
#define ICON_MD_TERRAIN
Definition icons.h:1952
#define ICON_MD_MAP
Definition icons.h:1173
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_COTTAGE
Definition icons.h:480
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
ImVec4 GetTextSecondaryVec4()