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