yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_object_interaction.cc
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include "imgui/imgui.h"
6
7namespace yaze::editor {
8
10 const ImGuiIO& io = ImGui::GetIO();
11
12 // Check if mouse is over the canvas
13 if (!canvas_->IsMouseHovering()) {
14 return;
15 }
16
17 // Get mouse position relative to canvas
18 ImVec2 mouse_pos = io.MousePos;
19 ImVec2 canvas_pos = canvas_->zero_point();
20 ImVec2 canvas_size = canvas_->canvas_size();
21
22 // Convert to canvas coordinates
23 ImVec2 canvas_mouse_pos =
24 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
25
26 // Handle mouse clicks
27 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
28 if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
29 ImGui::IsKeyDown(ImGuiKey_RightCtrl)) {
30 // Start selection box
31 is_selecting_ = true;
32 select_start_pos_ = canvas_mouse_pos;
33 select_current_pos_ = canvas_mouse_pos;
34 selected_objects_.clear();
35 } else {
36 // Start dragging or place object
37 if (object_loaded_) {
38 // Convert canvas coordinates to room coordinates
39 auto [room_x, room_y] =
40 CanvasToRoomCoordinates(static_cast<int>(canvas_mouse_pos.x),
41 static_cast<int>(canvas_mouse_pos.y));
42 PlaceObjectAtPosition(room_x, room_y);
43 } else {
44 // Start dragging existing objects
45 is_dragging_ = true;
46 drag_start_pos_ = canvas_mouse_pos;
47 drag_current_pos_ = canvas_mouse_pos;
48 }
49 }
50 }
51
52 // Handle mouse drag
53 if (is_selecting_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
54 select_current_pos_ = canvas_mouse_pos;
56 }
57
58 if (is_dragging_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
59 drag_current_pos_ = canvas_mouse_pos;
61 }
62
63 // Handle mouse release
64 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
65 if (is_selecting_) {
66 is_selecting_ = false;
68 }
69 if (is_dragging_) {
70 is_dragging_ = false;
71 // Apply drag transformation to selected objects
72 if (!selected_object_indices_.empty() && rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
73 auto& room = (*rooms_)[current_room_id_];
74 ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x,
76
77 // Convert pixel delta to tile delta
78 int tile_delta_x = static_cast<int>(drag_delta.x) / 8;
79 int tile_delta_y = static_cast<int>(drag_delta.y) / 8;
80
81 // Move all selected objects
82 auto& objects = room.GetTileObjects();
83 for (size_t index : selected_object_indices_) {
84 if (index < objects.size()) {
85 objects[index].x_ += tile_delta_x;
86 objects[index].y_ += tile_delta_y;
87
88 // Clamp to room bounds (64x64 tiles)
89 objects[index].x_ = std::clamp(static_cast<int>(objects[index].x_), 0, 63);
90 objects[index].y_ = std::clamp(static_cast<int>(objects[index].y_), 0, 63);
91 }
92 }
93
94 // Trigger cache invalidation and re-render
97 }
98 }
99 }
100 }
101}
102
104 // Draw object selection rectangle similar to OverworldEditor
106
107 // Handle object selection when rectangle is active
110 }
111}
112
114 if (!canvas_->IsMouseHovering()) return;
115
116 const ImGuiIO& io = ImGui::GetIO();
117 const ImVec2 canvas_pos = canvas_->zero_point();
118 const ImVec2 mouse_pos =
119 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
120
121 static bool dragging = false;
122 static ImVec2 drag_start_pos;
123
124 // Right click to start object selection
125 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !object_loaded_) {
126 drag_start_pos = mouse_pos;
127 object_select_start_ = mouse_pos;
129 object_select_active_ = false;
130 dragging = false;
131 }
132
133 // Right drag to create selection rectangle
134 if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && !object_loaded_) {
135 object_select_end_ = mouse_pos;
136 dragging = true;
137
138 // Draw selection rectangle
139 ImVec2 start =
140 ImVec2(canvas_pos.x + std::min(drag_start_pos.x, mouse_pos.x),
141 canvas_pos.y + std::min(drag_start_pos.y, mouse_pos.y));
142 ImVec2 end = ImVec2(canvas_pos.x + std::max(drag_start_pos.x, mouse_pos.x),
143 canvas_pos.y + std::max(drag_start_pos.y, mouse_pos.y));
144
145 ImDrawList* draw_list = ImGui::GetWindowDrawList();
146 draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
147 draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32));
148 }
149
150 // Complete selection on mouse release
151 if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
152 dragging = false;
155 }
156}
157
159 if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return;
160
161 auto& room = (*rooms_)[current_room_id_];
163
164 // Calculate selection bounds in room coordinates
165 auto [start_room_x, start_room_y] = CanvasToRoomCoordinates(
166 static_cast<int>(std::min(object_select_start_.x, object_select_end_.x)),
167 static_cast<int>(std::min(object_select_start_.y, object_select_end_.y)));
168 auto [end_room_x, end_room_y] = CanvasToRoomCoordinates(
169 static_cast<int>(std::max(object_select_start_.x, object_select_end_.x)),
170 static_cast<int>(std::max(object_select_start_.y, object_select_end_.y)));
171
172 // Find objects within selection rectangle
173 const auto& objects = room.GetTileObjects();
174 for (size_t i = 0; i < objects.size(); ++i) {
175 const auto& object = objects[i];
176 if (object.x_ >= start_room_x && object.x_ <= end_room_x &&
177 object.y_ >= start_room_y && object.y_ <= end_room_y) {
178 selected_object_indices_.push_back(i);
179 }
180 }
181}
182
184 if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return;
185
186 auto& room = (*rooms_)[current_room_id_];
187 const auto& objects = room.GetTileObjects();
188
189 // Draw highlights for all selected objects
190 ImDrawList* draw_list = ImGui::GetWindowDrawList();
191 ImVec2 canvas_pos = canvas_->zero_point();
192
193 for (size_t index : selected_object_indices_) {
194 if (index < objects.size()) {
195 const auto& object = objects[index];
196 auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
197
198 // Calculate object size for highlight
199 int obj_width = 8 + (object.size_ & 0x0F) * 4;
200 int obj_height = 8 + ((object.size_ >> 4) & 0x0F) * 4;
201 obj_width = std::min(obj_width, 64);
202 obj_height = std::min(obj_height, 64);
203
204 // Draw cyan selection highlight
205 ImVec2 obj_start(canvas_pos.x + canvas_x - 2,
206 canvas_pos.y + canvas_y - 2);
207 ImVec2 obj_end(canvas_pos.x + canvas_x + obj_width + 2,
208 canvas_pos.y + canvas_y + obj_height + 2);
209
210 // Animated selection (pulsing effect)
211 float pulse = 0.7f + 0.3f * std::sin(static_cast<float>(ImGui::GetTime()) * 4.0f);
212 draw_list->AddRect(obj_start, obj_end,
213 IM_COL32(0, static_cast<int>(255 * pulse), 255, 255),
214 0.0f, 0, 2.5f);
215
216 // Draw corner handles for selected objects
217 constexpr float handle_size = 4.0f;
218 draw_list->AddRectFilled(
219 ImVec2(obj_start.x - handle_size/2, obj_start.y - handle_size/2),
220 ImVec2(obj_start.x + handle_size/2, obj_start.y + handle_size/2),
221 IM_COL32(0, 255, 255, 255));
222 draw_list->AddRectFilled(
223 ImVec2(obj_end.x - handle_size/2, obj_start.y - handle_size/2),
224 ImVec2(obj_end.x + handle_size/2, obj_start.y + handle_size/2),
225 IM_COL32(0, 255, 255, 255));
226 draw_list->AddRectFilled(
227 ImVec2(obj_start.x - handle_size/2, obj_end.y - handle_size/2),
228 ImVec2(obj_start.x + handle_size/2, obj_end.y + handle_size/2),
229 IM_COL32(0, 255, 255, 255));
230 draw_list->AddRectFilled(
231 ImVec2(obj_end.x - handle_size/2, obj_end.y - handle_size/2),
232 ImVec2(obj_end.x + handle_size/2, obj_end.y + handle_size/2),
233 IM_COL32(0, 255, 255, 255));
234 }
235 }
236}
237
239 if (!object_loaded_ || preview_object_.id_ < 0 || !rooms_) return;
240
241 if (current_room_id_ < 0 || current_room_id_ >= 296) return;
242
243 // Create new object at the specified position
244 auto new_object = preview_object_;
245 new_object.x_ = room_x;
246 new_object.y_ = room_y;
247
248 // Add object to room
249 auto& room = (*rooms_)[current_room_id_];
250 room.AddTileObject(new_object);
251
252 // Notify callback if set
254 object_placed_callback_(new_object);
255 }
256
257 // Trigger cache invalidation
260 }
261}
262
264 if (!is_selecting_) return;
265
266 ImDrawList* draw_list = ImGui::GetWindowDrawList();
267 ImVec2 canvas_pos = canvas_->zero_point();
268
269 // Calculate select box bounds
270 ImVec2 start = ImVec2(
271 canvas_pos.x + std::min(select_start_pos_.x, select_current_pos_.x),
272 canvas_pos.y + std::min(select_start_pos_.y, select_current_pos_.y));
273 ImVec2 end = ImVec2(
274 canvas_pos.x + std::max(select_start_pos_.x, select_current_pos_.x),
275 canvas_pos.y + std::max(select_start_pos_.y, select_current_pos_.y));
276
277 // Draw selection box
278 draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
279 draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32));
280}
281
283 if (!is_dragging_ || selected_object_indices_.empty() || !rooms_) return;
284 if (current_room_id_ < 0 || current_room_id_ >= 296) return;
285
286 // Draw drag preview for selected objects
287 ImDrawList* draw_list = ImGui::GetWindowDrawList();
288 ImVec2 canvas_pos = canvas_->zero_point();
289 ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x,
291
292 auto& room = (*rooms_)[current_room_id_];
293 const auto& objects = room.GetTileObjects();
294
295 // Draw preview of where objects would be moved
296 for (size_t index : selected_object_indices_) {
297 if (index < objects.size()) {
298 const auto& object = objects[index];
299 auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
300
301 // Calculate object size
302 int obj_width = 8 + (object.size_ & 0x0F) * 4;
303 int obj_height = 8 + ((object.size_ >> 4) & 0x0F) * 4;
304 obj_width = std::min(obj_width, 64);
305 obj_height = std::min(obj_height, 64);
306
307 // Draw semi-transparent preview at new position
308 ImVec2 preview_start(canvas_pos.x + canvas_x + drag_delta.x,
309 canvas_pos.y + canvas_y + drag_delta.y);
310 ImVec2 preview_end(preview_start.x + obj_width, preview_start.y + obj_height);
311
312 // Draw ghosted object
313 draw_list->AddRectFilled(preview_start, preview_end, IM_COL32(0, 255, 255, 64));
314 draw_list->AddRect(preview_start, preview_end, IM_COL32(0, 255, 255, 255), 0.0f, 0, 1.5f);
315 }
316 }
317}
318
320 if (!is_selecting_ || !rooms_) return;
321
322 selected_objects_.clear();
323
324 if (current_room_id_ < 0 || current_room_id_ >= 296) return;
325
326 auto& room = (*rooms_)[current_room_id_];
327
328 // Check each object in the room
329 for (const auto& object : room.GetTileObjects()) {
330 if (IsObjectInSelectBox(object)) {
331 selected_objects_.push_back(object.id_);
332 }
333 }
334}
335
337 const zelda3::RoomObject& object) const {
338 if (!is_selecting_) return false;
339
340 // Convert object position to canvas coordinates
341 auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
342
343 // Calculate select box bounds
344 float min_x = std::min(select_start_pos_.x, select_current_pos_.x);
345 float max_x = std::max(select_start_pos_.x, select_current_pos_.x);
346 float min_y = std::min(select_start_pos_.y, select_current_pos_.y);
347 float max_y = std::max(select_start_pos_.y, select_current_pos_.y);
348
349 // Check if object is within select box
350 return (canvas_x >= min_x && canvas_x <= max_x && canvas_y >= min_y &&
351 canvas_y <= max_y);
352}
353
354std::pair<int, int> DungeonObjectInteraction::RoomToCanvasCoordinates(int room_x, int room_y) const {
355 // Dungeon tiles are 8x8 pixels, convert room coordinates (tiles) to pixels
356 return {room_x * 8, room_y * 8};
357}
358
359std::pair<int, int> DungeonObjectInteraction::CanvasToRoomCoordinates(int canvas_x, int canvas_y) const {
360 // Convert canvas pixels back to room coordinates (tiles)
361 return {canvas_x / 8, canvas_y / 8};
362}
363
364bool DungeonObjectInteraction::IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin) const {
365 auto canvas_size = canvas_->canvas_size();
366 auto global_scale = canvas_->global_scale();
367 int scaled_width = static_cast<int>(canvas_size.x * global_scale);
368 int scaled_height = static_cast<int>(canvas_size.y * global_scale);
369
370 return (canvas_x >= -margin && canvas_y >= -margin &&
371 canvas_x <= scaled_width + margin &&
372 canvas_y <= scaled_height + margin);
373}
374
375void DungeonObjectInteraction::SetCurrentRoom(std::array<zelda3::Room, 0x128>* rooms, int room_id) {
376 rooms_ = rooms;
377 current_room_id_ = room_id;
378}
379
381 preview_object_ = object;
382 object_loaded_ = loaded;
383}
384
391
393 if (!canvas_->IsMouseHovering()) return;
394
395 // Show context menu on right-click when not dragging
396 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !is_dragging_) {
397 ImGui::OpenPopup("DungeonObjectContextMenu");
398 }
399
400 if (ImGui::BeginPopup("DungeonObjectContextMenu")) {
401 // Show different options based on current state
402 if (!selected_object_indices_.empty()) {
403 if (ImGui::MenuItem("Delete Selected", "Del")) {
405 }
406 if (ImGui::MenuItem("Copy Selected", "Ctrl+C")) {
408 }
409 ImGui::Separator();
410 }
411
413 if (ImGui::MenuItem("Paste Objects", "Ctrl+V")) {
415 }
416 ImGui::Separator();
417 }
418
419 if (object_loaded_) {
420 ImGui::Text("Placing: Object 0x%02X", preview_object_.id_);
421 if (ImGui::MenuItem("Cancel Placement", "Esc")) {
422 object_loaded_ = false;
423 }
424 } else {
425 ImGui::Text("Right-click + drag to select");
426 ImGui::Text("Left-click + drag to move");
427 }
428
429 ImGui::EndPopup();
430 }
431}
432
434 if (selected_object_indices_.empty() || !rooms_) return;
435 if (current_room_id_ < 0 || current_room_id_ >= 296) return;
436
437 auto& room = (*rooms_)[current_room_id_];
438
439 // Sort indices in descending order to avoid index shifts during deletion
440 std::vector<size_t> sorted_indices = selected_object_indices_;
441 std::sort(sorted_indices.rbegin(), sorted_indices.rend());
442
443 // Delete selected objects using Room's RemoveTileObject method
444 for (size_t index : sorted_indices) {
445 room.RemoveTileObject(index);
446 }
447
448 // Clear selection
450
451 // Trigger cache invalidation and re-render
454 }
455}
456
458 if (selected_object_indices_.empty() || !rooms_) return;
459 if (current_room_id_ < 0 || current_room_id_ >= 296) return;
460
461 auto& room = (*rooms_)[current_room_id_];
462 const auto& objects = room.GetTileObjects();
463
464 // Copy selected objects to clipboard
465 clipboard_.clear();
466 for (size_t index : selected_object_indices_) {
467 if (index < objects.size()) {
468 clipboard_.push_back(objects[index]);
469 }
470 }
471
473}
474
476 if (!has_clipboard_data_ || !rooms_) return;
477 if (current_room_id_ < 0 || current_room_id_ >= 296) return;
478
479 auto& room = (*rooms_)[current_room_id_];
480
481 // Get mouse position for paste location
482 const ImGuiIO& io = ImGui::GetIO();
483 ImVec2 mouse_pos = io.MousePos;
484 ImVec2 canvas_pos = canvas_->zero_point();
485 ImVec2 canvas_mouse_pos =
486 ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
487 auto [paste_x, paste_y] = CanvasToRoomCoordinates(
488 static_cast<int>(canvas_mouse_pos.x),
489 static_cast<int>(canvas_mouse_pos.y));
490
491 // Calculate offset from first object in clipboard
492 if (!clipboard_.empty()) {
493 int offset_x = paste_x - clipboard_[0].x_;
494 int offset_y = paste_y - clipboard_[0].y_;
495
496 // Paste all objects with offset
497 for (const auto& obj : clipboard_) {
498 auto new_obj = obj;
499 new_obj.x_ = obj.x_ + offset_x;
500 new_obj.y_ = obj.y_ + offset_y;
501
502 // Clamp to room bounds
503 new_obj.x_ = std::clamp(static_cast<int>(new_obj.x_), 0, 63);
504 new_obj.y_ = std::clamp(static_cast<int>(new_obj.y_), 0, 63);
505
506 room.AddTileObject(new_obj);
507 }
508
509 // Trigger cache invalidation and re-render
512 }
513 }
514}
515
516} // namespace yaze::editor
std::array< zelda3::Room, 0x128 > * rooms_
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin=32) const
void SetCurrentRoom(std::array< zelda3::Room, 0x128 > *rooms, int room_id)
std::pair< int, int > RoomToCanvasCoordinates(int room_x, int room_y) const
bool IsObjectInSelectBox(const zelda3::RoomObject &object) const
std::vector< zelda3::RoomObject > clipboard_
std::function< void(const zelda3::RoomObject &)> object_placed_callback_
std::pair< int, int > CanvasToRoomCoordinates(int canvas_x, int canvas_y) const
void SetPreviewObject(const zelda3::RoomObject &object, bool loaded)
auto global_scale() const
Definition canvas.h:345
auto canvas_size() const
Definition canvas.h:314
auto zero_point() const
Definition canvas.h:310
bool IsMouseHovering() const
Definition canvas.h:301
Editors are the view controllers for the application.