yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_minecart_nodes.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstring>
5#include <optional>
6#include <type_traits>
7
12#include "imgui/imgui_internal.h"
13
14namespace yaze::editor {
15
16static_assert(std::is_trivially_copyable_v<MinecartToolPayload>);
17
19 const gui::CanvasRuntime& rt, zelda3::Room& room, int room_id) {
20 auto* panel = minecart_track_panel_;
22 return false;
23 }
24 const auto* active = ImGui::GetDragDropPayload();
25 bool node_drag = active && active->IsDataType(kMinecartNodeDragPayload) &&
26 active->DataSize == sizeof(MinecartToolPayload);
27 const bool rail_drag =
28 active && active->IsDataType(gui::kDragPayloadRoomObject) &&
29 active->DataSize == sizeof(gui::RoomObjectDragPayload) &&
30 static_cast<const gui::RoomObjectDragPayload*>(active->Data)->object_id ==
31 0x31;
32 if (!node_drag && !rail_drag && !panel->placement_brush()) {
33 return false;
34 }
35 if (!node_drag && !rail_drag && panel->placement_brush()->collision_only) {
36 return false; // Existing node gestures use the collision tool's CAS drag.
37 }
38 if (room_canvas_shortcut_focus_ && ImGui::IsKeyPressed(ImGuiKey_Escape)) {
39 panel->CancelNodePlacement();
40 return true;
41 }
42 if (node_drag &&
43 static_cast<const MinecartToolPayload*>(active->Data)->owner == panel &&
44 (ImGui::IsKeyPressed(ImGuiKey_Escape) ||
45 ImGui::IsMouseDown(ImGuiMouseButton_Right) ||
46 ImGui::IsMouseDown(ImGuiMouseButton_Middle))) {
47 panel->CancelNodePlacement();
48 return true;
49 }
50 std::optional<MinecartToolPayload> brush = panel->placement_brush();
51 const DungeonCanvasTransform transform(rt.canvas_p0, rt.scrolling, rt.scale);
52 const auto origin = transform.room_origin_screen();
53 ImRect target(origin, transform.RoomPixelsToScreen(ImVec2(512, 512)));
54 target.ClipWith(
55 ImRect(rt.canvas_p0,
56 ImVec2(rt.canvas_p0.x + rt.canvas_sz.x * transform.scale(),
57 rt.canvas_p0.y + rt.canvas_sz.y * transform.scale())));
58 if (rt.draw_list) {
59 target.ClipWith(
60 ImRect(rt.draw_list->GetClipRectMin(), rt.draw_list->GetClipRectMax()));
61 }
62 auto actor_at = [&](int x, int y) -> const zelda3::Sprite* {
63 if (!brush || brush->collision_only ||
64 (brush->node.kind != MinecartNodeKind::kSwitch &&
65 brush->node.kind != MinecartNodeKind::kSwitchTrack)) {
66 return nullptr;
67 }
68 const int wanted =
69 brush->node.kind == MinecartNodeKind::kSwitch ? 0xAF : 0xB0;
70 const zelda3::Sprite* found = nullptr;
71 for (const auto& sprite : room.GetSprites()) {
72 if (!sprite.deleted() && sprite.layer() == 0 && sprite.id() == wanted &&
73 sprite.x() * 2 == x && sprite.y() * 2 == y) {
74 if (found)
75 return nullptr; // Ambiguous source must not choose an actor.
76 found = &sprite;
77 }
78 }
79 return found;
80 };
81 if (!rail_drag && brush && !brush->collision_only &&
82 ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
83 !ImGui::IsMouseDown(ImGuiMouseButton_Right) &&
84 !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) {
85 const auto click = ImGui::GetIO().MouseClickedPos[ImGuiMouseButton_Left];
86 auto [source_x, source_y] = ImGui::IsMousePosValid(&click)
87 ? transform.ScreenToRoomTiles(click, 8)
88 : std::pair{-1, -1};
89 source_x &= ~1;
90 source_y &= ~1;
91 if (source_x >= 0 && source_y >= 0 && source_x <= 62 && source_y <= 62) {
92 if (const auto* sprite = actor_at(source_x, source_y);
93 sprite && ImGui::BeginDragDropSource()) {
94 auto moving = *brush;
95 moving.node.id = sprite->subtype();
96 moving.node.source_x = source_x;
97 moving.node.source_y = source_y;
98 moving.node.source_key_drop = static_cast<uint8_t>(sprite->key_drop());
99 moving.node.expected_collision = room.custom_collision();
100 if (sprite->id() == 0xB0) {
101 moving.node.collision =
102 room.custom_collision().tiles[source_y * 64 + source_x];
103 }
104 moving.owner = panel;
105 moving.source_viewer = this;
106 moving.revision = panel->canvas_revision();
107 moving.room_id = room_id;
108 ImGui::SetDragDropPayload(kMinecartNodeDragPayload, &moving,
109 sizeof(moving), ImGuiCond_Once);
110 ImGui::TextUnformatted(sprite->id() == 0xAF ? "Move switch"
111 : "Move switch track");
112 ImGui::EndDragDropSource();
113 // The canvas itself owns the source. Its first drag frame must also
114 // participate in target preview instead of falling through to
115 // placement.
116 active = ImGui::GetDragDropPayload();
117 node_drag = active && active->IsDataType(kMinecartNodeDragPayload) &&
118 active->DataSize == sizeof(MinecartToolPayload);
119 }
120 }
121 }
122 if (node_drag) {
123 MinecartToolPayload incoming;
124 std::memcpy(&incoming, active->Data, sizeof(incoming));
125 brush = incoming;
126 }
127 auto [x, y] = ImGui::IsMousePosValid()
128 ? transform.ScreenToRoomTiles(ImGui::GetMousePos(), 8)
129 : std::pair{-1, -1};
130 if (rail_drag || !brush || !brush->collision_only || brush->size == 2) {
131 x &= ~1;
132 y &= ~1;
133 }
134 // Ordinary item hover is suppressed while another item owns a drag source.
135 const bool hovered =
136 (node_drag || rail_drag)
137 ? ImGui::IsWindowHovered(
138 ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) &&
139 ImGui::IsMouseHoveringRect(target.Min, target.Max)
140 : rt.hovered;
141 const bool inside = hovered && x >= 0 && y >= 0 && x < 64 && y < 64;
142 absl::Status validity =
143 absl::InvalidArgumentError("Keep the node inside this room");
144 std::optional<zelda3::RoomObject> rail;
145 if (rail_drag) {
146 const auto& payload =
147 *static_cast<const gui::RoomObjectDragPayload*>(active->Data);
148 rail.emplace(0x31, x, y, payload.size, 0);
149 auto objects = room.GetTileObjects();
150 objects.push_back(*rail);
151 validity =
152 panel->automatic_rail_collision()
154 room.GetTileObjects(), objects, room.custom_collision())
155 .status()
156 : absl::OkStatus();
157 if (x < 0 || y < 0 || x > 62 || y > 62 || payload.size > 15) {
158 validity =
159 absl::InvalidArgumentError("Place a complete rail inside this room");
160 }
161 } else if (brush && inside) {
162 if (brush->owner != panel || brush->revision != panel->canvas_revision() ||
163 brush->room_id != room_id) {
164 validity = absl::FailedPreconditionError(
165 "Track tool belongs to an earlier room or project; select it again");
166 } else {
167 validity = brush->collision_only
169 room.custom_collision(),
170 {x, y, brush->size, brush->collision})
171 .status()
172 : PreviewMinecartNodePlacement(room, x, y, brush->node,
173 panel->GetTracks())
174 .status();
175 }
176 }
177 bool delivered = false;
178 if (node_drag || rail_drag) {
179 if (ImGui::BeginDragDropTargetCustom(target,
180 ImGui::GetID("##MinecartNodeDrop"))) {
181 if (const auto* payload = ImGui::AcceptDragDropPayload(
182 node_drag ? kMinecartNodeDragPayload
184 ImGuiDragDropFlags_AcceptBeforeDelivery |
185 ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) {
186 delivered = payload->IsDelivery();
187 }
188 ImGui::EndDragDropTarget();
189 }
190 }
191 const bool clicked = !node_drag && !rail_drag && inside && !actor_at(x, y) &&
192 ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
193 !ImGui::IsMouseDown(ImGuiMouseButton_Right) &&
194 !ImGui::IsMouseDown(ImGuiMouseButton_Middle);
195 if (inside && rt.draw_list) {
196 const auto& theme = AgentUI::GetTheme();
197 const auto color = ImGui::GetColorU32(validity.ok() ? theme.status_success
198 : theme.status_error);
199 const int pixels =
200 rail_drag || !brush || !brush->collision_only ? 16 : brush->size * 8;
201 const auto p = transform.RoomPixelsToScreen(ImVec2(x * 8, y * 8));
202 const auto q =
203 transform.RoomPixelsToScreen(ImVec2(x * 8 + pixels, y * 8 + pixels));
204 rt.draw_list->PushClipRect(
205 rt.canvas_p0,
206 ImVec2(rt.canvas_p0.x + rt.canvas_sz.x * transform.scale(),
207 rt.canvas_p0.y + rt.canvas_sz.y * transform.scale()),
208 true);
209 rt.draw_list->AddRectFilled(p, q, (color & 0x00FFFFFF) | 0x44000000);
210 rt.draw_list->AddRect(p, q, color, 0.0f, 2.0f);
211 rt.draw_list->PopClipRect();
212 if (!validity.ok()) {
213 ImGui::SetTooltip("%s", std::string(validity.message()).c_str());
214 }
215 }
216 if ((delivered || clicked) && inside) {
217 auto status = validity;
218 if (status.ok() && rail) {
220 if (!handler.PlaceObjectAt(room_id, *rail, x, y)) {
221 status = handler.mutation_status();
222 }
223 } else if (status.ok() && brush) {
224 if (brush->collision_only && panel->collision_stamp_callback()) {
225 status = panel->collision_stamp_callback()(
226 room_id, room.custom_collision(),
227 {x, y, brush->size, brush->collision});
228 } else if (!brush->collision_only && panel->node_placement_callback()) {
229 status = panel->node_placement_callback()(room_id, x, y, brush->node);
230 } else {
231 status =
232 absl::FailedPreconditionError("Track placement is unavailable");
233 }
234 }
235 panel->ReportToolStatus(status);
236 if (status.ok() && brush && !brush->collision_only &&
237 brush->node.kind == MinecartNodeKind::kCartStart) {
238 panel->CancelNodePlacement();
239 }
240 }
241 return true;
242}
243
244} // namespace yaze::editor
std::pair< int, int > ScreenToRoomTiles(ImVec2 screen, int tile_size) const
bool DrawMinecartNodePlacement(const gui::CanvasRuntime &rt, zelda3::Room &room, int room_id)
DungeonObjectInteraction object_interaction_
MinecartTrackEditorPanel * minecart_track_panel_
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
const absl::Status & mutation_status() const
const CustomCollisionMap & custom_collision() const
Definition room.h:559
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:316
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:445
A class for managing sprites in the overworld and underworld.
Definition sprite.h:39
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
constexpr const char * kMinecartNodeDragPayload
absl::StatusOr< MinecartNodePlacement > PreviewMinecartNodePlacement(const zelda3::Room &room, int tile_x, int tile_y, const MinecartNodeBrush &brush, std::span< const MinecartTrack > tracks, int layer)
absl::StatusOr< zelda3::CustomCollisionMap > PreviewMinecartCollisionStamp(const zelda3::CustomCollisionMap &before, const MinecartCollisionStamp &stamp)
constexpr const char * kDragPayloadRoomObject
Definition drag_drop.h:21
absl::StatusOr< MinecartTrackCollisionEdit > SynchronizeMinecartTrackCollision(std::span< const RoomObject > before_objects, std::span< const RoomObject > after_objects, const CustomCollisionMap &before_collision, const MinecartTrackCollisionOwnership &before_ownership)
std::array< uint8_t, 64 *64 > tiles