6#include "absl/status/status.h"
7#include "absl/strings/str_format.h"
18#include "imgui/imgui.h"
91 const int computed = tiles_per_row * rows;
99 std::array<uint8_t, 0x200>& all_tiles_types) {
104 current_gfx_bmp.
depth(), current_gfx_bmp.
vector());
112 tile16_blockset_bmp.
width(), tile16_blockset_bmp.
height(),
113 tile16_blockset_bmp.
depth(), tile16_blockset_bmp.
vector());
156 ImVector<std::string> tile16_names;
157 for (
int i = 0; i < 0x200; ++i) {
159 tile16_names.push_back(str);
170 Checkbox(
"X Flip", &
x_flip);
171 Checkbox(
"Y Flip", &
y_flip);
175 return absl::OkStatus();
180 return absl::InvalidArgumentError(
"Blockset not initialized, open a ROM.");
183 if (BeginMenuBar()) {
184 if (BeginMenu(
"View")) {
185 Checkbox(
"Show Collision Types",
190 if (BeginMenu(
"Edit")) {
191 if (MenuItem(
"Copy Current Tile16",
"Ctrl+C")) {
194 if (MenuItem(
"Paste to Current Tile16",
"Ctrl+V")) {
200 if (BeginMenu(
"File")) {
201 if (MenuItem(
"Save Changes to ROM",
"Ctrl+S")) {
204 if (MenuItem(
"Commit to Blockset",
"Ctrl+Shift+S")) {
209 if (MenuItem(
"Live Preview",
nullptr, &live_preview)) {
215 if (BeginMenu(
"Scratch Space")) {
216 for (
int i = 0; i < 4; i++) {
217 std::string slot_name =
"Slot " + std::to_string(i + 1);
219 if (MenuItem((slot_name +
" (Load)").c_str())) {
222 if (MenuItem((slot_name +
" (Save)").c_str())) {
225 if (MenuItem((slot_name +
" (Clear)").c_str())) {
229 if (MenuItem((slot_name +
" (Save)").c_str())) {
243 if (BeginPopupModal(
"About Tile16 Editor", NULL,
244 ImGuiWindowFlags_AlwaysAutoResize)) {
245 Text(
"Tile16 Editor for Link to the Past");
246 Text(
"This editor allows you to edit 16x16 tiles used in the game.");
248 BulletText(
"Edit Tile16 graphics by placing 8x8 tiles in the quadrants");
249 BulletText(
"Copy and paste Tile16 graphics");
250 BulletText(
"Save and load Tile16 graphics to/from scratch space");
251 BulletText(
"Preview Tile16 graphics at a larger size");
253 if (Button(
"Close")) {
261 OpenPopup(
"Unsaved Changes##Tile16Editor");
263 if (BeginPopupModal(
"Unsaved Changes##Tile16Editor", NULL,
264 ImGuiWindowFlags_AlwaysAutoResize)) {
266 Text(
"What would you like to do?");
311 if (Button(
"Cancel", ImVec2(80, 0))) {
331 return absl::OkStatus();
341 return absl::InvalidArgumentError(
"Blockset not initialized, open a ROM.");
346 OpenPopup(
"##Tile16EditorContextMenu");
349 TextDisabled(
"Right-click for more options");
355 if (BeginPopupModal(
"About Tile16 Editor", NULL,
356 ImGuiWindowFlags_AlwaysAutoResize)) {
357 Text(
"Tile16 Editor for Link to the Past");
358 Text(
"This editor allows you to edit 16x16 tiles used in the game.");
360 BulletText(
"Edit Tile16 graphics by placing 8x8 tiles in the quadrants");
361 BulletText(
"Copy and paste Tile16 graphics");
362 BulletText(
"Save and load Tile16 graphics to/from scratch space");
363 BulletText(
"Preview Tile16 graphics at a larger size");
365 if (Button(
"Close")) {
373 OpenPopup(
"Unsaved Changes##Tile16Editor");
375 if (BeginPopupModal(
"Unsaved Changes##Tile16Editor", NULL,
376 ImGuiWindowFlags_AlwaysAutoResize)) {
378 Text(
"What would you like to do?");
414 if (Button(
"Cancel", ImVec2(80, 0))) {
430 return absl::OkStatus();
434 if (BeginPopup(
"##Tile16EditorContextMenu")) {
435 if (BeginMenu(
"View")) {
436 Checkbox(
"Show Collision Types",
441 if (BeginMenu(
"Edit")) {
442 if (MenuItem(
"Copy Current Tile16",
"Ctrl+C")) {
445 if (MenuItem(
"Paste to Current Tile16",
"Ctrl+V")) {
449 if (MenuItem(
"Flip Horizontal",
"H")) {
452 if (MenuItem(
"Flip Vertical",
"V")) {
455 if (MenuItem(
"Rotate",
"R")) {
458 if (MenuItem(
"Clear",
"Delete")) {
464 if (BeginMenu(
"File")) {
465 if (MenuItem(
"Save Changes to ROM",
"Ctrl+S")) {
468 if (MenuItem(
"Commit to Blockset",
"Ctrl+Shift+S")) {
473 if (MenuItem(
"Live Preview",
nullptr, &live_preview)) {
479 if (BeginMenu(
"Scratch Space")) {
480 for (
int i = 0; i < 4; i++) {
481 std::string slot_name =
"Slot " + std::to_string(i + 1);
483 if (MenuItem((slot_name +
" (Load)").c_str())) {
486 if (MenuItem((slot_name +
" (Save)").c_str())) {
489 if (MenuItem((slot_name +
" (Clear)").c_str())) {
493 if (MenuItem((slot_name +
" (Save)").c_str())) {
536 if (result.selection_changed) {
539 util::logf(
"Selected Tile16 from blockset: %d", result.selected_tile);
545 return absl::OkStatus();
550 if (!
rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
559 return absl::FailedPreconditionError(
"Cannot access current tile16 data");
567 return absl::OkStatus();
572 return absl::FailedPreconditionError(
"Tile16 blockset not available");
585 util::logf(
"Tile16 blockset refreshed and regenerated");
586 return absl::OkStatus();
593 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
596 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
597 return absl::OutOfRangeError(
"Current tile16 ID out of range");
613 for (
int tile_y_offset = 0; tile_y_offset <
kTile16Size; ++tile_y_offset) {
614 for (
int tile_x_offset = 0; tile_x_offset <
kTile16Size;
616 int src_index = tile_y_offset *
kTile16Size + tile_x_offset;
619 (tile_x + tile_x_offset);
637 for (
int tile_y_offset = 0; tile_y_offset <
kTile16Size;
639 for (
int tile_x_offset = 0; tile_x_offset <
kTile16Size;
641 int src_index = tile_y_offset *
kTile16Size + tile_x_offset;
644 (tile_x + tile_x_offset);
660 return absl::OkStatus();
667 return absl::FailedPreconditionError(
"Cannot access current tile16 data");
674 for (
int quadrant = 0; quadrant < 4; ++quadrant) {
676 int quadrant_x = quadrant % 2;
677 int quadrant_y = quadrant / 2;
682 tile_info = &tile_data->tile0_;
685 tile_info = &tile_data->tile1_;
688 tile_info = &tile_data->tile2_;
691 tile_info = &tile_data->tile3_;
699 int tile8_id = tile_info->
id_;
725 if (src_index >= 0 &&
726 src_index <
static_cast<int>(source_tile8.size()) &&
728 uint8_t pixel = source_tile8.data()[src_index];
730 tile16_pixels[dst_index] = pixel;
747 util::logf(
"Regenerated Tile16 bitmap for tile %d from ROM data",
749 return absl::OkStatus();
758 auto now = std::chrono::steady_clock::now();
759 auto time_since_last_edit =
760 std::chrono::duration_cast<std::chrono::milliseconds>(now -
764 if (time_since_last_edit > 100) {
772 return absl::OutOfRangeError(
777 return absl::FailedPreconditionError(
"Source tile8 bitmap not active");
781 return absl::FailedPreconditionError(
"Target tile16 bitmap not active");
785 int quadrant_x = (pos.x >=
kTile8Size) ? 1 : 0;
786 int quadrant_y = (pos.y >=
kTile8Size) ? 1 : 0;
795 if (tile_to_use->
size() < 64) {
796 return absl::FailedPreconditionError(
"Source tile data too small");
800 for (
int tile_y = 0; tile_y <
kTile8Size; ++tile_y) {
801 for (
int tile_x = 0; tile_x <
kTile8Size; ++tile_x) {
818 int dst_x = start_x + tile_x;
819 int dst_y = start_y + tile_y;
823 if (src_index >= 0 && src_index <
static_cast<int>(tile_to_use->
size()) &&
826 uint8_t pixel_value = tile_to_use->
data()[src_index];
844 int quadrant_index = quadrant_x + (quadrant_y * 2);
845 if (quadrant_index >= 0 && quadrant_index < 4) {
853 switch (quadrant_index) {
855 quadrant_tile = &tile_data->tile0_;
858 quadrant_tile = &tile_data->tile1_;
861 quadrant_tile = &tile_data->tile2_;
864 quadrant_tile = &tile_data->tile3_;
868 if (quadrant_tile && !(*quadrant_tile == new_tile_info)) {
869 *quadrant_tile = new_tile_info;
870 SyncTilesInfoArray(tile_data);
873 "Updated ROM Tile16 %d, quadrant %d: Tile8=%d, Pal=%d, XFlip=%d, "
874 "YFlip=%d, Priority=%d",
896 "Local tile16 changes made (not saved to ROM yet). Use 'Apply Changes' "
899 return absl::OkStatus();
903 static bool show_advanced_controls =
false;
904 static bool show_debug_info =
false;
908 {{ImGuiStyleVar_FramePadding, ImVec2(8, 4)},
909 {ImGuiStyleVar_ItemSpacing, ImVec2(8, 4)}});
913 ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.0f, 1.0f),
"Tile16 Editor");
917 ImGui::TextDisabled(
"|");
924 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f),
"| %d pending",
929 if (show_debug_info) {
932 ImGui::TextDisabled(
"(Slot: %d)", actual_slot);
939 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 340);
943 util::logf(
"Failed to commit changes: %s", status.message().data());
946 if (ImGui::IsItemHovered()) {
947 ImGui::SetTooltip(
"Commit all %d pending changes to ROM",
955 if (ImGui::IsItemHovered()) {
956 ImGui::SetTooltip(
"Discard all %d pending changes",
964 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 180);
965 if (ImGui::Button(
"Debug Info", ImVec2(80, 0))) {
966 show_debug_info = !show_debug_info;
969 if (ImGui::Button(
"Advanced", ImVec2(80, 0))) {
970 show_advanced_controls = !show_advanced_controls;
976 if (ImGui::BeginTable(
"##Tile16EditLayout", 3,
977 ImGuiTableFlags_Resizable |
978 ImGuiTableFlags_BordersInnerV |
979 ImGuiTableFlags_SizingStretchProp)) {
980 ImGui::TableSetupColumn(
"Tile16 Blockset",
981 ImGuiTableColumnFlags_WidthStretch, 0.35f);
982 ImGui::TableSetupColumn(
"Tile8 Source", ImGuiTableColumnFlags_WidthStretch,
984 ImGui::TableSetupColumn(
"Editor & Controls",
985 ImGuiTableColumnFlags_WidthStretch, 0.30f);
987 ImGui::TableHeadersRow();
988 ImGui::TableNextRow();
991 ImGui::TableNextColumn();
995 ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.9f, 1.0f),
"Tile16 Blockset");
1008 ImGui::SetNextItemWidth(80);
1017 if (ImGui::IsItemHovered()) {
1018 ImGui::SetTooltip(
"Tile ID (0-%d) - navigates as you type",
1023 ImGui::TextDisabled(
"|");
1030 if (ImGui::Button(
"<<")) {
1034 if (ImGui::IsItemHovered())
1035 ImGui::SetTooltip(
"First page");
1038 if (ImGui::Button(
"<")) {
1043 if (ImGui::IsItemHovered())
1044 ImGui::SetTooltip(
"Previous page (PageUp)");
1047 ImGui::TextDisabled(
"Page %d/%d",
current_page_ + 1, total_pages);
1050 if (ImGui::Button(
">")) {
1055 if (ImGui::IsItemHovered())
1056 ImGui::SetTooltip(
"Next page (PageDown)");
1059 if (ImGui::Button(
">>")) {
1063 if (ImGui::IsItemHovered())
1064 ImGui::SetTooltip(
"Last page");
1068 ImGui::TextDisabled(
"|");
1074 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
1075 if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) {
1080 if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) {
1086 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
1090 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
1096 if (!ImGui::GetIO().KeyCtrl) {
1097 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
1103 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
1109 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
1115 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
1127 if (BeginChild(
"##BlocksetScrollable",
1128 ImVec2(0, ImGui::GetContentRegionAvail().y),
true,
1129 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1134 ImGui::SetScrollY(tile_y);
1151 bool tile_selected =
false;
1154 if (ImGui::IsItemClicked(ImGuiMouseButton_Left) &&
1156 tile_selected =
true;
1159 if (tile_selected) {
1160 const ImGuiIO& io = ImGui::GetIO();
1163 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1165 int grid_x =
static_cast<int>(mouse_pos.x /
1167 int grid_y =
static_cast<int>(mouse_pos.y /
1169 int selected_tile = grid_x + grid_y * 8;
1174 util::logf(
"Selected Tile16 from blockset: %d", selected_tile);
1186 ImGui::TableNextColumn();
1187 ImGui::BeginGroup();
1189 ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.9f, 1.0f),
"Tile8 Source");
1194 if (BeginChild(
"##Tile8SourceScrollable", ImVec2(0, 0),
true,
1195 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1208 bool tile8_selected =
false;
1212 if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
1213 tile8_selected =
true;
1216 if (tile8_selected) {
1217 const ImGuiIO& io = ImGui::GetIO();
1220 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1223 int tile_x =
static_cast<int>(
1229 int new_tile8 = tile_x + (tile_y * tiles_per_row);
1250 ImGui::BeginGroup();
1253 if (ImGui::BeginChild(
"##Tile16FixedCanvas", ImVec2(90, 90),
true,
1254 ImGuiWindowFlags_NoScrollbar |
1255 ImGuiWindowFlags_NoScrollWithMouse)) {
1258 tile16_edit_frame_opts.
canvas_size = ImVec2(64, 64);
1259 tile16_edit_frame_opts.
draw_grid =
true;
1260 tile16_edit_frame_opts.
grid_step = 8.0f;
1266 auto tile16_edit_rt =
1299 if (display_palette && !display_palette->
empty()) {
1309 if (palette_slot >= 0 &&
static_cast<size_t>(palette_slot + 16) <=
1310 display_palette->
size()) {
1312 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
1327 for (
int y = 0; y < 8; ++y) {
1328 for (
int x = 0; x < 4; ++x) {
1329 std::swap(data[y * 8 + x], data[y * 8 + (7 - x)]);
1335 for (
int y = 0; y < 4; ++y) {
1336 for (
int x = 0; x < 8; ++x) {
1337 std::swap(data[y * 8 + x], data[(7 - y) * 8 + x]);
1348 const auto preview_command =
1361 if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
1362 const ImGuiIO& io = ImGui::GetIO();
1364 ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x,
1365 io.MousePos.y - canvas_pos.y);
1369 constexpr float kBitmapOffset = 2.0f;
1370 constexpr float kBitmapScale = 4.0f;
1372 static_cast<int>((mouse_pos.x - kBitmapOffset) / kBitmapScale);
1374 static_cast<int>((mouse_pos.y - kBitmapOffset) / kBitmapScale);
1377 tile_x = std::max(0, std::min(15, tile_x));
1378 tile_y = std::max(0, std::min(15, tile_y));
1380 util::logf(
"Tile16 canvas click: (%.2f, %.2f) -> Tile16: (%d, %d)",
1381 mouse_pos.x, mouse_pos.y, tile_x, tile_y);
1390 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
1391 const ImGuiIO& io = ImGui::GetIO();
1393 ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x,
1394 io.MousePos.y - canvas_pos.y);
1398 constexpr float kBitmapOffset = 2.0f;
1399 constexpr float kBitmapScale = 4.0f;
1401 static_cast<int>((mouse_pos.x - kBitmapOffset) / kBitmapScale);
1403 static_cast<int>((mouse_pos.y - kBitmapOffset) / kBitmapScale);
1406 tile_x = std::max(0, std::min(15, tile_x));
1407 tile_y = std::max(0, std::min(15, tile_y));
1410 util::logf(
"Right-clicked to pick tile8 from tile16 at (%d, %d)",
1416 tile16_edit_frame_opts);
1431 if (tile8_texture) {
1432 ImGui::Image((ImTextureID)(intptr_t)tile8_texture, ImVec2(24, 24));
1438 int encoded_row = -1;
1443 switch (sheet_idx) {
1457 ImGui::TextDisabled(
"S%d", sheet_idx);
1458 if (ImGui::IsItemHovered()) {
1459 ImGui::BeginTooltip();
1460 ImGui::Text(
"Sheet: %d", sheet_idx);
1461 ImGui::Text(
"Encoded Palette Row: %d", encoded_row);
1464 "Graphics sheets have different palette encodings:\n"
1465 "- Sheets 0,3,4,5: Row 8 (offset 0x88)\n"
1466 "- Sheets 1,2,6,7: Row 0 (raw)");
1467 ImGui::EndTooltip();
1472 Checkbox(
"X Flip", &
x_flip);
1474 Checkbox(
"Y Flip", &
y_flip);
1482 if (show_debug_info) {
1485 ImGui::TextDisabled(
"(Slot %d)", actual_slot);
1489 ImGui::BeginGroup();
1490 float available_width = ImGui::GetContentRegionAvail().x;
1491 float button_size = std::min(32.0f, (available_width - 16.0f) / 4.0f);
1493 for (
int row = 0; row < 2; ++row) {
1494 for (
int col = 0; col < 4; ++col) {
1498 int i = row * 4 + col;
1506 is_current ? ImVec4(0.2f, 0.7f, 0.3f, 1.0f)
1507 : ImVec4(0.3f, 0.3f, 0.35f, 1.0f)},
1508 {ImGuiCol_ButtonHovered,
1509 is_current ? ImVec4(0.3f, 0.8f, 0.4f, 1.0f)
1510 : ImVec4(0.4f, 0.4f, 0.45f, 1.0f)},
1511 {ImGuiCol_ButtonActive,
1512 is_current ? ImVec4(0.1f, 0.6f, 0.2f, 1.0f)
1513 : ImVec4(0.25f, 0.25f, 0.3f, 1.0f)},
1515 is_current ? ImVec4(0.4f, 0.9f, 0.5f, 1.0f)
1516 : ImVec4(0.5f, 0.5f, 0.5f, 0.3f)}});
1518 ImGuiStyleVar_FrameBorderSize, 1.0f);
1520 if (ImGui::Button(absl::StrFormat(
"%d", i).c_str(),
1521 ImVec2(button_size, button_size))) {
1527 status.message().data());
1529 util::logf(
"Palette successfully changed to %d",
1538 if (ImGui::IsItemHovered()) {
1539 ImGui::BeginTooltip();
1540 if (show_debug_info) {
1541 ImGui::Text(
"Palette %d → Slots:", i);
1547 ImGui::Text(
"Palette %d", i);
1549 ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f),
"Active");
1552 ImGui::EndTooltip();
1561 if (Button(
"Clear", ImVec2(-1, 0))) {
1565 if (Button(
"Copy", ImVec2(-1, 0))) {
1569 if (Button(
"Paste", ImVec2(-1, 0))) {
1576 if (Button(
"Save Changes", ImVec2(-1, 0))) {
1579 HOVER_HINT(
"Apply changes to overworld and regenerate blockset");
1581 if (Button(
"Discard Changes", ImVec2(-1, 0))) {
1584 HOVER_HINT(
"Reload tile16 from ROM, discarding local changes");
1591 if (Button(
"Undo", ImVec2(-1, 0))) {
1598 if (show_advanced_controls) {
1602 if (Button(
"Palette Settings", ImVec2(-1, 0))) {
1606 if (Button(
"Analyze Data", ImVec2(-1, 0))) {
1609 HOVER_HINT(
"Analyze tile8 source data format and palette state");
1611 if (Button(
"Manual Edit", ImVec2(-1, 0))) {
1612 ImGui::OpenPopup(
"ManualTile8Editor");
1615 if (Button(
"Refresh Blockset", ImVec2(-1, 0))) {
1628 if (show_debug_info) {
1637 ImGui::TextDisabled(
"Sheet:%d Slot:%d", sheet_index, actual_slot);
1641 if (ImGui::CollapsingHeader(
"Palette Map",
1642 ImGuiTreeNodeFlags_DefaultOpen)) {
1643 ImGui::BeginChild(
"##PaletteMappingScroll", ImVec2(0, 120),
true);
1644 if (ImGui::BeginTable(
"##PalMap", 3,
1645 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
1646 ImGuiTableFlags_SizingFixedFit)) {
1647 ImGui::TableSetupColumn(
"Btn", ImGuiTableColumnFlags_WidthFixed, 30);
1648 ImGui::TableSetupColumn(
"S0,3-4", ImGuiTableColumnFlags_WidthFixed,
1650 ImGui::TableSetupColumn(
"S1-2", ImGuiTableColumnFlags_WidthFixed, 50);
1651 ImGui::TableHeadersRow();
1653 for (
int i = 0; i < 8; ++i) {
1654 ImGui::TableNextRow();
1655 ImGui::TableNextColumn();
1656 ImGui::Text(
"%d", i);
1657 ImGui::TableNextColumn();
1659 ImGui::TableNextColumn();
1668 if (ImGui::CollapsingHeader(
"Colors")) {
1671 ImGui::Text(
"Slot %d:", actual_slot);
1677 int color_index = actual_slot + i;
1679 ImVec4 display_color = color.rgb();
1681 ImGui::ColorButton(absl::StrFormat(
"##c%d", i).c_str(),
1682 display_color, ImGuiColorEditFlags_NoTooltip,
1684 if (ImGui::IsItemHovered()) {
1685 ImGui::SetTooltip(
"%d:0x%04X", color_index, color.snes());
1688 if ((i + 1) % 4 != 0)
1710 return absl::OkStatus();
1715 return absl::FailedPreconditionError(
1716 "Current graphics bitmap not initialized");
1726 const int total_tiles = tiles_per_row * total_rows;
1731 for (
int tile_y = 0; tile_y < total_rows; ++tile_y) {
1732 for (
int tile_x = 0; tile_x < tiles_per_row; ++tile_x) {
1733 std::vector<uint8_t> tile_data(64);
1737 for (
int py = 0; py < 8; ++py) {
1738 for (
int px = 0; px < 8; ++px) {
1739 int src_x = tile_x * 8 + px;
1740 int src_y = tile_y * 8 + py;
1742 int dst_index = py * 8 + px;
1752 tile_data[dst_index] = pixel_value;
1762 tile_bitmap.Create(8, 8, 8, tile_data);
1771 game_data()->palette_groups.overworld_main.size() > 0) {
1773 tile_bitmap.SetPalette(
game_data()->palette_groups.overworld_main[0]);
1778 }
catch (
const std::exception& e) {
1779 util::logf(
"Error creating tile at (%d,%d): %s", tile_x, tile_y,
1782 tile_bitmap.Create(8, 8, 8, std::vector<uint8_t>(64, 0));
1797 util::logf(
"Loaded %zu individual tile8 graphics",
1799 return absl::OkStatus();
1803 if (tile_id < 0 || tile_id >= kTile16Count) {
1804 return absl::OutOfRangeError(
1805 absl::StrFormat(
"Invalid tile16 id: %d", tile_id));
1809 return absl::FailedPreconditionError(
1810 "Tile16 blockset or ROM not initialized");
1830 bool bitmap_loaded =
false;
1834 pending_bitmap_it->second.is_active()) {
1836 pending_bitmap_it->second.vector());
1838 bitmap_loaded =
true;
1841 if (!tile_data.empty()) {
1842 for (
auto& pixel : tile_data) {
1848 bitmap_loaded =
true;
1852 if (!bitmap_loaded) {
1862 util::logf(
"SetCurrentTile: loaded tile %d successfully", tile_id);
1863 return absl::OkStatus();
1870 "RequestTileSwitch: Editor not initialized (blockset=%p, rom=%p)",
1876 if (target_tile_id < 0 || target_tile_id >= kTile16Count) {
1877 util::logf(
"RequestTileSwitch: Invalid target tile ID %d", target_tile_id);
1891 util::logf(
"Tile %d has pending changes, showing confirmation dialog",
1897 util::logf(
"Failed to switch to tile %d: %s", target_tile_id,
1898 status.message().data());
1904 if (tile_id < 0 || tile_id >= kTile16Count) {
1905 return absl::InvalidArgumentError(
"Invalid tile ID");
1908 return absl::FailedPreconditionError(
"ROM not available");
1922 bool bitmap_copied =
false;
1928 bitmap_copied =
true;
1930 pending_bitmap_it->second.is_active()) {
1932 pending_bitmap_it->second.vector());
1934 bitmap_copied =
true;
1937 if (!tile_pixels.empty()) {
1940 bitmap_copied =
true;
1944 if (bitmap_copied) {
1950 return absl::OkStatus();
1955 return absl::FailedPreconditionError(
"Clipboard is empty");
1979 return absl::OkStatus();
1984 return absl::InvalidArgumentError(
"Invalid scratch space slot");
1987 return absl::FailedPreconditionError(
"No active tile16 to save");
2000 return absl::OkStatus();
2005 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2009 return absl::FailedPreconditionError(
"Scratch space slot is empty");
2033 return absl::OkStatus();
2038 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2042 return absl::OkStatus();
2048 return absl::FailedPreconditionError(
"No active tile16 to flip");
2069 return absl::OkStatus();
2074 return absl::FailedPreconditionError(
"No active tile16 to flip");
2095 return absl::OkStatus();
2100 return absl::FailedPreconditionError(
"No active tile16 to rotate");
2123 return absl::OkStatus();
2129 return absl::InvalidArgumentError(
"Invalid tile8 ID");
2133 return absl::FailedPreconditionError(
"Source tile8 not active");
2140 for (
int quadrant = 0; quadrant < 4; ++quadrant) {
2154 return absl::OkStatus();
2159 return absl::FailedPreconditionError(
"No active tile16 to clear");
2165 for (
int quadrant = 0; quadrant < 4; ++quadrant) {
2179 return absl::OkStatus();
2187 new_palette = (new_palette + 1) % 8;
2189 new_palette = (new_palette == 0) ? 7 : new_palette - 1;
2198 return absl::OkStatus();
2203 return absl::OkStatus();
2206 if (palette_id >= 8) {
2207 return absl::InvalidArgumentError(
"Invalid palette ID");
2219 return absl::FailedPreconditionError(
"GameData not available");
2226 }
else if (ow_main_pal_group.size() > 0) {
2227 display_palette = &ow_main_pal_group[0];
2230 if (display_palette && !display_palette->
empty()) {
2243 return absl::OkStatus();
2325 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2328 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2329 return absl::OutOfRangeError(
"Current tile16 ID out of range");
2333 return absl::OutOfRangeError(
"Current palette ID out of range");
2336 return absl::OkStatus();
2341 tile_id < kTile16Count;
2347 return absl::FailedPreconditionError(
"ROM not available");
2351 return absl::FailedPreconditionError(
"No active tile16 to save");
2370 return absl::OkStatus();
2375 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2378 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2379 return absl::OutOfRangeError(
"Current tile16 ID out of range");
2385 return absl::OkStatus();
2390 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2405 return absl::OkStatus();
2427 util::logf(
"Committed Tile16 %d changes to overworld system",
2429 return absl::OkStatus();
2437 return absl::OkStatus();
2442 return absl::OkStatus();
2445 util::logf(
"Committing %zu pending tile16 changes to ROM",
2452 util::logf(
"Failed to write tile16 %d: %s", tile_id,
2453 status.message().data());
2471 util::logf(
"All pending tile16 changes committed successfully");
2472 return absl::OkStatus();
2480 util::logf(
"Discarding %zu pending tile16 changes",
2489 util::logf(
"Failed to reload tile after discard: %s",
2490 status.message().data());
2505 util::logf(
"Failed to reload tile after discard: %s",
2506 status.message().data());
2511 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2520 util::logf(
"Marked tile %d as modified (total pending: %zu)",
2525 if (!
rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2526 return absl::InvalidArgumentError(
"Invalid tile16 or ROM not set");
2530 int quad_x = (position.x < 8) ? 0 : 1;
2531 int quad_y = (position.y < 8) ? 0 : 1;
2532 int quadrant = quad_x + (quad_y * 2);
2537 return absl::FailedPreconditionError(
"Failed to get tile16 data");
2544 tile_info = tile16_data->tile0_;
2547 tile_info = tile16_data->tile1_;
2550 tile_info = tile16_data->tile2_;
2553 tile_info = tile16_data->tile3_;
2570 util::logf(
"Picked tile8 %d with palette %d from quadrant %d of tile16 %d",
2573 return absl::OkStatus();
2583 switch (sheet_index) {
2601 return static_cast<int>(
2612 int sheet_index)
const {
2613 const int clamped_button = std::clamp(palette_button, 0, 7);
2615 const int actual_row = std::clamp(base_row + clamped_button, 0, 15);
2618 return actual_row * 16;
2626 constexpr int kTilesPerSheet = 256;
2627 int sheet_index = tile8_id / kTilesPerSheet;
2630 return std::min(7, std::max(0, sheet_index));
2660 switch (sheet_index) {
2694 int sheet_index = 0;
2700 const int actual_target_row = std::clamp(base_row + target_row, 0, 15);
2702 for (
int i = 0; i < 256; ++i) {
2703 int low_nibble = i & 0x0F;
2704 int target_index = (actual_target_row * 16) + low_nibble;
2707 if (low_nibble == 0) {
2710 }
else if (target_index <
static_cast<int>(source.
size())) {
2711 remapped.
AddColor(source[target_index]);
2731 return pixel_value / 16;
2745 !
game_data()->palette_groups.overworld_main.empty()) {
2750 if (!display_palette || display_palette->
empty()) {
2760 if (palette_slot >= 0 &&
2761 static_cast<size_t>(palette_slot + 16) <= display_palette->
size()) {
2763 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
2780 return absl::InvalidArgumentError(
"Invalid tile8 ID");
2784 return absl::OkStatus();
2788 return absl::FailedPreconditionError(
"ROM not set");
2800 if (palette_groups.overworld_main.size() > 0) {
2801 display_palette = palette_groups.overworld_main[0];
2803 return absl::FailedPreconditionError(
"No overworld palette available");
2808 if (current_palette_ < 0 || current_palette_ >= 8) {
2814 const int palette_slot =
2818 if (!display_palette.
empty()) {
2820 if (palette_slot >= 0 &&
2821 static_cast<size_t>(palette_slot + 16) <= display_palette.
size()) {
2823 display_palette,
static_cast<size_t>(palette_slot + 1), 15);
2826 display_palette, 1, 15);
2838 util::logf(
"Updated tile8 %d with palette slot %d (palette size: %zu colors)",
2841 return absl::OkStatus();
2846 return absl::FailedPreconditionError(
"ROM not set");
2850 if (current_palette_ < 0 || current_palette_ >= 8) {
2862 util::logf(
"Using complete overworld palette with %zu colors",
2863 display_palette.
size());
2867 util::logf(
"Using fallback complete palette with %zu colors",
2868 display_palette.
size());
2872 if (palette_groups.overworld_main.size() > 0) {
2873 display_palette = palette_groups.overworld_main[0];
2874 util::logf(
"Warning: Using ROM main palette with %zu colors",
2875 display_palette.
size());
2877 return absl::FailedPreconditionError(
"No palette available");
2881 if (display_palette.
empty()) {
2882 return absl::FailedPreconditionError(
"Display palette empty");
2893 util::logf(
"Applied remapped palette (button %d) to source bitmap",
2897 util::logf(
"Applied full CGRAM palette to source bitmap");
2913 if (palette_slot >= 0 &&
2914 static_cast<size_t>(palette_slot + 16) <= display_palette.
size()) {
2916 display_palette,
static_cast<size_t>(palette_slot + 1), 15);
2941 if (palette_slot >= 0 &&
2942 static_cast<size_t>(palette_slot + 16) <= display_palette.
size()) {
2944 display_palette,
static_cast<size_t>(palette_slot + 1), 15);
2961 "Successfully refreshed all palettes in tile16 editor with palette %d",
2963 return absl::OkStatus();
2967 util::logf(
"=== TILE8 SOURCE DATA ANALYSIS ===");
2980 std::map<uint8_t, int> pixel_counts;
2981 for (
size_t i = 0; i < 64; ++i) {
2983 pixel_counts[val]++;
2985 util::logf(
" - First tile8 (Sheet 0) pixel distribution:");
2986 for (
const auto& [val, count] : pixel_counts) {
2988 int col = val & 0x0F;
2989 util::logf(
" Value 0x%02X (%3d) = Row %d, Col %d: %d pixels", val, val,
2994 bool all_4bpp =
true;
2995 for (
const auto& [val, count] : pixel_counts) {
3001 util::logf(
" - Values in raw 4bpp range (0-15): %s",
3002 all_4bpp ?
"yes" :
"NO (pre-encoded)");
3005 util::logf(
" - Palette remapping for viewing:");
3008 util::logf(
" Pixels are remapped: (value & 0x0F) + (selected_row * 16)");
3019 util::logf(
" - Size: %dx%d", first_tile.width(), first_tile.height());
3020 util::logf(
" - Depth: %d bpp", first_tile.depth());
3021 util::logf(
" - Palette size: %zu colors", first_tile.palette().size());
3023 if (first_tile.data() && first_tile.size() >= 64) {
3024 std::map<uint8_t, int> pixel_counts;
3025 for (
size_t i = 0; i < 64; ++i) {
3026 uint8_t val = first_tile.data()[i];
3027 pixel_counts[val]++;
3030 for (
const auto& [val, count] : pixel_counts) {
3031 util::logf(
" Value 0x%02X (%3d): %d pixels", val, val, count);
3046 util::logf(
" - Expected palette offset for SetPaletteWithTransparent: %d",
3051 util::logf(
" - First 16 palette colors (row 0):");
3052 for (
int i = 0; i < 16; ++i) {
3054 util::logf(
" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
3055 static_cast<int>(color.rgb().x),
3056 static_cast<int>(color.rgb().y),
3057 static_cast<int>(color.rgb().z));
3063 util::logf(
" - Colors at palette slot %d (row %d):", palette_slot,
3065 for (
int i = 0; i < 16; ++i) {
3067 util::logf(
" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
3068 static_cast<int>(color.rgb().x),
3069 static_cast<int>(color.rgb().y),
3070 static_cast<int>(color.rgb().z));
3080 Text(
"Pixel Normalization & Color Correction:");
3083 if (SliderInt(
"Normalization Mask", &mask_value, 1, 255,
"0x%02X")) {
3089 if (Button(
"Apply to All Graphics")) {
3091 if (!reload_result.ok()) {
3092 Text(
"Error: %s", reload_result.message().data());
3097 if (Button(
"Reset Defaults")) {
3101 (void)reload_result;
3105 Text(
"Current State:");
3106 static constexpr std::array<const char*, 7> palette_group_names = {
3107 "OW Main",
"OW Aux",
"OW Anim",
"Dungeon",
3108 "Sprites",
"Armor",
"Sword"};
3116 Text(
"Sheet-Specific Fixes:");
3119 static bool fix_sheet_0 =
true;
3120 static bool fix_sprite_sheets =
true;
3121 static bool use_transparent_for_terrain =
false;
3123 if (Checkbox(
"Fix Sheet 0 (Trees)", &fix_sheet_0)) {
3125 if (!reload_result.ok()) {
3126 Text(
"Error reloading: %s", reload_result.message().data());
3130 "Use direct palette for sheet 0 instead of transparent palette");
3132 if (Checkbox(
"Fix Sprite Sheets", &fix_sprite_sheets)) {
3134 if (!reload_result.ok()) {
3135 Text(
"Error reloading: %s", reload_result.message().data());
3138 HOVER_HINT(
"Use direct palette for sprite graphics sheets");
3140 if (Checkbox(
"Transparent for Terrain", &use_transparent_for_terrain)) {
3142 if (!reload_result.ok()) {
3143 Text(
"Error reloading: %s", reload_result.message().data());
3146 HOVER_HINT(
"Force transparent palette for terrain graphics");
3149 Text(
"Color Analysis:");
3153 Text(
"Selected Tile8 Analysis:");
3154 const auto& tile_data =
3156 std::map<uint8_t, int> pixel_counts;
3157 for (uint8_t pixel : tile_data) {
3158 pixel_counts[pixel & 0x0F]++;
3161 Text(
"Pixel Value Distribution:");
3162 for (
const auto& pair : pixel_counts) {
3163 int value = pair.first;
3164 int count = pair.second;
3165 Text(
" Value %d (0x%X): %d pixels", value, value, count);
3168 Text(
"Palette Colors Used:");
3170 for (
const auto& pair : pixel_counts) {
3171 int value = pair.first;
3172 int count = pair.second;
3173 if (value <
static_cast<int>(palette.size())) {
3174 auto color = palette[value];
3175 ImVec4 display_color = color.rgb();
3176 ImGui::ColorButton((
"##analysis" + std::to_string(value)).c_str(),
3177 display_color, ImGuiColorEditFlags_NoTooltip,
3179 if (ImGui::IsItemHovered()) {
3180 ImGui::SetTooltip(
"Index %d: 0x%04X (%d pixels)", value,
3181 color.snes(), count);
3191 if (CollapsingHeader(
"ROM Palette Manager") &&
rom_) {
3192 Text(
"Experimental ROM Palette Selection:");
3194 "Use ROM palettes to experiment with different color schemes");
3196 if (Button(
"Open Enhanced Palette Editor")) {
3200 if (Button(
"Show Color Analysis")) {
3205 static int quick_group = 0;
3206 static int quick_index = 0;
3208 SliderInt(
"ROM Group", &quick_group, 0, 6);
3209 SliderInt(
"Palette Index", &quick_index, 0, 7);
3211 if (Button(
"Apply to Tile8 Source")) {
3213 util::logf(
"Applied ROM palette group %d, index %d to Tile8 source",
3214 quick_group, quick_index);
3218 if (Button(
"Apply to Tile16 Editor")) {
3221 "Applied ROM palette group %d, index %d to Tile16 editor",
3222 quick_group, quick_index);
3232 Text(
"Layout Scratch:");
3233 for (
int i = 0; i < 4; ++i) {
3235 std::string slot_name =
"S" + std::to_string(i + 1);
3237 if (Button((slot_name +
" Save").c_str(), ImVec2(70, 20))) {
3244 ImGui::BeginDisabled();
3246 if (Button((slot_name +
" Load").c_str(), ImVec2(70, 20)) && can_load) {
3250 ImGui::EndDisabled();
3259 if (slot < 0 || slot >= 4) {
3260 return absl::InvalidArgumentError(
"Invalid scratch slot");
3264 if (total_tiles <= 0) {
3265 return absl::FailedPreconditionError(
"Tile16 blockset is not available");
3268 const int start_tile = std::clamp(
current_tile16_, 0, total_tiles - 1);
3269 for (
int y = 0; y < 8; ++y) {
3270 for (
int x = 0; x < 8; ++x) {
3271 const int tile_id = start_tile + (y * 8) + x;
3273 (tile_id < total_tiles) ? tile_id : -1;
3279 absl::StrFormat(
"From %03X",
static_cast<uint16_t
>(start_tile));
3281 return absl::OkStatus();
3285 if (slot < 0 || slot >= 4) {
3286 return absl::InvalidArgumentError(
"Invalid scratch slot");
3290 return absl::FailedPreconditionError(
"Scratch slot is empty");
3294 if (first_tile < 0) {
3295 return absl::FailedPreconditionError(
"Scratch slot has no valid tile data");
3304 for (
int y = 0; y < 8; ++y) {
3305 for (
int x = 0; x < 8; ++x) {
3314 return absl::OkStatus();
3318 if (ImGui::BeginPopupModal(
"ManualTile8Editor",
nullptr,
3319 ImGuiWindowFlags_AlwaysAutoResize)) {
3320 ImGui::Text(
"Manual Tile8 Configuration for Tile16 %02X",
current_tile16_);
3325 ImGui::Text(
"Current Tile16 ROM Data:");
3328 const char* quadrant_names[] = {
"Top-Left",
"Top-Right",
"Bottom-Left",
3331 for (
int q = 0; q < 4; q++) {
3332 ImGui::Text(
"%s Quadrant:", quadrant_names[q]);
3338 tile_info = &tile_data->tile0_;
3341 tile_info = &tile_data->tile1_;
3344 tile_info = &tile_data->tile2_;
3347 tile_info = &tile_data->tile3_;
3355 int tile_id_int =
static_cast<int>(tile_info->
id_);
3356 if (ImGui::InputInt(
"Tile8 ID", &tile_id_int, 1, 10)) {
3358 static_cast<uint16_t
>(std::max(0, std::min(tile_id_int, 1023)));
3361 int palette_int =
static_cast<int>(tile_info->
palette_);
3362 if (ImGui::SliderInt(
"Palette", &palette_int, 0, 7)) {
3363 tile_info->
palette_ =
static_cast<uint8_t
>(palette_int);
3370 ImGui::Checkbox(
"Priority", &tile_info->
over_);
3372 if (ImGui::Button(
"Apply to Graphics")) {
3373 SyncTilesInfoArray(tile_data);
3376 if (!update_result.ok()) {
3377 ImGui::Text(
"Error: %s", update_result.message().data());
3381 if (!refresh_result.ok()) {
3382 ImGui::Text(
"Refresh Error: %s", refresh_result.message().data());
3394 if (ImGui::Button(
"Apply All Changes")) {
3396 if (!update_result.ok()) {
3397 ImGui::Text(
"Update Error: %s", update_result.message().data());
3401 if (!save_result.ok()) {
3402 ImGui::Text(
"Save Error: %s", save_result.message().data());
3406 if (ImGui::Button(
"Refresh Display")) {
3408 if (!refresh_result.ok()) {
3409 ImGui::Text(
"Refresh Error: %s", refresh_result.message().data());
3414 ImGui::Text(
"Tile16 data not accessible");
3417 ImGui::Text(
"Valid range: 0-4095 (4096 total tiles)");
3422 if (ImGui::Button(
"Close")) {
3423 ImGui::CloseCurrentPopup();
3433 return absl::OkStatus();
3438 return absl::OkStatus();
3444 return absl::OkStatus();
3463 }
else if (ow_main_pal_group.size() > 0) {
3464 display_palette = &ow_main_pal_group[0];
3467 if (display_palette && !display_palette->
empty()) {
3485 return absl::OkStatus();
3489 if (!ImGui::IsAnyItemActive()) {
3490 bool ctrl_held = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
3491 ImGui::IsKeyDown(ImGuiKey_RightCtrl);
3494 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
3497 if (ImGui::IsKeyPressed(ImGuiKey_H) && !ctrl_held) {
3500 if (ImGui::IsKeyPressed(ImGuiKey_V) && !ctrl_held) {
3503 if (ImGui::IsKeyPressed(ImGuiKey_R) && !ctrl_held) {
3506 if (ImGui::IsKeyPressed(ImGuiKey_F) && !ctrl_held) {
3514 if (ImGui::IsKeyPressed(ImGuiKey_Q)) {
3517 if (ImGui::IsKeyPressed(ImGuiKey_E)) {
3522 for (
int i = 0; i < 8; ++i) {
3523 if (ImGui::IsKeyPressed(
static_cast<ImGuiKey
>(ImGuiKey_1 + i))) {
3531 if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
3534 if (ImGui::IsKeyPressed(ImGuiKey_Y)) {
3537 if (ImGui::IsKeyPressed(ImGuiKey_C)) {
3540 if (ImGui::IsKeyPressed(ImGuiKey_V)) {
3543 if (ImGui::IsKeyPressed(ImGuiKey_S)) {
3544 if (ImGui::IsKeyDown(ImGuiKey_LeftShift) ||
3545 ImGui::IsKeyDown(ImGuiKey_RightShift)) {
absl::StatusOr< gfx::Tile16 > ReadTile16(uint32_t tile16_id, uint32_t tile16_ptr)
absl::Status WriteTile16(int tile16_id, uint32_t tile16_ptr, const gfx::Tile16 &tile)
void set_dirty(bool dirty)
absl::Status SaveTile16ToScratchSpace(int slot)
gfx::Bitmap current_gfx_bmp_
std::map< int, gfx::Tile16 > pending_tile16_changes_
absl::Status LoadLayoutFromScratch(int slot)
zelda3::GameData * game_data() const
void FinalizePendingUndo()
Finalize any pending undo snapshot by capturing current state as "after" and pushing a Tile16EditActi...
std::chrono::steady_clock::time_point last_edit_time_
void DrawContextMenu()
Draw context menu with editor actions.
absl::Status CyclePalette(bool forward=true)
absl::Status ClearTile16()
void HandleKeyboardShortcuts()
absl::Status FillTile16WithTile8(int tile8_id)
gfx::Tilemap * tile16_blockset_
bool map_blockset_loaded_
absl::Status CommitAllChanges()
Write all pending changes to ROM and notify parent.
std::map< int, gfx::Bitmap > pending_tile16_bitmaps_
bool live_preview_enabled_
std::array< Tile16ScratchData, 4 > scratch_space_
void AnalyzeTile8SourceData() const
gfx::Tile16 current_tile16_data_
absl::Status SaveTile16ToROM()
Write current tile16 data directly to ROM (bypasses pending system)
void RestoreFromSnapshot(const Tile16Snapshot &snapshot)
Restore editor state from a Tile16Snapshot (used by undo actions).
void DiscardAllChanges()
Discard all pending changes (revert to ROM state)
int current_palette_group_
int GetActualPaletteSlotForCurrentTile16() const
Get the palette slot for the current tile being edited.
std::array< uint8_t, 0x200 > all_tiles_types_
UndoManager undo_manager_
absl::Status RegenerateTile16BitmapFromROM()
absl::Status DiscardChanges()
Discard current tile's changes (single tile)
bool show_palette_settings_
uint8_t palette_normalization_mask_
absl::Status PasteTile16FromClipboard()
gui::TileSelectorWidget blockset_selector_
int pending_changes_count() const
Get count of tiles with pending changes.
absl::Status SaveLayoutToScratch(int slot)
bool show_palette_preview_
absl::Status LoadTile16FromScratchSpace(int slot)
int GetPaletteSlotForSheet(int sheet_index) const
Get base palette slot for a graphics sheet.
gfx::SnesPalette CreateRemappedPaletteForViewing(const gfx::SnesPalette &source, int target_row) const
Create a remapped palette for viewing with user-selected palette.
int GetActualPaletteSlot(int palette_button, int sheet_index) const
Calculate actual palette slot from button + sheet.
bool show_unsaved_changes_dialog_
absl::Status FlipTile16Horizontal()
gfx::SnesPalette palette_
absl::Status UpdateTile16Edit()
gfx::SnesPalette overworld_palette_
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap ¤t_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status UpdateBlockset()
static constexpr int kTilesPerPage
int GetEncodedPaletteRow(uint8_t pixel_value) const
Get the encoded palette row for a pixel value.
static constexpr int kTilesPerRow
bool auto_normalize_pixels_
void EnableLivePreview(bool enable)
absl::Status SetCurrentTile(int id)
absl::Status UpdateTile8Palette(int tile8_id)
Update palette for a specific tile8.
absl::Status UpdateAsPanel()
Update the editor content without MenuBar (for EditorPanel usage)
void ApplyPaletteToCurrentTile16Bitmap()
absl::Status RefreshTile16Blockset()
Tile16ClipboardData clipboard_tile16_
absl::Status ValidateTile16Data()
void DrawManualTile8Inputs()
int pending_tile_switch_target_
absl::Status RefreshAllPalettes()
Refresh all tile8 palettes after a palette change.
void DrawPaletteSettings()
Draw palette settings UI.
absl::Status UpdateOverworldTilemap()
Update the overworld tilemap to reflect tile changes.
absl::Status RotateTile16()
gfx::Bitmap preview_tile16_
gui::Canvas tile16_edit_canvas_
absl::Status CommitChangesToBlockset()
Commit pending changes to the blockset atlas.
std::array< LayoutScratch, 4 > layout_scratch_
absl::Status UpdateROMTile16Data()
std::optional< Tile16Snapshot > pending_undo_before_
gui::Canvas blockset_canvas_
gui::Canvas tile8_source_canvas_
bool IsTile16Valid(int tile_id) const
gfx::Tile16 * GetCurrentTile16Data()
gfx::Bitmap tile16_blockset_bmp_
void MarkCurrentTileModified()
Mark the current tile as having pending changes.
std::vector< gfx::Bitmap > current_gfx_individual_
bool has_pending_changes() const
Check if any tiles have uncommitted changes.
absl::Status CommitChangesToOverworld()
Full commit workflow: ROM + blockset + notify parent.
absl::Status UpdateBlocksetBitmap()
void DiscardCurrentTileChanges()
Discard only the current tile's pending changes.
void RequestTileSwitch(int target_tile_id)
absl::Status DrawToCurrentTile16(ImVec2 pos, const gfx::Bitmap *source_tile=nullptr)
absl::Status UpdateLivePreview()
absl::Status PreviewPaletteChange(uint8_t palette_id)
gfx::Bitmap tile8_preview_bmp_
absl::Status CopyTile16ToClipboard(int tile_id)
absl::Status ClearScratchSpace(int slot)
int selection_start_tile_
bool is_tile_modified(int tile_id) const
Check if a specific tile has pending changes.
gui::Table tile_edit_table_
std::function< absl::Status()> on_changes_committed_
int GetPaletteBaseForSheet(int sheet_index) const
Get palette base row for a graphics sheet.
int GetSheetIndexForTile8(int tile8_id) const
Determine which graphics sheet contains a tile8.
absl::Status FlipTile16Vertical()
gfx::Bitmap current_tile16_bmp_
absl::Status PickTile8FromTile16(const ImVec2 &position)
std::vector< int > selected_tiles_
void CopyTile16ToAtlas(int tile_id)
void Push(std::unique_ptr< UndoAction > action)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
const SnesPalette & palette() const
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
TextureHandle texture() const
const std::vector< uint8_t > & vector() const
void UpdateSurfacePixels()
Update SDL surface with current pixel data from data_ vector Call this after modifying pixel data via...
void set_modified(bool modified)
void set_data(const std::vector< uint8_t > &data)
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
std::vector< uint8_t > & mutable_data()
SDL_Surface * surface() const
RAII timer for automatic timing management.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
Tile composition of four 8x8 tiles.
std::array< TileInfo, 4 > tiles_info
SNES 16-bit tile metadata container.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
void ShowScalingControls()
void ShowAdvancedCanvasProperties()
bool DrawTileSelector(int size, int size_y=0)
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
auto custom_labels_enabled()
void SetCanvasSize(ImVec2 canvas_size)
auto mutable_labels(int i)
void InitializePaletteEditor(Rom *rom)
void set_draggable(bool draggable)
float GetGlobalScale() const
bool IsMouseHovering() const
void InitializeDefaults()
void SetAutoResize(bool auto_resize)
bool ApplyROMPalette(int group_index, int palette_index)
RAII guard for ImGui style colors.
RAII guard for ImGui style vars.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
#define HOVER_HINT(string)
gfx::TileInfo HorizontalFlipInfo(gfx::TileInfo info)
void SyncTilesInfoArray(gfx::Tile16 *tile)
int ComputeTile16Count(const gfx::Tilemap *tile16_blockset)
gfx::TileInfo & TileInfoForQuadrant(gfx::Tile16 *tile, int quadrant)
gfx::TileInfo VerticalFlipInfo(gfx::TileInfo info)
constexpr int kTile16Count
constexpr int kTile16PixelCount
constexpr int kTile8PixelCount
constexpr float kTile8DisplayScale
constexpr int kTile16Size
constexpr int kNumScratchSlots
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
void EndCanvas(Canvas &canvas)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
bool SuccessButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a success action button (green color).
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
bool DangerButton(const char *label, const ImVec2 &size, const char *panel_id, const char *anim_id)
Draw a danger action button (error color).
void BeginChildWithScrollbar(const char *str_id)
std::string HexByte(uint8_t byte, HexStringParams params)
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
constexpr int kNumTile16Individual
constexpr uint32_t kTile16Ptr
#define RETURN_IF_ERROR(expr)
Snapshot of a Tile16's editable state for undo/redo.
std::vector< uint8_t > bitmap_data
gfx::SnesPalette bitmap_palette
PaletteGroup overworld_main
const SnesPalette & palette_ref(int i) const
Tilemap structure for SNES tile-based graphics management.
Bitmap atlas
Master bitmap containing all tiles.
std::optional< float > grid_step
gfx::PaletteGroupMap palette_groups