using System; using System.Collections.Generic; using BriarQueen.Framework.Coordinators.Events; using BriarQueen.Framework.Events.UI; using UnityEngine; using UnityEngine.UI; using VContainer; namespace BriarQueen.Framework.Managers.UI { public sealed class UICursorService : MonoBehaviour { public enum CursorStyle { Default, Interact, Talk, Pickup, Inspect, UseItem, Travel, Knife, } [Header("System Cursor")] [SerializeField] private bool _enableCustomCursor = true; [SerializeField] private bool _restoreDefaultCursorOnDisable = true; [Header("Software Cursor")] [SerializeField] private RectTransform _virtualCursorTransform; [SerializeField] private Image _virtualCursorImage; [Header("Styles")] [SerializeField] private CursorStyle _startingStyle = CursorStyle.Default; [SerializeField] private List _styles = new(); private readonly Dictionary _styleMap = new(); private EventCoordinator _eventCoordinator; private CursorStyle _currentStyle; private CursorStyle _currentStyleOverride = CursorStyle.Default; private bool _isStyleOverridden; private Texture2D _currentTexture; private Vector2 _currentHotspot; private CursorMode _currentMode; private bool _useVirtualCursor; public CursorStyleEntry CurrentStyleEntry => _styleMap[_currentStyle]; [Inject] private void Construct(EventCoordinator eventCoordinator) { _eventCoordinator = eventCoordinator; } private void Awake() { BuildStyleMap(); _currentStyle = _startingStyle; _currentStyleOverride = CursorStyle.Default; _isStyleOverridden = false; } private void OnEnable() { _eventCoordinator?.Subscribe(OnCursorStyleChangeEvent); _eventCoordinator?.Subscribe(OnOverrideCursorStyleChangeEvent); ApplyCurrentEffectiveStyle(); ApplyCursorModeVisuals(); } private void OnDisable() { _eventCoordinator?.Unsubscribe(OnCursorStyleChangeEvent); _eventCoordinator?.Unsubscribe(OnOverrideCursorStyleChangeEvent); if (_restoreDefaultCursorOnDisable) Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); } private void OnValidate() { BuildStyleMap(); } public void SetUseVirtualCursor(bool useVirtualCursor) { if (_useVirtualCursor == useVirtualCursor) return; _useVirtualCursor = useVirtualCursor; ApplyCursorModeVisuals(); ApplyCurrentEffectiveStyle(); } public void SetVirtualCursorPosition(Vector2 screenPosition) { if (!_useVirtualCursor || _virtualCursorTransform == null) return; _virtualCursorTransform.position = screenPosition; } public void SetStyle(CursorStyle style) { _currentStyle = style; ApplyCurrentEffectiveStyle(); } public void SetOverrideStyle(CursorStyle style) { if (style == CursorStyle.Default) { ClearOverrideStyle(); return; } _isStyleOverridden = true; _currentStyleOverride = style; ApplyCurrentEffectiveStyle(); } public void ClearOverrideStyle() { _isStyleOverridden = false; _currentStyleOverride = CursorStyle.Default; ApplyCurrentEffectiveStyle(); } public void ResetToDefault() { _currentStyle = CursorStyle.Default; ClearOverrideStyle(); } private void OnCursorStyleChangeEvent(CursorStyleChangeEvent evt) { _currentStyle = evt.Style; ApplyCurrentEffectiveStyle(); } private void OnOverrideCursorStyleChangeEvent(OverrideCursorStyleChangeEvent evt) { if (evt.Style == CursorStyle.Default) { ClearOverrideStyle(); return; } _isStyleOverridden = true; _currentStyleOverride = evt.Style; ApplyCurrentEffectiveStyle(); } private void ApplyCursorModeVisuals() { Cursor.visible = !_useVirtualCursor; if (_virtualCursorImage != null) _virtualCursorImage.enabled = _useVirtualCursor; } private void ApplyCurrentEffectiveStyle() { var style = GetEffectiveStyle(); if (_useVirtualCursor) { ApplyVirtualCursorStyle(style); } else { ApplySystemCursorStyle(style); } } private CursorStyle GetEffectiveStyle() { return _isStyleOverridden ? _currentStyleOverride : _currentStyle; } private void ApplyVirtualCursorStyle(CursorStyle style) { if (_virtualCursorImage == null) return; if (!_styleMap.TryGetValue(style, out var entry) || entry.Texture == null) { if (!_styleMap.TryGetValue(CursorStyle.Default, out entry) || entry.Texture == null) return; } var rect = new Rect(0, 0, entry.Texture.width, entry.Texture.height); var pivot = new Vector2(0.5f, 0.5f); _virtualCursorImage.sprite = Sprite.Create(entry.Texture, rect, pivot); } private void ApplySystemCursorStyle(CursorStyle style) { if (!_enableCustomCursor) return; if (!_styleMap.TryGetValue(style, out var entry) || entry.Texture == null) { if (_styleMap.TryGetValue(CursorStyle.Default, out var defaultEntry) && defaultEntry.Texture != null) { entry = defaultEntry; } else { Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); CacheApplied(null, Vector2.zero, CursorMode.Auto); return; } } var hotspot = entry.Hotspot; if (entry.Texture != null && entry.HotspotIsNormalized) { hotspot = new Vector2( hotspot.x * entry.Texture.width, hotspot.y * entry.Texture.height); } if (IsSameAsCurrent(entry.Texture, hotspot, entry.Mode)) return; Cursor.SetCursor(entry.Texture, hotspot, entry.Mode); CacheApplied(entry.Texture, hotspot, entry.Mode); } private bool IsSameAsCurrent(Texture2D texture, Vector2 hotspot, CursorMode mode) { return _currentTexture == texture && _currentHotspot == hotspot && _currentMode == mode; } private void CacheApplied(Texture2D texture, Vector2 hotspot, CursorMode mode) { _currentTexture = texture; _currentHotspot = hotspot; _currentMode = mode; } private void BuildStyleMap() { _styleMap.Clear(); if (_styles == null) return; for (var i = 0; i < _styles.Count; i++) { var entry = _styles[i]; _styleMap[entry.Style] = entry; } } [Serializable] public struct CursorStyleEntry { public CursorStyle Style; public Texture2D Texture; public Vector2 Hotspot; public CursorMode Mode; public bool HotspotIsNormalized; public Vector2 TooltipOffset; } } }