7#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
25 if (
rom_ ==
nullptr) {
26 return absl::InvalidArgumentError(
"ROM is null");
53 return absl::OkStatus();
57 if (
rom_ ==
nullptr) {
58 return absl::InvalidArgumentError(
"ROM is null");
62 return absl::InvalidArgumentError(
"Invalid room ID");
88 return absl::OkStatus();
93 return absl::FailedPreconditionError(
"No room loaded");
99 if (!validation_result.is_valid) {
100 std::string error_msg =
"Validation failed";
101 if (!validation_result.errors.empty()) {
102 error_msg +=
": " + validation_result.errors[0];
104 return absl::FailedPreconditionError(error_msg);
114 return absl::FailedPreconditionError(
"No room loaded");
134 return absl::OkStatus();
138 int size,
int layer) {
140 return absl::FailedPreconditionError(
"No room loaded");
145 if (object_type < 0 || object_type > 0xFFF) {
146 return absl::InvalidArgumentError(
"Invalid object type");
150 return absl::InvalidArgumentError(
"Invalid object size");
153 if (layer < kMinLayer || layer >
kMaxLayer) {
154 return absl::InvalidArgumentError(
"Invalid layer");
170 RoomObject new_object(object_type, x, y, size, layer);
178 return absl::FailedPreconditionError(
179 "Object placement would cause collision");
186 if (!add_status.ok()) {
209 return absl::OkStatus();
214 return absl::FailedPreconditionError(
"No room loaded");
218 return absl::OutOfRangeError(
"Object index out of range");
229 if (!remove_status.ok()) {
230 return remove_status;
235 if (selected_index > object_index) {
237 }
else if (selected_index == object_index) {
255 return absl::OkStatus();
260 return absl::FailedPreconditionError(
"No room loaded");
264 return absl::FailedPreconditionError(
"No objects selected");
275 std::sort(sorted_selection.begin(), sorted_selection.end(),
276 std::greater<size_t>());
279 for (
size_t index : sorted_selection) {
280 if (index < current_room_->GetTileObjectCount()) {
293 return absl::OkStatus();
299 return absl::FailedPreconditionError(
"No room loaded");
303 return absl::OutOfRangeError(
"Object index out of range");
324 test_object.
set_x(new_x);
325 test_object.
set_y(new_y);
328 if (i != object_index &&
330 return absl::FailedPreconditionError(
331 "Object move would cause collision");
337 const bool changed =
object.x() != new_x ||
object.y() != new_y;
356 return absl::OkStatus();
362 return absl::FailedPreconditionError(
"No room loaded");
366 return absl::OutOfRangeError(
"Object index out of range");
370 return absl::InvalidArgumentError(
"Invalid object size");
381 const bool changed =
object.
size() != new_size;
385 object.set_size(new_size);
399 return absl::OkStatus();
403 const std::vector<size_t>& indices,
int dx,
int dy) {
405 return absl::FailedPreconditionError(
"No room loaded");
408 if (indices.empty()) {
409 return absl::OkStatus();
419 for (
size_t index : indices) {
424 int new_x =
object.
x() + dx;
425 int new_y =
object.y() + dy;
428 new_x = std::max(0, std::min(63, new_x));
429 new_y = std::max(0, std::min(63, new_y));
431 const bool changed =
object.x() != new_x ||
object.y() != new_y;
450 return absl::OkStatus();
454 const std::vector<size_t>& indices,
int new_layer) {
456 return absl::FailedPreconditionError(
"No room loaded");
459 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
460 return absl::InvalidArgumentError(
"Invalid layer");
463 const std::vector<size_t> requested_indices = indices;
465 bool change_needed =
false;
466 for (
size_t index : requested_indices) {
467 if (index >= objects.size()) {
471 return absl::InvalidArgumentError(
472 "Torches and pushable blocks only support upper/lower draw-layer "
473 "selector values 0/1");
475 change_needed |= objects[index].GetLayerValue() != new_layer;
477 if (!change_needed) {
478 return absl::OkStatus();
487 const std::vector<RoomObject> before = objects;
489 if (!mutation.ok()) {
490 return mutation.status();
493 for (
size_t old_index : mutation->changed_old_indices) {
494 const size_t new_index = mutation->old_to_new_index[old_index];
499 std::vector<size_t> remapped_selection;
502 if (old_index < mutation->old_to_new_index.size()) {
503 remapped_selection.push_back(mutation->old_to_new_index[old_index]);
506 std::sort(remapped_selection.begin(), remapped_selection.end());
507 remapped_selection.erase(
508 std::unique(remapped_selection.begin(), remapped_selection.end()),
509 remapped_selection.end());
518 for (
size_t old_index : mutation->changed_old_indices) {
519 const size_t new_index = mutation->old_to_new_index[old_index];
528 return absl::OkStatus();
532 const std::vector<size_t>& indices,
int new_size) {
534 return absl::FailedPreconditionError(
"No room loaded");
538 return absl::InvalidArgumentError(
"Invalid object size");
547 for (
size_t index : indices) {
554 const bool changed =
object.
size() != new_size;
558 object.set_size(new_size);
572 return absl::OkStatus();
593 int new_x =
object.
x() + offset_x;
594 int new_y =
object.y() + offset_y;
597 new_x = std::max(0, std::min(63, new_x));
598 new_y = std::max(0, std::min(63, new_y));
618 const std::vector<size_t>& indices) {
624 for (
size_t index : indices) {
625 if (index < current_room_->GetTileObjectCount()) {
639 std::vector<size_t> new_indices;
651 int new_x = std::min(63, new_obj.
x() + 1);
652 int new_y = std::min(63, new_obj.
y() + 1);
653 new_obj.
set_x(new_x);
654 new_obj.
set_y(new_y);
657 new_indices.push_back(start_index++);
671 return absl::FailedPreconditionError(
"No room loaded");
675 return absl::OutOfRangeError(
"Object index out of range");
679 if (new_type < 0 || new_type > 0xFFF) {
680 return absl::InvalidArgumentError(
"Invalid object type");
690 const bool changed =
object.
id_ != new_type;
694 object.set_id(
static_cast<int16_t
>(new_type));
707 return absl::OkStatus();
713 return absl::FailedPreconditionError(
"No room loaded");
729 std::vector<RoomObject> new_objects =
734 for (
const auto& new_obj : new_objects) {
737 return absl::FailedPreconditionError(
738 "Template placement would cause collision");
745 for (
const auto& obj : new_objects) {
752 size_t added_count = new_objects.size();
753 for (
size_t i = 0; i < added_count; ++i) {
764 return absl::OkStatus();
770 return absl::FailedPreconditionError(
"No objects selected");
773 std::vector<RoomObject> objects;
774 int min_x = 64, min_y = 64;
778 if (index < current_room_->GetTileObjectCount()) {
780 objects.push_back(obj);
802 return absl::FailedPreconditionError(
"No room loaded");
806 return absl::OkStatus();
829 for (
size_t index : indices) {
836 if (obj.x() < ref_val)
840 if (obj.x() > ref_val)
844 if (obj.y() < ref_val)
848 if (obj.y() > ref_val)
864 ref_val = sum / count;
868 for (
size_t index : indices) {
876 const bool changed = changes_x ? obj.
x() != ref_val : obj.y() != ref_val;
907 return absl::OkStatus();
913 return absl::FailedPreconditionError(
"No room loaded");
917 return absl::OutOfRangeError(
"Object index out of range");
920 if (new_layer < kMinLayer || new_layer >
kMaxLayer) {
921 return absl::InvalidArgumentError(
"Invalid layer");
930 return absl::FailedPreconditionError(
"No room loaded");
945 int layer_delta = delta > 0 ? 1 : -1;
953 return absl::OkStatus();
956 return absl::OkStatus();
967 return absl::OkStatus();
974 if (object_index < current_room_->GetTileObjectCount()) {
985 return absl::OkStatus();
988 return absl::OkStatus();
998 if (current_size < 0x40) {
999 return current_size + 0x10;
1000 }
else if (current_size < 0x80) {
1001 return current_size + 0x08;
1003 return current_size + 0x04;
1007 if (current_size > 0x80) {
1008 return current_size - 0x04;
1009 }
else if (current_size > 0x40) {
1010 return current_size - 0x08;
1012 return current_size - 0x10;
1024 bool shift_pressed) {
1026 return absl::FailedPreconditionError(
"No room loaded");
1035 if (shift_pressed) {
1038 if (object_index.has_value()) {
1057 if (object_index.has_value()) {
1079 if (object_index.has_value()) {
1090 return absl::OkStatus();
1097 return absl::FailedPreconditionError(
"No room loaded");
1109 if (!undo_status.ok()) {
1120 return absl::FailedPreconditionError(
"No room loaded");
1133 return absl::OkStatus();
1138 return absl::FailedPreconditionError(
"No room loaded");
1147 if (object_index.has_value()) {
1157 return absl::OkStatus();
1174 return absl::OkStatus();
1179 return absl::FailedPreconditionError(
"No room loaded");
1183 return absl::OutOfRangeError(
"Object index out of range");
1200 return absl::OkStatus();
1219 if (object_type >= 0 && object_type <= 0xFFF) {
1228 return std::nullopt;
1235 return static_cast<size_t>(i);
1239 return std::nullopt;
1245 int obj_x =
object.x_;
1246 int obj_y =
object.y_;
1251 if (
object.size_ > 0x80) {
1256 return (x >= obj_x && x < obj_x + obj_width && y >= obj_y &&
1257 y < obj_y + obj_height);
1265 int obj1_x = obj1.
x_ * 16;
1266 int obj1_y = obj1.
y_ * 16;
1270 int obj2_x = obj2.
x_ * 16;
1271 int obj2_y = obj2.
y_ * 16;
1276 if (obj1.
size_ > 0x80) {
1281 if (obj2.
size_ > 0x80) {
1286 return !(obj1_x + obj1_w <= obj2_x || obj2_x + obj2_w <= obj1_x ||
1287 obj1_y + obj1_h <= obj2_y || obj2_y + obj2_h <= obj1_y);
1296 int room_x = screen_x / 16;
1297 int room_y = screen_y / 16;
1299 return {room_x, room_y};
1305 int screen_x = room_x * 16;
1306 int screen_y = room_y * 16;
1308 return {screen_x, screen_y};
1317 if (grid_size <= 0) {
1322 int tile_step = std::max(1, grid_size / 16);
1323 return (coordinate / tile_step) * tile_step;
1342 return absl::FailedPreconditionError(
"No room loaded");
1350 undo_point.
timestamp = std::chrono::steady_clock::now();
1363 return absl::OkStatus();
1368 return absl::FailedPreconditionError(
"Nothing to undo");
1376 current_state.
timestamp = std::chrono::steady_clock::now();
1389 return absl::FailedPreconditionError(
"Nothing to redo");
1397 current_state.
timestamp = std::chrono::steady_clock::now();
1410 return absl::FailedPreconditionError(
"No room loaded");
1432 return absl::OkStatus();
1453static uint32_t BlendColors(uint32_t base, uint32_t tint) {
1454 uint8_t a_tint = (tint >> 24) & 0xFF;
1458 uint8_t r_base = (base >> 16) & 0xFF;
1459 uint8_t g_base = (base >> 8) & 0xFF;
1460 uint8_t b_base = base & 0xFF;
1462 uint8_t r_tint = (tint >> 16) & 0xFF;
1463 uint8_t g_tint = (tint >> 8) & 0xFF;
1464 uint8_t b_tint = tint & 0xFF;
1466 float alpha = a_tint / 255.0f;
1467 uint8_t r = r_base * (1.0f - alpha) + r_tint * alpha;
1468 uint8_t g = g_base * (1.0f - alpha) + g_tint * alpha;
1469 uint8_t b = b_base * (1.0f - alpha) + b_tint * alpha;
1471 return 0xFF000000 | (r << 16) | (g << 8) | b;
1486 int x = obj.
x() * 16;
1487 int y = obj.y() * 16;
1488 int w = 16 + (obj.size() * 4);
1489 int h = 16 + (obj.size() * 4);
1497 for (
int py = y; py < y + h; py++) {
1498 for (
int px = x; px < x + w; px++) {
1499 if (px < canvas.
width() && py < canvas.
height() &&
1500 (px < x + 2 || px >= x + w - 2 || py < y + 2 || py >= y + h - 2)) {
1501 canvas.
SetPixel(px, py, sel_color);
1516 int x = obj.x() * 16;
1517 int y = obj.y() * 16;
1521 uint32_t tint_color = 0xFF000000;
1522 switch (obj.GetLayerValue()) {
1535 uint8_t r = (tint_color >> 16) & 0xFF;
1536 uint8_t g = (tint_color >> 8) & 0xFF;
1537 uint8_t b = tint_color & 0xFF;
1540 for (
int py = y; py < y + h && py < canvas.
height(); py++) {
1541 for (
int px = x; px < x + w && px < canvas.
width(); px++) {
1542 if (px == x || px == x + w - 1 || py == y || py == y + h - 1) {
1543 canvas.
SetPixel(px, py, layer_color);
1560 if (obj_idx < current_room_->GetTileObjectCount()) {
1570 ImGui::TableNextRow();
1571 ImGui::TableNextColumn();
1573 ImGui::TableNextColumn();
1575 ImGui::Text(
"0x%03X", obj.id_);
1577 ImGui::TextColored(theme.text_secondary_gray,
"(%s)", obj_name.c_str());
1581 ImGui::TableNextRow();
1582 ImGui::TableNextColumn();
1583 ImGui::Text(
"Type");
1584 ImGui::TableNextColumn();
1585 ImGui::Text(
"Subtype %d", subtype);
1596 ImGui::TableNextRow();
1597 ImGui::TableNextColumn();
1599 ImGui::TableNextColumn();
1601 ImGui::SetNextItemWidth(-1);
1602 if (ImGui::InputInt(
"##X", &x, 1, 4)) {
1603 if (x >= 0 && x < 64 && obj.x() != x) {
1614 ImGui::TableNextRow();
1615 ImGui::TableNextColumn();
1617 ImGui::TableNextColumn();
1619 ImGui::SetNextItemWidth(-1);
1620 if (ImGui::InputInt(
"##Y", &y, 1, 4)) {
1621 if (y >= 0 && y < 64 && obj.y() != y) {
1640 if (obj.id_ < 0x100) {
1641 ImGui::TableNextRow();
1642 ImGui::TableNextColumn();
1643 ImGui::Text(
"Size");
1644 ImGui::TableNextColumn();
1645 int size = obj.size();
1646 ImGui::SetNextItemWidth(-1);
1647 if (ImGui::SliderInt(
"##Size", &size, 0, 15,
"0x%02X")) {
1658 ImGui::TableNextRow();
1659 ImGui::TableNextColumn();
1660 ImGui::Text(
"Change ID");
1661 ImGui::TableNextColumn();
1663 ImGui::SetNextItemWidth(-1);
1664 if (ImGui::InputInt(
"##ID", &
id, 1, 16,
1665 ImGuiInputTextFlags_CharsHexadecimal)) {
1666 if (
id >= 0 &&
id <= 0xFFF && obj.id_ !=
id) {
1668 obj.set_id(
static_cast<int16_t
>(
id));
1682 const bool draws_to_both_bgs =
1685 uses_room_stream ?
"Object Stream" :
"Special Layer",
1687 bool storage_changed =
false;
1689 if (uses_room_stream) {
1691 ImGui::TableNextRow();
1692 ImGui::TableNextColumn();
1693 ImGui::Text(
"Draws To");
1694 ImGui::TableNextColumn();
1695 if (semantics.draws_to_both_bgs) {
1696 ImGui::TextColored(theme.text_warning_yellow,
"Both (BG1 + BG2)");
1703 ImGui::TableNextRow();
1704 ImGui::TableNextColumn();
1705 ImGui::Text(
"%s", uses_room_stream ?
"Stream" :
"Selector");
1706 ImGui::TableNextColumn();
1707 int layer = obj.GetLayerValue();
1708 ImGui::SetNextItemWidth(-1);
1709 const char* choices = uses_room_stream
1710 ?
"Primary\0BG2 overlay\0BG1 overlay\0"
1711 :
"Upper layer (BG1)\0Lower layer (BG2)\0";
1712 if (ImGui::Combo(
"##Layer", &layer, choices)) {
1713 if (obj.GetLayerValue() != layer) {
1717 if (draws_to_both_bgs) {
1720 "This object draws to both BG1 and BG2. Its object stream still "
1721 "controls draw order and ROM serialization.");
1726 if (storage_changed) {
1735 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1739 ImVec4(theme.status_error.x * 0.7f, theme.status_error.y * 0.7f,
1740 theme.status_error.z * 0.7f, 1.0f)},
1741 {ImGuiCol_ButtonHovered, theme.status_error}});
1742 if (ImGui::Button(
ICON_MD_DELETE " Delete", ImVec2(button_width, 0))) {
1750 ImVec2(button_width, 0))) {
1756 ImGui::TextColored(theme.text_warning_yellow,
1762 bool has_room_stream_object =
false;
1763 bool has_special_table_object =
false;
1769 has_room_stream_object =
true;
1771 has_special_table_object =
true;
1775 if (has_special_table_object) {
1778 has_room_stream_object ?
"Batch Placement" :
"Batch Special Layer",
1780 if (has_room_stream_object) {
1782 "Mixed selection: values apply as object streams to room objects "
1783 "and special draw-layer selectors to torches/blocks.");
1785 static int batch_special_layer = 0;
1786 batch_special_layer = std::clamp(batch_special_layer, 0, 1);
1787 ImGui::SetNextItemWidth(-1);
1788 const char* choices = has_room_stream_object
1789 ?
"Primary / Upper layer (BG1)\0BG2 overlay / "
1790 "Lower layer (BG2)\0"
1791 :
"Upper layer (BG1)\0Lower layer (BG2)\0";
1792 if (ImGui::Combo(
"##BatchSpecialLayer", &batch_special_layer, choices)) {
1794 batch_special_layer);
1799 static int batch_stream = 0;
1800 ImGui::SetNextItemWidth(-1);
1801 if (ImGui::Combo(
"##BatchStream", &batch_stream,
1802 "Primary\0BG2 overlay\0BG1 overlay\0")) {
1811 static int batch_size = 0x12;
1812 ImGui::SetNextItemWidth(-1);
1813 if (ImGui::InputInt(
"##BatchSize", &batch_size, 1, 16,
1814 ImGuiInputTextFlags_CharsHexadecimal)) {
1822 float nudge_btn_size = (ImGui::GetContentRegionAvail().x - 24) / 4;
1844 float button_width = (ImGui::GetContentRegionAvail().x - 8) / 2;
1848 ImVec4(theme.status_error.x * 0.7f, theme.status_error.y * 0.7f,
1849 theme.status_error.z * 0.7f, 1.0f)},
1850 {ImGuiCol_ButtonHovered, theme.status_error}});
1852 ImVec2(button_width, 0))) {
1860 ImVec2(button_width, 0))) {
1868 ImGui::Begin(
"Layer Controls");
1871 ImGui::Text(
"Current Layer:");
1881 static bool layer_visible[3] = {
true,
true,
true};
1882 ImGui::Text(
"Layer Visibility:");
1883 ImGui::Checkbox(
"Show Layer 0", &layer_visible[0]);
1884 ImGui::Checkbox(
"Show Layer 1", &layer_visible[1]);
1885 ImGui::Checkbox(
"Show Layer 2", &layer_visible[2]);
1901 int count0 = 0, count1 = 0, count2 = 0;
1903 switch (obj.GetLayerValue()) {
1915 ImGui::Text(
"Layer 0: %d objects", count0);
1916 ImGui::Text(
"Layer 1: %d objects", count1);
1917 ImGui::Text(
"Layer 2: %d objects", count2);
1927 return absl::OkStatus();
1938 if (grid_dx == 0 && grid_dy == 0) {
1939 return absl::OkStatus();
1948 int new_x = obj.
x() + grid_dx;
1949 int new_y = obj.y() + grid_dy;
1952 new_x = std::max(0, std::min(63, new_x));
1953 new_y = std::max(0, std::min(63, new_y));
1955 const bool changed = obj.x() != new_x || obj.y() != new_y;
1974 return absl::OkStatus();
1979 return {
false, {}, {
"No room loaded"}};
1988 for (
size_t i = 0; i < objects.size(); i++) {
1989 for (
size_t j = i + 1; j < objects.size(); j++) {
1992 absl::StrFormat(
"Objects at indices %d and %d collide", i, j));
2004 std::vector<std::string> all_issues = result.errors;
2005 all_issues.insert(all_issues.end(), result.warnings.begin(),
2006 result.warnings.end());
2057 return std::make_unique<DungeonObjectEditor>(rom);
2061namespace ObjectCategories {
2065 {
"Walls", {0x10, 0x11, 0x12, 0x13},
"Basic wall objects"},
2066 {
"Floors", {0x20, 0x21, 0x22, 0x23},
"Floor tile objects"},
2067 {
"Decorations", {0x30, 0x31, 0x32, 0x33},
"Decorative objects"},
2068 {
"Interactive", {0xF9, 0xFA, 0xFB},
"Interactive objects like chests"},
2069 {
"Stairs", {0x13, 0x14, 0x15, 0x16},
"Staircase objects"},
2070 {
"Doors", {0x17, 0x18, 0x19, 0x1A},
"Door objects"},
2072 {0xF80, 0xF81, 0xF82, 0xF97},
2073 "Special dungeon objects (Type 3)"}};
2077 const std::string& category_name) {
2080 for (
const auto&
category : categories) {
2081 if (
category.name == category_name) {
2086 return absl::NotFoundError(
"Category not found");
2092 for (
const auto&
category : categories) {
2093 for (
int id :
category.object_ids) {
2094 if (
id == object_id) {
2100 return absl::NotFoundError(
"Object category not found");
2105 info.
id = object_id;
2110 if (object_id >= 0x10 && object_id <= 0x1F) {
2117 }
else if (object_id >= 0x20 && object_id <= 0x2F) {
2118 info.
name =
"Floor";
2124 }
else if (object_id == 0xF9) {
2125 info.
name =
"Small Chest";
2132 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()
static constexpr int kDefaultObjectSize
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)
absl::Status InsertObject(int x, int y, int object_type, int size=0x12, int layer=0)
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()
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.
ObjectLayerSemantics GetObjectLayerSemantics(const RoomObject &object)
std::unique_ptr< DungeonObjectEditor > CreateDungeonObjectEditor(Rom *rom)
Factory function to create dungeon object editor.
int GetObjectSubtype(int object_id)
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