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