2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 using System.Collections.Generic;
20 using Tizen.NUI.BaseComponents;
24 internal class LayoutTransitionManager : Disposable
26 private bool overrideCoreAnimation = false;
27 private Animation coreAnimation;
28 private List<LayoutData> layoutTransitionDataQueue;
29 private List<LayoutItem> itemRemovalQueue;
31 internal LayoutTransitionManager()
33 layoutTransitionDataQueue = new List<LayoutData>();
38 /// Dispose Explicit or Implicit
40 protected override void Dispose(DisposeTypes type)
47 if (coreAnimation != null)
49 coreAnimation.Dispose();
56 public Animation CoreAnimation => coreAnimation;
58 public bool OverrideCoreAnimation
62 return overrideCoreAnimation;
66 overrideCoreAnimation = value;
71 /// Add transition data for a LayoutItem to the transition stack.
73 /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
74 internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
76 layoutTransitionDataQueue.Add(transitionDataEntry);
80 /// Add LayoutItem to a removal stack for removal after transitions finish.
82 /// <param name="itemToRemove">LayoutItem to remove.</param>
83 internal void AddToRemovalStack(LayoutItem itemToRemove)
85 if (itemRemovalQueue == null)
87 itemRemovalQueue = new List<LayoutItem>();
89 itemRemovalQueue.Add(itemToRemove);
93 /// Play the animation.
95 internal void SetupCoreAndPlayAnimation()
97 if (EnabledCoreAnimation())
101 NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
102 coreAnimation.Play();
107 /// Check if layout animation is needed
109 private bool EnabledCoreAnimation()
111 return layoutTransitionDataQueue.Count > 0 && !OverrideCoreAnimation;
115 /// Set up the animation from each LayoutItems position data.
116 /// Iterates the transition stack, adding an Animator to the core animation.
118 private void SetupCoreAnimation()
120 NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
124 coreAnimation = new Animation();
125 coreAnimation.EndAction = Animation.EndActions.StopFinal;
126 coreAnimation.Finished += AnimationFinished;
129 // Iterate all items that have been queued for repositioning.
130 foreach (var layoutPositionData in layoutTransitionDataQueue)
132 AddAnimatorsToAnimation(layoutPositionData);
134 // transitions have now been applied, clear stack, ready for new transitions on
135 // next layout traversal.
136 layoutTransitionDataQueue.Clear();
140 private void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
142 if (transitionsToAnimate?.Count > 0)
144 foreach (LayoutTransition transition in transitionsToAnimate)
146 if (transition.AnimatableProperty != AnimatableProperties.Position &&
147 transition.AnimatableProperty != AnimatableProperties.Size)
149 coreAnimation.AnimateTo(view,
150 transition.AnimatableProperty.ToString(),
151 transition.TargetValue,
152 transition.Animator.Delay,
153 transition.Animator.Duration,
154 transition.Animator.AlphaFunction);
156 NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
157 " Property:" + transition.AnimatableProperty.ToString() +
158 " delay:" + transition.Animator.Delay +
159 " duration:" + transition.Animator.Duration);
166 /// Iterate transitions and replace Position Components if replacements found in list.
168 private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
169 AnimatableProperties propertyToMatch,
170 ref TransitionComponents transitionComponentToUpdate)
172 foreach (LayoutTransition transition in sourceTransitionList)
174 if (transition.AnimatableProperty == propertyToMatch)
176 // Matched property to animate is for the propertyToMatch so use provided Animator.
177 transitionComponentToUpdate = transition.Animator;
182 private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
184 AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
185 return new TransitionComponents(delay, duration, alphaFunction);
189 /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
191 private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
193 LayoutTransition positionTransition = new LayoutTransition();
194 LayoutTransition sizeTransition = new LayoutTransition();
195 TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
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))
201 conditionForAnimators = TransitionCondition.LayoutChanged;
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);
208 bool matchedCustomTransitions = false;
211 TransitionList transitionsForCurrentCondition = new TransitionList();
212 // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
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))
218 // Child has transitions for the condition
219 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
222 if (!matchedCustomTransitions)
224 // Inherit parent transitions as none already set on View for the condition.
225 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
226 if (layoutParent != null)
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))
235 // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
236 LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
237 transitionsForCurrentCondition);
243 // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
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.
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);
256 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
257 AnimatableProperties.Size,
258 ref sizeTransitionComponents);
260 // Add animators to the core Animation,
262 SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
264 SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
266 SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
268 // Dispose components
269 positionTransitionComponents.Dispose();
270 sizeTransitionComponents.Dispose();
273 private void AnimationFinished(object sender, EventArgs e)
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)
279 foreach (LayoutItem item in itemRemovalQueue)
281 // Check incase the parent was already removed and the Owner was
285 // Check again incase the parent has already been removed.
286 if (item.GetParent() is LayoutGroup layoutGroup)
288 layoutGroup.Owner?.RemoveChild(item.Owner);
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
301 NUILog.Debug("LayoutController AnimationFinished");
302 coreAnimation?.Clear();
305 private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
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)
310 var vector = new Vector3(layoutPositionData.Left,
311 layoutPositionData.Top,
312 layoutPositionData.Item.Owner.Position.Z);
313 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
315 positionTransitionComponents.Delay,
316 positionTransitionComponents.Duration,
317 positionTransitionComponents.AlphaFunction);
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);
329 private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
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)
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);
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",
347 sizeTransitionComponents.Delay,
348 sizeTransitionComponents.Duration,
349 sizeTransitionComponents.AlphaFunction);
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);