1289 lines
40 KiB
C#
1289 lines
40 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using BriarQueen.Data.Identifiers;
|
|
using BriarQueen.Framework.Assets;
|
|
using BriarQueen.Framework.Managers.Interaction;
|
|
using BriarQueen.Framework.Managers.Player;
|
|
using BriarQueen.Framework.Managers.Player.Data;
|
|
using BriarQueen.Framework.Managers.UI.Base;
|
|
using BriarQueen.Framework.Registries;
|
|
using BriarQueen.UI.Menus;
|
|
using Cysharp.Threading.Tasks;
|
|
using PrimeTween;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.UI;
|
|
using VContainer;
|
|
|
|
namespace BriarQueen.UI.Codex
|
|
{
|
|
public class CodexWindow : MonoBehaviour, IUIWindow
|
|
{
|
|
[Header("Root UI")]
|
|
[SerializeField]
|
|
private CanvasGroup _canvasGroup;
|
|
|
|
[SerializeField]
|
|
private RectTransform _windowRect;
|
|
|
|
[Header("Category Buttons")]
|
|
[SerializeField]
|
|
private CodexCategoryButton _booksButton;
|
|
|
|
[SerializeField]
|
|
private CodexCategoryButton _cluesButton;
|
|
|
|
[SerializeField]
|
|
private CodexCategoryButton _photosButton;
|
|
|
|
[Header("Left Panel - Location View")]
|
|
[SerializeField]
|
|
private CanvasGroup _locationListCanvasGroup;
|
|
|
|
[SerializeField]
|
|
private RectTransform _locationListContainer;
|
|
|
|
[SerializeField]
|
|
private VerticalScrollbar _locationScrollbar;
|
|
|
|
[Header("Left Panel - Entry View")]
|
|
[SerializeField]
|
|
private CanvasGroup _entryListCanvasGroup;
|
|
|
|
[SerializeField]
|
|
private RectTransform _entryListContainer;
|
|
|
|
[SerializeField]
|
|
private VerticalScrollbar _entryScrollbar;
|
|
|
|
[SerializeField]
|
|
private Button _backToLocationsButton;
|
|
|
|
[Header("Display Area")]
|
|
[SerializeField]
|
|
private CanvasGroup _displayCanvasGroup;
|
|
|
|
[SerializeField]
|
|
private RectTransform _displayLayoutRoot;
|
|
|
|
[SerializeField]
|
|
private TextMeshProUGUI _titleText;
|
|
|
|
[SerializeField]
|
|
private TextMeshProUGUI _bodyText;
|
|
|
|
[SerializeField]
|
|
private TextMeshProUGUI _photoDescription;
|
|
|
|
[SerializeField]
|
|
private TextMeshProUGUI _polaroidWriting;
|
|
|
|
[SerializeField]
|
|
private Image _polaroid;
|
|
|
|
[SerializeField]
|
|
private Image _displayImage;
|
|
|
|
[SerializeField]
|
|
private GameObject _contentRoot;
|
|
|
|
[SerializeField]
|
|
private GameObject _emptyStateRoot;
|
|
|
|
[Header("Tween Settings")]
|
|
[SerializeField]
|
|
private TweenSettings _windowTweenSettings = new()
|
|
{
|
|
duration = 1.2f,
|
|
ease = Ease.OutQuad,
|
|
useUnscaledTime = true
|
|
};
|
|
|
|
[SerializeField]
|
|
private TweenSettings _leftPanelFadeSettings = new()
|
|
{
|
|
duration = 0.3f,
|
|
ease = Ease.OutQuad,
|
|
useUnscaledTime = true
|
|
};
|
|
|
|
[SerializeField]
|
|
private TweenSettings _displayFadeSettings = new()
|
|
{
|
|
duration = 0.3f,
|
|
ease = Ease.OutQuad,
|
|
useUnscaledTime = true
|
|
};
|
|
|
|
[Header("Scale")]
|
|
[SerializeField]
|
|
private float _hiddenScale = 0.85f;
|
|
|
|
[Header("Internal")]
|
|
[SerializeField]
|
|
private GraphicRaycaster _graphicRaycaster;
|
|
|
|
private readonly List<CodexEntryButton> _activeEntryButtons = new();
|
|
private readonly List<CodexLocationButton> _activeLocationButtons = new();
|
|
|
|
private readonly Dictionary<string, float> _entryScrollByCategoryAndLocation = new();
|
|
private readonly Dictionary<CodexType, string> _lastSelectedEntryByCategory = new();
|
|
private readonly Dictionary<CodexType, Location> _lastSelectedLocationByCategory = new();
|
|
private readonly Dictionary<CodexType, float> _locationScrollByCategory = new();
|
|
|
|
private readonly Stack<CodexEntryButton> _pooledEntryButtons = new();
|
|
private readonly Stack<CodexLocationButton> _pooledLocationButtons = new();
|
|
private AddressableManager _addressableManager;
|
|
private AssetRegistry _assetRegistry;
|
|
private bool _cached;
|
|
|
|
private CodexType _currentCategory = CodexType.BookEntry;
|
|
private CodexEntrySo _currentEntry;
|
|
private Location _currentLocation = Location.None;
|
|
private Sequence _displaySequence;
|
|
private AssetReference _entryButtonReference;
|
|
|
|
private InteractManager _interactManager;
|
|
private Sequence _leftPanelSequence;
|
|
|
|
private AssetReference _locationButtonReference;
|
|
|
|
private CancellationTokenSource _operationCts;
|
|
private PlayerManager _playerManager;
|
|
private bool _raycasterRegistered;
|
|
|
|
private bool _showingEntriesForLocation;
|
|
private bool _started;
|
|
|
|
private Sequence _windowSequence;
|
|
|
|
public bool IsModal => true;
|
|
|
|
public WindowType WindowType => WindowType.CodexWindow;
|
|
|
|
private void Awake()
|
|
{
|
|
if (_canvasGroup != null)
|
|
{
|
|
_canvasGroup.alpha = 0f;
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
}
|
|
|
|
if (_windowRect != null)
|
|
_windowRect.localScale = Vector3.one * _hiddenScale;
|
|
|
|
SetLocationPanelVisibleImmediate(true);
|
|
SetEntryPanelVisibleImmediate(false);
|
|
SetDisplayVisibleImmediate(false);
|
|
|
|
ClearDisplay();
|
|
SetEmptyStateVisible(true);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
InitializeStaticUi();
|
|
_started = true;
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (_booksButton != null)
|
|
_booksButton.OnCategoryClicked += OnCategoryClicked;
|
|
|
|
if (_cluesButton != null)
|
|
_cluesButton.OnCategoryClicked += OnCategoryClicked;
|
|
|
|
if (_photosButton != null)
|
|
_photosButton.OnCategoryClicked += OnCategoryClicked;
|
|
|
|
if (_backToLocationsButton != null)
|
|
_backToLocationsButton.onClick.AddListener(OnBackToLocationsClicked);
|
|
|
|
if (_started)
|
|
TryRegisterRaycaster();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (_booksButton != null)
|
|
_booksButton.OnCategoryClicked -= OnCategoryClicked;
|
|
|
|
if (_cluesButton != null)
|
|
_cluesButton.OnCategoryClicked -= OnCategoryClicked;
|
|
|
|
if (_photosButton != null)
|
|
_photosButton.OnCategoryClicked -= OnCategoryClicked;
|
|
|
|
if (_backToLocationsButton != null)
|
|
_backToLocationsButton.onClick.RemoveListener(OnBackToLocationsClicked);
|
|
|
|
ReleaseAllActiveLocationButtons();
|
|
ReleaseAllActiveEntryButtons();
|
|
CancelActiveOperations();
|
|
|
|
_interactManager?.ClearExclusiveRaycaster();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
CancelActiveOperations();
|
|
}
|
|
|
|
public async UniTask Show()
|
|
{
|
|
Debug.Log("[CodexWindow] Showing CodexWindow");
|
|
Debug.Log($"[CodexWindow] Started - {_started}");
|
|
|
|
CacheButtonReferences();
|
|
TryRegisterRaycaster();
|
|
|
|
if (!_started)
|
|
await UniTask.WaitUntil(() => _started, cancellationToken: this.GetCancellationTokenOnDestroy());
|
|
|
|
if (_canvasGroup == null || _windowRect == null)
|
|
{
|
|
Debug.LogError("[CodexWindow] Missing CanvasGroup or WindowRect reference.", this);
|
|
gameObject.SetActive(true);
|
|
await RefreshCurrentCategoryAsync(false, false);
|
|
return;
|
|
}
|
|
|
|
ResetOperationCancellation();
|
|
|
|
gameObject.SetActive(true);
|
|
|
|
_canvasGroup.alpha = 0f;
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
_windowRect.localScale = Vector3.one * _hiddenScale;
|
|
|
|
await RefreshCurrentCategoryAsync(false, false);
|
|
|
|
var token = _operationCts.Token;
|
|
|
|
_windowSequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = 0f,
|
|
endValue = 1f,
|
|
settings = _windowTweenSettings
|
|
}))
|
|
.Group(Tween.Scale(_windowRect, new TweenSettings<Vector3>
|
|
{
|
|
startValue = Vector3.one * _hiddenScale,
|
|
endValue = Vector3.one,
|
|
settings = _windowTweenSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _windowSequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
_windowSequence = default;
|
|
}
|
|
|
|
_canvasGroup.alpha = 1f;
|
|
_windowRect.localScale = Vector3.one;
|
|
_canvasGroup.blocksRaycasts = true;
|
|
_canvasGroup.interactable = true;
|
|
|
|
if (_currentEntry != null || IsShowingEmptyState())
|
|
{
|
|
await RefreshLayoutAsync(_displayLayoutRoot);
|
|
await FadeInDisplayAsync();
|
|
}
|
|
}
|
|
|
|
public async UniTask Hide()
|
|
{
|
|
if (_canvasGroup == null || _windowRect == null)
|
|
{
|
|
gameObject.SetActive(false);
|
|
return;
|
|
}
|
|
|
|
TryUnregisterRaycaster();
|
|
SaveCurrentScrollPosition();
|
|
ResetOperationCancellation();
|
|
|
|
_canvasGroup.blocksRaycasts = false;
|
|
_canvasGroup.interactable = false;
|
|
|
|
var token = _operationCts.Token;
|
|
|
|
_windowSequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = _canvasGroup.alpha,
|
|
endValue = 0f,
|
|
settings = _windowTweenSettings
|
|
}))
|
|
.Group(Tween.Scale(_windowRect, new TweenSettings<Vector3>
|
|
{
|
|
startValue = _windowRect.localScale,
|
|
endValue = Vector3.one * _hiddenScale,
|
|
settings = _windowTweenSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _windowSequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
_windowSequence = default;
|
|
}
|
|
|
|
_canvasGroup.alpha = 0f;
|
|
_windowRect.localScale = Vector3.one * _hiddenScale;
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
[Inject]
|
|
public void Construct(
|
|
InteractManager interactManager,
|
|
PlayerManager playerManager,
|
|
AssetRegistry assetRegistry,
|
|
AddressableManager addressableManager)
|
|
{
|
|
_interactManager = interactManager;
|
|
_playerManager = playerManager;
|
|
_assetRegistry = assetRegistry;
|
|
_addressableManager = addressableManager;
|
|
}
|
|
|
|
private void InitializeStaticUi()
|
|
{
|
|
if (_booksButton != null)
|
|
_booksButton.Initialize(CodexType.BookEntry);
|
|
|
|
if (_cluesButton != null)
|
|
_cluesButton.Initialize(CodexType.PuzzleClue);
|
|
|
|
if (_photosButton != null)
|
|
_photosButton.Initialize(CodexType.Photo);
|
|
}
|
|
|
|
private void CacheButtonReferences()
|
|
{
|
|
if (_cached)
|
|
return;
|
|
|
|
if (_assetRegistry == null)
|
|
{
|
|
Debug.LogError("[CodexWindow] AssetRegistry was not injected before Start.", this);
|
|
return;
|
|
}
|
|
|
|
if (!_assetRegistry.TryGetReference(AssetKeyIdentifiers.Get(UIKey.CodexLocationButton),
|
|
out _locationButtonReference) ||
|
|
_locationButtonReference == null)
|
|
Debug.LogError("[CodexWindow] Failed to resolve location button addressable reference.", this);
|
|
|
|
if (!_assetRegistry.TryGetReference(AssetKeyIdentifiers.Get(UIKey.CodexEntryButton),
|
|
out _entryButtonReference) ||
|
|
_entryButtonReference == null)
|
|
Debug.LogError("[CodexWindow] Failed to resolve entry button addressable reference.", this);
|
|
|
|
_cached = true;
|
|
}
|
|
|
|
private void TryRegisterRaycaster()
|
|
{
|
|
if (_raycasterRegistered)
|
|
return;
|
|
|
|
if (_interactManager == null || _graphicRaycaster == null)
|
|
return;
|
|
|
|
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
|
_interactManager?.SetExclusiveRaycaster(_graphicRaycaster);
|
|
_raycasterRegistered = true;
|
|
}
|
|
|
|
private void TryUnregisterRaycaster()
|
|
{
|
|
if (!_raycasterRegistered)
|
|
return;
|
|
|
|
if (_interactManager == null || _graphicRaycaster == null)
|
|
return;
|
|
|
|
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
|
|
_interactManager?.ClearExclusiveRaycaster();
|
|
_raycasterRegistered = false;
|
|
}
|
|
|
|
private void OnCategoryClicked(CodexType category)
|
|
{
|
|
SetCategoryAsync(category).Forget();
|
|
}
|
|
|
|
private void OnBackToLocationsClicked()
|
|
{
|
|
ShowLocationListAsync().Forget();
|
|
}
|
|
|
|
private async UniTask SetCategoryAsync(CodexType category)
|
|
{
|
|
if (_currentCategory == category)
|
|
return;
|
|
|
|
SaveCurrentScrollPosition();
|
|
|
|
_currentCategory = category;
|
|
_currentLocation = Location.None;
|
|
_currentEntry = null;
|
|
_showingEntriesForLocation = false;
|
|
|
|
await RefreshCurrentCategoryAsync(true, true);
|
|
}
|
|
|
|
private async UniTask RefreshCurrentCategoryAsync(bool animateLeftPanel, bool animateDisplay)
|
|
{
|
|
if (_playerManager == null)
|
|
return;
|
|
|
|
if (_locationListContainer == null || _entryListContainer == null)
|
|
{
|
|
Debug.LogError("[CodexWindow] Missing left-panel container references.", this);
|
|
ClearDisplay();
|
|
SetEmptyStateVisible(true);
|
|
SetDisplayVisibleImmediate(true);
|
|
return;
|
|
}
|
|
|
|
if (animateLeftPanel && animateDisplay)
|
|
await UniTask.WhenAll(
|
|
FadeOutCurrentLeftPanelAsync(),
|
|
FadeOutDisplayAsync());
|
|
else if (animateLeftPanel)
|
|
await FadeOutCurrentLeftPanelAsync();
|
|
else if (animateDisplay) await FadeOutDisplayAsync();
|
|
|
|
UpdateCategoryButtonSelection();
|
|
|
|
var entriesInCategory = _playerManager
|
|
.GetDiscoveredCodexEntriesByType(_currentCategory)
|
|
.Where(entry => entry != null)
|
|
.OrderBy(entry => entry.Title)
|
|
.ToList();
|
|
|
|
ReleaseAllActiveLocationButtons();
|
|
ReleaseAllActiveEntryButtons();
|
|
|
|
if (entriesInCategory.Count == 0)
|
|
{
|
|
ShowEmptyState();
|
|
|
|
if (animateLeftPanel)
|
|
{
|
|
await RefreshLayoutAsync(_locationListContainer);
|
|
await FadeInLocationsPanelAsync();
|
|
}
|
|
|
|
if (animateDisplay)
|
|
{
|
|
await RefreshLayoutAsync(_displayLayoutRoot);
|
|
await FadeInDisplayAsync();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
SetEmptyStateVisible(false);
|
|
|
|
var locations = entriesInCategory
|
|
.Select(entry => entry.Location)
|
|
.Where(location => location != Location.None)
|
|
.Distinct()
|
|
.OrderBy(location => location.ToString())
|
|
.ToList();
|
|
|
|
if (locations.Count == 0)
|
|
{
|
|
ShowEmptyState();
|
|
|
|
if (animateLeftPanel)
|
|
{
|
|
await RefreshLayoutAsync(_locationListContainer);
|
|
await FadeInLocationsPanelAsync();
|
|
}
|
|
|
|
if (animateDisplay)
|
|
{
|
|
await RefreshLayoutAsync(_displayLayoutRoot);
|
|
await FadeInDisplayAsync();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
foreach (var location in locations)
|
|
{
|
|
var button = await GetOrCreateLocationButtonAsync();
|
|
if (button == null)
|
|
continue;
|
|
|
|
button.Initialize(location);
|
|
button.OnLocationClicked -= OnLocationClicked;
|
|
button.OnLocationClicked += OnLocationClicked;
|
|
_activeLocationButtons.Add(button);
|
|
}
|
|
|
|
_locationScrollbar?.Rebuild();
|
|
|
|
_currentLocation = GetLocationToSelect(locations);
|
|
|
|
await BuildEntryButtonsForCurrentLocationAsync();
|
|
|
|
ClearDisplaySelectionForBrowsing();
|
|
|
|
_showingEntriesForLocation = false;
|
|
SetEntryPanelVisibleImmediate(false);
|
|
SetLocationPanelVisibleImmediate(true);
|
|
RestoreLocationScrollPosition(1f);
|
|
|
|
await RefreshLayoutAsync(_locationListContainer);
|
|
|
|
if (animateLeftPanel)
|
|
await FadeInLocationsPanelAsync();
|
|
}
|
|
|
|
private void ShowEmptyState()
|
|
{
|
|
_currentLocation = Location.None;
|
|
_currentEntry = null;
|
|
_showingEntriesForLocation = false;
|
|
|
|
ClearDisplay();
|
|
SetEmptyStateVisible(true);
|
|
SetDisplayVisibleImmediate(true);
|
|
SetLocationPanelVisibleImmediate(true);
|
|
SetEntryPanelVisibleImmediate(false);
|
|
RestoreLocationScrollPosition(1f);
|
|
}
|
|
|
|
private bool IsShowingEmptyState()
|
|
{
|
|
return _emptyStateRoot != null && _emptyStateRoot.activeSelf;
|
|
}
|
|
|
|
private Location GetLocationToSelect(List<Location> locations)
|
|
{
|
|
if (_currentLocation != Location.None && locations.Contains(_currentLocation))
|
|
return _currentLocation;
|
|
|
|
if (_lastSelectedLocationByCategory.TryGetValue(_currentCategory, out var rememberedLocation) &&
|
|
rememberedLocation != Location.None &&
|
|
locations.Contains(rememberedLocation))
|
|
return rememberedLocation;
|
|
|
|
return locations[0];
|
|
}
|
|
|
|
private void OnLocationClicked(Location location)
|
|
{
|
|
OnLocationClickedAsync(location).Forget();
|
|
}
|
|
|
|
private async UniTask OnLocationClickedAsync(Location location)
|
|
{
|
|
if (location == Location.None)
|
|
return;
|
|
|
|
SaveCurrentScrollPosition();
|
|
|
|
_currentLocation = location;
|
|
_lastSelectedLocationByCategory[_currentCategory] = location;
|
|
_currentEntry = null;
|
|
|
|
await UniTask.WhenAll(
|
|
FadeOutLocationsPanelAsync(),
|
|
FadeOutDisplayAsync());
|
|
|
|
await BuildEntryButtonsForCurrentLocationAsync();
|
|
ClearDisplaySelectionForBrowsing();
|
|
|
|
await RefreshLayoutAsync(_entryListContainer);
|
|
await RefreshLayoutAsync(_displayLayoutRoot);
|
|
|
|
await FadeInEntriesPanelAsync();
|
|
}
|
|
|
|
private async UniTask BuildEntryButtonsForCurrentLocationAsync()
|
|
{
|
|
ReleaseAllActiveEntryButtons();
|
|
|
|
if (_currentLocation == Location.None || _playerManager == null)
|
|
return;
|
|
|
|
var entries = _playerManager
|
|
.GetDiscoveredCodexEntriesByType(_currentCategory)
|
|
.Where(entry => entry != null && entry.Location == _currentLocation)
|
|
.OrderBy(entry => entry.Title)
|
|
.ToList();
|
|
|
|
foreach (var entry in entries)
|
|
{
|
|
var button = await GetOrCreateEntryButtonAsync();
|
|
if (button == null)
|
|
continue;
|
|
|
|
button.Initialize(entry);
|
|
button.SetSelected(false);
|
|
button.OnEntryClicked -= OnEntryClicked;
|
|
button.OnEntryClicked += OnEntryClicked;
|
|
_activeEntryButtons.Add(button);
|
|
}
|
|
|
|
_entryScrollbar?.Rebuild();
|
|
}
|
|
|
|
private void ClearDisplaySelectionForBrowsing()
|
|
{
|
|
_currentEntry = null;
|
|
ClearDisplay();
|
|
SetEmptyStateVisible(false);
|
|
SetDisplayVisibleImmediate(false);
|
|
UpdateEntryButtonSelection();
|
|
}
|
|
|
|
private void OnEntryClicked(CodexEntrySo entry)
|
|
{
|
|
ChangeDisplayedEntryAsync(entry).Forget();
|
|
}
|
|
|
|
private async UniTask ChangeDisplayedEntryAsync(CodexEntrySo entry)
|
|
{
|
|
if (entry == null)
|
|
return;
|
|
|
|
var hasCurrentVisibleEntry = _currentEntry != null &&
|
|
_displayCanvasGroup != null &&
|
|
_displayCanvasGroup.alpha > 0.001f;
|
|
|
|
var isChangingEntry = _currentEntry != null &&
|
|
_currentEntry.UniqueID != entry.UniqueID;
|
|
|
|
if (hasCurrentVisibleEntry && isChangingEntry)
|
|
await FadeOutDisplayAsync();
|
|
|
|
ApplyEntryToDisplay(entry);
|
|
UpdateEntryButtonSelection();
|
|
|
|
await RefreshLayoutAsync(_displayLayoutRoot);
|
|
await FadeInDisplayAsync();
|
|
}
|
|
|
|
private void ApplyEntryToDisplay(CodexEntrySo entry)
|
|
{
|
|
_currentEntry = entry;
|
|
|
|
if (entry == null)
|
|
{
|
|
ClearDisplay();
|
|
SetDisplayVisibleImmediate(false);
|
|
return;
|
|
}
|
|
|
|
_lastSelectedEntryByCategory[_currentCategory] = entry.UniqueID;
|
|
|
|
if (_titleText != null)
|
|
_titleText.text = entry.Title;
|
|
|
|
var isPhoto = entry.EntryType == CodexType.Photo || entry.IsPhotoOverride;
|
|
var showImage = isPhoto && entry.DisplayImage != null;
|
|
var showText = !showImage && !string.IsNullOrWhiteSpace(entry.BodyText) || entry.IsBodyTextOverride;
|
|
var showPhotoDescription = isPhoto && !string.IsNullOrWhiteSpace(entry.PhotoDescription);
|
|
var showPolaroidWriting = isPhoto && !string.IsNullOrWhiteSpace(entry.PolaroidWriting);
|
|
|
|
if (_polaroid != null)
|
|
_polaroid.gameObject.SetActive(isPhoto);
|
|
|
|
if (_displayImage != null)
|
|
{
|
|
_displayImage.sprite = showImage ? entry.DisplayImage : null;
|
|
_displayImage.enabled = showImage;
|
|
_displayImage.gameObject.SetActive(showImage);
|
|
}
|
|
|
|
if (_photoDescription != null)
|
|
{
|
|
_photoDescription.text = showPhotoDescription ? entry.PhotoDescription : string.Empty;
|
|
_photoDescription.gameObject.SetActive(showPhotoDescription);
|
|
}
|
|
|
|
if (_polaroidWriting != null)
|
|
{
|
|
_polaroidWriting.text = showPolaroidWriting ? entry.PolaroidWriting : string.Empty;
|
|
_polaroidWriting.gameObject.SetActive(showPolaroidWriting);
|
|
}
|
|
|
|
if (_bodyText != null)
|
|
{
|
|
_bodyText.text = showText ? entry.BodyText : string.Empty;
|
|
_bodyText.gameObject.SetActive(showText);
|
|
}
|
|
|
|
SetEmptyStateVisible(false);
|
|
}
|
|
|
|
private void ClearDisplay()
|
|
{
|
|
if (_titleText != null)
|
|
_titleText.text = string.Empty;
|
|
|
|
if (_polaroid != null)
|
|
_polaroid.gameObject.SetActive(false);
|
|
|
|
if (_bodyText != null)
|
|
{
|
|
_bodyText.text = string.Empty;
|
|
_bodyText.gameObject.SetActive(false);
|
|
}
|
|
|
|
if (_displayImage != null)
|
|
{
|
|
_displayImage.sprite = null;
|
|
_displayImage.enabled = false;
|
|
_displayImage.gameObject.SetActive(false);
|
|
}
|
|
|
|
if (_photoDescription != null)
|
|
{
|
|
_photoDescription.text = string.Empty;
|
|
_photoDescription.gameObject.SetActive(false);
|
|
}
|
|
|
|
if (_polaroidWriting != null)
|
|
{
|
|
_polaroidWriting.text = string.Empty;
|
|
_polaroidWriting.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
private void SetEmptyStateVisible(bool visible)
|
|
{
|
|
if (_emptyStateRoot != null)
|
|
_emptyStateRoot.SetActive(visible);
|
|
|
|
if (_contentRoot != null)
|
|
_contentRoot.SetActive(!visible);
|
|
}
|
|
|
|
private async UniTask<CodexLocationButton> GetOrCreateLocationButtonAsync()
|
|
{
|
|
if (_locationButtonReference == null)
|
|
return null;
|
|
|
|
if (_pooledLocationButtons.Count > 0)
|
|
{
|
|
var pooledButton = _pooledLocationButtons.Pop();
|
|
pooledButton.transform.SetParent(_locationListContainer, false);
|
|
pooledButton.gameObject.SetActive(true);
|
|
return pooledButton;
|
|
}
|
|
|
|
var instance =
|
|
await _addressableManager.InstantiateAsync(_locationButtonReference, parent: _locationListContainer);
|
|
if (instance == null)
|
|
return null;
|
|
|
|
var button = instance.GetComponent<CodexLocationButton>();
|
|
|
|
if (button == null)
|
|
{
|
|
Debug.LogError("[CodexWindow] Spawned location button is missing CodexLocationButton component.",
|
|
instance);
|
|
return null;
|
|
}
|
|
|
|
button.transform.SetParent(_locationListContainer, false);
|
|
button.gameObject.SetActive(true);
|
|
return button;
|
|
}
|
|
|
|
private async UniTask<CodexEntryButton> GetOrCreateEntryButtonAsync()
|
|
{
|
|
if (_entryButtonReference == null)
|
|
return null;
|
|
|
|
if (_pooledEntryButtons.Count > 0)
|
|
{
|
|
var pooledButton = _pooledEntryButtons.Pop();
|
|
pooledButton.transform.SetParent(_entryListContainer, false);
|
|
pooledButton.gameObject.SetActive(true);
|
|
return pooledButton;
|
|
}
|
|
|
|
var instance =
|
|
await _addressableManager.InstantiateAsync(_entryButtonReference, parent: _entryListContainer);
|
|
if (instance == null)
|
|
return null;
|
|
|
|
var button = instance.GetComponent<CodexEntryButton>();
|
|
if (button == null)
|
|
{
|
|
Debug.LogError("[CodexWindow] Spawned entry button is missing CodexEntryButton component.", instance);
|
|
return null;
|
|
}
|
|
|
|
button.transform.SetParent(_entryListContainer, false);
|
|
button.gameObject.SetActive(true);
|
|
return button;
|
|
}
|
|
|
|
private void ReleaseAllActiveLocationButtons()
|
|
{
|
|
foreach (var button in _activeLocationButtons)
|
|
{
|
|
if (button == null)
|
|
continue;
|
|
|
|
button.OnLocationClicked -= OnLocationClicked;
|
|
button.gameObject.SetActive(false);
|
|
_pooledLocationButtons.Push(button);
|
|
}
|
|
|
|
_activeLocationButtons.Clear();
|
|
_locationScrollbar?.Rebuild();
|
|
}
|
|
|
|
private void ReleaseAllActiveEntryButtons()
|
|
{
|
|
foreach (var button in _activeEntryButtons)
|
|
{
|
|
if (button == null)
|
|
continue;
|
|
|
|
button.OnEntryClicked -= OnEntryClicked;
|
|
button.SetSelected(false);
|
|
button.gameObject.SetActive(false);
|
|
_pooledEntryButtons.Push(button);
|
|
}
|
|
|
|
_activeEntryButtons.Clear();
|
|
_entryScrollbar?.Rebuild();
|
|
}
|
|
|
|
private void UpdateEntryButtonSelection()
|
|
{
|
|
foreach (var button in _activeEntryButtons)
|
|
{
|
|
if (button == null)
|
|
continue;
|
|
|
|
var isSelected = _currentEntry != null &&
|
|
button.Entry != null &&
|
|
button.Entry.UniqueID == _currentEntry.UniqueID;
|
|
|
|
button.SetSelected(isSelected);
|
|
}
|
|
}
|
|
|
|
private void UpdateCategoryButtonSelection()
|
|
{
|
|
if (_booksButton != null)
|
|
_booksButton.SetSelected(_currentCategory == CodexType.BookEntry);
|
|
|
|
if (_cluesButton != null)
|
|
_cluesButton.SetSelected(_currentCategory == CodexType.PuzzleClue);
|
|
|
|
if (_photosButton != null)
|
|
_photosButton.SetSelected(_currentCategory == CodexType.Photo);
|
|
}
|
|
|
|
private void SaveCurrentScrollPosition()
|
|
{
|
|
if (_showingEntriesForLocation)
|
|
{
|
|
if (_entryScrollbar != null && _currentLocation != Location.None)
|
|
_entryScrollByCategoryAndLocation[GetEntryScrollKey(_currentCategory, _currentLocation)] =
|
|
_entryScrollbar.Normalized;
|
|
}
|
|
else
|
|
{
|
|
if (_locationScrollbar != null)
|
|
_locationScrollByCategory[_currentCategory] = _locationScrollbar.Normalized;
|
|
}
|
|
}
|
|
|
|
private void RestoreLocationScrollPosition(float fallbackValue)
|
|
{
|
|
if (_locationScrollbar == null)
|
|
return;
|
|
|
|
_locationScrollbar.Rebuild();
|
|
|
|
var target = _locationScrollByCategory.TryGetValue(_currentCategory, out var saved)
|
|
? saved
|
|
: fallbackValue;
|
|
|
|
_locationScrollbar.SetNormalized(target);
|
|
}
|
|
|
|
private void RestoreEntryScrollPosition(float fallbackValue)
|
|
{
|
|
if (_entryScrollbar == null || _currentLocation == Location.None)
|
|
return;
|
|
|
|
_entryScrollbar.Rebuild();
|
|
|
|
var key = GetEntryScrollKey(_currentCategory, _currentLocation);
|
|
var target = _entryScrollByCategoryAndLocation.TryGetValue(key, out var saved)
|
|
? saved
|
|
: fallbackValue;
|
|
|
|
_entryScrollbar.SetNormalized(target);
|
|
}
|
|
|
|
private static string GetEntryScrollKey(CodexType category, Location location)
|
|
{
|
|
return $"{category}_{location}";
|
|
}
|
|
|
|
private async UniTask RefreshLayoutAsync(RectTransform layoutRoot)
|
|
{
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
if (layoutRoot != null)
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRoot);
|
|
|
|
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
|
|
if (layoutRoot != null)
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRoot);
|
|
}
|
|
|
|
private async UniTask FadeOutCurrentLeftPanelAsync()
|
|
{
|
|
if (_showingEntriesForLocation)
|
|
await FadeOutEntriesPanelAsync();
|
|
else
|
|
await FadeOutLocationsPanelAsync();
|
|
}
|
|
|
|
private async UniTask FadeOutLocationsPanelAsync()
|
|
{
|
|
if (_locationListCanvasGroup == null)
|
|
return;
|
|
|
|
ResetLeftPanelTweenOnly();
|
|
|
|
var token = _operationCts?.Token ?? this.GetCancellationTokenOnDestroy();
|
|
|
|
_leftPanelSequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_locationListCanvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = _locationListCanvasGroup.alpha,
|
|
endValue = 0f,
|
|
settings = _leftPanelFadeSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _leftPanelSequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
_leftPanelSequence = default;
|
|
}
|
|
|
|
SetLocationPanelVisibleImmediate(false);
|
|
}
|
|
|
|
private async UniTask FadeOutEntriesPanelAsync()
|
|
{
|
|
if (_entryListCanvasGroup == null)
|
|
return;
|
|
|
|
ResetLeftPanelTweenOnly();
|
|
|
|
var token = _operationCts?.Token ?? this.GetCancellationTokenOnDestroy();
|
|
|
|
_leftPanelSequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_entryListCanvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = _entryListCanvasGroup.alpha,
|
|
endValue = 0f,
|
|
settings = _leftPanelFadeSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _leftPanelSequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
_leftPanelSequence = default;
|
|
}
|
|
|
|
SetEntryPanelVisibleImmediate(false);
|
|
}
|
|
|
|
private async UniTask FadeInLocationsPanelAsync()
|
|
{
|
|
if (_locationListCanvasGroup == null)
|
|
return;
|
|
|
|
SetEntryPanelVisibleImmediate(false);
|
|
SetLocationPanelVisibleImmediate(true);
|
|
|
|
_showingEntriesForLocation = false;
|
|
|
|
ResetLeftPanelTweenOnly();
|
|
|
|
_locationListCanvasGroup.alpha = 0f;
|
|
|
|
var token = _operationCts?.Token ?? this.GetCancellationTokenOnDestroy();
|
|
|
|
_leftPanelSequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_locationListCanvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = 0f,
|
|
endValue = 1f,
|
|
settings = _leftPanelFadeSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _leftPanelSequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
_leftPanelSequence = default;
|
|
}
|
|
|
|
RestoreLocationScrollPosition(1f);
|
|
}
|
|
|
|
private async UniTask FadeInEntriesPanelAsync()
|
|
{
|
|
if (_entryListCanvasGroup == null)
|
|
return;
|
|
|
|
SetLocationPanelVisibleImmediate(false);
|
|
SetEntryPanelVisibleImmediate(true);
|
|
|
|
_showingEntriesForLocation = true;
|
|
|
|
ResetLeftPanelTweenOnly();
|
|
|
|
_entryListCanvasGroup.alpha = 0f;
|
|
|
|
var token = _operationCts?.Token ?? this.GetCancellationTokenOnDestroy();
|
|
|
|
_leftPanelSequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_entryListCanvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = 0f,
|
|
endValue = 1f,
|
|
settings = _leftPanelFadeSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _leftPanelSequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
_leftPanelSequence = default;
|
|
}
|
|
|
|
RestoreEntryScrollPosition(1f);
|
|
}
|
|
|
|
private async UniTask ShowLocationListAsync()
|
|
{
|
|
SaveCurrentScrollPosition();
|
|
_currentEntry = null;
|
|
|
|
await FadeOutEntriesPanelAsync();
|
|
await FadeOutDisplayAsync();
|
|
|
|
ClearDisplaySelectionForBrowsing();
|
|
|
|
await RefreshLayoutAsync(_locationListContainer);
|
|
await FadeInLocationsPanelAsync();
|
|
}
|
|
|
|
private async UniTask FadeOutDisplayAsync()
|
|
{
|
|
if (_displayCanvasGroup == null)
|
|
return;
|
|
|
|
ResetDisplayTweenOnly();
|
|
|
|
var token = _operationCts?.Token ?? this.GetCancellationTokenOnDestroy();
|
|
|
|
_displaySequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_displayCanvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = _displayCanvasGroup.alpha,
|
|
endValue = 0f,
|
|
settings = _displayFadeSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _displaySequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
_displaySequence = default;
|
|
}
|
|
|
|
SetDisplayVisibleImmediate(false);
|
|
}
|
|
|
|
private async UniTask FadeInDisplayAsync()
|
|
{
|
|
if (_displayCanvasGroup == null)
|
|
return;
|
|
|
|
SetDisplayVisibleImmediate(true);
|
|
ResetDisplayTweenOnly();
|
|
|
|
_displayCanvasGroup.alpha = 0f;
|
|
|
|
var token = _operationCts?.Token ?? this.GetCancellationTokenOnDestroy();
|
|
|
|
_displaySequence = Sequence.Create(useUnscaledTime: true)
|
|
.Group(Tween.Alpha(_displayCanvasGroup, new TweenSettings<float>
|
|
{
|
|
startValue = 0f,
|
|
endValue = 1f,
|
|
settings = _displayFadeSettings
|
|
}));
|
|
|
|
try
|
|
{
|
|
await _displaySequence.ToUniTask(cancellationToken: token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
_displaySequence = default;
|
|
}
|
|
}
|
|
|
|
private void SetLocationPanelVisibleImmediate(bool visible)
|
|
{
|
|
if (_locationListCanvasGroup == null)
|
|
return;
|
|
|
|
_locationListCanvasGroup.alpha = visible ? 1f : 0f;
|
|
_locationListCanvasGroup.blocksRaycasts = visible;
|
|
_locationListCanvasGroup.interactable = visible;
|
|
_locationListCanvasGroup.gameObject.SetActive(visible);
|
|
}
|
|
|
|
private void SetEntryPanelVisibleImmediate(bool visible)
|
|
{
|
|
if (_entryListCanvasGroup == null)
|
|
return;
|
|
|
|
_entryListCanvasGroup.alpha = visible ? 1f : 0f;
|
|
_entryListCanvasGroup.blocksRaycasts = visible;
|
|
_entryListCanvasGroup.interactable = visible;
|
|
_entryListCanvasGroup.gameObject.SetActive(visible);
|
|
|
|
if (_backToLocationsButton != null)
|
|
_backToLocationsButton.gameObject.SetActive(visible);
|
|
}
|
|
|
|
private void SetDisplayVisibleImmediate(bool visible)
|
|
{
|
|
if (_displayCanvasGroup == null)
|
|
return;
|
|
|
|
_displayCanvasGroup.alpha = visible ? 1f : 0f;
|
|
_displayCanvasGroup.blocksRaycasts = visible;
|
|
_displayCanvasGroup.interactable = visible;
|
|
_displayCanvasGroup.gameObject.SetActive(visible);
|
|
}
|
|
|
|
private void ResetLeftPanelTweenOnly()
|
|
{
|
|
if (_leftPanelSequence.isAlive)
|
|
_leftPanelSequence.Stop();
|
|
|
|
_leftPanelSequence = default;
|
|
}
|
|
|
|
private void ResetDisplayTweenOnly()
|
|
{
|
|
if (_displaySequence.isAlive)
|
|
_displaySequence.Stop();
|
|
|
|
_displaySequence = default;
|
|
}
|
|
|
|
private void CancelActiveOperations()
|
|
{
|
|
if (_windowSequence.isAlive)
|
|
_windowSequence.Stop();
|
|
|
|
ResetLeftPanelTweenOnly();
|
|
ResetDisplayTweenOnly();
|
|
|
|
if (_operationCts == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_operationCts.Cancel();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
_operationCts.Dispose();
|
|
_operationCts = null;
|
|
}
|
|
|
|
private void ResetOperationCancellation()
|
|
{
|
|
CancelActiveOperations();
|
|
_operationCts = new CancellationTokenSource();
|
|
}
|
|
}
|
|
} |