6#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
24 constexpr uint64_t kFnvPrime = 1099511628211ULL;
25 *fingerprint ^= value;
26 *fingerprint *= kFnvPrime;
30 const std::filesystem::path& rhs) {
34 std::error_code error;
35 const bool equivalent = std::filesystem::equivalent(lhs, rhs, error);
36 return !error && equivalent;
42 : renderer_(renderer), rom_(rom) {
43 tile_editor_ = std::make_unique<zelda3::ObjectTileEditor>(rom);
63 std::string message) {
98 return absl::UnimplementedError(
99 "Tile editing is not yet supported for this object layout");
101 if (rooms ==
nullptr) {
102 return absl::InvalidArgumentError(
103 "A dungeon room store is required to edit object tiles");
105 if (room_id < 0 || room_id >=
static_cast<int>(rooms->
size())) {
106 return absl::OutOfRangeError(
"Dungeon room id is outside the room store");
109 return absl::FailedPreconditionError(
110 "Apply, revert, or close the current Object Tile Editor session "
111 "before opening another object");
114 auto& room = (*rooms)[room_id];
116 tile_editor_->CaptureEditableObjectLayout(object_id, room, palette_group);
117 if (!layout_or.ok()) {
118 return layout_or.status();
131 return absl::OkStatus();
143 if (rooms ==
nullptr) {
144 return absl::InvalidArgumentError(
145 "A dungeon room store is required to edit custom object tiles");
147 if (room_id < 0 || room_id >=
static_cast<int>(rooms->
size())) {
148 return absl::OutOfRangeError(
"Dungeon room id is outside the room store");
151 return absl::FailedPreconditionError(
152 "Custom Objects is disabled for the current project");
155 return absl::FailedPreconditionError(
156 "Load the current dungeon room before editing custom object graphics");
159 return absl::FailedPreconditionError(
160 "Apply, revert, or explicitly discard the current Object Tile Editor "
161 "session before opening another custom object");
164 auto layout_or =
tile_editor_->LoadCustomObjectLayout(object_id, subtype);
165 if (!layout_or.ok()) {
166 return layout_or.status();
178 return absl::OkStatus();
240 return absl::FailedPreconditionError(
241 "Only an open custom-object session can add its first tile");
244 return absl::FailedPreconditionError(
245 "The custom-object layout already contains tiles");
255 return absl::FailedPreconditionError(
256 "Only an open custom-object session can add tiles; standard object "
261 return absl::OutOfRangeError(absl::StrFormat(
262 "Tile (%d, %d) is outside the %dx%d custom-object buffer; X and Y "
270 if (cell.rel_x == rel_x && cell.rel_y == rel_y) {
273 return absl::OkStatus();
277 return absl::FailedPreconditionError(
278 "Select a source tile before adding a custom-object tile");
294 candidate.
tiles.push_back(
299 if (!encoded_or.ok()) {
300 return encoded_or.status();
322 return absl::OkStatus();
328 return absl::FailedPreconditionError(
329 "Only an open custom-object session can move tiles; standard object "
334 return absl::FailedPreconditionError(
335 "Select a custom-object tile before moving it");
339 return absl::OutOfRangeError(absl::StrFormat(
340 "Tile (%d, %d) is outside the %dx%d custom-object buffer; X and Y "
346 if (selected.rel_x == rel_x && selected.rel_y == rel_y) {
347 return absl::OkStatus();
350 if (cell.rel_x == rel_x && cell.rel_y == rel_y) {
351 return absl::AlreadyExistsError(absl::StrFormat(
352 "Tile (%d, %d) is already occupied; move the selected tile to an "
366 candidate.
tiles.push_back({is_selected ? rel_x : cell.rel_x,
367 is_selected ? rel_y : cell.rel_y,
371 if (!encoded_or.ok()) {
372 return encoded_or.status();
378 cell.modified =
true;
387 return absl::OkStatus();
428absl::StatusOr<ObjectTileEditorPanel::SourceImpactSnapshot>
431 uint64_t fingerprint = 1469598103934665603ULL;
432 MixFingerprint(&fingerprint,
439 return absl::FailedPreconditionError(
440 "Custom object source-impact analysis requires its resolved asset "
445 uint64_t fingerprint = 1469598103934665603ULL;
446 MixFingerprint(&fingerprint, manager.asset_generation());
447 int consumer_count = 0;
448 std::vector<int> mapped_object_ids(
451 const auto manager_state = manager.SnapshotState();
452 for (
const auto& mapping : manager_state.custom_file_map) {
453 const int object_id = mapping.first;
454 if (std::find(mapped_object_ids.begin(), mapped_object_ids.end(),
455 object_id) == mapped_object_ids.end()) {
456 mapped_object_ids.push_back(object_id);
459 std::sort(mapped_object_ids.begin(), mapped_object_ids.end());
461 for (
const int object_id : mapped_object_ids) {
462 const int subtype_count = manager.GetSubtypeCount(object_id);
463 for (
int subtype = 0; subtype < subtype_count; ++subtype) {
464 const std::string filename =
465 manager.ResolveFilename(object_id, subtype);
466 if (filename.empty()) {
470 manager.GetBasePath(), filename);
471 if (!resolved_or.ok()) {
472 return absl::FailedPreconditionError(absl::StrFormat(
473 "Could not resolve custom runtime slot 0x%02X:%02X: %s",
474 object_id, subtype, resolved_or.status().message()));
476 if (!PathsReferToSameAsset(*resolved_or,
481 MixFingerprint(&fingerprint,
static_cast<uint16_t
>(object_id));
482 MixFingerprint(&fingerprint,
static_cast<uint16_t
>(subtype));
485 if (consumer_count == 0) {
486 return absl::AbortedError(
487 "Custom object mapping or project folder changed after this asset "
488 "was opened; edits were kept");
494 return absl::FailedPreconditionError(
495 "No matching standard object is selected for source-impact analysis");
498 return absl::FailedPreconditionError(
499 "A loaded ROM is required for source-impact analysis");
504 if (!impact_or.ok()) {
505 return impact_or.status();
508 uint64_t fingerprint = 1469598103934665603ULL;
509 MixFingerprint(&fingerprint, impact_or->affected_objects.size());
510 for (
const auto& entry : impact_or->affected_objects) {
511 MixFingerprint(&fingerprint, 0xA11EC7EDULL);
512 MixFingerprint(&fingerprint,
static_cast<uint16_t
>(entry.object_id));
513 MixFingerprint(&fingerprint, entry.overlapping_ranges.size());
514 for (
const auto& range : entry.overlapping_ranges) {
515 MixFingerprint(&fingerprint, range.begin);
516 MixFingerprint(&fingerprint, range.end);
519 MixFingerprint(&fingerprint, impact_or->runtime_consumers.size());
520 for (
const auto& entry : impact_or->runtime_consumers) {
521 MixFingerprint(&fingerprint, 0x52754E71ULL);
522 MixFingerprint(&fingerprint,
static_cast<uint64_t
>(entry.consumer));
523 MixFingerprint(&fingerprint, entry.overlapping_ranges.size());
524 for (
const auto& range : entry.overlapping_ranges) {
525 MixFingerprint(&fingerprint, range.begin);
526 MixFingerprint(&fingerprint, range.end);
535 if (!snapshot_or.ok()) {
536 return snapshot_or.status();
538 return snapshot_or->consumer_count;
542 uint64_t fingerprint = 1469598103934665603ULL;
543 MixFingerprint(&fingerprint,
545 MixFingerprint(&fingerprint,
549 MixFingerprint(&fingerprint,
551 MixFingerprint(&fingerprint,
553 for (
const unsigned char character :
555 MixFingerprint(&fingerprint, character);
558 MixFingerprint(&fingerprint,
560 MixFingerprint(&fingerprint,
564 MixFingerprint(&fingerprint,
static_cast<uint32_t
>(cell.rel_x));
565 MixFingerprint(&fingerprint,
static_cast<uint32_t
>(cell.rel_y));
566 MixFingerprint(&fingerprint, cell.original_word);
567 MixFingerprint(&fingerprint, cell.source_ref.has_value() ? 1 : 0);
568 if (cell.source_ref.has_value()) {
569 MixFingerprint(&fingerprint, cell.source_ref->span_index);
570 MixFingerprint(&fingerprint, cell.source_ref->word_index);
573 MixFingerprint(&fingerprint,
577 MixFingerprint(&fingerprint,
static_cast<uint16_t
>(provenance.object_id));
578 MixFingerprint(&fingerprint, provenance.descriptor_pc_address);
579 MixFingerprint(&fingerprint, provenance.expected_descriptor_word);
580 MixFingerprint(&fingerprint, provenance.spans.size());
581 for (
const auto& span : provenance.spans) {
582 MixFingerprint(&fingerprint, span.pc_address);
583 MixFingerprint(&fingerprint, span.expected_words.size());
584 for (
const uint16_t word : span.expected_words) {
585 MixFingerprint(&fingerprint, word);
609 if (!usage_count_or.ok()) {
610 return usage_count_or.status();
612 return *usage_count_or > 1;
616 return rooms_ !=
nullptr &&
635 if (p_open !=
nullptr) {
640 if (p_open !=
nullptr && !*p_open) {
646 const uint64_t graphics_revision =
655 ImGui::TextUnformatted(session_title.c_str());
661 if (p_open !=
nullptr) {
670 "%s", tr(
"This fixed slot intentionally draws nothing. Add its first "
671 "tile to begin a graphics override."));
672 if (ImGui::Button(
ICON_MD_ADD " Add First Tile")) {
676 std::string(status.message()));
685 if (ImGui::BeginTable(
686 "##TileEditorLayout", 2,
687 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
688 ImGui::TableSetupColumn(
"Tile Grid", ImGuiTableColumnFlags_WidthFixed,
690 ImGui::TableSetupColumn(
"Source Sheet", ImGuiTableColumnFlags_WidthStretch);
692 ImGui::TableNextRow();
693 ImGui::TableNextColumn();
696 ImGui::TableNextColumn();
710 const char* shared_popup_title =
712 ?
"Shared Custom Asset###SharedTileDataGlobalEdit"
713 :
"Shared Tile Data (Global Edit)###SharedTileDataGlobalEdit";
715 ImGui::OpenPopup(shared_popup_title);
718 if (ImGui::BeginPopupModal(shared_popup_title,
nullptr,
719 ImGuiWindowFlags_AlwaysAutoResize)) {
721 ImGui::Text(tr(
"This source asset is used by %d runtime slots."),
723 ImGui::Text(tr(
"Publishing will update all of those slots."));
725 tr(
"This project source-asset edit is not covered by Dungeon Editor "
728 ImGui::Text(tr(
"This tile data is shared by %d consumers."),
730 ImGui::Text(tr(
"Changes will affect all of them."));
732 tr(
"This shared tile edit is not covered by Dungeon Editor Ctrl+Z."));
735 const char* apply_label =
737 ? tr(
"Publish Shared Asset")
738 : tr(
"Apply Global Edit");
739 if (ImGui::Button(apply_label, ImVec2(170, 0))) {
741 ImGui::CloseCurrentPopup();
744 if (ImGui::Button(tr(
"Cancel"), ImVec2(120, 0))) {
747 ImGui::CloseCurrentPopup();
756 if (room ==
nullptr) {
781 if (room ==
nullptr) {
817 const int cell_palette =
static_cast<int>(cell.tile_info.palette_);
826 uint16_t attributes = 0;
850 constexpr float kMaxCustomGridHeight = 300.0f;
851 const ImGuiStyle& style = ImGui::GetStyle();
852 const float grid_height =
854 const float child_height =
855 std::min(grid_height + style.WindowPadding.y * 2 + style.ScrollbarSize,
856 kMaxCustomGridHeight);
857 if (ImGui::BeginChild(
"##CustomTileGridScroll", ImVec2(0, child_height),
858 true, ImGuiWindowFlags_HorizontalScrollbar)) {
867 int modified_count = 0;
881 constexpr float kScale = kTileGridScale;
885 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
886 ImVec2 canvas_size(grid_width, grid_height);
893 ImGui::Dummy(canvas_size);
896 ImDrawList* draw_list = ImGui::GetWindowDrawList();
900 float line_x = canvas_pos.x + gx * 8 *
kScale;
901 draw_list->AddLine(ImVec2(line_x, canvas_pos.y),
902 ImVec2(line_x, canvas_pos.y + grid_height),
903 IM_COL32(128, 128, 128, 80));
906 float line_y = canvas_pos.y + gy * 8 *
kScale;
907 draw_list->AddLine(ImVec2(canvas_pos.x, line_y),
908 ImVec2(canvas_pos.x + grid_width, line_y),
909 IM_COL32(128, 128, 128, 80));
916 ImVec2 cell_min(canvas_pos.x + cell.rel_x * 8 *
kScale,
917 canvas_pos.y + cell.rel_y * 8 *
kScale);
918 ImVec2 cell_max(cell_min.x + 8 *
kScale, cell_min.y + 8 *
kScale);
919 draw_list->AddRect(cell_min, cell_max, IM_COL32(255, 255, 0, 255), 0, 0,
924 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
925 ImVec2 mouse = ImGui::GetMousePos();
927 static_cast<int>((mouse.x - canvas_pos.x) / (8 *
kScale));
929 static_cast<int>((mouse.y - canvas_pos.y) / (8 *
kScale));
946 ImGui::TextUnformatted(tr(
"Destination (X/Y)"));
947 ImGui::SetNextItemWidth(90);
948 ImGui::SliderInt(tr(
"X##AddTileX"), &
add_tile_x_, 0, kMaxCoordinate,
"%d",
949 ImGuiSliderFlags_AlwaysClamp);
951 ImGui::SetNextItemWidth(90);
952 ImGui::SliderInt(tr(
"Y##AddTileY"), &
add_tile_y_, 0, kMaxCoordinate,
"%d",
953 ImGuiSliderFlags_AlwaysClamp);
960 if (ImGui::IsItemHovered()) {
961 ImGui::SetItemTooltip(
962 "%s", tr(
"Adds the selected source tile with its palette and "
963 "flip/priority attributes. An occupied position is "
964 "selected instead."));
967 const absl::Status status =
973 if (ImGui::IsItemHovered()) {
974 ImGui::SetItemTooltip(
975 "%s", tr(
"Moves the selected tile to X/Y, keeping its tile, palette, "
976 "and flip/priority attributes. An occupied position is "
977 "refused; tiles are never swapped or overwritten."));
980 tr(
"Custom objects draw in a %dx%d tile buffer (X/Y 0-%d)."),
993 ImGui::SetNextItemWidth(80);
998 constexpr float kAtlasScale = 2.0f;
1002 ImVec2 atlas_pos = ImGui::GetCursorScreenPos();
1003 ImVec2 atlas_size(display_width, display_height);
1006 ImGui::BeginChild(
"##AtlasScroll", ImVec2(display_width + 16, 300),
true,
1007 ImGuiWindowFlags_HorizontalScrollbar);
1009 atlas_pos = ImGui::GetCursorScreenPos();
1014 ImGui::Dummy(atlas_size);
1017 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1021 float line_x = atlas_pos.x + gx * 8 * kAtlasScale;
1022 draw_list->AddLine(ImVec2(line_x, atlas_pos.y),
1023 ImVec2(line_x, atlas_pos.y + display_height),
1024 IM_COL32(64, 64, 64, 60));
1027 float line_y = atlas_pos.y + gy * 8 * kAtlasScale;
1028 draw_list->AddLine(ImVec2(atlas_pos.x, line_y),
1029 ImVec2(atlas_pos.x + display_width, line_y),
1030 IM_COL32(64, 64, 64, 60));
1039 ImVec2 sel_min(atlas_pos.x + src_col * 8 * kAtlasScale,
1040 atlas_pos.y + src_row * 8 * kAtlasScale);
1041 ImVec2 sel_max(sel_min.x + 8 * kAtlasScale, sel_min.y + 8 * kAtlasScale);
1042 draw_list->AddRect(sel_min, sel_max, IM_COL32(0, 255, 255, 255), 0, 0,
1047 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
1048 ImVec2 mouse = ImGui::GetMousePos();
1050 static_cast<int>((mouse.x - atlas_pos.x) / (8 * kAtlasScale));
1052 static_cast<int>((mouse.y - atlas_pos.y) / (8 * kAtlasScale));
1054 if (click_col >= 0 &&
1067 cell.tile_info.id_ =
static_cast<uint16_t
>(tile_id);
1069 cell.modified =
true;
1086 ImGui::TextDisabled(tr(
"Select a tile cell to edit properties"));
1091 ImGui::Text(tr(
"Cell (%d, %d)"), cell.rel_x, cell.rel_y);
1095 int tile_id = cell.tile_info.id_;
1096 ImGui::SetNextItemWidth(80);
1097 if (ImGui::InputInt(tr(
"ID"), &tile_id, 1, 16)) {
1098 cell.tile_info.id_ =
static_cast<uint16_t
>(tile_id & 0x3FF);
1099 cell.modified =
true;
1107 int pal = cell.tile_info.palette_;
1108 ImGui::SetNextItemWidth(60);
1109 if (ImGui::SliderInt(tr(
"Pal"), &pal, 0, 7)) {
1110 cell.tile_info.palette_ =
static_cast<uint8_t
>(pal);
1111 cell.modified =
true;
1119 if (ImGui::Checkbox(tr(
"H"), &cell.tile_info.horizontal_mirror_)) {
1120 cell.modified =
true;
1126 if (ImGui::Checkbox(tr(
"V"), &cell.tile_info.vertical_mirror_)) {
1127 cell.modified =
true;
1133 if (ImGui::Checkbox(tr(
"Pri"), &cell.tile_info.over_)) {
1134 cell.modified =
true;
1147 if (!plan_or.ok()) {
1148 return plan_or.status();
1151 const auto& plan = *plan_or;
1153 !plan.write_ranges().empty()) {
1154 const absl::Status preflight =
1156 if (!preflight.ok()) {
1170 if (!impact_snapshot_or.ok()) {
1177 " Apply blocked: source impact could not be resolved: "
1179 impact_snapshot_or.status().message()));
1184 if (confirm_shared && shared_count > 1) {
1193 " Confirm shared asset publish: %d runtime slots use this "
1194 "file. This project-source edit is not covered by Ctrl+Z.",
1200 " Confirm global apply: %d consumers use this tile "
1201 "data. This edit is not covered by Ctrl+Z.",
1206 const bool missing_required_confirmation =
1208 const bool confirmed_impact_changed =
1211 if (!confirm_shared &&
1212 (missing_required_confirmation || confirmed_impact_changed)) {
1221 " Source impact changed; review and confirm the updated "
1222 "publish for %d runtime slots. This project-source edit is not "
1223 "covered by Ctrl+Z.",
1230 " Source impact changed; review and confirm the updated global "
1231 "apply for %d consumers. This edit is not covered by Ctrl+Z.",
1244 const auto refreshed =
1246 if (refreshed.ok() && *refreshed) {
1262 room.MarkObjectsDirty();
1263 room.RenderRoomGraphics();
1271 if (cell.modified) {
1273 cell.original_word = applied_word;
1276 cell.source_ref.has_value()) {
1277 const auto& source_ref = *cell.source_ref;
1279 if (source_ref.span_index < spans.size() &&
1280 source_ref.word_index <
1281 spans[source_ref.span_index].expected_words.size()) {
1282 spans[source_ref.span_index].expected_words[source_ref.word_index] =
1286 cell.modified =
false;
1294 if (shared_count > 1) {
1300 " Published shared custom asset used by %d runtime slots.",
1307 " Applied changes to shared tile data used by %d consumers.",
1318 absl::StrFormat(
ICON_MD_ERROR " Apply failed: %s", status.message()));
1323 int modified_count = 0;
1329 bool has_mods = modified_count > 0;
1332 ImGui::Text(tr(
"%d tile(s) modified"), modified_count);
1340 if (!shared_count_or.ok()) {
1343 if (ImGui::IsItemHovered()) {
1344 ImGui::SetItemTooltip(
"%s", shared_count_or.status().message().data());
1347 }
else if (*shared_count_or > 1) {
1348 const int shared_count = *shared_count_or;
1358 if (ImGui::IsItemHovered()) {
1360 ImGui::SetItemTooltip(
1361 tr(
"%d fixed runtime slots resolve to this source file.\n"
1362 "Publishing changes updates all of them.\nThis project "
1363 "source-asset edit is not covered by Ctrl+Z."),
1366 ImGui::SetItemTooltip(
1367 tr(
"This object reuses tile data with %d consumers.\nApplying "
1368 "changes will update every object or runtime consumer in that "
1369 "shared data group.\nThis shared tile edit is not covered by "
1379 ImGui::BeginDisabled();
1380 const char* primary_action_label =
1384 if (ImGui::Button(primary_action_label)) {
1388 ImGui::EndDisabled();
1394 ImGui::BeginDisabled();
1399 ImGui::EndDisabled();
1403 const char* close_label =
1405 if (ImGui::Button(close_label)) {
1406 if (p_open !=
nullptr) {
1412 if (has_mods && ImGui::IsItemHovered()) {
1413 ImGui::SetItemTooltip(
1414 "%s", tr(
"Close the panel and discard all unapplied tile changes."));
1420 ImGui::Text(
"Source: %s",
1421 std::filesystem::path(path).filename().
string().c_str());
1422 if (ImGui::IsItemHovered())
1423 ImGui::SetTooltip(
"%s", path.c_str());
1425 ImGui::TextDisabled(
1428 " Project source-asset edit; not covered by Ctrl+Z."
1429 :
ICON_MD_INFO " Global ROM tile edit; not covered by Ctrl+Z.");
1450 ImGui::PushTextWrapPos(0.0f);
1452 ImGui::PopTextWrapPos();
1458 if (p_open !=
nullptr) {
1463 " Unapplied tile changes were kept. Reopen the panel to continue, or "
1464 "use Discard & Close to throw them away.");
1468 if (p_open !=
nullptr) {
1476 if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows))
1484 auto find_neighbor = [&](
int dx,
int dy) ->
int {
1488 int target_x = cur.rel_x + dx;
1489 int target_y = cur.rel_y + dy;
1490 for (
int i = 0; i < cell_count; ++i) {
1499 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow,
false)) {
1503 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow,
false)) {
1507 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow,
false)) {
1511 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow,
false)) {
1519 for (
int key = 0; key <= 7; ++key) {
1520 if (ImGui::IsKeyPressed(
static_cast<ImGuiKey
>(ImGuiKey_0 + key),
false)) {
1521 cell.tile_info.palette_ =
static_cast<uint8_t
>(key);
1522 cell.modified =
true;
1530 if (ImGui::IsKeyPressed(ImGuiKey_H,
false)) {
1531 cell.tile_info.horizontal_mirror_ = !cell.tile_info.horizontal_mirror_;
1532 cell.modified =
true;
1539 if (ImGui::IsKeyPressed(ImGuiKey_V,
false)) {
1540 cell.tile_info.vertical_mirror_ = !cell.tile_info.vertical_mirror_;
1541 cell.modified =
true;
1548 if (ImGui::IsKeyPressed(ImGuiKey_P,
false)) {
1549 cell.tile_info.over_ = !cell.tile_info.over_;
1550 cell.modified =
true;
1558 if (ImGui::IsKeyPressed(ImGuiKey_Escape,
false)) {
1569 if (ImGui::IsKeyPressed(ImGuiKey_Tab,
false)) {
1570 if (cell_count > 0) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
uint64_t object_tile_revision() const
zelda3::Room * GetIfLoaded(int room_id)
void ForEachMaterialized(Fn &&fn)
void ClearRenderedBitmaps()
absl::StatusOr< SourceImpactSnapshot > AnalyzeSourceImpactSnapshot() const
void DrawCustomTileAdder()
void SyncSourceAttributesFromSelectedCell()
void ApplyChanges(bool confirm_shared=true)
void RequestSafeWindowClose(bool *p_open)
void DrawTileProperties()
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
absl::Status AppendCustomCell(int rel_x, int rel_y, gfx::TileInfo tile_info)
void SetCurrentPaletteGroupForRoom(int room_id, const gfx::PaletteGroup &group)
absl::Status OpenForCustomObject(int16_t object_id, int subtype, int room_id, DungeonRoomStore *rooms)
ObjectTileEditorPanel(gfx::IRenderer *renderer, Rom *rom)
gfx::PaletteGroup current_palette_group_
ActionStatusTone action_status_tone_
absl::Status WriteBackCurrentLayout()
std::string BuildWindowTitle() const
std::unique_ptr< zelda3::ObjectTileEditor > tile_editor_
void ResetTransientState()
TilesAppliedCallback on_tiles_applied_
bool HasRenderableRoomContext() const
std::optional< CustomLayoutBaseline > custom_baseline_
void RenderObjectPreview()
int16_t current_object_id_
void OnClose() override
Called when panel is hidden.
absl::StatusOr< bool > HasSharedTileDataConflict() const
absl::Status AddCustomTileAt(int rel_x, int rel_y)
absl::Status AddFirstTileToEmptyCustomLayout()
std::optional< SourceImpactDisplayCacheKey > source_impact_display_cache_key_
void RefreshRenderedViewsFromCurrentRoom()
static constexpr int kCustomObjectBufferTiles
StandardWritePreflightCallback standard_write_preflight_
void RevertCurrentLayout()
gfx::Bitmap object_preview_bmp_
void DiscardSourceImpactConfirmation()
zelda3::ObjectTileLayout current_layout_
int shared_tile_data_usage_override_
absl::Status MoveSelectedCustomTileTo(int rel_x, int rel_y)
absl::Status OpenForObject(int16_t object_id, int room_id, DungeonRoomStore *rooms)
void SyncSourceSelectionFromSelectedCell()
std::string action_status_message_
void DrawTileGridCanvas()
std::optional< absl::StatusOr< int > > source_impact_display_cache_result_
~ObjectTileEditorPanel() override
uint64_t room_graphics_revision_
int selected_source_tile_
void Draw(bool *p_open) override
Draw the panel content.
bool show_shared_confirm_
gfx::Bitmap tile8_atlas_bmp_
absl::StatusOr< int > GetDisplayedSharedTileDataUsageCount()
uint16_t source_attributes_
uint64_t BuildSourceImpactProvenanceFingerprint() const
void SnapshotCustomBaseline()
void SelectFirstCellIfAvailable()
void HandleKeyboardShortcuts(bool *p_open)
DungeonRoomStore * rooms_
void DrawActionBar(bool *p_open)
void SetActionStatus(ActionStatusTone tone, std::string message)
absl::StatusOr< int > GetSharedTileDataUsageCount() const
std::optional< SourceImpactSnapshot > pending_shared_confirmation_
void RetireBitmap(Bitmap &bitmap)
Explicitly retire resources owned by a bitmap that is being erased.
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
TextureHandle texture() const
void CreateTexture()
Creates the underlying SDL_Texture to be displayed.
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Defines an abstract interface for all rendering operations.
SNES 16-bit tile metadata container.
RAII guard for ImGui style colors.
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
static const std::array< int, 3 > & RuntimeObjectIds()
static CustomObjectManager & Get()
absl::StatusOr< std::shared_ptr< const DungeonTilePatterns > > GetTilePatterns()
static constexpr int kAtlasTilesPerRow
static bool IsEditableStandardObject(int16_t object_id)
static constexpr int kAtlasTileRows
static constexpr int kAtlasWidthPx
static constexpr int kAtlasHeightPx
uint64_t graphics_revision() const
#define ICON_MD_OPEN_WITH
#define ICON_MD_VISIBILITY_OFF
#define ICON_MD_CHECK_CIRCLE
void MixFingerprint(uint64_t *fingerprint, uint64_t value)
bool PathsReferToSameAsset(const std::filesystem::path &lhs, const std::filesystem::path &rhs)
constexpr float kTileGridScale
uint16_t TileInfoToWord(TileInfo tile_info)
TileInfo WordToTileInfo(uint16_t word)
ImVec4 ConvertColorToImVec4(const Color &color)
absl::StatusOr< std::vector< uint8_t > > EncodeCustomObjectBinary(const CustomObject &object)
absl::StatusOr< fs::path > ResolveCustomObjectAssetPath(const std::string &custom_objects_folder, const std::string &filename)
std::string GetObjectName(int object_id)
Represents a group of palettes.
Represents a decoded custom object (from binary format)
BoundingBox GetBoundingBox() const
std::vector< TileMapEntry > tiles
bool HasModifications() const
std::shared_ptr< const DungeonTilePatterns > pattern_source
std::optional< ObjectTileSourceProvenance > source_provenance
std::filesystem::path custom_resolved_path
std::vector< Cell > cells
std::string custom_filename