53 constexpr const char* kPopup =
"Edit Project Graphics";
55 ImGui::OpenPopup(kPopup);
56 if (!ImGui::IsPopupOpen(kPopup))
58 const auto* viewport = ImGui::GetMainViewport();
59 ImGui::SetNextWindowSize(
60 ImVec2(std::min(720.0f, viewport->WorkSize.x * 0.9f),
61 std::min(740.0f, viewport->WorkSize.y * 0.9f)),
63 if (!ImGui::BeginPopupModal(kPopup))
68 ImGui::CloseCurrentPopup();
78 const auto show_status = [
this](
const absl::Status& status) {
82 const auto refresh_after_edit = [
this,
83 &show_status](
const absl::Status& status) {
88 "This draft belongs to another project. Return to its project to "
89 "edit or publish its graphics.");
96 "Publish graphics first, then Save ROM for palettes, definitions and "
97 "map placement. Build Project includes source assets in the game.");
99 if (ImGui::BeginChild(
"Resource",
100 ImVec2(0, -ImGui::GetFrameHeightWithSpacing()),
true)) {
101 ImGui::BeginDisabled(!bound);
102 const auto* resource = source.selected_resource();
105 ImGui::BeginCombo(
"Resource", resource->source_path.c_str())) {
106 for (
const auto& choice : source.resources()) {
107 ImGui::PushID(choice.id.c_str());
108 const std::string label =
109 choice.source_path + (choice.dirty() ?
" *" :
"");
110 if (ImGui::Selectable(label.c_str(), choice.id == resource->id)) {
111 show_status(source.SelectResource(choice.id));
118 ImGui::EndDisabled();
119 resource = source.selected_resource();
121 ImGui::TextWrapped(
"Content ID: %s", resource->id.c_str());
122 std::string consumers;
123 for (
int parent : resource->parent_maps)
124 consumers += absl::StrFormat(
"$%02X ", parent);
125 ImGui::TextWrapped(
"Affected parent maps: %s", consumers.c_str());
126 if (ImGui::TreeNode(
"Source slices")) {
127 for (
const auto& slice : resource->slices) {
129 "%s: resource tiles %zu..%zu -> BG characters $%03X..$%03X",
130 slice.profile_id.c_str(), slice.first_resource_tile(),
131 slice.first_resource_tile() + slice.byte_count / 32 - 1,
132 slice.first_character(),
133 slice.first_character() +
134 static_cast<int>(slice.byte_count / 32) - 1);
141 const auto color = [&](uint8_t index) {
143 if (map && at < map->current_palette().size())
145 const float value = index / 15.0f;
146 return ImVec4(value, value, value, 1);
148 for (
int index = 0; index < 16; ++index) {
149 ImGui::PushID(index);
151 ImGui::SameLine(0, 2);
152 if (ImGui::ColorButton(
"Index", color(index),
153 ImGuiColorEditFlags_NoTooltip, ImVec2(24, 24)))
155 if (ImGui::IsItemHovered())
156 ImGui::SetTooltip(
"Pixel index %d%s", index,
157 index == 0 ?
" (transparent)" :
"");
159 ImGui::GetWindowDrawList()->AddRect(
160 ImGui::GetItemRectMin(), ImGui::GetItemRectMax(),
161 IM_COL32(255, 255, 255, 255), 0.0f, 2.0f);
167 static_cast<int>(resource->tiles.size()) - 1);
169 static_cast<int>(resource->tiles.size()) - 1);
170 const auto draw_tile = [&](
int tile, ImVec2 origin,
float pixel_size) {
171 auto* list = ImGui::GetWindowDrawList();
172 for (
int y = 0; y < 8; ++y) {
173 for (
int x = 0; x < 8; ++x) {
174 const auto index = source.GetPixel(tile, x, y);
177 const ImVec2 pos(origin.x + x * pixel_size,
178 origin.y + y * pixel_size);
179 list->AddRectFilled(pos,
180 ImVec2(pos.x + pixel_size, pos.y + pixel_size),
181 ImGui::ColorConvertFloat4ToU32(color(*index)));
185 if (ImGui::BeginChild(
"Tiles", ImVec2(0, 136),
true)) {
186 const int columns = std::max(
187 1,
static_cast<int>(ImGui::GetContentRegionAvail().x / 28));
188 ImGuiListClipper clipper;
190 (
static_cast<int>(resource->tiles.size()) + columns - 1) / columns,
191 24 + ImGui::GetStyle().ItemSpacing.y);
192 while (clipper.Step()) {
193 for (
int row = clipper.DisplayStart; row < clipper.DisplayEnd;
195 for (
int column = 0; column < columns; ++column) {
196 const int tile = row * columns + column;
197 if (tile >=
static_cast<int>(resource->tiles.size()))
200 ImGui::SameLine(0, 4);
202 const auto origin = ImGui::GetCursorScreenPos();
203 if (ImGui::InvisibleButton(
"Tile", ImVec2(24, 24)))
205 draw_tile(tile, origin, 3);
207 ImGui::GetWindowDrawList()->AddRect(
208 origin, ImVec2(origin.x + 24, origin.y + 24),
209 IM_COL32(255, 255, 255, 255), 0.0f, 2.0f);
216 const auto origin = ImGui::GetCursorScreenPos();
218 ImGui::InvisibleButton(
"Paint resource tile", ImVec2(160, 160));
220 if (ImGui::IsItemHovered() &&
221 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
222 const auto mouse = ImGui::GetIO().MousePos;
223 refresh_after_edit(source.SetPixel(
225 static_cast<int>((mouse.y - origin.y) / 20),
228 ImGui::BeginDisabled(!source.can_undo());
229 if (ImGui::Button(
"Undo Pixel"))
230 refresh_after_edit(source.Undo());
231 ImGui::EndDisabled();
233 ImGui::BeginDisabled(!source.can_redo());
234 if (ImGui::Button(
"Redo Pixel"))
235 refresh_after_edit(source.Redo());
236 ImGui::EndDisabled();
237 ImGui::EndDisabled();
240 "Use the main Undo and Redo commands to revert or restore the "
241 "combined graphics import, palette, definitions and placement.");
242 ImGui::BeginDisabled(!source.dirty() || !source.backend_available());
243 if (ImGui::Button(
"Review Source Publish")) {
244 const auto preview = source.Preview();
245 show_status(preview.status());
247 ImGui::EndDisabled();
248 if (!source.backend_available()) {
249#if defined(_WIN32) || defined(__EMSCRIPTEN__)
251 "Source publication currently requires a native macOS or Linux "
252 "build with Python 3.");
255 "The bound source needs Scripts/Graphics/edit_graphics_source.py "
256 "before this draft can be published.");
259 if (source.has_preview()) {
260 const auto& plan = source.preview_json();
262 "New content ID: %s",
263 plan.value(
"after_resource_id", std::string()).c_str());
264 if (ImGui::TreeNode(
"Review files and fingerprints")) {
265 ImGui::TextWrapped(
"%s", plan.dump(2).c_str());
268 if (ImGui::Button(
"Publish Reviewed Source")) {
269 const auto published = source.Publish();
270 show_status(published.status());
271 if (published.ok()) {
276 show_status(absl::FailedPreconditionError(
277 "Source published; graphics reload needs attention: " +
278 std::string(reload.message())));
281 "Graphics published and reloaded. Save ROM to persist "
282 "palettes, definitions and placement, then Build Project "
283 "to include the source assets in the game.";
289 ImGui::EndDisabled();
292 if (ImGui::Button(
"Close (keep draft)")) {
294 ImGui::CloseCurrentPopup();