5#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
19 using Apply = std::function<absl::Status(uint8_t, uint8_t)>;
21 uint8_t before, uint8_t after,
Apply apply)
22 : description_(absl::StrFormat(
"%s graphics group %02X slot %d", kind,
26 apply_(std::move(apply)) {}
27 absl::Status
Undo()
override {
return apply_(after_, before_); }
28 absl::Status
Redo()
override {
return apply_(before_, after_); }
29 std::string
Description()
const override {
return description_; }
39void DrawIds(
const char* label,
const std::vector<int>& ids,
int digits) {
40 ImGui::TextWrapped(
"%s (%zu)", label, ids.size());
42 for (
const int id : ids) {
45 text += absl::StrFormat(
"%0*X", digits,
id);
47 ImGui::TextWrapped(
"%s", text.empty() ?
"None" : text.c_str());
115 int slot,
int sheet) {
118 return absl::FailedPreconditionError(
"No editable graphics session");
123 if (group < 0 || group >= groups || slot < 0 || slot >= (roomset ? 4 : 8) ||
125 return absl::InvalidArgumentError(
126 "Choose a valid graphics group, slot and sheet");
128 if (!(roomset && sheet == 0) &&
132 return absl::InvalidArgumentError(
133 "Backgrounds require a loaded 3bpp sheet");
137 const auto after =
static_cast<uint8_t
>(sheet);
139 return absl::OkStatus();
142 roomset](uint8_t expected, uint8_t value) -> absl::Status {
143 const auto session = weak.lock();
144 if (!session || session->generation != generation || !session->rom ||
145 !session->rom->is_loaded() || !session->data ||
146 !session->data->gfx_groups_loaded) {
147 return absl::FailedPreconditionError(
148 "Graphics assignment belongs to another session");
150 auto& assigned = roomset ? session->data->room_blockset_ids[group][slot]
151 : session->data->main_blockset_ids[group][slot];
152 if (assigned != expected) {
153 return absl::FailedPreconditionError(
154 "Graphics assignment changed elsewhere; Undo was not applied");
159 session->rom->set_dirty(
true);
160 return absl::OkStatus();
163 undo_->
Push(std::make_unique<GraphicsSheetAssignmentAction>(
164 roomset ?
"Roomset" :
"Main", group, roomset ? slot + 3 : slot, before,
165 after, std::move(apply)));
166 return absl::OkStatus();
171 ImGui::TextDisabled(
"Main graphics usage unavailable.");
176 ImGui::TextWrapped(
"Usage unavailable: %s",
177 std::string(usage.status().message()).c_str());
180 const auto room_count = usage->explicit_preview_rooms.size() +
181 usage->direct_default_rooms.size() +
182 usage->inferred_default_rooms.size() +
183 usage->fallback_rooms.size();
184 ImGui::TextWrapped(
"Affects %zu room previews and %zu map screens",
185 room_count, usage->ow_areas.size());
187 "Rooms use current/default entrance previews. This is not a census of "
188 "all in-game entrance routes.");
189 if (usage->slot7_bottom_half_only) {
191 "Map slot 7: bottom half only. The animated top half has a separate "
194 if (usage->unscanned_rooms || usage->unscanned_ow_areas) {
195 ImGui::TextWrapped(
"Incomplete scan: %d rooms / %d maps unavailable.",
196 usage->unscanned_rooms, usage->unscanned_ow_areas);
198 if (ImGui::TreeNode(
"Main assignment details")) {
200 "%s", usage->uses_live_overworld
201 ?
"Includes unsaved room and map selections."
202 :
"Includes unsaved rooms; maps read from ROM until loaded.");
203 if (ImGui::BeginChild(
"##MainUsage", ImVec2(0, 150),
true)) {
204 DrawIds(
"Explicit room previews", usage->explicit_preview_rooms, 3);
205 DrawIds(
"Direct entrance defaults", usage->direct_default_rooms, 3);
206 DrawIds(
"Inferred entrance defaults", usage->inferred_default_rooms, 3);
207 DrawIds(
"Fallback previews", usage->fallback_rooms, 3);
208 DrawIds(
"Room previews overridden in this slot", usage->overridden_rooms,
210 DrawIds(
"Affected map screens", usage->ow_areas, 2);
211 DrawIds(
"Overridden map slots", usage->overridden_ow_areas, 2);
212 DrawIds(
"Maps using an independent table", usage->independent_ow_areas,
222 ImGui::TextDisabled(
"Roomset usage unavailable.");
227 ImGui::TextWrapped(
"Usage unavailable: %s",
228 std::string(usage.status().message()).c_str());
231 ImGui::TextWrapped(
"Shared by %zu rooms and %zu map screens",
232 usage->rooms.size(), usage->ow_areas.size());
233 if (usage->unscanned_rooms || usage->unscanned_ow_areas) {
234 ImGui::TextWrapped(
"Incomplete scan: %d rooms / %d maps unavailable.",
235 usage->unscanned_rooms, usage->unscanned_ow_areas);
237 if (ImGui::TreeNode(
"Roomset assignment details")) {
239 "%s", usage->uses_live_overworld
240 ?
"Includes unsaved room and map selections."
241 :
"Includes unsaved rooms; maps read from ROM until loaded.");
242 if (ImGui::BeginChild(
"##RoomsetUsage", ImVec2(0, 150),
true)) {
243 DrawIds(
"Rooms", usage->rooms, 3);
244 DrawIds(
"Affected map screens", usage->ow_areas, 2);
245 DrawIds(
"Maps using custom sheets", usage->overridden_ow_areas, 2);
246 DrawIds(
"Maps using an independent table", usage->independent_ow_areas,
263 for (
int slot = 0; slot < (roomset ? 4 : 8); ++slot) {
264 const int display_slot = roomset ? slot + 3 : slot;
265 const auto label = roomset && entries[slot] == 0
266 ? absl::StrFormat(
"Slot %d / Inherit", display_slot)
267 : absl::StrFormat(
"Slot %d / Sheet %02X",
268 display_slot, entries[slot]);
269 if (ImGui::Selectable(label.c_str(), selected == slot)) {
270 if (selected != slot)
276 absl::StrFormat(
"%s slot %d", roomset ?
"Roomset" :
"Main",
285 if (ImGui::Button(
"Choose replacement sheet")) {
293 ImGui::EndDisabled();
303 ImGui::TextWrapped(
"Preview uses the selected palette or slot default.");
306 "Inherit uses each room or map's main group. Its appearance can differ "
308 if (ImGui::Selectable(
"Inherit from main group (00)",
pending_sheet_ == 0))
312 ImGui::TextWrapped(
"Sheet 00 is a physical sheet, not inheritance.");
315 const char* apply_label =
316 roomset ?
"Apply to shared roomset" :
"Apply to shared main group";
317 if (ImGui::Button(apply_label)) {
323 "Assignment changed while choosing. Reopen the picker.";
334 ImGui::TextUnformatted(
"Selected: Inherit");
338 if (ImGui::BeginChild(
"##SheetPicker", ImVec2(0, 220),
true)) {
339 std::vector<int> candidates;
345 candidates.push_back(
id);
347 ImGuiListClipper clipper;
348 clipper.Begin(
static_cast<int>(candidates.size()), 54.0f);
349 while (clipper.Step()) {
350 for (
int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
351 const int id = candidates[row];
357 const auto position = ImGui::GetCursorScreenPos();
358 const auto label = absl::StrFormat(
"Sheet %02X##replacement",
id);
365 absl::StrFormat(
"%s sheet %02X", roomset ?
"Roomset" :
"Main", id));
366 if (preview && preview->texture()) {
367 ImGui::GetWindowDrawList()->AddImage(
368 (ImTextureID)preview->texture(),
369 ImVec2(position.x + 84, position.y + 8),
370 ImVec2(position.x + 212, position.y + 40), ImVec2(0, 0),
371 ImVec2(1, 1), ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
std::array< gfx::Bitmap, 4 > roomset_previews_
std::string assignment_error_
RoomsetUsageCallback roomset_usage_
absl::Status EditRoomsetGraphicsSheet(int group, int entry, int sheet)
absl::Status EditGraphicsSheet(GroupKind kind, int group, int slot, int sheet)
std::shared_ptr< AssignmentSession > assignment_session_
void DrawAssignmentControls(GroupKind kind)
MainUsageCallback main_usage_
std::optional< std::map< int, zelda3::OverworldAreaGfxInfo > > usage_areas_
zelda3::GameData * game_data_
GfxGroupWorkspaceState * workspace_
std::array< gfx::Bitmap, 4 > spriteset_previews_
absl::Status EditMainGraphicsSheet(int group, int slot, int sheet)
std::map< int, gfx::Bitmap > picker_previews_
GfxGroupWorkspaceState & Ws()
gfx::SnesPalette * current_palette_
std::optional< std::map< int, zelda3::RoomGfxInfo > > usage_rooms_
int selected_roomset_entry_
void DetachRuntimeContext()
std::array< gfx::Bitmap, 8 > blockset_previews_
uint8_t picker_expected_sheet_
void SetGameData(zelda3::GameData *data)
gfx::Bitmap * PrepareSheetPreview(int sheet_id, gfx::SheetRole role, gfx::Bitmap &preview)
Abstract base for all undoable actions (Command pattern)
void Push(std::unique_ptr< UndoAction > action)
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
absl::Status Undo() override
std::function< absl::Status(uint8_t, uint8_t)> Apply
absl::Status Redo() override
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
GraphicsSheetAssignmentAction(const char *kind, int group, int slot, uint8_t before, uint8_t after, Apply apply)
void RetireBitmap(Bitmap &bitmap)
Explicitly retire resources owned by a bitmap that is being erased.
bool HasSheet(uint16_t sheet) const
True when the buffer holds all 4096 bytes of sheet.
static constexpr uint16_t kSheetCount
Locates graphics sheets in the ROM and writes edited sheets back.
void DrawIds(const char *label, const std::vector< int > &ids, int digits)
Editors are the view controllers for the application.
SheetRole RoleForRoomsetSlot(int slot_index)
SheetRole RoleForBlocksetSlot(int slot_index)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
#define RETURN_IF_ERROR(expr)
uint8_t selected_blockset
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
GraphicsSheetStore sheet_store