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