using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.UI; namespace Watermelon { /// /// Null-safe extension helpers for and , /// plus snapping utilities. /// /// The KillActive / CompleteActive family is the recommended way to stop tweens /// stored in fields, because they handle both the null case (tween never started or returned null /// from ) and the already-completed case without requiring extra null checks. /// public static class TweenExtensions { /// Returns true if is not null and is currently active. public static bool ExistsAndActive(this TweenCase tweenCase) { return tweenCase != null && tweenCase.IsActive; } /// /// Kills if it is not null and is currently active. /// Returns true if the tween was killed, false if it was already inactive or null. /// public static bool KillActive(this TweenCase tweenCase) { if (tweenCase != null && tweenCase.IsActive) { tweenCase.Kill(); return true; } return false; } /// Kills every active tween in , skipping null entries and already-inactive tweens. public static void KillActive(this TweenCase[] tweenCases) { if(tweenCases != null) { foreach (TweenCase tweenCase in tweenCases) { if (tweenCase != null && tweenCase.IsActive) { tweenCase.Kill(); } } } } /// /// Kills collection if it is not null and is not yet complete. /// Returns true if the collection was killed. /// public static bool KillActive(this TweenCaseCollection tweenCase) { if (tweenCase != null && !tweenCase.IsComplete()) { tweenCase.Kill(); return true; } return false; } /// /// Completes if it is not null and has not yet finished. /// Returns true if the tween was completed. /// public static bool CompleteActive(this TweenCase tweenCase) { if (tweenCase != null && !tweenCase.IsCompleted) { tweenCase.Complete(); return true; } return false; } /// Forces immediate completion of every active tween in , skipping null entries. public static void CompleteActive(this TweenCase[] tweenCases) { if (tweenCases != null) { foreach (TweenCase tweenCase in tweenCases) { if (tweenCase != null && tweenCase.IsActive) { tweenCase.Complete(); } } } } /// /// Smoothly scrolls the so that the bottom edge of /// is visible within the viewport. /// /// The scroll rect to animate. /// The child to bring into view. /// Scroll animation duration in seconds. /// Additional horizontal offset applied to the target position. /// Additional vertical offset applied to the target position. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TweenCase DoSnapTargetBottom(this ScrollRect scrollRect, RectTransform target, float duration, float offsetX = 0, float offsetY = 0) { var targetPosition = target.position + Vector3.up * (scrollRect.viewport.rect.height / 2 + target.sizeDelta.y); return scrollRect.SnapToTarget(targetPosition, duration, offsetX, offsetY); } /// /// Smoothly scrolls the so that the top edge of /// is aligned with the top of the viewport. /// /// The scroll rect to animate. /// The child to bring into view. /// Scroll animation duration in seconds. /// Additional horizontal offset applied to the target position. /// Additional vertical offset applied to the target position. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TweenCase DoSnapTargetTop(this ScrollRect scrollRect, RectTransform target, float duration, float offsetX = 0, float offsetY = 0) { return scrollRect.SnapToTarget(target.position, duration, offsetX, offsetY); } /// /// Smoothly scrolls the so that a world-space position /// is visible, respecting the scroll rect's horizontal/vertical constraints. /// /// The scroll rect to animate. /// World-space position to scroll towards. /// Scroll animation duration in seconds. /// Additional horizontal offset in viewport space. /// Additional vertical offset in viewport space. public static TweenCase SnapToTarget(this ScrollRect scrollRect, Vector3 target, float duration, float offsetX = 0, float offsetY = 0) { Vector2 contentPosition = scrollRect.viewport.InverseTransformPoint(scrollRect.content.position); Vector2 newPosition = scrollRect.viewport.InverseTransformPoint(target); newPosition = new Vector2(newPosition.x + offsetX, newPosition.y + offsetY); if (!scrollRect.horizontal) newPosition.x = contentPosition.x; if (!scrollRect.vertical) newPosition.y = contentPosition.y; return scrollRect.content.DOAnchoredPosition(contentPosition - newPosition, duration); } } }