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
0 commit comments