6#include <unordered_set>
7#include "absl/strings/str_format.h"
13#include "imgui/imgui.h"
35 SDL_Surface* surface = source->surface();
37 if (!palette || palette->ncolors <= 0) {
41 std::vector<SDL_Color> colors(256, {0, 0, 0, 0});
42 const int color_count = std::min(palette->ncolors, 256);
43 std::copy_n(palette->colors, color_count, colors.begin());
44 colors[255] = {0, 0, 0, 0};
47 SDL_SetColorKey(ghost.
surface(), SDL_TRUE, 255);
48 SDL_SetSurfaceBlendMode(ghost.
surface(), SDL_BLENDMODE_BLEND);
57 bool selected =
false;
61 return std::clamp(
static_cast<int>(
object.GetLayerValue()), 0, 2);
65 size_t object_count) {
66 std::unordered_set<size_t> index_set;
67 for (
size_t index : indices) {
68 if (index < object_count) {
69 index_set.insert(index);
76 const std::vector<zelda3::RoomObject>& objects,
77 const std::unordered_set<size_t>& selected_indices) {
78 std::array<std::vector<LayerOrderEntry>, 3> buckets;
79 for (
size_t i = 0; i < objects.size(); ++i) {
88 const std::vector<size_t>& selected_indices_after_reorder) {
93 for (
size_t index : selected_indices_after_reorder) {
99 std::array<std::vector<LayerOrderEntry>, 3>& buckets,
101 std::vector<zelda3::RoomObject> reordered;
102 std::vector<size_t> selected_indices_after_reorder;
103 reordered.reserve(objects.size());
104 for (
auto& bucket : buckets) {
105 for (
auto& entry : bucket) {
106 if (entry.selected) {
107 selected_indices_after_reorder.push_back(reordered.size());
109 reordered.push_back(std::move(entry.object));
112 objects = std::move(reordered);
126 auto [x, y, width, height] =
128 return GuideRect{x, y, x + width, y + height, x + width / 2, y + height / 2};
132 constexpr int kDuplicateTolerancePx = 2;
133 if (std::any_of(guides.begin(), guides.end(), [&](
int guide) {
134 return std::abs(guide - value) <= kDuplicateTolerancePx;
138 guides.push_back(value);
143 ImU32 color,
float thickness) {
144 constexpr float kDash = 6.0f;
145 constexpr float kGap = 4.0f;
146 const bool vertical = std::abs(start.x - end.x) < 0.5f;
148 vertical ? std::abs(end.y - start.y) : std::abs(end.x - start.x);
149 for (
float offset = 0.0f; offset < total; offset += kDash + kGap) {
150 const float segment_end = std::min(offset + kDash, total);
152 const float y0 = start.y + offset;
153 const float y1 = start.y + segment_end;
154 draw_list->AddLine(ImVec2(start.x, y0), ImVec2(start.x, y1), color,
157 const float x0 = start.x + offset;
158 const float x1 = start.x + segment_end;
159 draw_list->AddLine(ImVec2(x0, start.y), ImVec2(x1, start.y), color,
170 return GhostCapacityState::kNormal;
173 const size_t current_obj_count = room ? room->
GetTileObjects().size() : 0;
180 if (room_id < 0 || room_id >=
static_cast<int>(
ctx_->
rooms->
size())) {
211 auto [room_x, room_y] =
CanvasToRoom(canvas_x, canvas_y);
223 if (!hovered.has_value())
226 const ImGuiIO& io = ImGui::GetIO();
236 }
else if (io.KeyCtrl || io.KeySuper) {
258 static_cast<int>(start_pos.y));
262 const ImVec2& mouse_pos,
bool mouse_left_down,
bool mouse_left_released,
263 bool shift_down,
bool toggle_down,
bool alt_down,
bool draw_box) {
272 if (mouse_left_down) {
274 static_cast<int>(mouse_pos.y));
283 if (mouse_left_released) {
285 static_cast<int>(mouse_pos.y));
293 constexpr int kMinRectPixels = 6;
316 const int tile_dx =
static_cast<int>(drag_delta.x) / 8;
317 const int tile_dy =
static_cast<int>(drag_delta.y) / 8;
323 if (inc_dx != 0 || inc_dy != 0) {
348 if (had_mutation &&
ctx_) {
366 int resize_delta = (delta > 0.0f) ? 1 : -1;
376 if (!pointer_screen_pos.has_value()) {
381 const auto [canvas_x, canvas_y] =
383 auto [room_x, room_y] =
CanvasToRoom(canvas_x, canvas_y);
388 auto [snap_canvas_x, snap_canvas_y] =
RoomToCanvas(room_x, room_y);
391 ImDrawList* draw_list = ImGui::GetWindowDrawList();
393 static_cast<float>(snap_canvas_x + preview_geometry.offset_x_tiles * 8),
394 static_cast<float>(snap_canvas_y + preview_geometry.offset_y_tiles * 8)));
396 ImVec2(
static_cast<float>(preview_geometry.width_pixels),
397 static_cast<float>(preview_geometry.height_pixels)));
398 const ImVec2 preview_end(preview_start.x + preview_size.x,
399 preview_start.y + preview_size.y);
401 ImVec2(
static_cast<float>(snap_canvas_x -
402 preview_geometry.render_anchor_x_tiles * 8),
403 static_cast<float>(snap_canvas_y -
404 preview_geometry.render_anchor_y_tiles * 8)));
407 const size_t current_obj_count = room ? room->
GetTileObjects().size() : 0;
412 theme, capacity_state, theme.dungeon_selection_primary);
413 bool drew_bitmap =
false;
417 if (bitmap.texture()) {
418 const int crop_width =
419 std::min(preview_geometry.buffer_width_pixels, bitmap.width());
420 const int crop_height =
421 std::min(preview_geometry.buffer_height_pixels, bitmap.height());
423 static_cast<float>(crop_width),
static_cast<float>(crop_height)));
424 const ImVec2 bitmap_end(bitmap_start.x + bitmap_size.x,
425 bitmap_start.y + bitmap_size.y);
426 const ImVec2 uv_end(
static_cast<float>(crop_width) / bitmap.width(),
427 static_cast<float>(crop_height) / bitmap.height());
428 ImVec4 tint = capacity_state == GhostCapacityState::kNormal
433 draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(), bitmap_start,
434 bitmap_end, ImVec2(0, 0), uv_end,
435 ImGui::GetColorU32(tint));
436 draw_list->AddRect(preview_start, preview_end,
437 ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
443 draw_list->AddRectFilled(
444 preview_start, preview_end,
446 ImVec4(outline_color.x, outline_color.y, outline_color.z, 0.25f)));
447 draw_list->AddRect(preview_start, preview_end,
448 ImGui::GetColorU32(outline_color), 0.0f, 0, 2.0f);
453 draw_list->AddText(ImVec2(preview_start.x + 2, preview_start.y + 1),
454 ImGui::GetColorU32(theme.text_primary), id_text.c_str());
457 if (capacity_state != GhostCapacityState::kNormal &&
458 ImGui::IsMouseHoveringRect(preview_start, preview_end)) {
459 ImGui::SetTooltip(tr(
"Objects: %zu/%zu\n%s"), current_obj_count,
464 const std::string badge_text = absl::StrFormat(
467 ImVec2(preview_start.x, preview_end.y + 6.0f),
468 theme, capacity_state, badge_text);
482 auto result = zelda3::DimensionService::Get().GetDimensions(obj);
483 return std::make_tuple(result.offset_x_tiles * 8,
484 result.offset_y_tiles * 8, result.width_pixels(),
485 result.height_pixels());
494 int canvas_x,
int canvas_y)
const {
504 for (
size_t i = objects.size(); i > 0; --i) {
505 size_t index = i - 1;
506 const auto&
object = objects[index];
514 auto [obj_tile_x, obj_tile_y, width_tiles, height_tiles] =
517 int obj_px = obj_tile_x * 8;
518 int obj_py = obj_tile_y * 8;
519 int w_px = width_tiles * 8;
520 int h_px = height_tiles * 8;
522 if (canvas_x >= obj_px && canvas_x < obj_px + w_px && canvas_y >= obj_py &&
523 canvas_y < obj_py + h_px) {
531 const std::vector<zelda3::RoomObject>& objects)
const {
538 if (selected.empty()) {
542 constexpr int kAlignmentTolerancePx = 2;
543 constexpr size_t kMaxGuidesPerAxis = 8;
544 std::vector<int> vertical_guides;
545 std::vector<int> horizontal_guides;
547 for (
size_t selected_index : selected) {
548 if (selected_index >= objects.size()) {
551 const GuideRect selected_rect = GetGuideRect(objects[selected_index]);
552 const std::array<int, 3> selected_x = {
553 selected_rect.left, selected_rect.center_x, selected_rect.right};
554 const std::array<int, 3> selected_y = {
555 selected_rect.top, selected_rect.center_y, selected_rect.bottom};
557 for (
size_t other_index = 0; other_index < objects.size(); ++other_index) {
561 const GuideRect other_rect = GetGuideRect(objects[other_index]);
562 const std::array<int, 3> other_x = {other_rect.left, other_rect.center_x,
564 const std::array<int, 3> other_y = {other_rect.top, other_rect.center_y,
567 for (
int sx : selected_x) {
568 for (
int ox : other_x) {
569 if (std::abs(sx - ox) <= kAlignmentTolerancePx) {
570 AddUniqueGuide(vertical_guides, ox);
574 for (
int sy : selected_y) {
575 for (
int oy : other_y) {
576 if (std::abs(sy - oy) <= kAlignmentTolerancePx) {
577 AddUniqueGuide(horizontal_guides, oy);
581 if (vertical_guides.size() >= kMaxGuidesPerAxis &&
582 horizontal_guides.size() >= kMaxGuidesPerAxis) {
588 if (vertical_guides.empty() && horizontal_guides.empty()) {
593 ImVec4 guide_color = theme.accent_color;
594 guide_color.w = 0.78f;
595 const ImU32 color = ImGui::GetColorU32(guide_color);
596 ImDrawList* draw_list = ImGui::GetWindowDrawList();
602 const size_t vertical_count =
603 std::min(vertical_guides.size(), kMaxGuidesPerAxis);
604 for (
size_t i = 0; i < vertical_count; ++i) {
607 DrawDashedLine(draw_list, ImVec2(x, canvas_pos.y),
608 ImVec2(x, canvas_pos.y + room_size.y), color, 1.2f);
611 const size_t horizontal_count =
612 std::min(horizontal_guides.size(), kMaxGuidesPerAxis);
613 for (
size_t i = 0; i < horizontal_count; ++i) {
616 DrawDashedLine(draw_list, ImVec2(canvas_pos.x, y),
617 ImVec2(canvas_pos.x + room_size.x, y), color, 1.2f);
626 const std::vector<size_t>& indices,
627 int delta_x,
int delta_y,
628 bool notify_mutation) {
630 if (!room || indices.empty())
632 if (notify_mutation &&
ctx_)
635 auto& objects = room->GetTileObjects();
636 for (
size_t index : indices) {
637 if (index < objects.size()) {
639 std::clamp(
static_cast<int>(objects[index].x_ + delta_x), 0, 63);
641 std::clamp(
static_cast<int>(objects[index].y_ + delta_y), 0, 63);
649 const std::vector<size_t>& indices,
652 if (!room || indices.empty())
657 auto& objects = room->GetTileObjects();
658 for (
size_t index : indices) {
659 if (index < objects.size()) {
661 objects[index].set_id(new_id);
668 const std::vector<size_t>& indices,
671 if (!room || indices.empty())
676 auto& objects = room->GetTileObjects();
677 for (
size_t index : indices) {
678 if (index < objects.size()) {
679 objects[index].size_ = new_size;
680 objects[index].tiles_loaded_ =
false;
687 const std::vector<size_t>& indices,
690 if (!room || indices.empty())
692 if (new_layer < 0 || new_layer > 2) {
694 "Rejected layer update with invalid target layer: %d", new_layer);
697 auto& objects = room->GetTileObjects();
698 std::vector<size_t> deduped_indices;
699 deduped_indices.reserve(indices.size());
700 std::unordered_set<size_t> seen_indices;
701 for (
size_t index : indices) {
702 if (index >= objects.size()) {
705 if (seen_indices.insert(index).second) {
706 deduped_indices.push_back(index);
709 if (deduped_indices.empty()) {
713 if (deduped_indices.size() > kMaxLayerBatchMutation) {
715 "Rejected layer batch mutation of %zu objects (max %zu)",
716 deduped_indices.size(), kMaxLayerBatchMutation);
720 auto candidate_objects = objects;
722 deduped_indices, new_layer);
723 if (!mutation.ok()) {
724 LOG_WARN(
"TileObjectHandler",
"Rejected object stream mutation: %s",
725 std::string(mutation.status().message()).c_str());
728 if (!mutation->changed) {
734 objects = std::move(candidate_objects);
736 mutation->selected_indices);
742 int room_id,
const std::vector<size_t>& indices,
int delta_x,
int delta_y,
743 bool notify_mutation) {
745 if (!room || indices.empty())
747 if (notify_mutation &&
ctx_)
750 auto& objects = room->GetTileObjects();
751 std::vector<size_t> new_indices;
753 const size_t base_index = objects.size();
754 for (
size_t index : indices) {
755 if (index < objects.size()) {
756 auto clone = objects[index].CopyForNewPlacement();
757 clone.x_ = std::clamp(
static_cast<int>(clone.x_ + delta_x), 0, 63);
758 clone.y_ = std::clamp(
static_cast<int>(clone.y_ + delta_y), 0, 63);
759 objects.push_back(clone);
760 new_indices.push_back(base_index + (new_indices.size()));
769 std::vector<size_t> indices) {
771 if (!room || indices.empty())
776 std::sort(indices.rbegin(), indices.rend());
777 for (
size_t index : indices) {
778 room->RemoveTileObject(index);
790 room->ClearTileObjects();
795 const std::vector<size_t>& indices) {
797 if (!room || indices.empty())
799 auto& objects = room->GetTileObjects();
800 auto selected_set = MakeValidIndexSet(indices, objects.size());
801 if (selected_set.empty()) {
806 auto buckets = BuildLayerBuckets(objects, selected_set);
807 for (
auto& bucket : buckets) {
808 std::vector<LayerOrderEntry> other;
809 std::vector<LayerOrderEntry> selected;
810 other.reserve(bucket.size());
811 selected.reserve(bucket.size());
812 for (
auto& entry : bucket) {
813 if (entry.selected) {
814 selected.push_back(std::move(entry));
816 other.push_back(std::move(entry));
819 bucket = std::move(other);
820 bucket.insert(bucket.end(), std::make_move_iterator(selected.begin()),
821 std::make_move_iterator(selected.end()));
828 const std::vector<size_t>& indices) {
830 if (!room || indices.empty())
832 auto& objects = room->GetTileObjects();
833 auto selected_set = MakeValidIndexSet(indices, objects.size());
834 if (selected_set.empty()) {
839 auto buckets = BuildLayerBuckets(objects, selected_set);
840 for (
auto& bucket : buckets) {
841 std::vector<LayerOrderEntry> selected;
842 std::vector<LayerOrderEntry> other;
843 selected.reserve(bucket.size());
844 other.reserve(bucket.size());
845 for (
auto& entry : bucket) {
846 if (entry.selected) {
847 selected.push_back(std::move(entry));
849 other.push_back(std::move(entry));
852 bucket = std::move(selected);
853 bucket.insert(bucket.end(), std::make_move_iterator(other.begin()),
854 std::make_move_iterator(other.end()));
861 const std::vector<size_t>& indices) {
863 if (!room || indices.empty())
865 auto& objects = room->GetTileObjects();
866 auto selected_set = MakeValidIndexSet(indices, objects.size());
867 if (selected_set.empty()) {
872 auto buckets = BuildLayerBuckets(objects, selected_set);
873 for (
auto& bucket : buckets) {
874 if (bucket.size() < 2) {
877 for (
size_t i = bucket.size() - 1; i > 0; --i) {
878 const size_t previous = i - 1;
879 if (bucket[previous].selected && !bucket[i].selected) {
880 std::swap(bucket[previous], bucket[i]);
889 const std::vector<size_t>& indices) {
891 if (!room || indices.empty())
893 auto& objects = room->GetTileObjects();
894 auto selected_set = MakeValidIndexSet(indices, objects.size());
895 if (selected_set.empty()) {
900 auto buckets = BuildLayerBuckets(objects, selected_set);
901 for (
auto& bucket : buckets) {
902 if (bucket.size() < 2) {
905 for (
size_t i = 1; i < bucket.size(); ++i) {
906 const size_t previous = i - 1;
907 if (bucket[i].selected && !bucket[previous].selected) {
908 std::swap(bucket[i], bucket[previous]);
917 const std::vector<size_t>& indices,
920 if (!room || indices.empty())
924 auto& objects = room->GetTileObjects();
925 for (
size_t index : indices) {
926 if (index < objects.size()) {
928 std::clamp(
static_cast<int>(objects[index].size_) + delta, 0, 15);
929 objects[index].size_ =
static_cast<uint8_t
>(new_size);
930 objects[index].tiles_loaded_ =
false;
954 auto new_obj =
object.CopyForNewPlacement();
955 new_obj.x_ = std::clamp(x, 0, 63);
956 new_obj.y_ = std::clamp(y, 0, 63);
957 room->AddTileObject(new_obj);
976 if (!room || !room->IsLoaded())
986 kGhostPreviewBufferSize, kGhostPreviewBufferSize);
991 ApplyRoomPaletteToGhost(*room, bitmap);
992 std::fill(bitmap.mutable_data().begin(), bitmap.mutable_data().end(), 255);
993 bitmap.set_modified(
true);
994 const uint8_t* gfx_data = room->get_gfx_buffer().data();
1002 render_object.
x_ = preview_geometry.render_anchor_x_tiles;
1003 render_object.y_ = preview_geometry.render_anchor_y_tiles;
1015 if (bitmap.size() > 0) {
1016 bitmap.UpdateSurfacePixels();
1017 if (bitmap.texture()) {
1027 if (bitmap.texture()) {
1038 const auto [resolved_anchor_x, resolved_anchor_y] =
1042 const int anchor_x =
1043 std::max({0, resolved_anchor_x, -dimensions.offset_x_tiles});
1044 const int anchor_y =
1045 std::max({0, resolved_anchor_y, -dimensions.offset_y_tiles});
1046 const int leading_x_tiles = anchor_x + dimensions.offset_x_tiles;
1047 const int leading_y_tiles = anchor_y + dimensions.offset_y_tiles;
1050 .render_anchor_y_tiles = anchor_y,
1051 .offset_x_tiles = dimensions.offset_x_tiles,
1052 .offset_y_tiles = dimensions.offset_y_tiles,
1053 .width_pixels = dimensions.width_pixels(),
1054 .height_pixels = dimensions.height_pixels(),
1055 .buffer_width_pixels = (leading_x_tiles + dimensions.width_tiles) * 8,
1056 .buffer_height_pixels = (leading_y_tiles + dimensions.height_tiles) * 8,
1065 int room_id,
const std::vector<size_t>& indices) {
1066 auto* room =
GetRoom(room_id);
1067 if (!room || indices.empty())
1071 const auto& objects = room->GetTileObjects();
1073 for (
size_t idx : indices) {
1074 if (idx < objects.size()) {
1083 auto* room =
GetRoom(room_id);
1089 std::vector<size_t> new_indices;
1090 size_t base_index = room->GetTileObjects().size();
1093 obj = obj.CopyForNewPlacement();
1094 obj.x_ = std::clamp(obj.x_ + offset_x, 0, 63);
1095 obj.y_ = std::clamp(obj.y_ + offset_y, 0, 63);
1096 obj.tiles_loaded_ =
false;
1097 room->AddTileObject(obj);
1098 new_indices.push_back(base_index++);
std::optional< ImVec2 > GetPointerScreenPosition() const
DungeonCanvasTransform GetCanvasTransform() const
std::pair< int, int > CanvasToRoom(int canvas_x, int canvas_y) const
Convert canvas pixel coordinates to room tile coordinates.
void TriggerSuccessToast()
InteractionContext * ctx_
bool IsWithinBounds(int canvas_x, int canvas_y) const
Check if coordinates are within room bounds.
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.
Manages object selection state and operations for the dungeon editor.
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.
bool IsObjectSelected(size_t index) const
Check if an object is selected.
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.
bool HasSelection() const
Check if any objects are selected.
Handles functional mutations and queries for tile objects.
zelda3::RoomObject preview_object_
void DrawGhostPreview() override
Draw ghost preview during placement.
void DrawSmartGuides(const std::vector< zelda3::RoomObject > &objects) const
std::vector< zelda3::RoomObject > clipboard_
bool ghost_preview_bitmap_ready_
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.
void RenderGhostPreviewBitmap()
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.
bool drag_has_duplicated_
void HandleDrag(ImVec2 current_pos, ImVec2 delta) override
Handle mouse drag.
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 UpdateObjectsLayer(int room_id, const std::vector< size_t > &indices, int new_layer)
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.
bool object_placement_mode_
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.
bool ghost_preview_create_queued_
PlacementBlockReason placement_block_reason_
void DeleteObjects(int room_id, std::vector< size_t > indices)
Delete objects by indices.
bool drag_mutation_started_
static GhostPreviewGeometry CalculateGhostPreviewGeometry(const zelda3::RoomObject &object)
Resolve the render anchor and visual extent used by placement previews.
void UpdateObjectsId(int room_id, const std::vector< size_t > &indices, int16_t new_id)
zelda3::Room * GetRoom(int room_id)
PlacementCapacityState GhostCapacityState
void NotifyChange(zelda3::Room *room)
GhostCapacityState GetPlacementGhostCapacityState() const
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)
void ProcessTextureQueue(IRenderer *renderer)
Represents a bitmap image optimized for SNES ROM hacking.
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
SDL_Surface * surface() const
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
DimensionResult GetDimensions(const RoomObject &obj) const
std::tuple< int, int, int, int > GetHitTestBounds(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.
std::pair< int, int > ResolveAnchor(int16_t object_id, uint8_t size_byte) const
Resolve the canvas anchor (x, y) for a given object's draw routine.
static ObjectGeometry & Get()
void MarkTileObjectCollectionDirty()
auto & object_bg2_buffer()
const std::vector< RoomObject > & GetTileObjects() const
auto & object_bg1_buffer()
#define LOG_WARN(category, format,...)
const AgentUITheme & GetTheme()
void DrawDashedLine(ImDrawList *draw_list, ImVec2 start, ImVec2 end, ImU32 color, float thickness)
constexpr int kGhostPreviewBufferSize
void RestoreObjectSelection(ObjectSelection *selection, const std::vector< size_t > &selected_indices_after_reorder)
constexpr size_t kMaxLayerBatchMutation
bool ApplyRoomPaletteToGhost(const zelda3::Room &room, gfx::Bitmap &ghost)
std::unordered_set< size_t > MakeValidIndexSet(const std::vector< size_t > &indices, size_t object_count)
GuideRect GetGuideRect(const zelda3::RoomObject &object)
int LayerBucketIndex(const zelda3::RoomObject &object)
void FlattenLayerBuckets(std::vector< zelda3::RoomObject > &objects, std::array< std::vector< LayerOrderEntry >, 3 > &buckets, ObjectSelection *selection)
bool AddUniqueGuide(std::vector< int > &guides, int value)
std::array< std::vector< LayerOrderEntry >, 3 > BuildLayerBuckets(const std::vector< zelda3::RoomObject > &objects, const std::unordered_set< size_t > &selected_indices)
constexpr int kRoomPixelHeight
constexpr int kRoomPixelWidth
ImVec2 SnapToTileGrid(const ImVec2 &point)
Snap a point to the 8px tile grid.
Editors are the view controllers for the application.
void DrawPlacementCapacityBadge(ImDrawList *draw_list, const ImVec2 &badge_min, const AgentUITheme &theme, PlacementCapacityState state, std::string_view primary_text)
ImVec4 GetPlacementAccentColor(const AgentUITheme &theme, PlacementCapacityState state, const ImVec4 &normal_color)
std::string_view GetPlacementCapacityTooltipSuffix(PlacementCapacityState state)
PlacementCapacityState GetPlacementCapacityState(size_t current_count, size_t max_count)
constexpr size_t kMaxTileObjects
absl::StatusOr< ObjectStorageMutationResult > ReassignObjectStorage(std::vector< RoomObject > &objects, const std::vector< size_t > &indices, int target_value)
SDL2/SDL3 compatibility layer.
void NotifyInvalidateCache(MutationDomain domain=MutationDomain::kUnknown) const
Notify that cache invalidation is needed.
ObjectSelection * selection
void NotifyMutation(MutationDomain domain=MutationDomain::kUnknown) const
Notify that a mutation is about to happen.
gfx::PaletteGroup current_palette_group
int render_anchor_x_tiles
zelda3::RoomObject object