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