[NUI] Fix Process of LayoutController (#2841)
[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             if (root.Layout != null)
192             {
193                 // Determine measure specification for root.
194                 // The root layout policy could be an exact size, be match parent or wrap children.
195                 // If wrap children then at most it can be the root parent size.
196                 // If match parent then should be root parent size.
197                 // If exact then should be that size limited by the root parent size.
198                 float widthSize = GetLengthSize(parentWidth, root.WidthSpecification);
199                 float heightSize = GetLengthSize(parentHeight, root.HeightSpecification);
200                 MeasureSpecification.ModeType widthMode = GetMode(root.WidthSpecification);
201                 MeasureSpecification.ModeType heightMode = GetMode(root.HeightSpecification);
202
203                 if (root.Layout.NeedsLayout(widthSize, heightSize, widthMode, heightMode))
204                 {
205                     MeasureSpecification widthSpec = CreateMeasureSpecification(widthSize, widthMode);
206                     MeasureSpecification heightSpec = CreateMeasureSpecification(heightSize, heightMode);
207
208                     // Start at root with it's parent's widthSpecification and heightSpecification
209                     MeasureHierarchy(root, widthSpec, heightSpec);
210                 }
211
212                 float positionX = root.PositionX;
213                 float positionY = root.PositionY;
214                 // Start at root which was just measured.
215                 PerformLayout(root, new LayoutLength(positionX),
216                                      new LayoutLength(positionY),
217                                      new LayoutLength(positionX) + root.Layout.MeasuredWidth.Size,
218                                      new LayoutLength(positionY) + root.Layout.MeasuredHeight.Size);
219             }
220
221             if (SetupCoreAnimation() && OverrideCoreAnimation == false)
222             {
223                 PlayAnimation();
224             }
225         }
226
227         private float GetLengthSize(float size, int specification)
228         {
229             // exact size provided so match width exactly
230             return (specification >= 0) ? specification : size;
231         }
232
233         private MeasureSpecification.ModeType GetMode(int specification)
234         {
235             if (specification >= 0 || specification == LayoutParamPolicies.MatchParent)
236             {
237                 return MeasureSpecification.ModeType.Exactly;
238             }
239             return MeasureSpecification.ModeType.Unspecified;
240         }
241
242         private MeasureSpecification CreateMeasureSpecification(float size, MeasureSpecification.ModeType mode)
243         {
244             return new MeasureSpecification(new LayoutLength(size), mode);
245         }
246
247         /// <summary>
248         /// Entry point into the C# Layouting that starts the Processing
249         /// </summary>
250         private void Process(int id)
251         {
252             Vector2 windowSize = window.GetSize();
253             float width = windowSize.Width;
254             float height = windowSize.Height;
255
256             window.LayersChildren?.ForEach(layer =>
257             {
258                 layer?.Children?.ForEach(view =>
259                 {
260                     if (view != null)
261                     {
262                         FindRootLayouts(view, width, height);
263                     }
264                 });
265             });
266             windowSize.Dispose();
267             windowSize = null;
268         }
269
270         /// <summary>
271         /// Starts measuring the tree, starting from the root layout.
272         /// </summary>
273         private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
274         {
275             // Does this View have a layout?
276             // Yes - measure the layout. It will call this method again for each of it's children.
277             // No -  reached leaf or no layouts set
278             //
279             // If in a leaf View with no layout, it's natural size is bubbled back up.
280             root.Layout?.Measure(widthSpec, heightSpec);
281         }
282
283         /// <summary>
284         /// Performs the layouting positioning
285         /// </summary>
286         private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
287         {
288             root.Layout?.Layout(left, top, right, bottom);
289         }
290
291         /// <summary>
292         /// Play the animation.
293         /// </summary>
294         private void PlayAnimation()
295         {
296             NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
297             coreAnimation.Play();
298         }
299
300         private void AnimationFinished(object sender, EventArgs e)
301         {
302             // Iterate list of LayoutItem that were set for removal.
303             // Now the core animation has finished their Views can be removed.
304             if (itemRemovalQueue != null)
305             {
306                 foreach (LayoutItem item in itemRemovalQueue)
307                 {
308                     // Check incase the parent was already removed and the Owner was
309                     // removed already.
310                     if (item.Owner)
311                     {
312                         // Check again incase the parent has already been removed.
313                         ILayoutParent layoutParent = item.GetParent();
314                         LayoutGroup layoutGroup = layoutParent as LayoutGroup;
315                         if (layoutGroup != null)
316                         {
317                             layoutGroup.Owner?.RemoveChild(item.Owner);
318                         }
319
320                     }
321                 }
322                 itemRemovalQueue.Clear();
323                 // If LayoutItems added to stack whilst the core animation is playing
324                 // they would have been cleared here.
325                 // Could have another stack to be added to whilst the animation is running.
326                 // After the running stack is cleared it can be populated with the content
327                 // of the other stack.  Then the main removal stack iterated when AnimationFinished
328                 // occurs again.
329             }
330             NUILog.Debug("LayoutController AnimationFinished");
331             coreAnimation?.Clear();
332         }
333
334         /// <summary>
335         /// Set up the animation from each LayoutItems position data.
336         /// Iterates the transition stack, adding an Animator to the core animation.
337         /// </summary>
338         private bool SetupCoreAnimation()
339         {
340             // Initialize animation for this layout run.
341             bool animationPending = false;
342
343             NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
344
345             if (layoutTransitionDataQueue.Count > 0) // Something to animate
346             {
347                 if (!coreAnimation)
348                 {
349                     coreAnimation = new Animation();
350                 }
351                 coreAnimation.EndAction = Animation.EndActions.StopFinal;
352                 coreAnimation.Finished += AnimationFinished;
353
354                 // Iterate all items that have been queued for repositioning.
355                 foreach (LayoutData layoutPositionData in layoutTransitionDataQueue)
356                 {
357                     AddAnimatorsToAnimation(layoutPositionData);
358                 }
359
360                 animationPending = true;
361
362                 // transitions have now been applied, clear stack, ready for new transitions on
363                 // next layout traversal.
364                 layoutTransitionDataQueue.Clear();
365             }
366             return animationPending;
367         }
368
369         private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
370         {
371             // A removed item does not have a valid target position within the layout so don't try to position.
372             if (layoutPositionData.ConditionForAnimation != TransitionCondition.Remove)
373             {
374                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
375                             new Vector3(layoutPositionData.Left,
376                                         layoutPositionData.Top,
377                                         layoutPositionData.Item.Owner.Position.Z),
378                             positionTransitionComponents.Delay,
379                             positionTransitionComponents.Duration,
380                             positionTransitionComponents.AlphaFunction);
381
382                 NUILog.Debug("LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
383                                    " left:" + layoutPositionData.Left +
384                                    " top:" + layoutPositionData.Top +
385                                    " delay:" + positionTransitionComponents.Delay +
386                                    " duration:" + positionTransitionComponents.Duration);
387             }
388         }
389
390         private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
391         {
392             // Text size cant be animated so is set to it's final size.
393             // It is due to the internals of the Text not being able to recalculate fast enough.
394             if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
395             {
396                 float itemWidth = layoutPositionData.Right - layoutPositionData.Left;
397                 float itemHeight = layoutPositionData.Bottom - layoutPositionData.Top;
398                 // Set size directly.
399                 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
400             }
401             else
402             {
403                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
404                                          new Vector3(layoutPositionData.Right - layoutPositionData.Left,
405                                                      layoutPositionData.Bottom - layoutPositionData.Top,
406                                                      layoutPositionData.Item.Owner.Position.Z),
407                                          sizeTransitionComponents.Delay,
408                                          sizeTransitionComponents.Duration,
409                                          sizeTransitionComponents.AlphaFunction);
410
411                 NUILog.Debug("LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
412                                    " width:" + (layoutPositionData.Right - layoutPositionData.Left) +
413                                    " height:" + (layoutPositionData.Bottom - layoutPositionData.Top) +
414                                    " delay:" + sizeTransitionComponents.Delay +
415                                    " duration:" + sizeTransitionComponents.Duration);
416             }
417         }
418
419         void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
420         {
421             if (transitionsToAnimate?.Count > 0)
422             {
423                 foreach (LayoutTransition transition in transitionsToAnimate)
424                 {
425                     if (transition.AnimatableProperty != AnimatableProperties.Position &&
426                         transition.AnimatableProperty != AnimatableProperties.Size)
427                     {
428                         coreAnimation.AnimateTo(view,
429                                                  transition.AnimatableProperty.ToString(),
430                                                  transition.TargetValue,
431                                                  transition.Animator.Delay,
432                                                  transition.Animator.Duration,
433                                                  transition.Animator.AlphaFunction);
434
435                         NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
436                                            " Property:" + transition.AnimatableProperty.ToString() +
437                                            " delay:" + transition.Animator.Delay +
438                                            " duration:" + transition.Animator.Duration);
439                     }
440                 }
441             }
442         }
443
444         /// <summary>
445         /// Iterate transitions and replace Position Components if replacements found in list.
446         /// </summary>
447         private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
448                                                                  AnimatableProperties propertyToMatch,
449                                                                  ref TransitionComponents transitionComponentToUpdate)
450         {
451             foreach (LayoutTransition transition in sourceTransitionList)
452             {
453                 if (transition.AnimatableProperty == propertyToMatch)
454                 {
455                     // Matched property to animate is for the propertyToMatch so use provided Animator.
456                     transitionComponentToUpdate = transition.Animator;
457                 }
458             }
459         }
460
461         private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
462         {
463             AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
464             return new TransitionComponents(delay, duration, alphaFunction);
465         }
466
467         /// <summary>
468         /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
469         /// </summary>
470         private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
471         {
472             LayoutTransition positionTransition = new LayoutTransition();
473             LayoutTransition sizeTransition = new LayoutTransition();
474             TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
475
476             // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
477             // reposition to the new layout not to the insertion/removal of a sibling.
478             if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
479             {
480                 conditionForAnimators = TransitionCondition.LayoutChanged;
481             }
482
483             // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
484             TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 300);
485             TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 300);
486
487             bool matchedCustomTransitions = false;
488
489
490             TransitionList transitionsForCurrentCondition = new TransitionList();
491             // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
492
493             // Check if item to animate has it's own Transitions for this condition.
494             // If a key exists then a List of at least 1 transition exists.
495             if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
496             {
497                 // Child has transitions for the condition
498                 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
499             }
500
501             if (!matchedCustomTransitions)
502             {
503                 // Inherit parent transitions as none already set on View for the condition.
504                 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
505                 if (layoutParent != null)
506                 {
507                     // Item doesn't have it's own transitions for this condition so copy parents if
508                     // has a parent with transitions.
509                     LayoutGroup layoutGroup = layoutParent as LayoutGroup;
510                     TransitionList parentTransitionList;
511                     // Note TryGetValue returns null if key not matched.
512                     if (layoutGroup != null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
513                     {
514                         // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
515                         LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
516                                                                 transitionsForCurrentCondition);
517                     }
518                 }
519             }
520
521
522             // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
523
524             // There can only be one position transition and one size position, they will be replaced if set multiple times.
525             // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
526
527             // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
528             // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
529             // Typically Delay, Duration and Alphafunction can be custom.
530             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
531                                                         AnimatableProperties.Position,
532                                                         ref positionTransitionComponents);
533
534             // Size
535             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
536                                                         AnimatableProperties.Size,
537                                                         ref sizeTransitionComponents);
538
539             // Add animators to the core Animation,
540
541             SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
542
543             SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
544
545             SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
546         }
547
548     } // class LayoutController
549 } // namespace Tizen.NUI