[NUI] Improve LayoutController.Process performance (#2755)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Layouting / LayoutController.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 Tizen.NUI.BaseComponents;
19 using System.Runtime.InteropServices;
20 using System.Collections.Generic;
21 using System.Diagnostics;
22 using System;
23 using System.ComponentModel;
24
25 namespace Tizen.NUI
26 {
27     /// <summary>
28     /// [Draft] The class that initiates the Measuring and Layouting of the tree,
29     ///         It sets a callback that becomes the entry point into the C# Layouting.
30     /// </summary>
31     internal class LayoutController : Disposable
32     {
33         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
34         internal delegate void Callback(int id);
35
36         event Callback instance;
37
38         private Window window;
39
40         Animation coreAnimation;
41
42         private List<LayoutData> layoutTransitionDataQueue;
43
44         private List<LayoutItem> itemRemovalQueue;
45
46         /// <summary>
47         /// Constructs a LayoutController which controls the measuring and layouting.<br />
48         /// <param name="window">Window attach this LayoutController to.</param>
49         /// </summary>
50         public LayoutController(Window window) : this(Interop.LayoutController.New(), true)
51         {
52             this.window = window;
53             layoutTransitionDataQueue = new List<LayoutData>();
54         }
55
56         internal LayoutController(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
57         {
58         }
59
60         /// <summary>
61         /// Get the unique id of the LayoutController
62         /// </summary>
63         public int GetId()
64         {
65             return Interop.LayoutController.GetId(SwigCPtr);
66         }
67
68         /// <summary>
69         /// Request relayout of a LayoutItem and it's parent tree.
70         /// </summary>
71         /// <param name="layoutItem">LayoutItem that is required to be laid out again.</param>
72         public void RequestLayout(LayoutItem layoutItem)
73         {
74             // Go up the tree and mark all parents to relayout
75             ILayoutParent layoutParent = layoutItem.GetParent();
76             if (layoutParent != null)
77             {
78                 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
79                 if (layoutGroup != null && !layoutGroup.LayoutRequested)
80                 {
81                     layoutGroup.RequestLayout();
82                 }
83             }
84         }
85
86         /// <summary>
87         /// Get the Layouting animation object that transitions layouts and content.
88         /// Use OverrideCoreAnimation to explicitly control Playback.
89         /// </summary>
90         /// <returns> The layouting core Animation. </returns>
91         [EditorBrowsable(EditorBrowsableState.Never)]
92         public Animation GetCoreAnimation()
93         {
94             return coreAnimation;
95         }
96
97         /// <summary>
98         /// Set or Get Layouting core animation override property.
99         /// Gives explicit control over the Layouting animation playback if set to True.
100         /// Reset to False if explicit control no longer required.
101         /// </summary>
102         [EditorBrowsable(EditorBrowsableState.Never)]
103         public bool OverrideCoreAnimation { get; set; } = false;
104
105         /// <summary>
106         /// Create and set process callback
107         /// </summary>
108         internal void CreateProcessCallback()
109         {
110             if (instance == null)
111             {
112                 instance = new Callback(Process);
113                 Interop.LayoutController.SetCallback(SwigCPtr, instance);
114             }
115         }
116
117         /// <summary>
118         /// Add transition data for a LayoutItem to the transition stack.
119         /// </summary>
120         /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
121         internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
122         {
123             layoutTransitionDataQueue.Add(transitionDataEntry);
124         }
125
126         /// <summary>
127         /// Add LayoutItem to a removal stack for removal after transitions finish.
128         /// </summary>
129         /// <param name="itemToRemove">LayoutItem to remove.</param>
130         internal void AddToRemovalStack(LayoutItem itemToRemove)
131         {
132             if (itemRemovalQueue == null)
133             {
134                 itemRemovalQueue = new List<LayoutItem>();
135             }
136             itemRemovalQueue.Add(itemToRemove);
137         }
138
139         /// <summary>
140         /// Dispose Explicit or Implicit
141         /// </summary>
142         protected override void Dispose(DisposeTypes type)
143         {
144             if (disposed)
145             {
146                 return;
147             }
148
149             //Release your own unmanaged resources here.
150             //You should not access any managed member here except static instance.
151             //because the execution order of Finalizes is non-deterministic.
152
153             if (SwigCPtr.Handle != global::System.IntPtr.Zero)
154             {
155                 Interop.LayoutController.DeleteLayoutController(SwigCPtr);
156                 SwigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
157             }
158
159             base.Dispose(type);
160         }
161
162         // Traverse the tree looking for a root node that is a layout.
163         // Once found, it's children can be assigned Layouts and the Measure process started.
164         private void FindRootLayouts(View rootNode, float parentWidth, float parentHeight)
165         {
166             if (rootNode.Layout != null)
167             {
168                 NUILog.Debug("LayoutController Root found:" + rootNode.Name);
169                 // rootNode has a layout, start measuring and layouting from here.
170                 MeasureAndLayout(rootNode, parentWidth, parentHeight);
171             }
172             else
173             {
174                 float rootWidth = rootNode.SizeWidth;
175                 float rootHeight = rootNode.SizeHeight;
176                 // Search children of supplied node for a layout.
177                 for (uint i = 0; i < rootNode.ChildCount; i++)
178                 {
179                     View view = rootNode.GetChildAt(i);
180                     FindRootLayouts(view, rootWidth, rootHeight);
181                 }
182             }
183         }
184
185         // Starts of the actual measuring and layouting from the given root node.
186         // Can be called from multiple starting roots but in series.
187         // Get parent View's Size.  If using Legacy size negotiation then should have been set already.
188         // Parent not a View so assume it's a Layer which is the size of the window.
189         void MeasureAndLayout(View root, float parentWidth, float parentHeight)
190         {
191             float positionX = root.PositionX;
192             float positionY = root.PositionY;
193
194             // Determine measure specification for root.
195             // The root layout policy could be an exact size, be match parent or wrap children.
196             // If wrap children then at most it can be the root parent size.
197             // If match parent then should be root parent size.
198             // If exact then should be that size limited by the root parent size.
199             MeasureSpecification parentWidthSpecification = CreateMeasureSpecification(parentWidth, root.WidthSpecification);
200             MeasureSpecification parentHeightSpecification = CreateMeasureSpecification(parentHeight, root.HeightSpecification);
201
202             // Start at root with it's parent's widthSpecification and heightSpecification
203             MeasureHierarchy(root, parentWidthSpecification, parentHeightSpecification);
204
205             // Start at root which was just measured.
206             PerformLayout(root, new LayoutLength(positionX),
207                                  new LayoutLength(positionY),
208                                  new LayoutLength(positionX) + root.Layout.MeasuredWidth.Size,
209                                  new LayoutLength(positionY) + root.Layout.MeasuredHeight.Size);
210
211             if (SetupCoreAnimation() && OverrideCoreAnimation == false)
212             {
213                 PlayAnimation();
214             }
215         }
216
217         private MeasureSpecification CreateMeasureSpecification(float size, int specification)
218         {
219             LayoutLength length = new LayoutLength(size);
220             MeasureSpecification.ModeType mode = MeasureSpecification.ModeType.Unspecified;
221
222             if (specification >= 0)
223             {
224                 // exact size provided so match width exactly
225                 length = new LayoutLength(specification);
226                 mode = MeasureSpecification.ModeType.Exactly;
227             }
228             else if (specification == LayoutParamPolicies.MatchParent)
229             {
230                 mode = MeasureSpecification.ModeType.Exactly;
231             }
232             return new MeasureSpecification(length, mode);
233         }
234
235         /// <summary>
236         /// Entry point into the C# Layouting that starts the Processing
237         /// </summary>
238         private void Process(int id)
239         {
240             Vector2 windowSize = window.GetSize();
241             float width = windowSize.Width;
242             float height = windowSize.Height;
243             // First layer in the Window should be the default layer (index 0 )
244             foreach (Layer layer in window.LayersChildren)
245             {
246                 foreach (View view in layer.Children)
247                 {
248                     FindRootLayouts(view, width, height);
249                 }
250             }
251             windowSize.Dispose();
252             windowSize = null;
253         }
254
255         /// <summary>
256         /// Starts measuring the tree, starting from the root layout.
257         /// </summary>
258         private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
259         {
260             // Does this View have a layout?
261             // Yes - measure the layout. It will call this method again for each of it's children.
262             // No -  reached leaf or no layouts set
263             //
264             // If in a leaf View with no layout, it's natural size is bubbled back up.
265             root.Layout?.Measure(widthSpec, heightSpec);
266         }
267
268         /// <summary>
269         /// Performs the layouting positioning
270         /// </summary>
271         private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
272         {
273             root.Layout?.Layout(left, top, right, bottom);
274         }
275
276         /// <summary>
277         /// Play the animation.
278         /// </summary>
279         private void PlayAnimation()
280         {
281             NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
282             coreAnimation.Play();
283         }
284
285         private void AnimationFinished(object sender, EventArgs e)
286         {
287             // Iterate list of LayoutItem that were set for removal.
288             // Now the core animation has finished their Views can be removed.
289             if (itemRemovalQueue != null)
290             {
291                 foreach (LayoutItem item in itemRemovalQueue)
292                 {
293                     // Check incase the parent was already removed and the Owner was
294                     // removed already.
295                     if (item.Owner)
296                     {
297                         // Check again incase the parent has already been removed.
298                         ILayoutParent layoutParent = item.GetParent();
299                         LayoutGroup layoutGroup = layoutParent as LayoutGroup;
300                         if (layoutGroup != null)
301                         {
302                             layoutGroup.Owner?.RemoveChild(item.Owner);
303                         }
304
305                     }
306                 }
307                 itemRemovalQueue.Clear();
308                 // If LayoutItems added to stack whilst the core animation is playing
309                 // they would have been cleared here.
310                 // Could have another stack to be added to whilst the animation is running.
311                 // After the running stack is cleared it can be populated with the content
312                 // of the other stack.  Then the main removal stack iterated when AnimationFinished
313                 // occurs again.
314             }
315             NUILog.Debug("LayoutController AnimationFinished");
316             coreAnimation?.Clear();
317         }
318
319         /// <summary>
320         /// Set up the animation from each LayoutItems position data.
321         /// Iterates the transition stack, adding an Animator to the core animation.
322         /// </summary>
323         private bool SetupCoreAnimation()
324         {
325             // Initialize animation for this layout run.
326             bool animationPending = false;
327
328             NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
329
330             if (layoutTransitionDataQueue.Count > 0) // Something to animate
331             {
332                 if (!coreAnimation)
333                 {
334                     coreAnimation = new Animation();
335                 }
336                 coreAnimation.EndAction = Animation.EndActions.StopFinal;
337                 coreAnimation.Finished += AnimationFinished;
338
339                 // Iterate all items that have been queued for repositioning.
340                 foreach (LayoutData layoutPositionData in layoutTransitionDataQueue)
341                 {
342                     AddAnimatorsToAnimation(layoutPositionData);
343                 }
344
345                 animationPending = true;
346
347                 // transitions have now been applied, clear stack, ready for new transitions on
348                 // next layout traversal.
349                 layoutTransitionDataQueue.Clear();
350             }
351             return animationPending;
352         }
353
354         private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
355         {
356             // A removed item does not have a valid target position within the layout so don't try to position.
357             if (layoutPositionData.ConditionForAnimation != TransitionCondition.Remove)
358             {
359                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
360                             new Vector3(layoutPositionData.Left,
361                                         layoutPositionData.Top,
362                                         layoutPositionData.Item.Owner.Position.Z),
363                             positionTransitionComponents.Delay,
364                             positionTransitionComponents.Duration,
365                             positionTransitionComponents.AlphaFunction);
366
367                 NUILog.Debug("LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
368                                    " left:" + layoutPositionData.Left +
369                                    " top:" + layoutPositionData.Top +
370                                    " delay:" + positionTransitionComponents.Delay +
371                                    " duration:" + positionTransitionComponents.Duration);
372             }
373         }
374
375         private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
376         {
377             // Text size cant be animated so is set to it's final size.
378             // It is due to the internals of the Text not being able to recalculate fast enough.
379             if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
380             {
381                 float itemWidth = layoutPositionData.Right - layoutPositionData.Left;
382                 float itemHeight = layoutPositionData.Bottom - layoutPositionData.Top;
383                 // Set size directly.
384                 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
385             }
386             else
387             {
388                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
389                                          new Vector3(layoutPositionData.Right - layoutPositionData.Left,
390                                                      layoutPositionData.Bottom - layoutPositionData.Top,
391                                                      layoutPositionData.Item.Owner.Position.Z),
392                                          sizeTransitionComponents.Delay,
393                                          sizeTransitionComponents.Duration,
394                                          sizeTransitionComponents.AlphaFunction);
395
396                 NUILog.Debug("LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
397                                    " width:" + (layoutPositionData.Right - layoutPositionData.Left) +
398                                    " height:" + (layoutPositionData.Bottom - layoutPositionData.Top) +
399                                    " delay:" + sizeTransitionComponents.Delay +
400                                    " duration:" + sizeTransitionComponents.Duration);
401             }
402         }
403
404         void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
405         {
406             if (transitionsToAnimate?.Count > 0)
407             {
408                 foreach (LayoutTransition transition in transitionsToAnimate)
409                 {
410                     if (transition.AnimatableProperty != AnimatableProperties.Position &&
411                         transition.AnimatableProperty != AnimatableProperties.Size)
412                     {
413                         coreAnimation.AnimateTo(view,
414                                                  transition.AnimatableProperty.ToString(),
415                                                  transition.TargetValue,
416                                                  transition.Animator.Delay,
417                                                  transition.Animator.Duration,
418                                                  transition.Animator.AlphaFunction);
419
420                         NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
421                                            " Property:" + transition.AnimatableProperty.ToString() +
422                                            " delay:" + transition.Animator.Delay +
423                                            " duration:" + transition.Animator.Duration);
424                     }
425                 }
426             }
427         }
428
429         /// <summary>
430         /// Iterate transitions and replace Position Components if replacements found in list.
431         /// </summary>
432         private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
433                                                                  AnimatableProperties propertyToMatch,
434                                                                  ref TransitionComponents transitionComponentToUpdate)
435         {
436             foreach (LayoutTransition transition in sourceTransitionList)
437             {
438                 if (transition.AnimatableProperty == propertyToMatch)
439                 {
440                     // Matched property to animate is for the propertyToMatch so use provided Animator.
441                     transitionComponentToUpdate = transition.Animator;
442                 }
443             }
444         }
445
446         private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
447         {
448             AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
449             return new TransitionComponents(delay, duration, alphaFunction);
450         }
451
452         /// <summary>
453         /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
454         /// </summary>
455         private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
456         {
457             LayoutTransition positionTransition = new LayoutTransition();
458             LayoutTransition sizeTransition = new LayoutTransition();
459             TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
460
461             // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
462             // reposition to the new layout not to the insertion/removal of a sibling.
463             if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
464             {
465                 conditionForAnimators = TransitionCondition.LayoutChanged;
466             }
467
468             // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
469             TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 300);
470             TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 300);
471
472             bool matchedCustomTransitions = false;
473
474
475             TransitionList transitionsForCurrentCondition = new TransitionList();
476             // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
477
478             // Check if item to animate has it's own Transitions for this condition.
479             // If a key exists then a List of at least 1 transition exists.
480             if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
481             {
482                 // Child has transitions for the condition
483                 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
484             }
485
486             if (!matchedCustomTransitions)
487             {
488                 // Inherit parent transitions as none already set on View for the condition.
489                 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
490                 if (layoutParent != null)
491                 {
492                     // Item doesn't have it's own transitions for this condition so copy parents if
493                     // has a parent with transitions.
494                     LayoutGroup layoutGroup = layoutParent as LayoutGroup;
495                     TransitionList parentTransitionList;
496                     // Note TryGetValue returns null if key not matched.
497                     if (layoutGroup != null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
498                     {
499                         // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
500                         LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
501                                                                 transitionsForCurrentCondition);
502                     }
503                 }
504             }
505
506
507             // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
508
509             // There can only be one position transition and one size position, they will be replaced if set multiple times.
510             // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
511
512             // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
513             // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
514             // Typically Delay, Duration and Alphafunction can be custom.
515             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
516                                                         AnimatableProperties.Position,
517                                                         ref positionTransitionComponents);
518
519             // Size
520             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
521                                                         AnimatableProperties.Size,
522                                                         ref sizeTransitionComponents);
523
524             // Add animators to the core Animation,
525
526             SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
527
528             SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
529
530             SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
531         }
532
533     } // class LayoutController
534 } // namespace Tizen.NUI