6#include "absl/status/status.h"
7#include "absl/strings/str_format.h"
21#include "imgui/imgui.h"
46 std::vector<gfx::SnesColor> grayscale;
49 const float value =
static_cast<float>(i) / 255.0f;
50 grayscale.emplace_back(ImVec4(value, value, value, 1.0f));
52 if (!grayscale.empty()) {
53 grayscale[0].set_transparent(
true);
88 std::array<uint8_t, 0x200>& all_tiles_types) {
130 ImVector<std::string> tile16_names;
131 for (
int i = 0; i < 0x200; ++i) {
133 tile16_names.push_back(str);
144 Checkbox(
"X Flip", &
x_flip);
145 Checkbox(
"Y Flip", &
y_flip);
149 return absl::OkStatus();
154 return absl::InvalidArgumentError(
"Blockset not initialized, open a ROM.");
157 if (BeginMenuBar()) {
158 if (BeginMenu(
"View")) {
159 Checkbox(
"Show Collision Types",
164 if (BeginMenu(
"Edit")) {
165 if (MenuItem(
"Copy Current Tile16",
"Ctrl+C")) {
168 if (MenuItem(
"Paste to Current Tile16",
"Ctrl+V")) {
174 if (BeginMenu(
"File")) {
175 if (MenuItem(
"Write Pending to ROM",
"Ctrl+S")) {
178 if (MenuItem(
"Refresh Blockset Preview",
"Ctrl+Shift+S")) {
183 if (MenuItem(
"Live Preview",
nullptr, &live_preview)) {
189 if (BeginMenu(
"Scratch Space")) {
190 for (
int i = 0; i < 4; i++) {
191 std::string slot_name =
"Slot " + std::to_string(i + 1);
193 if (MenuItem((slot_name +
" (Load)").c_str())) {
196 if (MenuItem((slot_name +
" (Save)").c_str())) {
199 if (MenuItem((slot_name +
" (Clear)").c_str())) {
203 if (MenuItem((slot_name +
" (Save)").c_str())) {
217 if (BeginPopupModal(
"About Tile16 Editor", NULL,
218 ImGuiWindowFlags_AlwaysAutoResize)) {
219 Text(
"Tile16 Editor for Link to the Past");
220 Text(
"This editor allows you to edit 16x16 tiles used in the game.");
222 BulletText(
"Edit Tile16 graphics by placing 8x8 tiles in the quadrants");
223 BulletText(
"Copy and paste Tile16 graphics");
224 BulletText(
"Save and load Tile16 graphics to/from scratch space");
225 BulletText(
"Preview Tile16 graphics at a larger size");
227 if (Button(
"Close")) {
235 OpenPopup(
"Unsaved Changes##Tile16Editor");
237 if (BeginPopupModal(
"Unsaved Changes##Tile16Editor", NULL,
238 ImGuiWindowFlags_AlwaysAutoResize)) {
240 Text(
"What would you like to do?");
243 if (Button(
"Keep Staged & Continue", ImVec2(220, 0))) {
244 if (IsItemHovered()) {
246 "Switch to the requested tile now. Other tiles with staged edits "
247 "stay in the write queue until you use Write Pending or Discard.");
267 util::logf(
"Failed to write/switch pending changes: %s",
268 status.message().data());
290 if (Button(
"Cancel", ImVec2(220, 0))) {
310 return absl::OkStatus();
320 return absl::InvalidArgumentError(
"Blockset not initialized, open a ROM.");
325 OpenPopup(
"##Tile16EditorContextMenu");
328 TextDisabled(
"Right-click for more options");
334 if (BeginPopupModal(
"About Tile16 Editor", NULL,
335 ImGuiWindowFlags_AlwaysAutoResize)) {
336 Text(
"Tile16 Editor for Link to the Past");
337 Text(
"This editor allows you to edit 16x16 tiles used in the game.");
339 BulletText(
"Edit Tile16 graphics by placing 8x8 tiles in the quadrants");
340 BulletText(
"Copy and paste Tile16 graphics");
341 BulletText(
"Save and load Tile16 graphics to/from scratch space");
342 BulletText(
"Preview Tile16 graphics at a larger size");
344 if (Button(
"Close")) {
352 OpenPopup(
"Unsaved Changes##Tile16Editor");
354 if (BeginPopupModal(
"Unsaved Changes##Tile16Editor", NULL,
355 ImGuiWindowFlags_AlwaysAutoResize)) {
357 Text(
"What would you like to do?");
360 if (Button(
"Keep Staged & Continue", ImVec2(220, 0))) {
361 if (IsItemHovered()) {
363 "Switch to the requested tile now. Other tiles with staged edits "
364 "stay in the write queue until you use Write Pending or Discard.");
384 util::logf(
"Failed to write/switch pending changes: %s",
385 status.message().data());
407 if (Button(
"Cancel", ImVec2(220, 0))) {
423 return absl::OkStatus();
427 if (BeginPopup(
"##Tile16EditorContextMenu")) {
428 if (BeginMenu(
"View")) {
429 Checkbox(
"Show Collision Types",
434 if (BeginMenu(
"Edit")) {
435 if (MenuItem(
"Copy Current Tile16",
"Ctrl+C")) {
438 if (MenuItem(
"Paste to Current Tile16",
"Ctrl+V")) {
442 if (MenuItem(
"Flip Horizontal",
"H")) {
445 if (MenuItem(
"Flip Vertical",
"V")) {
448 if (MenuItem(
"Rotate",
"R")) {
451 if (MenuItem(
"Clear",
"Delete")) {
457 if (BeginMenu(
"File")) {
458 if (MenuItem(
"Write Pending to ROM",
"Ctrl+S")) {
461 if (MenuItem(
"Refresh Blockset Preview",
"Ctrl+Shift+S")) {
466 if (MenuItem(
"Live Preview",
nullptr, &live_preview)) {
472 if (BeginMenu(
"Scratch Space")) {
473 for (
int i = 0; i < 4; i++) {
474 std::string slot_name =
"Slot " + std::to_string(i + 1);
476 if (MenuItem((slot_name +
" (Load)").c_str())) {
479 if (MenuItem((slot_name +
" (Save)").c_str())) {
482 if (MenuItem((slot_name +
" (Clear)").c_str())) {
486 if (MenuItem((slot_name +
" (Save)").c_str())) {
527 return absl::FailedPreconditionError(
528 "Tile16 blockset bitmap not initialized");
534 if (result.selection_changed) {
537 util::logf(
"Selected Tile16 from blockset: %d", result.selected_tile);
543 return absl::OkStatus();
548 if (!
rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
557 return absl::FailedPreconditionError(
"Cannot access current tile16 data");
565 return absl::OkStatus();
570 return absl::FailedPreconditionError(
"Tile16 blockset not available");
583 util::logf(
"Tile16 blockset refreshed and regenerated");
584 return absl::OkStatus();
617 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
620 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
621 return absl::OutOfRangeError(
"Current tile16 ID out of range");
625 return absl::FailedPreconditionError(
"Current tile16 bitmap is not active");
639 return absl::OkStatus();
647 return absl::FailedPreconditionError(
"Cannot access current tile16 data");
654 return absl::FailedPreconditionError(
"Tile8 source bitmap not active");
669 util::logf(
"Regenerated Tile16 bitmap for tile %d from ROM data",
671 return absl::OkStatus();
680 auto now = std::chrono::steady_clock::now();
681 auto time_since_last_edit =
682 std::chrono::duration_cast<std::chrono::milliseconds>(now -
686 if (time_since_last_edit > 100) {
694 return absl::OutOfRangeError(
699 return absl::FailedPreconditionError(
"Target tile16 bitmap not active");
702 const int tile8_count =
704 const int max_tile8_id = std::max(0, tile8_count - 1);
705 const int tile8_row_stride =
707 const int quadrant_x = (pos.x >=
kTile8Size) ? 1 : 0;
708 const int quadrant_y = (pos.y >=
kTile8Size) ? 1 : 0;
709 const int quadrant_index = quadrant_x + (quadrant_y * 2);
730 for (
const auto& mutation : staged_tiles) {
731 const int tile16_id = mutation.tile16_id;
774 "Local tile16 stamp staged (size=%dx, tiles=%zu). Use 'Write Pending' to "
778 return absl::OkStatus();
784 if (!left_click && !right_click) {
785 return absl::OkStatus();
790 util::logf(
"Picked tile8 from tile16 at (%d, %d)",
791 static_cast<int>(tile_position.x),
792 static_cast<int>(tile_position.y));
793 return absl::OkStatus();
809 return absl::OkStatus();
813 static bool show_advanced_controls =
false;
814 static bool show_debug_info =
false;
818 {{ImGuiStyleVar_FramePadding, ImVec2(8, 4)},
819 {ImGuiStyleVar_ItemSpacing, ImVec2(8, 4)}});
832 if (ImGui::BeginTable(
"##Tile16EditLayout", 3,
833 ImGuiTableFlags_Resizable |
834 ImGuiTableFlags_BordersInnerV |
835 ImGuiTableFlags_SizingStretchProp)) {
836 ImGui::TableSetupColumn(
"Tile16 Blockset",
837 ImGuiTableColumnFlags_WidthStretch, 0.35f);
838 ImGui::TableSetupColumn(
"Tile8 Source", ImGuiTableColumnFlags_WidthStretch,
840 ImGui::TableSetupColumn(
"Editor & Controls",
841 ImGuiTableColumnFlags_WidthStretch, 0.30f);
843 ImGui::TableHeadersRow();
844 ImGui::TableNextRow();
847 ImGui::TableNextColumn();
851 ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.9f, 1.0f),
"Tile16 Blockset");
864 ImGui::SetNextItemWidth(80);
873 if (ImGui::IsItemHovered()) {
874 ImGui::SetTooltip(
"Tile ID (0-%d) - navigates as you type",
879 ImGui::TextDisabled(
"|");
886 if (ImGui::Button(
"<<")) {
890 if (ImGui::IsItemHovered())
891 ImGui::SetTooltip(
"First page");
894 if (ImGui::Button(
"<")) {
899 if (ImGui::IsItemHovered())
900 ImGui::SetTooltip(
"Previous page (PageUp)");
903 ImGui::TextDisabled(
"Page %d/%d",
current_page_ + 1, total_pages);
906 if (ImGui::Button(
">")) {
912 if (ImGui::IsItemHovered())
913 ImGui::SetTooltip(
"Next page (PageDown)");
916 if (ImGui::Button(
">>")) {
920 if (ImGui::IsItemHovered())
921 ImGui::SetTooltip(
"Last page");
925 ImGui::TextDisabled(
"|");
931 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
932 if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) {
937 if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) {
943 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
947 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
953 if (!ImGui::GetIO().KeyCtrl) {
954 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
960 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
966 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
972 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
984 if (BeginChild(
"##BlocksetScrollable",
985 ImVec2(0, ImGui::GetContentRegionAvail().y),
true,
986 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
991 ImGui::SetScrollY(tile_y);
1008 bool tile_selected =
false;
1011 if (ImGui::IsItemClicked(ImGuiMouseButton_Left) &&
1013 tile_selected =
true;
1016 if (tile_selected) {
1017 const ImGuiIO& io = ImGui::GetIO();
1020 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1022 int grid_x =
static_cast<int>(mouse_pos.x /
1024 int grid_y =
static_cast<int>(mouse_pos.y /
1026 int selected_tile = grid_x + grid_y * 8;
1031 util::logf(
"Selected Tile16 from blockset: %d", selected_tile);
1046 ImGui::TableNextColumn();
1047 ImGui::BeginGroup();
1053 ImGui::BeginGroup();
1056 if (ImGui::BeginChild(
"##Tile16FixedCanvas", ImVec2(90, 90),
true,
1057 ImGuiWindowFlags_NoScrollbar |
1058 ImGuiWindowFlags_NoScrollWithMouse)) {
1061 tile16_edit_frame_opts.
canvas_size = ImVec2(64, 64);
1062 tile16_edit_frame_opts.
draw_grid =
true;
1063 tile16_edit_frame_opts.
grid_step = 8.0f;
1069 auto tile16_edit_rt =
1091 preview_data.begin());
1104 if (display_palette && !display_palette->
empty()) {
1114 if (palette_slot >= 0 &&
static_cast<size_t>(palette_slot + 16) <=
1115 display_palette->
size()) {
1117 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
1132 for (
int y = 0; y < 8; ++y) {
1133 for (
int x = 0; x < 4; ++x) {
1134 std::swap(data[y * 8 + x], data[y * 8 + (7 - x)]);
1140 for (
int y = 0; y < 4; ++y) {
1141 for (
int x = 0; x < 8; ++x) {
1142 std::swap(data[y * 8 + x], data[(7 - y) * 8 + x]);
1153 const auto preview_command =
1165 const bool left_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
1166 const bool right_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
1168 if (left_clicked || right_clicked) {
1169 const ImGuiIO& io = ImGui::GetIO();
1171 ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x,
1172 io.MousePos.y - canvas_pos.y);
1176 constexpr float kBitmapOffset = 2.0f;
1177 constexpr float kBitmapScale = 4.0f;
1179 static_cast<int>((mouse_pos.x - kBitmapOffset) / kBitmapScale);
1181 static_cast<int>((mouse_pos.y - kBitmapOffset) / kBitmapScale);
1184 tile_x = std::max(0, std::min(15, tile_x));
1185 tile_y = std::max(0, std::min(15, tile_y));
1188 "Tile16 canvas click: (%.2f, %.2f) -> Tile16: (%d, %d), mode=%s",
1189 mouse_pos.x, mouse_pos.y, tile_x, tile_y,
1193 left_clicked, right_clicked));
1198 tile16_edit_frame_opts);
1212 if (tile8_texture) {
1213 ImGui::Image((ImTextureID)(intptr_t)tile8_texture, ImVec2(24, 24));
1219 int encoded_row = -1;
1224 switch (sheet_idx) {
1238 ImGui::TextDisabled(
"S%d", sheet_idx);
1239 if (ImGui::IsItemHovered()) {
1240 ImGui::BeginTooltip();
1241 ImGui::Text(
"Sheet: %d", sheet_idx);
1242 ImGui::Text(
"Encoded Palette Row: %d", encoded_row);
1245 "Graphics sheets have different palette encodings:\n"
1246 "- Sheets 0,3,4,5: Row 8 (offset 0x88)\n"
1247 "- Sheets 1,2,6,7: Row 0 (raw)");
1248 ImGui::EndTooltip();
1253 Checkbox(
"X Flip", &
x_flip);
1255 Checkbox(
"Y Flip", &
y_flip);
1273 "1x: paint one quadrant\n2x: fill current tile16 from a 2x2 tile8 "
1274 "block\n4x: stamp a 2x2 tile16 patch from a 4x4 tile8 block");
1285 if (ImGui::RadioButton(
"Usage (U)",
1290 "Paint: left-click places Tile8 into Tile16.\n"
1291 "Pick: left-click samples Tile8 from Tile16.\n"
1292 "Usage: keeps usage overlay visible and samples on click.\n"
1293 "Right-click on Tile16 preview always samples.");
1304 if (show_advanced_controls) {
1308 if (Button(
"Palette Settings", ImVec2(-1, 0))) {
1312 if (Button(
"Analyze Data", ImVec2(-1, 0))) {
1315 HOVER_HINT(
"Analyze tile8 source data format and palette state");
1317 if (Button(
"Manual Edit", ImVec2(-1, 0))) {
1318 ImGui::OpenPopup(
"ManualTile8Editor");
1321 if (Button(
"Refresh Blockset", ImVec2(-1, 0))) {
1334 if (show_debug_info) {
1343 ImGui::TextDisabled(
"Sheet:%d Slot:%d", sheet_index, actual_slot);
1347 if (ImGui::CollapsingHeader(
"Palette Map",
1348 ImGuiTreeNodeFlags_DefaultOpen)) {
1349 ImGui::BeginChild(
"##PaletteMappingScroll", ImVec2(0, 120),
true);
1350 if (ImGui::BeginTable(
"##PalMap", 3,
1351 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
1352 ImGuiTableFlags_SizingFixedFit)) {
1353 ImGui::TableSetupColumn(
"Btn", ImGuiTableColumnFlags_WidthFixed, 30);
1354 ImGui::TableSetupColumn(
"S0,3-4", ImGuiTableColumnFlags_WidthFixed,
1356 ImGui::TableSetupColumn(
"S1-2", ImGuiTableColumnFlags_WidthFixed, 50);
1357 ImGui::TableHeadersRow();
1359 for (
int i = 0; i < 8; ++i) {
1360 ImGui::TableNextRow();
1361 ImGui::TableNextColumn();
1362 ImGui::Text(
"%d", i);
1363 ImGui::TableNextColumn();
1365 ImGui::TableNextColumn();
1374 if (ImGui::CollapsingHeader(
"Colors")) {
1377 ImGui::Text(
"Slot %d:", actual_slot);
1383 int color_index = actual_slot + i;
1385 ImVec4 display_color = color.rgb();
1387 ImGui::ColorButton(absl::StrFormat(
"##c%d", i).c_str(),
1388 display_color, ImGuiColorEditFlags_NoTooltip,
1390 if (ImGui::IsItemHovered()) {
1391 ImGui::SetTooltip(
"%d:0x%04X", color_index, color.snes());
1394 if ((i + 1) % 4 != 0)
1419 return absl::OkStatus();
1424 static constexpr std::array<const char*, 4> kQuadrantLabels = {
"TL",
"TR",
1426 int quadrant_palette_tl = -1;
1427 int quadrant_palette_tr = -1;
1428 int quadrant_palette_bl = -1;
1429 int quadrant_palette_br = -1;
1431 quadrant_palette_tl = tile_data->tile0_.palette_;
1432 quadrant_palette_tr = tile_data->tile1_.palette_;
1433 quadrant_palette_bl = tile_data->tile2_.palette_;
1434 quadrant_palette_br = tile_data->tile3_.palette_;
1437 ImGui::BeginGroup();
1438 ImGui::TextColored(ImVec4(0.8f, 0.9f, 1.0f, 1.0f),
"Tile16 Editor");
1444 ImGui::TextDisabled(
"| Active Quadrant: %s",
1447 ImGui::TextDisabled(
"| Edit Mode: %s", EditModeLabel(
edit_mode_));
1449 if (quadrant_palette_tl >= 0) {
1451 ImGui::TextDisabled(
"| Tile Meta TL/TR/BL/BR: %d/%d/%d/%d",
1452 quadrant_palette_tl, quadrant_palette_tr,
1453 quadrant_palette_bl, quadrant_palette_br);
1459 usage_mode_active ? ImVec4(0.55f, 0.90f, 0.60f, 1.0f)
1460 : ImVec4(0.95f, 0.86f, 0.46f, 1.0f),
1461 usage_mode_active ?
"Usage Mode: ACTIVE" :
"Usage Mode: Hold RMB or U");
1462 if (ImGui::IsItemHovered()) {
1463 ImGui::BeginTooltip();
1464 ImGui::Text(
"Usage Mode");
1466 ImGui::TextDisabled(
"Current edit mode: %s", EditModeLabel(
edit_mode_));
1467 ImGui::TextDisabled(
1468 "Press U to lock usage overlay and probe interactions.");
1469 ImGui::TextDisabled(
1470 "Hold RMB on the Tile8 Source panel for temporary usage highlighting.");
1471 ImGui::TextDisabled(
1472 "Pick mode (I) and Usage mode (U) sample on left-click.");
1473 ImGui::TextDisabled(
1474 "Paint mode (P) places Tile8 data and metadata on left-click.");
1475 ImGui::TextDisabled(
"Navigation shortcuts: PgUp/PgDn/Home/End + arrows.");
1476 ImGui::TextDisabled(
"Modes: P/I/U | Palette: Ctrl+1..8 | Quadrants: 1..4");
1477 ImGui::EndTooltip();
1480 if (show_debug_info) {
1483 ImGui::TextDisabled(
"(Slot: %d)", actual_slot);
1489 bool* show_advanced_controls) {
1490 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 180);
1491 if (ImGui::Button(
"Debug Info", ImVec2(80, 0))) {
1492 *show_debug_info = !*show_debug_info;
1495 if (ImGui::Button(
"Advanced", ImVec2(80, 0))) {
1496 *show_advanced_controls = !*show_advanced_controls;
1501 bool current_tile_pending,
1502 int pending_count) {
1503 const ImVec4 staged_bar_bg =
1504 current_tile_pending ? ImVec4(0.27f, 0.18f, 0.09f, 0.65f)
1505 : (has_pending ? ImVec4(0.16f, 0.15f, 0.18f, 0.65f)
1506 : ImVec4(0.12f, 0.17f, 0.13f, 0.65f));
1508 if (ImGui::BeginChild(
1509 "##Tile16StagedStateBar", ImVec2(0, 58),
true,
1510 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
1511 if (current_tile_pending) {
1512 ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.30f, 1.0f),
"Tile %02X: STAGED",
1515 ImGui::TextColored(ImVec4(0.45f, 0.85f, 0.55f, 1.0f),
"Tile %02X: CLEAN",
1520 ImGui::TextDisabled(
"| Queue: %d tile%s pending", pending_count,
1521 pending_count == 1 ?
"" :
"s");
1524 const auto seconds_since_write =
1525 std::chrono::duration_cast<std::chrono::seconds>(
1529 ImGui::TextDisabled(
"| Last write: %d tile%s, %lds ago",
1532 static_cast<long>(seconds_since_write));
1534 ImGui::TextDisabled(
1535 "Action rail at bottom: Write Pending / Discard / Undo");
1541 bool show_debug_info) {
1543 Text(
"Brush Palette:");
1544 if (show_debug_info) {
1547 ImGui::TextDisabled(
"(Slot %d)", actual_slot);
1549 ImGui::TextDisabled(
"Used for new tile8 placements");
1552 ImGui::BeginGroup();
1553 float available_width = ImGui::GetContentRegionAvail().x;
1554 float button_size = std::min(32.0f, (available_width - 16.0f) / 4.0f);
1556 for (
int row = 0; row < 2; ++row) {
1557 for (
int col = 0; col < 4; ++col) {
1561 int i = row * 4 + col;
1568 {{ImGuiCol_Button, is_current ? ImVec4(0.2f, 0.7f, 0.3f, 1.0f)
1569 : ImVec4(0.3f, 0.3f, 0.35f, 1.0f)},
1570 {ImGuiCol_ButtonHovered, is_current
1571 ? ImVec4(0.3f, 0.8f, 0.4f, 1.0f)
1572 : ImVec4(0.4f, 0.4f, 0.45f, 1.0f)},
1573 {ImGuiCol_ButtonActive, is_current
1574 ? ImVec4(0.1f, 0.6f, 0.2f, 1.0f)
1575 : ImVec4(0.25f, 0.25f, 0.3f, 1.0f)},
1576 {ImGuiCol_Border, is_current ? ImVec4(0.4f, 0.9f, 0.5f, 1.0f)
1577 : ImVec4(0.5f, 0.5f, 0.5f, 0.3f)}});
1581 if (ImGui::Button(absl::StrFormat(
"%d", i).c_str(),
1582 ImVec2(button_size, button_size))) {
1588 status.message().data());
1597 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
1603 if (ImGui::IsItemHovered()) {
1604 ImGui::BeginTooltip();
1605 if (show_debug_info) {
1606 ImGui::Text(
"Palette %d -> Slots:", i);
1612 ImGui::Text(
"Brush Palette %d", i);
1613 ImGui::TextDisabled(
"Applied to new tile8 placements");
1614 ImGui::TextDisabled(
"RMB: apply to all tile quadrants");
1615 ImGui::TextDisabled(
"Quadrant metadata is shown in strip below");
1616 ImGui::TextDisabled(
1617 "Hotkeys: Ctrl+1..8 palette, 1..4 quadrant focus");
1619 ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f),
"Active");
1622 ImGui::EndTooltip();
1630 Text(
"Quadrant Focus:");
1632 ImGui::TextDisabled(
"1-4");
1633 static constexpr std::array<const char*, 4> kQuadrantLabels = {
"TL",
"TR",
1635 const float quadrant_button_width = std::max(58.0f, button_size + 24.0f);
1636 for (
int q = 0; q < 4; ++q) {
1641 const gfx::TileInfo& info = TileInfoForQuadrant(*tile_data, q);
1642 const uint8_t quadrant_palette = info.
palette_;
1646 ImGui::PushID(100 + q);
1650 ? ImVec4(0.16f, 0.48f, 0.72f, 1.0f)
1651 : (matches_brush ? ImVec4(0.23f, 0.35f, 0.50f, 1.0f)
1652 : ImVec4(0.28f, 0.28f, 0.32f, 1.0f))},
1653 {ImGuiCol_ButtonHovered,
1655 ? ImVec4(0.20f, 0.56f, 0.82f, 1.0f)
1656 : (matches_brush ? ImVec4(0.28f, 0.43f, 0.60f, 1.0f)
1657 : ImVec4(0.38f, 0.38f, 0.42f, 1.0f))},
1658 {ImGuiCol_ButtonActive,
1660 ? ImVec4(0.12f, 0.40f, 0.62f, 1.0f)
1661 : (matches_brush ? ImVec4(0.18f, 0.30f, 0.44f, 1.0f)
1662 : ImVec4(0.24f, 0.24f, 0.28f, 1.0f))},
1663 {ImGuiCol_Border, is_active_quadrant
1664 ? ImVec4(0.45f, 0.78f, 1.0f, 1.0f)
1665 : ImVec4(0.4f, 0.4f, 0.4f, 0.4f)}});
1667 is_active_quadrant ? 2.0f : 1.0f);
1669 if (ImGui::Button(absl::StrFormat(
"%d %s:%d", q + 1, kQuadrantLabels[q],
1672 ImVec2(quadrant_button_width, 0))) {
1679 status.message().data());
1684 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
1689 if (ImGui::IsItemHovered()) {
1690 ImGui::BeginTooltip();
1691 ImGui::Text(
"Quadrant %s metadata", kQuadrantLabels[q]);
1693 ImGui::Text(
"Tile8: %02X", info.
id_);
1694 ImGui::Text(
"Palette: %d", quadrant_palette);
1697 ImGui::Text(
"Priority: %s", info.
over_ ?
"Y" :
"N");
1698 ImGui::TextDisabled(
"LMB: set brush palette from this quadrant");
1699 ImGui::TextDisabled(
1700 "RMB: apply current brush palette to this quadrant");
1701 ImGui::TextDisabled(
"Keys 1..4: focus TL/TR/BL/BR");
1702 ImGui::EndTooltip();
1710 ImGui::TextDisabled(
"Active %s: Tile8 %02X | P%d | H:%s V:%s | Pri:%s",
1715 active_info.
over_ ?
"Y" :
"N");
1717 if (Button(
"Apply Brush to Active Quadrant", ImVec2(-1, 0))) {
1722 "Copy the Brush Palette into the selected quadrant metadata.\n"
1723 "Use keys 1..4 to change active quadrant quickly.");
1727 if (Button(
"Apply Brush to All Quadrants", ImVec2(-1, 0))) {
1731 "Copy the Brush Palette into Tile Palette metadata for all 4 "
1733 "Tip: right-click any brush palette button above for a one-step apply.");
1734 return absl::OkStatus();
1738 ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.9f, 1.0f),
"Tile8 Source");
1742 if (BeginChild(
"##Tile8SourceScrollable", ImVec2(0, 0),
true,
1743 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1756 const bool left_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
1757 const bool right_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
1761 ImGui::IsItemHovered(), ImGui::IsMouseDown(ImGuiMouseButton_Right));
1765 if (left_clicked || right_clicked) {
1778 return absl::OkStatus();
1782 const ImGuiIO& io = ImGui::GetIO();
1785 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1791 return absl::OkStatus();
1796 if (right_clicked) {
1801 return absl::OkStatus();
1806 if (Button(
"Clear", ImVec2(-1, 0))) {
1810 if (Button(
"Copy", ImVec2(-1, 0))) {
1814 if (Button(
"Paste", ImVec2(-1, 0))) {
1818 return absl::OkStatus();
1822 bool current_tile_pending,
1823 int pending_count) {
1828 {{ImGuiCol_ChildBg, ImVec4(0.10f, 0.13f, 0.16f, 0.85f)}});
1829 if (ImGui::BeginChild(
1830 "##Tile16BottomActionRail", ImVec2(0, 64),
true,
1831 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
1832 ImGui::TextDisabled(
"Queue: %d tile%s staged", pending_count,
1833 pending_count == 1 ?
"" :
"s");
1835 ImGui::TextDisabled(
"| Mode: %s", EditModeLabel(
edit_mode_));
1837 const float spacing = ImGui::GetStyle().ItemSpacing.x;
1838 const float avail = ImGui::GetContentRegionAvail().x;
1839 const float button_width =
1840 std::max(110.0f, (avail - spacing * 3.0f) / 4.0f);
1843 ImGui::BeginDisabled();
1849 ImGui::EndDisabled();
1851 if (ImGui::IsItemHovered()) {
1852 ImGui::SetTooltip(
"Write all %d pending tile16 edits to ROM",
1858 ImGui::BeginDisabled();
1860 if (ImGui::Button(
"Discard Current", ImVec2(button_width, 0))) {
1864 ImGui::EndDisabled();
1869 ImGui::BeginDisabled();
1875 ImGui::EndDisabled();
1880 ImGui::BeginDisabled();
1882 if (ImGui::Button(
"Undo", ImVec2(button_width, 0))) {
1886 ImGui::EndDisabled();
1890 return absl::OkStatus();
1895 return absl::FailedPreconditionError(
1896 "Current graphics bitmap not initialized");
1906 const int total_tiles = tiles_per_row * total_rows;
1911 for (
int tile_y = 0; tile_y < total_rows; ++tile_y) {
1912 for (
int tile_x = 0; tile_x < tiles_per_row; ++tile_x) {
1917 for (
int py = 0; py < 8; ++py) {
1918 for (
int px = 0; px < 8; ++px) {
1919 int src_x = tile_x * 8 + px;
1920 int src_y = tile_y * 8 + py;
1922 int dst_index = py * 8 + px;
1932 tile_data[dst_index] = pixel_value;
1946 if (display_palette && !display_palette->
empty()) {
1950 "LoadTile8: display palette not available yet; deferring refresh");
1959 util::logf(
"Loaded %zu individual tile8 graphics",
1961 return absl::OkStatus();
1965 if (tile_id < 0 || tile_id >= kTile16Count) {
1966 return absl::OutOfRangeError(
1967 absl::StrFormat(
"Invalid tile16 id: %d", tile_id));
1971 return absl::FailedPreconditionError(
1972 "Tile16 blockset or ROM not initialized");
1992 bool bitmap_loaded =
false;
1996 pending_bitmap_it->second.is_active()) {
1998 pending_bitmap_it->second.vector());
2000 bitmap_loaded =
true;
2003 if (!tile_data.empty()) {
2004 for (
auto& pixel : tile_data) {
2010 bitmap_loaded =
true;
2014 if (!bitmap_loaded) {
2024 util::logf(
"SetCurrentTile: loaded tile %d successfully", tile_id);
2029 return absl::OkStatus();
2036 "RequestTileSwitch: Editor not initialized (blockset=%p, rom=%p)",
2042 if (target_tile_id < 0 || target_tile_id >= kTile16Count) {
2043 util::logf(
"RequestTileSwitch: Invalid target tile ID %d", target_tile_id);
2057 util::logf(
"Tile %d has pending changes, showing confirmation dialog",
2063 util::logf(
"Failed to switch to tile %d: %s", target_tile_id,
2064 status.message().data());
2070 if (tile_id < 0 || tile_id >= kTile16Count) {
2071 return absl::InvalidArgumentError(
"Invalid tile ID");
2074 return absl::FailedPreconditionError(
"ROM not available");
2088 bool bitmap_copied =
false;
2094 bitmap_copied =
true;
2096 pending_bitmap_it->second.is_active()) {
2098 pending_bitmap_it->second.vector());
2100 bitmap_copied =
true;
2103 if (!tile_pixels.empty()) {
2106 bitmap_copied =
true;
2110 if (bitmap_copied) {
2116 return absl::OkStatus();
2121 return absl::FailedPreconditionError(
"Clipboard is empty");
2145 return absl::OkStatus();
2150 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2153 return absl::FailedPreconditionError(
"No active tile16 to save");
2166 return absl::OkStatus();
2171 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2175 return absl::FailedPreconditionError(
"Scratch space slot is empty");
2199 return absl::OkStatus();
2204 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2208 return absl::OkStatus();
2214 return absl::FailedPreconditionError(
"No active tile16 to flip");
2230 return absl::OkStatus();
2235 return absl::FailedPreconditionError(
"No active tile16 to flip");
2251 return absl::OkStatus();
2256 return absl::FailedPreconditionError(
"No active tile16 to rotate");
2274 return absl::OkStatus();
2280 return absl::FailedPreconditionError(
"Source tile8 bitmap not active");
2287 return absl::InvalidArgumentError(
"Invalid tile8 ID");
2292 const gfx::TileInfo fill_info(
static_cast<uint16_t
>(tile8_id),
2295 for (
int quadrant = 0; quadrant < 4; ++quadrant) {
2309 return absl::OkStatus();
2314 return absl::FailedPreconditionError(
"No active tile16 to clear");
2320 for (
int quadrant = 0; quadrant < 4; ++quadrant) {
2334 return absl::OkStatus();
2342 new_palette = (new_palette + 1) % 8;
2344 new_palette = (new_palette == 0) ? 7 : new_palette - 1;
2353 return absl::OkStatus();
2358 return absl::OkStatus();
2361 if (palette_id >= 8) {
2362 return absl::InvalidArgumentError(
"Invalid palette ID");
2374 if (!display_palette || display_palette->
empty()) {
2375 return absl::OkStatus();
2378 const bool use_sub_palette_view =
2380 if (use_sub_palette_view) {
2382 const int palette_slot =
2384 if (palette_slot >= 0 &&
2385 static_cast<size_t>(palette_slot + 16) <= display_palette->
size()) {
2387 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
2400 return absl::OkStatus();
2404 if (palette_id >= 8) {
2405 return absl::InvalidArgumentError(
"Invalid palette ID");
2410 return absl::FailedPreconditionError(
"No current tile16 data");
2431 util::logf(
"Applied palette %d to all quadrants of tile %d", palette_id,
2433 return absl::OkStatus();
2437 uint8_t palette_id) {
2438 if (palette_id >= 8) {
2439 return absl::InvalidArgumentError(
"Invalid palette ID");
2441 if (quadrant < 0 || quadrant > 3) {
2442 return absl::InvalidArgumentError(
"Invalid quadrant index");
2447 return absl::FailedPreconditionError(
"No current tile16 data");
2452 return absl::InvalidArgumentError(
"Invalid quadrant index");
2463 util::logf(
"Applied palette %d to quadrant %d of tile %d", palette_id,
2465 return absl::OkStatus();
2548 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2551 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2552 return absl::OutOfRangeError(
"Current tile16 ID out of range");
2556 return absl::OutOfRangeError(
"Current palette ID out of range");
2559 return absl::OkStatus();
2563 return tile16_blockset_ !=
nullptr && tile_id >= 0 && tile_id < kTile16Count;
2569 return absl::FailedPreconditionError(
"ROM not available");
2573 return absl::FailedPreconditionError(
"No active tile16 to save");
2596 return absl::OkStatus();
2601 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2604 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2605 return absl::OutOfRangeError(
"Current tile16 ID out of range");
2611 return absl::OkStatus();
2616 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2631 return absl::OkStatus();
2657 util::logf(
"Committed Tile16 %d changes to overworld system",
2659 return absl::OkStatus();
2673 return absl::OkStatus();
2678 return absl::OkStatus();
2682 util::logf(
"Committing %zu pending tile16 changes to ROM",
2689 util::logf(
"Failed to write tile16 %d: %s", tile_id,
2690 status.message().data());
2712 util::logf(
"All pending tile16 changes committed successfully");
2713 return absl::OkStatus();
2721 util::logf(
"Discarding %zu pending tile16 changes",
2731 util::logf(
"Failed to reload tile after discard: %s",
2732 status.message().data());
2748 util::logf(
"Failed to reload tile after discard: %s",
2749 status.message().data());
2754 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2770 return absl::FailedPreconditionError(
"ROM not available for usage cache");
2774 auto tile_provider = [
this](
int tile_id) -> absl::StatusOr<gfx::Tile16> {
2777 return pending_it->second;
2786 return absl::OkStatus();
2797 if (!cache_status.ok()) {
2798 util::logf(
"Tile8 usage cache rebuild failed: %s",
2799 cache_status.message().data());
2809 ImDrawList* draw_list = ImGui::GetWindowDrawList();
2812 const float tile16_display = 32.0f * scale;
2813 const float quadrant_display = 16.0f * scale;
2814 const int tiles_per_row =
2817 for (
const auto& hit : hits) {
2818 const int tile_x = hit.tile16_id % tiles_per_row;
2819 const int tile_y = hit.tile16_id / tiles_per_row;
2820 const int quad_x = hit.quadrant % 2;
2821 const int quad_y = hit.quadrant / 2;
2824 canvas_pos.x + (tile_x * tile16_display) + (quad_x * quadrant_display),
2825 canvas_pos.y + (tile_y * tile16_display) + (quad_y * quadrant_display));
2826 const ImVec2 max(min.x + quadrant_display, min.y + quadrant_display);
2829 draw_list->AddRectFilled(min, max, IM_COL32(150, 0, 210, 80));
2830 draw_list->AddRect(min, max, IM_COL32(215, 170, 255, 180));
2835 if (!
rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2836 return absl::InvalidArgumentError(
"Invalid tile16 or ROM not set");
2840 int quad_x = (position.x < 8) ? 0 : 1;
2841 int quad_y = (position.y < 8) ? 0 : 1;
2842 int quadrant = quad_x + (quad_y * 2);
2848 return absl::FailedPreconditionError(
"Failed to get tile16 data");
2852 gfx::TileInfo tile_info = TileInfoForQuadrant(*tile16_data, quadrant);
2867 util::logf(
"Picked tile8 %d with palette %d from quadrant %d of tile16 %d",
2870 return absl::OkStatus();
2880 switch (sheet_index) {
2898 return static_cast<int>(
2909 int sheet_index)
const {
2910 const int clamped_button = std::clamp(palette_button, 0, 7);
2912 const int actual_row = std::clamp(base_row + clamped_button, 0, 15);
2915 return actual_row * 16;
2923 constexpr int kTilesPerSheet = 256;
2924 int sheet_index = tile8_id / kTilesPerSheet;
2927 return std::min(7, std::max(0, sheet_index));
2957 switch (sheet_index) {
2991 int sheet_index = 0;
2997 const int actual_target_row = std::clamp(base_row + target_row, 0, 15);
2999 for (
int i = 0; i < 256; ++i) {
3000 int low_nibble = i & 0x0F;
3001 int target_index = (actual_target_row * 16) + low_nibble;
3004 if (low_nibble == 0) {
3007 }
else if (target_index <
static_cast<int>(source.
size())) {
3008 remapped.
AddColor(source[target_index]);
3028 return pixel_value / 16;
3050 for (
size_t i = 0; i < bitmap.
size(); ++i) {
3051 if ((bitmap.
data()[i] & 0xF0) != 0) {
3065 if (!display_palette || display_palette->
empty()) {
3066 fallback_palette = BuildFallbackDisplayPalette();
3067 display_palette = &fallback_palette;
3077 if (palette_slot >= 0 &&
3078 static_cast<size_t>(palette_slot + 16) <= display_palette->
size()) {
3080 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
3097 return absl::InvalidArgumentError(
"Invalid tile8 ID");
3101 return absl::FailedPreconditionError(
"ROM not set");
3105 if (!display_palette || display_palette->
empty()) {
3106 return absl::FailedPreconditionError(
"No overworld palette available");
3110 if (current_palette_ < 0 || current_palette_ >= 8) {
3116 const int palette_slot =
3119 util::logf(
"Updated tile8 %d with palette slot %d (palette size: %zu colors)",
3122 return absl::OkStatus();
3127 return absl::FailedPreconditionError(
"ROM not set");
3131 if (current_palette_ < 0 || current_palette_ >= 8) {
3138 if (!display_palette || display_palette->
empty()) {
3139 fallback_palette = BuildFallbackDisplayPalette();
3140 display_palette = &fallback_palette;
3141 util::logf(
"Display palette unavailable; using fallback grayscale palette");
3143 util::logf(
"Using resolved display palette with %zu colors",
3144 display_palette->
size());
3154 util::logf(
"Applied remapped palette (button %d) to source bitmap",
3158 util::logf(
"Applied full CGRAM palette to source bitmap");
3171 if (!regen_status.ok()) {
3181 "Successfully refreshed all palettes in tile16 editor with palette %d",
3183 return absl::OkStatus();
3187 util::logf(
"=== TILE8 SOURCE DATA ANALYSIS ===");
3201 std::map<uint8_t, int> pixel_counts;
3202 for (
size_t i = 0; i < 64; ++i) {
3204 pixel_counts[val]++;
3206 util::logf(
" - First tile8 (Sheet 0) pixel distribution:");
3207 for (
const auto& [val, count] : pixel_counts) {
3209 int col = val & 0x0F;
3210 util::logf(
" Value 0x%02X (%3d) = Row %d, Col %d: %d pixels", val, val,
3215 bool all_4bpp =
true;
3216 for (
const auto& [val, count] : pixel_counts) {
3222 util::logf(
" - Values in raw 4bpp range (0-15): %s",
3223 all_4bpp ?
"yes" :
"NO (pre-encoded)");
3226 util::logf(
" - Palette remapping for viewing:");
3229 util::logf(
" Pixels are remapped: (value & 0x0F) + (selected_row * 16)");
3241 std::map<uint8_t, int> pixel_counts;
3242 for (uint8_t val : first_tile) {
3243 pixel_counts[val]++;
3246 for (
const auto& [val, count] : pixel_counts) {
3247 util::logf(
" Value 0x%02X (%3d): %d pixels", val, val, count);
3261 util::logf(
" - Expected palette offset for SetPaletteWithTransparent: %d",
3266 util::logf(
" - First 16 palette colors (row 0):");
3267 for (
int i = 0; i < 16; ++i) {
3269 util::logf(
" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
3270 static_cast<int>(color.rgb().x),
3271 static_cast<int>(color.rgb().y),
3272 static_cast<int>(color.rgb().z));
3278 util::logf(
" - Colors at palette slot %d (row %d):", palette_slot,
3280 for (
int i = 0; i < 16; ++i) {
3282 util::logf(
" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
3283 static_cast<int>(color.rgb().x),
3284 static_cast<int>(color.rgb().y),
3285 static_cast<int>(color.rgb().z));
3295 Text(
"Pixel Normalization & Color Correction:");
3298 if (SliderInt(
"Normalization Mask", &mask_value, 1, 255,
"0x%02X")) {
3304 if (Button(
"Apply to All Graphics")) {
3306 if (!reload_result.ok()) {
3307 Text(
"Error: %s", reload_result.message().data());
3312 if (Button(
"Reset Defaults")) {
3316 (void)reload_result;
3320 Text(
"Current State:");
3321 static constexpr std::array<const char*, 7> palette_group_names = {
3322 "OW Main",
"OW Aux",
"OW Anim",
"Dungeon",
3323 "Sprites",
"Armor",
"Sword"};
3331 Text(
"Sheet-Specific Fixes:");
3334 static bool fix_sheet_0 =
true;
3335 static bool fix_sprite_sheets =
true;
3336 static bool use_transparent_for_terrain =
false;
3338 if (Checkbox(
"Fix Sheet 0 (Trees)", &fix_sheet_0)) {
3340 if (!reload_result.ok()) {
3341 Text(
"Error reloading: %s", reload_result.message().data());
3345 "Use direct palette for sheet 0 instead of transparent palette");
3347 if (Checkbox(
"Fix Sprite Sheets", &fix_sprite_sheets)) {
3349 if (!reload_result.ok()) {
3350 Text(
"Error reloading: %s", reload_result.message().data());
3353 HOVER_HINT(
"Use direct palette for sprite graphics sheets");
3355 if (Checkbox(
"Transparent for Terrain", &use_transparent_for_terrain)) {
3357 if (!reload_result.ok()) {
3358 Text(
"Error reloading: %s", reload_result.message().data());
3361 HOVER_HINT(
"Force transparent palette for terrain graphics");
3364 Text(
"Color Analysis:");
3367 Text(
"Selected Tile8 Analysis:");
3369 std::map<uint8_t, int> pixel_counts;
3370 for (uint8_t pixel : tile_data) {
3371 pixel_counts[pixel & 0x0F]++;
3374 Text(
"Pixel Value Distribution:");
3375 for (
const auto& pair : pixel_counts) {
3376 int value = pair.first;
3377 int count = pair.second;
3378 Text(
" Value %d (0x%X): %d pixels", value, value, count);
3381 Text(
"Palette Colors Used:");
3383 if (analysis_palette ==
nullptr || analysis_palette->
empty()) {
3387 for (
const auto& pair : pixel_counts) {
3388 int value = pair.first;
3389 int count = pair.second;
3390 if (value <
static_cast<int>(analysis_palette->
size())) {
3391 auto color = (*analysis_palette)[value];
3392 ImVec4 display_color = color.rgb();
3393 ImGui::ColorButton((
"##analysis" + std::to_string(value)).c_str(),
3394 display_color, ImGuiColorEditFlags_NoTooltip,
3396 if (ImGui::IsItemHovered()) {
3397 ImGui::SetTooltip(
"Index %d: 0x%04X (%d pixels)", value,
3398 color.snes(), count);
3408 if (CollapsingHeader(
"ROM Palette Manager") &&
rom_) {
3409 Text(
"Experimental ROM Palette Selection:");
3411 "Use ROM palettes to experiment with different color schemes");
3413 if (Button(
"Open Enhanced Palette Editor")) {
3417 if (Button(
"Show Color Analysis")) {
3422 static int quick_group = 0;
3423 static int quick_index = 0;
3425 SliderInt(
"ROM Group", &quick_group, 0, 6);
3426 SliderInt(
"Palette Index", &quick_index, 0, 7);
3428 if (Button(
"Apply to Tile8 Source")) {
3430 util::logf(
"Applied ROM palette group %d, index %d to Tile8 source",
3431 quick_group, quick_index);
3435 if (Button(
"Apply to Tile16 Editor")) {
3438 "Applied ROM palette group %d, index %d to Tile16 editor",
3439 quick_group, quick_index);
3449 Text(
"Layout Scratch:");
3450 for (
int i = 0; i < 4; ++i) {
3452 std::string slot_name =
"S" + std::to_string(i + 1);
3454 if (Button((slot_name +
" Save").c_str(), ImVec2(70, 20))) {
3461 ImGui::BeginDisabled();
3463 if (Button((slot_name +
" Load").c_str(), ImVec2(70, 20)) && can_load) {
3467 ImGui::EndDisabled();
3476 if (slot < 0 || slot >= 4) {
3477 return absl::InvalidArgumentError(
"Invalid scratch slot");
3481 if (total_tiles <= 0) {
3482 return absl::FailedPreconditionError(
"Tile16 blockset is not available");
3485 const int start_tile = std::clamp(
current_tile16_, 0, total_tiles - 1);
3486 for (
int y = 0; y < 8; ++y) {
3487 for (
int x = 0; x < 8; ++x) {
3488 const int tile_id = start_tile + (y * 8) + x;
3490 (tile_id < total_tiles) ? tile_id : -1;
3496 absl::StrFormat(
"From %03X",
static_cast<uint16_t
>(start_tile));
3498 return absl::OkStatus();
3502 if (slot < 0 || slot >= 4) {
3503 return absl::InvalidArgumentError(
"Invalid scratch slot");
3507 return absl::FailedPreconditionError(
"Scratch slot is empty");
3511 if (first_tile < 0) {
3512 return absl::FailedPreconditionError(
"Scratch slot has no valid tile data");
3521 for (
int y = 0; y < 8; ++y) {
3522 for (
int x = 0; x < 8; ++x) {
3531 return absl::OkStatus();
3535 if (ImGui::BeginPopupModal(
"ManualTile8Editor",
nullptr,
3536 ImGuiWindowFlags_AlwaysAutoResize)) {
3537 ImGui::Text(
"Manual Tile8 Configuration for Tile16 %02X",
current_tile16_);
3542 ImGui::Text(
"Current Tile16 Staged Data:");
3544 auto stage_current_tile = [&]() -> absl::Status {
3545 SyncTilesInfoArray(tile_data);
3552 return absl::OkStatus();
3556 const char* quadrant_names[] = {
"Top-Left",
"Top-Right",
"Bottom-Left",
3559 for (
int q = 0; q < 4; q++) {
3560 ImGui::Text(
"%s Quadrant:", quadrant_names[q]);
3561 ImGui::TextDisabled(
"Tile Palette metadata + Tile8/flip flags");
3567 tile_info = &tile_data->tile0_;
3570 tile_info = &tile_data->tile1_;
3573 tile_info = &tile_data->tile2_;
3576 tile_info = &tile_data->tile3_;
3584 int tile_id_int =
static_cast<int>(tile_info->
id_);
3585 if (ImGui::InputInt(
"Tile8 ID", &tile_id_int, 1, 10)) {
3587 static_cast<uint16_t
>(std::max(0, std::min(tile_id_int, 1023)));
3590 int palette_int =
static_cast<int>(tile_info->
palette_);
3591 if (ImGui::SliderInt(
"Tile Palette", &palette_int, 0, 7)) {
3592 tile_info->
palette_ =
static_cast<uint8_t
>(palette_int);
3599 ImGui::Checkbox(
"Priority", &tile_info->
over_);
3601 if (ImGui::Button(
"Stage Quadrant Edit")) {
3602 auto stage_result = stage_current_tile();
3603 if (!stage_result.ok()) {
3604 ImGui::Text(
"Stage Error: %s", stage_result.message().data());
3616 if (ImGui::Button(
"Stage All Edits")) {
3617 auto stage_result = stage_current_tile();
3618 if (!stage_result.ok()) {
3619 ImGui::Text(
"Stage Error: %s", stage_result.message().data());
3623 if (ImGui::Button(
"Write Pending to ROM")) {
3625 if (!write_result.ok()) {
3626 ImGui::Text(
"Write Error: %s", write_result.message().data());
3630 if (ImGui::Button(
"Refresh Display")) {
3632 if (!refresh_result.ok()) {
3633 ImGui::Text(
"Refresh Error: %s", refresh_result.message().data());
3638 ImGui::Text(
"Tile16 data not accessible");
3641 ImGui::Text(
"Valid range: 0-4095 (4096 total tiles)");
3646 if (ImGui::Button(
"Close")) {
3647 ImGui::CloseCurrentPopup();
3657 return absl::OkStatus();
3662 return absl::OkStatus();
3668 return absl::OkStatus();
3681 if (display_palette && !display_palette->
empty()) {
3682 const bool use_sub_palette_view =
3684 if (use_sub_palette_view) {
3686 const int palette_slot =
3688 if (palette_slot >= 0 &&
3689 static_cast<size_t>(palette_slot + 16) <= display_palette->
size()) {
3691 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
3707 return absl::OkStatus();
3711 if (!ImGui::IsAnyItemActive()) {
3712 const ImGuiIO& io = ImGui::GetIO();
3713#if defined(__APPLE__)
3714 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
3716 const bool platform_primary_held = io.KeyCtrl;
3718 const bool ctrl_held = platform_primary_held ||
3719 ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
3720 ImGui::IsKeyDown(ImGuiKey_RightCtrl);
3723 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
3726 if (ImGui::IsKeyPressed(ImGuiKey_H) && !ctrl_held) {
3729 if (ImGui::IsKeyPressed(ImGuiKey_V) && !ctrl_held) {
3732 if (ImGui::IsKeyPressed(ImGuiKey_R) && !ctrl_held) {
3735 if (ImGui::IsKeyPressed(ImGuiKey_F) && !ctrl_held) {
3743 if (ImGui::IsKeyPressed(ImGuiKey_P)) {
3746 if (ImGui::IsKeyPressed(ImGuiKey_I)) {
3749 if (ImGui::IsKeyPressed(ImGuiKey_U)) {
3755 if (ImGui::IsKeyPressed(ImGuiKey_Q)) {
3758 if (ImGui::IsKeyPressed(ImGuiKey_E)) {
3765 for (
int i = 0; i < 8; ++i) {
3766 if (!ImGui::IsKeyPressed(
static_cast<ImGuiKey
>(ImGuiKey_1 + i))) {
3782 if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
3785 if (ImGui::IsKeyPressed(ImGuiKey_Y)) {
3788 if (ImGui::IsKeyPressed(ImGuiKey_C)) {
3791 if (ImGui::IsKeyPressed(ImGuiKey_V)) {
3794 if (ImGui::IsKeyPressed(ImGuiKey_S)) {
3795 if (ImGui::IsKeyDown(ImGuiKey_LeftShift) ||
3796 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 DrawTile8SourcePanel()
absl::Status SaveTile16ToScratchSpace(int slot)
const gfx::SnesPalette * ResolveDisplayPalette() const
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 HandleTile16CanvasClick(const ImVec2 &tile_position, bool left_click, bool right_click)
absl::Status ClearTile16()
void HandleKeyboardShortcuts()
std::chrono::steady_clock::time_point last_rom_write_time_
absl::Status FillTile16WithTile8(int tile8_id)
gfx::Tilemap * tile16_blockset_
bool map_blockset_loaded_
bool BitmapHasEncodedPaletteRows(const gfx::Bitmap &bitmap) const
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).
absl::Status ApplyPaletteToAll(uint8_t palette_id)
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()
std::function< void(int)> on_current_tile_changed_
absl::Status DiscardChanges()
Discard current tile's changes (single tile)
bool show_palette_settings_
uint8_t palette_normalization_mask_
bool tile8_usage_cache_dirty_
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_
int last_rom_write_count_
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_
gfx::Bitmap * tile16_blockset_bmp_
std::vector< zelda3::Tile8PixelData > current_gfx_individual_
absl::Status UpdateTile16Edit()
gfx::SnesPalette overworld_palette_
absl::Status UpdateBlockset()
static constexpr int kTilesPerPage
absl::Status DrawBrushAndTilePaletteControls(bool show_debug_info)
int GetEncodedPaletteRow(uint8_t pixel_value) const
Get the encoded palette row for a pixel value.
static constexpr int kTilesPerRow
void DrawTile8UsageOverlay()
bool auto_normalize_pixels_
void EnableLivePreview(bool enable)
Tile16EditMode edit_mode_
absl::Status SetCurrentTile(int id)
gfx::Bitmap * current_gfx_bmp_
absl::Status UpdateTile8Palette(int tile8_id)
Update palette for a specific tile8.
zelda3::Tile8UsageIndex tile8_usage_cache_
absl::Status UpdateAsPanel()
Update the editor content without MenuBar (for WindowContent 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.
bool HasTile16BlocksetBitmap() const
void DrawPaletteSettings()
Draw palette settings UI.
bool highlight_tile8_usage_
absl::Status ApplyPaletteToQuadrant(int quadrant, uint8_t palette_id)
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_
void DrawStagedStateBar(bool has_pending, bool current_tile_pending, int pending_count)
gui::Canvas blockset_canvas_
gui::Canvas tile8_source_canvas_
bool IsTile16Valid(int tile_id) const
gfx::Tile16 * GetCurrentTile16Data()
void MarkCurrentTileModified()
Mark the current tile as having pending changes.
absl::Status RebuildTile8UsageCache()
bool has_pending_changes() const
Check if any tiles have uncommitted changes.
absl::Status CommitChangesToOverworld()
Single-tile commit: ROM + blockset + parent refresh callback. Prefer CommitAllChanges() from the main...
absl::Status UpdateBlocksetBitmap()
void DiscardCurrentTileChanges()
Discard only the current tile's pending changes.
void RequestTileSwitch(int target_tile_id)
void DrawEditorHeaderToggles(bool *show_debug_info, bool *show_advanced_controls)
absl::Status DrawToCurrentTile16(ImVec2 pos, const gfx::Bitmap *source_tile=nullptr)
absl::Status HandleTile8SourceSelection(bool right_clicked)
absl::Status UpdateLivePreview()
absl::Status Initialize(gfx::Bitmap &tile16_blockset_bmp, gfx::Bitmap ¤t_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
void CopyTileBitmapToBlockset(int tile_id, const gfx::Bitmap &tile_bitmap)
absl::Status PreviewPaletteChange(uint8_t palette_id)
gfx::Bitmap tile8_preview_bmp_
absl::Status CopyTile16ToClipboard(int tile_id)
absl::Status DrawPrimaryActionControls()
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.
bool HasCurrentGfxBitmap() const
gui::Table tile_edit_table_
std::function< absl::Status()> on_changes_committed_
absl::Status BuildTile16BitmapFromData(const gfx::Tile16 &tile_data, gfx::Bitmap *output_bitmap) const
void DrawEditorHeader(bool show_debug_info)
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 DrawBottomActionRail(bool has_pending, bool current_tile_pending, int pending_count)
absl::Status FlipTile16Vertical()
gfx::Bitmap current_tile16_bmp_
bool has_rom_write_history_
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 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 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)
static constexpr size_t kMaxColors
Tile composition of four 8x8 tiles.
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)
const char * EditModeLabel(Tile16EditMode mode)
void SyncTilesInfoArray(gfx::Tile16 *tile)
gfx::TileInfo & TileInfoForQuadrant(gfx::Tile16 *tile, int quadrant)
gfx::SnesPalette BuildFallbackDisplayPalette()
constexpr int kTile16Count
constexpr int kTile16PixelCount
constexpr int kTile8PixelCount
constexpr float kTile8DisplayScale
bool ComputeTile8UsageHighlight(bool source_hovered, bool right_mouse_down)
constexpr int kTile16Size
constexpr int kNumScratchSlots
int ComputeTile8IndexFromCanvasMouse(float mouse_x, float mouse_y, int source_bitmap_width_px, int max_tile_count, float display_scale)
Tile16ActionControlState ComputeTile16ActionControlState(bool has_pending, bool current_tile_pending, bool can_undo)
Tile16NumericShortcutResult ResolveTile16NumericShortcut(bool ctrl_held, int number_index)
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)
gfx::Tile16 HorizontalFlipTile16(gfx::Tile16 tile)
gfx::TileInfo & MutableTile16QuadrantInfo(gfx::Tile16 &tile, int quadrant)
void SetTile16AllQuadrantPalettes(gfx::Tile16 *tile, uint8_t palette_id)
absl::Status RenderTile16BitmapFromMetadata(const gfx::Tile16 &tile_data, const std::vector< Tile8PixelData > &tile8_pixels, gfx::Bitmap *output_bitmap)
void SyncTile16TilesInfo(gfx::Tile16 *tile)
constexpr int kNumTile16Individual
gfx::Tile16 VerticalFlipTile16(gfx::Tile16 tile)
gfx::Tile16 RotateTile16Clockwise(gfx::Tile16 tile)
constexpr uint32_t kTile16Ptr
int ComputeTile16Count(const gfx::Tilemap *tile16_blockset)
absl::StatusOr< std::vector< Tile16StampMutation > > BuildTile16StampMutations(const Tile16StampRequest &request)
const gfx::TileInfo & Tile16QuadrantInfo(const gfx::Tile16 &tile, int quadrant)
absl::Status BuildTile8UsageIndex(int total_tiles, const std::function< absl::StatusOr< gfx::Tile16 >(int)> &tile_provider, Tile8UsageIndex *usage_index)
void BlitTile16BitmapToAtlas(gfx::Bitmap *destination, int tile_id, const gfx::Bitmap &source_bitmap)
bool SetTile16QuadrantPalette(gfx::Tile16 *tile, int quadrant, uint8_t palette_id)
constexpr int kMaxTile8UsageId
std::array< uint8_t, 64 > Tile8PixelData
#define RETURN_IF_ERROR(expr)
std::optional< int > quadrant_focus
std::optional< uint8_t > palette_id
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
Bitmap atlas
Master bitmap containing all tiles.
std::optional< float > grid_step
gfx::PaletteGroupMap palette_groups
gfx::Tile16 current_tile16