using System; using System.Collections.Generic; using Jovian.PopupSystem.UI; using UnityEngine; using Object = UnityEngine.Object; namespace Jovian.PopupSystem { public sealed class PopupSystem : IPopupSystem { readonly PopupSettings settings; readonly PopupView viewPrefab; readonly Func animatorFactory; readonly Dictionary categories = new(); public PopupSystem(PopupSettings settings, PopupView viewPrefab, Func animatorFactory = null) { this.settings = settings; this.viewPrefab = viewPrefab; this.animatorFactory = animatorFactory ?? (() => new FadePopupAnimator()); } public void RegisterCategory(PopupCategory category, int priority = 0) { if(categories.ContainsKey(category)) { return; } var effectivePriority = settings.GetPriority(category); if(effectivePriority == 0) { effectivePriority = priority; } categories[category] = new ViewState { priority = effectivePriority, delay = settings.GetDelay(category), animator = animatorFactory() }; } public void Show(PopupCategory category, Action buildContent, RectTransform anchor = null, AnchorSide? anchorSide = null) { if(!categories.TryGetValue(category, out var state)) { return; } DismissLowerPriority(state.priority); state.pendingBuild = buildContent; state.pendingAnchor = anchor; state.pendingAnchorSide = anchorSide ?? settings.defaultAnchorSide; state.pendingScreenPos = null; state.delayTimer = state.delay; state.isPending = true; } public void ShowAtPosition(PopupCategory category, Action buildContent, Vector2 screenPosition) { if(!categories.TryGetValue(category, out var state)) { return; } DismissLowerPriority(state.priority); state.pendingBuild = buildContent; state.pendingAnchor = null; state.pendingScreenPos = screenPosition; state.delayTimer = state.delay; state.isPending = true; } public void Hide(PopupCategory category) { if(!categories.TryGetValue(category, out var state)) { return; } state.isPending = false; if(state.view != null && state.view.IsVisible) { state.animator.Hide(state.view.CanvasGroup, settings.fadeDuration, () => { state.view.SetVisible(false); }); } } public void HideAll() { foreach(var kvp in categories) { Hide(kvp.Key); } } public void Tick(float deltaTime) { foreach(var kvp in categories) { kvp.Value.animator.Tick(deltaTime); } foreach(var kvp in categories) { var state = kvp.Value; if(!state.isPending) { continue; } state.delayTimer -= deltaTime; if(state.delayTimer > 0f) { continue; } state.isPending = false; ShowImmediate(state); } foreach(var kvp in categories) { var state = kvp.Value; if(state.view != null && state.view.IsVisible && state.isFollowMouse) { state.view.UpdatePosition(); } } } public void Dispose() { foreach(var kvp in categories) { if(kvp.Value.view != null) { Object.Destroy(kvp.Value.view.gameObject); } } categories.Clear(); } private void ShowImmediate(ViewState state) { EnsureView(state); state.view.ClearContent(); var builder = new PopupContentBuilder(state.view); state.pendingBuild?.Invoke(builder); if(state.pendingScreenPos.HasValue) { state.view.SetFixedPosition(state.pendingScreenPos.Value, settings.screenEdgePadding); state.isFollowMouse = false; } else if(state.pendingAnchor != null) { state.view.SetAnchorMode(state.pendingAnchor, state.pendingAnchorSide, settings.screenEdgePadding); state.isFollowMouse = false; } else { state.view.SetFollowMouseMode(settings.followMouseOffset, settings.screenEdgePadding); state.isFollowMouse = true; } state.view.SetVisible(true); state.view.UpdatePosition(); state.animator.Show(state.view.CanvasGroup, settings.fadeDuration, null); } private void EnsureView(ViewState state) { if(state.view != null) { return; } state.view = Object.Instantiate(viewPrefab); state.view.SetVisible(false); state.view.SetMaxWidth(settings.maxPopupWidth); var canvas = state.view.GetComponent(); if(canvas != null) { canvas.sortingOrder = settings.sortingOrder; } } private void DismissLowerPriority(int showingPriority) { foreach(var kvp in categories) { var state = kvp.Value; if(state.priority < showingPriority && state.view != null && state.view.IsVisible) { state.isPending = false; state.animator.Hide(state.view.CanvasGroup, settings.fadeDuration, () => { state.view.SetVisible(false); }); } } } private sealed class ViewState { public PopupView view; public IPopupAnimator animator; public int priority; public float delay; public float delayTimer; public bool isPending; public bool isFollowMouse; public Action pendingBuild; public RectTransform pendingAnchor; public AnchorSide pendingAnchorSide; public Vector2? pendingScreenPos; } } }