using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; using Unheimlich.Gameplay.DetectiveBoard; using System.Collections.Generic; using UnityEngine.EventSystems; namespace Unheimlich.UI.DetectiveBoard { /// /// Handles mouse interaction for the detective board World Space canvas. /// Manages hover, selection, dragging, and click forwarding for Post-It nodes. /// Interactions are only active when the board is in inspection mode. /// public class DetectiveBoardInteraction : MonoBehaviour { [Header("References")] [SerializeField] private Camera _camera; [SerializeField] private Canvas _boardCanvas; [SerializeField] private RectTransform _boardArea; [SerializeField] private LayerMask _boardLayer; [SerializeField] private DetectiveBoardController _boardController; [Header("Settings")] [SerializeField] private float _minInteractionDistance = 0.1f; [SerializeField] private float _maxInteractionDistance = 10f; private DetectiveBoardCanvas _canvasManager; private PostItNode _hoveredNode; private PostItNode _selectedNode; private bool _isDragging; private Plane _canvasPlane; private Vector3 _dragOffset; private void Start() { if (_camera == null) _camera = Camera.main; if (_boardController == null) _boardController = FindFirstObjectByType(); if (_canvasManager == null) _canvasManager = GetComponent(); UpdateCanvasPlane(); } private void Update() { if (!IsInBoardInteractionMode()) { ClearInteractionState(); return; } UpdateCanvasPlane(); if (_isDragging) { HandleDrag(); } else { HandleHover(); } HandleClick(); } private bool IsInBoardInteractionMode() { return _boardController != null && _boardController.IsBoardActive; } private void ClearInteractionState() { if (_hoveredNode != null) { _hoveredNode.SetHovered(false); _hoveredNode = null; } if (_selectedNode != null && _isDragging) { _selectedNode.SetSelected(false); _selectedNode = null; _isDragging = false; } } private void UpdateCanvasPlane() { if (_boardCanvas != null) { Vector3 planeNormal = -_boardCanvas.transform.forward; Vector3 planePosition = _boardCanvas.transform.position; _canvasPlane = new Plane(planeNormal, planePosition); } } private void HandleHover() { PostItNode hitNode = RaycastForNode(); if (hitNode != _hoveredNode) { if (_hoveredNode != null) { _hoveredNode.SetHovered(false); } _hoveredNode = hitNode; if (_hoveredNode != null) { _hoveredNode.SetHovered(true); } } } private void HandleClick() { if (Mouse.current == null) return; PostItNode hitNode = RaycastForNode(); if (Mouse.current.leftButton.wasPressedThisFrame) { HandleLeftClick(hitNode); } else if (Mouse.current.rightButton.wasPressedThisFrame && hitNode != null && _canvasManager != null) { _canvasManager.OnNodeRightClicked(hitNode); } } private void HandleLeftClick(PostItNode hitNode) { if (_canvasManager != null && _canvasManager.IsConnectMode) { if (hitNode != null) { _canvasManager.OnNodeClicked(hitNode); } return; } if (_isDragging) { _isDragging = false; if (_selectedNode != null && hitNode == _selectedNode) { return; } if (_selectedNode != null) { _selectedNode.SetSelected(false); } if (hitNode != null) { _selectedNode = hitNode; _selectedNode.SetSelected(true); _isDragging = true; CalculateDragOffset(); } else { _selectedNode = null; } } else { if (hitNode != null) { if (_selectedNode != null && _selectedNode != hitNode) { _selectedNode.SetSelected(false); } _selectedNode = hitNode; _selectedNode.SetSelected(true); _isDragging = true; CalculateDragOffset(); } else { if (_selectedNode != null) { _selectedNode.SetSelected(false); _selectedNode = null; } } } } private void HandleDrag() { if (_selectedNode == null || _camera == null) return; Vector2 mousePos = Mouse.current.position.ReadValue(); Ray ray = _camera.ScreenPointToRay(new Vector3(mousePos.x, mousePos.y, 0)); if (_canvasPlane.Raycast(ray, out float enter)) { Vector3 worldHitPoint = ray.GetPoint(enter); Vector3 newWorldPos = worldHitPoint + _dragOffset; Vector3 localPos = _boardCanvas.transform.InverseTransformPoint(newWorldPos); Vector2 clampedPos = ClampPositionToBoard(new Vector2(localPos.x, localPos.y)); _selectedNode.SetPosition(clampedPos); if (_canvasManager != null) { _canvasManager.UpdateConnectionsForNode(_selectedNode); } } } private Vector2 ClampPositionToBoard(Vector2 position) { if (_boardArea == null) return position; Rect boardRect = _boardArea.rect; float halfWidth = boardRect.width * 0.5f; float halfHeight = boardRect.height * 0.5f; float clampedX = Mathf.Clamp(position.x, -halfWidth, halfWidth); float clampedY = Mathf.Clamp(position.y, -halfHeight, halfHeight); return new Vector2(clampedX, clampedY); } private void CalculateDragOffset() { if (_selectedNode == null || _camera == null) return; Vector2 mousePos = Mouse.current.position.ReadValue(); Ray ray = _camera.ScreenPointToRay(new Vector3(mousePos.x, mousePos.y, 0)); if (_canvasPlane.Raycast(ray, out float enter)) { Vector3 worldHitPoint = ray.GetPoint(enter); Vector3 currentWorldPos = _selectedNode.transform.position; _dragOffset = currentWorldPos - worldHitPoint; } } private PostItNode RaycastForNode() { if (_camera == null || Mouse.current == null) return null; Vector2 mousePos = Mouse.current.position.ReadValue(); GraphicRaycaster raycaster = _boardCanvas?.GetComponent(); if (raycaster != null && EventSystem.current != null) { var pointerEventData = new PointerEventData(EventSystem.current) { position = mousePos }; var results = new List(); raycaster.Raycast(pointerEventData, results); foreach (var result in results) { PostItNode node = result.gameObject.GetComponentInParent(); if (node != null) return node; } } Ray ray = _camera.ScreenPointToRay(new Vector3(mousePos.x, mousePos.y, 0)); if (Physics.Raycast(ray, out RaycastHit hit, _maxInteractionDistance, _boardLayer)) { float distance = hit.distance; if (distance >= _minInteractionDistance) { return hit.collider.GetComponentInParent(); } } return null; } } }