8#include "absl/status/status.h"
9#include "absl/strings/str_format.h"
24#include "imgui/imgui.h"
49 std::vector<gfx::SnesColor> grayscale;
52 const float value =
static_cast<float>(i) / 255.0f;
53 grayscale.emplace_back(ImVec4(value, value, value, 1.0f));
55 if (!grayscale.empty()) {
56 grayscale[0].set_transparent(
true);
91 std::array<uint8_t, 0x200>& all_tiles_types) {
133 ImVector<std::string> tile16_names;
134 for (
int i = 0; i < 0x200; ++i) {
136 tile16_names.push_back(str);
147 Checkbox(tr(
"X Flip"), &
x_flip);
148 Checkbox(tr(
"Y Flip"), &
y_flip);
152 return absl::OkStatus();
157 return absl::InvalidArgumentError(
"Blockset not initialized, open a ROM.");
160 if (BeginMenuBar()) {
161 if (BeginMenu(tr(
"View"))) {
162 Checkbox(tr(
"Show Collision Types"),
167 if (BeginMenu(tr(
"Edit"))) {
168 if (MenuItem(tr(
"Copy Current Tile16"),
"Ctrl+C")) {
171 if (MenuItem(tr(
"Paste to Current Tile16"),
"Ctrl+V")) {
177 if (BeginMenu(tr(
"File"))) {
178 if (MenuItem(tr(
"Write Pending to ROM"),
"Ctrl+S")) {
181 if (MenuItem(tr(
"Refresh Blockset Preview"),
"Ctrl+Shift+S")) {
186 if (MenuItem(tr(
"Live Preview"),
nullptr, &live_preview)) {
192 if (BeginMenu(tr(
"Scratch Space"))) {
193 for (
int i = 0; i < 4; i++) {
194 std::string slot_name =
"Slot " + std::to_string(i + 1);
196 if (MenuItem((slot_name +
" (Load)").c_str())) {
199 if (MenuItem((slot_name +
" (Save)").c_str())) {
202 if (MenuItem((slot_name +
" (Clear)").c_str())) {
206 if (MenuItem((slot_name +
" (Save)").c_str())) {
220 if (BeginPopupModal(
"About Tile16 Editor", NULL,
221 ImGuiWindowFlags_AlwaysAutoResize)) {
222 Text(tr(
"Tile16 Editor for Link to the Past"));
223 Text(tr(
"This editor allows you to edit 16x16 tiles used in the game."));
224 Text(tr(
"Features:"));
226 tr(
"Edit Tile16 graphics by placing 8x8 tiles in the quadrants"));
227 BulletText(tr(
"Copy and paste Tile16 graphics"));
228 BulletText(tr(
"Save and load Tile16 graphics to/from scratch space"));
229 BulletText(tr(
"Preview Tile16 graphics at a larger size"));
231 if (Button(tr(
"Close"))) {
239 OpenPopup(
"Unsaved Changes##Tile16Editor");
241 if (BeginPopupModal(
"Unsaved Changes##Tile16Editor", NULL,
242 ImGuiWindowFlags_AlwaysAutoResize)) {
244 Text(tr(
"What would you like to do?"));
247 if (Button(tr(
"Keep Staged & Continue"), ImVec2(220, 0))) {
248 if (IsItemHovered()) {
250 "Switch to the requested tile now. Other tiles with staged edits "
251 "stay in the write queue until you use Write Pending or Discard."));
271 util::logf(
"Failed to write/switch pending changes: %s",
272 status.message().data());
294 if (Button(tr(
"Cancel"), ImVec2(220, 0))) {
314 return absl::OkStatus();
324 return absl::InvalidArgumentError(
"Blockset not initialized, open a ROM.");
329 OpenPopup(
"##Tile16EditorContextMenu");
332 TextDisabled(tr(
"Right-click for more options"));
338 if (BeginPopupModal(
"About Tile16 Editor", NULL,
339 ImGuiWindowFlags_AlwaysAutoResize)) {
340 Text(tr(
"Tile16 Editor for Link to the Past"));
341 Text(tr(
"This editor allows you to edit 16x16 tiles used in the game."));
342 Text(tr(
"Features:"));
344 tr(
"Edit Tile16 graphics by placing 8x8 tiles in the quadrants"));
345 BulletText(tr(
"Copy and paste Tile16 graphics"));
346 BulletText(tr(
"Save and load Tile16 graphics to/from scratch space"));
347 BulletText(tr(
"Preview Tile16 graphics at a larger size"));
349 if (Button(tr(
"Close"))) {
357 OpenPopup(
"Unsaved Changes##Tile16Editor");
359 if (BeginPopupModal(
"Unsaved Changes##Tile16Editor", NULL,
360 ImGuiWindowFlags_AlwaysAutoResize)) {
362 Text(tr(
"What would you like to do?"));
365 if (Button(tr(
"Keep Staged & Continue"), ImVec2(220, 0))) {
366 if (IsItemHovered()) {
368 "Switch to the requested tile now. Other tiles with staged edits "
369 "stay in the write queue until you use Write Pending or Discard."));
389 util::logf(
"Failed to write/switch pending changes: %s",
390 status.message().data());
412 if (Button(tr(
"Cancel"), ImVec2(220, 0))) {
428 return absl::OkStatus();
432 if (BeginPopup(
"##Tile16EditorContextMenu")) {
433 if (BeginMenu(tr(
"View"))) {
437 if (MenuItem(tr(
"Show Collision IDs"),
nullptr,
446 if (BeginMenu(tr(
"Edit"))) {
447 if (MenuItem(tr(
"Copy Current Tile16"),
"Ctrl+C")) {
450 if (MenuItem(tr(
"Paste to Current Tile16"),
"Ctrl+V")) {
454 if (MenuItem(tr(
"Flip Horizontal"),
"H")) {
457 if (MenuItem(tr(
"Flip Vertical"),
"V")) {
460 if (MenuItem(tr(
"Rotate"),
"R")) {
463 if (MenuItem(tr(
"Clear"),
"Delete")) {
469 if (BeginMenu(tr(
"File"))) {
470 if (MenuItem(tr(
"Write Pending to ROM"),
"Ctrl+S")) {
473 if (MenuItem(tr(
"Refresh Blockset Preview"),
"Ctrl+Shift+S")) {
478 if (MenuItem(tr(
"Live Preview"),
nullptr, &live_preview)) {
484 if (BeginMenu(tr(
"Scratch Space"))) {
485 for (
int i = 0; i < 4; i++) {
486 std::string slot_name =
"Slot " + std::to_string(i + 1);
488 if (MenuItem((slot_name +
" (Load)").c_str())) {
491 if (MenuItem((slot_name +
" (Save)").c_str())) {
494 if (MenuItem((slot_name +
" (Clear)").c_str())) {
498 if (MenuItem((slot_name +
" (Save)").c_str())) {
539 return absl::FailedPreconditionError(
540 "Tile16 blockset bitmap not initialized");
546 if (result.selection_changed) {
549 util::logf(
"Selected Tile16 from blockset: %d", result.selected_tile);
555 return absl::OkStatus();
560 if (!
rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
569 return absl::FailedPreconditionError(
"Cannot access current tile16 data");
577 return absl::OkStatus();
582 return absl::FailedPreconditionError(
"Tile16 blockset not available");
595 util::logf(
"Tile16 blockset refreshed and regenerated");
596 return absl::OkStatus();
629 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
632 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
633 return absl::OutOfRangeError(
"Current tile16 ID out of range");
637 return absl::FailedPreconditionError(
"Current tile16 bitmap is not active");
651 return absl::OkStatus();
659 return absl::FailedPreconditionError(
"Cannot access current tile16 data");
666 return absl::FailedPreconditionError(
"Tile8 source bitmap not active");
681 util::logf(
"Regenerated Tile16 bitmap for tile %d from ROM data",
683 return absl::OkStatus();
692 auto now = std::chrono::steady_clock::now();
693 auto time_since_last_edit =
694 std::chrono::duration_cast<std::chrono::milliseconds>(now -
698 if (time_since_last_edit > 100) {
706 return absl::OutOfRangeError(
711 return absl::FailedPreconditionError(
"Target tile16 bitmap not active");
714 const int tile8_count =
716 const int max_tile8_id = std::max(0, tile8_count - 1);
717 const int tile8_row_stride =
719 const int quadrant_x = (pos.x >=
kTile8Size) ? 1 : 0;
720 const int quadrant_y = (pos.y >=
kTile8Size) ? 1 : 0;
721 const int quadrant_index = quadrant_x + (quadrant_y * 2);
742 for (
const auto& mutation : staged_tiles) {
743 const int tile16_id = mutation.tile16_id;
786 "Local tile16 stamp staged (size=%dx, tiles=%zu). Use 'Write Pending' to "
790 return absl::OkStatus();
796 if (!left_click && !right_click) {
797 return absl::OkStatus();
802 util::logf(
"Picked tile8 from tile16 at (%d, %d)",
803 static_cast<int>(tile_position.x),
804 static_cast<int>(tile_position.y));
805 return absl::OkStatus();
821 return absl::OkStatus();
825 const ImVec2& display_position) {
831 static bool show_advanced_controls =
false;
832 static bool show_debug_info =
false;
836 {{ImGuiStyleVar_FramePadding, ImVec2(8, 4)},
837 {ImGuiStyleVar_ItemSpacing, ImVec2(8, 4)}});
844 pending_count, &show_debug_info,
845 &show_advanced_controls));
849 const float layout_width = ImGui::GetContentRegionAvail().x;
850 const bool compact_layout = layout_width < 1040.0f;
851 const int layout_columns = compact_layout ? 1 : 3;
855 if (ImGui::BeginTable(
856 "##Tile16EditLayout", layout_columns,
857 ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) {
858 if (compact_layout) {
859 ImGui::TableSetupColumn(
"Tile16 Workbench",
860 ImGuiTableColumnFlags_WidthStretch, 1.0f);
862 ImGui::TableSetupColumn(
"Tile16 Blockset",
863 ImGuiTableColumnFlags_WidthStretch, 0.30f);
864 ImGui::TableSetupColumn(
"Tile8 Source",
865 ImGuiTableColumnFlags_WidthStretch, 0.48f);
866 ImGui::TableSetupColumn(
"Editor", ImGuiTableColumnFlags_WidthStretch,
870 ImGui::TableNextRow();
873 ImGui::TableNextColumn();
876 ImGui::Text(tr(
"Tile16 Blockset"));
882 std::max(0, total_tiles - 1));
890 ImGui::SetNextItemWidth(80);
899 if (ImGui::IsItemHovered()) {
900 ImGui::SetTooltip(tr(
"Tile ID (0-%d) - navigates as you type"),
909 if (ImGui::Button(
"<<")) {
913 if (ImGui::IsItemHovered())
914 ImGui::SetTooltip(tr(
"First page"));
917 if (ImGui::Button(
"<")) {
922 if (ImGui::IsItemHovered())
923 ImGui::SetTooltip(tr(
"Previous page (PageUp)"));
926 ImGui::TextDisabled(tr(
"Page %d/%d"),
current_page_ + 1, total_pages);
929 if (ImGui::Button(
">")) {
935 if (ImGui::IsItemHovered())
936 ImGui::SetTooltip(tr(
"Next page (PageDown)"));
939 if (ImGui::Button(
">>")) {
943 if (ImGui::IsItemHovered())
944 ImGui::SetTooltip(tr(
"Last page"));
947 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
948 if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) {
953 if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) {
959 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
963 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
969 if (!ImGui::GetIO().KeyCtrl) {
970 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
976 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
982 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
988 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
1000 const float blockset_height =
1004 std::max(200.0f, ImGui::GetContentRegionAvail().y * 0.38f))
1005 : ImGui::GetContentRegionAvail().y;
1006 if (BeginChild(
"##BlocksetScrollable", ImVec2(0, blockset_height),
true,
1007 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1032 if (result.selection_changed) {
1034 util::logf(
"Selected Tile16 from blockset: %d", result.selected_tile);
1045 ImGui::TableNextColumn();
1046 ImGui::BeginGroup();
1052 ImGui::BeginGroup();
1055 if (ImGui::BeginChild(
"##Tile16FixedCanvas", ImVec2(90, 90),
true,
1056 ImGuiWindowFlags_NoScrollbar |
1057 ImGuiWindowFlags_NoScrollWithMouse)) {
1060 tile16_edit_frame_opts.
canvas_size = ImVec2(64, 64);
1070 auto tile16_edit_rt =
1093 preview_data.begin());
1106 if (display_palette && !display_palette->
empty()) {
1116 for (
int y = 0; y < 8; ++y) {
1117 for (
int x = 0; x < 4; ++x) {
1118 std::swap(data[y * 8 + x], data[y * 8 + (7 - x)]);
1124 for (
int y = 0; y < 4; ++y) {
1125 for (
int x = 0; x < 8; ++x) {
1126 std::swap(data[y * 8 + x], data[(7 - y) * 8 + x]);
1137 const auto preview_command =
1149 const bool left_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
1150 const bool right_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
1152 if (left_clicked || right_clicked) {
1153 const ImGuiIO& io = ImGui::GetIO();
1155 ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x,
1156 io.MousePos.y - canvas_pos.y);
1160 const ImVec2 tile_position =
1162 int tile_x =
static_cast<int>(tile_position.x);
1163 int tile_y =
static_cast<int>(tile_position.y);
1166 tile_x = std::max(0, std::min(15, tile_x));
1167 tile_y = std::max(0, std::min(15, tile_y));
1170 "Tile16 canvas click: (%.2f, %.2f) -> Tile16: (%d, %d), mode=%s",
1171 mouse_pos.x, mouse_pos.y, tile_x, tile_y,
1175 left_clicked, right_clicked));
1180 tile16_edit_frame_opts);
1194 if (tile8_texture) {
1195 ImGui::Image((ImTextureID)(intptr_t)tile8_texture, ImVec2(24, 24));
1200 ImGui::TextDisabled(tr(
"G%d"), sheet_idx);
1201 if (ImGui::IsItemHovered()) {
1202 ImGui::BeginTooltip();
1203 ImGui::Text(tr(
"Graphics chunk: %d"), sheet_idx);
1204 ImGui::EndTooltip();
1209 Checkbox(tr(
"X Flip"), &
x_flip);
1211 Checkbox(tr(
"Y Flip"), &
y_flip);
1229 "1x: paint one quadrant\n2x: fill current tile16 from a 2x2 tile8 "
1230 "block\n4x: stamp a 2x2 tile16 patch from a 4x4 tile8 block");
1232 Text(tr(
"Edit Mode:"));
1233 if (ImGui::RadioButton(tr(
"Paint (P)"),
1238 if (ImGui::RadioButton(tr(
"Pick (I)"),
1243 if (ImGui::RadioButton(tr(
"Usage (U)"),
1248 "Paint: left-click places Tile8 into Tile16.\n"
1249 "Pick: left-click samples Tile8 from Tile16.\n"
1250 "Usage: keeps usage overlay visible and samples on click.\n"
1251 "Right-click on Tile16 preview always samples.");
1262 if (show_advanced_controls) {
1264 Text(tr(
"Advanced:"));
1266 if (Button(tr(
"Palette Settings"), ImVec2(-1, 0))) {
1270 if (Button(tr(
"Analyze Data"), ImVec2(-1, 0))) {
1273 HOVER_HINT(
"Analyze tile8 source data format and palette state");
1275 if (Button(tr(
"Manual Edit"), ImVec2(-1, 0))) {
1276 ImGui::OpenPopup(
"ManualTile8Editor");
1279 if (Button(tr(
"Refresh Blockset"), ImVec2(-1, 0))) {
1284 Text(tr(
"Scratch:"));
1292 if (show_debug_info) {
1301 ImGui::TextDisabled(tr(
"Sheet:%d Slot:%d"), sheet_index, actual_slot);
1305 if (ImGui::CollapsingHeader(tr(
"Palette Map"),
1306 ImGuiTreeNodeFlags_DefaultOpen)) {
1307 ImGui::BeginChild(
"##PaletteMappingScroll", ImVec2(0, 120),
true);
1308 if (ImGui::BeginTable(
"##PalMap", 2,
1309 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
1310 ImGuiTableFlags_SizingFixedFit)) {
1311 ImGui::TableSetupColumn(
"Btn", ImGuiTableColumnFlags_WidthFixed, 30);
1312 ImGui::TableSetupColumn(
"CGRAM", ImGuiTableColumnFlags_WidthFixed,
1314 ImGui::TableHeadersRow();
1316 for (
int i = 0; i < 8; ++i) {
1317 ImGui::TableNextRow();
1318 ImGui::TableNextColumn();
1319 ImGui::Text(
"%d", i);
1320 ImGui::TableNextColumn();
1329 if (ImGui::CollapsingHeader(tr(
"Colors"))) {
1332 ImGui::Text(tr(
"Slot %d:"), actual_slot);
1338 int color_index = actual_slot + i;
1340 ImVec4 display_color = color.rgb();
1342 ImGui::ColorButton(absl::StrFormat(
"##c%d", i).c_str(),
1343 display_color, ImGuiColorEditFlags_NoTooltip,
1345 if (ImGui::IsItemHovered()) {
1346 ImGui::SetTooltip(tr(
"%d:0x%04X"), color_index, color.snes());
1349 if ((i + 1) % 4 != 0)
1371 return absl::OkStatus();
1375 bool has_pending,
bool current_tile_pending,
int pending_count,
1376 bool* show_debug_info,
bool* show_advanced_controls) {
1380 const float available_width = ImGui::GetContentRegionAvail().x;
1381 const int action_count = 7;
1384 const float row_height = ImGui::GetTextLineHeightWithSpacing() +
1385 rows * ImGui::GetFrameHeightWithSpacing() +
1386 ImGui::GetStyle().FramePadding.y * 2.0f;
1387 absl::Status action_status = absl::OkStatus();
1389 if (ImGui::BeginChild(
1390 "##Tile16CompactStatus", ImVec2(0, row_height),
false,
1391 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
1395 ImGui::TextDisabled(tr(
"%s, %d pending"),
1396 current_tile_pending ?
"dirty" :
"clean",
1399 ImGui::TextDisabled(tr(
"clean"));
1402 ImGui::TextDisabled(
"| %s", EditModeLabel(
edit_mode_));
1403 if (show_debug_info !=
nullptr && *show_debug_info &&
1405 const auto seconds_since_write =
1406 std::chrono::duration_cast<std::chrono::seconds>(
1410 ImGui::TextDisabled(tr(
"| Last write: %d tile%s, %lds ago"),
1413 static_cast<long>(seconds_since_write));
1416 if (ImGui::BeginTable(
"##Tile16CompactActions", columns,
1417 ImGuiTableFlags_SizingStretchProp)) {
1418 int action_index = 0;
1419 auto next_action_cell = [&]() {
1420 if (action_index % columns == 0) {
1421 ImGui::TableNextRow();
1423 ImGui::TableNextColumn();
1429 ImGui::BeginDisabled();
1432 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
1436 ImGui::EndDisabled();
1438 if (ImGui::IsItemHovered()) {
1439 ImGui::SetTooltip(tr(
"Write all %d pending Tile16 edits to ROM"),
1445 ImGui::BeginDisabled();
1447 if (ImGui::Button(tr(
"Discard Current"),
1448 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
1452 ImGui::EndDisabled();
1457 ImGui::BeginDisabled();
1460 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
1464 ImGui::EndDisabled();
1469 ImGui::BeginDisabled();
1471 if (ImGui::Button(tr(
"Undo"),
1472 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
1473 action_status =
Undo();
1476 ImGui::EndDisabled();
1481 ImGui::BeginDisabled();
1483 if (ImGui::Button(tr(
"Redo"),
1484 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
1485 action_status =
Redo();
1488 ImGui::EndDisabled();
1492 const char* advanced_label = *show_advanced_controls
1493 ?
"Advanced*##Tile16AdvancedToggle"
1494 :
"Advanced##Tile16AdvancedToggle";
1495 if (ImGui::Button(advanced_label,
1496 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
1497 *show_advanced_controls = !*show_advanced_controls;
1501 const char* debug_label = *show_debug_info ?
"Debug*##Tile16DebugToggle"
1502 :
"Debug##Tile16DebugToggle";
1503 if (ImGui::Button(debug_label,
1504 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
1505 *show_debug_info = !*show_debug_info;
1512 return action_status;
1516 bool show_debug_info) {
1518 Text(tr(
"Brush Palette:"));
1519 if (show_debug_info) {
1522 ImGui::TextDisabled(tr(
"(Slot %d)"), actual_slot);
1524 ImGui::TextDisabled(tr(
"Used for new tile8 placements"));
1527 ImGui::BeginGroup();
1528 float available_width = ImGui::GetContentRegionAvail().x;
1538 for (
int row = 0; row < 2; ++row) {
1539 for (
int col = 0; col < 4; ++col) {
1543 int i = row * 4 + col;
1550 is_current ? selected_button_colors : default_button_colors;
1551 const ImVec4 palette_button_border =
1555 {{ImGuiCol_Button, palette_button_colors.
button},
1556 {ImGuiCol_ButtonHovered, palette_button_colors.
hovered},
1557 {ImGuiCol_ButtonActive, palette_button_colors.
active},
1558 {ImGuiCol_Border, palette_button_border}});
1562 if (ImGui::Button(absl::StrFormat(
"%d", i).c_str(),
1563 ImVec2(button_size, button_size))) {
1569 status.message().data());
1578 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
1584 if (ImGui::IsItemHovered()) {
1585 ImGui::BeginTooltip();
1586 if (show_debug_info) {
1587 ImGui::Text(tr(
"Palette %d -> CGRAM 0x%02X"), i,
1590 ImGui::Text(tr(
"Brush Palette %d"), i);
1591 ImGui::TextDisabled(tr(
"Applied to new tile8 placements"));
1592 ImGui::TextDisabled(tr(
"RMB: apply to all tile quadrants"));
1593 ImGui::TextDisabled(tr(
"Quadrant metadata is shown in strip below"));
1594 ImGui::TextDisabled(
1595 tr(
"Hotkeys: Ctrl+1..8 palette, 1..4 quadrant focus"));
1600 ImGui::EndTooltip();
1608 Text(tr(
"Quadrant Focus:"));
1610 ImGui::TextDisabled(
"1-4");
1611 static constexpr std::array<const char*, 4> kQuadrantLabels = {
"TL",
"TR",
1613 const float quadrant_button_width = std::max(58.0f, button_size + 24.0f);
1616 for (
int q = 0; q < 4; ++q) {
1621 const gfx::TileInfo& info = TileInfoForQuadrant(*tile_data, q);
1622 const uint8_t quadrant_palette = info.
palette_;
1626 ImGui::PushID(100 + q);
1628 is_active_quadrant ? active_quadrant_colors
1629 : (matches_brush ? selected_button_colors
1630 : default_button_colors);
1631 const ImVec4 quadrant_border =
1637 {{ImGuiCol_Button, quadrant_button_colors.
button},
1638 {ImGuiCol_ButtonHovered, quadrant_button_colors.
hovered},
1639 {ImGuiCol_ButtonActive, quadrant_button_colors.
active},
1640 {ImGuiCol_Border, quadrant_border}});
1642 is_active_quadrant ? 2.0f : 1.0f);
1644 if (ImGui::Button(absl::StrFormat(
"%d %s:%d", q + 1, kQuadrantLabels[q],
1647 ImVec2(quadrant_button_width, 0))) {
1654 status.message().data());
1659 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
1664 if (ImGui::IsItemHovered()) {
1665 ImGui::BeginTooltip();
1666 ImGui::Text(tr(
"Quadrant %s metadata"), kQuadrantLabels[q]);
1668 ImGui::Text(tr(
"Tile8: %02X"), info.
id_);
1669 ImGui::Text(tr(
"Palette: %d"), quadrant_palette);
1672 ImGui::Text(tr(
"Priority: %s"), info.
over_ ?
"Y" :
"N");
1673 ImGui::TextDisabled(tr(
"LMB: set brush palette from this quadrant"));
1674 ImGui::TextDisabled(
1675 tr(
"RMB: apply current brush palette to this quadrant"));
1676 ImGui::TextDisabled(tr(
"Keys 1..4: focus TL/TR/BL/BR"));
1677 ImGui::EndTooltip();
1685 ImGui::TextDisabled(tr(
"Active %s: Tile8 %02X | P%d | H:%s V:%s | Pri:%s"),
1690 active_info.
over_ ?
"Y" :
"N");
1692 if (Button(tr(
"Apply Brush to Active Quadrant"), ImVec2(-1, 0))) {
1697 "Copy the Brush Palette into the selected quadrant metadata.\n"
1698 "Use keys 1..4 to change active quadrant quickly.");
1702 if (Button(tr(
"Apply Brush to All Quadrants"), ImVec2(-1, 0))) {
1706 "Copy the Brush Palette into Tile Palette metadata for all 4 "
1708 "Tip: right-click any brush palette button above for a one-step apply.");
1709 return absl::OkStatus();
1714 ImGui::Text(tr(
"Tile8 Source"));
1718 if (ImGui::Checkbox(tr(
"IDs##Tile16CollisionIds"),
1723 int source_bitmap_height = 0;
1738 ImGui::GetContentRegionAvail().y, source_bitmap_height,
1741 if (BeginChild(
"##Tile8SourceScrollable", ImVec2(0, panel_height),
true,
1742 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) {
1779 return absl::OkStatus();
1783 float display_scale) {
1784 const ImGuiIO& io = ImGui::GetIO();
1787 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1793 return absl::OkStatus();
1798 if (right_clicked) {
1803 return absl::OkStatus();
1808 if (Button(tr(
"Clear"), ImVec2(-1, 0))) {
1812 if (Button(tr(
"Copy"), ImVec2(-1, 0))) {
1816 if (Button(tr(
"Paste"), ImVec2(-1, 0))) {
1820 return absl::OkStatus();
1825 return absl::FailedPreconditionError(
1826 "Current graphics bitmap not initialized");
1836 const int total_tiles = tiles_per_row * total_rows;
1841 for (
int tile_y = 0; tile_y < total_rows; ++tile_y) {
1842 for (
int tile_x = 0; tile_x < tiles_per_row; ++tile_x) {
1847 for (
int py = 0; py < 8; ++py) {
1848 for (
int px = 0; px < 8; ++px) {
1849 int src_x = tile_x * 8 + px;
1850 int src_y = tile_y * 8 + py;
1852 int dst_index = py * 8 + px;
1862 tile_data[dst_index] = pixel_value;
1876 if (display_palette && !display_palette->
empty()) {
1880 "LoadTile8: display palette not available yet; deferring refresh");
1889 util::logf(
"Loaded %zu individual tile8 graphics",
1891 return absl::OkStatus();
1895 if (tile_id < 0 || tile_id >= kTile16Count) {
1896 return absl::OutOfRangeError(
1897 absl::StrFormat(
"Invalid tile16 id: %d", tile_id));
1901 return absl::FailedPreconditionError(
1902 "Tile16 blockset or ROM not initialized");
1917 const bool loaded_pending_metadata =
1929 if (loaded_pending_metadata) {
1933 util::logf(
"SetCurrentTile: loaded tile %d successfully", tile_id);
1938 return absl::OkStatus();
1945 "RequestTileSwitch: Editor not initialized (blockset=%p, rom=%p)",
1951 if (target_tile_id < 0 || target_tile_id >= kTile16Count) {
1952 util::logf(
"RequestTileSwitch: Invalid target tile ID %d", target_tile_id);
1966 util::logf(
"Tile %d has pending changes, showing confirmation dialog",
1972 util::logf(
"Failed to switch to tile %d: %s", target_tile_id,
1973 status.message().data());
1979 if (tile_id < 0 || tile_id >= kTile16Count) {
1980 return absl::InvalidArgumentError(
"Invalid tile ID");
1983 return absl::FailedPreconditionError(
"ROM not available");
1997 bool bitmap_copied =
false;
2003 bitmap_copied =
true;
2005 pending_bitmap_it->second.is_active()) {
2007 pending_bitmap_it->second.vector());
2009 bitmap_copied =
true;
2012 if (!tile_pixels.empty()) {
2015 bitmap_copied =
true;
2019 if (bitmap_copied) {
2025 return absl::OkStatus();
2030 return absl::FailedPreconditionError(
"Clipboard is empty");
2054 return absl::OkStatus();
2059 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2062 return absl::FailedPreconditionError(
"No active tile16 to save");
2075 return absl::OkStatus();
2080 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2084 return absl::FailedPreconditionError(
"Scratch space slot is empty");
2108 return absl::OkStatus();
2113 return absl::InvalidArgumentError(
"Invalid scratch space slot");
2117 return absl::OkStatus();
2123 return absl::FailedPreconditionError(
"No active tile16 to flip");
2139 return absl::OkStatus();
2144 return absl::FailedPreconditionError(
"No active tile16 to flip");
2160 return absl::OkStatus();
2165 return absl::FailedPreconditionError(
"No active tile16 to rotate");
2183 return absl::OkStatus();
2189 return absl::FailedPreconditionError(
"Source tile8 bitmap not active");
2196 return absl::InvalidArgumentError(
"Invalid tile8 ID");
2201 const gfx::TileInfo fill_info(
static_cast<uint16_t
>(tile8_id),
2204 for (
int quadrant = 0; quadrant < 4; ++quadrant) {
2218 return absl::OkStatus();
2223 return absl::FailedPreconditionError(
"No active tile16 to clear");
2229 for (
int quadrant = 0; quadrant < 4; ++quadrant) {
2243 return absl::OkStatus();
2251 new_palette = (new_palette + 1) % 8;
2253 new_palette = (new_palette == 0) ? 7 : new_palette - 1;
2262 return absl::OkStatus();
2267 return absl::OkStatus();
2270 if (palette_id >= 8) {
2271 return absl::InvalidArgumentError(
"Invalid palette ID");
2283 if (!display_palette || display_palette->
empty()) {
2284 return absl::OkStatus();
2287 const bool use_sub_palette_view =
2289 if (use_sub_palette_view) {
2291 const int palette_slot =
2293 if (palette_slot >= 0 &&
2294 static_cast<size_t>(palette_slot + 16) <= display_palette->
size()) {
2296 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
2309 return absl::OkStatus();
2313 if (palette_id >= 8) {
2314 return absl::InvalidArgumentError(
"Invalid palette ID");
2319 return absl::FailedPreconditionError(
"No current tile16 data");
2340 util::logf(
"Applied palette %d to all quadrants of tile %d", palette_id,
2342 return absl::OkStatus();
2346 uint8_t palette_id) {
2347 if (palette_id >= 8) {
2348 return absl::InvalidArgumentError(
"Invalid palette ID");
2350 if (quadrant < 0 || quadrant > 3) {
2351 return absl::InvalidArgumentError(
"Invalid quadrant index");
2356 return absl::FailedPreconditionError(
"No current tile16 data");
2361 return absl::InvalidArgumentError(
"Invalid quadrant index");
2372 util::logf(
"Applied palette %d to quadrant %d of tile %d", palette_id,
2374 return absl::OkStatus();
2457 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2460 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2461 return absl::OutOfRangeError(
"Current tile16 ID out of range");
2465 return absl::OutOfRangeError(
"Current palette ID out of range");
2468 return absl::OkStatus();
2472 return tile16_blockset_ !=
nullptr && tile_id >= 0 && tile_id < kTile16Count;
2478 return absl::FailedPreconditionError(
"ROM not available");
2482 return absl::FailedPreconditionError(
"No active tile16 to save");
2505 return absl::OkStatus();
2510 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2513 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2514 return absl::OutOfRangeError(
"Current tile16 ID out of range");
2520 return absl::OkStatus();
2525 return absl::FailedPreconditionError(
"Tile16 blockset not initialized");
2540 return absl::OkStatus();
2544 std::vector<Tile16Commit> commits;
2569 util::logf(
"Committed Tile16 %d changes to overworld system",
2571 return absl::OkStatus();
2585 return absl::OkStatus();
2590 return absl::OkStatus();
2594 std::vector<Tile16Commit> commits;
2596 util::logf(
"Committing %zu pending tile16 changes to ROM",
2603 util::logf(
"Failed to write tile16 %d: %s", tile_id,
2604 status.message().data());
2607 commits.push_back({tile_id, tile_data});
2627 util::logf(
"All pending tile16 changes committed successfully");
2628 return absl::OkStatus();
2636 util::logf(
"Discarding %zu pending tile16 changes",
2646 util::logf(
"Failed to reload tile after discard: %s",
2647 status.message().data());
2663 util::logf(
"Failed to reload tile after discard: %s",
2664 status.message().data());
2669 if (current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2685 return absl::FailedPreconditionError(
"ROM not available for usage cache");
2689 auto tile_provider = [
this](
int tile_id) -> absl::StatusOr<gfx::Tile16> {
2692 return pending_it->second;
2701 return absl::OkStatus();
2712 if (!cache_status.ok()) {
2713 util::logf(
"Tile8 usage cache rebuild failed: %s",
2714 cache_status.message().data());
2724 ImDrawList* draw_list = ImGui::GetWindowDrawList();
2727 const float tile16_display = 32.0f * scale;
2728 const float quadrant_display = 16.0f * scale;
2729 const int tiles_per_row =
2732 for (
const auto& hit : hits) {
2733 const int tile_x = hit.tile16_id % tiles_per_row;
2734 const int tile_y = hit.tile16_id / tiles_per_row;
2735 const int quad_x = hit.quadrant % 2;
2736 const int quad_y = hit.quadrant / 2;
2739 canvas_pos.x + (tile_x * tile16_display) + (quad_x * quadrant_display),
2740 canvas_pos.y + (tile_y * tile16_display) + (quad_y * quadrant_display));
2741 const ImVec2 max(min.x + quadrant_display, min.y + quadrant_display);
2744 draw_list->AddRectFilled(min, max, IM_COL32(150, 0, 210, 80));
2745 draw_list->AddRect(min, max, IM_COL32(215, 170, 255, 180));
2750 if (!
rom_ || current_tile16_ < 0 || current_tile16_ >= kTile16Count) {
2751 return absl::InvalidArgumentError(
"Invalid tile16 or ROM not set");
2755 int quad_x = (position.x < 8) ? 0 : 1;
2756 int quad_y = (position.y < 8) ? 0 : 1;
2757 int quadrant = quad_x + (quad_y * 2);
2763 return absl::FailedPreconditionError(
"Failed to get tile16 data");
2767 gfx::TileInfo tile_info = TileInfoForQuadrant(*tile16_data, quadrant);
2782 util::logf(
"Picked tile8 %d with palette %d from quadrant %d of tile16 %d",
2785 return absl::OkStatus();
2794 int sheet_index)
const {
2796 const int clamped_button = std::clamp(palette_button, 0, 7);
2797 return clamped_button * 16;
2804 constexpr int kTile8sPerGraphicsChunk = 64;
2805 int sheet_index = tile8_id / kTile8sPerGraphicsChunk;
2807 return std::min(15, std::max(0, sheet_index));
2834 const int actual_target_row = std::clamp(target_row, 0, 7);
2836 for (
int i = 0; i < 256; ++i) {
2837 int low_nibble = i & 0x0F;
2838 int target_index = (actual_target_row * 16) + low_nibble;
2841 if (low_nibble == 0) {
2844 remapped.
AddColor(transparent_color);
2845 }
else if (target_index <
static_cast<int>(source.
size())) {
2846 remapped.
AddColor(source[target_index]);
2866 return pixel_value / 16;
2888 for (
size_t i = 0; i < bitmap.
size(); ++i) {
2889 if ((bitmap.
data()[i] & 0xF0) != 0) {
2903 if (!display_palette || display_palette->
empty()) {
2904 fallback_palette = BuildFallbackDisplayPalette();
2905 display_palette = &fallback_palette;
2915 if (palette_slot >= 0 &&
2916 static_cast<size_t>(palette_slot + 16) <= display_palette->
size()) {
2918 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
2935 return absl::InvalidArgumentError(
"Invalid tile8 ID");
2939 return absl::FailedPreconditionError(
"ROM not set");
2943 if (!display_palette || display_palette->
empty()) {
2944 return absl::FailedPreconditionError(
"No overworld palette available");
2948 if (current_palette_ < 0 || current_palette_ >= 8) {
2954 const int palette_slot =
2965 util::logf(
"Updated tile8 %d with palette slot %d (palette size: %zu colors)",
2966 tile8_id, palette_slot, display_palette->
size());
2968 return absl::OkStatus();
2973 return absl::FailedPreconditionError(
"ROM not set");
2977 if (current_palette_ < 0 || current_palette_ >= 8) {
2984 if (!display_palette || display_palette->
empty()) {
2985 fallback_palette = BuildFallbackDisplayPalette();
2986 display_palette = &fallback_palette;
2987 util::logf(
"Display palette unavailable; using fallback grayscale palette");
2989 util::logf(
"Using resolved display palette with %zu colors",
2990 display_palette->
size());
3012 if (!regen_status.ok()) {
3022 "Successfully refreshed all palettes in tile16 editor with palette %d",
3024 return absl::OkStatus();
3028 util::logf(
"=== TILE8 SOURCE DATA ANALYSIS ===");
3042 std::map<uint8_t, int> pixel_counts;
3043 for (
size_t i = 0; i < 64; ++i) {
3045 pixel_counts[val]++;
3047 util::logf(
" - First tile8 (Sheet 0) pixel distribution:");
3048 for (
const auto& [val, count] : pixel_counts) {
3050 int col = val & 0x0F;
3051 util::logf(
" Value 0x%02X (%3d) = Row %d, Col %d: %d pixels", val, val,
3056 bool all_4bpp =
true;
3057 for (
const auto& [val, count] : pixel_counts) {
3063 util::logf(
" - Values in raw 4bpp range (0-15): %s",
3064 all_4bpp ?
"yes" :
"NO (pre-encoded)");
3067 util::logf(
" - Palette remapping for viewing:");
3070 util::logf(
" Pixels are remapped: (value & 0x0F) + (selected_row * 16)");
3082 std::map<uint8_t, int> pixel_counts;
3083 for (uint8_t val : first_tile) {
3084 pixel_counts[val]++;
3087 for (
const auto& [val, count] : pixel_counts) {
3088 util::logf(
" Value 0x%02X (%3d): %d pixels", val, val, count);
3102 util::logf(
" - Expected palette offset for SetPaletteWithTransparent: %d",
3107 util::logf(
" - First 16 palette colors (row 0):");
3108 for (
int i = 0; i < 16; ++i) {
3110 util::logf(
" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
3111 static_cast<int>(color.rgb().x),
3112 static_cast<int>(color.rgb().y),
3113 static_cast<int>(color.rgb().z));
3119 util::logf(
" - Colors at palette slot %d (row %d):", palette_slot,
3121 for (
int i = 0; i < 16; ++i) {
3123 util::logf(
" [%2d] SNES: 0x%04X RGB: (%d,%d,%d)", i, color.snes(),
3124 static_cast<int>(color.rgb().x),
3125 static_cast<int>(color.rgb().y),
3126 static_cast<int>(color.rgb().z));
3136 Text(tr(
"Pixel Normalization & Color Correction:"));
3139 if (SliderInt(tr(
"Normalization Mask"), &mask_value, 1, 255,
"0x%02X")) {
3145 if (Button(tr(
"Apply to All Graphics"))) {
3147 if (!reload_result.ok()) {
3148 Text(tr(
"Error: %s"), reload_result.message().data());
3153 if (Button(tr(
"Reset Defaults"))) {
3157 (void)reload_result;
3161 Text(tr(
"Current State:"));
3162 static constexpr std::array<const char*, 7> palette_group_names = {
3163 "OW Main",
"OW Aux",
"OW Anim",
"Dungeon",
3164 "Sprites",
"Armor",
"Sword"};
3172 Text(tr(
"Sheet-Specific Fixes:"));
3175 static bool fix_sheet_0 =
true;
3176 static bool fix_sprite_sheets =
true;
3177 static bool use_transparent_for_terrain =
false;
3179 if (Checkbox(tr(
"Fix Sheet 0 (Trees)"), &fix_sheet_0)) {
3181 if (!reload_result.ok()) {
3182 Text(tr(
"Error reloading: %s"), reload_result.message().data());
3186 "Use direct palette for sheet 0 instead of transparent palette");
3188 if (Checkbox(tr(
"Fix Sprite Sheets"), &fix_sprite_sheets)) {
3190 if (!reload_result.ok()) {
3191 Text(tr(
"Error reloading: %s"), reload_result.message().data());
3194 HOVER_HINT(
"Use direct palette for sprite graphics sheets");
3196 if (Checkbox(tr(
"Transparent for Terrain"),
3197 &use_transparent_for_terrain)) {
3199 if (!reload_result.ok()) {
3200 Text(tr(
"Error reloading: %s"), reload_result.message().data());
3203 HOVER_HINT(
"Force transparent palette for terrain graphics");
3206 Text(tr(
"Color Analysis:"));
3209 Text(tr(
"Selected Tile8 Analysis:"));
3211 std::map<uint8_t, int> pixel_counts;
3212 for (uint8_t pixel : tile_data) {
3213 pixel_counts[pixel & 0x0F]++;
3216 Text(tr(
"Pixel Value Distribution:"));
3217 for (
const auto& pair : pixel_counts) {
3218 int value = pair.first;
3219 int count = pair.second;
3220 Text(tr(
" Value %d (0x%X): %d pixels"), value, value, count);
3223 Text(tr(
"Palette Colors Used:"));
3225 if (analysis_palette ==
nullptr || analysis_palette->
empty()) {
3229 for (
const auto& pair : pixel_counts) {
3230 int value = pair.first;
3231 int count = pair.second;
3232 if (value <
static_cast<int>(analysis_palette->
size())) {
3233 auto color = (*analysis_palette)[value];
3234 ImVec4 display_color = color.rgb();
3235 ImGui::ColorButton((
"##analysis" + std::to_string(value)).c_str(),
3236 display_color, ImGuiColorEditFlags_NoTooltip,
3238 if (ImGui::IsItemHovered()) {
3239 ImGui::SetTooltip(tr(
"Index %d: 0x%04X (%d pixels)"), value,
3240 color.snes(), count);
3250 if (CollapsingHeader(tr(
"ROM Palette Manager")) &&
rom_) {
3251 Text(tr(
"Experimental ROM Palette Selection:"));
3253 "Use ROM palettes to experiment with different color schemes");
3255 if (Button(tr(
"Open Enhanced Palette Editor"))) {
3259 if (Button(tr(
"Show Color Analysis"))) {
3264 static int quick_group = 0;
3265 static int quick_index = 0;
3267 SliderInt(tr(
"ROM Group"), &quick_group, 0, 6);
3268 SliderInt(tr(
"Palette Index"), &quick_index, 0, 7);
3270 if (Button(tr(
"Apply to Tile8 Source"))) {
3272 util::logf(
"Applied ROM palette group %d, index %d to Tile8 source",
3273 quick_group, quick_index);
3277 if (Button(tr(
"Apply to Tile16 Editor"))) {
3280 "Applied ROM palette group %d, index %d to Tile16 editor",
3281 quick_group, quick_index);
3291 Text(tr(
"Layout Scratch:"));
3292 for (
int i = 0; i < 4; ++i) {
3294 std::string slot_name =
"S" + std::to_string(i + 1);
3296 if (Button((slot_name +
" Save").c_str(), ImVec2(70, 20))) {
3303 ImGui::BeginDisabled();
3305 if (Button((slot_name +
" Load").c_str(), ImVec2(70, 20)) && can_load) {
3309 ImGui::EndDisabled();
3318 if (slot < 0 || slot >= 4) {
3319 return absl::InvalidArgumentError(
"Invalid scratch slot");
3323 if (total_tiles <= 0) {
3324 return absl::FailedPreconditionError(
"Tile16 blockset is not available");
3327 const int start_tile = std::clamp(
current_tile16_, 0, total_tiles - 1);
3328 for (
int y = 0; y < 8; ++y) {
3329 for (
int x = 0; x < 8; ++x) {
3330 const int tile_id = start_tile + (y * 8) + x;
3332 (tile_id < total_tiles) ? tile_id : -1;
3338 absl::StrFormat(
"From %03X",
static_cast<uint16_t
>(start_tile));
3340 return absl::OkStatus();
3344 if (slot < 0 || slot >= 4) {
3345 return absl::InvalidArgumentError(
"Invalid scratch slot");
3349 return absl::FailedPreconditionError(
"Scratch slot is empty");
3353 if (first_tile < 0) {
3354 return absl::FailedPreconditionError(
"Scratch slot has no valid tile data");
3363 for (
int y = 0; y < 8; ++y) {
3364 for (
int x = 0; x < 8; ++x) {
3373 return absl::OkStatus();
3377 if (ImGui::BeginPopupModal(
"ManualTile8Editor",
nullptr,
3378 ImGuiWindowFlags_AlwaysAutoResize)) {
3379 ImGui::Text(tr(
"Manual Tile8 Configuration for Tile16 %02X"),
3385 ImGui::Text(tr(
"Current Tile16 Staged Data:"));
3387 auto stage_current_tile = [&]() -> absl::Status {
3388 SyncTilesInfoArray(tile_data);
3395 return absl::OkStatus();
3399 const char* quadrant_names[] = {
"Top-Left",
"Top-Right",
"Bottom-Left",
3402 for (
int q = 0; q < 4; q++) {
3403 ImGui::Text(tr(
"%s Quadrant:"), quadrant_names[q]);
3404 ImGui::TextDisabled(tr(
"Tile Palette metadata + Tile8/flip flags"));
3410 tile_info = &tile_data->tile0_;
3413 tile_info = &tile_data->tile1_;
3416 tile_info = &tile_data->tile2_;
3419 tile_info = &tile_data->tile3_;
3427 int tile_id_int =
static_cast<int>(tile_info->
id_);
3428 if (ImGui::InputInt(tr(
"Tile8 ID"), &tile_id_int, 1, 10)) {
3430 static_cast<uint16_t
>(std::max(0, std::min(tile_id_int, 1023)));
3433 int palette_int =
static_cast<int>(tile_info->
palette_);
3434 if (ImGui::SliderInt(tr(
"Tile Palette"), &palette_int, 0, 7)) {
3435 tile_info->
palette_ =
static_cast<uint8_t
>(palette_int);
3442 ImGui::Checkbox(tr(
"Priority"), &tile_info->
over_);
3444 if (ImGui::Button(tr(
"Stage Quadrant Edit"))) {
3445 auto stage_result = stage_current_tile();
3446 if (!stage_result.ok()) {
3447 ImGui::Text(tr(
"Stage Error: %s"), stage_result.message().data());
3459 if (ImGui::Button(tr(
"Stage All Edits"))) {
3460 auto stage_result = stage_current_tile();
3461 if (!stage_result.ok()) {
3462 ImGui::Text(tr(
"Stage Error: %s"), stage_result.message().data());
3466 if (ImGui::Button(tr(
"Write Pending to ROM"))) {
3468 if (!write_result.ok()) {
3469 ImGui::Text(tr(
"Write Error: %s"), write_result.message().data());
3473 if (ImGui::Button(tr(
"Refresh Display"))) {
3475 if (!refresh_result.ok()) {
3476 ImGui::Text(tr(
"Refresh Error: %s"), refresh_result.message().data());
3481 ImGui::Text(tr(
"Tile16 data not accessible"));
3484 ImGui::Text(tr(
"Valid range: 0-4095 (4096 total tiles)"));
3489 if (ImGui::Button(tr(
"Close"))) {
3490 ImGui::CloseCurrentPopup();
3500 return absl::OkStatus();
3505 return absl::OkStatus();
3511 return absl::OkStatus();
3524 if (display_palette && !display_palette->
empty()) {
3525 const bool use_sub_palette_view =
3527 if (use_sub_palette_view) {
3529 const int palette_slot =
3531 if (palette_slot >= 0 &&
3532 static_cast<size_t>(palette_slot + 16) <= display_palette->
size()) {
3534 *display_palette,
static_cast<size_t>(palette_slot + 1), 15);
3550 return absl::OkStatus();
3554 if (!ImGui::IsAnyItemActive()) {
3555 const ImGuiIO& io = ImGui::GetIO();
3556#if defined(__APPLE__)
3557 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
3559 const bool platform_primary_held = io.KeyCtrl;
3561 const bool ctrl_held = platform_primary_held ||
3562 ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
3563 ImGui::IsKeyDown(ImGuiKey_RightCtrl);
3566 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
3569 if (ImGui::IsKeyPressed(ImGuiKey_H) && !ctrl_held) {
3572 if (ImGui::IsKeyPressed(ImGuiKey_V) && !ctrl_held) {
3575 if (ImGui::IsKeyPressed(ImGuiKey_R) && !ctrl_held) {
3578 if (ImGui::IsKeyPressed(ImGuiKey_F) && !ctrl_held) {
3586 if (ImGui::IsKeyPressed(ImGuiKey_P)) {
3589 if (ImGui::IsKeyPressed(ImGuiKey_I)) {
3592 if (ImGui::IsKeyPressed(ImGuiKey_U)) {
3598 if (ImGui::IsKeyPressed(ImGuiKey_Q)) {
3601 if (ImGui::IsKeyPressed(ImGuiKey_E)) {
3608 for (
int i = 0; i < 8; ++i) {
3609 if (!ImGui::IsKeyPressed(
static_cast<ImGuiKey
>(ImGuiKey_1 + i))) {
3625 if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
3628 if (ImGui::IsKeyPressed(ImGuiKey_Y)) {
3631 if (ImGui::IsKeyPressed(ImGuiKey_C)) {
3634 if (ImGui::IsKeyPressed(ImGuiKey_V)) {
3637 if (ImGui::IsKeyPressed(ImGuiKey_S)) {
3638 if (ImGui::IsKeyDown(ImGuiKey_LeftShift) ||
3639 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)
const gfx::SnesPalette * ResolveDisplayPalette() const
std::function< absl::Status(const std::vector< Tile16Commit > &) on_changes_committed_)
std::map< int, gfx::Tile16 > pending_tile16_changes_
absl::Status LoadLayoutFromScratch(int slot)
static ImVec2 Tile16PreviewDisplayPixelToTilePosition(const ImVec2 &display_position)
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)
absl::Status HandleTile8SourceSelection(bool right_clicked, float display_scale)
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)
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.
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)
absl::Status DrawTile8SourcePanel(float preferred_height=0.0f)
Tile16EditMode edit_mode_
absl::Status SetCurrentTile(int id)
bool show_tile_collision_ids_
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()
gfx::SnesPalette CreateRemappedPaletteForTile8(const gfx::SnesPalette &source, int target_row, int tile8_id) const
std::optional< Tile16Snapshot > pending_undo_before_
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.
float tile8_source_display_scale_
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)
absl::Status DrawToCurrentTile16(ImVec2 pos, const gfx::Bitmap *source_tile=nullptr)
absl::Status UpdateLivePreview()
absl::Status Initialize(gfx::Bitmap &tile16_blockset_bmp, gfx::Bitmap ¤t_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status DrawCompactActionStatusRow(bool has_pending, bool current_tile_pending, int pending_count, bool *show_debug_info, bool *show_advanced_controls)
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_
absl::Status BuildTile16BitmapFromData(const gfx::Tile16 &tile_data, gfx::Bitmap *output_bitmap) const
int GetSheetIndexForTile8(int tile8_id) const
Determine which graphics sheet contains a tile8.
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()
RAII timer for automatic timing management.
constexpr void set_transparent(bool t)
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
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
float ComputeTile8SourcePanelHeight(float available_height_px, int source_bitmap_height_px, float display_scale, float preferred_height_px=0.0f, float min_height_px=220.0f, float max_height_px=520.0f)
constexpr int kTile8PixelCount
int ComputeTile16CompactActionColumnCount(float available_width_px)
constexpr float kTile8DisplayScale
float ComputeTile16PaletteButtonSize(float available_width_px)
Tile16ActionControlState ComputeTile16ActionControlState(bool has_pending, bool current_tile_pending, bool can_undo, bool can_redo)
bool ComputeTile8UsageHighlight(bool source_hovered, bool right_mouse_down)
int ComputeTile16ActionRowCount(int action_count, int column_count)
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)
float ComputeTile8SourceDisplayScale(float available_width_px, int source_bitmap_width_px, float min_scale=1.0f, float max_scale=4.0f)
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).
ButtonColorSet GetSuccessButtonColors()
void BeginChildWithScrollbar(const char *str_id)
Color GetThemeColor(const std::string &color_name)
void ThemedText(const char *text, SemanticColor color)
ButtonColorSet GetPrimaryButtonColors()
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