All working but codex.
This commit is contained in:
144
Assets/Scripts/UI/Menus/Components/ScrollableTextBox.cs
Normal file
144
Assets/Scripts/UI/Menus/Components/ScrollableTextBox.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3727dc2fa2f418f82fcfae0993856cb
|
||||
timeCreated: 1778584851
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user