3#include "absl/strings/str_format.h"
11#include "imgui/imgui.h"
32 return io.MouseWheel != 0.0f || !io.KeyShift ? io.MouseWheel : io.MouseWheelH;
38 ImGuiIO& io = ImGui::GetIO();
40 const bool mouse_left_down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
41 const bool mouse_left_released =
42 ImGui::IsMouseReleased(ImGuiMouseButton_Left);
47 const bool should_process_without_hover =
51 (mouse_left_down || mouse_left_released)) ||
54 if (!hovered && !should_process_without_hover) {
58 const ImVec2 canvas_mouse_pos =
60 const int canvas_mouse_x =
static_cast<int>(std::floor(canvas_mouse_pos.x));
61 const int canvas_mouse_y =
static_cast<int>(std::floor(canvas_mouse_pos.y));
62 const bool pointer_within_room =
66 if (ImGui::IsKeyPressed(ImGuiKey_Escape) &&
80 if (hovered && mouse_left_down) {
83 if (pointer_within_room) {
89 if (pointer_within_room) {
96 if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
101 if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
106 if (mouse_left_released) {
112 int canvas_x =
static_cast<int>(std::floor(canvas_mouse_pos.x));
113 int canvas_y =
static_cast<int>(std::floor(canvas_mouse_pos.y));
140 const ImVec2& canvas_mouse_pos) {
141 auto [room_x, room_y] =
143 static_cast<int>(canvas_mouse_pos.y));
149 if (room_x >= 0 && room_x < 64 && room_y >= 0 && room_y < 64) {
151 if (!state.is_painting) {
153 state.paint_mutation_started =
false;
154 state.paint_last_tile_x = room_x;
155 state.paint_last_tile_y = room_y;
159 (state.paint_last_tile_x >= 0) ? state.paint_last_tile_x : room_x;
161 (state.paint_last_tile_y >= 0) ? state.paint_last_tile_y : room_y;
163 bool changed =
false;
164 auto ensure_mutation = [&]() {
165 if (!state.paint_mutation_started) {
167 state.paint_mutation_started =
true;
172 x0, y0, room_x, room_y, [&](
int lx,
int ly) {
174 lx, ly, state.paint_brush_radius,
176 [&](
int bx,
int by) {
177 if (room.HasCollisionOverride(bx, by) &&
178 room.GetCollisionTile(bx, by) ==
179 state.paint_collision_value) {
183 room.SetCollisionTile(bx, by, state.paint_collision_value);
193 state.paint_last_tile_x = room_x;
194 state.paint_last_tile_y = room_y;
199void DungeonObjectInteraction::UpdateWaterFillPainting(
200 const ImVec2& canvas_mouse_pos) {
202 mode_manager_.SetMode(InteractionMode::Select);
206 const ImGuiIO& io = ImGui::GetIO();
207 const bool erase = io.KeyAlt;
209 auto [room_x, room_y] =
210 CanvasToRoomCoordinates(
static_cast<int>(canvas_mouse_pos.x),
211 static_cast<int>(canvas_mouse_pos.y));
212 if (rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
213 auto& room = (*rooms_)[current_room_id_];
214 auto& state = mode_manager_.GetModeState();
217 if (room_x >= 0 && room_x < 64 && room_y >= 0 && room_y < 64) {
218 const bool new_val = !erase;
220 if (!state.is_painting) {
221 state.is_painting =
true;
222 state.paint_mutation_started =
false;
223 state.paint_last_tile_x = room_x;
224 state.paint_last_tile_y = room_y;
228 (state.paint_last_tile_x >= 0) ? state.paint_last_tile_x : room_x;
230 (state.paint_last_tile_y >= 0) ? state.paint_last_tile_y : room_y;
232 bool changed =
false;
233 auto ensure_mutation = [&]() {
234 if (!state.paint_mutation_started) {
235 interaction_context_.NotifyMutation(MutationDomain::kWaterFill);
236 state.paint_mutation_started =
true;
240 paint_util::ForEachPointOnLine(
241 x0, y0, room_x, room_y, [&](
int lx,
int ly) {
242 paint_util::ForEachPointInSquareBrush(
243 lx, ly, state.paint_brush_radius,
245 [&](
int bx,
int by) {
246 if (room.GetWaterFillTile(bx, by) == new_val) {
250 room.SetWaterFillTile(bx, by, new_val);
256 interaction_context_.NotifyInvalidateCache(MutationDomain::kWaterFill);
259 state.paint_last_tile_x = room_x;
260 state.paint_last_tile_y = room_y;
265void DungeonObjectInteraction::HandleObjectSelectionStart(
266 const ImVec2& canvas_mouse_pos) {
267 const bool has_object_selection = selection_.HasSelection();
268 const bool has_entity_selection = HasEntitySelection();
269 if (!has_object_selection && !has_entity_selection) {
273 mode_manager_.SetMode(InteractionMode::DraggingObjects);
274 if (has_object_selection) {
275 entity_coordinator_.tile_handler().InitDrag(canvas_mouse_pos);
277 if (has_entity_selection) {
278 entity_coordinator_.BeginSelectionDrag(canvas_mouse_pos);
282void DungeonObjectInteraction::HandleEmptySpaceClick(
283 const ImVec2& canvas_mouse_pos) {
284 const ImGuiIO& io = ImGui::GetIO();
285 const bool additive = io.KeyShift || io.KeyCtrl || io.KeySuper;
286 const bool had_selection =
287 selection_.HasSelection() || entity_coordinator_.HasEntitySelection();
292 ClearEntitySelection();
293 selection_.ClearSelection();
296 if (!had_selection) {
297 entity_coordinator_.tile_handler().BeginMarqueeSelection(canvas_mouse_pos);
301void DungeonObjectInteraction::FinishPaintStroke() {
304 const auto mode = mode_manager_.GetMode();
305 if (mode != InteractionMode::PaintCollision &&
306 mode != InteractionMode::PaintWaterFill) {
309 auto& state = mode_manager_.GetModeState();
310 const bool had_mutation = state.paint_mutation_started;
311 state.is_painting =
false;
312 state.paint_mutation_started =
false;
313 state.paint_last_tile_x = -1;
314 state.paint_last_tile_y = -1;
316 interaction_context_.NotifyInvalidateCache(
317 (mode == InteractionMode::PaintCollision)
318 ? MutationDomain::kCustomCollision
319 : MutationDomain::kWaterFill);
323void DungeonObjectInteraction::HandleMouseRelease() {
326 if (mode_manager_.GetMode() == InteractionMode::DraggingObjects) {
327 mode_manager_.SetMode(InteractionMode::Select);
329 entity_coordinator_.HandleRelease();
334bool DungeonObjectInteraction::HandleKeyboardNudge() {
335 if (!rooms_ || current_room_id_ < 0 ||
336 current_room_id_ >=
static_cast<int>(rooms_->size())) {
340 const ImGuiIO& io = ImGui::GetIO();
341 if (ImGui::IsAnyItemActive() || io.KeyCtrl || io.KeySuper || io.KeyAlt ||
342 ImGui::IsMouseDown(ImGuiMouseButton_Left) ||
343 entity_coordinator_.IsPlacementActive()) {
347 const auto mode = mode_manager_.GetMode();
348 if (mode == InteractionMode::DraggingObjects ||
349 mode == InteractionMode::PaintCollision ||
350 mode == InteractionMode::PaintWaterFill) {
356 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow,
true)) {
358 }
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow,
true)) {
361 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow,
true)) {
363 }
else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow,
true)) {
367 if (delta_x == 0 && delta_y == 0) {
371 return NudgeSelected(delta_x, delta_y);
374bool DungeonObjectInteraction::NudgeSelected(
int delta_x,
int delta_y) {
375 return entity_coordinator_.NudgeSelected(delta_x, delta_y);
378void DungeonObjectInteraction::CheckForObjectSelection() {
380 const ImGuiIO& io = ImGui::GetIO();
381 const ImVec2 mouse_pos = GetCanvasTransform().ScreenToRoomPixels(io.MousePos);
382 const bool mouse_left_released =
383 ImGui::IsMouseReleased(ImGuiMouseButton_Left);
385 if (mouse_left_released && selection_.IsRectangleSelectionActive()) {
386 selection_.UpdateRectangleSelection(
static_cast<int>(mouse_pos.x),
387 static_cast<int>(mouse_pos.y));
388 constexpr int kMinRectPixels = 6;
389 if (!io.KeyAlt && selection_.IsRectangleLargeEnough(kMinRectPixels)) {
390 entity_coordinator_.SelectEntitiesInRect(
391 selection_.GetRectangleSelectionBounds(),
397 entity_coordinator_.tile_handler().HandleMarqueeSelection(
399 ImGui::IsMouseDown(ImGuiMouseButton_Left),
402 io.KeyCtrl || io.KeySuper,
406void DungeonObjectInteraction::DrawSelectionHighlights() {
407 if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
410 auto& room = (*rooms_)[current_room_id_];
411 const auto& objects = room.GetTileObjects();
414 selection_.DrawSelectionHighlights(
417 return std::make_tuple(result.offset_x_tiles * 8,
418 result.offset_y_tiles * 8, result.width_pixels(),
419 result.height_pixels());
424 if (entity_coordinator_.HasEntitySelection()) {
428 if (canvas_->IsMouseHovering()) {
430 ImGuiIO& io = ImGui::GetIO();
431 const auto [cursor_x, cursor_y] =
432 GetCanvasTransform().ScreenToRoomPixelCoordinates(io.MousePos);
433 auto entity_at_cursor =
434 entity_coordinator_.GetEntityAtPosition(cursor_x, cursor_y);
435 if (entity_at_cursor.has_value()) {
437 DrawHoverHighlight(objects);
441 auto hovered_index = entity_coordinator_.tile_handler().GetEntityAtPosition(
443 if (hovered_index.has_value() && *hovered_index < objects.size()) {
444 const auto&
object = objects[*hovered_index];
447 const int layer =
object.GetLayerValue();
450 const char* subtype_names[] = {
"Unknown",
"Type 1",
"Type 2",
"Type 3"};
451 const char* subtype_name =
452 (subtype >= 0 && subtype <= 3) ? subtype_names[subtype] :
"Unknown";
456 tooltip += object_name;
457 tooltip +=
" (" + std::string(subtype_name) +
")";
459 tooltip +=
"ID: 0x" + absl::StrFormat(
"%03X",
object.id_);
461 static constexpr const char* kStreamNames[] = {
"Primary",
"BG2 overlay",
463 const char* stream_name =
464 (layer >= 0 && layer < 3) ? kStreamNames[layer] :
"Unknown";
465 tooltip +=
" | Object stream: " + std::string(stream_name);
467 const char* layer_name =
468 layer == 0 ?
"Upper layer (BG1)" :
"Lower layer (BG2)";
469 tooltip +=
" | Special layer: " + std::string(layer_name);
471 tooltip +=
" | Pos: (" + std::to_string(
object.x_) +
", " +
472 std::to_string(
object.y_) +
")";
475 tooltip += absl::StrFormat(
476 "\nSize: %d x %d tiles",
480 tooltip += absl::StrFormat(
"\nSize: 0x%02X",
object.size_);
483 if (selection_.IsObjectSelected(*hovered_index)) {
486 "\n" ICON_MD_MOUSE " Wheel: grow/shrink | Shift+wheel: width";
495 ImGui::SetTooltip(
"%s", tooltip.c_str());
500 DrawHoverHighlight(objects);
503void DungeonObjectInteraction::DrawHoverHighlight(
504 const std::vector<zelda3::RoomObject>& objects) {
505 if (!canvas_->IsMouseHovering())
509 if (entity_coordinator_.HasEntitySelection())
514 ImGuiIO& io = ImGui::GetIO();
516 const auto [cursor_canvas_x, cursor_canvas_y] =
518 auto entity_at_cursor =
519 entity_coordinator_.GetEntityAtPosition(cursor_canvas_x, cursor_canvas_y);
520 if (entity_at_cursor.has_value()) {
524 auto hovered_index = entity_coordinator_.tile_handler().GetEntityAtPosition(
525 cursor_canvas_x, cursor_canvas_y);
526 if (!hovered_index.has_value() || *hovered_index >= objects.size()) {
529 const auto&
object = objects[*hovered_index];
532 if (selection_.IsObjectSelected(*hovered_index)) {
536 const auto& theme = AgentUI::GetTheme();
537 ImDrawList* draw_list = ImGui::GetWindowDrawList();
539 auto [sel_x_px, sel_y_px, pixel_width, pixel_height] =
543 ImVec2(
static_cast<float>(sel_x_px),
static_cast<float>(sel_y_px)));
545 static_cast<float>(pixel_width),
static_cast<float>(pixel_height)));
546 ImVec2 obj_end(obj_start.x + obj_size.x, obj_start.y + obj_size.y);
549 constexpr float margin = 2.0f;
550 obj_start.x -= margin;
551 obj_start.y -= margin;
556 ImVec4 hover_fill = theme.selection_hover;
557 hover_fill.w *= 0.5f;
559 ImVec4 hover_border = theme.selection_hover;
562 draw_list->AddRectFilled(obj_start, obj_end, ImGui::GetColorU32(hover_fill));
565 draw_list->AddRect(obj_start, obj_end, ImGui::GetColorU32(hover_border), 0.0f,
569void DungeonObjectInteraction::PlaceObjectAtPosition(
int room_x,
int room_y) {
570 auto& tile_handler = entity_coordinator_.tile_handler();
571 if (!tile_handler.IsPlacementActive()) {
574 auto object = tile_handler.GetPreviewObject().CopyForNewPlacement();
575 object.x_ = std::clamp(room_x, 0, 63);
576 object.y_ = std::clamp(room_y, 0, 63);
577 if (!tile_handler.PlaceObjectAt(interaction_context_.current_room_id,
object,
581 if (object_placed_callback_) {
582 object_placed_callback_(
object);
586void DungeonObjectInteraction::CompleteObjectPlacement(
588 entity_coordinator_.ClearEntitySelection();
589 if (!entity_coordinator_.tile_handler().IsPlacementActive() &&
590 mode_manager_.GetMode() == InteractionMode::PlaceObject) {
591 mode_manager_.SetMode(InteractionMode::Select);
595bool DungeonObjectInteraction::BeginPlacementFromSelection() {
596 const auto indices = selection_.GetSelectedIndices();
597 const auto* room = interaction_context_.GetCurrentRoomConst();
598 if (!room || indices.size() != 1 || HasEntitySelection() ||
599 indices.front() >= room->GetTileObjects().size()) {
603 room->GetTileObjects()[indices.front()].CopyForNewPlacement();
604 SetPreviewObject(
object,
true);
608std::pair<int, int> DungeonObjectInteraction::RoomToCanvasCoordinates(
609 int room_x,
int room_y)
const {
611 return {room_x * 8, room_y * 8};
614std::pair<int, int> DungeonObjectInteraction::CanvasToRoomCoordinates(
615 int canvas_x,
int canvas_y)
const {
617 return {canvas_x / 8, canvas_y / 8};
620bool DungeonObjectInteraction::IsWithinCanvasBounds(
int canvas_x,
int canvas_y,
622 return dungeon_coords::IsWithinBounds(canvas_x, canvas_y, margin);
631 if (rooms != rooms_ || room_id != current_room_id_) {
633 const auto mode = mode_manager_.GetMode();
634 if (mode == InteractionMode::DraggingObjects ||
635 mode == InteractionMode::DraggingEntity ||
636 mode == InteractionMode::RectangleSelect) {
637 mode_manager_.SetMode(InteractionMode::Select);
642 entity_coordinator_.tile_handler().HandleRelease();
643 selection_.CancelRectangleSelection();
644 entity_coordinator_.ClearAllEntitySelections();
648 current_room_id_ = room_id;
649 interaction_context_.rooms = rooms;
650 interaction_context_.current_room_id = room_id;
651 interaction_context_.selection = &selection_;
652 entity_coordinator_.SetContext(&interaction_context_);
655void DungeonObjectInteraction::SetPreviewObject(
657 if (loaded &&
object.id_ >= 0) {
660 entity_coordinator_.CancelCurrentMode();
663 mode_manager_.SetMode(InteractionMode::PlaceObject);
667 auto& tile_handler = entity_coordinator_.tile_handler();
668 tile_handler.SetPreviewObject(
object);
669 if (!tile_handler.IsPlacementActive()) {
670 tile_handler.BeginPlacement();
674 if (mode_manager_.GetMode() == InteractionMode::PlaceObject) {
680void DungeonObjectInteraction::ClearSelection() {
681 selection_.ClearSelection();
682 if (mode_manager_.GetMode() == InteractionMode::DraggingObjects) {
683 mode_manager_.SetMode(InteractionMode::Select);
687absl::Status DungeonObjectInteraction::HandleDeleteSelected() {
689 request.
kind = DungeonSelectionEditKind::kDelete;
690 request.
objects = selection_.GetSelectedIndices();
691 request.
entities = entity_coordinator_.SelectedEntitiesForEdit();
692 return entity_coordinator_.CommitSelectionEdit(request);
695void DungeonObjectInteraction::HandleDeleteAllObjects() {
696 if (entity_coordinator_.tile_handler().DeleteAllObjects(current_room_id_)) {
697 selection_.ClearSelection();
701absl::Status DungeonObjectInteraction::HandleCopySelected() {
702 const auto* room = interaction_context_.GetCurrentRoomConst();
704 return entity_coordinator_.ReportSelectionEditStatus(
705 absl::FailedPreconditionError(
"No room is available to copy"));
707 const auto objects = selection_.GetSelectedIndices();
708 const auto entities = entity_coordinator_.SelectedEntitiesForEdit();
709 if (objects.empty() && entities.empty())
710 return absl::OkStatus();
713 return entity_coordinator_.ReportSelectionEditStatus(copied.status());
716 clipboard() = std::move(*copied);
717 return entity_coordinator_.ReportSelectionEditStatus(absl::OkStatus());
720absl::Status DungeonObjectInteraction::HandleDuplicateSelected() {
722 request.
kind = DungeonSelectionEditKind::kDuplicate;
723 request.
objects = selection_.GetSelectedIndices();
724 request.
entities = entity_coordinator_.SelectedEntitiesForEdit();
725 const bool includes_door = std::any_of(
727 [](
const auto entity) { return entity.type == EntityType::Door; });
731 includes_door ? 0 : entity_coordinator_.SelectionMoveStepPixels();
734 return entity_coordinator_.CommitSelectionEdit(request);
737absl::Status DungeonObjectInteraction::HandlePasteAt(
int pixel_x,
int pixel_y) {
738 if (!HasClipboardData())
739 return absl::OkStatus();
740 if (!dungeon_coords::IsWithinBounds(pixel_x, pixel_y)) {
741 return entity_coordinator_.ReportSelectionEditStatus(
742 absl::OutOfRangeError(
"Paste anchor is outside the room"));
745 request.
kind = DungeonSelectionEditKind::kPaste;
746 request.
objects = selection_.GetSelectedIndices();
747 request.
entities = entity_coordinator_.SelectedEntitiesForEdit();
748 auto& copied = clipboard();
752 return entity_coordinator_.CommitSelectionEdit(request);
755absl::Status DungeonObjectInteraction::HandlePasteObjects() {
756 if (!HasClipboardData())
757 return absl::OkStatus();
758 const auto& copied = clipboard();
759 int target_x = copied.origin_pixel_x;
760 int target_y = copied.origin_pixel_y;
761 const int step = !copied.sprites.empty() || !copied.items.empty()
762 ? dungeon_coords::kSpriteTileSize
763 : dungeon_coords::kTileSize;
764 if (canvas_ && ImGui::GetCurrentContext()) {
765 const auto [mouse_x, mouse_y] =
766 GetCanvasTransform().ScreenToRoomPixelCoordinates(
767 ImGui::GetIO().MousePos);
768 if (dungeon_coords::IsWithinBounds(mouse_x, mouse_y)) {
771 target_x += (mouse_x - target_x) / step * step;
772 target_y += (mouse_y - target_y) / step * step;
773 return HandlePasteAt(target_x, target_y);
778 const int fallback_offset = copied.doors.empty() ? step : 0;
779 return HandlePasteAt(target_x + fallback_offset, target_y + fallback_offset);
782void DungeonObjectInteraction::DrawGhostPreview() {
783 entity_coordinator_.DrawGhostPreviews();
786void DungeonObjectInteraction::HandleScrollWheelResize() {
787 const ImGuiIO& io = ImGui::GetIO();
788 entity_coordinator_.HandleMouseWheel(ObjectResizeWheelDelta(io));
791bool DungeonObjectInteraction::SetObjectId(
size_t index, int16_t
id) {
792 entity_coordinator_.tile_handler().UpdateObjectsId(current_room_id_, {index},
797bool DungeonObjectInteraction::SetObjectSize(
size_t index, uint8_t size) {
798 entity_coordinator_.tile_handler().UpdateObjectsSize(current_room_id_,
803bool DungeonObjectInteraction::SetObjectLayer(
805 return entity_coordinator_.tile_handler().UpdateObjectsLayer(
806 current_room_id_, {index},
static_cast<int>(layer));
809std::pair<int, int> DungeonObjectInteraction::CalculateObjectBounds(
814bool DungeonObjectInteraction::CanAssignSelectedObjectsToLayer(
815 int target_layer)
const {
816 if (!rooms_ || current_room_id_ < 0 ||
817 current_room_id_ >=
static_cast<int>(rooms_->size()) ||
818 target_layer < 0 || target_layer > 2) {
822 const auto selected = selection_.GetSelectedIndices();
823 if (selected.empty()) {
827 const auto& objects = (*rooms_)[current_room_id_].GetTileObjects();
828 for (
const size_t index : selected) {
829 if (index >= objects.size() ||
837bool DungeonObjectInteraction::SendSelectedToLayer(
int target_layer) {
838 if (!CanAssignSelectedObjectsToLayer(target_layer)) {
841 return entity_coordinator_.tile_handler().UpdateObjectsLayer(
842 current_room_id_, selection_.GetSelectedIndices(), target_layer);
845void DungeonObjectInteraction::SendSelectedToFront() {
846 entity_coordinator_.tile_handler().SendToFront(
847 current_room_id_, selection_.GetSelectedIndices());
850void DungeonObjectInteraction::SendSelectedToBack() {
851 entity_coordinator_.tile_handler().SendToBack(
852 current_room_id_, selection_.GetSelectedIndices());
855void DungeonObjectInteraction::BringSelectedForward() {
856 entity_coordinator_.tile_handler().MoveForward(
857 current_room_id_, selection_.GetSelectedIndices());
860void DungeonObjectInteraction::SendSelectedBackward() {
861 entity_coordinator_.tile_handler().MoveBackward(
862 current_room_id_, selection_.GetSelectedIndices());
865void DungeonObjectInteraction::HandleLayerKeyboardShortcuts() {
867 if (!selection_.HasSelection())
871 if (ImGui::IsAnyItemActive())
876 const auto& io = ImGui::GetIO();
877 if (io.KeyMods == ImGuiMod_None) {
878 if (ImGui::IsKeyPressed(ImGuiKey_1)) {
879 SendSelectedToLayer(0);
880 }
else if (ImGui::IsKeyPressed(ImGuiKey_2)) {
881 SendSelectedToLayer(1);
882 }
else if (ImGui::IsKeyPressed(ImGuiKey_3)) {
883 SendSelectedToLayer(2);
890 if (io.KeyCtrl && io.KeyShift) {
891 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
892 SendSelectedToFront();
893 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
894 SendSelectedToBack();
896 }
else if (io.KeyCtrl) {
897 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
898 BringSelectedForward();
899 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
900 SendSelectedBackward();
909void DungeonObjectInteraction::SetDoorPlacementMode(
bool enabled,
914 mode_manager_.SetMode(InteractionMode::PlaceDoor);
915 entity_coordinator_.door_handler().SetDoorType(type);
916 entity_coordinator_.door_handler().BeginPlacement();
918 entity_coordinator_.door_handler().CancelPlacement();
919 if (mode_manager_.GetMode() == InteractionMode::PlaceDoor)
920 mode_manager_.SetMode(InteractionMode::Select);
928void DungeonObjectInteraction::SetSpritePlacementMode(
bool enabled,
933 mode_manager_.SetMode(InteractionMode::PlaceSprite);
934 entity_coordinator_.sprite_handler().SetSpriteId(sprite_id);
935 entity_coordinator_.sprite_handler().BeginPlacement();
937 entity_coordinator_.sprite_handler().CancelPlacement();
938 if (mode_manager_.GetMode() == InteractionMode::PlaceSprite)
939 mode_manager_.SetMode(InteractionMode::Select);
947void DungeonObjectInteraction::SetItemPlacementMode(
bool enabled,
952 mode_manager_.SetMode(InteractionMode::PlaceItem);
953 entity_coordinator_.item_handler().SetItemId(item_id);
954 entity_coordinator_.item_handler().BeginPlacement();
956 entity_coordinator_.item_handler().CancelPlacement();
957 if (mode_manager_.GetMode() == InteractionMode::PlaceItem)
958 mode_manager_.SetMode(InteractionMode::Select);
966void DungeonObjectInteraction::SelectEntity(
EntityType type,
size_t index) {
967 selection_.ClearSelection();
968 entity_coordinator_.SelectEntity(type, index);
971void DungeonObjectInteraction::ClearEntitySelection() {
972 entity_coordinator_.ClearEntitySelection();
975void DungeonObjectInteraction::CancelPlacement() {
976 entity_coordinator_.CancelPlacement();
977 if (mode_manager_.IsPlacementActive()) {
978 mode_manager_.SetMode(InteractionMode::Select);
982void DungeonObjectInteraction::DrawEntitySelectionHighlights() {
983 entity_coordinator_.DrawSelectionHighlights();
984 entity_coordinator_.DrawPostPlacementOverlays();
987void DungeonObjectInteraction::DrawDoorSnapIndicators() {
DungeonCanvasTransform GetCanvasTransform() const
InteractionContext interaction_context_
void HandleCanvasMouseInput()
void HandleObjectSelectionStart(const ImVec2 &canvas_mouse_pos)
InteractionCoordinator entity_coordinator_
ObjectSelection selection_
void HandleMouseRelease()
void HandleLayerKeyboardShortcuts()
void HandleLeftClick(const ImVec2 &canvas_mouse_pos)
void UpdateWaterFillPainting(const ImVec2 &canvas_mouse_pos)
bool HasEntitySelection() const
std::pair< int, int > CanvasToRoomCoordinates(int canvas_x, int canvas_y) const
void HandleEmptySpaceClick(const ImVec2 &canvas_mouse_pos)
DungeonRoomStore * rooms_
bool HandleKeyboardNudge()
InteractionModeManager mode_manager_
void UpdateCollisionPainting(const ImVec2 &canvas_mouse_pos)
bool IsPlacementActive() const
Check if any placement mode is active.
bool HandleClick(int canvas_x, int canvas_y)
Handle click at canvas position.
bool HasEntitySelection() const
void HandleDrag(ImVec2 current_pos, ImVec2 delta)
Handle drag operation.
InteractionMode GetMode() const
Get current interaction mode.
ModeState & GetModeState()
Get mutable reference to mode state.
bool IsRectangleSelectionActive() const
Check if a rectangle selection is in progress.
bool HasSelection() const
Check if any objects are selected.
bool IsMouseHovering() const
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
DimensionResult GetDimensions(const RoomObject &obj) const
std::pair< int, int > GetPixelDimensions(const RoomObject &obj) const
#define ICON_MD_DRAG_INDICATOR
#define ICON_MD_TOUCH_APP
float ObjectResizeWheelDelta(const ImGuiIO &io)
bool IsWithinBounds(int canvas_x, int canvas_y, int margin=0)
Check if coordinates are within room bounds.
void ForEachPointInSquareBrush(int cx, int cy, int radius, int min_x, int min_y, int max_x, int max_y, Fn &&fn)
void ForEachPointOnLine(int x0, int y0, int x1, int y1, Fn &&fn)
Editors are the view controllers for the application.
absl::StatusOr< DungeonSelectionClipboard > CopyDungeonSelection(const zelda3::Room &room, const std::vector< size_t > &objects, const std::vector< SelectedEntity > &entities)
EntityType
Type of entity that can be selected in the dungeon editor.
DoorType
Door types from ALTTP.
int GetObjectSubtype(int object_id)
int RoomObjectSizeAxisStep(int object_id)
bool UsesRoomObjectStream(const RoomObject &object)
std::string GetObjectName(int object_id)
bool IsRoomObjectResizable(int object_id)
int RoomObjectSizeAxisTiles(int object_id, uint8_t size, bool horizontal)
DungeonSelectionEditKind kind
std::vector< SelectedEntity > entities
std::vector< size_t > objects
const DungeonSelectionClipboard * clipboard
void NotifyInvalidateCache(MutationDomain domain=MutationDomain::kUnknown) const
Notify that cache invalidation is needed.
void NotifyMutation(MutationDomain domain=MutationDomain::kUnknown) const
Notify that a mutation is about to happen.