All working but codex.

This commit is contained in:
2026-05-13 14:20:25 +01:00
parent c203f836b1
commit 602e1ec0d3
49 changed files with 186784 additions and 62239 deletions

View File

@@ -1,40 +0,0 @@
using System;
using BriarQueen.Data.Identifiers;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.UI.Codex
{
public class CodexCategoryButton : MonoBehaviour
{
[SerializeField]
private Button _button;
public CodexType Category { get; private set; }
private void Awake()
{
if (_button != null)
_button.onClick.AddListener(HandleClicked);
}
private void OnDestroy()
{
if (_button != null)
_button.onClick.RemoveListener(HandleClicked);
}
public event Action<CodexType> OnCategoryClicked;
public void Initialize(CodexType category)
{
Category = category;
}
private void HandleClicked()
{
OnCategoryClicked?.Invoke(Category);
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 2cfb06e8e46d402e86eb8dc6e9f6c96e
timeCreated: 1773684333

View File

@@ -10,6 +10,7 @@ using BriarQueen.Framework.Managers.Player.Data;
using BriarQueen.Framework.Managers.UI.Base;
using BriarQueen.Framework.Registries;
using BriarQueen.Framework.Services.Destruction;
using BriarQueen.UI.Components;
using BriarQueen.UI.Menus;
using BriarQueen.UI.Menus.Components;
using Cysharp.Threading.Tasks;
@@ -25,31 +26,29 @@ namespace BriarQueen.UI.Codex
{
public class CodexWindow : MonoBehaviour, IUIWindow, IUIBackHandler
{
// ── Root ──────────────────────────────────────────────────────
[Header("Root UI")]
[SerializeField] private CanvasGroup _canvasGroup;
[SerializeField] private RectTransform _windowRect;
// ── Left panel — header ───────────────────────────────────────
[Header("Content")]
[SerializeField] private CanvasGroup _backgroundGroup; // Direct child — faded instead of root
[Header("Left Panel — Header")]
[SerializeField] private TextMeshProUGUI _leftPanelTitleText;
[SerializeField] private CanvasGroup _leftPanelTitleGroup;
// ── Left panel — categories ───────────────────────────────────
[Header("Left Panel — Categories")]
[SerializeField] private CanvasGroup _categoriesGroup;
[SerializeField] private CodexCategoryButton _booksButton;
[SerializeField] private CodexCategoryButton _cluesButton;
[SerializeField] private CodexCategoryButton _photosButton;
[SerializeField] private UnderlineButton _booksButton;
[SerializeField] private UnderlineButton _cluesButton;
[SerializeField] private UnderlineButton _photosButton;
// ── Left panel — locations ────────────────────────────────────
[Header("Left Panel — Locations")]
[SerializeField] private CanvasGroup _locationListGroup;
[SerializeField] private RectTransform _locationListContainer;
[SerializeField] private VerticalScrollbar _locationScrollbar;
[SerializeField] private Button _backToCategoriesButton;
// ── Left panel — entries ──────────────────────────────────────
[Header("Left Panel — Entries")]
[SerializeField] private CanvasGroup _entryListGroup;
[SerializeField] private RectTransform _entryListContainer;
@@ -57,24 +56,20 @@ namespace BriarQueen.UI.Codex
[SerializeField] private Button _backToLocationsButton;
[SerializeField] private UnderlineButtonGroup _entryButtonGroup;
// ── Right panel — display ─────────────────────────────────────
[Header("Right Panel — Display")]
[SerializeField] private CanvasGroup _displayGroup;
[SerializeField] private RectTransform _displayLayoutRoot;
[SerializeField] private CanvasGroup _rightPanelGroup;
[SerializeField] private RectTransform _rightPanelRoot;
[SerializeField] private TextMeshProUGUI _titleText;
[SerializeField] private CanvasGroup _titleGroup;
[SerializeField] private CanvasGroup _contentGroup;
[SerializeField] private RectTransform _contentScrollContainer;
[SerializeField] private VerticalScrollbar _contentScrollbar;
[SerializeField] private TextMeshProUGUI _bodyText;
[SerializeField] private TextMeshProUGUI _photoDescription;
[SerializeField] private CanvasGroup _displayAreaGroup;
[SerializeField] private ScrollableTextBox _bodyText;
[SerializeField] private ScrollableTextBox _photoDescription;
[SerializeField] private TextMeshProUGUI _polaroidWriting;
[SerializeField] private Image _polaroid;
[SerializeField] private Image _displayImage;
[SerializeField] private GameObject _contentRoot;
[SerializeField] private GameObject _displayAreaRoot;
[SerializeField] private GameObject _emptyStateRoot;
// ── Tween settings ────────────────────────────────────────────
[Header("Tween Settings")]
[SerializeField] private TweenSettings _windowTweenSettings = new()
{
@@ -117,7 +112,6 @@ namespace BriarQueen.UI.Codex
[Header("Internal")]
[SerializeField] private GraphicRaycaster _graphicRaycaster;
// ── Runtime state ─────────────────────────────────────────────
private enum LeftPanelState { Categories, Locations, Entries }
private LeftPanelState _leftPanelState = LeftPanelState.Categories;
@@ -161,11 +155,59 @@ namespace BriarQueen.UI.Codex
public bool IsModal => true;
public WindowType WindowType => WindowType.CodexWindow;
// ── Raycaster ─────────────────────────────────────────────────
private void TryRegisterRaycaster()
{
Debug.Log($"[CodexWindow] TryRegisterRaycaster " +
$"registered={_raycasterRegistered} " +
$"interactManager={_interactManager != null} " +
$"raycaster={_graphicRaycaster != null}");
Debug.Log("[CodexWindow] Try register raycaster.");
if (_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return;
_interactManager.AddUIRaycaster(_graphicRaycaster);
_interactManager.SetExclusiveRaycaster(_graphicRaycaster);
_raycasterRegistered = true;
Debug.Log("[CodexWindow] Registered raycaster.");
}
private void TryUnregisterRaycaster()
{
Debug.Log("[CodexWindow] Try unregister raycaster.");
if (!_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return;
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
_interactManager.ClearExclusiveRaycaster();
_raycasterRegistered = false;
Debug.Log("[CodexWindow] Raycaster unregistered.");
}
// ── Unity lifecycle ───────────────────────────────────────────
private void Awake()
{
SetCanvasGroupImmediate(_canvasGroup, 0f, false);
if (_canvasGroup != null)
{
_canvasGroup.alpha = 0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
}
// Background group controls visual fading
if (_backgroundGroup != null)
{
_backgroundGroup.alpha = 0f;
_backgroundGroup.blocksRaycasts = false;
_backgroundGroup.interactable = false;
}
if (_windowRect != null)
_windowRect.localScale = Vector3.one * _hiddenScale;
@@ -177,29 +219,26 @@ namespace BriarQueen.UI.Codex
SetLeftPanelTitleImmediate("Codex");
}
private void Start()
private async UniTaskVoid Start()
{
InitializeCategoryButtons();
_started = true;
CacheButtonReferences();
await EnsurePoolsReady();
}
private void OnEnable()
{
BindCategoryButtons();
BindNavButtons();
if (_started)
TryRegisterRaycaster();
}
private void OnDisable()
{
UnbindCategoryButtons();
UnbindNavButtons();
TryUnregisterRaycaster();
ReleaseAllLocationButtons();
ReleaseAllEntryButtons();
CancelAllOperations();
_interactManager?.ClearExclusiveRaycaster();
}
private void OnDestroy()
@@ -207,25 +246,31 @@ namespace BriarQueen.UI.Codex
CancelAllOperations();
_locationButtonPool?.Dispose();
_entryButtonPool?.Dispose();
TryUnregisterRaycaster();
}
// ── IUIWindow ─────────────────────────────────────────────────
public async UniTask Show()
{
CacheButtonReferences();
ResetOperationCts();
gameObject.SetActive(true);
TryRegisterRaycaster();
if (!_started)
await UniTask.WaitUntil(() => _started,
cancellationToken: this.GetCancellationTokenOnDestroy());
if (_canvasGroup != null)
{
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
}
await EnsurePoolsReady();
if (_backgroundGroup != null)
{
_backgroundGroup.alpha = 0f;
_backgroundGroup.blocksRaycasts = false;
_backgroundGroup.interactable = false;
}
ResetOperationCts();
gameObject.SetActive(true);
SetCanvasGroupImmediate(_canvasGroup, 0f, false);
if (_windowRect != null)
_windowRect.localScale = Vector3.one * _hiddenScale;
@@ -236,7 +281,7 @@ namespace BriarQueen.UI.Codex
SetLeftPanelTitleImmediate("Codex");
_windowSequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
.Group(Tween.Alpha(_backgroundGroup, new TweenSettings<float>
{
startValue = 0f,
endValue = 1f,
@@ -251,26 +296,54 @@ namespace BriarQueen.UI.Codex
try
{
await _windowSequence.ToUniTask(cancellationToken: _operationCts.Token);
}
catch (OperationCanceledException) { return; }
finally { _windowSequence = default; }
var canvasTask = Tween.Alpha(_canvasGroup, new TweenSettings<float>
{
startValue = 0f,
endValue = 1f,
settings = _windowTweenSettings
}).ToUniTask();
var backgroundTask = _windowSequence.ToUniTask();
await UniTask.WhenAll(canvasTask, backgroundTask).AttachExternalCancellation(_operationCts.Token);
_backgroundGroup.blocksRaycasts = true;
_backgroundGroup.interactable = true;
}
catch (OperationCanceledException)
{
return;
}
finally
{
_windowSequence = default;
}
_canvasGroup.interactable = true;
_canvasGroup.blocksRaycasts = true;
_canvasGroup.alpha = 1f;
SetCanvasGroupImmediate(_canvasGroup, 1f, true);
await TransitionToCategories(instant: true);
Debug.Log($"[CodexWindow] Codex Window Show Complete.");
}
public async UniTask Hide()
{
TryUnregisterRaycaster();
Debug.Log($"[CodexWindow] Codex Window Hide Started.");
ResetOperationCts();
SetCanvasGroupInteractivity(_canvasGroup, false);
if (_canvasGroup != null)
{
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
}
TryUnregisterRaycaster();
_windowSequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.Alpha(_canvasGroup, new TweenSettings<float>
.Group(Tween.Alpha(_backgroundGroup, new TweenSettings<float>
{
startValue = _canvasGroup.alpha,
startValue = _backgroundGroup != null ? _backgroundGroup.alpha : 1f,
endValue = 0f,
settings = _windowTweenSettings
}))
@@ -288,7 +361,15 @@ namespace BriarQueen.UI.Codex
catch (OperationCanceledException) { return; }
finally { _windowSequence = default; }
if (_canvasGroup != null)
{
_canvasGroup.alpha = 0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
}
gameObject.SetActive(false);
Debug.Log($"[CodexWindow] Codex Window Hide Complete.");
}
// ── IUIBackHandler ────────────────────────────────────────────
@@ -297,19 +378,10 @@ namespace BriarQueen.UI.Codex
{
switch (_leftPanelState)
{
case LeftPanelState.Entries:
NavigateBackToLocations().Forget();
return true;
case LeftPanelState.Locations:
NavigateBackToCategories().Forget();
return true;
case LeftPanelState.Categories:
return false;
default:
return false;
case LeftPanelState.Entries: NavigateBackToLocations().Forget(); return true;
case LeftPanelState.Locations: NavigateBackToCategories().Forget(); return true;
case LeftPanelState.Categories: return false;
default: return false;
}
}
@@ -415,7 +487,6 @@ namespace BriarQueen.UI.Codex
{
if (_entryButtonGroup != null)
_entryButtonGroup.RemoveButton(btn.UnderlineButton);
btn.OnEntryClicked -= OnEntryClicked;
btn.SetSelected(false);
btn.gameObject.SetActive(false);
@@ -432,30 +503,15 @@ namespace BriarQueen.UI.Codex
// ── Navigation ────────────────────────────────────────────────
private void OnCategoryClicked(CodexType category)
{
NavigateToLocations(category).Forget();
}
private void OnCluesClicked(UnderlineButton _) => NavigateToLocations(CodexType.PuzzleClue).Forget();
private void OnDocumentsClicked(UnderlineButton _) => NavigateToLocations(CodexType.DocumentEntry).Forget();
private void OnPhotosClicked(UnderlineButton _) => NavigateToLocations(CodexType.Photo).Forget();
private void OnLocationClicked(Location location)
{
NavigateToEntries(location).Forget();
}
private void OnEntryClicked(CodexEntrySo entry)
{
DisplayEntry(entry).Forget();
}
private void OnBackToCategoriesClicked()
{
NavigateBackToCategories().Forget();
}
private void OnBackToLocationsClicked()
{
NavigateBackToLocations().Forget();
}
private void OnLocationClicked(Location location) => NavigateToEntries(location).Forget();
private void OnEntryClicked(CodexEntrySo entry) => DisplayEntry(entry).Forget();
private void OnBackToCategoriesClicked() => NavigateBackToCategories().Forget();
private void OnBackToLocationsClicked() => NavigateBackToLocations().Forget();
// ── Left panel transitions ────────────────────────────────────
@@ -494,7 +550,6 @@ namespace BriarQueen.UI.Codex
await FadeLeftPanelTitle(GetCategoryDisplayName(category));
await FadeOutCurrentLeftPanel();
await BuildLocationButtons(token);
token.ThrowIfCancellationRequested();
_leftPanelState = LeftPanelState.Locations;
@@ -502,7 +557,6 @@ namespace BriarQueen.UI.Codex
await RefreshLayout(_locationListContainer);
RestoreLocationScroll();
await FadePanelIn(_locationListGroup);
await FadeOutDisplay();
ShowEmptyDisplay();
}
@@ -524,7 +578,6 @@ namespace BriarQueen.UI.Codex
await FadeLeftPanelTitle(GetLocationDisplayName(location));
await FadePanelOut(_locationListGroup);
await BuildEntryButtons(token);
token.ThrowIfCancellationRequested();
_leftPanelState = LeftPanelState.Entries;
@@ -532,7 +585,6 @@ namespace BriarQueen.UI.Codex
await RefreshLayout(_entryListContainer);
RestoreEntryScroll();
await FadePanelIn(_entryListGroup);
await FadeOutDisplay();
ShowEmptyDisplay();
}
@@ -542,7 +594,6 @@ namespace BriarQueen.UI.Codex
private async UniTask NavigateBackToCategories()
{
ResetOperationCts();
try
{
await FadeOutDisplay();
@@ -565,7 +616,6 @@ namespace BriarQueen.UI.Codex
await FadeLeftPanelTitle(GetCategoryDisplayName(_currentCategory));
await FadePanelOut(_entryListGroup);
ReleaseAllEntryButtons();
token.ThrowIfCancellationRequested();
_leftPanelState = LeftPanelState.Locations;
@@ -573,7 +623,6 @@ namespace BriarQueen.UI.Codex
await RefreshLayout(_locationListContainer);
RestoreLocationScroll();
await FadePanelIn(_locationListGroup);
await FadeOutDisplay();
ShowEmptyDisplay();
}
@@ -584,15 +633,9 @@ namespace BriarQueen.UI.Codex
{
switch (_leftPanelState)
{
case LeftPanelState.Categories:
await FadePanelOut(_categoriesGroup);
break;
case LeftPanelState.Locations:
await FadePanelOut(_locationListGroup);
break;
case LeftPanelState.Entries:
await FadePanelOut(_entryListGroup);
break;
case LeftPanelState.Categories: await FadePanelOut(_categoriesGroup); break;
case LeftPanelState.Locations: await FadePanelOut(_locationListGroup); break;
case LeftPanelState.Entries: await FadePanelOut(_entryListGroup); break;
}
}
@@ -600,11 +643,12 @@ namespace BriarQueen.UI.Codex
private void SetLeftPanelTitleImmediate(string title)
{
if (_leftPanelTitleText != null) _leftPanelTitleText.text = title;
if (_leftPanelTitleText != null) _leftPanelTitleText.text = title;
if (_leftPanelTitleGroup != null)
{
_leftPanelTitleGroup.alpha = 1f;
_leftPanelTitleGroup.gameObject.SetActive(true);
_leftPanelTitleGroup.alpha = 1f;
_leftPanelTitleGroup.interactable = true;
_leftPanelTitleGroup.blocksRaycasts = true;
}
}
@@ -617,7 +661,6 @@ namespace BriarQueen.UI.Codex
}
StopLeftPanelTitleTween();
var token = _operationCts?.Token ?? this.GetCancellationTokenOnDestroy();
_leftPanelTitleSequence = Sequence.Create(useUnscaledTime: true)
@@ -663,10 +706,8 @@ namespace BriarQueen.UI.Codex
_ => string.Empty
};
private static string GetLocationDisplayName(Location location)
{
return location.ToString().Replace("_", " ");
}
private static string GetLocationDisplayName(Location location) =>
location.ToString().Replace("_", " ");
// ── Display area ──────────────────────────────────────────────
@@ -679,7 +720,7 @@ namespace BriarQueen.UI.Codex
try
{
if (_currentEntry != null && _displayGroup.alpha > 0.001f)
if (_currentEntry != null && _rightPanelGroup.alpha > 0.001f)
await FadeOutDisplay();
token.ThrowIfCancellationRequested();
@@ -687,10 +728,9 @@ namespace BriarQueen.UI.Codex
_currentEntry = entry;
_lastEntryByCategory[_currentCategory] = entry.UniqueID;
UpdateEntryButtonSelection();
ApplyEntryToDisplay(entry);
await RefreshLayout(_displayLayoutRoot);
await RefreshLayout(_rightPanelRoot);
SetTitleVisible(false);
SetContentVisible(false);
@@ -718,9 +758,9 @@ namespace BriarQueen.UI.Codex
private async UniTask FadeInTitle(CancellationToken token)
{
if (_titleGroup == null) return;
_titleGroup.alpha = 0f;
_titleGroup.gameObject.SetActive(true);
_titleGroup.alpha = 0f;
_titleGroup.interactable = true;
_titleGroup.blocksRaycasts = true;
await Tween.Alpha(_titleGroup, new TweenSettings<float>
{
@@ -734,31 +774,31 @@ namespace BriarQueen.UI.Codex
private async UniTask FadeInContent(CancellationToken token)
{
if (_contentGroup == null) return;
if (_displayAreaGroup == null) return;
_displayAreaGroup.alpha = 0f;
_displayAreaGroup.interactable = true;
_displayAreaGroup.blocksRaycasts = true;
_contentGroup.alpha = 0f;
_contentGroup.gameObject.SetActive(true);
await Tween.Alpha(_contentGroup, new TweenSettings<float>
await Tween.Alpha(_displayAreaGroup, new TweenSettings<float>
{
startValue = 0f,
endValue = 1f,
settings = _contentFadeSettings
}).ToUniTask(cancellationToken: token);
_contentGroup.alpha = 1f;
_displayAreaGroup.alpha = 1f;
}
private async UniTask FadeOutDisplay()
{
if (_displayGroup == null || _displayGroup.alpha < 0.001f) return;
if (_rightPanelGroup == null || _rightPanelGroup.alpha < 0.001f) return;
StopDisplayTween();
_displaySequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.Alpha(_displayGroup, new TweenSettings<float>
.Group(Tween.Alpha(_rightPanelGroup, new TweenSettings<float>
{
startValue = _displayGroup.alpha,
startValue = _rightPanelGroup.alpha,
endValue = 0f,
settings = _contentFadeSettings
}));
@@ -777,15 +817,17 @@ namespace BriarQueen.UI.Codex
private void SetTitleVisible(bool visible)
{
if (_titleGroup == null) return;
_titleGroup.alpha = visible ? 1f : 0f;
_titleGroup.gameObject.SetActive(visible);
_titleGroup.alpha = visible ? 1f : 0f;
_titleGroup.interactable = visible;
_titleGroup.blocksRaycasts = visible;
}
private void SetContentVisible(bool visible)
{
if (_contentGroup == null) return;
_contentGroup.alpha = visible ? 1f : 0f;
_contentGroup.gameObject.SetActive(visible);
if (_displayAreaGroup == null) return;
_displayAreaGroup.alpha = visible ? 1f : 0f;
_displayAreaGroup.interactable = visible;
_displayAreaGroup.blocksRaycasts = visible;
}
// ── Panel fade helpers ────────────────────────────────────────
@@ -796,7 +838,6 @@ namespace BriarQueen.UI.Codex
StopLeftPanelTween();
group.alpha = 0f;
group.gameObject.SetActive(true);
group.blocksRaycasts = false;
group.interactable = false;
@@ -825,7 +866,12 @@ namespace BriarQueen.UI.Codex
{
if (group == null || group.alpha < 0.001f)
{
SetPanelImmediate(group, false);
if (group != null)
{
group.alpha = 0f;
group.interactable = false;
group.blocksRaycasts = false;
}
return;
}
@@ -849,7 +895,9 @@ namespace BriarQueen.UI.Codex
catch (OperationCanceledException) { return; }
finally { _leftPanelSequence = default; }
SetPanelImmediate(group, false);
group.alpha = 0f;
group.interactable = false;
group.blocksRaycasts = false;
}
// ── Button building ───────────────────────────────────────────
@@ -872,10 +920,8 @@ namespace BriarQueen.UI.Codex
foreach (var location in locations)
{
token.ThrowIfCancellationRequested();
var button = _locationButtonPool.Get();
if (button == null) continue;
button.Initialize(location);
button.OnLocationClicked += OnLocationClicked;
_activeLocationButtons.Add(button);
@@ -901,14 +947,11 @@ namespace BriarQueen.UI.Codex
foreach (var entry in entries)
{
token.ThrowIfCancellationRequested();
var button = _entryButtonPool.Get();
if (button == null) continue;
button.Initialize(entry);
button.OnEntryClicked += OnEntryClicked;
_activeEntryButtons.Add(button);
if (_entryButtonGroup != null && button.UnderlineButton != null)
_entryButtonGroup.AddButton(button.UnderlineButton);
}
@@ -923,14 +966,13 @@ namespace BriarQueen.UI.Codex
{
if (entry == null) { ClearDisplay(); return; }
if (_titleText != null)
_titleText.text = entry.Title;
if (_titleText != null) _titleText.text = entry.Title;
var isPhoto = entry.EntryType == CodexType.Photo || entry.IsPhotoOverride;
var showImage = isPhoto && entry.DisplayImage != null;
var showText = !string.IsNullOrWhiteSpace(entry.BodyText) || entry.IsBodyTextOverride;
var showPhotoDesc = isPhoto && !string.IsNullOrWhiteSpace(entry.PhotoDescription);
var showPolaroid = isPhoto && !string.IsNullOrWhiteSpace(entry.PolaroidWriting);
var showPhotoDesc = isPhoto && !string.IsNullOrWhiteSpace(entry.PhotoDescription);
if (_polaroid != null)
_polaroid.gameObject.SetActive(isPhoto);
@@ -942,36 +984,36 @@ namespace BriarQueen.UI.Codex
_displayImage.gameObject.SetActive(showImage);
}
if (_photoDescription != null)
{
_photoDescription.text = showPhotoDesc ? entry.PhotoDescription : string.Empty;
_photoDescription.gameObject.SetActive(showPhotoDesc);
}
if (_polaroidWriting != null)
{
_polaroidWriting.text = showPolaroid ? entry.PolaroidWriting : string.Empty;
_polaroidWriting.gameObject.SetActive(showPolaroid);
}
if (_photoDescription != null)
{
if (showPhotoDesc) { _photoDescription.gameObject.SetActive(true); _photoDescription.SetText(entry.PhotoDescription); }
else { _photoDescription.Clear(); _photoDescription.gameObject.SetActive(false); }
}
if (_bodyText != null)
{
_bodyText.text = showText ? entry.BodyText : string.Empty;
_bodyText.gameObject.SetActive(showText);
if (showText) { _bodyText.gameObject.SetActive(true); _bodyText.SetText(entry.BodyText); }
else { _bodyText.Clear(); _bodyText.gameObject.SetActive(false); }
}
SetEmptyStateVisible(false);
if (_contentRoot != null) _contentRoot.SetActive(true);
if (_displayAreaRoot != null) _displayAreaRoot.SetActive(true);
}
private void ClearDisplay()
{
if (_titleText != null) _titleText.text = string.Empty;
if (_bodyText != null) { _bodyText.text = string.Empty; _bodyText.gameObject.SetActive(false); }
if (_polaroid != null) _polaroid.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); }
if (_titleText != null) _titleText.text = string.Empty;
if (_bodyText != null) { _bodyText.Clear(); _bodyText.gameObject.SetActive(false); }
if (_photoDescription != null) { _photoDescription.Clear(); _photoDescription.gameObject.SetActive(false); }
if (_polaroid != null) _polaroid.gameObject.SetActive(false);
if (_displayImage != null) { _displayImage.sprite = null; _displayImage.enabled = false; _displayImage.gameObject.SetActive(false); }
if (_polaroidWriting != null) { _polaroidWriting.text = string.Empty; _polaroidWriting.gameObject.SetActive(false); }
}
// ── Selection state ───────────────────────────────────────────
@@ -981,13 +1023,10 @@ namespace BriarQueen.UI.Codex
foreach (var button in _activeEntryButtons)
{
if (button == null) continue;
var isSelected = _currentEntry != null &&
button.Entry != null &&
button.Entry.UniqueID == _currentEntry.UniqueID;
button.SetSelected(isSelected);
if (_entryButtonGroup != null && button.UnderlineButton != null && isSelected)
_entryButtonGroup.SelectButton(button.UnderlineButton);
}
@@ -1050,26 +1089,19 @@ namespace BriarQueen.UI.Codex
}
// ── Binding ───────────────────────────────────────────────────
private void InitializeCategoryButtons()
{
if (_booksButton != null) _booksButton.Initialize(CodexType.DocumentEntry);
if (_cluesButton != null) _cluesButton.Initialize(CodexType.PuzzleClue);
if (_photosButton != null) _photosButton.Initialize(CodexType.Photo);
}
private void BindCategoryButtons()
{
if (_booksButton != null) _booksButton.OnCategoryClicked += OnCategoryClicked;
if (_cluesButton != null) _cluesButton.OnCategoryClicked += OnCategoryClicked;
if (_photosButton != null) _photosButton.OnCategoryClicked += OnCategoryClicked;
if (_booksButton != null) _booksButton.SelectionRequested += OnDocumentsClicked;
if (_cluesButton != null) _cluesButton.SelectionRequested += OnCluesClicked;
if (_photosButton != null) _photosButton.SelectionRequested += OnPhotosClicked;
}
private void UnbindCategoryButtons()
{
if (_booksButton != null) _booksButton.OnCategoryClicked -= OnCategoryClicked;
if (_cluesButton != null) _cluesButton.OnCategoryClicked -= OnCategoryClicked;
if (_photosButton != null) _photosButton.OnCategoryClicked -= OnCategoryClicked;
if (_booksButton != null) _booksButton.SelectionRequested -= OnDocumentsClicked;
if (_cluesButton != null) _cluesButton.SelectionRequested -= OnCluesClicked;
if (_photosButton != null) _photosButton.SelectionRequested -= OnPhotosClicked;
}
private void BindNavButtons()
@@ -1088,24 +1120,6 @@ namespace BriarQueen.UI.Codex
_backToLocationsButton.onClick.RemoveListener(OnBackToLocationsClicked);
}
// ── Raycaster ─────────────────────────────────────────────────
private void TryRegisterRaycaster()
{
if (_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return;
_interactManager.AddUIRaycaster(_graphicRaycaster);
_interactManager.SetExclusiveRaycaster(_graphicRaycaster);
_raycasterRegistered = true;
}
private void TryUnregisterRaycaster()
{
if (!_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return;
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
_interactManager.ClearExclusiveRaycaster();
_raycasterRegistered = false;
}
// ── Layout ────────────────────────────────────────────────────
private async UniTask RefreshLayout(RectTransform root)
@@ -1142,7 +1156,6 @@ namespace BriarQueen.UI.Codex
group.alpha = visible ? 1f : 0f;
group.interactable = visible;
group.blocksRaycasts = visible;
group.gameObject.SetActive(visible);
}
private static void SetCanvasGroupImmediate(CanvasGroup group, float alpha, bool interactable)
@@ -1162,17 +1175,16 @@ namespace BriarQueen.UI.Codex
private void SetDisplayImmediate(bool visible)
{
if (_displayGroup == null) return;
_displayGroup.alpha = visible ? 1f : 0f;
_displayGroup.interactable = visible;
_displayGroup.blocksRaycasts = visible;
_displayGroup.gameObject.SetActive(visible);
if (_rightPanelGroup == null) return;
_rightPanelGroup.alpha = visible ? 1f : 0f;
_rightPanelGroup.interactable = visible;
_rightPanelGroup.blocksRaycasts = visible;
}
private void SetEmptyStateVisible(bool visible)
{
if (_emptyStateRoot != null) _emptyStateRoot.SetActive(visible);
if (_contentRoot != null) _contentRoot.SetActive(!visible);
if (_emptyStateRoot != null) _emptyStateRoot.SetActive(visible);
if (_displayAreaRoot != null) _displayAreaRoot.SetActive(!visible);
}
// ── CTS helpers ───────────────────────────────────────────────

View File

@@ -0,0 +1,144 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.UI.Components
{
[ExecuteAlways]
public class ScrollableTextBox : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI _text;
[SerializeField] private RectTransform _content;
[SerializeField] private BriarQueen.UI.Menus.VerticalScrollbar _scrollbar;
private RectTransform _textRect;
private LayoutElement _layoutElement;
private void Awake()
{
Init();
}
private void OnValidate()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.delayCall += () =>
{
if (this == null) return;
Init();
if (_text != null && !string.IsNullOrEmpty(_text.text))
ApplyTextHeight();
};
#endif
}
private void Init()
{
if (_text != null)
{
_textRect = _text.GetComponent<RectTransform>();
_layoutElement = _text.GetComponent<LayoutElement>();
if (_layoutElement == null)
_layoutElement = _text.gameObject.AddComponent<LayoutElement>();
}
}
private void ApplyTextHeight()
{
_text.ForceMeshUpdate();
var height = _text.preferredHeight;
if (_layoutElement != null)
_layoutElement.preferredHeight = height;
if (_textRect != null)
{
var delta = _textRect.sizeDelta;
delta.y = height;
_textRect.sizeDelta = delta;
}
if (_content != null)
{
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
}
}
public void SetText(string content)
{
if (_text == null) return;
_text.text = content;
_text.ForceMeshUpdate();
var height = _text.preferredHeight;
if (_layoutElement != null)
_layoutElement.preferredHeight = height;
if (_textRect != null)
{
var delta = _textRect.sizeDelta;
delta.y = height;
_textRect.sizeDelta = delta;
}
// Also resize Content to match so scrollbar measures correctly
if (_content != null)
{
var delta = _content.sizeDelta;
delta.y = height;
_content.sizeDelta = delta;
var pos = _content.anchoredPosition;
pos.y = 0f;
_content.anchoredPosition = pos;
}
if (Application.isPlaying)
StartCoroutine(RebuildNextFrame());
else
_scrollbar?.Rebuild();
}
private System.Collections.IEnumerator RebuildNextFrame()
{
yield return null;
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
yield return null;
_scrollbar?.Rebuild();
_scrollbar?.SetNormalized(1f);
}
public void Clear()
{
if (_text == null) return;
_text.text = string.Empty;
if (_layoutElement != null)
_layoutElement.preferredHeight = 0f;
if (_textRect != null)
{
var sd = _textRect.sizeDelta;
sd.y = 0f;
_textRect.sizeDelta = sd;
}
if (_content != null)
{
var pos = _content.anchoredPosition;
pos.y = 0f;
_content.anchoredPosition = pos;
}
_scrollbar?.Rebuild();
}
public void ScrollToTop() => _scrollbar?.SetNormalized(1f);
public void ScrollToBottom() => _scrollbar?.SetNormalized(0f);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c3727dc2fa2f418f82fcfae0993856cb
timeCreated: 1778584851

View File

@@ -15,11 +15,17 @@ namespace BriarQueen.UI.Menus.Components
IDeselectHandler
{
[Header("References")]
[SerializeField] private TextMeshProUGUI _label;
[SerializeField] protected TextMeshProUGUI _label;
private Button _button;
private UnderlineButtonGroup _group;
private bool _isHovered;
[SerializeField]
protected TextMeshProUGUI _contextLabel;
[SerializeField]
protected string _contextText;
protected Button _button;
protected UnderlineButtonGroup _group;
protected bool _isHovered;
public event Action<UnderlineButton> SelectionRequested;
public event Action<UnderlineButton> HoverEntered;
@@ -29,14 +35,15 @@ namespace BriarQueen.UI.Menus.Components
// True when this button is registered with a group
public bool IsGrouped => _group != null;
private void Awake()
protected virtual void Awake()
{
_button = GetComponent<Button>();
SetUnderline(false);
}
private void OnDisable()
protected virtual void OnDisable()
{
_isHovered = false;
SetUnderline(false);
@@ -68,6 +75,8 @@ namespace BriarQueen.UI.Menus.Components
HoverEntered?.Invoke(this);
else
RefreshStandaloneState();
UpdateContextText();
}
public void OnPointerExit(PointerEventData eventData)
@@ -78,10 +87,13 @@ namespace BriarQueen.UI.Menus.Components
HoverExited?.Invoke(this);
else
RefreshStandaloneState();
UpdateContextText();
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log($"[UnderlineButton] OnPointerClick {gameObject.name}");
if (_button != null && !_button.interactable) return;
SelectionRequested?.Invoke(this);
}
@@ -102,6 +114,8 @@ namespace BriarQueen.UI.Menus.Components
IsSelected = true;
RefreshStandaloneState();
}
UpdateContextText();
}
public void OnDeselect(BaseEventData eventData)
@@ -115,6 +129,8 @@ namespace BriarQueen.UI.Menus.Components
IsSelected = false;
RefreshStandaloneState();
}
UpdateContextText();
}
// ── Public API ────────────────────────────────────────────────
@@ -154,5 +170,16 @@ namespace BriarQueen.UI.Menus.Components
SetUnderline(IsSelected || _isHovered);
}
private void UpdateContextText()
{
if (_contextLabel == null)
return;
if(IsSelected || _isHovered)
_contextLabel.text = _contextText;
else
_contextLabel.text = string.Empty;
}
}
}

View File

@@ -5,11 +5,14 @@ using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.Audio;
using BriarQueen.Framework.Managers.Interaction;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Managers.Levels;
using BriarQueen.Framework.Managers.UI.Base;
using BriarQueen.Framework.Managers.UI.Events;
using BriarQueen.Framework.Services.Game;
using BriarQueen.UI.Menus.Components;
using Cysharp.Threading.Tasks;
using PrimeTween;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
@@ -20,226 +23,392 @@ namespace BriarQueen.UI.Menus
public class PauseMenuWindow : MonoBehaviour, IUIWindow
{
[Header("Root UI")]
[SerializeField]
private CanvasGroup _canvasGroup;
[SerializeField] private CanvasGroup _canvasGroup;
[SerializeField] private RectTransform _windowRect;
[SerializeField]
private RectTransform _windowRect;
[Header("Background")]
[SerializeField] private CanvasGroup _backgroundGroup;
[Header("Transient Info")]
[SerializeField] private TextMeshProUGUI _levelName;
[Header("Buttons")]
[SerializeField]
private Button _resumeButton;
[SerializeField] private UnderlineButton _resumeButton;
[SerializeField] private UnderlineButton _saveButton;
[SerializeField] private UnderlineButton _settingsButton;
[SerializeField] private UnderlineButton _exitButton;
[SerializeField] private UnderlineButton _quitToDesktopButton;
[SerializeField]
private Button _saveButton;
[SerializeField]
private Button _settingsButton;
[SerializeField]
private Button _exitButton;
[SerializeField]
private Button _quitToDesktopButton;
[Header("Layout")]
[SerializeField] private CanvasGroup _buttonsGroup;
[Header("Selection")]
[SerializeField]
private Selectable _firstSelectedOnOpen;
[SerializeField] private Selectable _firstSelectedOnOpen;
[Header("Tween Settings")]
[SerializeField]
private TweenSettings _tweenSettings = new()
[SerializeField] private TweenSettings _backgroundFadeSettings = new()
{
duration = 0.25f,
ease = Ease.OutQuad,
duration = 0.4f,
ease = Ease.OutQuad,
useUnscaledTime = true
};
[SerializeField] private TweenSettings _buttonFadeSettings = new()
{
duration = 0.25f,
ease = Ease.OutQuad,
useUnscaledTime = true
};
[Header("Scale")]
[SerializeField]
private float _hiddenScale = 0.85f;
[SerializeField] private float _hiddenScale = 0.85f;
[Header("Internal")]
[SerializeField]
private GraphicRaycaster _graphicRaycaster;
[SerializeField] private GraphicRaycaster _graphicRaycaster;
private AudioManager _audioManager;
private CancellationTokenSource _cts;
private EventCoordinator _eventCoordinator;
private GameService _gameService;
private InteractManager _interactManager;
private SaveManager _saveManager;
private LevelManager _levelManager;
private CancellationTokenSource _cts;
private Sequence _sequence;
private bool _raycasterRegistered;
public bool IsModal => true;
public WindowType WindowType => WindowType.PauseMenuWindow;
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;
}
// ── Unity lifecycle ───────────────────────────────────────────
private void Awake()
{
// Start hidden by default
if (_canvasGroup != null)
{
_canvasGroup.alpha = 0f;
_canvasGroup.alpha = 0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
_canvasGroup.interactable = false;
}
if (_windowRect != null)
_windowRect.localScale = Vector3.one * _hiddenScale;
_windowRect.localScale = Vector3.one;
SetGroupImmediate(_backgroundGroup, 0f);
HideButtons();
}
private void OnEnable()
{
if (_resumeButton != null) _resumeButton.onClick.AddListener(OnResumeButtonClick);
if (_saveButton != null) _saveButton.onClick.AddListener(OnSaveButtonClick);
if (_settingsButton != null) _settingsButton.onClick.AddListener(OnSettingsButtonClick);
if (_exitButton != null) _exitButton.onClick.AddListener(OnExitButtonClick);
if (_quitToDesktopButton != null) _quitToDesktopButton.onClick.AddListener(OnQuitToDesktopButtonClick);
_interactManager.SetExclusiveRaycaster(_graphicRaycaster);
BindButtons();
}
private void OnDisable()
{
if (_resumeButton != null) _resumeButton.onClick.RemoveListener(OnResumeButtonClick);
if (_saveButton != null) _saveButton.onClick.RemoveListener(OnSaveButtonClick);
if (_settingsButton != null) _settingsButton.onClick.RemoveListener(OnSettingsButtonClick);
if (_exitButton != null) _exitButton.onClick.RemoveListener(OnExitButtonClick);
if (_quitToDesktopButton != null) _quitToDesktopButton.onClick.RemoveListener(OnQuitToDesktopButtonClick);
_interactManager.ClearExclusiveRaycaster();
UnbindButtons();
}
private void OnDestroy()
{
StopAndResetCancellation();
TryUnregisterRaycaster();
}
// ── IUIWindow ─────────────────────────────────────────────────
public async UniTask Show()
{
if (_canvasGroup == null || _windowRect == null)
{
Debug.LogError("[PauseMenuWindow] Missing CanvasGroup or WindowRect reference.");
gameObject.SetActive(true);
return;
}
StopAndResetCancellation();
SetLevelName();
gameObject.SetActive(true);
_canvasGroup.alpha = 0f;
TryRegisterRaycaster();
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
_canvasGroup.interactable = false;
_windowRect.localScale = Vector3.one * _hiddenScale;
var alpha = new TweenSettings<float>
{
startValue = 0f,
endValue = 1f,
settings = _tweenSettings
};
var scale = new TweenSettings<Vector3>
{
startValue = Vector3.one * _hiddenScale,
endValue = Vector3.one,
settings = _tweenSettings
};
_sequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.Alpha(_canvasGroup, alpha))
.Group(Tween.Scale(_windowRect, scale));
SetGroupImmediate(_backgroundGroup, 0f);
HideButtons();
try
{
await _sequence.ToUniTask(cancellationToken: _cts.Token);
var fadeWindowTask = FadeGroup(_canvasGroup, 1f, _backgroundFadeSettings, _cts.Token);
var fadeBackgroundTask = FadeGroup(_backgroundGroup, 1f, _backgroundFadeSettings, _cts.Token);
await UniTask.WhenAll(fadeWindowTask, fadeBackgroundTask);
_backgroundGroup.blocksRaycasts = true;
_backgroundGroup.interactable = true;
_buttonsGroup.blocksRaycasts = true;
_buttonsGroup.interactable = true;
await FadeGroup(_buttonsGroup, 1f, _buttonFadeSettings, _cts.Token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
_sequence = default;
}
_canvasGroup.alpha = 1f;
_windowRect.localScale = Vector3.one;
_canvasGroup.blocksRaycasts = true;
_canvasGroup.interactable = true;
SelectDefault();
_canvasGroup.interactable = true;
}
public async UniTask Hide()
{
if (_canvasGroup == null || _windowRect == null)
{
gameObject.SetActive(false);
return;
}
StopAndResetCancellation();
// Block clicks immediately
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
var alpha = new TweenSettings<float>
{
startValue = _canvasGroup.alpha,
endValue = 0f,
settings = _tweenSettings
};
var scale = new TweenSettings<Vector3>
{
startValue = _windowRect.localScale,
endValue = Vector3.one * _hiddenScale,
settings = _tweenSettings
};
_sequence = Sequence.Create(useUnscaledTime: true)
.Group(Tween.Alpha(_canvasGroup, alpha))
.Group(Tween.Scale(_windowRect, scale));
_canvasGroup.interactable = false;
TryUnregisterRaycaster();
try
{
await _sequence.ToUniTask(cancellationToken: _cts.Token);
await FadeGroup(_buttonsGroup, 0f, _buttonFadeSettings, _cts.Token);
await FadeGroup(_backgroundGroup, 0f, _backgroundFadeSettings, _cts.Token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
_sequence = default;
}
_resumeButton.SetSelectedState(false);
_saveButton.SetSelectedState(false);
_settingsButton.SetSelectedState(false);
_exitButton.SetSelectedState(false);
_quitToDesktopButton.SetSelectedState(false);
_canvasGroup.alpha = 0f;
_windowRect.localScale = Vector3.one * _hiddenScale;
_canvasGroup.alpha = 0f;
_canvasGroup.blocksRaycasts = false;
_canvasGroup.interactable = false;
gameObject.SetActive(false);
}
// ── DI ────────────────────────────────────────────────────────
[Inject]
public void Construct(EventCoordinator eventCoordinator, SaveManager saveManager, GameService gameService,
InteractManager interactManager, AudioManager audioManager)
public void Construct(
EventCoordinator eventCoordinator,
SaveManager saveManager,
GameService gameService,
InteractManager interactManager,
AudioManager audioManager,
LevelManager levelManager)
{
_eventCoordinator = eventCoordinator;
_saveManager = saveManager;
_gameService = gameService;
_interactManager = interactManager;
_audioManager = audioManager;
_saveManager = saveManager;
_gameService = gameService;
_interactManager = interactManager;
_audioManager = audioManager;
_levelManager = levelManager;
}
// ── Level ─────────────────────────────────────────────────────
private void SetLevelName()
{
if (_levelManager == null || _levelName == null)
return;
var currentLevel = _levelManager.CurrentLevel;
if (!string.IsNullOrEmpty(currentLevel.LevelName))
_levelName.text = currentLevel.LevelName;
}
// ── Button binding ────────────────────────────────────────────
private void BindButtons()
{
if (_resumeButton != null)
_resumeButton.SelectionRequested += OnResumeClicked;
if (_saveButton != null)
_saveButton.SelectionRequested += OnSaveClicked;
if (_settingsButton != null)
_settingsButton.SelectionRequested += OnSettingsClicked;
if (_exitButton != null)
_exitButton.SelectionRequested += OnExitClicked;
if (_quitToDesktopButton != null)
_quitToDesktopButton.SelectionRequested += OnQuitToDesktopClicked;
}
private void UnbindButtons()
{
if (_resumeButton != null)
_resumeButton.SelectionRequested -= OnResumeClicked;
if (_saveButton != null)
_saveButton.SelectionRequested -= OnSaveClicked;
if (_settingsButton != null)
_settingsButton.SelectionRequested -= OnSettingsClicked;
if (_exitButton != null)
_exitButton.SelectionRequested -= OnExitClicked;
if (_quitToDesktopButton != null)
_quitToDesktopButton.SelectionRequested -= OnQuitToDesktopClicked;
}
// ── Button callbacks ──────────────────────────────────────────
private void OnResumeClicked(UnderlineButton _)
{
_eventCoordinator?.Publish(new PauseButtonClickedEvent());
}
private void OnSaveClicked(UnderlineButton _)
{
SaveGame().Forget();
}
private void OnSettingsClicked(UnderlineButton _)
{
_eventCoordinator?.Publish(new UIToggleSettingsWindow(true));
}
private void OnExitClicked(UnderlineButton _)
{
_eventCoordinator?.Publish(new FadeEvent(false, 1f));
ExitInternal().Forget();
}
private void OnQuitToDesktopClicked(UnderlineButton _)
{
QuitInternal().Forget();
}
// ── Internal async ────────────────────────────────────────────
private async UniTask ExitInternal()
{
await UniTask.Delay(TimeSpan.FromSeconds(1));
_audioManager?.StopAllAudio();
await SaveGame();
_eventCoordinator?.Publish(new PauseButtonClickedEvent());
await _gameService.LoadMainMenu();
}
private async UniTask QuitInternal()
{
_eventCoordinator?.Publish(new FadeEvent(false, 1f));
await UniTask.Delay(TimeSpan.FromSeconds(1));
await SaveGame();
_gameService?.QuitGame();
}
private async UniTask SaveGame()
{
if (_saveManager == null)
return;
await _saveManager.SaveGameDataLatest();
}
// ── Layout helpers ────────────────────────────────────────────
private void HideButtons()
{
SetGroupImmediate(_buttonsGroup, 0f);
}
private static void SetGroupImmediate(CanvasGroup group, float alpha)
{
if (group == null)
return;
group.alpha = alpha;
group.interactable = false;
group.blocksRaycasts = false;
}
private async UniTask FadeGroup(
CanvasGroup group,
float target,
TweenSettings settings,
CancellationToken token)
{
if (group == null)
return;
await Tween.Alpha(group, new TweenSettings<float>
{
startValue = group.alpha,
endValue = target,
settings = settings
}).ToUniTask(cancellationToken: token);
group.alpha = target;
}
// ── Selection ─────────────────────────────────────────────────
private void SelectDefault()
{
if (EventSystem.current == null)
return;
EventSystem.current.SetSelectedGameObject(null);
if (_firstSelectedOnOpen != null)
{
EventSystem.current.SetSelectedGameObject(
_firstSelectedOnOpen.gameObject);
}
else if (_resumeButton != null)
{
EventSystem.current.SetSelectedGameObject(
_resumeButton.gameObject);
}
Debug.Log(
$"[PauseMenuWindow] Selected: {EventSystem.current.currentSelectedGameObject}");
}
// ── CTS ───────────────────────────────────────────────────────
private void StopAndResetCancellation()
{
if (_sequence.isAlive)
@@ -256,77 +425,9 @@ namespace BriarQueen.UI.Menus
}
_cts.Dispose();
_cts = null;
}
_cts = new CancellationTokenSource();
}
private void OnResumeButtonClick()
{
_eventCoordinator?.Publish(new PauseButtonClickedEvent());
}
private void SelectDefault()
{
var selectable = _firstSelectedOnOpen != null ? _firstSelectedOnOpen : _resumeButton;
if (selectable == null)
{
return;
}
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(selectable.gameObject);
return;
}
selectable.Select();
}
private void OnSaveButtonClick()
{
SaveGame().Forget();
}
private void OnSettingsButtonClick()
{
_eventCoordinator.Publish(new UIToggleSettingsWindow(true));
}
private void OnExitButtonClick()
{
_eventCoordinator.Publish(new FadeEvent(false, 1f));
ExitButtonInternal().Forget();
}
private async UniTask ExitButtonInternal()
{
await UniTask.Delay(TimeSpan.FromSeconds(1));
_audioManager.StopAllAudio();
await SaveGame();
_eventCoordinator.Publish(new PauseButtonClickedEvent());
await _gameService.LoadMainMenu();
}
private void OnQuitToDesktopButtonClick()
{
QuitButtonInternal().Forget();
}
private async UniTask QuitButtonInternal()
{
_eventCoordinator.Publish(new FadeEvent(false, 1f));
await UniTask.Delay(TimeSpan.FromSeconds(1));
await SaveGame();
_gameService.QuitGame();
}
private async UniTask SaveGame()
{
if (_saveManager == null) return;
await _saveManager.SaveGameDataLatest();
// TODO: Saved feedback popup/toast
}
}
}
}

