Files
2026-06-22 16:18:34 +02:00

297 lines
9.4 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using DG.Tweening;
using UnityEngine.InputSystem;
namespace Ashwild.Inventory
{
public class SlotContextMenu : MonoBehaviour
{
[Header("References")]
[SerializeField] private RectTransform panelRect;
[SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private Transform buttonContainer;
[SerializeField] private GameObject buttonPrefab;
[Header("Settings")]
[SerializeField] private Vector2 offset = new Vector2(10f, -10f);
[Header("Animation")]
[SerializeField] private float showDuration = 0.2f;
[SerializeField] private float hideDuration = 0.15f;
[SerializeField] private Ease showEase = Ease.OutBack;
[SerializeField] private Ease hideEase = Ease.InQuad;
private int currentSlotIndex = -1;
private readonly List<GameObject> activeButtons = new List<GameObject>();
private Canvas rootCanvas;
private bool isVisible;
private Tweener scaleTween;
private Tweener fadeTween;
private void Awake()
{
rootCanvas = GetComponentInParent<Canvas>();
SetHidden();
}
private void SetHidden()
{
canvasGroup.gameObject.SetActive(false);
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
panelRect.localScale = Vector3.zero;
}
private void OnDestroy()
{
scaleTween?.Kill();
fadeTween?.Kill();
}
private void Update()
{
if (!isVisible) return;
Mouse mouse = Mouse.current;
if (mouse == null) return;
if (mouse.leftButton.wasPressedThisFrame || mouse.rightButton.wasPressedThisFrame)
{
if (!IsPointerOverMenu())
Hide();
}
}
public void Show(int slotIndex)
{
PlayerInventory inv = PlayerInventory.Instance;
InventorySlot slot = inv.GetSlot(slotIndex);
if (slot == null || slot.IsEmpty)
return;
// Kill any running animation and clear previous buttons
scaleTween?.Kill();
fadeTween?.Kill();
ClearButtons();
currentSlotIndex = slotIndex;
bool isHotbar = slotIndex < inv.HotbarSize;
bool canSplit = slot.Quantity > 1;
if (isHotbar)
AddButton("Move to Inventory", OnMoveToInventory);
else
AddButton("Equip", OnEquipToHotbar);
if (slot.ItemData.ItemType == ItemType.Consumable)
AddButton("Use", OnUseItem);
AddButton("Drop", OnDrop);
if (canSplit)
AddButton("Split", OnSplit);
// Activate and position
canvasGroup.gameObject.SetActive(true);
Vector2 mousePos = Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero;
PositionMenu(mousePos);
// Animate in
isVisible = true;
panelRect.localScale = Vector3.one * 0.5f;
canvasGroup.alpha = 0f;
scaleTween?.Kill();
fadeTween?.Kill();
scaleTween = panelRect.DOScale(1f, showDuration).SetEase(showEase).SetUpdate(true);
fadeTween = canvasGroup.DOFade(1f, showDuration).SetUpdate(true).OnComplete(() =>
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
});
}
public void Hide()
{
if (!isVisible) return;
isVisible = false;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
scaleTween?.Kill();
fadeTween?.Kill();
scaleTween = panelRect.DOScale(0f, hideDuration).SetEase(hideEase).SetUpdate(true);
fadeTween = canvasGroup.DOFade(0f, hideDuration).SetUpdate(true).OnComplete(() =>
{
canvasGroup.gameObject.SetActive(false);
ClearButtons();
currentSlotIndex = -1;
});
}
private void PositionMenu(Vector2 screenPosition)
{
if (rootCanvas == null)
rootCanvas = GetComponentInParent<Canvas>();
RectTransform canvasRect = rootCanvas.transform as RectTransform;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvasRect, screenPosition, rootCanvas.worldCamera, out Vector2 anchoredPos);
anchoredPos += offset;
// Clamp to stay within canvas bounds
Vector2 menuSize = panelRect.sizeDelta;
Vector2 canvasSize = canvasRect.sizeDelta;
float halfW = canvasSize.x * 0.5f;
float halfH = canvasSize.y * 0.5f;
if (anchoredPos.x + menuSize.x > halfW)
anchoredPos.x = halfW - menuSize.x;
if (anchoredPos.y - menuSize.y < -halfH)
anchoredPos.y = -halfH + menuSize.y;
panelRect.anchoredPosition = anchoredPos;
}
private void AddButton(string label, Action onClick)
{
GameObject btnGO = Instantiate(buttonPrefab, buttonContainer);
btnGO.SetActive(true);
TextMeshProUGUI text = btnGO.GetComponentInChildren<TextMeshProUGUI>();
if (text != null)
text.text = label;
Button btn = btnGO.GetComponent<Button>();
if (btn != null)
btn.onClick.AddListener(() =>
{
onClick?.Invoke();
Hide();
});
activeButtons.Add(btnGO);
}
private void ClearButtons()
{
foreach (GameObject btn in activeButtons)
Destroy(btn);
activeButtons.Clear();
}
private bool IsPointerOverMenu()
{
Vector2 mousePos = Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero;
PointerEventData eventData = new PointerEventData(EventSystem.current)
{
position = mousePos
};
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventData, results);
foreach (RaycastResult result in results)
{
if (result.gameObject.transform.IsChildOf(transform))
return true;
}
return false;
}
// --- Actions ---
private void OnEquipToHotbar()
{
if (currentSlotIndex < 0) return;
PlayerInventory inv = PlayerInventory.Instance;
int targetSlot = inv.SelectedHotbarIndex;
for (int i = 0; i < inv.HotbarSize; i++)
{
if (inv.GetSlot(i).IsEmpty)
{
targetSlot = i;
break;
}
}
InventoryUI.OnSwapRequested(currentSlotIndex, targetSlot);
}
private void OnMoveToInventory()
{
if (currentSlotIndex < 0) return;
PlayerInventory inv = PlayerInventory.Instance;
for (int i = inv.HotbarSize; i < inv.InventorySize; i++)
{
if (inv.GetSlot(i).IsEmpty)
{
InventoryUI.OnSwapRequested(currentSlotIndex, i);
return;
}
}
InventorySlot sourceSlot = inv.GetSlot(currentSlotIndex);
for (int i = inv.HotbarSize; i < inv.InventorySize; i++)
{
InventorySlot slot = inv.GetSlot(i);
if (!slot.IsEmpty && slot.ItemData == sourceSlot.ItemData && slot.CanAccept(sourceSlot.ItemData))
{
InventoryUI.OnSwapRequested(currentSlotIndex, i);
return;
}
}
}
private void OnUseItem()
{
if (currentSlotIndex < 0) return;
PlayerInventory.Instance.UseItem(currentSlotIndex);
}
private void OnDrop()
{
if (currentSlotIndex < 0) return;
PlayerInventory inv = PlayerInventory.Instance;
InventorySlot slot = inv.GetSlot(currentSlotIndex);
if (!slot.IsEmpty)
inv.DropItem(currentSlotIndex, slot.Quantity);
}
private void OnSplit()
{
if (currentSlotIndex < 0) return;
PlayerInventory inv = PlayerInventory.Instance;
InventorySlot slot = inv.GetSlot(currentSlotIndex);
if (slot.IsEmpty || slot.Quantity <= 1) return;
int halfQty = slot.Quantity / 2;
int remaining = slot.Quantity - halfQty;
for (int i = 0; i < inv.InventorySize; i++)
{
if (inv.GetSlot(i).IsEmpty)
{
slot.Set(slot.ItemData, remaining);
inv.GetSlot(i).Set(slot.ItemData, halfQty);
inv.onSlotChanged?.Invoke(currentSlotIndex);
inv.onSlotChanged?.Invoke(i);
return;
}
}
}
}
}