yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile_object_handler.cc
Go to the documentation of this file.
2#include <algorithm>
3#include <cmath>
4#include <unordered_set>
5#include "absl/strings/str_format.h"
6#include "imgui/imgui.h"
10#include "util/log.h"
16
18
19namespace yaze::editor {
20
21namespace {
22constexpr size_t kMaxLayerBatchMutation = 128;
23} // namespace
24
26 if (!ctx_ || !ctx_->rooms) return nullptr;
27 if (room_id < 0 ||
28 room_id >= static_cast<int>(ctx_->rooms->size())) {
29 return nullptr;
30 }
31 return &(*ctx_->rooms)[room_id];
32}
33
39
40// ========================================================================
41// BaseEntityHandler implementation
42// ========================================================================
43
48
53
54bool TileObjectHandler::HandleClick(int canvas_x, int canvas_y) {
55 if (!HasValidContext()) return false;
56
58 auto [room_x, room_y] = CanvasToRoom(canvas_x, canvas_y);
59 if (IsWithinBounds(canvas_x, canvas_y)) {
61 }
62 return true; // Placement click is handled even if outside bounds.
63 }
64
65 // Handle selection (click)
66 if (!ctx_ || !ctx_->selection) return false;
67
68 auto hovered = GetEntityAtPosition(canvas_x, canvas_y);
69 if (!hovered.has_value()) return false;
70
71 const ImGuiIO& io = ImGui::GetIO();
72
73 // Alt-click is reserved for caller-specific behaviors (inspector/etc). Treat
74 // it as a handled click over an object without changing selection.
75 if (io.KeyAlt) return true;
76
78 if (io.KeyShift) {
80 } else if (io.KeyCtrl || io.KeySuper) {
82 }
83
84 ctx_->selection->SelectObject(*hovered, mode);
85 return true;
86}
87
88void TileObjectHandler::InitDrag(const ImVec2& start_pos) {
89 is_dragging_ = true;
92 drag_last_dx_ = 0;
93 drag_last_dy_ = 0;
96}
97
98void TileObjectHandler::BeginMarqueeSelection(const ImVec2& start_pos) {
99 if (!ctx_ || !ctx_->selection) return;
100 ctx_->selection->BeginRectangleSelection(static_cast<int>(start_pos.x),
101 static_cast<int>(start_pos.y));
102}
103
104void TileObjectHandler::HandleMarqueeSelection(const ImVec2& mouse_pos,
105 bool mouse_left_down,
106 bool mouse_left_released,
107 bool shift_down,
108 bool toggle_down,
109 bool alt_down,
110 bool draw_box) {
111 if (!ctx_ || !ctx_->selection) return;
113
114 // Update + draw while the drag is in progress.
115 if (mouse_left_down) {
116 ctx_->selection->UpdateRectangleSelection(static_cast<int>(mouse_pos.x),
117 static_cast<int>(mouse_pos.y));
118 if (draw_box) {
119 if (ctx_->canvas) {
121 }
122 }
123 }
124
125 // Finalize selection on release.
126 if (mouse_left_released) {
127 ctx_->selection->UpdateRectangleSelection(static_cast<int>(mouse_pos.x),
128 static_cast<int>(mouse_pos.y));
129
130 auto* room = GetRoom(ctx_->current_room_id);
131 if (!room) {
133 return;
134 }
135
136 constexpr int kMinRectPixels = 6;
137 if (alt_down || !ctx_->selection->IsRectangleLargeEnough(kMinRectPixels)) {
139 return;
140 }
141
143 if (shift_down) {
145 } else if (toggle_down) {
147 }
148
149 ctx_->selection->EndRectangleSelection(room->GetTileObjects(), mode);
150 }
151}
152
153void TileObjectHandler::HandleDrag(ImVec2 current_pos, ImVec2 delta) {
154 (void)delta;
155 if (!is_dragging_ || !ctx_ || !ctx_->selection) return;
156
158 const bool alt_down = ImGui::GetIO().KeyAlt;
159
160 // Calculate total drag delta from start (snapped)
161 ImVec2 drag_delta = ImVec2(drag_current_.x - drag_start_.x,
163 drag_delta = ApplyDragModifiers(drag_delta);
164
165 const int tile_dx = static_cast<int>(drag_delta.x) / 8;
166 const int tile_dy = static_cast<int>(drag_delta.y) / 8;
167
168 // Option-drag (Alt) duplicates once
169 if (alt_down && !drag_has_duplicated_) {
173 }
174
175 // Duplicate objects at current position (delta 0 initially)
176 auto new_indices = DuplicateObjects(
178 /*delta_x=*/0, /*delta_y=*/0,
179 /*notify_mutation=*/false);
180
181 // Update selection to the new clones
183 for (size_t idx : new_indices) {
185 }
187 }
188
189 // Calculate incremental move
190 const int inc_dx = tile_dx - drag_last_dx_;
191 const int inc_dy = tile_dy - drag_last_dy_;
192
193 if (inc_dx != 0 || inc_dy != 0) {
197 }
198
200 inc_dx, inc_dy,
201 /*notify_mutation=*/false);
202
203 drag_last_dx_ = tile_dx;
204 drag_last_dy_ = tile_dy;
205 }
206}
207
209 if (is_dragging_) {
210 const bool had_mutation = drag_mutation_started_;
211 is_dragging_ = false;
213 drag_has_duplicated_ = false;
214 // Drag operations mutate incrementally while the mouse is held down. The
215 // editor's undo capture wants to finalize after the drag ends (once the
216 // interaction mode has returned to Select), so emit one more invalidation
217 // on release if anything changed.
218 if (had_mutation && ctx_) {
220 }
221 }
222}
223
224ImVec2 TileObjectHandler::ApplyDragModifiers(const ImVec2& delta) const {
225 const ImGuiIO& io = ImGui::GetIO();
226 if (!io.KeyShift) return delta;
227 if (std::abs(delta.x) >= std::abs(delta.y)) return ImVec2(delta.x, 0.0f);
228 return ImVec2(0.0f, delta.y);
229}
230
231
233 if (!HasValidContext() || !ctx_ || !ctx_->selection || delta == 0.0f) return false;
234
235 auto indices = ctx_->selection->GetSelectedIndices();
236 if (indices.empty()) return false;
237
238 int resize_delta = (delta > 0.0f) ? 1 : -1;
239 ResizeObjects(ctx_->current_room_id, indices, resize_delta);
240 return true;
241}
242
245
246 const ImGuiIO& io = ImGui::GetIO();
247 ImVec2 canvas_pos = GetCanvasZeroPoint();
248 ImVec2 mouse_pos = io.MousePos;
249 float scale = GetCanvasScale();
250
251 ImVec2 canvas_mouse_pos = ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
252 auto [room_x, room_y] = CanvasToRoom(static_cast<int>(canvas_mouse_pos.x), static_cast<int>(canvas_mouse_pos.y));
253
254 if (!IsWithinBounds(static_cast<int>(canvas_mouse_pos.x), static_cast<int>(canvas_mouse_pos.y))) return;
255
256 auto [snap_canvas_x, snap_canvas_y] = RoomToCanvas(room_x, room_y);
257 auto [obj_width, obj_height] = CalculateObjectBounds(preview_object_);
258
259 ImDrawList* draw_list = ImGui::GetWindowDrawList();
260 ImVec2 preview_start(canvas_pos.x + snap_canvas_x * scale, canvas_pos.y + snap_canvas_y * scale);
261 ImVec2 preview_end(preview_start.x + obj_width * scale, preview_start.y + obj_height * scale);
262
264 size_t current_obj_count = room ? room->GetTileObjects().size() : 0;
265 const bool at_obj_limit = (current_obj_count >= zelda3::kMaxTileObjects);
266 const bool near_obj_limit = (current_obj_count >= zelda3::kMaxTileObjects * 9 / 10);
267
268 const auto& theme = AgentUI::GetTheme();
269 ImVec4 outline_color = theme.dungeon_selection_primary;
270 if (at_obj_limit) {
271 outline_color = theme.status_error;
272 } else if (near_obj_limit) {
273 outline_color = theme.status_warning;
274 }
275 bool drew_bitmap = false;
276
278 auto& bitmap = ghost_preview_buffer_->bitmap();
279 if (bitmap.texture()) {
280 ImVec2 bitmap_end(preview_start.x + bitmap.width() * scale, preview_start.y + bitmap.height() * scale);
281 ImVec4 tint = at_obj_limit ? theme.status_error : theme.text_primary;
282 tint.w = 0.70f;
283 draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(), preview_start,
284 bitmap_end, ImVec2(0, 0), ImVec2(1, 1),
285 ImGui::GetColorU32(tint));
286 draw_list->AddRect(preview_start, bitmap_end, ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
287 drew_bitmap = true;
288 }
289 }
290
291 if (!drew_bitmap) {
292 draw_list->AddRectFilled(preview_start, preview_end,
293 ImGui::GetColorU32(ImVec4(outline_color.x, outline_color.y, outline_color.z, 0.25f)));
294 draw_list->AddRect(preview_start, preview_end, ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
295 }
296
297 // ID label
298 std::string id_text = absl::StrFormat("0x%02X", preview_object_.id_);
299 draw_list->AddText(ImVec2(preview_start.x + 2, preview_start.y + 1), ImGui::GetColorU32(theme.text_primary), id_text.c_str());
300
301 // Capacity tooltip while hovering — proactive warning before user clicks.
302 if ((at_obj_limit || near_obj_limit) &&
303 ImGui::IsMouseHoveringRect(preview_start, preview_end)) {
304 ImGui::SetTooltip("Objects: %zu/%zu%s", current_obj_count, zelda3::kMaxTileObjects,
305 at_obj_limit ? "\nPlacement blocked" : "\nNear limit");
306 }
307
308}
309
311 if (!HasValidContext() || !ctx_->selection) return;
312
313 auto* room = GetCurrentRoom();
314 if (!room) return;
315
316 // Use ObjectSelection's rendering (handles pulsing border, corner handles)
318 ctx_->canvas, room->GetTileObjects(), [](const zelda3::RoomObject& obj) {
319 auto result = zelda3::DimensionService::Get().GetDimensions(obj);
320 return std::make_tuple(result.offset_x_tiles * 8,
321 result.offset_y_tiles * 8,
322 result.width_pixels(), result.height_pixels());
323 });
324}
325
326std::optional<size_t> TileObjectHandler::GetEntityAtPosition(int canvas_x, int canvas_y) const {
327 auto* room = const_cast<TileObjectHandler*>(this)->GetRoom(ctx_->current_room_id);
328 if (!room) return std::nullopt;
329
330 const auto& objects = room->GetTileObjects();
331 for (size_t i = objects.size(); i > 0; --i) {
332 size_t index = i - 1;
333 const auto& object = objects[index];
334
335 // Respect layer filter if available in context
337 continue;
338 }
339
340 auto [obj_tile_x, obj_tile_y, width_tiles, height_tiles] = zelda3::DimensionService::Get().GetHitTestBounds(object);
341
342 int obj_px = obj_tile_x * 8;
343 int obj_py = obj_tile_y * 8;
344 int w_px = width_tiles * 8;
345 int h_px = height_tiles * 8;
346
347 if (canvas_x >= obj_px && canvas_x < obj_px + w_px &&
348 canvas_y >= obj_py && canvas_y < obj_py + h_px) {
349 return index;
350 }
351 }
352 return std::nullopt;
353}
354
355// ========================================================================
356// Mutation Logic
357// ========================================================================
358
360 const std::vector<size_t>& indices,
361 int delta_x, int delta_y,
362 bool notify_mutation) {
363 auto* room = GetRoom(room_id);
364 if (!room || indices.empty()) return;
365 if (notify_mutation && ctx_) ctx_->NotifyMutation(MutationDomain::kTileObjects);
366
367 auto& objects = room->GetTileObjects();
368 for (size_t index : indices) {
369 if (index < objects.size()) {
370 objects[index].x_ = std::clamp(static_cast<int>(objects[index].x_ + delta_x), 0, 63);
371 objects[index].y_ = std::clamp(static_cast<int>(objects[index].y_ + delta_y), 0, 63);
372 }
373 }
374
375 NotifyChange(room);
376}
377
378void TileObjectHandler::UpdateObjectsId(int room_id, const std::vector<size_t>& indices, int16_t new_id) {
379 auto* room = GetRoom(room_id);
380 if (!room || indices.empty()) return;
382
383 auto& objects = room->GetTileObjects();
384 for (size_t index : indices) {
385 if (index < objects.size()) {
386 // Use the setter so derived flags + tile caches stay coherent.
387 objects[index].set_id(new_id);
388 }
389 }
390 NotifyChange(room);
391}
392
393void TileObjectHandler::UpdateObjectsSize(int room_id, const std::vector<size_t>& indices, uint8_t new_size) {
394 auto* room = GetRoom(room_id);
395 if (!room || indices.empty()) return;
397
398 auto& objects = room->GetTileObjects();
399 for (size_t index : indices) {
400 if (index < objects.size()) {
401 objects[index].size_ = new_size;
402 objects[index].tiles_loaded_ = false;
403 }
404 }
405 NotifyChange(room);
406}
407
408void TileObjectHandler::UpdateObjectsLayer(int room_id, const std::vector<size_t>& indices, int new_layer) {
409 auto* room = GetRoom(room_id);
410 if (!room || indices.empty()) return;
411 if (new_layer < 0 || new_layer > 2) {
412 LOG_WARN("TileObjectHandler",
413 "Rejected layer update with invalid target layer: %d", new_layer);
414 return;
415 }
416 auto& objects = room->GetTileObjects();
417 std::vector<size_t> deduped_indices;
418 deduped_indices.reserve(indices.size());
419 std::unordered_set<size_t> seen_indices;
420 for (size_t index : indices) {
421 if (index >= objects.size()) {
422 continue;
423 }
424 if (seen_indices.insert(index).second) {
425 deduped_indices.push_back(index);
426 }
427 }
428 if (deduped_indices.empty()) {
429 return;
430 }
431
432 if (deduped_indices.size() > kMaxLayerBatchMutation) {
433 LOG_WARN("TileObjectHandler",
434 "Rejected layer batch mutation of %zu objects (max %zu)",
435 deduped_indices.size(), kMaxLayerBatchMutation);
436 return;
437 }
438
439 int current_bg3_count = 0;
440 for (const auto& object : objects) {
441 if (object.GetLayerValue() == 2) {
442 ++current_bg3_count;
443 }
444 }
445
446 int moving_to_bg3 = 0;
447 int moving_from_bg3 = 0;
448 auto target_layer = static_cast<zelda3::RoomObject::LayerType>(new_layer);
449 for (size_t index : deduped_indices) {
450 const auto& object = objects[index];
451 const auto semantics = zelda3::GetObjectLayerSemantics(object);
452 if (semantics.draws_to_both_bgs &&
453 target_layer != zelda3::RoomObject::LayerType::BG1) {
454 continue;
455 }
456 if (object.layer_ == target_layer) {
457 continue;
458 }
459 if (object.GetLayerValue() == 2) {
460 ++moving_from_bg3;
461 }
462 if (new_layer == 2) {
463 ++moving_to_bg3;
464 }
465 }
466 const int projected_bg3_count =
467 current_bg3_count - moving_from_bg3 + moving_to_bg3;
468 if (projected_bg3_count > zelda3::kMaxBg3Objects) {
469 LOG_WARN("TileObjectHandler",
470 "Rejected layer mutation: projected BG3 count %d exceeds max %d",
471 projected_bg3_count, zelda3::kMaxBg3Objects);
472 return;
473 }
474
475 bool changed = false;
476 for (size_t index : deduped_indices) {
477 auto& object = objects[index];
478 const auto semantics = zelda3::GetObjectLayerSemantics(object);
479 if (semantics.draws_to_both_bgs &&
480 target_layer != zelda3::RoomObject::LayerType::BG1) {
481 continue;
482 }
483 if (object.layer_ == target_layer) {
484 continue;
485 }
486 object.layer_ = target_layer;
487 changed = true;
488 }
489 if (!changed) {
490 return;
491 }
492
494 NotifyChange(room);
495}
496
498 int room_id, const std::vector<size_t>& indices, int delta_x, int delta_y,
499 bool notify_mutation) {
500 auto* room = GetRoom(room_id);
501 if (!room || indices.empty()) return {};
502 if (notify_mutation && ctx_) ctx_->NotifyMutation(MutationDomain::kTileObjects);
503
504 auto& objects = room->GetTileObjects();
505 std::vector<size_t> new_indices;
506
507 const size_t base_index = objects.size();
508 for (size_t index : indices) {
509 if (index < objects.size()) {
510 auto clone = objects[index];
511 clone.x_ = std::clamp(static_cast<int>(clone.x_ + delta_x), 0, 63);
512 clone.y_ = std::clamp(static_cast<int>(clone.y_ + delta_y), 0, 63);
513 objects.push_back(clone);
514 new_indices.push_back(base_index + (new_indices.size()));
515 }
516 }
517
518 NotifyChange(room);
519 return new_indices;
520}
521
522void TileObjectHandler::DeleteObjects(int room_id, std::vector<size_t> indices) {
523 auto* room = GetRoom(room_id);
524 if (!room || indices.empty()) return;
526
527 std::sort(indices.rbegin(), indices.rend());
528 for (size_t index : indices) {
529 room->RemoveTileObject(index);
530 }
531
532 NotifyChange(room);
533}
534
536 auto* room = GetRoom(room_id);
537 if (!room) return;
539 room->ClearTileObjects();
540 NotifyChange(room);
541}
542
543void TileObjectHandler::SendToFront(int room_id, const std::vector<size_t>& indices) {
544 auto* room = GetRoom(room_id);
545 if (!room || indices.empty()) return;
547
548 auto& objects = room->GetTileObjects();
549 std::vector<zelda3::RoomObject> selected, other;
550
551 for (size_t i = 0; i < objects.size(); ++i) {
552 if (std::find(indices.begin(), indices.end(), i) != indices.end())
553 selected.push_back(objects[i]);
554 else
555 other.push_back(objects[i]);
556 }
557
558 objects = std::move(other);
559 objects.insert(objects.end(), selected.begin(), selected.end());
560 NotifyChange(room);
561}
562
563void TileObjectHandler::SendToBack(int room_id, const std::vector<size_t>& indices) {
564 auto* room = GetRoom(room_id);
565 if (!room || indices.empty()) return;
567
568 auto& objects = room->GetTileObjects();
569 std::vector<zelda3::RoomObject> selected, other;
570
571 for (size_t i = 0; i < objects.size(); ++i) {
572 if (std::find(indices.begin(), indices.end(), i) != indices.end())
573 selected.push_back(objects[i]);
574 else
575 other.push_back(objects[i]);
576 }
577
578 objects = std::move(selected);
579 objects.insert(objects.end(), other.begin(), other.end());
580 NotifyChange(room);
581}
582
583void TileObjectHandler::MoveForward(int room_id, const std::vector<size_t>& indices) {
584 auto* room = GetRoom(room_id);
585 if (!room || indices.empty()) return;
587
588 auto& objects = room->GetTileObjects();
589 auto sorted_indices = indices;
590 std::sort(sorted_indices.rbegin(), sorted_indices.rend());
591
592 for (size_t idx : sorted_indices) {
593 if (idx < objects.size() - 1) {
594 std::swap(objects[idx], objects[idx + 1]);
595 }
596 }
597 NotifyChange(room);
598}
599
600void TileObjectHandler::MoveBackward(int room_id, const std::vector<size_t>& indices) {
601 auto* room = GetRoom(room_id);
602 if (!room || indices.empty()) return;
604
605 auto& objects = room->GetTileObjects();
606 auto sorted_indices = indices;
607 std::sort(sorted_indices.begin(), sorted_indices.end());
608
609 for (size_t idx : sorted_indices) {
610 if (idx > 0) {
611 std::swap(objects[idx], objects[idx - 1]);
612 }
613 }
614 NotifyChange(room);
615}
616
617void TileObjectHandler::ResizeObjects(int room_id, const std::vector<size_t>& indices, int delta) {
618 auto* room = GetRoom(room_id);
619 if (!room || indices.empty()) return;
621 auto& objects = room->GetTileObjects();
622 for (size_t index : indices) {
623 if (index < objects.size()) {
624 int new_size = std::clamp(static_cast<int>(objects[index].size_) + delta, 0, 15);
625 objects[index].size_ = static_cast<uint8_t>(new_size);
626 objects[index].tiles_loaded_ = false;
627 }
628 }
629 NotifyChange(room);
630}
631
632bool TileObjectHandler::PlaceObjectAt(int room_id, const zelda3::RoomObject& object, int x, int y) {
633 auto* room = GetRoom(room_id);
634 if (!room) {
636 return false;
637 }
638
639 // Hard-stop: enforce ROM object limit before committing placement.
640 if (room->GetTileObjects().size() >= zelda3::kMaxTileObjects) {
642 return false;
643 }
644
647 auto new_obj = object;
648 new_obj.x_ = std::clamp(x, 0, 63);
649 new_obj.y_ = std::clamp(y, 0, 63);
650 room->AddTileObject(new_obj);
651 NotifyChange(room);
653 return true;
654}
655
656
663
665 if (!ctx_ || !ctx_->rom || !ctx_->rom->is_loaded()) return;
666
667 auto* room = GetRoom(ctx_->current_room_id);
668 if (!room || !room->IsLoaded()) return;
669
670 auto [width, height] = CalculateObjectBounds(preview_object_);
671 width = std::max(width, 16);
672 height = std::max(height, 16);
673
674 ghost_preview_buffer_ = std::make_unique<gfx::BackgroundBuffer>(width, height);
675 const uint8_t* gfx_data = room->get_gfx_buffer().data();
676
677 zelda3::ObjectDrawer drawer(ctx_->rom, ctx_->current_room_id, gfx_data);
678 drawer.InitializeDrawRoutines();
679
681 if (!status.ok()) {
682 ghost_preview_buffer_.reset();
683 return;
684 }
685
686 auto& bitmap = ghost_preview_buffer_->bitmap();
687 if (bitmap.size() > 0) {
689 &bitmap);
691 }
692}
693
695 const zelda3::RoomObject& object) {
697}
698
699// ========================================================================
700// Clipboard Operations
701// ========================================================================
702
704 int room_id, const std::vector<size_t>& indices) {
705 auto* room = GetRoom(room_id);
706 if (!room || indices.empty()) return;
707
708 clipboard_.clear();
709 const auto& objects = room->GetTileObjects();
710
711 for (size_t idx : indices) {
712 if (idx < objects.size()) {
713 clipboard_.push_back(objects[idx]);
714 }
715 }
716}
717
719 int room_id, int offset_x, int offset_y) {
720 auto* room = GetRoom(room_id);
721 if (!room || clipboard_.empty()) return {};
723
724 std::vector<size_t> new_indices;
725 size_t base_index = room->GetTileObjects().size();
726
727 for (auto obj : clipboard_) {
728 obj.x_ = std::clamp(obj.x_ + offset_x, 0, 63);
729 obj.y_ = std::clamp(obj.y_ + offset_y, 0, 63);
730 obj.tiles_loaded_ = false;
731 room->AddTileObject(obj);
732 new_indices.push_back(base_index++);
733 }
734
735 NotifyChange(room);
736 return new_indices;
737}
738
740 int room_id, int target_x, int target_y) {
741 if (clipboard_.empty()) return {};
742
743 int offset_x = target_x - clipboard_[0].x_;
744 int offset_y = target_y - clipboard_[0].y_;
745
746 return PasteFromClipboard(room_id, offset_x, offset_y);
747}
748
749} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:132
std::pair< int, int > CanvasToRoom(int canvas_x, int canvas_y) const
Convert canvas pixel coordinates to room tile coordinates.
bool IsWithinBounds(int canvas_x, int canvas_y) const
Check if coordinates are within room bounds.
float GetCanvasScale() const
Get canvas global scale.
zelda3::Room * GetCurrentRoom() const
Get current room (convenience method)
bool HasValidContext() const
Check if context is valid.
std::pair< int, int > RoomToCanvas(int room_x, int room_y) const
Convert room tile coordinates to canvas pixel coordinates.
ImVec2 GetCanvasZeroPoint() const
Get canvas zero point (for screen coordinate conversion)
bool IsRectangleSelectionActive() const
Check if a rectangle selection is in progress.
void UpdateRectangleSelection(int canvas_x, int canvas_y)
Update rectangle selection endpoint.
void DrawSelectionHighlights(gui::Canvas *canvas, const std::vector< zelda3::RoomObject > &objects, std::function< std::tuple< int, int, int, int >(const zelda3::RoomObject &)> bounds_calculator)
Draw selection highlights for all selected objects.
std::vector< size_t > GetSelectedIndices() const
Get all selected object indices.
bool PassesLayerFilterForObject(const zelda3::RoomObject &object) const
Check if an object passes the current layer filter.
bool IsRectangleLargeEnough(int min_pixels) const
Check if rectangle selection exceeds a minimum pixel size.
void SelectObject(size_t index, SelectionMode mode=SelectionMode::Single)
Select a single object by index.
void ClearSelection()
Clear all selections.
void EndRectangleSelection(const std::vector< zelda3::RoomObject > &objects, SelectionMode mode=SelectionMode::Single)
Complete rectangle selection operation.
void BeginRectangleSelection(int canvas_x, int canvas_y)
Begin a rectangle selection operation.
void CancelRectangleSelection()
Cancel rectangle selection without modifying selection.
void DrawRectangleSelectionBox(gui::Canvas *canvas)
Draw the active rectangle selection box.
Handles functional mutations and queries for tile objects.
void DrawGhostPreview() override
Draw ghost preview during placement.
std::vector< zelda3::RoomObject > clipboard_
void HandleMarqueeSelection(const ImVec2 &mouse_pos, bool mouse_left_down, bool mouse_left_released, bool shift_down, bool toggle_down, bool alt_down, bool draw_box=true)
void BeginPlacement() override
Begin placement mode.
bool HandleMouseWheel(float delta) override
std::unique_ptr< gfx::BackgroundBuffer > ghost_preview_buffer_
void UpdateObjectsSize(int room_id, const std::vector< size_t > &indices, uint8_t new_size)
void SendToFront(int room_id, const std::vector< size_t > &indices)
Reorder objects.
std::vector< size_t > DuplicateObjects(int room_id, const std::vector< size_t > &indices, int delta_x, int delta_y, bool notify_mutation=true)
Clone a set of objects and move them by a tile delta.
void DeleteAllObjects(int room_id)
Delete all objects in a room.
void MoveBackward(int room_id, const std::vector< size_t > &indices)
ImVec2 ApplyDragModifiers(const ImVec2 &delta) const
void HandleRelease() override
Handle mouse release.
void CopyObjectsToClipboard(int room_id, const std::vector< size_t > &indices)
Copy objects to internal clipboard.
void HandleDrag(ImVec2 current_pos, ImVec2 delta) override
Handle mouse drag.
void UpdateObjectsLayer(int room_id, const std::vector< size_t > &indices, int new_layer)
std::vector< size_t > PasteFromClipboardAt(int room_id, int target_x, int target_y)
Paste objects from clipboard at target location. Use first clipboard item as origin.
void DrawSelectionHighlight() override
Draw selection highlight for selected entities.
bool PlaceObjectAt(int room_id, const zelda3::RoomObject &object, int x, int y)
Place a new object. Returns false if blocked by ROM limits.
void SendToBack(int room_id, const std::vector< size_t > &indices)
std::vector< size_t > PasteFromClipboard(int room_id, int offset_x, int offset_y)
Paste objects from clipboard with offset.
bool HandleClick(int canvas_x, int canvas_y) override
Handle mouse click at canvas position.
void SetPreviewObject(const zelda3::RoomObject &object)
Set object for placement.
void BeginMarqueeSelection(const ImVec2 &start_pos)
void MoveForward(int room_id, const std::vector< size_t > &indices)
void MoveObjects(int room_id, const std::vector< size_t > &indices, int delta_x, int delta_y, bool notify_mutation=true)
Move a set of objects by a tile delta.
void CancelPlacement() override
Cancel current placement.
PlacementBlockReason placement_block_reason_
void DeleteObjects(int room_id, std::vector< size_t > indices)
Delete objects by indices.
void UpdateObjectsId(int room_id, const std::vector< size_t > &indices, int16_t new_id)
zelda3::Room * GetRoom(int room_id)
std::pair< int, int > CalculateObjectBounds(const zelda3::RoomObject &object)
void NotifyChange(zelda3::Room *room)
void ResizeObjects(int room_id, const std::vector< size_t > &indices, int delta)
Resize objects by a delta.
std::optional< size_t > GetEntityAtPosition(int canvas_x, int canvas_y) const override
Get entity at canvas position.
void InitDrag(const ImVec2 &start_pos)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:116
static Arena & Get()
Definition arena.cc:21
static DimensionService & Get()
std::tuple< int, int, int, int > GetHitTestBounds(const RoomObject &obj) const
std::pair< int, int > GetPixelDimensions(const RoomObject &obj) const
Draws dungeon objects to background buffers using game patterns.
void InitializeDrawRoutines()
Initialize draw routine registry Must be called before drawing objects.
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw a room object to background buffers.
void MarkObjectsDirty()
Definition room.h:334
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:314
#define LOG_WARN(category, format,...)
Definition log.h:107
const AgentUITheme & GetTheme()
ImVec2 SnapToTileGrid(const ImVec2 &point)
Snap a point to the 8px tile grid.
Editors are the view controllers for the application.
ObjectLayerSemantics GetObjectLayerSemantics(const RoomObject &object)
constexpr size_t kMaxBg3Objects
constexpr size_t kMaxTileObjects
void NotifyInvalidateCache(MutationDomain domain=MutationDomain::kUnknown) const
Notify that cache invalidation is needed.
std::array< zelda3::Room, dungeon_coords::kRoomCount > * rooms
void NotifyMutation(MutationDomain domain=MutationDomain::kUnknown) const
Notify that a mutation is about to happen.