7#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
25 return (object_id >= 0x000 && object_id <= 0x0F7) ||
26 (object_id >= 0x100 && object_id <= 0x13F) ||
27 (object_id >= 0xF80 && object_id <= 0xFFF);
35 if (
rom_ ==
nullptr) {
36 return absl::InvalidArgumentError(
"ROM is null");
64 return absl::OkStatus();
68 if (
rom_ ==
nullptr) {
69 return absl::InvalidArgumentError(
"ROM is null");
73 return absl::InvalidArgumentError(
"Invalid room ID");
99 return absl::OkStatus();
104 return absl::FailedPreconditionError(
"No room loaded");
110 if (!validation_result.is_valid) {
111 std::string error_msg =
"Validation failed";
112 if (!validation_result.errors.empty()) {
113 error_msg +=
": " + validation_result.errors[0];
115 return absl::FailedPreconditionError(error_msg);
125 return absl::FailedPreconditionError(
"No room loaded");
145 return absl::OkStatus();
149 int size,
int layer) {
151 return absl::FailedPreconditionError(
"No room loaded");
155 if (!IsRepresentableRoomObjectId(object_type)) {
156 return absl::InvalidArgumentError(absl::StrFormat(
157 "Object ID 0x%03X is not representable; expected 0x000..0x0F7, "
158 "0x100..0x13F, or 0xF80..0xFFF",
163 return absl::InvalidArgumentError(
"Invalid object size");
166 if (layer < kMinLayer || layer >
kMaxLayer) {
167 return absl::InvalidArgumentError(
"Invalid layer");
185 const uint8_t canonical_size =
187 RoomObject new_object(object_type, x, y, canonical_size, layer);
195 return absl::FailedPreconditionError(
196 "Object placement would cause collision");
203 if (!add_status.ok()) {
226 return absl::OkStatus();
231 return absl::FailedPreconditionError(
"No room loaded");
235 return absl::OutOfRangeError(
"Object index out of range");
246 if (!remove_status.ok()) {
247 return remove_status;
252 if (selected_index > object_index) {
254 }
else if (selected_index == object_index) {
272 return absl::OkStatus();
277 return absl::FailedPreconditionError(
"No room loaded");
281 return absl::FailedPreconditionError(
"No objects selected");
292 std::sort(sorted_selection.begin(), sorted_selection.end(),
293 std::greater<size_t>());
296 for (
size_t index : sorted_selection) {
297 if (index < current_room_->GetTileObjectCount()) {
310 return absl::OkStatus();
316 return absl::FailedPreconditionError(
"No room loaded");
320 return absl::OutOfRangeError(
"Object index out of range");
341 test_object.
set_x(new_x);
342 test_object.
set_y(new_y);
345 if (i != object_index &&
347 return absl::FailedPreconditionError(
348 "Object move would cause collision");
354 const bool changed =
object.x() != new_x ||
object.y() != new_y;
373 return absl::OkStatus();
379 return absl::FailedPreconditionError(
"No room loaded");
383 return absl::OutOfRangeError(
"Object index out of range");
387 return absl::InvalidArgumentError(
"Invalid object size");
392 return absl::OkStatus();
395 const uint8_t canonical_size =
397 if (
object.size() == canonical_size) {
398 return absl::OkStatus();
409 object.set_size(canonical_size);
421 return absl::OkStatus();
425 const std::vector<size_t>& indices,
int dx,
int dy) {
427 return absl::FailedPreconditionError(
"No room loaded");
430 if (indices.empty()) {
431 return absl::OkStatus();
441 for (
size_t index : indices) {
446 int new_x =
object.
x() + dx;
447 int new_y =
object.y() + dy;
450 new_x = std::max(0, std::min(63, new_x));
451 new_y = std::max(0, std::min(63, new_y));
453 const bool changed =
object.x() != new_x ||
object.y() != new_y;
472 return absl::OkStatus();
476 const std::vector<size_t>& indices,
int new_layer) {
478 return absl::FailedPreconditionError(
"No room loaded");
481 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
482 return absl::InvalidArgumentError(
"Invalid layer");
485 const std::vector<size_t> requested_indices = indices;
487 bool change_needed =
false;
488 for (
size_t index : requested_indices) {
489 if (index >= objects.size()) {
493 return absl::InvalidArgumentError(
494 "Torches and pushable blocks only support upper/lower draw-layer "
495 "selector values 0/1");
497 change_needed |= objects[index].GetLayerValue() != new_layer;
499 if (!change_needed) {
500 return absl::OkStatus();
509 const std::vector<RoomObject> before = objects;
511 if (!mutation.ok()) {
512 return mutation.status();
515 for (
size_t old_index : mutation->changed_old_indices) {
516 const size_t new_index = mutation->old_to_new_index[old_index];
521 std::vector<size_t> remapped_selection;
524 if (old_index < mutation->old_to_new_index.size()) {
525 remapped_selection.push_back(mutation->old_to_new_index[old_index]);
528 std::sort(remapped_selection.begin(), remapped_selection.end());
529 remapped_selection.erase(
530 std::unique(remapped_selection.begin(), remapped_selection.end()),
531 remapped_selection.end());
540 for (
size_t old_index : mutation->changed_old_indices) {
541 const size_t new_index = mutation->old_to_new_index[old_index];
550 return absl::OkStatus();
554 const std::vector<size_t>& indices,
int new_size) {
556 return absl::FailedPreconditionError(
"No room loaded");
560 return absl::InvalidArgumentError(
"Invalid object size");
563 const uint8_t requested_size =
static_cast<uint8_t
>(new_size);
564 bool change_needed =
false;
565 for (
size_t index : indices) {
572 change_needed =
true;
576 if (!change_needed) {
577 return absl::OkStatus();
586 for (
size_t index : indices) {
595 const uint8_t canonical_size =
597 if (
object.size() == canonical_size) {
601 object.set_size(canonical_size);
613 return absl::OkStatus();
634 int new_x =
object.
x() + offset_x;
635 int new_y =
object.y() + offset_y;
638 new_x = std::max(0, std::min(63, new_x));
639 new_y = std::max(0, std::min(63, new_y));
659 const std::vector<size_t>& indices) {
665 for (
size_t index : indices) {
666 if (index < current_room_->GetTileObjectCount()) {
680 std::vector<size_t> new_indices;
692 int new_x = std::min(63, new_obj.
x() + 1);
693 int new_y = std::min(63, new_obj.
y() + 1);
694 new_obj.
set_x(new_x);
695 new_obj.
set_y(new_y);
698 new_indices.push_back(start_index++);
712 return absl::FailedPreconditionError(
"No room loaded");
716 return absl::OutOfRangeError(
"Object index out of range");
719 if (!IsRepresentableRoomObjectId(new_type)) {
720 return absl::InvalidArgumentError(absl::StrFormat(
721 "Object ID 0x%03X is not representable; expected 0x000..0x0F7, "
722 "0x100..0x13F, or 0xF80..0xFFF",
727 if (
object.id_ == new_type) {
728 return absl::OkStatus();
731 const uint8_t canonical_size =
741 object.set_id(
static_cast<int16_t
>(new_type));
742 object.set_size(canonical_size);
753 return absl::OkStatus();
759 return absl::FailedPreconditionError(
"No room loaded");
775 std::vector<RoomObject> new_objects =
780 for (
const auto& new_obj : new_objects) {
783 return absl::FailedPreconditionError(
784 "Template placement would cause collision");
791 for (
const auto& obj : new_objects) {
798 size_t added_count = new_objects.size();
799 for (
size_t i = 0; i < added_count; ++i) {
810 return absl::OkStatus();
816 return absl::FailedPreconditionError(
"No objects selected");
819 std::vector<RoomObject> objects;
820 int min_x = 64, min_y = 64;
824 if (index < current_room_->GetTileObjectCount()) {
826 objects.push_back(obj);
848 return absl::FailedPreconditionError(
"No room loaded");
852 return absl::OkStatus();
875 for (
size_t index : indices) {
882 if (obj.x() < ref_val)
886 if (obj.x() > ref_val)
890 if (obj.y() < ref_val)
894 if (obj.y() > ref_val)
910 ref_val = sum / count;
914 for (
size_t index : indices) {
922 const bool changed = changes_x ? obj.
x() != ref_val : obj.y() != ref_val;
953 return absl::OkStatus();
959 return absl::FailedPreconditionError(
"No room loaded");
963 return absl::OutOfRangeError(
"Object index out of range");
966 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
967 return absl::InvalidArgumentError(
"Invalid layer");
976 return absl::FailedPreconditionError(
"No room loaded");
991 int layer_delta = delta > 0 ? 1 : -1;
999 return absl::OkStatus();
1002 return absl::OkStatus();
1013 return absl::OkStatus();
1020 if (object_index < current_room_->GetTileObjectCount()) {
1031 return absl::OkStatus();
1034 return absl::OkStatus();
1044 if (current_size < 0x40) {
1045 return current_size + 0x10;
1046 }
else if (current_size < 0x80) {
1047 return current_size + 0x08;
1049 return current_size + 0x04;
1053 if (current_size > 0x80) {
1054 return current_size - 0x04;
1055 }
else if (current_size > 0x40) {
1056 return current_size - 0x08;
1058 return current_size - 0x10;
1070 bool shift_pressed) {
1072 return absl::FailedPreconditionError(
"No room loaded");
1081 if (shift_pressed) {
1084 if (object_index.has_value()) {
1103 if (object_index.has_value()) {
1125 if (object_index.has_value()) {
1136 return absl::OkStatus();
1143 return absl::FailedPreconditionError(
"No room loaded");
1155 if (!undo_status.ok()) {
1166 return absl::FailedPreconditionError(
"No room loaded");
1179 return absl::OkStatus();
1184 return absl::FailedPreconditionError(
"No room loaded");
1193 if (object_index.has_value()) {
1203 return absl::OkStatus();
1220 return absl::OkStatus();
1225 return absl::FailedPreconditionError(
"No room loaded");
1229 return absl::OutOfRangeError(
"Object index out of range");
1246 return absl::OkStatus();
1264 if (IsRepresentableRoomObjectId(object_type)) {
1275 return std::nullopt;
1282 return static_cast<size_t>(i);
1286 return std::nullopt;
1292 int obj_x =
object.x_;
1293 int obj_y =
object.y_;
1298 if (
object.size_ > 0x80) {
1303 return (x >= obj_x && x < obj_x + obj_width && y >= obj_y &&
1304 y < obj_y + obj_height);
1312 int obj1_x = obj1.
x_ * 16;
1313 int obj1_y = obj1.
y_ * 16;
1317 int obj2_x = obj2.
x_ * 16;
1318 int obj2_y = obj2.
y_ * 16;
1323 if (obj1.
size_ > 0x80) {
1328 if (obj2.
size_ > 0x80) {
1333 return !(obj1_x + obj1_w <= obj2_x || obj2_x + obj2_w <= obj1_x ||
1334 obj1_y + obj1_h <= obj2_y || obj2_y + obj2_h <= obj1_y);
1343 int room_x = screen_x / 16;
1344 int room_y = screen_y / 16;
1346 return {room_x, room_y};
1352 int screen_x = room_x * 16;
1353 int screen_y = room_y * 16;
1355 return {screen_x, screen_y};
1364 if (grid_size <= 0) {
1369 int tile_step = std::max(1, grid_size / 16);
1370 return (coordinate / tile_step) * tile_step;
1393 return absl::FailedPreconditionError(
"No room loaded");
1401 undo_point.
timestamp = std::chrono::steady_clock::now();
1414 return absl::OkStatus();
1419 return absl::FailedPreconditionError(
"Nothing to undo");
1427 current_state.
timestamp = std::chrono::steady_clock::now();
1440 return absl::FailedPreconditionError(
"Nothing to redo");
1448 current_state.
timestamp = std::chrono::steady_clock::now();
1461 return absl::FailedPreconditionError(
"No room loaded");
1483 return absl::OkStatus();
1504static uint32_t BlendColors(uint32_t base, uint32_t tint) {
1505 uint8_t a_tint = (tint >> 24) & 0xFF;
1509 uint8_t r_base = (base >> 16) & 0xFF;
1510 uint8_t g_base = (base >> 8) & 0xFF;
1511 uint8_t b_base = base & 0xFF;
1513 uint8_t r_tint = (tint >> 16) & 0xFF;
1514 uint8_t g_tint = (tint >> 8) & 0xFF;
1515 uint8_t b_tint = tint & 0xFF;
1517 float alpha = a_tint / 255.0f;
1518 uint8_t r = r_base * (1.0f - alpha) + r_tint * alpha;
1519 uint8_t g = g_base * (1.0f - alpha) + g_tint * alpha;
1520 uint8_t b = b_base * (1.0f - alpha) + b_tint * alpha;
1522 return 0xFF000000 | (r << 16) | (g << 8) | b;
1537 int x = obj.
x() * 16;
1538 int y = obj.y() * 16;
1539 int w = 16 + (obj.size() * 4);
1540 int h = 16 + (obj.size() * 4);
1548 for (
int py = y; py < y + h; py++) {
1549 for (
int px = x; px < x + w; px++) {
1550 if (px < canvas.
width() && py < canvas.
height() &&
1551 (px < x + 2 || px >= x + w - 2 || py < y + 2 || py >= y + h - 2)) {
1552 canvas.
SetPixel(px, py, sel_color);
1567 int x = obj.x() * 16;
1568 int y = obj.y() * 16;
1572 uint32_t tint_color = 0xFF000000;
1573 switch (obj.GetLayerValue()) {
1586 uint8_t r = (tint_color >> 16) & 0xFF;
1587 uint8_t g = (tint_color >> 8) & 0xFF;
1588 uint8_t b = tint_color & 0xFF;
1591 for (
int py = y; py < y + h && py < canvas.
height(); py++) {
1592 for (
int px = x; px < x + w && px < canvas.
width(); px++) {
1593 if (px == x || px == x + w - 1 || py == y || py == y + h - 1) {
1594 canvas.
SetPixel(px, py, layer_color);
1611 if (obj_idx < current_room_->GetTileObjectCount()) {
1621 ImGui::TableNextRow();
1622 ImGui::TableNextColumn();
1624 ImGui::TableNextColumn();
1626 ImGui::Text(
"0x%03X", obj.id_);
1628 ImGui::TextColored(theme.text_secondary_gray,
"(%s)", obj_name.c_str());
1632 ImGui::TableNextRow();
1633 ImGui::TableNextColumn();
1634 ImGui::Text(
"Type");
1635 ImGui::TableNextColumn();
1636 ImGui::Text(
"Subtype %d", subtype);
1647 ImGui::TableNextRow();
1648 ImGui::TableNextColumn();
1650 ImGui::TableNextColumn();
1652 ImGui::SetNextItemWidth(-1);
1653 if (ImGui::InputInt(
"##X", &x, 1, 4)) {
1654 if (x >= 0 && x < 64 && obj.x() != x) {
1665 ImGui::TableNextRow();
1666 ImGui::TableNextColumn();
1668 ImGui::TableNextColumn();
1670 ImGui::SetNextItemWidth(-1);
1671 if (ImGui::InputInt(
"##Y", &y, 1, 4)) {
1672 if (y >= 0 && y < 64 && obj.y() != y) {
1692 ImGui::TableNextRow();
1693 ImGui::TableNextColumn();
1694 ImGui::Text(
"Size");
1695 ImGui::TableNextColumn();
1696 int size = obj.size();
1697 ImGui::SetNextItemWidth(-1);
1698 if (ImGui::SliderInt(
"##Size", &size, 0, 15,
"0x%02X")) {
1704 ImGui::TableNextRow();
1705 ImGui::TableNextColumn();
1706 ImGui::Text(
"Change ID");
1707 ImGui::TableNextColumn();
1709 ImGui::SetNextItemWidth(-1);
1710 if (ImGui::InputInt(
"##ID", &
id, 1, 16,
1711 ImGuiInputTextFlags_CharsHexadecimal)) {
1712 if (
id >= 0 &&
id <= 0xFFF && obj.id_ !=
id) {
1723 const bool draws_to_both_bgs =
1726 uses_room_stream ?
"Object Stream" :
"Special Layer",
1728 bool storage_changed =
false;
1730 if (uses_room_stream) {
1732 ImGui::TableNextRow();
1733 ImGui::TableNextColumn();
1734 ImGui::Text(
"Draws To");
1735 ImGui::TableNextColumn();
1736 if (semantics.draws_to_both_bgs) {
1737 ImGui::TextColored(theme.text_warning_yellow,
"Both (BG1 + BG2)");
1744 ImGui::TableNextRow();
1745 ImGui::TableNextColumn();
1746 ImGui::Text(
"%s", uses_room_stream ?
"Stream" :
"Selector");
1747 ImGui::TableNextColumn();
1748 int layer = obj.GetLayerValue();
1749 ImGui::SetNextItemWidth(-1);
1750 const char* choices = uses_room_stream
1751 ?
"Primary\0BG2 overlay\0BG1 overlay\0"
1752 :
"Upper layer (BG1)\0Lower layer (BG2)\0";
1753 if (ImGui::Combo(
"##Layer", &layer, choices)) {
1754 if (obj.GetLayerValue() != layer) {
1758 if (draws_to_both_bgs) {
1761 "This object draws to both BG1 and BG2. Its object stream still "
1762 "controls draw order and ROM serialization.");
1767 if (storage_changed) {
1776 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1780 ImVec4(theme.status_error.x * 0.7f, theme.status_error.y * 0.7f,
1781 theme.status_error.z * 0.7f, 1.0f)},
1782 {ImGuiCol_ButtonHovered, theme.status_error}});
1783 if (ImGui::Button(
ICON_MD_DELETE " Delete", ImVec2(button_width, 0))) {
1791 ImVec2(button_width, 0))) {
1797 ImGui::TextColored(theme.text_warning_yellow,
1803 bool has_room_stream_object =
false;
1804 bool has_special_table_object =
false;
1805 bool has_editable_size_object =
false;
1812 has_room_stream_object =
true;
1814 has_special_table_object =
true;
1819 if (has_special_table_object) {
1822 has_room_stream_object ?
"Batch Placement" :
"Batch Special Layer",
1824 if (has_room_stream_object) {
1826 "Mixed selection: values apply as object streams to room objects "
1827 "and special draw-layer selectors to torches/blocks.");
1829 static int batch_special_layer = 0;
1830 batch_special_layer = std::clamp(batch_special_layer, 0, 1);
1831 ImGui::SetNextItemWidth(-1);
1832 const char* choices = has_room_stream_object
1833 ?
"Primary / Upper layer (BG1)\0BG2 overlay / "
1834 "Lower layer (BG2)\0"
1835 :
"Upper layer (BG1)\0Lower layer (BG2)\0";
1836 if (ImGui::Combo(
"##BatchSpecialLayer", &batch_special_layer, choices)) {
1838 batch_special_layer);
1843 static int batch_stream = 0;
1844 ImGui::SetNextItemWidth(-1);
1845 if (ImGui::Combo(
"##BatchStream", &batch_stream,
1846 "Primary\0BG2 overlay\0BG1 overlay\0")) {
1855 static int batch_size = 0x02;
1856 ImGui::SetNextItemWidth(-1);
1857 ImGui::BeginDisabled(!has_editable_size_object);
1858 if (ImGui::InputInt(
"##BatchSize", &batch_size, 1, 16,
1859 ImGuiInputTextFlags_CharsHexadecimal)) {
1860 batch_size = std::clamp(batch_size, 0, 15);
1863 ImGui::EndDisabled();
1869 float nudge_btn_size = (ImGui::GetContentRegionAvail().x - 24) / 4;
1891 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1895 ImVec4(theme.status_error.x * 0.7f, theme.status_error.y * 0.7f,
1896 theme.status_error.z * 0.7f, 1.0f)},
1897 {ImGuiCol_ButtonHovered, theme.status_error}});
1899 ImVec2(button_width, 0))) {
1907 ImVec2(button_width, 0))) {
1915 ImGui::Begin(
"Layer Controls");
1918 ImGui::Text(
"Current Layer:");
1928 static bool layer_visible[3] = {
true,
true,
true};
1929 ImGui::Text(
"Layer Visibility:");
1930 ImGui::Checkbox(
"Show Layer 0", &layer_visible[0]);
1931 ImGui::Checkbox(
"Show Layer 1", &layer_visible[1]);
1932 ImGui::Checkbox(
"Show Layer 2", &layer_visible[2]);
1948 int count0 = 0, count1 = 0, count2 = 0;
1950 switch (obj.GetLayerValue()) {
1962 ImGui::Text(
"Layer 0: %d objects", count0);
1963 ImGui::Text(
"Layer 1: %d objects", count1);
1964 ImGui::Text(
"Layer 2: %d objects", count2);
1974 return absl::OkStatus();
1985 if (grid_dx == 0 && grid_dy == 0) {
1986 return absl::OkStatus();
1995 int new_x = obj.
x() + grid_dx;
1996 int new_y = obj.y() + grid_dy;
1999 new_x = std::max(0, std::min(63, new_x));
2000 new_y = std::max(0, std::min(63, new_y));
2002 const bool changed = obj.x() != new_x || obj.y() != new_y;
2021 return absl::OkStatus();
2026 return {
false, {}, {
"No room loaded"}};
2035 for (
size_t i = 0; i < objects.size(); i++) {
2036 for (
size_t j = i + 1; j < objects.size(); j++) {
2039 absl::StrFormat(
"Objects at indices %d and %d collide", i, j));
2051 std::vector<std::string> all_issues = result.errors;
2052 all_issues.insert(all_issues.end(), result.warnings.begin(),
2053 result.warnings.end());
2104 return std::make_unique<DungeonObjectEditor>(rom);
2108namespace ObjectCategories {
2112 {
"Walls", {0x10, 0x11, 0x12, 0x13},
"Basic wall objects"},
2113 {
"Floors", {0x20, 0x21, 0x22, 0x23},
"Floor tile objects"},
2114 {
"Decorations", {0x30, 0x31, 0x32, 0x33},
"Decorative objects"},
2115 {
"Interactive", {0xF9, 0xFA, 0xFB},
"Interactive objects like chests"},
2116 {
"Stairs", {0x13, 0x14, 0x15, 0x16},
"Staircase objects"},
2117 {
"Doors", {0x17, 0x18, 0x19, 0x1A},
"Door objects"},
2119 {0xF80, 0xF81, 0xF82, 0xF97},
2120 "Special dungeon objects (Type 3)"}};
2124 const std::string& category_name) {
2127 for (
const auto&
category : categories) {
2128 if (
category.name == category_name) {
2133 return absl::NotFoundError(
"Category not found");
2139 for (
const auto&
category : categories) {
2140 for (
int id :
category.object_ids) {
2141 if (
id == object_id) {
2147 return absl::NotFoundError(
"Object category not found");
2152 info.
id = object_id;
2157 if (object_id >= 0x10 && object_id <= 0x1F) {
2164 }
else if (object_id >= 0x20 && object_id <= 0x2F) {
2165 info.
name =
"Floor";
2171 }
else if (object_id == 0xF9) {
2172 info.
name =
"Small Chest";
2179 info.
name =
"Unknown Object";
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Represents a bitmap image optimized for SNES ROM hacking.
void SetPixel(int x, int y, const SnesColor &color)
Set a pixel at the given x,y coordinates with SNES color.
RAII guard for ImGui style colors.
ValidationResult ValidateRoom()
std::function< void(const SelectionState &)> SelectionChangedCallback
absl::Status HandleMouseDrag(int start_x, int start_y, int current_x, int current_y)
absl::Status HandleScrollWheel(int delta, int x, int y, bool ctrl_pressed)
std::pair< int, int > ScreenToRoomCoordinates(int screen_x, int screen_y)
int GetNextSize(int current_size, int delta)
void SetRoomChangedCallback(RoomChangedCallback callback)
std::pair< int, int > RoomToScreenCoordinates(int room_x, int room_y)
EditingState editing_state_
static constexpr int kMaxLayer
ObjectTemplateManager template_manager_
const std::vector< ObjectTemplate > & GetTemplates() const
absl::Status BatchMoveObjects(const std::vector< size_t > &indices, int dx, int dy)
DungeonObjectEditor(Rom *rom)
absl::Status ChangeObjectLayer(size_t object_index, int new_layer)
absl::Status BatchChangeObjectLayer(const std::vector< size_t > &indices, int new_layer)
absl::Status DeleteObject(size_t object_index)
void CopySelectedObjects(const std::vector< size_t > &indices)
SelectionState selection_state_
absl::Status InitializeEditor()
void SetSelectionChangedCallback(SelectionChangedCallback callback)
absl::Status SelectObject(int screen_x, int screen_y)
std::optional< size_t > DuplicateObject(size_t object_index, int offset_x=1, int offset_y=1)
absl::Status AlignSelectedObjects(Alignment alignment)
absl::Status CreateUndoPoint()
absl::Status InsertObject(int x, int y, int object_type, int size=0x02, int layer=0)
std::vector< UndoPoint > undo_history_
static constexpr size_t kMaxUndoHistory
bool IsValidSize(int size)
std::optional< size_t > FindObjectAt(int room_x, int room_y)
static constexpr int kMaxObjectSize
absl::Status AddToSelection(size_t object_index)
std::function< void()> RoomChangedCallback
std::vector< RoomObject > clipboard_
static constexpr int kMinLayer
ObjectChangedCallback object_changed_callback_
DungeonValidator validator_
void RenderLayerVisualization(gfx::Bitmap &canvas)
std::vector< std::string > GetValidationErrors()
absl::Status LoadRoom(int room_id)
std::unique_ptr< Room > owned_room_
void SetConfig(const EditorConfig &config)
std::function< void(size_t object_index, const RoomObject &object)> ObjectChangedCallback
std::vector< UndoPoint > redo_history_
absl::Status HandleDragOperation(int current_x, int current_y)
absl::Status HandleSizeEdit(int delta, int x, int y)
bool ObjectsCollide(const RoomObject &obj1, const RoomObject &obj2)
void SetExternalRoom(Room *room)
absl::Status ClearSelection()
bool IsObjectAtPosition(const RoomObject &object, int x, int y)
void SetCurrentLayer(int layer)
void SetObjectChangedCallback(ObjectChangedCallback callback)
std::vector< size_t > PasteObjects()
absl::Status HandleMouseRelease(int x, int y)
void UpdatePreviewObject()
absl::Status InsertTemplate(const ObjectTemplate &tmpl, int x, int y)
absl::Status MoveObject(size_t object_index, int new_x, int new_y)
absl::Status ResizeObject(size_t object_index, int new_size)
std::optional< RoomObject > preview_object_
absl::Status BatchResizeObjects(const std::vector< size_t > &indices, int new_size)
static constexpr int kMinObjectSize
absl::Status ChangeObjectType(size_t object_index, int new_type)
void SetCurrentObjectType(int object_type)
absl::Status DeleteSelectedObjects()
SelectionChangedCallback selection_changed_callback_
int SnapToGrid(int coordinate)
absl::Status ApplyUndoPoint(const UndoPoint &undo_point)
absl::Status HandleMouseClick(int x, int y, bool left_button, bool right_button, bool shift_pressed)
void RenderSelectionHighlight(gfx::Bitmap &canvas)
absl::Status CreateTemplateFromSelection(const std::string &name, const std::string &description)
RoomChangedCallback room_changed_callback_
void RenderLayerControls()
ValidationResult ValidateRoom(const Room &room)
static ObjectTemplate CreateFromObjects(const std::string &name, const std::string &description, const std::vector< RoomObject > &objects, int origin_x, int origin_y)
const std::vector< ObjectTemplate > & GetTemplates() const
std::vector< RoomObject > InstantiateTemplate(const ObjectTemplate &tmpl, int x, int y, Rom *rom)
absl::Status LoadTemplates(const std::string &directory_path)
absl::Status SaveTemplate(const ObjectTemplate &tmpl, const std::string &directory_path)
RoomObject CopyForNewPlacement() const
void MarkSaveDirtyForTileObject(const RoomObject &object)
absl::Status RemoveObject(size_t index)
size_t GetTileObjectCount() const
RoomObject & GetTileObject(size_t index)
absl::Status SaveObjects(const DungeonStreamLayout *layout=nullptr)
const std::vector< RoomObject > & GetTileObjects() const
void SetTileObjects(const std::vector< RoomObject > &objects)
absl::Status AddObject(const RoomObject &object)
void RemoveTileObject(size_t index)
#define ICON_MD_ARROW_FORWARD
#define ICON_MD_OPEN_WITH
#define ICON_MD_ARROW_DOWNWARD
#define ICON_MD_ASPECT_RATIO
#define ICON_MD_ARROW_UPWARD
#define ICON_MD_ARROW_BACK
#define ICON_MD_SELECT_ALL
#define ICON_MD_CONTENT_COPY
#define ICON_MD_DELETE_SWEEP
const AgentUITheme & GetTheme()
void PropertyRow(const char *label, const char *value)
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
bool BeginPropertyTable(const char *id, int columns, ImGuiTableFlags extra_flags)
void HelpMarker(const char *desc)
std::vector< ObjectCategory > GetObjectCategories()
Get all available object categories.
absl::StatusOr< ObjectInfo > GetObjectInfo(int object_id)
absl::StatusOr< std::string > GetObjectCategory(int object_id)
Get category for a specific object.
absl::StatusOr< std::vector< int > > GetObjectsInCategory(const std::string &category_name)
Get objects in a specific category.
bool IsRepresentableRoomObjectId(int object_id)
ObjectLayerSemantics GetObjectLayerSemantics(const RoomObject &object)
uint8_t DefaultRoomObjectSizeForPlacement(int object_id)
bool IsRoomObjectSizeEditable(int object_id)
std::unique_ptr< DungeonObjectEditor > CreateDungeonObjectEditor(Rom *rom)
Factory function to create dungeon object editor.
int GetObjectSubtype(int object_id)
uint8_t CanonicalRoomObjectSize(int object_id, uint8_t requested_size)
bool UsesRoomObjectStream(const RoomObject &object)
std::string GetObjectName(int object_id)
constexpr int kNumberOfRooms
bool UsesSpecialLayerSelector(const RoomObject &object)
absl::StatusOr< ObjectStorageMutationResult > ReassignObjectStorage(std::vector< RoomObject > &objects, const std::vector< size_t > &indices, int target_value)
const char * EffectiveBgLayerLabel(EffectiveBgLayer layer)
bool show_collision_bounds
bool show_selection_highlight
std::vector< size_t > selected_objects
std::vector< RoomObject > objects
std::chrono::steady_clock::time_point timestamp
std::vector< std::pair< int, int > > valid_sizes
std::vector< int > valid_layers
std::vector< std::string > errors