44 if (!IsInside(stamp.
x, stamp.
y, stamp.
size) ||
46 return absl::InvalidArgumentError(
"Choose a rail stamp inside the room");
50 return absl::FailedPreconditionError(
51 "Move requires a matching stop, T junction, or switch override");
61 for (
int y = stamp.
y; y < stamp.
y + stamp.
size; ++y) {
62 for (
int x = stamp.
x; x < stamp.
x + stamp.
size; ++x) {
63 const auto old = before.
tiles[y * 64 + x];
64 if (before.
HasOverride(y * 64 + x) && !IsRail(old)) {
65 return absl::FailedPreconditionError(
66 "Other collision occupies this cell; use the collision inspector");
68 if (stamp.
value != 0 && ((IsStop(old) && !IsStop(stamp.
value)) ||
69 (IsSwitch(old) && !IsSwitch(stamp.
value)))) {
70 return absl::FailedPreconditionError(
71 "Stop or switch protected; choose the matching behavior tool");
73 if (stamp.
value == 0) {
75 after.ClearOverride(y * 64 + x);
77 after.SetOverride(y * 64 + x, stamp.
value);
92 int room_id, uint8_t value,
int size,
94 const int frame = ImGui::GetFrameCount();
96 if (
drag_ && (
drag_->room_id != room_id ||
drag_->stamp.value != value ||
101 if (size != 1 && size != 2) {
103 message_ =
"Choose a one-cell or two-cell collision stamp";
106 if (ImGui::IsKeyPressed(ImGuiKey_Escape) ||
107 ImGui::IsMouseDown(ImGuiMouseButton_Right) ||
108 ImGui::IsMouseDown(ImGuiMouseButton_Middle)) {
110 message_ =
"Collision move canceled";
114 auto [x, y] = ImGui::IsMousePosValid()
117 const bool inside = rt.
hovered && x >= 0 && y >= 0 && x < 64 && y < 64;
126 const auto& before =
drag_ ?
drag_->before : collision;
128 const auto delta = ImGui::GetIO().MouseDelta;
129 if (inside && (
drag_ ||
message_.empty() || delta.x != 0 || delta.y != 0 ||
131 message_ = !preview.ok() ? std::string(preview.status().message())
132 :
drag_ ?
"Release to move. Escape / right / middle cancels."
133 : IsMovableNode(value)
134 ?
"Drag a matching node to move. Click elsewhere to stamp."
135 :
"Click to stamp. Escape leaves stamping.";
143 for (
int my = 0; my < 64; my += size == 2 ? 2 : 1) {
144 for (
int mx = 0; mx < 64; mx += size == 2 ? 2 : 1) {
145 if (!MatchesNode(collision, mx, my, size, value))
149 ImVec2((mx + size) * 8, (my + size) * 8));
150 rt.
draw_list->AddRect(p, q, ImGui::GetColorU32(theme.selection_primary),
154 const auto color = ImGui::GetColorU32(preview.ok() ? theme.status_warning
155 : theme.status_error);
156 for (
int dy = 0; inside && dy < size; ++dy) {
157 for (
int dx = 0; dx < size; ++dx) {
161 ImVec2((x + dx + 1) * 8, (y + dy + 1) * 8));
162 rt.
draw_list->AddRectFilled(p, q, (color & 0x00FFFFFF) | 0x55000000);
169 const auto mouse = ImGui::GetMousePos();
170 const float dx = mouse.x -
drag_->mouse_start_x;
171 const float dy = mouse.y -
drag_->mouse_start_y;
172 const float threshold = ImGui::GetIO().MouseDragThreshold;
173 drag_->moved |= dx * dx + dy * dy >= threshold * threshold;
174 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
175 if (inside && preview.ok() && apply &&
drag_->moved &&
176 (x != stamp.source->x || y != stamp.source->y)) {
177 const auto status = apply(room_id,
drag_->before, stamp);
179 message_ = std::string(status.message());
182 }
else if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
185 }
else if (inside && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
186 preview.ok() && apply) {
187 if (MatchesNode(collision, x, y, size, value)) {
189 const auto mouse = ImGui::GetMousePos();
190 drag_ =
Drag{stamp, collision, mouse.x, mouse.y, room_id};
192 const auto status = apply(room_id, collision, stamp);
194 message_ = std::string(status.message());
199 ImGui::BeginTooltip();
200 ImGui::Text(
"Upper layer: %d x %d cells at %d, %d", size, size, x, y);
202 ImGui::Text(
"Source override: %d, %d",
drag_->stamp.source->x,
203 drag_->stamp.source->y);
204 if (
drag_ || IsMovableNode(value))
205 ImGui::TextUnformatted(
206 "Move clears the old override; underlying room collision returns.");
207 ImGui::TextUnformatted(
message_.c_str());