## ManagedBehaviour System Refactor - **Sealed `Awake()`** to prevent override mistakes that break singleton registration - **Added `OnManagedAwake()`** for early initialization (fires during registration) - **Renamed lifecycle hook:** `OnManagedAwake()` → `OnManagedStart()` (fires after boot, mirrors Unity's Awake→Start) - **40 files migrated** to new pattern (2 core, 38 components) - Eliminated all fragile `private new void Awake()` patterns - Zero breaking changes - backward compatible ## Centralized Logging System - **Automatic tagging** via `CallerMemberName` and `CallerFilePath` - logs auto-tagged as `[ClassName][MethodName] message` - **Unified API:** Single `Logging.Debug/Info/Warning/Error()` replaces custom `LogDebugMessage()` implementations - **~90 logging call sites** migrated across 10 files - **10 redundant helper methods** removed - All logs broadcast via `Logging.OnLogEntryAdded` event for real-time monitoring ## Custom Log Console (Editor Window) - **Persistent filter popups** for multi-selection (classes, methods, log levels) - windows stay open during selection - **Search** across class names, methods, and message content - **Time range filter** with MinMaxSlider - **Export** filtered logs to timestamped `.txt` files - **Right-click context menu** for quick filtering and copy actions - **Visual improvements:** White text, alternating row backgrounds, color-coded log levels - **Multiple instances** supported for simultaneous system monitoring - Open via `AppleHills > Custom Log Console` Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: #56
367 lines
14 KiB
C#
367 lines
14 KiB
C#
using AppleHills.Data.CardSystem;
|
|
using Core;
|
|
using Data.CardSystem;
|
|
using UI.DragAndDrop.Core;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace UI.CardSystem
|
|
{
|
|
/// <summary>
|
|
/// Specialized slot for album pages that only accepts a specific card.
|
|
/// Validates cards based on their CardDefinition.
|
|
/// Self-populates with owned cards when enabled.
|
|
/// Shows preview of target card when empty slot is tapped.
|
|
/// </summary>
|
|
public class AlbumCardSlot : DraggableSlot, IPointerClickHandler
|
|
{
|
|
[Header("Album Slot Configuration")]
|
|
[SerializeField] private CardDefinition targetCardDefinition; // Which card this slot accepts
|
|
[SerializeField] private GameObject albumCardPrefab; // Prefab to spawn when card is owned
|
|
|
|
[Header("Preview Card (for empty slots)")]
|
|
[SerializeField] private CardDisplay previewCardDisplay; // Nested CardDisplay showing greyed-out preview
|
|
[SerializeField] private float previewEnlargedScale = 2.5f;
|
|
[SerializeField] private float previewScaleDuration = 0.3f;
|
|
|
|
private bool _isOccupiedPermanently = false;
|
|
private AlbumCard _placedCard;
|
|
private bool _isPreviewShowing = false;
|
|
private Vector3 _previewOriginalScale;
|
|
|
|
private void Awake()
|
|
{
|
|
// Store original scale of preview card
|
|
if (previewCardDisplay != null)
|
|
{
|
|
_previewOriginalScale = previewCardDisplay.transform.localScale;
|
|
// Hide preview card by default
|
|
previewCardDisplay.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the target card this slot should accept
|
|
/// </summary>
|
|
public void SetTargetCard(CardDefinition definition)
|
|
{
|
|
targetCardDefinition = definition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if this slot can accept a specific card
|
|
/// </summary>
|
|
public bool CanAcceptCard(CardData cardData)
|
|
{
|
|
if (cardData == null || targetCardDefinition == null) return false;
|
|
if (_isOccupiedPermanently) return false;
|
|
|
|
// Card must match this slot's target definition
|
|
return cardData.DefinitionId == targetCardDefinition.Id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a card is successfully placed in this slot
|
|
/// </summary>
|
|
public void OnCardPlaced(AlbumCard albumCard = null)
|
|
{
|
|
_isOccupiedPermanently = true;
|
|
|
|
if (albumCard != null)
|
|
{
|
|
_placedCard = albumCard;
|
|
albumCard.SetParentSlot(this);
|
|
|
|
// Register with AlbumViewPage for enlarge/shrink handling
|
|
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
|
|
if (albumPage != null)
|
|
{
|
|
albumPage.RegisterAlbumCard(albumCard);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if this slot has a placed card
|
|
/// </summary>
|
|
public bool HasPlacedCard()
|
|
{
|
|
return _placedCard != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the placed card (if any)
|
|
/// </summary>
|
|
public AlbumCard GetPlacedCard()
|
|
{
|
|
return _placedCard;
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
// Check if we should spawn a card for this slot
|
|
CheckAndSpawnOwnedCard();
|
|
|
|
// Setup preview card display if slot is empty
|
|
SetupPreviewCard();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setup the preview card display to show target card with preview visuals
|
|
/// Preview stays hidden until user taps to show it
|
|
/// </summary>
|
|
private void SetupPreviewCard()
|
|
{
|
|
if (previewCardDisplay == null || targetCardDefinition == null)
|
|
return;
|
|
|
|
// Only setup preview if slot is empty
|
|
if (_isOccupiedPermanently || _placedCard != null)
|
|
{
|
|
// Hide preview if slot is occupied
|
|
previewCardDisplay.gameObject.SetActive(false);
|
|
return;
|
|
}
|
|
|
|
// Setup preview card data
|
|
CardData previewData = targetCardDefinition.CreateCardData();
|
|
previewData.Rarity = CardRarity.Normal; // Show as normal rarity
|
|
previewCardDisplay.SetupCard(previewData);
|
|
|
|
// Apply preview visuals (black tint and ?????? name)
|
|
previewCardDisplay.SetPreviewVisuals();
|
|
|
|
// Keep preview hidden - it'll show when user taps to enlarge
|
|
previewCardDisplay.gameObject.SetActive(false);
|
|
|
|
Logging.Debug($"[AlbumCardSlot] Setup preview card for {targetCardDefinition.Name} (hidden until tap)");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if player owns the card for this slot and spawn it if so
|
|
/// </summary>
|
|
private void CheckAndSpawnOwnedCard()
|
|
{
|
|
// Guard: need CardSystemManager and target definition
|
|
if (CardSystemManager.Instance == null || targetCardDefinition == null)
|
|
return;
|
|
|
|
// Guard: don't spawn if already occupied
|
|
if (_isOccupiedPermanently || _placedCard != null)
|
|
return;
|
|
|
|
// Guard: need prefab to spawn
|
|
if (albumCardPrefab == null)
|
|
{
|
|
Logging.Warning($"[AlbumCardSlot] No albumCardPrefab assigned for slot targeting {targetCardDefinition.name}");
|
|
return;
|
|
}
|
|
|
|
// Check if player owns this card at ANY rarity (prioritize highest rarity)
|
|
CardData ownedCard = null;
|
|
|
|
// Check in order: Legendary > Rare > Normal
|
|
foreach (CardRarity rarity in new[] { CardRarity.Legendary, CardRarity.Rare, CardRarity.Normal })
|
|
{
|
|
CardData card = CardSystemManager.Instance.GetCardInventory().GetCard(targetCardDefinition.Id, rarity);
|
|
if (card != null)
|
|
{
|
|
ownedCard = card;
|
|
break; // Found highest rarity owned
|
|
}
|
|
}
|
|
|
|
// Spawn card if owned
|
|
if (ownedCard != null)
|
|
{
|
|
SpawnAlbumCard(ownedCard);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spawn an AlbumCard in this slot
|
|
/// </summary>
|
|
private void SpawnAlbumCard(CardData cardData)
|
|
{
|
|
GameObject cardObj = Instantiate(albumCardPrefab, transform);
|
|
AlbumCard albumCard = cardObj.GetComponent<AlbumCard>();
|
|
|
|
if (albumCard != null)
|
|
{
|
|
albumCard.SetupCard(cardData);
|
|
albumCard.SetParentSlot(this);
|
|
_placedCard = albumCard;
|
|
_isOccupiedPermanently = true;
|
|
|
|
// Resize the card to match the slot size (same as placed cards)
|
|
RectTransform cardRect = albumCard.transform as RectTransform;
|
|
RectTransform slotRect = transform as RectTransform;
|
|
if (cardRect != null && slotRect != null)
|
|
{
|
|
// Set height to match slot height (AspectRatioFitter will handle width)
|
|
float targetHeight = slotRect.rect.height;
|
|
cardRect.sizeDelta = new Vector2(cardRect.sizeDelta.x, targetHeight);
|
|
|
|
// Ensure position and rotation are centered
|
|
cardRect.localPosition = Vector3.zero;
|
|
cardRect.localRotation = Quaternion.identity;
|
|
}
|
|
|
|
// Register with AlbumViewPage for enlarge/shrink handling
|
|
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
|
|
if (albumPage != null)
|
|
{
|
|
albumPage.RegisterAlbumCard(albumCard);
|
|
}
|
|
|
|
Logging.Debug($"[AlbumCardSlot] Spawned owned card '{cardData.Name}' ({cardData.Rarity}) in slot");
|
|
}
|
|
else
|
|
{
|
|
Logging.Warning($"[AlbumCardSlot] Spawned prefab has no AlbumCard component!");
|
|
Destroy(cardObj);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the target card definition for this slot
|
|
/// </summary>
|
|
public CardDefinition GetTargetCardDefinition()
|
|
{
|
|
return targetCardDefinition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle click on slot - show/hide preview if empty
|
|
/// </summary>
|
|
public void OnPointerClick(PointerEventData eventData)
|
|
{
|
|
Logging.Debug($"[CLICK-TRACE-SLOT] OnPointerClick on {name}, _isOccupiedPermanently={_isOccupiedPermanently}, _placedCard={((_placedCard != null) ? _placedCard.name : "NULL")}, _isPreviewShowing={_isPreviewShowing}, position={eventData.position}");
|
|
|
|
// Only handle clicks if slot is empty
|
|
if (_isOccupiedPermanently || _placedCard != null)
|
|
{
|
|
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - Slot is occupied, ignoring");
|
|
return;
|
|
}
|
|
|
|
// Only handle if we have a preview card setup
|
|
if (previewCardDisplay == null || targetCardDefinition == null)
|
|
{
|
|
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - No preview setup, ignoring");
|
|
return;
|
|
}
|
|
|
|
if (_isPreviewShowing)
|
|
{
|
|
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - Preview is showing, hiding it");
|
|
HidePreview();
|
|
}
|
|
else
|
|
{
|
|
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - Preview is hidden, showing it");
|
|
ShowPreview();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show enlarged preview of target card
|
|
/// </summary>
|
|
private void ShowPreview()
|
|
{
|
|
if (_isPreviewShowing || previewCardDisplay == null)
|
|
return;
|
|
|
|
_isPreviewShowing = true;
|
|
|
|
// Show the preview card (already has preview visuals applied)
|
|
previewCardDisplay.gameObject.SetActive(true);
|
|
|
|
// Enable preview mode so clicks on CardDisplay forward to this slot
|
|
previewCardDisplay.SetPreviewMode(true, this);
|
|
|
|
// Reset to normal scale before enlarging
|
|
previewCardDisplay.transform.localScale = _previewOriginalScale;
|
|
|
|
// Get AlbumViewPage to show backdrop and reparent
|
|
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
|
|
if (albumPage != null)
|
|
{
|
|
albumPage.ShowSlotPreview(this, previewCardDisplay.transform);
|
|
}
|
|
|
|
// Scale up preview card
|
|
Pixelplacement.Tween.LocalScale(previewCardDisplay.transform, _previewOriginalScale * previewEnlargedScale,
|
|
previewScaleDuration, 0f, Pixelplacement.Tween.EaseOutBack);
|
|
|
|
Logging.Debug($"[AlbumCardSlot] Showing preview for {targetCardDefinition.Name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hide preview and return to normal
|
|
/// </summary>
|
|
private void HidePreview()
|
|
{
|
|
if (!_isPreviewShowing || previewCardDisplay == null)
|
|
return;
|
|
|
|
_isPreviewShowing = false;
|
|
|
|
// Disable preview mode on CardDisplay
|
|
previewCardDisplay.SetPreviewMode(false, null);
|
|
|
|
// Get AlbumViewPage to hide backdrop
|
|
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
|
|
if (albumPage != null)
|
|
{
|
|
albumPage.HideSlotPreview(this, previewCardDisplay.transform, () =>
|
|
{
|
|
// After shrink completes, reparent back to slot
|
|
previewCardDisplay.transform.SetParent(transform, false);
|
|
|
|
// Reset RectTransform properties
|
|
RectTransform previewRect = previewCardDisplay.transform as RectTransform;
|
|
if (previewRect != null)
|
|
{
|
|
// Set anchors to stretch in all directions (matching original setup)
|
|
previewRect.anchorMin = Vector2.zero; // (0, 0)
|
|
previewRect.anchorMax = Vector2.one; // (1, 1)
|
|
|
|
// Reset offsets to zero (left, right, top, bottom all = 0)
|
|
previewRect.offsetMin = Vector2.zero; // Sets left and bottom to 0
|
|
previewRect.offsetMax = Vector2.zero; // Sets right and top to 0 (note: these are negative values internally)
|
|
|
|
previewRect.pivot = new Vector2(0.5f, 0.5f);
|
|
}
|
|
|
|
previewCardDisplay.transform.localPosition = Vector3.zero;
|
|
previewCardDisplay.transform.localRotation = Quaternion.identity;
|
|
previewCardDisplay.transform.localScale = _previewOriginalScale;
|
|
|
|
// Hide the preview card after returning to slot
|
|
previewCardDisplay.gameObject.SetActive(false);
|
|
|
|
Logging.Debug($"[AlbumCardSlot] Preview hidden and reset for {targetCardDefinition.Name}");
|
|
});
|
|
}
|
|
|
|
Logging.Debug($"[AlbumCardSlot] Hiding preview for {targetCardDefinition.Name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Public method to dismiss preview - can be called by CardDisplay when clicked
|
|
/// </summary>
|
|
public void DismissPreview()
|
|
{
|
|
Logging.Debug($"[CLICK-TRACE-SLOT] DismissPreview called on {name}");
|
|
HidePreview();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the target card definition for this slot
|
|
/// </summary>
|
|
public CardDefinition TargetCardDefinition => targetCardDefinition;
|
|
}
|
|
}
|
|
|