Skip to content

Commit b2bc19f

Browse files
committed
Add transition async extensions.
1 parent aa2f3a7 commit b2bc19f

File tree

11 files changed

+333
-0
lines changed

11 files changed

+333
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#if UNITYMVVMTOOLKIT_UNITASK_SUPPORT
2+
3+
namespace UnityMvvmToolkit.UniTask.Interfaces
4+
{
5+
using UnityEngine.UIElements;
6+
7+
public interface ITransitionPredicate
8+
{
9+
bool TransitionEnd(TransitionEndEvent e);
10+
bool TransitionCancel(TransitionCancelEvent e);
11+
}
12+
}
13+
14+
#endif

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/Interfaces/ITransitionPredicate.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#if UNITYMVVMTOOLKIT_UNITASK_SUPPORT
2+
3+
namespace UnityMvvmToolkit.UniTask
4+
{
5+
using System;
6+
using System.Linq;
7+
using System.Threading;
8+
using Interfaces;
9+
using TransitionPredicates;
10+
using UnityEngine.UIElements;
11+
using Cysharp.Threading.Tasks;
12+
using System.Runtime.CompilerServices;
13+
14+
public static class TransitionAsyncExtensions
15+
{
16+
private const char Dash = '-';
17+
private const int DefaultTimeoutMs = 2500;
18+
19+
public static async UniTask WaitForLongestTransition(this VisualElement element,
20+
CancellationToken cancellationToken = default)
21+
{
22+
var maxTransitionDelay = element.resolvedStyle.transitionDuration.Max(duration =>
23+
duration.unit == TimeUnit.Millisecond ? duration.value : duration.value * 1000);
24+
25+
if (maxTransitionDelay > float.Epsilon)
26+
{
27+
await UniTask.Delay((int) maxTransitionDelay, cancellationToken: cancellationToken);
28+
}
29+
}
30+
31+
public static async UniTask WaitForTransitionEnd(this VisualElement element, int transitionIndex,
32+
int timeoutMs = DefaultTimeoutMs, CancellationToken cancellationToken = default)
33+
{
34+
if (GetTransitionDuration(element, transitionIndex, out var stylePropertyName) > float.Epsilon)
35+
{
36+
await WaitForTransition(element, stylePropertyName, timeoutMs, cancellationToken);
37+
}
38+
}
39+
40+
public static async UniTask WaitForTransitionEnd(this VisualElement element, StylePropertyName stylePropertyName,
41+
int timeoutMs = DefaultTimeoutMs, CancellationToken cancellationToken = default)
42+
{
43+
if (GetTransitionDuration(element, stylePropertyName) > float.Epsilon)
44+
{
45+
await WaitForTransition(element, stylePropertyName, timeoutMs, cancellationToken);
46+
}
47+
}
48+
49+
/// <summary>
50+
///
51+
/// </summary>
52+
/// <param name="element"></param>
53+
/// <param name="propertyName">CamelCase propertyName: nameof(propertyName).</param>
54+
/// <param name="timeoutMs"></param>
55+
/// <param name="cancellationToken"></param>
56+
/// <returns></returns>
57+
public static async UniTask WaitForTransitionEnd(this VisualElement element, string propertyName,
58+
int timeoutMs = DefaultTimeoutMs, CancellationToken cancellationToken = default)
59+
{
60+
var transitionData = GetTransitionData(element, propertyName);
61+
if (transitionData is { Duration: { value: > float.Epsilon } })
62+
{
63+
await WaitForTransition(element, transitionData.Value.StylePropertyName, timeoutMs, cancellationToken);
64+
}
65+
}
66+
67+
public static async UniTask WaitForAnyTransitionEnd(this VisualElement element, int timeoutMs = DefaultTimeoutMs,
68+
CancellationToken cancellationToken = default)
69+
{
70+
if (AnyTransitionHasDuration(element))
71+
{
72+
await WaitForTransitionEnd(element, new TransitionAnyPredicate(), timeoutMs, cancellationToken);
73+
}
74+
}
75+
76+
public static async UniTask WaitForAllTransitionsEnd(this VisualElement element, int timeoutMs = DefaultTimeoutMs,
77+
CancellationToken cancellationToken = default)
78+
{
79+
var transitionsCount = GetTransitionsWithDurationCount(element);
80+
if (transitionsCount == 0)
81+
{
82+
return;
83+
}
84+
85+
await WaitForTransitionEnd(element, new TransitionCounterPredicate(transitionsCount), timeoutMs,
86+
cancellationToken);
87+
}
88+
89+
public static async UniTask WaitForTransitionEnd<T>(this VisualElement element, T transitionPredicate,
90+
int timeoutMs = DefaultTimeoutMs, CancellationToken cancellationToken = default)
91+
where T : ITransitionPredicate
92+
{
93+
var taskCompletionSource = new UniTaskCompletionSource();
94+
95+
void OnTransitionEnd(TransitionEndEvent e)
96+
{
97+
if (transitionPredicate.TransitionEnd(e))
98+
{
99+
taskCompletionSource.TrySetResult();
100+
}
101+
}
102+
103+
void OnTransitionCancel(TransitionCancelEvent e)
104+
{
105+
if (transitionPredicate.TransitionCancel(e))
106+
{
107+
taskCompletionSource.TrySetCanceled();
108+
}
109+
}
110+
111+
try
112+
{
113+
element.RegisterCallback<TransitionCancelEvent>(OnTransitionCancel);
114+
element.RegisterCallback<TransitionEndEvent>(OnTransitionEnd);
115+
116+
var transitionTask = taskCompletionSource.Task;
117+
var timeoutTask = UniTask.Delay(timeoutMs, cancellationToken: cancellationToken);
118+
119+
var task = await UniTask.WhenAny(transitionTask, timeoutTask).SuppressCancellationThrow();
120+
if (task.IsCanceled || task.Result == 1)
121+
{
122+
taskCompletionSource.TrySetCanceled();
123+
}
124+
}
125+
finally
126+
{
127+
element.UnregisterCallback<TransitionCancelEvent>(OnTransitionCancel);
128+
element.UnregisterCallback<TransitionEndEvent>(OnTransitionEnd);
129+
}
130+
}
131+
132+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
133+
private static UniTask WaitForTransition(this VisualElement element, StylePropertyName stylePropertyName,
134+
int timeoutMs = DefaultTimeoutMs, CancellationToken cancellationToken = default)
135+
{
136+
return WaitForTransitionEnd(element, new TransitionNamePredicate(stylePropertyName), timeoutMs,
137+
cancellationToken);
138+
}
139+
140+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
141+
private static bool AnyTransitionHasDuration(VisualElement element)
142+
{
143+
return element.resolvedStyle.transitionDuration.Any(duration => duration.value > float.Epsilon);
144+
}
145+
146+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
147+
private static int GetTransitionsWithDurationCount(VisualElement element)
148+
{
149+
return element.resolvedStyle.transitionDuration.Count(duration => duration.value > float.Epsilon);
150+
}
151+
152+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
153+
private static float GetTransitionDuration(VisualElement element, int transitionIndex,
154+
out StylePropertyName stylePropertyName)
155+
{
156+
using var properties = element.resolvedStyle.transitionProperty.GetEnumerator();
157+
using var durations = element.resolvedStyle.transitionDuration.GetEnumerator();
158+
159+
var index = 0;
160+
161+
while (properties.MoveNext() && durations.MoveNext())
162+
{
163+
if (index == transitionIndex)
164+
{
165+
stylePropertyName = properties.Current;
166+
return durations.Current.value;
167+
}
168+
169+
index++;
170+
}
171+
172+
stylePropertyName = default;
173+
return 0;
174+
}
175+
176+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
177+
private static float GetTransitionDuration(VisualElement element, StylePropertyName stylePropertyName)
178+
{
179+
using var properties = element.resolvedStyle.transitionProperty.GetEnumerator();
180+
using var durations = element.resolvedStyle.transitionDuration.GetEnumerator();
181+
182+
while (properties.MoveNext() && durations.MoveNext())
183+
{
184+
if (properties.Current == stylePropertyName)
185+
{
186+
return durations.Current.value;
187+
}
188+
}
189+
190+
return 0;
191+
}
192+
193+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
194+
private static (StylePropertyName StylePropertyName, TimeValue Duration)? GetTransitionData(VisualElement element,
195+
ReadOnlySpan<char> propertyName)
196+
{
197+
using var properties = element.resolvedStyle.transitionProperty.GetEnumerator();
198+
using var durations = element.resolvedStyle.transitionDuration.GetEnumerator();
199+
200+
while (properties.MoveNext() && durations.MoveNext())
201+
{
202+
if (LettersEqual(properties.Current.ToString(), propertyName))
203+
{
204+
return (properties.Current, durations.Current);
205+
}
206+
}
207+
208+
return null;
209+
}
210+
211+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
212+
private static bool LettersEqual(ReadOnlySpan<char> dashCase, ReadOnlySpan<char> camelCase)
213+
{
214+
if (camelCase.Length > dashCase.Length)
215+
{
216+
return false;
217+
}
218+
219+
var camelIndex = 0;
220+
221+
for (var i = 0; i < dashCase.Length; i++)
222+
{
223+
var dashChar = dashCase[i];
224+
if (dashChar == Dash)
225+
{
226+
continue;
227+
}
228+
229+
if (dashChar != char.ToLowerInvariant(camelCase[camelIndex]))
230+
{
231+
return false;
232+
}
233+
234+
camelIndex++;
235+
}
236+
237+
return true;
238+
}
239+
}
240+
}
241+
242+
#endif

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/TransitionAsyncExtensions.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/TransitionPredicates.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#if UNITYMVVMTOOLKIT_UNITASK_SUPPORT
2+
3+
namespace UnityMvvmToolkit.UniTask.TransitionPredicates
4+
{
5+
using Interfaces;
6+
using UnityEngine.UIElements;
7+
8+
public readonly struct TransitionAnyPredicate : ITransitionPredicate
9+
{
10+
public bool TransitionEnd(TransitionEndEvent e) => true;
11+
public bool TransitionCancel(TransitionCancelEvent e) => true;
12+
}
13+
}
14+
15+
#endif

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/TransitionPredicates/TransitionAnyPredicate.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#if UNITYMVVMTOOLKIT_UNITASK_SUPPORT
2+
3+
namespace UnityMvvmToolkit.UniTask.TransitionPredicates
4+
{
5+
using Interfaces;
6+
using UnityEngine.UIElements;
7+
8+
public struct TransitionCounterPredicate : ITransitionPredicate
9+
{
10+
private int _transitionsCount;
11+
12+
public TransitionCounterPredicate(int transitionsCount)
13+
{
14+
_transitionsCount = transitionsCount;
15+
}
16+
17+
public bool TransitionEnd(TransitionEndEvent e) => --_transitionsCount == 0;
18+
public bool TransitionCancel(TransitionCancelEvent e) => true;
19+
}
20+
}
21+
22+
#endif

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/External/UniTask/TransitionPredicates/TransitionCounterPredicate.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#if UNITYMVVMTOOLKIT_UNITASK_SUPPORT
2+
3+
namespace UnityMvvmToolkit.UniTask.TransitionPredicates
4+
{
5+
using Interfaces;
6+
using UnityEngine.UIElements;
7+
8+
public readonly struct TransitionNamePredicate : ITransitionPredicate
9+
{
10+
private readonly StylePropertyName _stylePropertyName;
11+
12+
public TransitionNamePredicate(StylePropertyName stylePropertyName)
13+
{
14+
_stylePropertyName = stylePropertyName;
15+
}
16+
17+
public bool TransitionEnd(TransitionEndEvent e) => e.AffectsProperty(_stylePropertyName);
18+
public bool TransitionCancel(TransitionCancelEvent e) => true;
19+
}
20+
}
21+
22+
#endif

0 commit comments

Comments
 (0)