[NUI] Separate the LayoutTransition from LayoutController (#3592)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Layouting / LayoutTransitionManager.cs
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using Tizen.NUI.BaseComponents;
21
22 namespace Tizen.NUI
23 {
24     internal class LayoutTransitionManager : Disposable
25     {
26         private bool overrideCoreAnimation = false;
27         private Animation coreAnimation;
28         private List<LayoutData> layoutTransitionDataQueue;
29         private List<LayoutItem> itemRemovalQueue;
30
31         internal LayoutTransitionManager()
32         {
33             layoutTransitionDataQueue = new List<LayoutData>();
34
35         }
36
37         /// <summary>
38         /// Dispose Explicit or Implicit
39         /// </summary>
40         protected override void Dispose(DisposeTypes type)
41         {
42             if (Disposed)
43             {
44                 return;
45             }
46
47             if (coreAnimation != null)
48             {
49                 coreAnimation.Dispose();
50                 coreAnimation = null;
51             }
52
53             base.Dispose(type);
54         }
55
56         public Animation CoreAnimation => coreAnimation;
57
58         public bool OverrideCoreAnimation
59         {
60             get
61             {
62                 return overrideCoreAnimation;
63             }
64             set
65             {
66                 overrideCoreAnimation = value;
67             }
68         }
69
70         /// <summary>
71         /// Add transition data for a LayoutItem to the transition stack.
72         /// </summary>
73         /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
74         internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
75         {
76             layoutTransitionDataQueue.Add(transitionDataEntry);
77         }
78
79         /// <summary>
80         /// Add LayoutItem to a removal stack for removal after transitions finish.
81         /// </summary>
82         /// <param name="itemToRemove">LayoutItem to remove.</param>
83         internal void AddToRemovalStack(LayoutItem itemToRemove)
84         {
85             if (itemRemovalQueue == null)
86             {
87                 itemRemovalQueue = new List<LayoutItem>();
88             }
89             itemRemovalQueue.Add(itemToRemove);
90         }
91
92         /// <summary>
93         /// Play the animation.
94         /// </summary>
95         internal void SetupCoreAndPlayAnimation()
96         {
97             if (EnabledCoreAnimation())
98             {
99                 SetupCoreAnimation();
100
101                 NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
102                 coreAnimation.Play();
103             }
104         }
105
106         /// <summary>
107         /// Check if layout animation is needed
108         /// </summary>
109         private bool EnabledCoreAnimation()
110         {
111             return layoutTransitionDataQueue.Count > 0 && !OverrideCoreAnimation;
112         }
113
114         /// <summary>
115         /// Set up the animation from each LayoutItems position data.
116         /// Iterates the transition stack, adding an Animator to the core animation.
117         /// </summary>
118         private void SetupCoreAnimation()
119         {
120             NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
121
122             if (!coreAnimation)
123             {
124                 coreAnimation = new Animation();
125                 coreAnimation.EndAction = Animation.EndActions.StopFinal;
126                 coreAnimation.Finished += AnimationFinished;
127             }
128
129             // Iterate all items that have been queued for repositioning.
130             foreach (var layoutPositionData in layoutTransitionDataQueue)
131             {
132                 AddAnimatorsToAnimation(layoutPositionData);
133             }
134             // transitions have now been applied, clear stack, ready for new transitions on
135             // next layout traversal.
136             layoutTransitionDataQueue.Clear();
137         }
138
139
140         private void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
141         {
142             if (transitionsToAnimate?.Count > 0)
143             {
144                 foreach (LayoutTransition transition in transitionsToAnimate)
145                 {
146                     if (transition.AnimatableProperty != AnimatableProperties.Position &&
147                         transition.AnimatableProperty != AnimatableProperties.Size)
148                     {
149                         coreAnimation.AnimateTo(view,
150                                                  transition.AnimatableProperty.ToString(),
151                                                  transition.TargetValue,
152                                                  transition.Animator.Delay,
153                                                  transition.Animator.Duration,
154                                                  transition.Animator.AlphaFunction);
155
156                         NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
157                                            " Property:" + transition.AnimatableProperty.ToString() +
158                                            " delay:" + transition.Animator.Delay +
159                                            " duration:" + transition.Animator.Duration);
160                     }
161                 }
162             }
163         }
164
165         /// <summary>
166         /// Iterate transitions and replace Position Components if replacements found in list.
167         /// </summary>
168         private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
169                                                                  AnimatableProperties propertyToMatch,
170                                                                  ref TransitionComponents transitionComponentToUpdate)
171         {
172             foreach (LayoutTransition transition in sourceTransitionList)
173             {
174                 if (transition.AnimatableProperty == propertyToMatch)
175                 {
176                     // Matched property to animate is for the propertyToMatch so use provided Animator.
177                     transitionComponentToUpdate = transition.Animator;
178                 }
179             }
180         }
181
182         private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
183         {
184             AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
185             return new TransitionComponents(delay, duration, alphaFunction);
186         }
187
188         /// <summary>
189         /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
190         /// </summary>
191         private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
192         {
193             LayoutTransition positionTransition = new LayoutTransition();
194             LayoutTransition sizeTransition = new LayoutTransition();
195             TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
196
197             // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
198             // reposition to the new layout not to the insertion/removal of a sibling.
199             if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
200             {
201                 conditionForAnimators = TransitionCondition.LayoutChanged;
202             }
203
204             // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
205             TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 300);
206             TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 300);
207
208             bool matchedCustomTransitions = false;
209
210
211             TransitionList transitionsForCurrentCondition = new TransitionList();
212             // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
213
214             // Check if item to animate has it's own Transitions for this condition.
215             // If a key exists then a List of at least 1 transition exists.
216             if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
217             {
218                 // Child has transitions for the condition
219                 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
220             }
221
222             if (!matchedCustomTransitions)
223             {
224                 // Inherit parent transitions as none already set on View for the condition.
225                 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
226                 if (layoutParent != null)
227                 {
228                     // Item doesn't have it's own transitions for this condition so copy parents if
229                     // has a parent with transitions.
230                     LayoutGroup layoutGroup = layoutParent as LayoutGroup;
231                     TransitionList parentTransitionList;
232                     // Note TryGetValue returns null if key not matched.
233                     if (layoutGroup != null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
234                     {
235                         // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
236                         LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
237                                                                 transitionsForCurrentCondition);
238                     }
239                 }
240             }
241
242
243             // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
244
245             // There can only be one position transition and one size position, they will be replaced if set multiple times.
246             // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
247
248             // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
249             // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
250             // Typically Delay, Duration and Alphafunction can be custom.
251             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
252                                                         AnimatableProperties.Position,
253                                                         ref positionTransitionComponents);
254
255             // Size
256             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
257                                                         AnimatableProperties.Size,
258                                                         ref sizeTransitionComponents);
259
260             // Add animators to the core Animation,
261
262             SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
263
264             SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
265
266             SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
267
268             // Dispose components
269             positionTransitionComponents.Dispose();
270             sizeTransitionComponents.Dispose();
271         }
272
273         private void AnimationFinished(object sender, EventArgs e)
274         {
275             // Iterate list of LayoutItem that were set for removal.
276             // Now the core animation has finished their Views can be removed.
277             if (itemRemovalQueue != null)
278             {
279                 foreach (LayoutItem item in itemRemovalQueue)
280                 {
281                     // Check incase the parent was already removed and the Owner was
282                     // removed already.
283                     if (item.Owner)
284                     {
285                         // Check again incase the parent has already been removed.
286                         if (item.GetParent() is LayoutGroup layoutGroup)
287                         {
288                             layoutGroup.Owner?.RemoveChild(item.Owner);
289                         }
290
291                     }
292                 }
293                 itemRemovalQueue.Clear();
294                 // If LayoutItems added to stack whilst the core animation is playing
295                 // they would have been cleared here.
296                 // Could have another stack to be added to whilst the animation is running.
297                 // After the running stack is cleared it can be populated with the content
298                 // of the other stack.  Then the main removal stack iterated when AnimationFinished
299                 // occurs again.
300             }
301             NUILog.Debug("LayoutController AnimationFinished");
302             coreAnimation?.Clear();
303         }
304
305         private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
306         {
307             // A removed item does not have a valid target position within the layout so don't try to position.
308             if (layoutPositionData.ConditionForAnimation != TransitionCondition.Remove)
309             {
310                 var vector = new Vector3(layoutPositionData.Left,
311                                         layoutPositionData.Top,
312                                         layoutPositionData.Item.Owner.Position.Z);
313                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
314                             vector,
315                             positionTransitionComponents.Delay,
316                             positionTransitionComponents.Duration,
317                             positionTransitionComponents.AlphaFunction);
318
319                 NUILog.Debug("LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
320                                    " left:" + layoutPositionData.Left +
321                                    " top:" + layoutPositionData.Top +
322                                    " delay:" + positionTransitionComponents.Delay +
323                                    " duration:" + positionTransitionComponents.Duration);
324                 vector.Dispose();
325                 vector = null;
326             }
327         }
328
329         private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
330         {
331             // Text size cant be animated so is set to it's final size.
332             // It is due to the internals of the Text not being able to recalculate fast enough.
333             if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
334             {
335                 float itemWidth = layoutPositionData.Right - layoutPositionData.Left;
336                 float itemHeight = layoutPositionData.Bottom - layoutPositionData.Top;
337                 // Set size directly.
338                 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
339             }
340             else
341             {
342                 var vector = new Vector3(layoutPositionData.Right - layoutPositionData.Left,
343                                                          layoutPositionData.Bottom - layoutPositionData.Top,
344                                                          layoutPositionData.Item.Owner.Position.Z);
345                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
346                                          vector,
347                                          sizeTransitionComponents.Delay,
348                                          sizeTransitionComponents.Duration,
349                                          sizeTransitionComponents.AlphaFunction);
350
351                 NUILog.Debug("LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
352                                    " width:" + (layoutPositionData.Right - layoutPositionData.Left) +
353                                    " height:" + (layoutPositionData.Bottom - layoutPositionData.Top) +
354                                    " delay:" + sizeTransitionComponents.Delay +
355                                    " duration:" + sizeTransitionComponents.Duration);
356                 vector.Dispose();
357                 vector = null;
358             }
359         }
360     }
361 }