View File

@@ -205,6 +205,7 @@ namespace BriarQueen.UI.Menus
{
StopAndResetCancellation();
CancelAndDisposePanelCts();
TryUnregisterRaycaster();
if (_applyButton != null) _applyButton.onClick.RemoveListener(OnApplyClicked);
if (_backButton != null) _backButton.onClick.RemoveListener(OnBackClicked);
@@ -915,7 +916,6 @@ namespace BriarQueen.UI.Menus
return;
_interactManager.AddUIRaycaster(_graphicRaycaster);
_interactManager?.SetExclusiveRaycaster(_graphicRaycaster);
_raycasterRegistered = true;
}
@@ -928,7 +928,6 @@ namespace BriarQueen.UI.Menus
return;
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
_interactManager?.ClearExclusiveRaycaster();
_raycasterRegistered = false;
}
}

View File

@@ -17,51 +17,28 @@ namespace BriarQueen.UI.Menus
private static readonly Vector3[] Corners = new Vector3[4];
[Header("Hierarchy")]
[SerializeField]
private RectTransform _viewport;
[SerializeField]
private RectTransform _content;
[SerializeField]
private RectTransform _trackRect;
[SerializeField]
private RectTransform _handleRect;
[SerializeField]
private Image _scrollBarImage;
[SerializeField] private RectTransform _viewport;
[SerializeField] private RectTransform _content;
[SerializeField] private RectTransform _trackRect;
[SerializeField] private RectTransform _handleRect;
[SerializeField] private Image _scrollBarImage;
[Header("Scroll Settings")]
[SerializeField]
private float _wheelPixels = 80f;
[SerializeField]
private float _padSpeed = 900f;
[SerializeField]
private float _inputSystemWheelScale = 0.05f;
[SerializeField] private float _wheelPixels = 80f;
[SerializeField] private float _padSpeed = 900f;
[SerializeField] private float _inputSystemWheelScale = 0.05f;
[Header("Handle")]
[SerializeField]
private bool _useCustomHandleSizing;
[SerializeField]
private float _minHandleHeight = 24f;
[SerializeField] private bool _useCustomHandleSizing;
[SerializeField] private float _minHandleHeight = 24f;
[Header("Alignment")]
[SerializeField]
private bool _centerContentWhenNotScrollable = true;
[SerializeField]
private float _topInset = 6f;
[SerializeField]
private float _bottomInset = 6f;
[SerializeField] private bool _centerContentWhenNotScrollable = true;
[SerializeField] private float _topInset = 6f;
[SerializeField] private float _bottomInset = 6f;
[Header("Track")]
[SerializeField]
private bool _hideTrackWhenNotScrollable = true;
[SerializeField] private bool _hideTrackWhenNotScrollable = true;
#if UNITY_EDITOR
[Header("Editor Debug")]
@@ -70,7 +47,7 @@ namespace BriarQueen.UI.Menus
private float _debugNormalized;
#endif
private bool _isScrollable;
private bool _isScrollable;
private float _scrollRange;
private Camera _uiCamera;
@@ -108,11 +85,14 @@ namespace BriarQueen.UI.Menus
if (!TryGetContentBounds(out var top, out var bottom))
return;
var contentHeight = top - bottom;
var contentHeight = top - bottom;
var viewportHeight = _viewport.rect.height - _topInset - _bottomInset;
_isScrollable = contentHeight > viewportHeight;
_scrollRange = Mathf.Max(0f, contentHeight - viewportHeight);
_scrollRange = Mathf.Max(0f, contentHeight - viewportHeight);
Debug.Log($"[Scrollbar] OnValidate — contentHeight:{contentHeight} viewportHeight:{viewportHeight} isScrollable:{_isScrollable} scrollRange:{_scrollRange}");
Normalized = Mathf.Clamp01(_debugNormalized);
if (_centerContentWhenNotScrollable && !_isScrollable)
@@ -155,11 +135,13 @@ namespace BriarQueen.UI.Menus
if (!TryGetContentBounds(out var top, out var bottom))
return;
var contentHeight = top - bottom;
var contentHeight = top - bottom;
var viewportHeight = _viewport.rect.height - _topInset - _bottomInset;
_isScrollable = contentHeight > viewportHeight;
_scrollRange = Mathf.Max(0f, contentHeight - viewportHeight);
_scrollRange = Mathf.Max(0f, contentHeight - viewportHeight);
Debug.Log($"[Scrollbar] Rebuild — contentHeight:{contentHeight} viewportHeight:{viewportHeight} isScrollable:{_isScrollable} scrollRange:{_scrollRange}");
if (_centerContentWhenNotScrollable && !_isScrollable)
{
@@ -179,6 +161,8 @@ namespace BriarQueen.UI.Menus
{
Normalized = Mathf.Clamp01(normalized);
Debug.Log($"[Scrollbar] SetNormalized:{normalized} isScrollable:{_isScrollable} scrollRange:{_scrollRange}");
if (!_isScrollable)
return;
@@ -193,13 +177,13 @@ namespace BriarQueen.UI.Menus
private void CenterContent(float top, float bottom)
{
var contentCenter = (top + bottom) * 0.5f;
var contentCenter = (top + bottom) * 0.5f;
var viewportCenter = (_viewport.rect.yMin + _viewport.rect.yMax) * 0.5f;
var delta = viewportCenter - contentCenter;
var position = _content.anchoredPosition;
position.y += delta;
position.y += delta;
_content.anchoredPosition = position;
}
@@ -222,13 +206,13 @@ namespace BriarQueen.UI.Menus
first.GetWorldCorners(Corners);
var childTop = _viewport.InverseTransformPoint(Corners[1]).y;
var childTop = _viewport.InverseTransformPoint(Corners[1]).y;
var targetTop = _viewport.rect.yMax - _topInset;
var delta = targetTop - childTop;
var position = _content.anchoredPosition;
position.y += delta;
position.y += delta;
_content.anchoredPosition = position;
}
@@ -238,7 +222,7 @@ namespace BriarQueen.UI.Menus
return;
var current = Normalized * _scrollRange;
var next = Mathf.Clamp(current + pixels, 0f, _scrollRange);
var next = Mathf.Clamp(current + pixels, 0f, _scrollRange);
Normalized = _scrollRange > 0f ? next / _scrollRange : 0f;
SetNormalized(Normalized);
@@ -258,7 +242,7 @@ namespace BriarQueen.UI.Menus
var min = _trackRect.rect.yMin + halfHandleHeight;
var max = _trackRect.rect.yMax - halfHandleHeight;
var y = Mathf.Clamp(localPoint.y, min, max);
var y = Mathf.Clamp(localPoint.y, min, max);
var normalized = 1f - Mathf.InverseLerp(min, max, y);
SetNormalized(normalized);
@@ -284,8 +268,9 @@ namespace BriarQueen.UI.Menus
private void SetContentY(float y)
{
Debug.Log($"[Scrollbar] SetContentY:{y}");
var position = _content.anchoredPosition;
position.y = y;
position.y = y;
_content.anchoredPosition = position;
#if UNITY_EDITOR
@@ -308,22 +293,15 @@ namespace BriarQueen.UI.Menus
return;
}
if (_useCustomHandleSizing)
{
var ratio = Mathf.Clamp01(_viewport.rect.height / (_scrollRange + _viewport.rect.height));
var height = Mathf.Max(_trackRect.rect.height * ratio, _minHandleHeight);
_handleRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
}
var half = _handleRect.rect.height * 0.5f;
var min = _trackRect.rect.yMin + half;
var max = _trackRect.rect.yMax - half;
var yPos = Mathf.Lerp(max, min, Normalized);
var min = _trackRect.rect.yMin + half;
var max = _trackRect.rect.yMax - half;
var y = Mathf.Lerp(max, min, Normalized);
Debug.Log($"[Scrollbar] UpdateHandle — half:{half} trackYMin:{_trackRect.rect.yMin} trackYMax:{_trackRect.rect.yMax} min:{min} max:{max} yPos:{yPos} Normalized:{Normalized}");
var position = _handleRect.anchoredPosition;
position.y = y;
position.y = yPos;
_handleRect.anchoredPosition = position;
}
@@ -341,7 +319,7 @@ namespace BriarQueen.UI.Menus
private bool TryGetContentBounds(out float top, out float bottom)
{
top = float.MinValue;
top = float.MinValue;
bottom = float.MaxValue;
var found = false;
@@ -357,13 +335,15 @@ namespace BriarQueen.UI.Menus
for (var c = 0; c < 4; c++)
{
var local = _viewport.InverseTransformPoint(Corners[c]);
top = Mathf.Max(top, local.y);
top = Mathf.Max(top, local.y);
bottom = Mathf.Min(bottom, local.y);
}
found = true;
Debug.Log($"[Scrollbar] Child: {child.name} top:{top} bottom:{bottom} height:{top - bottom}");
}
Debug.Log($"[Scrollbar] Found:{found} ContentHeight:{top - bottom} ViewportHeight:{_viewport.rect.height}");
return found;
}
}