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 (ImGui::GetCurrentContext() !=
nullptr && ImGui::GetIO().KeyAlt);
152 if (room_x >= 0 && room_x < 64 && room_y >= 0 && room_y < 64) {
154 if (!state.is_painting) {
155 state.is_painting =
true;
156 state.paint_mutation_started =
false;
157 state.paint_last_tile_x = room_x;
158 state.paint_last_tile_y = room_y;
162 (state.paint_last_tile_x >= 0) ? state.paint_last_tile_x : room_x;
164 (state.paint_last_tile_y >= 0) ? state.paint_last_tile_y : room_y;
166 bool changed =
false;
167 auto ensure_mutation = [&]() {
168 if (!state.paint_mutation_started) {
170 state.paint_mutation_started =
true;
175 x0, y0, room_x, room_y, [&](
int lx,
int ly) {
177 lx, ly, state.paint_brush_radius,
179 [&](
int bx,
int by) {
181 if (!room.HasCollisionOverride(bx, by)) {
185 room.ClearCollisionTile(bx, by);
189 if (room.HasCollisionOverride(bx, by) &&
190 room.GetCollisionTile(bx, by) ==
191 state.paint_collision_value) {
195 room.SetCollisionTile(bx, by, state.paint_collision_value);
205 state.paint_last_tile_x = room_x;
206 state.paint_last_tile_y = room_y;
211void DungeonObjectInteraction::UpdateWaterFillPainting(
212 const ImVec2& canvas_mouse_pos) {
214 mode_manager_.SetMode(InteractionMode::Select);
218 const ImGuiIO& io = ImGui::GetIO();
219 const bool erase = io.KeyAlt;
221 auto [room_x, room_y] =
222 CanvasToRoomCoordinates(
static_cast<int>(canvas_mouse_pos.x),
223 static_cast<int>(canvas_mouse_pos.y));
224 if (rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
225 auto& room = (*rooms_)[current_room_id_];
226 auto& state = mode_manager_.GetModeState();
229 if (room_x >= 0 && room_x < 64 && room_y >= 0 && room_y < 64) {
230 const bool new_val = !erase;
232 if (!state.is_painting) {
233 state.is_painting =
true;
234 state.paint_mutation_started =
false;
235 state.paint_last_tile_x = room_x;
236 state.paint_last_tile_y = room_y;
240 (state.paint_last_tile_x >= 0) ? state.paint_last_tile_x : room_x;
242 (state.paint_last_tile_y >= 0) ? state.paint_last_tile_y : room_y;
244 bool changed =
false;
245 auto ensure_mutation = [&]() {
246 if (!state.paint_mutation_started) {
247 interaction_context_.NotifyMutation(MutationDomain::kWaterFill);
248 state.paint_mutation_started =
true;
252 paint_util::ForEachPointOnLine(
253 x0, y0, room_x, room_y, [&](
int lx,
int ly) {
254 paint_util::ForEachPointInSquareBrush(
255 lx, ly, state.paint_brush_radius,
257 [&](
int bx,
int by) {
258 if (room.GetWaterFillTile(bx, by) == new_val) {
262 room.SetWaterFillTile(bx, by, new_val);
268 interaction_context_.NotifyInvalidateCache(MutationDomain::kWaterFill);
271 state.paint_last_tile_x = room_x;
272 state.paint_last_tile_y = room_y;
277void DungeonObjectInteraction::HandleObjectSelectionStart(
278 const ImVec2& canvas_mouse_pos) {
279 const bool has_object_selection = selection_.HasSelection();
280 const bool has_entity_selection = HasEntitySelection();
281 if (!has_object_selection && !has_entity_selection) {
285 mode_manager_.SetMode(InteractionMode::DraggingObjects);
286 if (has_object_selection) {
287 entity_coordinator_.tile_handler().InitDrag(canvas_mouse_pos);
289 if (has_entity_selection) {
290 entity_coordinator_.BeginSelectionDrag(canvas_mouse_pos);
294void DungeonObjectInteraction::HandleEmptySpaceClick(
295 const ImVec2& canvas_mouse_pos) {
296 const ImGuiIO& io = ImGui::GetIO();
297 const bool additive = io.KeyShift || io.KeyCtrl || io.KeySuper;
298 const bool had_selection =
299 selection_.HasSelection() || entity_coordinator_.HasEntitySelection();
304 ClearEntitySelection();
305 selection_.ClearSelection();
308 if (!had_selection) {
309 entity_coordinator_.tile_handler().BeginMarqueeSelection(canvas_mouse_pos);
313void DungeonObjectInteraction::FinishPaintStroke() {
316 const auto mode = mode_manager_.GetMode();
317 if (mode != InteractionMode::PaintCollision &&
318 mode != InteractionMode::PaintWaterFill) {
321 auto& state = mode_manager_.GetModeState();
322 const bool had_mutation = state.paint_mutation_started;
323 state.is_painting =
false;
324 state.paint_mutation_started =
false;
325 state.paint_last_tile_x = -1;
326 state.paint_last_tile_y = -1;
328 interaction_context_.NotifyInvalidateCache(
329 (mode == InteractionMode::PaintCollision)
330 ? MutationDomain::kCustomCollision
331 : MutationDomain::kWaterFill);
335void DungeonObjectInteraction::HandleMouseRelease() {
338 if (mode_manager_.GetMode() == InteractionMode::DraggingObjects) {
339 mode_manager_.SetMode(InteractionMode::Select);
341 entity_coordinator_.HandleRelease();
346bool DungeonObjectInteraction::HandleKeyboardNudge() {
347 if (!rooms_ || current_room_id_ < 0 ||
348 current_room_id_ >=
static_cast<int>(rooms_->size())) {
352 const ImGuiIO& io = ImGui::GetIO();
353 if (ImGui::IsAnyItemActive() || io.KeyCtrl || io.KeySuper || io.KeyAlt ||
354 ImGui::IsMouseDown(ImGuiMouseButton_Left) ||
355 entity_coordinator_.IsPlacementActive()) {
359 const auto mode = mode_manager_.GetMode();
360 if (mode == InteractionMode::DraggingObjects ||
361 mode == InteractionMode::PaintCollision ||
362 mode == InteractionMode::PaintWaterFill) {
368 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow,
true)) {
370 }
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow,
true)) {
373 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow,
true)) {
375 }
else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow,
true)) {
379 if (delta_x == 0 && delta_y == 0) {
383 return NudgeSelected(delta_x, delta_y);
386bool DungeonObjectInteraction::NudgeSelected(
int delta_x,
int delta_y) {
387 return entity_coordinator_.NudgeSelected(delta_x, delta_y);
390void DungeonObjectInteraction::CheckForObjectSelection() {
392 const ImGuiIO& io = ImGui::GetIO();
393 const ImVec2 mouse_pos = GetCanvasTransform().ScreenToRoomPixels(io.MousePos);
394 const bool mouse_left_released =
395 ImGui::IsMouseReleased(ImGuiMouseButton_Left);
397 if (mouse_left_released && selection_.IsRectangleSelectionActive()) {
398 selection_.UpdateRectangleSelection(
static_cast<int>(mouse_pos.x),
399 static_cast<int>(mouse_pos.y));
400 constexpr int kMinRectPixels = 6;
401 if (!io.KeyAlt && selection_.IsRectangleLargeEnough(kMinRectPixels)) {
402 entity_coordinator_.SelectEntitiesInRect(
403 selection_.GetRectangleSelectionBounds(),
409 entity_coordinator_.tile_handler().HandleMarqueeSelection(
411 ImGui::IsMouseDown(ImGuiMouseButton_Left),
414 io.KeyCtrl || io.KeySuper,
418void DungeonObjectInteraction::DrawSelectionHighlights() {
419 if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
422 auto& room = (*rooms_)[current_room_id_];
423 const auto& objects = room.GetTileObjects();
426 selection_.DrawSelectionHighlights(
429 return std::make_tuple(result.offset_x_tiles * 8,
430 result.offset_y_tiles * 8, result.width_pixels(),
431 result.height_pixels());
436 if (entity_coordinator_.HasEntitySelection()) {
440 if (canvas_->IsMouseHovering()) {
442 ImGuiIO& io = ImGui::GetIO();
443 const auto [cursor_x, cursor_y] =
444 GetCanvasTransform().ScreenToRoomPixelCoordinates(io.MousePos);
445 auto entity_at_cursor =
446 entity_coordinator_.GetEntityAtPosition(cursor_x, cursor_y);
447 if (entity_at_cursor.has_value()) {
449 DrawHoverHighlight(objects);
453 auto hovered_index = entity_coordinator_.tile_handler().GetEntityAtPosition(
455 if (hovered_index.has_value() && *hovered_index < objects.size()) {
456 const auto&
object = objects[*hovered_index];
459 const int layer =
object.GetLayerValue();
462 const char* subtype_names[] = {
"Unknown",
"Type 1",
"Type 2",
"Type 3"};
463 const char* subtype_name =
464 (subtype >= 0 && subtype <= 3) ? subtype_names[subtype] :
"Unknown";
468 tooltip += object_name;
469 tooltip +=
" (" + std::string(subtype_name) +
")";
471 tooltip +=
"ID: 0x" + absl::StrFormat(
"%03X",
object.id_);
473 static constexpr const char* kStreamNames[] = {
"Primary",
"BG2 overlay",
475 const char* stream_name =
476 (layer >= 0 && layer < 3) ? kStreamNames[layer] :
"Unknown";
477 tooltip +=
" | Object stream: " + std::string(stream_name);
479 const char* layer_name =
480 layer == 0 ?
"Upper layer (BG1)" :
"Lower layer (BG2)";
481 tooltip +=
" | Special layer: " + std::string(layer_name);
483 tooltip +=
" | Pos: (" + std::to_string(
object.x_) +
", " +
484 std::to_string(
object.y_) +
")";
487 tooltip += absl::StrFormat(
488 "\nSize: %d x %d tiles",
492 tooltip += absl::StrFormat(
"\nSize: 0x%02X",
object.size_);
495 if (selection_.IsObjectSelected(*hovered_index)) {
498 "\n" ICON_MD_MOUSE " Wheel: grow/shrink | Shift+wheel: width";
507 ImGui::SetTooltip(
"%s", tooltip.c_str());
512 DrawHoverHighlight(objects);
515void DungeonObjectInteraction::DrawHoverHighlight(
516 const std::vector<zelda3::RoomObject>& objects) {
517 if (!canvas_->IsMouseHovering())
521 if (entity_coordinator_.HasEntitySelection())
526 ImGuiIO& io = ImGui::GetIO();
528 const auto [cursor_canvas_x, cursor_canvas_y] =
530 auto entity_at_cursor =
531 entity_coordinator_.GetEntityAtPosition(cursor_canvas_x, cursor_canvas_y);
532 if (entity_at_cursor.has_value()) {
536 auto hovered_index = entity_coordinator_.tile_handler().GetEntityAtPosition(
537 cursor_canvas_x, cursor_canvas_y);
538 if (!hovered_index.has_value() || *hovered_index >= objects.size()) {
541 const auto&
object = objects[*hovered_index];
544 if (selection_.IsObjectSelected(*hovered_index)) {
548 const auto& theme = AgentUI::GetTheme();
549 ImDrawList* draw_list = ImGui::GetWindowDrawList();
551 auto [sel_x_px, sel_y_px, pixel_width, pixel_height] =
555 ImVec2(
static_cast<float>(sel_x_px),
static_cast<float>(sel_y_px)));
557 static_cast<float>(pixel_width),
static_cast<float>(pixel_height)));
558 ImVec2 obj_end(obj_start.x + obj_size.x, obj_start.y + obj_size.y);
561 constexpr float margin = 2.0f;
562 obj_start.x -= margin;
563 obj_start.y -= margin;
568 ImVec4 hover_fill = theme.selection_hover;
569 hover_fill.w *= 0.5f;
571 ImVec4 hover_border = theme.selection_hover;
574 draw_list->AddRectFilled(obj_start, obj_end, ImGui::GetColorU32(hover_fill));
577 draw_list->AddRect(obj_start, obj_end, ImGui::GetColorU32(hover_border), 0.0f,
581void DungeonObjectInteraction::PlaceObjectAtPosition(
int room_x,
int room_y) {
582 auto& tile_handler = entity_coordinator_.tile_handler();
583 if (!tile_handler.IsPlacementActive()) {
586 auto object = tile_handler.GetPreviewObject().CopyForNewPlacement();
587 object.x_ = std::clamp(room_x, 0, 63);
588 object.y_ = std::clamp(room_y, 0, 63);
589 if (!tile_handler.PlaceObjectAt(interaction_context_.current_room_id,
object,
593 if (object_placed_callback_) {
594 object_placed_callback_(
object);
598void DungeonObjectInteraction::CompleteObjectPlacement(
600 entity_coordinator_.ClearEntitySelection();
601 if (!entity_coordinator_.tile_handler().IsPlacementActive() &&
602 mode_manager_.GetMode() == InteractionMode::PlaceObject) {
603 mode_manager_.SetMode(InteractionMode::Select);
607bool DungeonObjectInteraction::BeginPlacementFromSelection() {
608 const auto indices = selection_.GetSelectedIndices();
609 const auto* room = interaction_context_.GetCurrentRoomConst();
610 if (!room || indices.size() != 1 || HasEntitySelection() ||
611 indices.front() >= room->GetTileObjects().size()) {
615 room->GetTileObjects()[indices.front()].CopyForNewPlacement();
616 SetPreviewObject(
object,
true);
620std::pair<int, int> DungeonObjectInteraction::RoomToCanvasCoordinates(
621 int room_x,
int room_y)
const {
623 return {room_x * 8, room_y * 8};
626std::pair<int, int> DungeonObjectInteraction::CanvasToRoomCoordinates(
627 int canvas_x,
int canvas_y)
const {
629 return {canvas_x / 8, canvas_y / 8};
632bool DungeonObjectInteraction::IsWithinCanvasBounds(
int canvas_x,
int canvas_y,
634 return dungeon_coords::IsWithinBounds(canvas_x, canvas_y, margin);
643 if (rooms != rooms_ || room_id != current_room_id_) {
645 const auto mode = mode_manager_.GetMode();
646 if (mode == InteractionMode::DraggingObjects ||
647 mode == InteractionMode::DraggingEntity ||
648 mode == InteractionMode::RectangleSelect) {
649 mode_manager_.SetMode(InteractionMode::Select);
654 entity_coordinator_.tile_handler().HandleRelease();
655 selection_.CancelRectangleSelection();
656 entity_coordinator_.ClearAllEntitySelections();
660 current_room_id_ = room_id;
661 interaction_context_.rooms = rooms;
662 interaction_context_.current_room_id = room_id;
663 interaction_context_.selection = &selection_;
664 entity_coordinator_.SetContext(&interaction_context_);
667void DungeonObjectInteraction::SetPreviewObject(
669 if (loaded &&
object.id_ >= 0) {
672 entity_coordinator_.CancelCurrentMode();
675 mode_manager_.SetMode(InteractionMode::PlaceObject);
679 auto& tile_handler = entity_coordinator_.tile_handler();
680 tile_handler.SetPreviewObject(
object);
681 if (!tile_handler.IsPlacementActive()) {
682 tile_handler.BeginPlacement();
686 if (mode_manager_.GetMode() == InteractionMode::PlaceObject) {
692void DungeonObjectInteraction::ClearSelection() {
693 selection_.ClearSelection();
694 if (mode_manager_.GetMode() == InteractionMode::DraggingObjects) {
695 mode_manager_.SetMode(InteractionMode::Select);
699absl::Status DungeonObjectInteraction::HandleDeleteSelected() {
701 request.
kind = DungeonSelectionEditKind::kDelete;
702 request.
objects = selection_.GetSelectedIndices();
703 request.
entities = entity_coordinator_.SelectedEntitiesForEdit();
704 return entity_coordinator_.CommitSelectionEdit(request);
707void DungeonObjectInteraction::HandleDeleteAllObjects() {
708 if (entity_coordinator_.tile_handler().DeleteAllObjects(current_room_id_)) {
709 selection_.ClearSelection();
713absl::Status DungeonObjectInteraction::HandleCopySelected() {
714 const auto* room = interaction_context_.GetCurrentRoomConst();
716 return entity_coordinator_.ReportSelectionEditStatus(
717 absl::FailedPreconditionError(
"No room is available to copy"));
719 const auto objects = selection_.GetSelectedIndices();
720 const auto entities = entity_coordinator_.SelectedEntitiesForEdit();
721 if (objects.empty() && entities.empty())
722 return absl::OkStatus();
725 return entity_coordinator_.ReportSelectionEditStatus(copied.status());
728 clipboard() = std::move(*copied);
729 return entity_coordinator_.ReportSelectionEditStatus(absl::OkStatus());
732absl::Status DungeonObjectInteraction::HandleDuplicateSelected() {
734 request.
kind = DungeonSelectionEditKind::kDuplicate;
735 request.
objects = selection_.GetSelectedIndices();
736 request.
entities = entity_coordinator_.SelectedEntitiesForEdit();
737 const bool includes_door = std::any_of(
739 [](
const auto entity) { return entity.type == EntityType::Door; });
743 includes_door ? 0 : entity_coordinator_.SelectionMoveStepPixels();
746 return entity_coordinator_.CommitSelectionEdit(request);
749absl::Status DungeonObjectInteraction::HandlePasteAt(
int pixel_x,
int pixel_y) {
750 if (!HasClipboardData())
751 return absl::OkStatus();
752 if (!dungeon_coords::IsWithinBounds(pixel_x, pixel_y)) {
753 return entity_coordinator_.ReportSelectionEditStatus(
754 absl::OutOfRangeError(
"Paste anchor is outside the room"));
757 request.
kind = DungeonSelectionEditKind::kPaste;
758 request.
objects = selection_.GetSelectedIndices();
759 request.
entities = entity_coordinator_.SelectedEntitiesForEdit();
760 auto& copied = clipboard();
764 return entity_coordinator_.CommitSelectionEdit(request);
767absl::Status DungeonObjectInteraction::HandlePasteObjectsAt(
int pixel_x,
769 if (!HasClipboardData())
770 return absl::OkStatus();
771 if (!dungeon_coords::IsWithinBounds(pixel_x, pixel_y)) {
772 return HandlePasteAt(pixel_x, pixel_y);
774 const auto& copied = clipboard();
775 const int step = !copied.sprites.empty() || !copied.items.empty()
776 ? dungeon_coords::kSpriteTileSize
777 : dungeon_coords::kTileSize;
780 return HandlePasteAt(
781 copied.origin_pixel_x + (pixel_x - copied.origin_pixel_x) / step * step,
782 copied.origin_pixel_y + (pixel_y - copied.origin_pixel_y) / step * step);
785absl::Status DungeonObjectInteraction::HandlePasteObjects() {
786 if (!HasClipboardData())
787 return absl::OkStatus();
788 if (canvas_ && ImGui::GetCurrentContext()) {
789 const auto [mouse_x, mouse_y] =
790 GetCanvasTransform().ScreenToRoomPixelCoordinates(
791 ImGui::GetIO().MousePos);
792 if (dungeon_coords::IsWithinBounds(mouse_x, mouse_y)) {
793 return HandlePasteObjectsAt(mouse_x, mouse_y);
798 const auto& copied = clipboard();
799 const int step = !copied.sprites.empty() || !copied.items.empty()
800 ? dungeon_coords::kSpriteTileSize
801 : dungeon_coords::kTileSize;
802 const int fallback_offset = copied.doors.empty() ? step : 0;
803 return HandlePasteAt(copied.origin_pixel_x + fallback_offset,
804 copied.origin_pixel_y + fallback_offset);
807void DungeonObjectInteraction::DrawGhostPreview() {
808 entity_coordinator_.DrawGhostPreviews();
811void DungeonObjectInteraction::HandleScrollWheelResize() {
812 const ImGuiIO& io = ImGui::GetIO();
813 entity_coordinator_.HandleMouseWheel(ObjectResizeWheelDelta(io));
816bool DungeonObjectInteraction::SetObjectId(
size_t index, int16_t
id) {
817 entity_coordinator_.tile_handler().UpdateObjectsId(current_room_id_, {index},
822bool DungeonObjectInteraction::SetObjectSize(
size_t index, uint8_t size) {
823 entity_coordinator_.tile_handler().UpdateObjectsSize(current_room_id_,
828bool DungeonObjectInteraction::SetObjectLayer(
830 return entity_coordinator_.tile_handler().UpdateObjectsLayer(
831 current_room_id_, {index},
static_cast<int>(layer));
834std::pair<int, int> DungeonObjectInteraction::CalculateObjectBounds(
839bool DungeonObjectInteraction::CanAssignSelectedObjectsToLayer(
840 int target_layer)
const {
841 if (!rooms_ || current_room_id_ < 0 ||
842 current_room_id_ >=
static_cast<int>(rooms_->size()) ||
843 target_layer < 0 || target_layer > 2) {
847 const auto selected = selection_.GetSelectedIndices();
848 if (selected.empty()) {
852 const auto& objects = (*rooms_)[current_room_id_].GetTileObjects();
853 for (
const size_t index : selected) {
854 if (index >= objects.size() ||
862bool DungeonObjectInteraction::SendSelectedToLayer(
int target_layer) {
863 if (!CanAssignSelectedObjectsToLayer(target_layer)) {
866 return entity_coordinator_.tile_handler().UpdateObjectsLayer(
867 current_room_id_, selection_.GetSelectedIndices(), target_layer);
870void DungeonObjectInteraction::SendSelectedToFront() {
871 entity_coordinator_.tile_handler().SendToFront(
872 current_room_id_, selection_.GetSelectedIndices());
875void DungeonObjectInteraction::SendSelectedToBack() {
876 entity_coordinator_.tile_handler().SendToBack(
877 current_room_id_, selection_.GetSelectedIndices());
880void DungeonObjectInteraction::BringSelectedForward() {
881 entity_coordinator_.tile_handler().MoveForward(
882 current_room_id_, selection_.GetSelectedIndices());
885void DungeonObjectInteraction::SendSelectedBackward() {
886 entity_coordinator_.tile_handler().MoveBackward(
887 current_room_id_, selection_.GetSelectedIndices());
890void DungeonObjectInteraction::HandleLayerKeyboardShortcuts() {
892 if (!selection_.HasSelection())
896 if (ImGui::IsAnyItemActive())
901 const auto& io = ImGui::GetIO();
902 if (io.KeyMods == ImGuiMod_None) {
903 if (ImGui::IsKeyPressed(ImGuiKey_1)) {
904 SendSelectedToLayer(0);
905 }
else if (ImGui::IsKeyPressed(ImGuiKey_2)) {
906 SendSelectedToLayer(1);
907 }
else if (ImGui::IsKeyPressed(ImGuiKey_3)) {
908 SendSelectedToLayer(2);
915 if (io.KeyCtrl && io.KeyShift) {
916 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
917 SendSelectedToFront();
918 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
919 SendSelectedToBack();
921 }
else if (io.KeyCtrl) {
922 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
923 BringSelectedForward();
924 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
925 SendSelectedBackward();
934void DungeonObjectInteraction::SetDoorPlacementMode(
bool enabled,
939 mode_manager_.SetMode(InteractionMode::PlaceDoor);
940 entity_coordinator_.door_handler().SetDoorType(type);
941 entity_coordinator_.door_handler().BeginPlacement();
943 entity_coordinator_.door_handler().CancelPlacement();
944 if (mode_manager_.GetMode() == InteractionMode::PlaceDoor)
945 mode_manager_.SetMode(InteractionMode::Select);
953void DungeonObjectInteraction::SetSpritePlacementMode(
bool enabled,
958 mode_manager_.SetMode(InteractionMode::PlaceSprite);
959 entity_coordinator_.sprite_handler().SetSpriteId(sprite_id);
960 entity_coordinator_.sprite_handler().BeginPlacement();
962 entity_coordinator_.sprite_handler().CancelPlacement();
963 if (mode_manager_.GetMode() == InteractionMode::PlaceSprite)
964 mode_manager_.SetMode(InteractionMode::Select);
972void DungeonObjectInteraction::SetItemPlacementMode(
bool enabled,
977 mode_manager_.SetMode(InteractionMode::PlaceItem);
978 entity_coordinator_.item_handler().SetItemId(item_id);
979 entity_coordinator_.item_handler().BeginPlacement();
981 entity_coordinator_.item_handler().CancelPlacement();
982 if (mode_manager_.GetMode() == InteractionMode::PlaceItem)
983 mode_manager_.SetMode(InteractionMode::Select);
991void DungeonObjectInteraction::SelectEntity(
EntityType type,
size_t index) {
992 selection_.ClearSelection();
993 entity_coordinator_.SelectEntity(type, index);
996void DungeonObjectInteraction::ClearEntitySelection() {
997 entity_coordinator_.ClearEntitySelection();
1000void DungeonObjectInteraction::CancelPlacement() {
1001 entity_coordinator_.CancelPlacement();
1002 if (mode_manager_.IsPlacementActive()) {
1003 mode_manager_.SetMode(InteractionMode::Select);
1007void DungeonObjectInteraction::DrawEntitySelectionHighlights() {
1008 entity_coordinator_.DrawSelectionHighlights();
1009 entity_coordinator_.DrawPostPlacementOverlays();
1012void 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.
bool paint_collision_erase