using System.Collections.Generic;
using Jovian.PopupSystem.UI;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace Jovian.PopupSystem {
///
/// Behavior class for a popup view. Manages the generic grow-only element cache,
/// positioning, visibility, and layout. Operates on a
/// MonoBehaviour for scene references.
///
public sealed class PopupView {
readonly PopupReference reference;
readonly PopupSettings settings;
// Generic element cache: keyed by element type
readonly Dictionary> elementCache = new();
readonly Dictionary elementIndex = new();
// Positioning state
PopupPositionMode positionMode;
RectTransform anchorTarget;
AnchorSide anchorSide;
Vector2 followOffset;
float screenEdgePadding;
float maxWidth;
bool isVisible;
bool layoutDirty;
Canvas rootCanvas;
Camera canvasCamera;
/// The underlying MonoBehaviour reference holder.
public PopupReference Reference => reference;
/// The CanvasGroup for animation control.
public CanvasGroup CanvasGroup => reference.CanvasGroup;
/// The content RectTransform where elements are parented.
public RectTransform Content => reference.Content;
/// The root Transform of the popup GameObject.
public Transform Transform => reference.transform;
/// Whether the popup is currently visible.
public bool IsVisible => isVisible;
///
/// Creates a new popup view wrapping the given reference and settings.
///
public PopupView(PopupReference reference, PopupSettings settings) {
this.reference = reference;
this.settings = settings;
}
// --- Element cache (generic, grow-only) ---
///
/// Returns the next available cached element for the given type, or instantiates
/// a new one from the settings prefab registry. The element is activated and placed
/// at the end of the content layout.
///
public GameObject GetElement(PopupElementType elementType) {
if(!elementCache.TryGetValue(elementType, out var cache)) {
cache = new List();
elementCache[elementType] = cache;
elementIndex[elementType] = 0;
}
var index = elementIndex[elementType];
if(index < cache.Count) {
var existing = cache[index];
existing.SetActive(true);
existing.transform.SetAsLastSibling();
elementIndex[elementType] = index + 1;
return existing;
}
var prefab = settings.GetPrefab(elementType);
if(prefab == null) {
Debug.LogWarning($"[PopupView] No prefab registered for element '{elementType}'");
return null;
}
var created = Object.Instantiate(prefab, reference.Content);
created.SetActive(true);
created.transform.SetAsLastSibling();
cache.Add(created);
elementIndex[elementType] = index + 1;
return created;
}
///
/// Deactivates all cached content elements and marks layout as dirty.
///
public void ClearContent() {
foreach(var kvp in elementCache) {
var cache = kvp.Value;
var activeCount = elementIndex[kvp.Key];
for(int i = 0; i < activeCount; i++) {
cache[i].SetActive(false);
}
elementIndex[kvp.Key] = 0;
}
layoutDirty = true;
}
// --- Visibility ---
/// Shows or hides the popup GameObject. Resets alpha to 0 when hiding.
public void SetVisible(bool visible) {
isVisible = visible;
reference.gameObject.SetActive(visible);
if(!visible) {
reference.CanvasGroup.alpha = 0f;
}
}
/// Constrains the popup's horizontal size to the given maximum width in pixels.
public void SetMaxWidth(float maxPopupWidth) {
maxWidth = maxPopupWidth;
if(maxWidth > 0f) {
var rt = (RectTransform)reference.transform;
var size = rt.sizeDelta;
size.x = Mathf.Min(size.x, maxWidth);
rt.sizeDelta = size;
}
}
// --- Positioning ---
/// Configures the popup to anchor to a target element on the specified side.
public void SetAnchorMode(RectTransform target, AnchorSide side, float edgePadding) {
positionMode = PopupPositionMode.AnchorToElement;
anchorTarget = target;
anchorSide = side;
screenEdgePadding = edgePadding;
}
/// Configures the popup to follow the mouse cursor with the given offset.
public void SetFollowMouseMode(Vector2 offset, float edgePadding) {
positionMode = PopupPositionMode.FollowMouse;
followOffset = offset;
screenEdgePadding = edgePadding;
}
/// Positions the popup at a fixed screen coordinate with edge clamping.
public void SetFixedPosition(Vector2 screenPos, float edgePadding) {
positionMode = PopupPositionMode.AnchorToElement;
anchorTarget = null;
screenEdgePadding = edgePadding;
PositionAtScreenPoint(screenPos);
}
/// Updates the popup position based on the current mode (follow mouse or anchored).
public void UpdatePosition() {
if(positionMode == PopupPositionMode.FollowMouse) {
PositionAtScreenPoint(Mouse.current.position.ReadValue() + followOffset);
}
else if(anchorTarget != null) {
PositionAnchoredTo(anchorTarget, anchorSide);
}
}
private void CacheCanvas() {
if(rootCanvas != null) {
return;
}
rootCanvas = reference.GetComponentInParent